Consider the following model:
STOCK_IMAGE_DIR = os.path.join(settings.MEDIA_ROOT, 'stock_images')
class Product(models.Model):
stock_image = models.FilePathField(path=STOCK_IMAGE_DIR, default='image.png')
When accessing the stock_image of a product, it only returns the name of the image:
>>> p = Product.objects.first()
>>> p.stock_image
'image.png'
How can I output the entire path of the file?
import os
p = Product.objects.first()
print(os.path.join(Product._meta.get_field('stock_image').path, p.stock_image))
Or
print(os.path.join(STOCK_IMAGE_DIR, p.stock_image))
Or, if you store the path in the class you can add a property for getting the full path
class Product(models.Model):
STOCK_IMAGE_DIR = os.path.join(settings.MEDIA_ROOT, 'stock_images')
stock_image = models.FilePathField(path=STOCK_IMAGE_DIR, default='image.png')
#property
def stock_image_path(self):
return os.path.join(self.STOCK_IMAGE_DIR, self.stock_image)
Then you can just use the property
p = Product.objects.first()
print(p.stock_image_path)
While #IainShelvington provides a solution to obtain the path, you might want to use a FileField field [Django-doc], or if you work with images, like the name stock_image suggests, with an ImageField field [Django-doc].
If you model this like:
STOCK_IMAGE_DIR = os.path.join(settings.MEDIA_ROOT, 'stock_images')
class Product(models.Model):
stock_image = models.ImageField(upload_to=STOCK_IMAGE_DIR, default='image.png')
then the some_product.stock_image will be an ImageFieldFile [GitHub]. This is an object that mimics a File, and has for example a path attribute to obtain the path where the file is stored:
some_product.stock_image.path
Furthermore a FileField and ImageField make it more convenient to work with forms where you upload images, and furthermore these have a .url attribute to obtain the URL such that the server can serve media files.
Related
what else do I need to add to that "file = models.FileField()"
this is what I have done but am still not getting any results, why that?
class Course(models.Model):
TOPIC_CHOICES = (
("History", "History"),
("Chemistry", "Chemistry"),
("Computer", "Computer")
)
lecturer = models.ForeignKey(Lecturer, on_delete=models.CASCADE)
category = models.CharField(choices=TOPIC_CHOICES, max_length=100)
topic = models.CharField(max_length=250)
file = models.FileField()
date_created = models.DateTimeField(default=datetime.now)
def __str__(self):
return f"{self.lecturer}: {self.topic}"
According to Django documentation, FileField takes two optional arguments.
upload_to: Sets the upload directory. The value of this argument can have several types. It can be String, Path, or a callable function. Here is an example:
upload = models.FileField(upload_to='uploads/')
If you want to define a function for this argument which returns the upload directory, you have to define it based on Django's specification of such function. The function should have the instance and filename arguments. Here is an example:
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return 'user_{0}/{1}'.format(instance.user.id, filename)
class MyModel(models.Model):
upload = models.FileField(upload_to=user_directory_path)
storage: A storage object, or a callable which returns a storage object. This argument is used to specify a storage setting for your file-upload field. This argument enables you to choose the appropriate storage environment at runtime.
from django.conf import settings
from django.db import models
from .storages import MyLocalStorage, MyRemoteStorage
def select_storage():
return MyLocalStorage() if settings.DEBUG else MyRemoteStorage()
class MyModel(models.Model):
my_file = models.FileField(storage=select_storage)
Another use-case of this argument is having different storage environments for different types of files.
from django.conf import settings
from django.db import models
from .storages import LargeFilesStorage
class MyModel(models.Model):
my_file = models.FileField(storage=LargeFilesStorage())
As these arguments are optional, you can instantiate a FileField without them. The default values for these arguments are: upload_to='', storage=None
I have a standard Django admin form.
When the file is uploaded in the file selection box, I would like to leave only the file name, instead of the full path in the static.
Is this possible without editing the template, but only by overriding a formset, form, or model methods?
In the picture above the button, change the display of the line "ws_document_studygroup/2021/2/123123123123123123png" to "123123123123123123png". But without changing the real path in the model.
Please advise the best practice.
You could try adding an #property getter to your model class:
import os
class Document:
def __init__(self, full_path: str):
self.full_path = full_path
#property
def filename(self) -> str:
return os.path.basename(self.full_path)
The os.path.basename function takes a path and returns the path segment following the final slash character (in other words, the filename).
>>> doc = Document("ws_document_studygroup/2021/2/123123123123123123.png")
>>> doc.filename
123123123123123123.png
So all you need to do is use this property in your template.
I found different resolve this issue.
I override ClearableFileInput widget and clearable_file_input.html template
from django.forms import ClearableFileInput
import os
class CustomClearableFileInput(ClearableFileInput):
template_name = 'custom_clearable_file_input.html'
def format_value(self, value):
"""
Return the file object if it has a defined url attribute.
"""
if self.is_initial(value):
setattr(value, 'file_short_name', os.path.basename(str(value)))
return value
and in template file change string to:
{{ widget.value.file_short_name }}
And just add widget to file field:
class StudyGroupDocumentsForm(forms.ModelForm):
file = forms.FileField(widget=CustomClearableFileInput)
class Meta:
model = StudyGroupDocuments
fields = '__all__'
And add the form to Inline.
Hope it is helpful for someone.
I'm trying to create a folder and upload an image to in it. My model is similar to this:
class XYZ(models.Model):
def code(self):
syms = ['#','#','$','%','&','*','+','-']
return ''.join(x+random.choice(syms) for x in [self.name[-2:],self.name[2:]])
def make_folder(self):
os.mkdir(os.getcwd()+'/XYZ/'+folder)
def save(self):
self.make_folder()
super(XYZ,self).save(*args,**kwargs)
name = models.CharField(max_length=20)
folder = property(code)
image = models.ImageField(upload_to='HELP_HERE')
I've tried using folder, self.folder, property(code) but it says it isn't defined, how do I access this attribute? Is this the correct approach?
I've also set in the configs.py my MEDIA_URL and MEDIA_ROOT
I think a more simple and understandable way to do this would be to avoid using the inner class function and doing something like this:
# Reference this function outside of your model class.
def xyz_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/xyz_<id>/<filename>
return 'xyz_{0}/{1}'.format(instance.id, filename)
Then, on your models, reference the function in your upload_to field like so:
class XYZ(models.Model):
image = models.ImageField(upload_to=xyz_directory_path)
With your media config set up, this should work. There's a really nice and simple explanation about file handling here.
I have some images inside the static directory and want to create a model that has a ImageField. I want to set the default field to any one of those images. I have tried using this -
def randomImage():
return ImageFile('media/blog/image/' + str(random.randrange(1, 15, 1)) + '.jpg')
# ----------------------- Model for each post in the blog-------------------
class Post(models.Model):
heading = models.CharField(max_length=150)
author = models.ForeignKey(User)
postBody = models.TextField()
postDate = models.DateTimeField('posting date')
postImage = models.ImageField(upload_to='media/blog/image/'+str(int(time.time())), default=randomImage)
Here I'm taking some assumptions,
1. Your default images are inside the static directory
2. Inside the static directory, all files are images
What is the major trick here ?
Django needs a valid file path only to create or update a file entry (image is also a file). So, what we're doing here is, list out all the files (assuming those are only images) and picking up one entry randomly using random.choice() and retun an absoulute path (something like static/my_img.jpg) to the default argument
import time
from random import choice
from os.path import join as path_join
from os import listdir
from os.path import isfile
def random_img():
dir_path = 'static'
files = [content for content in listdir(dir_path) if isfile(path_join(dir_path, content))]
return path_join(dir_path, choice(files))
class Post(models.Model):
# other fields
postImage = models.ImageField(
upload_to='media/blog/image/' + str(int(time.time())),
default=random_img)
UPDATE-1
I've created a minimal example in Django 2.1.1 which can be found in follwing git repo
Repopsitory link -> django2X
1. clone the repository,create a virtual env and install the dependencies (provided a requirements.txt file)
2.create a new superuser or use mine (username -> admin, pass-> jerin123#)
3.run the server and login to django admin : (screenshot-1)
4. Create a new Post instance (again and again)
That's it
UPDATE-2
I've made few changes in my minimal example, which are
1. Created a simple object creation (here, the Post model object) while every django startup (checkout sample/app.py)
2. Added MEDIA_ROOT and MEDIA_URL to the settings.py file
Now, start your project (it will create 3 objects per every startup) and go to http://127.0.0.1:8000/admin/sample/post/. Then open any instance and click on the image link (screenshot-2) and it will open the image (screenshot-3)
My solution is to override the model save method and check if the model is being created for the first time and also check if the postImage image field is empty. If so populate the postImage field with contents of a Radom image. Django will handle the rest
If we use the path of the Radom image directly in our model we will end up like serving some of the post model files from the media folder and some other from the static directory which is not recommended. Instead, we feed the image file content to the postImage field and Django will save the image to media folder and thus we can serve all our model images from media folder itself. Wola
Code
The following code is tested in Python 3.6
Please add the code to your models.py
from pathlib import Path
from random import randint
import time
from django.core.files import File
from django.db import models
allowed_image_extensions = ['.jpg', '.png', '.jpeg']
def get_file_extension(file_path):
return Path(file_path).suffix
def get_files_in_directory(directory, absolute_path=False):
from os import listdir
from os.path import isfile
only_files = [f for f in listdir(directory) if isfile("{}/{}".format(directory, f))]
if not absolute_path:
return only_files
else:
return ["{}/{}".format(directory, file_) for file_ in only_files]
def get_random_image_from_directory(directory_path, image_extension=None):
files_in_directory_path = get_files_in_directory(directory_path, absolute_path=True)
if image_extension:
allowed_file_types = [image_extension]
else:
allowed_file_types = allowed_image_extensions
# filter out files of type other than required file types
filtered_files_list = [_file for _file in files_in_directory_path if
get_file_extension(_file).lower() in allowed_file_types]
random_index = randint(0, len(filtered_files_list) - 1)
random_file_path = filtered_files_list[random_index]
random_file_content = File(open(random_file_path, 'rb'))
return random_file_content
def get_post_image_path(instance, filename):
path_first_component = 'posts'
ext = get_file_extension(filename)
current_time_stamp = int(time.time())
file_name = '{}/posts_{}_{}{}'.format(path_first_component, instance.id, current_time_stamp, ext)
full_path = path_first_component + file_name
return full_path
class Post(models.Model):
heading = models.CharField(max_length=150)
author = models.ForeignKey(User)
postBody = models.TextField()
postDate = models.DateTimeField('posting date')
postImage = models.ImageField(blank=True, null=True, upload_to=get_post_image_path)
# override model save method
def save(self, *args, **kwargs):
# check model is new and postImage is empty
if self.pk is None and not self.postImage:
random_image = get_random_image_from_directory(settings.STATIC_ROOT)
self.postImage = random_image
random_image.close()
super(Post, self).save(*args, **kwargs)
Also no need to set ‘/media’ in upload_to path. Django will read media path from settings variable
The best practice is to move those set of default images out of static directory to another folder probably another folder with name resources or any another meaningful name since the static directory contents will change frequently as the project grows
I have a model:
class Photo(models.Model):
filename = models.CharField(max_length=240)
And a corresponding MySQL table, filled with filenames (copied from an existing table).
In the future I may want to upload new photos to the model via admin. Is it possible to evolve the current model into something with ImageFields and integrate my legacy data?
It is possible, assuming the current filename field in your model contains the full path to the actual file, you can add a new field (ImageField) to your model and migrate it using South, then write a script to update your data.
A skeleton example,
from django.core.files import File
# assuming your updated model looks like:
# class Photo(models.Model):
# filename = models.CharField(max_length=240)
# image = models.ImageField(max_length=240)
photos = Photo.objects.all()
for p in photos:
f = open(p.filename)
myimage = File(f)
p.image.save(image_name, myimage) # name, content
And then remove the old filename field via South. Take a look at Django's FileField first for more information, since ImageField essentially inherits all of the former's attribute and methods. (see https://docs.djangoproject.com/en/1.3/ref/models/fields/#django.db.models.FileField)