I have a model like
class MyModel(models.Model):
uuid = models.CharField(max_length=40, unique=True)
and a serializer
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('uuid')
And I want to receive JSON with MyModel object but it can be existing objects. So, when I use serializer.is_valid() with data about existing object it gives me an error:
for record in request['records']:
# request - body of JSON request,
# 'records' - array of records I want to add or update
serializer = MyModelSerializer(data=record)
if serializer.is_valid():
# Do stuff
serializer.save()
Error:
{"uuid":["This field must be unique."]}
Is there a way to separate behavior for new and existing objects? Particularly, I want to create new MyModel object if it's not it database yet and update existing MyModel object if it's present.
You are basically overloading a single entry point of your REST API by trying to both create new instances and update existing instances using a POST request. In addition, it seems you are trying to create and update multiple instances simultaneously within a single POST request.
Django REST Framework (DRF) expects a POST request to only create new instances. Therefore, sending an existing instance record triggers a unique constraint violation for the uuid field since DRF tries to create that record as a new instance, as the existing instance already has that uuid value.
A solution to make your REST API more "RESTful" would be to separate the creation and updating of records into POST and PUT requests respectively. It is unclear if you are using the generic API views provided by DRF, but you can use the CreateAPIView to POST new instances, then create a separate UpdateAPIView to PUT and/or PATCH existing instances. Even better you could allow retrieval via GET for both of these endpoints using the generic views ListCreateAPIView and RetrieveUpdateAPIView.
Finally, for handling bulk requests (i.e. multi-instances in a single request) you can either override the built-in view methods or use a 3rd-party package such as django-rest-framework-bulk.
I had a situation where I had a deep create method, with 2 levels of hierarchy above the end point, that it was important that all models were idempotent.
I override the validation in the serializer, and created it by hand.
It is important that you add the field to the class at the top (otherwise the validator won't be run)
class ParticipantSerializer(serializers.HyperlinkedModelSerializer):
device = DeviceSerializer(required=False)
uuid = serializers.CharField()
def validate_uuid(self, value):
if value is not None and isinstance(value, basestring) and len(value) < 256:
return value
else:
if value is not None:
raise serializers.ValidationError("UUID can't be none")
elif isinstance(value, basestring):
raise serializers.ValidationError("UUID must be a string")
elif len(value) < 256:
raise serializers.ValidationError("UUID must be below 256 characters")
else:
raise serializers.ValidationError("UUID has failed validation")
class Meta:
model = Participant
fields = ("uuid", "platform", "device")
Related
For the part of developing an API using restframe work and DJango, I need to receive few parameters through get method in 'list' function of my Viewset. In client side they are sending data as query prams, I somehow managed to get the data using 'request.query_params.get()' but received data is text format instead of bool, I had to convert this to boolean . see the code how I did this
if 'status' in (request.query_params):
"""receiving input argument is string need to convert that to boolean for further processing"""
input_status=(request.query_params.get('status', None)=='true')
is there any better way to getting data without losing datatype. is it possible to receive 'bool' or 'int' values directly in get parameter?
My model and view set classes are given below
class ModuleType(models.Model):
"""
This module type class used to represent each module of the application. It is inherited from
django content type which holds the complete model informations
"""
#this active field is true this module will be active on our user controller
content_type = models.OneToOneField(
ContentType,
on_delete=models.CASCADE,
related_name='status',
null=True
)
active=models.BooleanField(default=False)
class Meta:
db_table = 'module_types'
My Viewset
class ContentTypeViewSet(viewsets.ModelViewSet):
"""
This api deals all operations related with module management
You will have `list`, `create`, `retrieve`,
update` and `destroy` actions.
Additionally we also provide an action to update status.
"""
queryset = ContentType.objects.all()
serializer_class = ContentTypeSerializer
permission_classes = [permissions.AllowAny]
"""
Over ride list method to list values
"""
def list(self, request):
status='Sucess'
message='details of all modules included'
print(request)
try:
if 'status' in (request.query_params):
#input argument is striing need to convert that to boolean
input_status=(request.query_params.get('status', None)=='true')
validated_value= self.validate_input_params({'status':input_status})
if (validated_value==1):
all_modules = ContentType.objects.filter(status__active=input_status)
message='All value set status as '+str(input_status)
else:
message='Failed to validate all input values'
status='fail'
return Response({"status":status,"data":[],"message":message})
else:
all_modules=ContentType.objects.all()
serializer = self.get_serializer(
all_modules,
many=True
)
return Response({"status":status,"data":serializer.data,'message':message})
except:
message='unknown exception'
status='Fail'
return Response({"status":status,"data":[],"message":message})
Your view will read query params as string.
I suggest you use query_params = dict(request.GET.items())
That way you can parse parameters easier by using dict.
I've added some custom permissions to my Post model.
I've also created a form to add/edit groups with only this custom permissions:
class GroupFornm(forms.ModelForm):
permissions = forms.MultipleChoiceField(choices=Post._meta.permissions)
class Meta:
model = Group
fields = '__all__'
It works because I can see and select only my custom permissions but when I try to save the form I got the error:
invalid literal for int() with base 10: 'can_view'
What am I doing wrong? It seems that this form field waits for (int, str) pair but documentation says that as usually, (str, str) should work.
Edit
Post._meta.permissions:
(('can_view', 'Can see tickets'), ('can_edit', 'Can edit tickets'), ('can_delete', 'Can delete tickets'), ('can_create', 'Can add new tickets'))
The problem is not really related to the form itself, but the fact that you somehow need to translate those permissions into Permission objects that should be stored in the Group instance (the one that this ModelForm is managing).
I think displaying the options is not a problem. But if a user later for example performs a POST request, with the options (like can_write), then the question is how the Form should translate these into Permission objects (or the primary keys of Permission objects).
In that case you need to coerce the name of the permissions to Permission objects, or the ids of Permission objects. We can for example use a TypedMultipleChoiceField, and coerce with:
def get_permission_from_name(name):
return Permission.objects.get(name=name)
class GroupFornm(forms.ModelForm):
permissions = forms.TypedMultipleChoiceField(
choices=Post._meta.permissions,
coerce=get_permission_from_name,
)
class Meta:
model = Group
fields = '__all__'
Note that the above is not really a very efficient implementation, since it requires a query for every value send. Furthermore in case no permission with that name exists, then this will raise an error.
If you want to construct Permissions on the fly (in case these are not yet constructed), then you can change the function to:
def get_permission_from_name(name):
return Permission.objects.get_or_create(
name=name,
defaults={
'content_type': ContentType.objects.get_for_model(Post),
'codename': name
}
)
I want to create a viewset/apiview with a path like this: list/<slug:entry>/ that once I provide the entry it will check if that entry exists in the database.
*Note: on list/ I have a path to a ViewSet. I wonder if I could change the id with the specific field that I want to check, so I could see if the entry exists or not, but I want to keep the id as it is, so
I tried:
class CheckCouponAPIView(APIView):
def get(self, request, format=None):
try:
Coupon.objects.get(coupon=self.kwargs.get('coupon'))
except Coupon.DoesNotExist:
return Response(data={'message': False})
else:
return Response(data={'message': True})
But I got an error: get() got an unexpected keyword argument 'coupon'.
Here's the path: path('check/<slug:coupon>/', CheckCouponAPIView.as_view()),
Is there any good practice that I could apply in my situation?
What about trying something like this,
class CheckCouponAPIView(viewsets.ModelViewSet):
# other fields
lookup_field = 'slug'
From the official DRF Doc,
lookup_field - The model field that should be used to for performing
object lookup of individual model instances. Defaults to pk
I've been learning Django over the past couple of weeks, and there is one thing that really seems to confuse me. Which model's attributes does Django use to define the <pk> that is used in urls.py?
For example, If I have:
urlpatterns = [
url(r'^(?P<pk>\d+)/$', ProductDetailView.as_view(), name="product-detail"),
]
I had assumed previously that the pk would be derived from the model instance that is being used in the given view, in this case, ProductDetailView.as_view(). However, I'm starting to question that logic as you can pass multiple models into a view, of course.
Part 2
Also, what if I wanted to use the pk of one model instance, while only using a different model instance in the view?
For example, what if I had two models, Products & Stores which both hold a many-to-many relationship (eg. a product could be in multiple stores, and a store can hold many products). Then I wanted to have a url where I have a StoreListView listing all the stores that hold a given product, so my url would be something like:
url(r'^(?P<pk>\d+)/$', StoreListView.as_view(), name="store-list")
Where the pk is of the Product instance but the view is of the Store instance
To finalize the question, again, how does Django define pk?
Django doesn't define anything. You define it. ProductDetailView must have a model attribute; it is that attribute that defines what model to use.
Unfortunately, part 2 of your question doesn't really make sense; a view is not an instance of a model.
Have a look at the various methods and attributes of the detail view here, particularly the get_object method:
http://ccbv.co.uk/projects/Django/1.9/django.views.generic.detail/DetailView
def get_object(self, queryset=None):
"""
Returns the object the view is displaying.
By default this requires `self.queryset` and a `pk` or `slug` argument
in the URLconf, but subclasses can override this to return any object.
"""
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
if queryset is None:
queryset = self.get_queryset()
# Next, try looking up by primary key.
pk = self.kwargs.get(self.pk_url_kwarg)
slug = self.kwargs.get(self.slug_url_kwarg)
if pk is not None:
queryset = queryset.filter(pk=pk)
# Next, try looking up by slug.
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field: slug})
# If none of those are defined, it's an error.
if pk is None and slug is None:
raise AttributeError("Generic detail view %s must be called with "
"either an object pk or a slug."
% self.__class__.__name__)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
It's Django, so its fairly customizable, so it doesn't need to be called 'PK'. You could override that by using the pk_url_kwarg. By default the id field is the pk, unless you specify it in your model definition.
Django's DetailView uses the SingleObjectMixin mixin which has a method called get_object which will look for a pk_url_kwarg
pk = self.kwargs.get(self.pk_url_kwarg)
By default this is set to 'pk'
I have a django-rest-framework REST API with hierarchical resources. I want to be able to create subobjects by POSTing to /v1/objects/<pk>/subobjects/ and have it automatically set the foreign key on the new subobject to the pk kwarg from the URL without having to put it in the payload. Currently, the serializer is causing a 400 error, because it expects the object foreign key to be in the payload, but it shouldn't be considered optional either. The URL of the subobjects is /v1/subobjects/<pk>/ (since the key of the parent isn't necessary to identify it), so it is still required if I want to PUT an existing resource.
Should I just make it so that you POST to /v1/subobjects/ with the parent in the payload to add subobjects, or is there a clean way to pass the pk kwarg from the URL to the serializer? I'm using HyperlinkedModelSerializer and ModelViewSet as my respective base classes. Is there some recommended way of doing this? So far the only idea I had was to completely re-implement the ViewSets and make a custom Serializer class whose get_default_fields() comes from a dictionary that is passed in from the ViewSet, populated by its kwargs. This seems quite involved for something that I would have thought is completely run-of-the-mill, so I can't help but think I'm missing something. Every REST API I've ever seen that has writable endpoints has this kind of URL-based argument inference, so the fact that django-rest-framework doesn't seem to be able to do it at all seems strange.
Make the parent object serializer field read_only. It's not optional but it's not coming from the request data either. Instead you pull the pk/slug from the URL in pre_save()...
# Assuming list and detail URLs like:
# /v1/objects/<parent_pk>/subobjects/
# /v1/objects/<parent_pk>/subobjects/<pk>/
def pre_save(self, obj):
parent = models.MainObject.objects.get(pk=self.kwargs['parent_pk'])
obj.parent = parent
Here's what I've done to solve it, although it would be nice if there was a more general way to do it, since it's such a common URL pattern. First I created a mixin for my ViewSets that redefined the create method:
class CreatePartialModelMixin(object):
def initial_instance(self, request):
return None
def create(self, request, *args, **kwargs):
instance = self.initial_instance(request)
serializer = self.get_serializer(
instance=instance, data=request.DATA, files=request.FILES,
partial=True)
if serializer.is_valid():
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Mostly it is copied and pasted from CreateModelMixin, but it defines an initial_instance method that we can override in subclasses to provide a starting point for the serializer, which is set up to do a partial deserialization. Then I can do, for example,
class SubObjectViewSet(CreatePartialModelMixin, viewsets.ModelViewSet):
# ....
def initial_instance(self, request):
instance = models.SubObject(owner=request.user)
if 'pk' in self.kwargs:
parent = models.MainObject.objects.get(pk=self.kwargs['pk'])
instance.parent = parent
return instance
(I realize I don't actually need to do a .get on the pk to associate it on the model, but in my case I'm exposing the slug rather than the primary key in the public API)
If you're using ModelSerializer (which is implemented by HyperlinkedModelSerializer) it's as easy as implementing the restore_object() method:
class MySerializer(serializers.ModelSerializer):
def restore_object(self, attrs, instance=None):
if instance is None:
# If `instance` is `None`, it means we're creating
# a new object, so we set the `parent_id` field.
attrs['parent_id'] = self.context['view'].kwargs['parent_pk']
return super(MySerializer, self).restore_object(attrs, instance)
# ...
restore_object() is used to deserialize a dictionary of attributes into an object instance. ModelSerializer implements this method and creates/updates the instance for the model you specified in the Meta class. If the given instance is None it means the object still has to be created, so you just add the parent_id attribute on the attrs argument and call super().
So this way you don't have to specify a read-only field, or have a custom view/serializer.
More information:
http://www.django-rest-framework.org/api-guide/serializers#declaring-serializers
Maybe a bit late, but i guess this drf nested routers library could be helpful for that operation.