python - source - Campo SerializerClass en Serializer save from primary key
serializer source (4)
Estoy trabajando desarrollando una API con Django-rest-framework y consumiéndola desde una aplicación web. Tiene un modelo de médico con un Fk del modelo de usuario django.auth. Deseo publicar desde un formulario al Modelo médico pero el serializador devuelve este mensaje:
{"usuario": {"non_field_errors": ["Datos no válidos. Se esperaba un diccionario, pero se obtuvo unicode".]}}
Estoy enviando la clave principal del objeto de usuario. ¿Cuál es el derecho (o solo una forma) de almacenar una clave externa en DRF? He intentado anular get_validation_exclusions en el serializador y reemplazar el método perform_create en el viewset.
La API y la aplicación web están desacopladas. La API se desarrolla con django y la aplicación web con angularjs.
Mi modelo
class Physician(models.Model):
medical_office_number = models.CharField(max_length = 15)
fiscal_id_number = models.CharField(max_length = 20)
user = models.OneToOneField(User)
def __unicode__(self):
return self.user.first_name +'' ''+ self.user.last_name
Serializador
class PhysicianSerializer(serializers.ModelSerializer):
user = AccountSerializer()
class Meta:
model = Physician
fields = (''id'', ''user'', ''medical_office_number'', ''fiscal_id_number'')
read_only_fields = (''id'')
depth = 1
def get_validation_exclusions(self, *args, **kwargs):
exclusions = super(PhysicianSerializer, self).get_validation_exclusions()
return exclusions + [''user'']
* Editar Este es mi serializador de cuenta, que se basa en esta implementación y con la sugerencia de @Kevin Brown
class PrimaryKeyNestedMixin(serializers.RelatedField, serializers.ModelSerializer):
def to_internal_value(self, data):
return serializers.PrimaryKeyRelatedField.to_internal_value(self, data)
def to_representation(self, data):
return serializers.ModelSerializer.to_representation(self, data)
class AccountSerializer(PrimaryKeyNestedMixin):
password = serializers.CharField(write_only=True, required=False)
confirm_password = serializers.CharField(write_only=True, required=False)
class Meta:
model = Account
fields = (''id'', ''email'', ''username'', ''created_at'', ''updated_at'',
''first_name'', ''last_name'', ''password'',
''confirm_password'', ''is_admin'',)
read_only_fields = (''created_at'', ''updated_at'',)
Viewset
class AccountViewSet(viewsets.ModelViewSet):
lookup_field = ''username''
queryset = Account.objects.all()
serializer_class = AccountSerializer
Cuando trato de serializar este objeto, desencadena un error.
Entonces puedo publicar cualquier usuario del elemento <select>
. Pero no puedo verificar la solución. Algo que me estoy perdiendo?
Error Stacktrace
TypeError at /api/v1/accounts/
__init__() takes exactly 1 argument (5 given)
Exception Location: /home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/relations.py in many_init, line 68
Python Executable: /home/jlromeroc/workspace/asclepios/venv/bin/python
Python Version: 2.7.3
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response 111. response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/django/views/decorators/csrf.py" in wrapped_view 57. return view_func(*args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/viewsets.py" in view 85. return self.dispatch(request, *args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/views.py" in dispatch 407. response = self.handle_exception(exc) File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/views.py" in dispatch 404. response = handler(request, *args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/mixins.py" in list 45. serializer = self.get_serializer(instance, many=True)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/generics.py" in get_serializer 90. instance, data=data, many=many, partial=partial, context=context File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/relations.py" in __new__ 48. return cls.many_init(*args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/relations.py" in many_init 68. list_kwargs = {''child_relation'': cls(*args, **kwargs)}
Exception Type: TypeError at /api/v1/accounts/
Exception Value: __init__() takes exactly 1 argument (5 given)
Editar ** He optado por anular la función de creación en el conjunto de vistas e incluir el objeto en la solicitud, por lo que se puede validar, pero luego, el serializador intenta insertar un nuevo objeto para el modelo de Cuenta. ¿Cómo puedo prevenir este comportamiento? Traté de configurar el serializador en la clase PhysicianSerializer como read_only pero luego, django intenta almacenar el modelo con un user_id nulo. ¿Cómo puedo guardar un modelo sin intentar insertar un objeto relacionado también?
El problema aquí es que con los serializadores anidados, el marco Django REST espera que tanto la entrada como la salida sean una representación anidada. DRF validará automáticamente la entrada para asegurarse de que coincida con el serializador anidado, lo que le permite crear el objeto principal y las relaciones en una sola solicitud.
Está buscando tener una salida anidada con una entrada PrimaryKeyRelatedField
. Esto es muy común para aquellos que no necesitan crear relaciones en la misma solicitud, sino que siempre usarán objetos existentes en sus relaciones. La forma en que tendrá que hacerlo es básicamente tomar una clave principal (como PrimaryKeyRelatedField
) en to_internal_value
, pero generar un serializador en la to_representation
. Algo así (no probado) debería funcionar
class PrimaryKeyNestedMixin(serializers.PrimaryKeyRelatedField, serializers.ModelSerializer):
def to_internal_value(self, data):
return serializers.PrimaryKeyRelatedField.to_internal_value(self, data)
def to_representation(self, data):
return serializers.ModelSerializer.to_representation(self, data)
Debería usar esto como una mezcla en el serializador anidado, AccountSerializer
en su caso, y debería hacer lo que está buscando.
Trabajé alrededor de este problema al tener vistas diferentes para manejar el elemento individual y la publicación, y obtener una lista anidada. El elemento get get y get list usaban un serializador anidado y el método post usaba un serializador no anidado. Al publicar para crear una nueva alerta de trabajo, puede usar las claves principales para el trabajo y el usuario, que son los objetos relacionados.
class JobAlertList(APIView):
"""
List all job alerts or create a new job alert
"""
def get(self, request, format=None):
job_alerts = JobAlert.objects.all()
serializer = JobAlertNestedSerializer(job_alerts, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = JobAlertSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class JobAlertDetail(APIView):
"""
Retrieve or delete a job alert instance.
"""
def get_object(self, pk):
try:
return JobAlert.objects.get(pk=pk)
except JobAlert.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
job_alert = self.get_object(pk)
serializer = JobAlertNestedSerializer(job_alert)
return Response(serializer.data)
def delete(self, request, pk, format=None):
job_alert = self.get_object(pk)
job_alert.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class JobAlertSerializer(serializers.ModelSerializer):
class Meta:
model = JobAlert
fields = (''job'', ''user'')
depth = 0
def create(self, validated_data):
user = validated_data.pop(''user'')
job = validated_data.pop(''job'')
job_alert = JobAlert.objects.create(user=user, job=job)
return job_alert
class JobAlertNestedSerializer(serializers.ModelSerializer):
class Meta:
model = JobAlert
fields = (''id'', ''job'', ''user'')
depth = 1
url(r''^job_alerts/$'', views.JobAlertList.as_view(), name=''job-alerts-list''),
url(r''^job_alerts/(?P<pk>[0-9]+)/$'', views.JobAlertDetail.as_view(), name=''job-alerts-detail''),
Me encontré con un problema similar (querer POST id / FK del objeto, pero esperando el objeto serializado en un GET). Implementé la solución de Kevin Brown con éxito para mi caso. Adaptando eso a su problema (demasiado tarde, pero espero que alguien más, incluido yo futuro, tropiece con esto y lo encuentre útil).
def get_primary_key_related_model(model_class, **kwargs):
"""
Nested serializers are a mess. https://.com/a/28016439/2689986
This lets us accept ids when saving / updating instead of nested objects.
Representation would be into an object (depending on model_class).
"""
class PrimaryKeyNestedMixin(model_class):
def to_internal_value(self, data):
try:
return model_class.Meta.model.objects.get(pk=data)
except model_class.Meta.model.DoesNotExist:
self.fail(''does_not_exist'', pk_value=data)
except (TypeError, ValueError):
self.fail(''incorrect_type'', data_type=type(data).__name__)
def to_representation(self, data):
return model_class.to_representation(self, data)
return PrimaryKeyNestedMixin(**kwargs)
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=False)
confirm_password = serializers.CharField(write_only=True, required=False)
class Meta:
model = Account
# ...
class PhysicianSerializer(serializers.ModelSerializer):
user = get_primary_key_related_model(AccountSerializer)
class Meta:
model = Physician
# ...
El generador de class
es muy útil cuando tiene campos personalizados de serialización (restricción de acceso en request.user).
Seguí esta respuesta de SO. Deshabilite la creación de objetos anidados en django rest framework. Es un poco complicado, pero funciona. De cualquier manera, eso es algo que le falta DRF.