Opened 4 months ago

Closed 4 months ago

Last modified 4 months ago

#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 Changed 4 months ago by Mariusz Felisiak

Component: File uploads/storagecontrib.staticfiles
Resolution: invalid
Status: newclosed

This is an intended change made in c574bec0929cd2527268c96a492d25223a9fd576 (see #25598, and comment).

comment:2 Changed 4 months ago by Mariusz Felisiak

Cc: Florian Apolloner added

comment:3 Changed 4 months ago by Florian Apolloner

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 Changed 4 months ago by Elchin Mammadov

MEDIA_URL is indeed an empty string in our settings, which is also the default value.

comment:5 Changed 4 months ago by Florian Apolloner

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 Changed 4 months ago by Elchin Mammadov

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.

Last edited 4 months ago by Elchin Mammadov (previous) (diff)
Note: See TracTickets for help on using tickets.
Back to Top