#32243 closed Cleanup/optimization (fixed)
Clarify docs on manually setting and saving a FileField.
| Reported by: | Gordon Wrigley | Owned by: | Joshua Massover |
|---|---|---|---|
| Component: | Documentation | Version: | 3.1 |
| Severity: | Normal | Keywords: | |
| Cc: | Joshua Massover | Triage Stage: | Ready for checkin |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
The documentation talks about how to set a filefield on a new object and how to set a filefield using a form but I couldn't find anything that talked about how to directly set a filefield on an existing object.
https://docs.djangoproject.com/en/3.1/topics/http/file-uploads/#handling-uploaded-files-with-a-model
Maybe those docs could use some extra detail on how you can set an existing file field by either assigning a ContentFile (with a name) to the field and saving the instance, or by calling save on the field and passing a ContentFile and a name. And particularly how the name is required in both cases and will be passed through upload_to.
Further figuring this out for myself was substantially complicated by the following behaviour:
In [24]: e=MyModel.objects.first() In [25]: e.my_file Out[25]: <FieldFile: None> In [26]: e.my_file=ContentFile(content=b"fred") In [27]: e.save() In [28]: e=MyModel.objects.first() In [29]: e.my_file Out[29]: <FieldFile: None> In [30]: e.my_file=ContentFile(content=b"bob", name="bob.txt") In [31]: e.save() In [32]: e=MyModel.objects.first() In [33]: e.my_file Out[33]: <FieldFile: files/5bc2fe4c-4262-4134-9397-c740de5a7edf/bob.txt> In [34]: e.my_file.open().read() Out[34]: b'bob'
Particularly 26-29 where setting the filefield to a ContentFile with no name and then saving is effectively just ignored with no error.
Is this expected behaviour?
Change History (18)
comment:1 by , 5 years ago
| Component: | Uncategorized → Documentation |
|---|---|
| Type: | Uncategorized → Cleanup/optimization |
comment:2 by , 5 years ago
| Triage Stage: | Unreviewed → Accepted |
|---|
follow-up: 5 comment:3 by , 5 years ago
| Summary: | Saving filefields. → Clarify docs on manually setting and saving a FileField. |
|---|
comment:4 by , 5 years ago
| Owner: | changed from to |
|---|---|
| Status: | new → assigned |
follow-up: 6 comment:5 by , 5 years ago
Hi Carlton,
As the file is not being saved without the name attribute of the file, shouldn't we generate the name property randomly if not provided by the user?
If yes, I will update the relevant codes implementing random name generation, otherwise, I will update the documentation mentioning name is required. However, I think we should not enforce the user to provide the name attribute. I would prefer to update the code.
comment:6 by , 5 years ago
Replying to Hasanul Islam:
Hi Carlton,
As the file is not being saved without thenameattribute of the file, shouldn't we generate thenameproperty randomly if not provided by the user?
If yes, I will update the relevant codes implementing random name generation, otherwise, I will update the documentation mentioning
nameis required. However, I think we should not enforce the user to provide thenameattribute. I would prefer to update the code.
TL;DR: Allow ContentFiles with a blank/None name. In FileField.save(), add a check after the generate_filename() call to make sure the file actually has a name once we're ready to save it to disk.
When saving the model it seems to still use the upload_to argument. If you pass something for upload_to it will overwrite whatever was passed for name.
Here's an example of my use case. A user can upload a picture through the admin panel. Whenever a new picture is uploaded I create a thumbnail for it.
def photo_upload_path(instance, original_filename):
filename = hashlib.sha256(str(time.time()).encode("utf-8")).hexdigest()
filename = filename[:16]
return f"photos/{filename}.jpg"
def thumbnail_upload_path(instance, original_filename):
filename = os.path.basename(instance.photo.name)
return f"photos/thumbnails/{filename}"
class MyModel(models.Model):
photo = models.ImageField(upload_to=photo_upload_path)
# Hidden in Django admin. Managed by us.
photo_thumbnail = models.ImageField(upload_to=thumbnail_upload_path)
def save(self):
img = Image.open(self.photo.path)
img.thumbnail(settings.THUMBNAIL_SIZE)
contents = io.BytesIO()
img.save(contents, "JPEG")
self.photo_thumbnail = ContentFile(contents.getvalue(), name="foo")
super.save()
In this case, the photo gets renamed to photos/<hash>.jpg, and I want thumbnails to go to photos/thumbnails/<hash>.jpg.
I resize the image, save its contents to a BytesIO object, and create the ContentFile object. However I have to pass in a bogus name for the ContentFile even though it will use the upload_to to overwrite it. If I pass an empty name Django seems to throw the file away and it doesn't get saved.
comment:7 by , 4 years ago
#33388 was closed as a duplicate to this one. I included an example but not in the Using files in models as Carlton suggested, I put it in Handling uploaded files Glad to move it where ever if this does not suffice.
comment:8 by , 4 years ago
| Cc: | added |
|---|---|
| Has patch: | set |
| Owner: | changed from to |
comment:13 by , 4 years ago
| Patch needs improvement: | unset |
|---|
comment:14 by , 4 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
Hi Gordon. Thanks for the report.
I agree this is an area that folks find confusing. Happy to review suggested changes.
I think the right place for a clarification would be in the Using files in models section of the Files topic documentation.
Perhaps a review of the tests in tests/files to ensure we've covered all the relevant cases would also be in order.