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?
--
Ticket URL: <https://code.djangoproject.com/ticket/32243>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* type: Uncategorized => Cleanup/optimization
* component: Uncategorized => Documentation
Comment:
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
[https://docs.djangoproject.com/en/3.1/topics/files/#using-files-in-models
Using files in models] section of the Files topic documentation.
Perhaps a review of the tests in
[https://github.com/django/django/tree/master/tests/files tests/files] to
ensure we've covered all the relevant cases would also be in order.
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:1>
* stage: Unreviewed => Accepted
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:2>
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:3>
* owner: nobody => Hasanul Islam
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:4>
Comment (by Hasanul Islam):
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.
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:5>
Comment (by Jesse):
Replying to [comment:5 Hasanul Islam]:
> 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.
**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.
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:6>
Comment (by Joshua Massover):
#33388 was closed as a duplicate to this one. I included an example but
not in the [https://docs.djangoproject.com/en/3.1/topics/files/#using-
files-in-models Using files in models] as Carlton suggested, I put it in
[https://docs.djangoproject.com/en/3.1/topics/http/file-uploads
/#handling-uploaded-files-with-a-model Handling uploaded files with a
model]. Glad to move it where ever if this does not suffice.
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:7>
* cc: Joshua Massover (added)
* owner: Hasanul Islam => Joshua Massover
* has_patch: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:8>
* needs_better_patch: 0 => 1
Comment:
See [https://github.com/django/django/pull/15245#issuecomment-1002670888
comment].
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:9>
Comment (by Joshua Massover):
Replying to [comment:9 Mariusz Felisiak]:
> See [https://github.com/django/django/pull/15245#issuecomment-1002670888
comment].
Updated the PR
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:10>
Comment (by Joshua Massover):
PR updated from more feedback
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:11>
Comment (by Joshua Massover):
Updated again from more feedback!
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:12>
* needs_better_patch: 1 => 0
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:13>
* stage: Accepted => Ready for checkin
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:14>
* status: assigned => closed
* resolution: => fixed
Comment:
In [changeset:"c9d6e3595cfd0aa58cde1656bd735ecfcd7a872b" c9d6e35]:
{{{
#!CommitTicketReference repository=""
revision="c9d6e3595cfd0aa58cde1656bd735ecfcd7a872b"
Fixed #32243 -- Added docs examples for manually saving Files.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:15>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"76c80d96f3828a5a3f66842932a5624674ba99a2" 76c80d9]:
{{{
#!CommitTicketReference repository=""
revision="76c80d96f3828a5a3f66842932a5624674ba99a2"
[4.0.x] Fixed #32243 -- Added docs examples for manually saving Files.
Backport of c9d6e3595cfd0aa58cde1656bd735ecfcd7a872b from main
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:16>
Comment (by GitHub <noreply@…>):
In [changeset:"25514b604a64686ba603bf10a8a63390dc38b79d" 25514b60]:
{{{
#!CommitTicketReference repository=""
revision="25514b604a64686ba603bf10a8a63390dc38b79d"
Refs #32243 -- Fixed typo in docs/topics/files.txt.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:17>
Comment (by Mariusz Felisiak <felisiak.mariusz@…>):
In [changeset:"3714b44142dbf5b55b21530b2cc6d9cc2751da68" 3714b44]:
{{{
#!CommitTicketReference repository=""
revision="3714b44142dbf5b55b21530b2cc6d9cc2751da68"
[4.0.x] Refs #32243 -- Fixed typo in docs/topics/files.txt.
Backport of 25514b604a64686ba603bf10a8a63390dc38b79d from main
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/32243#comment:18>