I am trying to partially update a model and overwriting the patch method. It works fine when I send correct data, but my problem is, that when I send incorrect data the serializer stays True and I still get a 200 status code and nothing gets updated. With incorrect data I mean wrong field names. I could basically send any field name and get a 200 status code. So I think the fields are not checked by the serializer...
class BuildingUpdateAPI(UpdateAPIView):
serializer_class = BuildingSerializer
def patch(self, request, *args, **kwargs):
"""Patches building."""
building = buildings.get(name=self.kwargs['name_building'])
serializer = BuildingSerializer(building, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return JsonResponse(status=200, data=serializer.data)
return JsonResponse(status=400, data="Wrong data provided")
serializer:
class BuildingSerializer(serializers.ModelSerializer):
"""Serializer for Buildings."""
class Meta:
model = Building
fields = (
"id",
"name",
"...",
.....
Now I am wondering if this is like this by design or if I am doing something wrong in my view. I thought I could overwrite the validate-method in the serializer but my model has quite a lot of fields... So I would have to check every field single field which is not optimal I think. Or is there a way to do this somehow elegantly?
I am patching like this:
result = requests.request("patch", url=endpoint, json=payload, headers=get_headers())
Related
I am sending multipart/formdata from a Next.js API and I can format the data whichever way I want but I am struggling to get the right format.
For now, I have the following formdata:
<QueryDict: {
'name': ['Test Product'],
'brands[0]': ['1'],
'brands[1]': ['2'],
'option_types[0]': ['1'],
'product_variants[0]option_values[0]': ['1'],
'product_variants[0]option_values[1]': ['2'],
>
and the following ModelSerializer:
class ProductDetailAdminSerializer(
UniqueFieldsMixin, ProductAdminMixin, WritableNestedModelSerializer
):
categories = PrimaryKeyRelatedField(
many=True, allow_null=True, queryset=Category.objects.all()
)
option_types = PrimaryKeyRelatedField(
many=True, allow_null=True, queryset=OptionType.objects.all()
)
brands = PrimaryKeyRelatedField(
many=True, allow_null=True, queryset=Brand.objects.all()
)
product_variants = ProductVariantDetailAdminSerializer(many=True)
class Meta:
model = Product
fields = (
"pk",
"name",
"subtitle",
"sku_symbol",
"categories",
"brands",
"description",
"option_types",
"product_variants",
)
My ModelSerializer is not accepting the way I am specifying the lists/arrays. For instance, if I try to do:
def validate_option_types(self, data):
print(data)
return data
I get an empty list meaning the format for option_types list is wrong and the same applies for the product_variants and option_values. I am simply passing the QueryDict obtained from request.data as follows:
def create(self, request, *args, **kwargs):
serializer = ProductDetailAdminSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
return JsonResponse(serializer.errors, status=400)
The serializer and everything else are working fine if I use a JSON version of the QueryDict above and the JSON content-type. Incidentally, if I use ListField instead of PrimaryKeyRelatedField it also works as expected although, ListField doesn't actually give me the objects required.
So to summarise my question, what is the correct QueryDict format (specifically for fields which represent lists) for the DRF ModelSerializer? Or is there an extra step that I am missing in getting the QueryDict to the format expected by the model serializer.
ok, You can not add many-to-many type field as the way you are trying to -
first create the object without many to many fields
obj = MyModal.save()
and then you can do something like -
to create and add your many to many fields.
obj.brand.create(name='val1')
obj.option_types.create(name='val2')
You can also search StackOverflow on how to add data to ManyToManyFields
Seems like PrimaryKeyRelatedField can't process multipart/formdata in the desired way. Here's a related question.
My solution has been to convert all the non-file form data into a single JSON string from the front-end i.e.
const formData = new FormData();
formData.append('form_data', JSON.stringify(data));
and then in Django, in the create function of my viewset, do something like
data = ujson.loads(request.data.get("form_data"))
new_query_dict = QueryDict('', mutable=True)
new_query_dict.update(data)
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')
I am trying to pass into my DRF API a post that needs to be queried against the database. For now I have included only 2 fields that can be queried. I am having trouble getting the view to work. Here is what I have so far.
POST
{
"city": "Denver",
"state": "CO"
}
Serializer only lets city and state be accepted
class EventQuerySerializer(serializers.ModelSerializer):
class Meta:
model = Events
fields = ('city', 'state')
View note that EventsSerializer is used in the return and I can confirm it works for a basic get request
class QueryEvents(APIView):
#staticmethod
def post(request):
serializer = EventQuerySerializer(data=request.data)
if serializer.is_valid():
events = Events.objects.get(serializer)
return Response(EventsSerializer(events).data)
Error
AttributeError: 'CharField' object has no attribute 'split'
You need to run save on you serializer to retrieve instance:
# ...
if serializer.is_valid():
event = serializer.save()
events = Events.objects.get(pk=event.pk)
# ...
When I try to deserialize some data into an object, if I include a field that is unique and give it a value that is already assigned to an object in the database, I get a key constraint error. This makes sense, as it is trying to create an object with a unique value that is already in use.
Is there a way to have a get_or_create type of functionality for a ModelSerializer? I want to be able to give the Serializer some data, and if an object exists that has the given unique field, then just return that object.
In my experience nmgeek's solution won't work in DRF 3+ as serializer.is_valid() correctly honors the model's unique_together constraint. You can work around this by removing the UniqueTogetherValidator and overriding your serializer's create method.
class MyModelSerializer(serializers.ModelSerializer):
def run_validators(self, value):
for validator in self.validators:
if isinstance(validator, validators.UniqueTogetherValidator):
self.validators.remove(validator)
super(MyModelSerializer, self).run_validators(value)
def create(self, validated_data):
instance, _ = models.MyModel.objects.get_or_create(**validated_data)
return instance
class Meta:
model = models.MyModel
The Serializer restore_object method was removed starting with the 3.0 version of REST Framework.
A straightforward way to add get_or_create functionality is as follows:
class MyObjectSerializer(serializers.ModelSerializer):
class Meta:
model = MyObject
fields = (
'unique_field',
'other_field',
)
def get_or_create(self):
defaults = self.validated_data.copy()
identifier = defaults.pop('unique_field')
return MyObject.objects.get_or_create(unique_field=identifier, defaults=defaults)
def post(self, request, format=None):
serializer = MyObjectSerializer(data=request.data)
if serializer.is_valid():
instance, created = serializer.get_or_create()
if not created:
serializer.update(instance, serializer.validated_data)
return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
However, it doesn't seem to me that the resulting code is any more compact or easy to understand than if you query if the instance exists then update or save depending upon the result of the query.
#Groady's answer works, but you have now lost your ability to validate the uniqueness when creating new objects (UniqueValidator has been removed from your list of validators regardless the cicumstance). The whole idea of using a serializer is that you have a comprehensive way to create a new object that validates the integrity of the data you want to use to create the object. Removing validation isn't what you want. You DO want this validation to be present when creating new objects, you'd just like to be able to throw data at your serializer and get the right behavior under the hood (get_or_create), validation and all included.
I'd recommend overwriting your is_valid() method on the serializer instead. With the code below you first check to see if the object exists in your database, if not you proceed with full validation as usual. If it does exist you simply attach this object to your serializer and then proceed with validation as usual as if you'd instantiated the serializer with the associated object and data. Then when you hit serializer.save() you'll simply get back your already created object and you can have the same code pattern at a high level: instantiate your serializer with data, call .is_valid(), then call .save() and get returned your model instance (a la get_or_create). No need to overwrite .create() or .update().
The caveat here is that you will get an unnecessary UPDATE transaction on your database when you hit .save(), but the cost of one extra database call to have a clean developer API with full validation still in place seems worthwhile. It also allows you the extensibility of using custom models.Manager and custom models.QuerySet to uniquely identify your model from a few fields only (whatever the primary identifying fields may be) and then using the rest of the data in initial_data on the Serializer as an update to the object in question, thereby allowing you to grab unique objects from a subset of the data fields and treat the remaining fields as updates to the object (in which case the UPDATE call would not be extra).
Note that calls to super() are in Python3 syntax. If using Python 2 you'd want to use the old style: super(MyModelSerializer, self).is_valid(**kwargs)
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
class MyModelSerializer(serializers.ModelSerializer):
def is_valid(self, raise_exception=False):
if hasattr(self, 'initial_data'):
# If we are instantiating with data={something}
try:
# Try to get the object in question
obj = Security.objects.get(**self.initial_data)
except (ObjectDoesNotExist, MultipleObjectsReturned):
# Except not finding the object or the data being ambiguous
# for defining it. Then validate the data as usual
return super().is_valid(raise_exception)
else:
# If the object is found add it to the serializer. Then
# validate the data as usual
self.instance = obj
return super().is_valid(raise_exception)
else:
# If the Serializer was instantiated with just an object, and no
# data={something} proceed as usual
return super().is_valid(raise_exception)
class Meta:
model = models.MyModel
There are a couple of scenarios where a serializer might need to be able to get or create Objects based on data received by a view - where it's not logical for the view to do the lookup / create functionality - I ran into this this week.
Yes it is possible to have get_or_create functionality in a Serializer. There is a hint about this in the documentation here: http://www.django-rest-framework.org/api-guide/serializers#specifying-which-fields-should-be-write-only where:
restore_object method has been written to instantiate new users.
The instance attribute is fixed as None to ensure that this method is not used to update Users.
I think you can go further with this to put full get_or_create into the restore_object - in this instance loading Users from their email address which was posted to a view:
class UserFromEmailSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = [
'email',
]
def restore_object(self, attrs, instance=None):
assert instance is None, 'Cannot update users with UserFromEmailSerializer'
(user_object, created) = get_user_model().objects.get_or_create(
email=attrs.get('email')
)
# You can extend here to work on `user_object` as required - update etc.
return user_object
Now you can use the serializer in a view's post method, for example:
def post(self, request, format=None):
# Serialize "new" member's email
serializer = UserFromEmailSerializer(data=request.DATA)
if not serializer.is_valid():
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
# Loaded or created user is now available in the serializer object:
person=serializer.object
# Save / update etc.
A better way of doing this is to use the PUT verb instead, then override the get_object() method in the ModelViewSet. I answered this here: https://stackoverflow.com/a/35024782/3025825.
A simple workaround is to use to_internal_value method:
class MyModelSerializer(serializers.ModelSerializer):
def to_internal_value(self, validated_data):
instance, _ = models.MyModel.objects.get_or_create(**validated_data)
return instance
class Meta:
model = models.MyModel
I know it's a hack, but in case if you need a quick solution
P.S. Of course, editing is not supported
class ExpoDeviceViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, ]
serializer_class = ExpoDeviceSerializer
def get_queryset(self):
user = self.request.user
return ExpoDevice.objects.filter(user=user)
def perform_create(self, serializer):
existing_token = self.request.user.expo_devices.filter(
token=serializer.validated_data['token']).first()
if existing_token:
return existing_token
return serializer.save(user=self.request.user)
In case anyone needs to create an object if it does not exist on GET request:
class MyModelViewSet(viewsets.ModelViewSet):
queryset = models.MyModel.objects.all()
serializer_class = serializers.MyModelSerializer
def retrieve(self, request, pk=None):
instance, _ = models.MyModel.objects.get_or_create(pk=pk)
serializer = self.serializer_class(instance)
return response.Response(serializer.data)
Another solution, as I found that UniqueValidator wasn't in the validators for the serializer, but rather in the field's validators.
def is_valid(self, raise_exception=False):
self.fields["my_field_to_fix"].validators = [
v
for v in self.fields["my_field_to_fix"].validators
if not isinstance(v, validators.UniqueValidator)
]
return super().is_valid(raise_exception)
After resolving some of my troubles while converting from django-rest-framwork 0.3.2 to the lates 2.1.9 I cannot see to fix this one (which i agree with a blog of Reinout.... it's a real pain in the ...)
I had this code:
class ApiSomeInputView(View):
form = ApiSomeForm
permissions = (IsAuthenticated, )
resource=SomeResource
def get(self, request):
"""
Handle GET requests.
"""
return "Error: No GET request Possible, use post"
def post(self, request, format=None):
some_thing = self.CONTENT['some_thing']
# check if something exist:
something = get_object_or_none(Something,some_field=int(some_thing))
if not something:
raise _404_SOMETHING_NOT_FOUND
#Note exludes are set in SomeResource
data = Serializer(depth=4).serialize(something)
return Response(status.HTTP_200_OK, data)
Now I have followed the tutorial and saw how you can do this different (maybe even prettier). By using slug in the url.
However.... I want to keep things backward compatible for the client side software... so I want to have this without putting the value of the query in the url. The client side uses json data and ContentType json in the header of a post.
In the first version of django rest framwork, I even got a nice browsable form in which to fill in the values for this query
My question: how to get this done in the latest version?
I can't seem to get a form in the views.... where I can fill in values and use in the proces
maybe good to post what I have tried until sofar...
first I changed the ModelResource in a Serializer:
class SomethingSerializer(HyperlinkedModelSerializer):
class Meta:
model = Something
#exclude = ('id',)
depth = 4
and than the view changed in to:
class ApiSomeInputView(APIView):
permissions = (IsAuthenticated, )
def post(self, request, format=None):
some_thing = request.DATA['some_thing']
# check if something exist: .... well actually this above already does not work
something = get_object_or_none(Something,some_field=int(some_thing))
if not something:
raise _404_SOMETHING_NOT_FOUND
serializer = SomethingSerializer(something)
return Response(status.HTTP_200_OK, serializer.data)
Note: Bases upon the accepted answer (by Tom Christie) I als put an answer in which I show how I got it working (in more detail).
When you're inheriting from APIView, the browseable API renderer has no way of knowing what serializer you want to use to present in the HTML, so it falls back to allowing you to post a plain JSON (or whatever) representation.
If you instead inherit from GenericAPIView, set the serializer using the serializer_class attribute, and get an instance of the serializer using the get_serializer(...) method - see here, then the browseable API will use a form to display the user input.
Based upon the answer of Tom Christie (which I'll accept as the answer). I got it working:
I made an extra serializer which defines the field(s) to be shown to fill in for the post and shown using the GenericAPIView... (correct me if I Am wrong Tom, just documenting it here for others... so better say it correct)
class SomethingSerializerForm(Serializer):
some_thing = serializers.IntegerField()
And with this serializer and the other one I aready had.
And a view:
class ApiSomeInputView(GenericAPIView):
permissions = (IsAuthenticated, )
model = Something
serializer_class = SomethingSerializerForm
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA)
if not serializer.is_valid():
raise ParseError(detail="No valid values")
some_thing = request.DATA['some_thing']
something = get_object_or_none(Something,some_field=int(some_thing))
if not something:
raise Http404
serializer = SomethingSerializer(something)
return Response(serializer.data)
Above is working, and exactly the same as before....
I still got the feeling I Am abusing the Serializer class as a Form.