Django REST框架和FileField绝对URL

38

我已经定义了一个简单的Django应用程序,其中包括以下模型:

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

从技术上讲,那可能是一个ImageField。

在模板中,将MEDIA_URL值(在settings.py中正确编码)作为缩略图URL的前缀包含进去很容易。以下方式可以正常工作:

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

我使用DRF定义了一个 HyperlinkedModelSerializer 的子类,名为 ProjectSerializer:

class ProjectSerializer(serializers.HyperlinkedModelSerializer):

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

我定义了一个非常简单的ModelViewSet后代:

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

转换后的JSON样本如下:

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

我还没有找到方法在我的项目JSON表示中提供包含图像完整URL的缩略图字段。

我认为我需要在ProjectSerializer中创建自定义字段,但尚未成功。

11个回答

61

尝试使用SerializerMethodField

示例(未经测试):

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)

请求必须是可用于序列化器的,这样它才能为您构建完整的绝对URL。一种方式是在创建序列化器时明确传递它,类似于:

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

1
您不需要传递“request”。它已经在“self.context ['view'] .request”中可用。 - sthzg
3
问题在于SerializerMethodField是只读的。 - Tom Manterfield
24
对于我的情况,只需在实例化序列化器时设置 context={'request': request} 就足以获得绝对 URL,无需使用 SerializerMethodField - Serrano
2
难道不应该是 obj.thumbnail.url,而不是 self.thumbnail_url 吗? - T.Coutlakis
2
这个答案让我意识到,在使用context={'request': request}作为序列化器参数时,如果从另一个域访问文件字段,则会返回绝对URL。 - Juan García
显示剩余3条评论

12

谢谢你,shavenwarthog。你的示例和文档参考对我帮助很大。我的实现略有不同,但非常接近于你发布的内容:

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') 

3
您不需要导入setting.MEDIA_URL。只需返回:obj.thumbnail.url即可。 - François Constant

7

要获取使用FileField的文件的URL,您只需调用FieldFile的url属性(这是文件实例而不是字段),它使用Storage类确定此文件的URL。如果您使用外部存储,例如Amazon S3或者您的存储更改,则非常直接。

get_thumbnail_url将如下所示。

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

您可以这样在模板中使用它:


{{ current_project.thumbnail.url }}

6
不需要任何覆盖或自定义。DRF会自动处理。看一下FileField的to_representation方法(source)。
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

请注意,如果序列化器的上下文没有正确设置,它将无法正常工作。如果您正在使用ViewSet,那就没问题了,一切都会悄无声息地完成;但是如果您手动实例化序列化器,就必须在上下文中传入请求对象。
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


这里的重点是使用FileField(而不是URLField)并将请求添加到上下文中。 - Moritz
使用ListAPIView遇到了问题,你提供的带上下文的解决方案很有效!非常感谢。 - Nwawel A Iroume

6

我发现为序列化的方法字段编写相同的代码很烦人。 如果您已经将MEDIA_ROOT正确设置为您的S3存储桶URL,您可以像下面这样向序列化器添加一个字段:

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

    class Meta:
        model = Project

logo是模型中的一个ImageField。为了避免出现ValueError:The 'img' attribute has no file associated with it.等错误,它不能是可空的。

我只在序列化器的方法字段中使用.build_absolute_uri来返回使用我的API中其他视图的绝对URL。例如,在我的项目中有一个URL /webviews/projects/<pk> 显示一个标题和一个收集一些用户输入的按钮(即不完全是资源的普通表示,而是包括一些逻辑)。端点/projects/<pk>/包含一个指向那里的“webview_url”字段,该字段是使用SerializerMethodField生成的。它不是媒体。


1

在调用模型序列化器类对对象进行序列化时,只需传递"context={'request': request}"参数即可。您可以按照下面的片段获取完整的URL字段。

serialized_object = serializers.MySerializer(data, many=true, context={'request': request})

1
如果您在使用Viewsets时无法访问序列化程序中的额外上下文,请尝试在urls.py中注册路由器并使用basename:

router.register('projects', ProjectViewSet, basename='project')

你可以使用 build_absolute_uri:

def get_thumbnail_url(self, obj):
    return self.context.get('request').build_absolute_uri(obj.thumbnail.url)


1

只需传递上下文和请求对象即可,如果您正在使用@api_view

serializer = CustomerSerializer(customer, context={"request": request})

对于ViewSet用户,使用get_serializer_context方法。
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer

def get_serializer_context(self):
    return {'request': self.request}

0
在我的情况下,覆盖to_representation方法正常运行。
# models.py
class DailyLove(models.Model):
    content = models.CharField(max_length=1000)
    pic = models.FileField(upload_to='upload/api/media/DailyLove/')
    date = models.DateTimeField(auto_created=True)

    def __str__(self):
        return str(self.date)

# serializers.py
class DailyLoveSerializer(serializers.HyperlinkedModelSerializer):
    def to_representation(self, instance):
        representation = super(DailyLoveSerializer, self).to_representation(instance)
        representation['pic_url'] = self.context['request'].build_absolute_uri('/' + instance.pic.url)
        return representation

    class Meta:
        model = DailyLove
        fields = '__all__'

# views.py
class DailyLoveViewSet(viewsets.ModelViewSet):
    queryset = DailyLove.objects.all().order_by('-date')
    serializer_class = DailyLoveSerializer

# result
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

[
    {
        "url": "http://localhost:8088/daily/3/",
        "date": "2019-05-04T12:33:00+08:00",
        "content": "123",
        "pic": "http://localhost:8088/daily/upload/api/media/DailyLove/nitish-meena-37745-unsplash.jpg",
        "pic_url": "http://localhost:8088/upload/api/media/DailyLove/nitish-meena-37745-unsplash.jpg"
    }
]

0

请检查您的settings.py文件

媒体设置。

我曾经遇到过同样的错误,并发现:

MEDIA_URL = '/media/' 解决了问题。

之前我只有:

MEDIA_URL = 'media/'


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接