web-dev-qa-db-fra.com

Django REST Framework et FileField URL absolue

J'ai défini une application Django simple comprenant le modèle suivant:

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

(Techniquement oui, cela aurait pu être un ImageField.)

Dans un modèle, il est assez facile d'inclure la valeur MEDIA_URL (dûment codée dans settings.py) en tant que préfixe de l'URL de la vignette. Ce qui suit fonctionne bien:

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

À l'aide de DRF, j'ai défini un descendant de HyperlinkedModelSerializer appelé ProjectSerializer:

class ProjectSerializer(serializers.HyperlinkedModelSerializer):

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

Et j'ai défini un descendant de ModelViewSet très simple:

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

Un exemple du JSON résultant ressemble à ceci:

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

Je n'ai pas encore réussi à comprendre comment fournir un champ de vignette contenant l'URL complète de l'image dans la représentation JSON de mon projet.

Je penserais que je devrais créer un champ personnalisé dans ProjectSerializer, mais je n'ai pas réussi.

21
Mark Semsel

Essayez SerializerMethodField

Exemple (non testé):

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)

La demande doit être disponible pour le sérialiseur afin qu'il puisse créer l'URL absolue absolue pour vous. Une solution consiste à le transmettre explicitement lors de la création du sérialiseur, comme ceci:

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

Merci, shavenwarthog. Votre exemple et votre référence à la documentation ont énormément aidé. Ma mise en œuvre est légèrement différente, mais très proche de ce que vous avez posté:

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

Pour obtenir l'URL d'un fichier qui utilise FileField, vous pouvez simplement appeler l'attribut URL du FieldFile (il s'agit de l'instance du fichier et non du champ). Il utilise la classe Storage pour déterminer l'URL de ce fichier. C'est très simple si vous utilisez un stockage externe comme Amazon S3 ou si votre stockage change.

Le get_thumbnail_url serait comme ceci.

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

Vous pouvez également l'utiliser dans le modèle de cette façon:

{{ current_project.thumbnail.url }}
6
Johnny Well

Je trouvais ennuyeux d’écrire le même code pour un champ de méthode sérialisée . Si vous avez défini correctement le MEDIA_ROOT sur votre URL de compartiment S3, vous pouvez ajouter un champ au sérialiseur comme:

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

    class Meta:
        model = Project

le logo est un ImageField dans le modèle. il ne doit pas être nullable pour éviter des erreurs comme ValueError: The 'img' attribute has no file associated with it.

J'utilise uniquement .build_absolute_uri dans une méthode de sérialisation pour renvoyer des URL absolues utilisant d'autres vues de mon API. Par exemple, dans mon projet, il existe une URL /webviews/projects/<pk> qui indique un titre et un bouton qui collecte certaines entrées de l'utilisateur (c.-à-d. pas exactement ce que vous feriez avec des suffixes, car ce n'est pas une représentation claire de la ressource mais une certaine logique) . le noeud final /projects/<pk>/ contient un champ "webview_url", généré avec SerializerMethodField. ce n'est pas un média.

3
Eduard Gamonal

Passez juste le contexte et passez l'objet de requête. si vous utilisez @api_view

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

Pour la méthode get_serializer_context de l'utilisateur ViewSet

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

def get_serializer_context(self):
    return {'request': self.request}
1
Tarikul Islam Rasel

Pas besoin de substitutions ou de personnalisations. DRF le gère automatiquement. Examinez la méthode to_representation de 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

Notez que cela ne fonctionnera pas si le contexte du sérialiseur n'est pas défini correctement. Si vous utilisez ViewSets, pas de problème, tout se fait en silence, mais si vous instanciez le sérialiseur manuellement, vous devez transmettre la demande dans le contexte.

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

1
MJafar Mash

Dans mon cas, la méthode to_representation de substitution fonctionne correctement.

# 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
bovenson

Vérifiez vous settings.py

paramètres de médias.

J'ai eu la même erreur et constaté que:

MEDIA_URL = '/media/'did.

Avant j'avais seulement:

MEDIA_URL = 'media /'

0
jakobdo