【问题标题】:Django REST Framework and FileField absolute urlDjango REST Framework 和 FileField 绝对 url
【发布时间】:2014-07-18 02:09:25
【问题描述】:

我定义了一个简单的 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,我定义了一个名为 ProjectSerializer 的 HyperlinkedModelSerializer 后代:

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 中创建一个自定义字段,但没有成功。

【问题讨论】:

    标签: python django url filefield


    【解决方案1】:

    试试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})
    

    【讨论】:

    • 您不需要传递request。它已经在self.context['view'].request 中可用。
    • 这个问题是SerializerMethodField是只读的
    • 就我而言,仅在实例化序列化程序时设置 context={'request': request} 就足以获取绝对 URL。不需要SerializerMethodField
    • 不应该是obj.thumbnail.url,而不是self.thumbnail_url
    • 这个答案让我意识到使用context={'request': request}作为序列化器参数,如果文件字段是从另一个域访问的,它将返回绝对URL。
    【解决方案2】:

    谢谢,剃须猪。您的示例和文档参考帮助很大。我的实现略有不同,但与您发布的内容非常接近:

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

    【讨论】:

    • 您不需要导入setting.MEDIA_URL。只需返回:obj.thumbnail.url
    【解决方案3】:

    要获取使用 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 }}
    

    【讨论】:

      【解决方案4】:

      我发现为序列化方法字段编写相同的代码很烦人。 如果您已将 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/&lt;pk&gt; 显示,一个标题和一个收集一些用户输入的按钮(即不完全是你会用后缀做什么,因为它不是资源的简单表示,但包含一些逻辑反而)。端点/projects/&lt;pk&gt;/ 包含一个“webview_url”字段,该字段由 SerializerMethodField 生成。这不是媒体。

      【讨论】:

        【解决方案5】:

        无需任何覆盖或自定义。 DRF 会自动处理它。看看FileFieldto_representation方法:

        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
        

        请注意,如果序列化程序的上下文设置不正确,它将无法工作。如果您使用的是ViewSets,不用担心,一切都会静默完成,但如果您手动实例化序列化程序,则必须在上下文中传递请求。

        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)并将请求添加到上下文中。
        • ListAPIView 出现问题,您的上下文解决方案正在运行!非常感谢
        【解决方案6】:

        检查你的 settings.py

        媒体设置。

        我有同样的错误,发现:

        MEDIA_URL = '/媒体/' 成功了。

        之前我只有:

        MEDIA_URL = '媒体/'

        【讨论】:

          【解决方案7】:

          只需传递上下文并传递请求对象。如果您使用的是@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}
          

          【讨论】:

            【解决方案8】:

            只需传递这个"context={'request': request}" 参数,您可以在其中调用模型序列化程序类来序列化对象。您可以按照下面的 sn-p 获取完整的 URL 字段。

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

            【讨论】:

              【解决方案9】:

              在我的情况下,覆盖 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"
                  }
              ]
              

              【讨论】:

                【解决方案10】:

                检查一下!

                class FileFieldWithLinkRepresentation(serializers.FileField):
                
                    def to_representation(self, value):
                        return create_link(value.url, self.context['request'])
                

                create_link方法:

                def create_link(path: str, request: Request):
                    domain = request.META['HTTP_HOST']
                    if not path.startswith('/', 0, 1):
                        path = '/' + path
                    return request.scheme + "://" + domain + path
                

                你可以在每个需要超链接的类中使用FileFieldWithLinkRepresentation FileField 的表示。

                【讨论】:

                  【解决方案11】:

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

                  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)

                  【讨论】:

                    猜你喜欢
                    • 2017-07-17
                    • 1970-01-01
                    • 1970-01-01
                    • 2010-11-15
                    • 1970-01-01
                    • 1970-01-01
                    • 2010-12-17
                    • 2020-02-20
                    • 2020-12-16
                    相关资源
                    最近更新 更多