Django pass extra data to ModelSerializer create from a ModelViewSet serializer.save() - django

I have this ModelViewSet
def create(self, request, *args, **kwargs):
data_to_save = request.data
pharmacy = Pharmacy.objects.get(pk=request.data['pharmacy'])
serializer = self.get_serializer(data=data_to_save)
serializer.is_valid(raise_exception=True)
serializer.save(myArg=pharmacy)
headers = self.get_success_headers(serializer.data)
return Response({'results': serializer.data}, status=status.HTTP_201_CREATED, headers=headers)
The self.get_serializer(...) points to a class PharmacyUserSerializer(serializers.ModelSerializer): ...
The PharmacyUserSerializer(...), I'm overriding the create(...) function like so
def create(self, validated_data):
request = self.context['request']
myArg = self.context['myArg']
pharmacy = request.user.pharmacy
user = User.objects.create_user(
**validated_data,
user_type=c.PHARMACY,
pharmacy=pharmacy
)
return user
ACcording to the DRF docs, this line looks right (passing arguments to the save method)
serializer.save(myArg=pharmacy)
Doing the above gives the error,
TypeError: 'myArg' is an invalid keyword argument for this function
So what's going on? What's the right way to pass data (i guess I'm missing something in the docs).
And how do I intercept this extra data in the PharmacyUserSerializer

You can only pass attribute of your model to serializer in save method not whatever you want. To pass additional variable to your serializer you can use context. Check this link to how use it.
In your case you should use this code:
self.get_serializer(data=data_to_save, context={'myArg': pharmacy})
And you already write the code in PharmacyUserSerializer to get myArg.

Related

Django Rest Framework - Serializer update_or_create giving IntegrityError: Unique Constraint Failed

I am having an issue when using the API to send an update to an existing record.
When I send the API for a new record, it works perfectly. But when I send it for an existing record, I would like it to update the current record, but it just gives me an integrity error instead.
My Serializers.py looks like this:
class PartSerializer(serializers.ModelSerializer):
part = serializers.CharField()
class Meta:
model = DocumentRef
fields = ('part', 'field1', 'field2', 'field3')
def create(self, validated_data):
part = Part.objects.get(part_number=validated_data['part'])
validated_data['part'] = part
return DocumentRef.objects.update_or_create(**validated_data)
I have tried changing update_or_create to just create or just update but it will still only work if the record does not exist yet.
The model it should be referencing is DocumentRef, which looks like this:
class DocumentRef(models.Model):
part = models.OneToOneField(Part, on_delete=models.CASCADE)
field1 = models.FileField(upload_to='mcp/')
field2 = models.FileField(upload_to='qcp/')
field3 = models.FileField(upload_to='cus/')
The API View I am using is this:
class APIDetailTest(APIView):
def get_object(self, pk):
try:
return DocumentRef.objects.get(pk=pk)
except DocumentRef.DoesNotExist:
return HttpResponse(status=status.HTTP_404_NOT_FOUND)
def get(self, request, pk):
part = self.get_object(pk)
serializer = PartSerializer(part)
return Response(serializer.data)
def put(self, request, pk):
part = self.get_object(pk)
serializer = PartSerializer(part, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Edit: Changed create_or_update to update_or_create -- Just made this error in this post, in my code it was correct from the beginning.
Edit2: Have also tried changing the return value to:
return DocumentRef.objects.update_or_create(defaults={'part_id': part.id}, field1=validated_data['field1'], field2=validated_data['field2'], field3=validated_data['field3']) but that still gives the unique constraint failed error.
This is more of a workaround than an answer, but you could try catching the error and treating the request differently.
something like this:
def create(self, validated_data):
...
try:
return DocumentRef.objects.create(**validated_data)
except IntegrityError:
DocumentRef.objects.filter(part=validated_data['part']).delete()
return DocumentRef.objects.create(**validated_data)
obviously, this is not updating the record. Just deleting the existing one and making a new one.
Try using it with defaults and kwargs please read here: django docs
The update_or_create method tries to fetch an object from database
based on the given kwargs. If a match is found, it updates the fields
passed in the defaults dictionary.
You need to update your query like this
def create(self, validated_data):
part = Part.objects.get(part_number=validated_data['part'])
return DocumentRef.objects.update_or_create(defaults={'part': part}, **validated_data)

DRF SerializerMethodField method only running when serializer variable opened in debugger

My SerializerMethodField method is only printing HERE when I have a breakpoint at the return in the get method, and open the serializer variable after it has triggered OR when serializer.data is called (in which case it prints the expected data, but validated_data is still empty).
View:
class EventAddPeople(generics.GenericAPIView):
serializer_class = EventAddPeopleSerializer_Read
def get(self, request, *args, **kwargs):
serializer = EventAddPeopleSerializer_Read(data=request.GET)
serializer.is_valid(raise_exception=True)
print(serializer.validated_data)
return HttpResponse(serializer.validated_data)
Serializer:
class EventAddPeopleSerializer_Read(serializers.Serializer):
event_id = serializers.SerializerMethodField(method_name='get_event_id')
person_ids = serializers.SerializerMethodField()
def get_event_id(self, obj):
print("HERE")
return "TEST00"
def get_person_ids(self, obj):
print("HERE")
return "TEST00"
class Meta:
fields = ('event_id', 'person_ids')
Your get method is not called (may be).
check by just printing('anything') in your get method
check Methos for genericApiViews
Thanks
First thing, request.data is applicable for non-GET requests. You are not supposed to send data in the payload section with HTTP GET. If you want to send data with GET method, pass it through URL query parameters
So, the url will become, /api/my/end-point/?event_id=1&person_ids=3
and you need to pass this query param to serializer as,
serializer = EventAddPeopleSerializer_Read(data=request.GET)
Second thing, you've missed to add the Meta class in the serializer
class EventAddPeopleSerializer_Read(serializers.Serializer):
# other code
class Meta:
fields = ('event_id', 'person_ids')

How can I get User at Validate in Serializer?

In my view(CreateView) I overriding my method def create, but in my validate I cant get logged user by self.context.get('request').user, so, how can I get the user logged in my validate?
UPDATE:
The Error is:
line 293, in validate
user = self.context.get('request').user
AttributeError: 'NoneType' object has no attribute 'user'
UPDATE 2
class OrderAPIPost(CreateAPIView):
permission_classes = (permissions.IsAuthenticated, )
serializer_class = MultipleOrderSerializer
queryset = Order.objects
def create(self, request, *args, **kwargs):
write_serializer = MultipleOrderSerializer(data=request.data)
write_serializer.is_valid(raise_exception=True)
orders = write_serializer.data.get('items')
orders = list(map(lambda order: Order.create_order(order, self.request.user), orders))
read_serializer = list(map(lambda order: OrderSerializerList(order), orders))
read_serializer = list(map(lambda order: order.data, read_serializer))
return Response(read_serializer, status=status.HTTP_201_CREATED)
So, from what I can see in your code, you are creating the serializer manually without adding the context. In most cases, allowing the CreateView create the serializer by itself suffices but if you really need to create it by yourself, then you need to remember to pass the context. Somthing like this:
context = {'request': self.request}
write_serializer = MultipleOrderSerializer(data=request.data, context=context)
You can check the view's get_serializer() method to see how a serializer is properly created. I really advice that you refactor your code and try to use the existing solution for creating serializers

Django SerializerMethodField can't save a decimal

So, according to the docs, SerializerMethodField is a read-only field.
Well in my case, it's interfering with my write:
# old value is 2.5
data={'score': 1.7}
serializer = ScoreTraitSerializer(
score_trait, data=data, partial=True)
if serializer.is_valid():
new_score_trait = serializer.save()
Now if I inspect the new_score_trait, my score is still 2.5.
The serializer looks as such:
score = serializers.SerializerMethodField()
def get_score(self, obj):
if isinstance(obj.score, decimal.Decimal):
return float(obj.score)
else:
return obj.score
If I comment out my SerializerMethodField, I can save the new decimal value (but can't serialize it).
So ... am I using my serializer correctly? Why does my write to the serializer hitting the SerializerMethodField?
Thanks in advance
SerializerMethodField is a read-only field.Only used for to_representation, it's used for list/retrieve not create/update.
the serializer field score must conflict with model field score,try change it to:
float_score = serializers.SerializerMethodField(required=False)
def get_float_score (self, obj):
if isinstance(obj.score, decimal.Decimal):
return float(obj.score)
else:
return obj.score
See the source code you will know why:
class SerializerMethodField(Field):
"""
A read-only field that get its representation from calling a method on the
parent serializer class. The method called will be of the form
"get_{field_name}", and should take a single argument, which is the
object being serialized.
For example:
class ExampleSerializer(self):
extra_info = SerializerMethodField()
def get_extra_info(self, obj):
return ... # Calculate some data to return.
"""
def __init__(self, method_name=None, **kwargs):
self.method_name = method_name
kwargs['source'] = '*'
kwargs['read_only'] = True
super(SerializerMethodField, self).__init__(**kwargs)

Django rest framework disable field update after condition

I'm using DRF and I need to disable the update of a field if a condition on the same model is respected.
example:
class Foo(models.Model):
text = models.CharField()
checkfield = models.BooleanField(default=False)
text can be modified unless checkfield is True.
So if Foo.checkfield is True Foo.text cannot be modified via DRF API.
What is the best way to do so?
I think Advanced serializers will do what you want.
Just create your custom serializer and in your view, check the value of checkfield. If it's true, pass it the text argument so it enables the field in the serializer.
Btw, since you only need one fixed extra field to be removed or added, instead of passing the fields argument as in the example, you can pass it something like enable_text=checkfield and then add the text field to the 'fields' variable in your serializer according to the value of 'checkfield'.
update to clarify:
Define your serializer without the text field. Then in your ModelViewSet, override the update method so you get the serializer this way (I think the get_serializer() method does not allow to pass extra args):
YourSerializer(object, enable_text=True)
And, inside your serializer init method, when 'enable_text' is True, you add the text field to the self.fields attribute.
I haven't tested if this works but I think it is the way to go.
Edit with snippet and modification
I've been digging a bit with what I explained and turned out it is a bit messy for the simple modification you are trying to do. What I've come up with is just to override the update method in your ViewSet. Here is the code:
from rest_framework import viewsets, status
from rest_framework.response import Response
from models import Test, TestSerializer
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
self.object = self.get_object_or_none()
if 'enable_text' in request.DATA and request.DATA['enable_text'] == True:
request.DATA['text'] = self.object.text
serializer = self.get_serializer(self.object, data=request.DATA,
files=request.FILES, partial=partial)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
try:
self.pre_save(serializer.object)
except ValidationError as err:
# full_clean on model instance may be called in pre_save,
# so we have to handle eventual errors.
return Response(err.message_dict, status=status.HTTP_400_BAD_REQUEST)
if self.object is None:
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)
self.object = serializer.save(force_update=True)
self.post_save(self.object, created=False)
return Response(serializer.data, status=status.HTTP_200_OK)
This code is taken from the rest_framework source code for the UpdateMixin. Take special attention at lines if 'enable_text' in request.DATA and... and request.DATA['text'] = self.object.text. Those are the ones allowing you to do the funcionality you need. Basically:
If you send the enable_text with True along with text, text will be modified.
If you send the enable_text with False along with text, it will be ignored.
Note that this code only takes into account the value of enable_text passed in the current request. You maybe want also that if enable_text is not in the current request, to check the value of enable_text in the self.object (which is the database instance itself).