Django REST Framework and FileField absolute url

32,665

Solution 1

Try SerializerMethodField

Example (untested):

class MySerializer(serializers.ModelSerializer):
    thumbnail_url = serializers.SerializerMethodField('get_thumbnail_url')

    def get_thumbnail_url(self, obj):
        return self.context['request'].build_absolute_uri(obj.thumbnail_url)

The request must available to the serializer, so it can build the full absolute URL for you. One way is to explicitly pass it in when the serializer is created, similar to this:

serializer = MySerializer(account, context={'request': request})

Solution 2

Thanks, shavenwarthog. Your example and documentation reference helped enormously. My implementation is slightly different, but very close to what you posted:

from SomeProject import settings

class ProjectSerializer(serializers.HyperlinkedModelSerializer):

    thumbnail_url = serializers.SerializerMethodField('get_thumbnail_url')

    def get_thumbnail_url(self, obj):
        return '%s%s' % (settings.MEDIA_URL, obj.thumbnail)

    class Meta:
        model = Project
        fields = ('id', 'url', 'name', 'thumbnail_url') 

Solution 3

To get the url of a file which uses FileField you can just call the url attribute of the FieldFile (this is the file instance not the field), it use the Storage class to determine the url for this file. It's very straightforward if you are using a external storage like Amazon S3 or if your storage changes.

The get_thumbnail_url would be like this.

def get_thumbnail_url(self, obj):
    return obj.thumbnail.url

You can also use it in the template this way:

{{ current_project.thumbnail.url }}

Solution 4

I found it annoying to write the same code for a serialized method field. If you have set correctly the MEDIA_ROOT to your S3 bucket URL, you can add a field to the serializer like:

class ProjectSerializer(serializers.ModelSerializer):
    logo_url = serializers.URLField(read_only=True, source='logo.url')

    class Meta:
        model = Project

logo is an ImageField in the model. it must not be nullable in order to avoid errors like ValueError: The 'img' attribute has no file associated with it.

I only use .build_absolute_uri in a serializer methodfield to return absolute urls that use other views in my API. for example, in my project there is an URL /webviews/projects/<pk> that shows, a title and a button that collects some user input (i.e. not exactly what you would do with suffixes, as it's not a plain representation of the resource but includes some logic instead). the end point /projects/<pk>/ contains a field "webview_url" ponting there, which is generated with SerializerMethodField. it's not media.

Solution 5

No need for any overrides or customizations. DRF handles it automatically. Take a look at to_representation method of FileField:

def to_representation(self, value):
    if not value:
        return None

    use_url = getattr(self, 'use_url', api_settings.UPLOADED_FILES_USE_URL)

    if use_url:
        if not getattr(value, 'url', None):
            # If the file has not been saved it may not have a URL.
            return None
        url = value.url
        request = self.context.get('request', None)
        if request is not None:
            return request.build_absolute_uri(url)
        return url
    return value.name

Note that it won't work if the context of the serializer is not set properly. If you're using ViewSets, no worries, everything is done silently but if you're instantiating the serializer manually you have to pass in the request in the context.

context = {'request': request}
serializer = ExampleSerializer(instance, context=context)
return Response(serializer.data)

https://www.django-rest-framework.org/community/3.0-announcement/#file-fields-as-urls

Share:
32,665

Related videos on Youtube

Mark Semsel
Author by

Mark Semsel

iOS developer with background in Business Intelligence and Finance Technology, now building mobile apps and the web services to support them. Some day I will build apps for aviators.

Updated on July 09, 2022

Comments

  • Mark Semsel
    Mark Semsel almost 2 years

    I've defined a simple Django app that includes the following model:

    class Project(models.Model):
        name = models.CharField(max_length=200)
        thumbnail = models.FileField(upload_to='media', null=True)
    

    (Technically yes, that could have been an ImageField.)

    In a template, it's easy enough to include the MEDIA_URL value (duly coded in settings.py) as a prefix to the thumbnail URL. The following works fine:

    <div id="thumbnail"><img src="{{ MEDIA_URL }}{{ current_project.thumbnail }}" alt="thumbnail" width="400" height="300" border="0" /></div>
    

    Using DRF, I've defined a HyperlinkedModelSerializer descendant called ProjectSerializer:

    class ProjectSerializer(serializers.HyperlinkedModelSerializer):
    
        class Meta:
            model = Project
            fields = ( 'id' ,'url', 'name', 'thumbnail')
    

    And I've defined a very straightforward ModelViewSet descendant:

    class ProjectViewSet(viewsets.ModelViewSet):
        queryset = Project.objects.all()
        serializer_class = ProjectSerializer
    

    A sample of the resulting JSON looks like this:

    {
        "id": 1, 
        "url": "http://localhost:8000/api/v1/projects/1/", 
        "name": "Institutional", 
        "thumbnail": "media/institutional_thumb_1.jpg"
    }
    

    I have not yet been able to figure out how to provide a thumbnail field that includes the full url to the image in my project's JSON representation.

    I would think that I would need to create a custom field in the ProjectSerializer, but have not been successful.

  • François Constant
    François Constant over 9 years
    You don't need to import setting.MEDIA_URL. Simply return: obj.thumbnail.url
  • Meetarp
    Meetarp over 9 years
    How do you modify this to make it work with multiple images? Say he had a class called Preview that has a ForeignKey relation to Project. So Project has a preview_set and he wanted to achieve the same thing for each preview he had for the project. How do you achieve the same thing for each preview?
  • sthzg
    sthzg over 9 years
    You don't need to pass the request. It is available already in self.context['view'].request.
  • Tom Manterfield
    Tom Manterfield over 9 years
    The problem with this is that SerializerMethodField is read-only
  • Serrano
    Serrano about 9 years
    In my case, just setting context={'request': request} when instantiating a serializer was enough to get the absolute URL. No need for SerializerMethodField.
  • dnaranjo
    dnaranjo over 8 years
    as @SerranoPereira said, for me it worked only with the context
  • T.Coutlakis
    T.Coutlakis over 8 years
    Isn't it supposed to be obj.thumbnail.url, instead of self.thumbnail_url
  • Lorenzo Peña
    Lorenzo Peña over 5 years
    This won't work if MEDIA_URL has a different domain.
  • Moritz
    Moritz about 5 years
    The important bit here is to use FileField (not URLField) and add the request to the context.
  • Nwawel A Iroume
    Nwawel A Iroume almost 5 years
    got issue with ListAPIView and your solution with context is working! thanks alot
  • Juan García
    Juan García about 4 years
    This answer made me realize that using context={'request': request} as serializer parameter, the file field will return the absolute URL if it's accessed from another domain.