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