Modified Output Data in to_representation method in django drf - python

can I return a list in the to_representation method in Django Rest Framework
I need to modify the output in the serializer here is a recent output
{
"id": 1,
"display_sequence": 2
}
I need to modify the recent output to
[
{
"id": 1
"display_sequence": 2
},
{
"id" : 2
"display_sequence": 1
}
]
so the second data I got from the query filter based on the container id and target_container_id
instance = Container.objects.filter(id__in=[self.container_id, self.target_container_id])
if I return to serializer I got this error
Got AttributeError when attempting to get a value for field `display_sequence` on serializer `ContainerSequenceSwapSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `list` instance.
Original exception text was: 'list' object has no attribute 'display_sequence'.
how to I can return yo expected output?
here are my views
Views
class ContainerViewSet(viewsets.ModelViewSet):
"""Container ViewSet for CRUD operations."""
queryset = Container.objects.all()
def get_serializer_class(self):
return ContainerSerializerV1
#action(methods=["patch"], detail=True, url_path="swap-sequence")
def swap_sequence(self, request, pk):
# serializer = ContainerSequenceSwapSerializer(data=request.data)
container = self.get_object()
serializer = ContainerSequenceSwapSerializer(container, data=request.data, context={'container': container})
if serializer.is_valid(raise_exception=True):
# display data here
content = serializer.data
return Response(data=content, status=status.HTTP_200_OK)
Serializer
class ContainerSequenceSwapSerializer(serializers.ModelSerializer):
"""Serializer for Container sequence swap detail action."""
display_sequence = serializers.IntegerField()
# add class meta here to display id, swap sequence
class Meta:
model = Container
fields = (
"id",
"display_sequence",
)
read_only_fields = ("id", "display_sequence")
# sorting data
def to_representation(self, instance):
instance = Container.objects.filter(id__in=[self.container_id, self.target_container_id])
# data = super().to_representation(instance)
return instance
# custom validation
def validate(self, attrs):
display_sequence = super().validate(attrs)
container = self.context.get("container")
if not container:
return display_sequence
target_display_sequence = display_sequence["display_sequence"]
try:
target_container = Container.objects.get(
module=container.module, display_sequence=target_display_sequence
)
except Container.DoesNotExist:
raise serializers.ValidationError(
{"display_sequence": ["Invalid swap target"]}
)
else:
# switching
container.display_sequence, target_container.display_sequence = (
target_container.display_sequence,
container.display_sequence,
)
container.save()
target_container.save()
# datas
self.container_id = container.id
self.target_container_id = target_container.id
return display_sequence
how do I can return the expected output without modifying the views.py

Related

Django DRF serializer with many=true missuses the respective validate()

I have run into the following problem:
There are 2 serializers CreateSerializer(child) and BulkCreateSerializer(parent).
They are connected via list_serializer_class.
I have overridden create() and validate() methods for both serializers and expect them to trigger respectively on whether a single instance is coming via POST or a list of instances.
However, when I am sending a post request with a list of instances serializer does switch to many=true but uses validate() that belongs to child CreateSerializer instead of dedicated BulkCreateSerializer, which runs me into errors of course.
So my question is, what could be the logic under the hood, that prevents my serializers to distribute the items for validation respectively? And how to make it work that way?
serializers.py
class RecipeStepDraftBulkCreateSerializer(serializers.ListSerializer):
def validate(self, data):
print("bulk validate")
new_step_numbers = [s.get('step_number') for s in data]
if None in new_step_numbers:
raise serializers.ValidationError("step_number filed is required")
if new_step_numbers != list(set(new_step_numbers)):
raise serializers.ValidationError("Wrong order of step_number(s) supplied")
try:
recipe = Recipe.objects.get(pk=self.context.get('recipe_id'))
existing_steps = recipe.recipe_steps.get_queryset().all()
if existing_steps:
ex_step_numbers = [s.step_number for s in existing_steps]
if new_step_numbers[0] != ex_step_numbers[-1] + 1:
raise serializers.ValidationError(
f"The next first supplied step_number must be: {ex_step_numbers[-1] + 1}")
steps_combined = ex_step_numbers + new_step_numbers
if steps_combined != list(set(steps_combined)):
raise serializers.ValidationError(f"Wrong order of step_number(s) supplied")
return data
except ObjectDoesNotExist:
raise serializers.ValidationError("Recipe under provided id doesn't exist.")
def create(self, validated_data):
recipe = Recipe.objects.get(pk=self.context.get('recipe_id'))
for step in validated_data:
step['recipe'] = recipe
RecipeStep.objects.create(**step)
return validated_data
class RecipeStepDraftCreateSerializer(serializers.ModelSerializer):
class Meta:
model = RecipeStep
fields = [
'id',
'step_number',
'step_image',
'instruction',
'tip']
list_serializer_class = RecipeStepDraftBulkCreateSerializer
def validate(self, data):
print("single validate")
if not data.get("step_number"):
raise serializers.ValidationError("step_number field is required.")
try:
recipe = Recipe.objects.get(pk=self.context.get('recipe_id'))
existing_steps = recipe.recipe_steps.get_queryset().all()
if existing_steps:
ex_step_numbers = [s.step_number for s in existing_steps]
if data["step_number"] != ex_step_numbers[-1] + 1:
raise serializers.ValidationError(
f"The next first supplied step_number must be: {ex_step_numbers[-1] + 1}")
if data["step_number"] != 1:
raise serializers.ValidationError(f"Wrong step_number. 1 expected, got {data['step_number']}")
return data
except ObjectDoesNotExist:
raise serializers.ValidationError("Recipe under provided id doesn't exist.")
def create(self, validated_data):
recipe = Recipe.objects.get(pk=self.context.get('recipe_id'))
validated_data['recipe'] = recipe
step = RecipeStep.objects.create(**validated_data)
return step
views.py
class DraftsRecipeStepsCreateView(APIView):
serializer_class = RecipeStepDraftCreateSerializer
def post(self, request, *args, **kwargs):
print(f'DATA IS LIST: {isinstance(request.data, list)}')
serializer = self.serializer_class(
data=request.data,
many=isinstance(request.data, list),
context={
'request': request,
'recipe_id': kwargs.get('recipe_id')})
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
The code that explains this behavior can be found here. It describes how a list serializer runs validations (the run_validation method).
Based on that, the list serializer first validates each child serializer as seen in the to_internal_value implementation:
def to_internal_value(self, data):
...
for item in data:
try:
validated = self.child.run_validation(item)
except ValidationError as exc:
errors.append(exc.detail)
else:
ret.append(validated)
errors.append({})
...
before running its own list validate:
def run_validation(self, data=empty):
...
value = self.to_internal_value(data) # <-- children validation first
try:
self.run_validators(value) # <-- then the list validation
...
I tried this out with the below serializer:
class MyModelListSerializer(serializers.ListSerializer):
def validate(self, attrs):
print('mymodel list validate')
return attrs
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = '__all__'
list_serializer_class = MyModelListSerializer
def validate(self, attrs):
print('mymodel validate')
return attrs
using some sample data, and then validating:
somedata = [
{
"data": "1",
},
{
"data": "2",
}
]
s = MyModelSerializer(data=somedata, many=True); s.is_valid();
The output I get is then consistent on what the source suggests:
mymodel validate
mymodel validate
mymodel list validate
In your case I suspect print("bulk validate") did not trigger because there were problems in validating the child serializers causing the list serializer to not be run anymore.

Django get_serializer 'NoneType' object is not callable

I am testing an API of Django (DRF) application.
I am calling http://127.0.0.1:8000/api/users/1/documents/ (1 - user id)
And receive an error
...
File "/app/backend/apps/users/views/users/views.py" in create
542. serializer = self.get_serializer(data=request.data)
File "/usr/local/lib/python3.7/site-packages/rest_framework/generics.py" in get_serializer
110. return serializer_class(*args, **kwargs)
Exception Type: TypeError at /api/users/1/documents/
Exception Value: 'NoneType' object is not callable
How can i identify the problem?
Request related view /app/backend/apps/users/views/users/views.py (problematic line is serializer = self.get_serializer(data=request.data))
class UserDocumentCreate(generics.CreateAPIView, generics.RetrieveAPIView):
serializer_class = UserDocumentSerializer
permission_classes = (UserIsOwner, IsAuthenticatedDriver)
queryset = Document.objects.all()
def get_serializer_class(self):
if self.request.version == "1.0":
return UserDocumentSerializer
# return UserDocumentSerializer2
def create(self, request, *args, **kwargs):
request.data._mutable = True
request.data["owner"] = kwargs.get("pk")
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(raise_exception=True):
owner = serializer.validated_data.get("owner")
document_type = serializer.validated_data.get("document_type")
message_status = request.data.get("message_status")
documents = owner.document_owner.filter(
document_type=document_type
)
for document in documents:
if document.status == DocumentStatus.DOCUMENT_REJECTED_STATUS:
document.delete()
# Mark user as new
owner.is_new_user = True
owner.save()
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response = {
"status": status.HTTP_201_CREATED,
"result": serializer.data,
}
# accept corresponding registration message
if message_status:
driver_reg = DriverRegistration.objects.filter(user=kwargs.get("pk")).first()
driver_reg.accept_by_status(message_status)
next_id = driver_reg.get_next()
# add information about next registration message to response
if next_id != -1:
response["next_message"] = REG_MESSAGES[next_id].to_json()
return Response(
response, status=status.HTTP_201_CREATED, headers=headers
)
Related serializer (nothing special)
class UserDocumentSerializer(serializers.ModelSerializer):
is_new_document = serializers.BooleanField(read_only=True)
class Meta:
model = Document
fields = (
"id",
"owner",
"file",
"document_type",
"uploaded_at",
"is_new_document",
)
You need must always return a value from get_serializer_class method.
You need to implement an else condition, either explicitly (like the lone you've commented), or using the serializer_class, or falling back to super method.
class UserDocumentCreate(generics.CreateAPIView, generics.RetrieveAPIView):
serializer_class = UserDocumentSerializer
permission_classes = (UserIsOwner, IsAuthenticatedDriver)
queryset = Document.objects.all()
def get_serializer_class(self):
if self.request.version == "1.0":
return UserDocumentSerializer
else:
# explicit
return UserDocumentSerializer2
# property
return self.serializer_class
# super
return super(UserDocumentCreate, self).get_serializer_class()

How to solve key error in nested representation of a model in django

This is my model:
class MyModel(models.Model):
ID = models.ForeignKey(OtherModel,related_name='NewModel', on_delete=models.CASCADE)
start = models.BigIntegerField()
duration = models.BigIntegerField(default= 30)
value = models.IntegerField()
where OtherModel has 2 fields, biginteger and foreignkey. I am trying to create an instance of the MyModel and it gives me a key error. The serializer is as below:
class ModifyReadingSerializer(serializers.Serializer):
duration = serializers.IntegerField()
start = serializers.IntegerField()
class OriginalSerializer(serializers.ModelSerializer):
timePeriod = ModifyReadingSerializer(source = '*')
class Meta:
model = MyModel
fields = ('timePeriod', 'value',)
And the view to create it shown below:
class RegisterValues(generics.ListCreateAPIView):
''' GET/POST urltemp/{ID = pk}'''
queryset = MyModel.objects.all()
serializer_class = OriginalSerializer
def post(self, request, *args, **kwargs):
s1 = OtherModel.objects.get(mRID=kwargs["pk"])
a_temp = MyModel.objects.create(
ID=s1,
value=request.data["value"],
duration=request.data["duration"],
start=request.data["start"],)
return Response(data=OriginalSerializer(a_temp).data)
I get the following error KeyError at /urltemp/1 'duration'. I understand why I am getting the error but not sure how to fix it while maintaining the nested representation. And I can see the error is in the line where I am creating duration (I can see it in the terminal).
EDIT: ADDED THE PAYLOAD
{
"timePeriod": {
"duration": 30,
"start": 24
},
"value": 34,
}
Try this snippet,
class RegisterValues(generics.ListCreateAPIView):
''' GET/POST urltemp/{ID = pk}'''
queryset = MyModel.objects.all()
serializer_class = OriginalSerializer
def post(self, request, *args, **kwargs):
s1 = OtherModel.objects.get(mRID=kwargs["pk"])
a_temp = MyModel.objects.create(
ID=s1,
value=request.data["value"],
duration=request.data["timePeriod"]["duration"],
start=request.data["timePeriod"]["start"], )
return Response(data=OriginalSerializer(a_temp).data)
The error doesn't seem to have anything to do with your model structure. Since it's a KeyError, the problem is that there is no value in request.data associated with the key duration.
In other words, you must be calling your API without a field named durantion in the request body.
Make sure you to call the API endpoint including in the request body all the field you try to access in the your code and this error should disapear.

Django API : Create Serializer Issue

I'm working on my Django web application and I'm beginning API part.
I have a Create Serializer class like this :
class IndividuCreateSerializer(serializers.ModelSerializer) :
class Meta :
model = Individu
fields = [
'Etat',
'Civilite',
'Nom',
'Prenom',
'Sexe',
'Statut',
'DateNaissance',
'VilleNaissance',
'PaysNaissance',
'Nationalite1',
'Nationalite2',
'Profession',
'Adresse',
'Ville',
'Zip',
'Pays',
'Mail',
'Telephone',
'Image',
'CarteIdentite',
]
def create(self, validated_data):
obj = Individu.objects.create(**validated_data)
IdentityIndividuResumeView.get_context_data(obj.id)
return obj
In this class, I have my create function which should redirect to IdentityIndividuResumeView class when my person is created.
class IdentityIndividuResumeView(LoginRequiredMixin, TemplateView) :
template_name = 'Identity_Individu_Resume.html'
model = Individu
def get_context_data(self, **kwargs) :
context_data = super(IdentityIndividuResumeView, self).get_context_data(**kwargs)
id = self.kwargs['id']
personne = get_object_or_404(Individu, pk=id)
NIU = lib.Individu_Recherche.NIUGeneratorIndividu(personne)
personne.NumeroIdentification = NIU
...
But I don't overcome to pass argument in my function get_context_data. I'm getting this issue :
File "/Users/valentin/Desktop/Identity/api/serializers.py" in create
80. IdentityIndividuResumeView.get_context_data(obj.id)
File "/Users/valentin/Desktop/Identity/views.py" in get_context_data
228. context_data = super(IdentityIndividuResumeView, self).get_context_data(**kwargs)
Exception Type: TypeError at /Api/Identification/create/
Exception Value: super(type, obj): obj must be an instance or subtype of type
EDIT :
It works with FBV model, but I would like to convert this to CBV :
#login_required
def Identity_Individu_Resume(request, id) :
personne = get_object_or_404(Individu, pk=id)
NIU = lib.Individu_Recherche.NIUGeneratorIndividu(personne)
personne.NumeroIdentification = NIU
...
and serializers.py file :
def create(self, validated_data):
obj = Individu.objects.create(**validated_data)
Identity_Individu_Resume(self.context.get('request'), obj.id)
return obj
The create function of the serializer is calling using the class object IdentityIndividuResumeView.get_context_data(obj.id) and not the instance of the class. While in the function based view, you are passing all the required arguments. That's why it is working for it.
Check for your self object at /Users/valentin/Desktop/Identity/views.py line 228 self object is wrong.
I mean Content in the self object is of not proper type.

django-rest-framework PUT manytomany through model

I have a Django Model w/ a m2m relationship that uses a through model:
models.py
class ModelA(models.Model):
name = models.CharField(max_length=64)
class ModelB(models.Model):
name = models.CharField(max_length=64)
other_models = models.ManyToManyField("ModelA", through="ModelC")
class ModelC(models.Model):
model_a = models.ForeignKey("ModelA", related_name="link_to_model_a")
model_b = models.ForeignKey("ModelB", related_name="link_to_model_b")
some_other_info = models.TextField()
class Meta:
unique_together = ("model_a", "model_b", )
I want to serialize this using django-rest-framework:
serializers.py
class ModelCSerializer(ModelSerializer):
class Meta:
model = ModelC
fields = ('id', 'model_a', 'model_b', 'some_other_info', )
class QModelBSerializer(ModelSerializer):
class Meta:
model = ModelB
fields = ('id', 'other_models', )
other_models = ModelCSerializer(many=True, required=False, source="link_to_model_b")
Now, for existing models the GET displays properly:
{
"id": 2,
"name": "i am an instance of model_b",
"other_models": [
{"id": 1, "model_a": 1,"model_b": 2, "some_other_info":"here is some other info"}
],
}
But, if I try to PUT some data it fails w/ a unique_together error. I thought that sending this as a PUT would cause an update (which shouldn't raise a unique_together error) not a create? Here is the code for PUT:
views.py
class ModelBDetail(APIView):
def put(self, request, pk, format=None):
model = ModelB.objects.get(id=pk)
serializer = ModelBSerializer(model, data=request.data, context={"request": request})
if serializer.is_valid(): # THIS IS RETURNING FALSE
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Any thoughts?
Django rest framework documentation states that it is up to developer to implement creates and updates for nested representations.
Although #Ivan was correct about writing my own create & update fn, the specific issue I was seeing was that the nested serialization did not have an instance attribute associated with it.
The new code looks like this:
serializers.py
class ModelBSerializer(ModelSerializer):
....
def update(self, model_instance, validated_data):
model_c_serializer = self.fields["other_models"]
model_c_data = validated_data.pop(model_c_serializer.source, [])
for key, value in validated_data.iteritems():
setattr(model_instance, key, value)
model_instance.save()
model_c_serializer.update(model_instance.link_to_model_b.all(),
model_c_data)
return model_instance
class ModelCSerializer(ModelSerializer):
...
def to_internal_value(self, data):
# this is as good a place as any to set the instance
try:
model_class = self.Meta.model
self.instance = model_class.objects.get(pk=data.get("id"))
except ObjectDoesNotExist:
pass
return super(ModelCSerializer, self).to_internal_value(data)
Basically, I call update for the nested serializers explicitly and I also force each nested serializer to check the data that is passed to them for an instance.

Categories

Resources