start how framework example create django django-rest-framework

how - django rest framework vs django



Escribir serializador anidado con objetos existentes utilizando Django Rest Framework 3.2.2 (3)

Convierta CategorySerializer.create en un método update_or_create en el name

class CategorySerializer(serializers.ModelSerializer): ... # update_or_create on `name` def create(self, validated_data): try: self.instance = Category.objects.get(name=validated_data[''name'']) self.instance = self.update(self.instance, validated_data) assert self.instance is not None, ( ''`update()` did not return an object instance.'' ) return self.instance except Category.DoesNotExist: return super(CategorySerializer, self).create(validated_data) ...

Recomiendo mirar la fuente DRF cuando necesites crear una funcionalidad personalizada.

Pregunta relacionada respondida por el creador de DRF : django-rest-framework 3.0 crea o actualiza en serializador anidado

Editar

Así que todavía estaba en el modo de pensar DRF 2 donde los campos de escritura anidados se manejan automáticamente. Puede leer sobre el tema aquí: http://www.django-rest-framework.org/topics/3.0-announcement/

He probado el siguiente código y funciona:

class CategorySerializer(serializers.ModelSerializer): class Meta: ... extra_kwargs = { ''name'': {''validators'': []}, ''description'': {''required'': False}, } class ListingSerializer(serializers.ModelSerializer): ... def update_or_create_category(self, validated_data): data = validated_data.pop(''category'', None) if not data: return None category, created = models.Category.objects.update_or_create( name=data.pop(''name''), defaults=data) validated_data[''category''] = category def create(self, validated_data): self.update_or_create_category(validated_data) return super(ListingSerializer, self).create(validated_data) def update(self, instance, validated_data): self.update_or_create_category(validated_data) return super(ListingSerializer, self).update(instance, validated_data)

Editar

La forma correcta de usar SlugRelatedField es así, en caso de que se esté preguntando:

class ListingSerializer(serializers.ModelSerializer): ... # slug_field should be ''name'', i.e. the name of the field on the related model category = serializers.SlugRelatedField(slug_field=''name'', queryset=models.Category.objects.all()) ...

Considere un modelo de Listado que tiene una Categoría asociada. Quiero crear un nuevo Listado para una Categoría existente haciendo un POST con datos: {"title": "myapp", "category": {"name": "Business"}} , donde title es el título del Listado debe crearse, y Business es el nombre de una categoría existente para usar para esta nueva lista.

Cuando trato de realizar una solicitud de este tipo y crear una instancia del ListingSerializer para esto, ListingSerializer un error que indica que el nombre de la categoría debe ser único: no quiero crear una nueva categoría, sino que usar una existente. Intenté configurar los validadores en el campo de categoría en [] , pero eso no cambió el comportamiento.

Puedo usar un SlugRelatedField , pero eso fuerza a mis datos de solicitud a parecerse más a {"title": "myapp", "category": "Business"} , que no es lo que quiero. Intenté usar el argumento source para SlugRelatedField para especificar una relación anidada, pero eso tampoco funcionó:

category = serializers.SlugRelatedField( slug_field=''category.name'', queryset=models.Category.objects.all() )

rendimientos:

"category": [ "Object with name={''name'': ''Business''} does not exist." ]

models.py:

import django.contrib.auth from django.db import models from django.conf import settings class Profile(models.Model): display_name = models.CharField(max_length=255) user = models.OneToOneField(settings.AUTH_USER_MODEL) class Category(models.Model): name = models.CharField(max_length=50, unique=True) description = models.CharField(max_length=200) class Listing(models.Model): title = models.CharField(max_length=50, unique=True) category = models.ForeignKey(Category, related_name=''listings'', null=True) owners = models.ManyToManyField( Profile, related_name=''owned_listings'', db_table=''profile_listing'', blank=True )

serializers.py:

import logging import django.contrib.auth from rest_framework import serializers import myapp.models as models logger = logging.getLogger(''mylogger'') class ShortUserSerializer(serializers.ModelSerializer): class Meta: model = django.contrib.auth.models.User fields = (''username'', ''email'') class ProfileSerializer(serializers.ModelSerializer): user = ShortUserSerializer() class Meta: model = models.Profile fields = (''user'', ''display_name'') read_only = (''display_name'',) class CategorySerializer(serializers.ModelSerializer): class Meta: model = models.Category fields = (''name'', ''description'') read_only = (''description'',) class ListingSerializer(serializers.ModelSerializer): owners = ProfileSerializer(required=False, many=True) # TODO: how to indicate that this should look for an existing category? category = CategorySerializer(required=False, validators=[]) class Meta: model = models.Listing depth = 2 def validate(self, data): logger.info(''inside ListingSerializer validate'') return data def create(self, validated_data): logger.info(''inside ListingSerializer.create'') # not even getting this far...

views.py:

import logging from django.http import HttpResponse from django.shortcuts import get_object_or_404 import django.contrib.auth from rest_framework import viewsets from rest_framework.response import Response import myapp.serializers as serializers import myapp.models as models # Get an instance of a logger logger = logging.getLogger(''mylogger'') class CategoryViewSet(viewsets.ModelViewSet): queryset = models.Category.objects.all() serializer_class = serializers.CategorySerializer class UserViewSet(viewsets.ModelViewSet): queryset = django.contrib.auth.models.User.objects.all() serializer_class = serializers.ShortUserSerializer class ProfileViewSet(viewsets.ModelViewSet): queryset = models.Profile.objects.all() serializer_class = serializers.ProfileSerializer class ListingViewSet(viewsets.ModelViewSet): logger.info(''inside ListingSerializerViewSet'') queryset = models.Listing.objects.all() serializer_class = serializers.ListingSerializer

Ejemplo completo: https://github.com/arw180/drf-example


Esto no es ideal, pero encontré una solución que resolvió mi problema (estoy esperando aceptarlo como la respuesta, esperando que alguien más pueda hacerlo mejor). Hay dos partes:

Primero, use el argumento partial=True al inicializar el ListingSerializer ( http://www.django-rest-framework.org/api-guide/serializers/#partial-updates ). Luego use el método de validate del serializador para obtener la instancia del modelo real correspondiente a los datos de entrada.

En segundo lugar, elimine explícitamente los validadores para el campo de name en CategorySerializer . Esto es especialmente malo porque afecta mucho más que el ListingSerializer .

Si deja salir cualquiera de las piezas, se generarán errores de validación en el momento en que se crea una instancia del serializador.

modificaciones a views.py:

class ListingViewSet(viewsets.ModelViewSet): queryset = models.Listing.objects.all() serializer_class = serializers.ListingSerializer def create(self, request): serializer = serializers.ListingSerializer(data=request.data, context={''request'': request}, partial=True) if not serializer.is_valid(): logger.error(''%s'' % serializer.errors) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED)

modificaciones a serializers.py:

class CategorySerializer(serializers.ModelSerializer): class Meta: model = models.Category fields = (''name'', ''description'') read_only = (''description'',) # also need to explicitly remove validators for `name` field extra_kwargs = { ''name'': { ''validators'': [] } } class ListingSerializer(serializers.ModelSerializer): owners = ProfileSerializer(required=False, many=True) category = CategorySerializer(required=False) class Meta: model = models.Listing depth = 2 def validate(self, data): # manually get the Category instance from the input data data[''category''] = models.Category.objects.get(name=data[''category''][''name'']) return data def create(self, validated_data): title = validated_data[''title''] listing = models.Listing(title=validated_data[''title''], category=validated_data[''category'']) listing.save() if ''owners'' in validated_data: logger.debug(''owners: %s'' % validated_data[''owners'']) for owner in validated_data[''owners'']: print (''adding owner: %s'' % owner) listing.owners.add(owner) return listing

Voy a esperar un poco para aceptar esto como la respuesta en caso de que alguien pueda encontrar una mejor solución (por ejemplo, cómo hacer que el argumento de source funcione correctamente con un SlugRelatedField ) - Tengo un ejemplo de trabajo usando la solución anterior en https: / /github.com/arw180/drf-example si quieres experimentar También me gustaría escuchar comentarios sobre por qué las cosas extra_kwargs son necesarias en el CategorySerializer . ¿Por qué no crear una instancia de este tipo: category = CategorySerializer(required=False, validators=[]) sufficient (en el ListingSerializer )? ACTUALIZACIÓN: creo que eso no funciona porque el validador único se agrega automáticamente a partir de las restricciones de la base de datos y se ejecuta independientemente de los validadores explícitos establecidos aquí, como se explica en esta respuesta: http://iswwwup.com/t/3bf20dfabe1f/python- order-of-serializer-validation-in-django-rest-framework.html


Tuve un problema similar: necesitaba verificar si el serializador anidado ( CategorySerializer ) existe para usarlo y, si no, crearlo desde el serializador de anidamiento ( ListingSerializer ). La solución de @demux me funcionó por completo solo si no utilicé la validación personalizada para un campo en el serializador anidado (el campo por el cual verificaría el serializador de anidamiento si esta instancia existe). Así que agregué el método create() al serializador anidado y @demux personalizado update_or_create_category() , create() , update() para ListingSerializer funcionó perfectamente.

class CategorySerializer(serializers.ModelSerializer): class Meta: model = Category ... def create(self, validated_data): if Category.objects.filter(name=self.validated_data[''name'']).exists(): raise serializers.ValidationError("This category name already exists") return Category.objects.create(**validated_data)