related primarykeyrelatedfield one many framework foreign python django django-rest-framework

python - primarykeyrelatedfield - serializer django rest framework



DRF: asignaciĆ³n de clave externa simple con serializadores anidados? (8)

Con Django REST Framework, un ModelSerializer estándar permitirá asignar o cambiar las relaciones del modelo ForeignKey PUBLICANDO un ID como un entero.

¿Cuál es la forma más sencilla de obtener este comportamiento de un serializador anidado?

Tenga en cuenta que solo estoy hablando de asignar objetos de base de datos existentes, no creación anidada.

En el pasado, he eliminado estos campos con campos de ''identificación'' adicionales en el serializador y con métodos personalizados de create y update , pero este es un problema aparentemente tan simple y frecuente para mí que tengo curiosidad por saber la mejor manera.

class Child(models.Model): name = CharField(max_length=20) class Parent(models.Model): name = CharField(max_length=20) phone_number = models.ForeignKey(PhoneNumber) child = models.ForeignKey(Child) class ChildSerializer(ModelSerializer): class Meta: model = Child class ParentSerializer(ModelSerializer): # phone_number relation is automatic and will accept ID integers children = ChildSerializer() # this one will not class Meta: model = Parent



Algunas personas aquí han colocado una forma de mantener un campo pero aún así pueden obtener los detalles al recuperar el objeto y crearlo solo con la ID. Hice una implementación un poco más genérica si la gente está interesada:

Primero de las pruebas:

from rest_framework.relations import PrimaryKeyRelatedField from django.test import TestCase from .serializers import ModelRepresentationPrimaryKeyRelatedField, ProductSerializer from .factories import SomethingElseFactory from .models import SomethingElse class TestModelRepresentationPrimaryKeyRelatedField(TestCase): def setUp(self): self.serializer = ModelRepresentationPrimaryKeyRelatedField( model_serializer_class=SomethingElseSerializer, queryset=SomethingElse.objects.all(), ) def test_inherits_from_primary_key_related_field(self): assert issubclass(ModelRepresentationPrimaryKeyRelatedField, PrimaryKeyRelatedField) def test_use_pk_only_optimization_returns_false(self): self.assertFalse(self.serializer.use_pk_only_optimization()) def test_to_representation_returns_serialized_object(self): obj = SomethingElseFactory() ret = self.serializer.to_representation(obj) self.assertEqual(ret, SomethingElseSerializer(instance=obj).data)

Entonces la clase misma:

from rest_framework.relations import PrimaryKeyRelatedField class ModelRepresentationPrimaryKeyRelatedField(PrimaryKeyRelatedField): def __init__(self, **kwargs): self.model_serializer_class = kwargs.pop(''model_serializer_class'') super().__init__(**kwargs) def use_pk_only_optimization(self): return False def to_representation(self, value): return self.model_serializer_class(instance=value).data

El uso es así, si tiene un serializador en alguna parte:

class YourSerializer(ModelSerializer): something_else = ModelRepresentationPrimaryKeyRelatedField(queryset=SomethingElse.objects.all(), model_serializer_class=SomethingElseSerializer)

Esto le permitirá crear un objeto con una clave foránea aún solo con la PK, pero devolverá el modelo anidado serializado completo al recuperar el objeto que creó (o cuando sea realmente).


Aquí hay un ejemplo de lo que está hablando la respuesta de Kevin, si desea adoptar ese enfoque y utilizar 2 campos separados.

En tus modelos.py ...

class Child(models.Model): name = CharField(max_length=20) class Parent(models.Model): name = CharField(max_length=20) phone_number = models.ForeignKey(PhoneNumber) child = models.ForeignKey(Child)

entonces serializers.py ...

class ChildSerializer(ModelSerializer): class Meta: model = Child class ParentSerializer(ModelSerializer): # if child is required child = ChildSerializer(read_only=True) # if child is a required field and you want write to child properties through parent # child = ChildSerializer(required=False) # otherwise the following should work (untested) # child = ChildSerializer() child_id = serializers.PrimaryKeyRelatedField( queryset=Child.objects.all(), source=''child'', write_only=True) class Meta: model = Parent

Establecer source=child permite que child_id actúe como hijo por defecto si no se anula (nuestro comportamiento deseado). write_only=True hace que child_id esté disponible para escribir, pero evita que aparezca en la respuesta ya que la identificación ya aparece en ChildSerializer .


Así es como he resuelto este problema.

serializers.py

class ChildSerializer(ModelSerializer): def to_internal_value(self, data): if data.get(''id''): return get_object_or_404(Child.objects.all(), pk=data.get(''id'')) return super(ChildSerializer, self).to_internal_value(data)

Simplemente pasará su serializador hijo anidado tal como lo obtiene del serializador, es decir, hijo como un json / diccionario. en to_internal_value instanciamos el objeto hijo si tiene una ID válida para que DRF pueda seguir trabajando con el objeto.


Creo que el enfoque esbozado por Kevin probablemente sería la mejor solución, pero nunca pude lograr que funcione. DRF seguía arrojando errores cuando tenía tanto un serializador anidado como un conjunto de campos de clave primaria. Eliminar uno u otro funcionaría, pero obviamente no me dio el resultado que necesitaba. Lo mejor que se me ocurre es crear dos serializadores diferentes para leer y escribir, así ...

serializers.py:

class ChildSerializer(serializers.ModelSerializer): class Meta: model = Child class ParentSerializer(serializers.ModelSerializer): class Meta: abstract = True model = Parent fields = (''id'', ''child'', ''foo'', ''bar'', ''etc'') class ParentReadSerializer(ParentSerializer): child = ChildSerializer()

views.py

class ParentViewSet(viewsets.ModelViewSet): serializer_class = ParentSerializer queryset = Parent.objects.all() def get_serializer_class(self): if self.request.method == ''GET'': return ParentReadSerializer else: return self.serializer_class


Hay una manera de sustituir un campo en la operación de creación / actualización:

class ChildSerializer(ModelSerializer): class Meta: model = Child class ParentSerializer(ModelSerializer): child = ChildSerializer() # called on create/update operations def to_internal_value(self, data): self.fields[''child''] = serializers.PrimaryKeyRelatedField( queryset=Child.objects.all()) return super(ParentSerializer, self).to_internal_value(data) class Meta: model = Parent


La mejor solución aquí es usar dos campos diferentes: uno para leer y el otro para escribir. Sin hacer algo de trabajo pesado , es difícil obtener lo que está buscando en un solo campo .

El campo de solo lectura sería su serializador anidado ( ChildSerializer en este caso) y le permitirá obtener la misma representación anidada que espera. La mayoría de las personas definen esto como solo un child , porque ya tienen su front-end escrito en este punto y cambiarlo causaría problemas.

El campo de solo escritura sería un PrimaryKeyRelatedField , que es lo que normalmente usaría para asignar objetos en función de su clave principal. Esto no tiene que ser solo de escritura, especialmente si está tratando de obtener simetría entre lo que se recibe y lo que se envía, pero parece que eso podría ser mejor para usted. Este campo debe tener una source establecida en el campo de clave externa ( child en este ejemplo) para que se asigne correctamente en la creación y actualización.

Esto ha sido mencionado en el grupo de discusión varias veces, y creo que esta sigue siendo la mejor solución. Gracias a Sven Maurer por señalarlo .


Usar dos campos diferentes estaría bien (como mencionaron y ), pero creo que no es perfecto (para mí). Porque obtener datos de una clave ( child ) y enviar datos a otra clave ( child_id ) puede ser un poco ambiguo para los desarrolladores front-end . (sin ofender en absoluto)


Entonces, lo que sugiero aquí es que anule el método to_representation() de ParentSerializer que hará el trabajo.

def to_representation(self, instance): response = super().to_representation(instance) response[''child''] = ChildSerializer(instance.child).data return response


Representación completa de serializador

class ChildSerializer(ModelSerializer): class Meta: model = Child fields = ''__all__'' class ParentSerializer(ModelSerializer): class Meta: model = Parent fields = ''__all__'' def to_representation(self, instance): response = super().to_representation(instance) response[''child''] = ChildSerializer(instance.child).data return response



¿Ventaja de este método?

Al usar este método, no necesitamos dos campos separados para la creación y la lectura. Aquí, tanto la creación como la lectura se pueden hacer utilizando child clave child .


Carga útil de muestra para crear parent instancia parent

{ "name": "TestPOSTMAN_name", "phone_number": 1, "child": 1 }



Captura de pantalla