#32991 closed Bug (invalid)
Change in behavior of FileField.url between versions 2.2.16 -> 3.2.5; returned 'relative' value contains leading slash
Reported by: | Elchin Mammadov | Owned by: | nobody |
---|---|---|---|
Component: | contrib.staticfiles | Version: | 3.2 |
Severity: | Normal | Keywords: | FileField.url |
Cc: | Florian Apolloner | Triage Stage: | Unreviewed |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | yes | UI/UX: | no |
Description
Going through an upgrade of Django from version 2.2.16 to 3.2.5, believe I have found a regression in FileField.url
I have searched all the release notes between these two versions for any mentions of "url" but didn't find changes being mentioned to this behavior. Some of the recent release notes for Django 2.2 (2.2.21 and 2.2.23) mention changes to FileField, maybe this is when the regression was introduced.
Details of the issue:
Using the same model FileField definition and settings in both versions.
In 2.2.16 FileField.url returns a relative URL without a leading slash.
In 3.2.5 the same call returns a relative URL with a leading slash.
How to reproduce:
Using the following Model definition:
from django.contrib.gis.db import models class TestModel(models.Model): file_field = models.FileField( upload_to='photos/%Y/%m/%d', max_length=255, null=True, help_text=u"Test" )
Using manage.py shell:
In 2.2.16
>>> test_model = models.TestModel.objects.create() >>> f = open('setup.py') >>> from django.core.files.base import File >>> test_model.file_field.save('new_name', File(f)) >>> test_model.file_field.url 'photos/2021/08/05/new_name' >>> import django >>> django.__version__ '2.2.16'
In 3.2.5:
>>> from vault import models >>> test_model = models.TestModel.objects.create() >>> f = open('setup.py') >>> from django.core.files.base import File >>> test_model.file_field.save('new_name', File(f)) >>> test_model.file_field.url '/photos/2021/08/05/new_name' >>> import django >>> django.__version__ '3.2.5'
The problem with the leading slash is that it is now treated as an absolute URL.
Change History (6)
comment:1 by , 3 years ago
Component: | File uploads/storage → contrib.staticfiles |
---|---|
Resolution: | → invalid |
Status: | new → closed |
comment:2 by , 3 years ago
Cc: | added |
---|
comment:3 by , 3 years ago
Interesting, this is honestly a somewhat unexpected result. While the docs do say (https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.fields.files.FieldFile.url)
A read-only property to access the file’s relative URL by calling the url() method of the underlying Storage class.
that this seems to be a relative URL, it also says that it calls Storage.url
which to the best of my knowledge usually always returned an absolute URL (https://docs.djangoproject.com/en/3.2/ref/files/storage/#django.core.files.storage.Storage.url) -- well technically it returned whatever MEDIA_URL + "/" + path
is (more or less):
def url(self, name): if self.base_url is None: raise ValueError("This file is not accessible via a URL.") url = filepath_to_uri(name) if url is not None: url = url.lstrip('/') return urljoin(self.base_url, url)
so the only way I could imagine this to ever return a relative URL is by settings MEDIA_URL
to an empty string. What did you set MEDIA_URL
to (and if it is empty, then why)
comment:4 by , 3 years ago
MEDIA_URL
is indeed an empty string in our settings, which is also the default value.
comment:5 by , 3 years ago
It is true that it is the default value but that was never a useful value to begin with if you deployed your site. Assume the following (with the default MEDIA_ROOT
): you are on page http://example.com/sub1/sub2 and request the URL of my_image.jpg
which would yield photos/123/my_image.jpg
. Putting that into an <img>
tag would result in a final URL of http://example.com/sub1/sub2/photos/123/my_image.jpg. Do the same on http://example.com/sub3 instead and you'd get http://example.com/sub3/photos/123/my_image.jpg. So how did you ever serve your images with this setup?
comment:6 by , 3 years ago
I see your point about the default value of MEDIA_URL
not being useful when using .url
for serving static content from the site. In this use case MEDIA_URL
must be configured. This may be the most common use case.
The other use case to consider is when uploading files for consumption by the web application. After the files have been uploaded, the application has a need to find a relative path to the uploaded file via a model. And Django FAQ documentation actually describes how to achieve this https://docs.djangoproject.com/en/3.2/faq/usage/#how-do-i-use-image-and-file-fields:
All that will be stored in your database is a path to the file (relative to MEDIA_ROOT). You’ll most likely want to use the convenience url attribute provided by Django
What is returned by .url
attribute is also a relative path to the file, which the application can then access via the FileField on the model. If there is no static content being served, then MEDIA_URL
will not be set (most likely). This is the use case and the setup used in our application. We are not using Django templates or serving any static content. In our application there are only a few places where .url
attribute is being used, so it is a simple change for us. There are other attributes provided by FileField that can be used in lieu of .url
.
This is an intended change made in c574bec0929cd2527268c96a492d25223a9fd576 (see #25598, and comment).