creating model while setting slug and user in Django Rest Framework - django

I have a model where I create an instance using django rest framework. The serializer complains about a missing slug and an empty ForeignKey-Field that is supposed to point to the User.
I tried to override perform_create in my serializer to set these values, but I realized that in the standard CreateModelMixin:
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
the method is_valid() is called before perform_create(). Therefore populating the foreign key for user and adding a slug does not work.
Is there a proper way to solve this problem or should I override create?
Update:
I was asked by #Rahul Gupta to add Model and Serializer. Here they are:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields=('id','name','slug',..., 'shop','category')
and
class Product(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
shop = models.ForeignKey(Shop)
category = models.ForeignKey("Category")
def __unicode__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Product, self).save(*args, **kwargs)
I should probably make myself a bit clearer. In the browser the user fills in a form to create a new Product instance. However the user does not enter shop or slug, as these are determined by automatically. Slug is derived from name and shop from a different model, that is linked to request.user.

You don't have to copy and paste code from the parent class, you can just call super in order to borrow functionality from the parent's create method:
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all().order_by('name')
serializer_class = ProductSerializer
def create(self, request, *args, **kwargs):
request.data['shop'] = drequest.user.shop.id
# Everything above is from Sebastian Langer's answer
# difference is below: just call parent's `create`:
return super(ProductViewSet, self).create(request, *args, **kwargs)

So I'll answer this myself. I've done something rather silly - I thought that I have to override the create method in the serializer when it really seems to be the method in the ViewSet that I have to override. Here is the code that did it for me:
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all().order_by('name')
serializer_class = ProductSerializer
def create(self, request, *args, **kwargs):
request.data['shop']=request.user.shop.id # <-- I ADDED THIS LINE
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
The comment "I ADDED THIS LINE" is in the line where I add functionality to the original method. It does the job, but there might be a more elegant solution. Something similar can be done for the slug.

If you using the rest framework and serializer after add this code in the model:
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Product, self).save(*args, **kwargs)
you need also add this code to the serializer function of the model:
slug = serializers.SlugField(required=False)
and you good to go.

Related

Return different serializer after create() in CreateAPIView in Django REST Framework

I'm using Django 2.2 and Django REST Framework.
I have to serializers for the same model.
class OrderListSerializer(serializers.ModelSerializer):
plan = PlanBaseSerializer(read_only=True, many=False)
class Meta:
model = Order
fields = [
'id', 'name', 'plan', 'pricing',
'created', 'completed',
]
class OrderCreateSerializer(serializers.ModelSerializer):
plan_pricing = serializers.IntegerField(required=True, write_only=True)
class Meta:
model = Order
fields = [
'plan_pricing'
]
def create(self, validated_data):
plan_pricing_ = validated_data.pop('plan_pricing', None)
try:
plan_pricing = PlanPricing.objects.get(pk=plan_pricing_)
except PlanPricing.DoesNotExists:
raise ValidationError('Plan pricing not available')
validated_data['plan'] = plan_pricing.plan
validated_data['amount'] = plan_pricing.price
return super().create(validated_data)
OrderListSerializer serializer is used for listing orders or order detail view and OrderCreateSerializer is used for creating a new order instance.
The view is
class CreateOrderView(generics.CreateAPIView):
serializer_class = OrderCreateSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
This is working fine as the order object is creating as expected. But the returned value contains no data.
I want to use OrderListSerializer to render saved order details after creating the order.
How to change the serializer class after creating the object?
Also, I have to trigger a signal after the object has been successfully created. What is the best place to trigger a signal?
Change CreateOrderView as below,
class CreateOrderView(generics.CreateAPIView):
serializer_class = OrderCreateSerializer
def perform_create(self, serializer):
return serializer.save(user=self.request.user)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer)
instance_serializer = OrderListSerializer(instance)
return Response(instance_serializer.data)
serializer.save() returns the instance that just created or updated. So we use that istance to pass to the OrderListSerializer and returning the corresponding response.
You could overwrite create(), and return whatever you want:
from rest_framework import response, status
(...)
def create(self, request, *args, **kwargs):
super().create(request, *args, **kwargs)
return response.Response(status=status.HTTP_201_CREATED)
(...)
There are several ways you can use here. First,
class CreateOrderView(generics.ListCreateAPIView):
serializer_class = OrderCreateSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def list(self, *args, **kwargs):
serializer_class = OrderListSerializer
serializer = serializer_class(self.get_queryset())
return Response(serializer.data)
The alternative would be a conditional if statement, where
if self.request.method=='POST':
self.serializer_class = OrderCreateSerializer
elif self.request.method=='GET':
self.serializer_class = OrderListSerializer

Django HiddenField value generated on Views

Overview: need to generate a codename value for a hidden field in order to send it to an external API (1) and after that include the generated codename field in database (2).
Issue: How can I send the generated codename (HiddenField) to database?
models.py
class Category(Model):
name = models.CharField(max_length=45)
class Animal(Model):
name = models.CharField(max_length=45)
codename = models.CharField(max_length=45)
categories = models.ManyToManyField(Category)
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = models.Category
fields = '__all__'
class AnimalSerializer(serializers.ModelSerializer):
codename = serializers.HiddenField(default="auto_replace_me")
class Meta:
model = models.Animal
fields = '__all__'
views.py
class CategoryViewSet(ModelViewSet):
queryset = models.Category.objects.all()
serializer_class = serializers.CategorySerializer
class AnimalViewSet(ModelViewSet, CreateModelMixin):
queryset = models.Animal.objects.all()
serializer_class = serializers.AnimalSerializer
def create(self, request, *args, **kwargs):
codename = generate_codename()
# (1) external API request (with codename included on request) and returns a boolean `result`
if result:
# will call create method from parent class BUT
# (2) it needs to save the codename in database
return super(AnimalViewSet, self).create(request, *args, **kwargs)
else:
return HttpResponseServerError()
def generate_codename():
return ''.join([random.choice(string.ascii_letters) for n in range(10)])
Following code should do the job:
class AnimalViewSet(ModelViewSet, CreateModelMixin):
queryset = models.Animal.objects.all()
serializer_class = serializers.AnimalSerializer
def perform_create(serializer, codename):
serializer.save(codename=codename)
def create_with_codename(self, request, codename, *args, **kwargs):
# this is almost the same as CreateModelMixin 'create'
# but with one change: passing codename to 'perform_create' method
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer, codename)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def create(self, request, *args, **kwargs):
codename = generate_codename()
# (1) external API request (with codename included on request) and returns a boolean `result`
if result:
# will call create method from parent class BUT
# (2) it needs to save the codename in database
return self.create_with_codename(request, codename, *args, **kwargs)
else:
return HttpResponseServerError()
So basically, you should write your own function create_with_codename to change default behaviour of CreateModelMixin def create(self, request, *args, **kwargs)
Most important thing was to pass codename argument to perform_create. According to docs, everytime when you need to save serializer with appending additional values to it, you should use serializer.save(extra_field=extra_value)
After learn from you guys, I got this solution (only changing views.py):
views.py
class CategoryViewSet(ModelViewSet):
queryset = models.Category.objects.all()
serializer_class = serializers.CategorySerializer
class AnimalViewSet(ModelViewSet, CreateModelMixin):
queryset = models.Animal.objects.all()
serializer_class = serializers.AnimalSerializer
codename = None
def perform_create(self, serializer):
serializer.save(codename=self.codename)
def create(self, request, *args, **kwargs):
self.codename = generate_codename()
# (1) external API request (with codename included on request) and returns a boolean `result`
if result:
# will call create method from parent class BUT
# (2) it needs to save the codename in database
return super(AnimalViewSet, self).create(request, *args, **kwargs)
else:
return HttpResponseServerError()
def generate_codename():
return ''.join([random.choice(string.ascii_letters) for n in range(10)])

AttributeError - object has no attribute 'create'

I'm trying to save a through model which has the following attributes via Django-rest-framework
when sending a POST (I'm trying to create a new instance), I get the following error:
AttributeError at /api/organisation/provider/
'EnabledExternalProvider' object has no attribute 'create'
any ideas as to what i'm doing incorrectly?
the through model in question is:
class EnabledExternalProvider(models.Model):
provider = models.ForeignKey(ExternalProvider, related_name='associatedProvider')
organisation = models.ForeignKey(Organisation, related_name='associatedOrg')
enabled = models.BooleanField(default=True)
tenantIdentifier = models.CharField('Tenant identifer for organisation', max_length = 128, null=True, blank=True)
def __str__(self):
return self.provider + '-' + self.organisation
my view is:
class EnabledExternalProvider(mixins.RetrieveModelMixin, mixins.UpdateModelMixin,generics.GenericAPIView):
serializer_class = ConnectedServiceSerializer
def get_queryset(self):
return EnabledExternalProvider.objects.filter(organisation=self.request.user.organisation_id)
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
# make sure to catch 404's below
obj = queryset.get(organisation=self.request.user.organisation_id)
self.check_object_permissions(self.request, obj)
return obj
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
and my serializer is:
class ConnectedServiceSerializer(serializers.ModelSerializer):
provider=ExternalProviderSerializer(read_only=True)
organisation=OrganisationDetailSerializer(read_only=True)
class Meta:
model = EnabledExternalProvider
fields = ( 'organisation', 'enabled', 'tenantIdentifier')
read_only_fields = ('organisation', 'provider')
I'm POSTing the following:
{"provider":"1","tenantIdentifier":"9f0e40fe-3d6d-4172-9015-4298684a9ad2","enabled":true}
Your view doesn't have that method because you haven't defined it, or inherited from a class that has it; your mixins provide retrieve and update, but not create.
You could add mixins.CreateModelMixin to the inheritance, but at this point you should really be using a ViewSet instead.

How to make a PATCH request using DJANGO REST framework

I am not very experience with Django REST framework and have been trying out many things but can not make my PATCH request work.
I have a Model serializer. This is the same one I use to add a new entry and ideally I Would want to re-use when I update an entry.
class TimeSerializer(serializers.ModelSerializer):
class Meta:
model = TimeEntry
fields = ('id', 'project', 'amount', 'description', 'date')
def __init__(self, user, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
super(TimeSerializer, self).__init__(*args, **kwargs)
self.user = user
def validate_project(self, attrs, source):
"""
Check that the project is correct
"""
.....
def validate_amount(self, attrs, source):
"""
Check the amount in valid
"""
.....
I tried to use a class based view :
class UserViewSet(generics.UpdateAPIView):
"""
API endpoint that allows timeentries to be edited.
"""
queryset = TimeEntry.objects.all()
serializer_class = TimeSerializer
My urls are:
url(r'^api/edit/(?P<pk>\d+)/$', UserViewSet.as_view(), name='timeentry_api_edit'),
My JS call is:
var putData = { 'id': '51', 'description': "new desc" }
$.ajax({
url: '/en/hours/api/edit/' + id + '/',
type: "PATCH",
data: putData,
success: function(data, textStatus, jqXHR) {
// ....
}
}
In this case I would have wanted my description to be updated, but I get errors that the fields are required(for 'project'and all the rest). The validation fails. If add to the AJAX call all the fields it still fails when it haves to retrieve the 'project'.
I tried also to make my own view:
#api_view(['PATCH'])
#permission_classes([permissions.IsAuthenticated])
def edit_time(request):
if request.method == 'PATCH':
serializer = TimeSerializer(request.user, data=request.DATA, partial=True)
if serializer.is_valid():
time_entry = serializer.save()
return Response(status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST)
This did not work for partial update for the same reason(the validation for the fields were failing) and it did not work even if I've sent all the fields. It creates a new entry instead of editing the existing one.
I would like to re-use the same serializer and validations, but I am open to any other suggestions.
Also, if someone has a piece of working code (ajax code-> api view-> serializer) would be great.
class DetailView(APIView):
def get_object(self, pk):
return TestModel.objects.get(pk=pk)
def patch(self, request, pk):
testmodel_object = self.get_object(pk)
serializer = TestModelSerializer(testmodel_object, data=request.data, partial=True) # set partial=True to update a data partially
if serializer.is_valid():
serializer.save()
return JsonResponse(code=201, data=serializer.data)
return JsonResponse(code=400, data="wrong parameters")
Documentation
You do not need to write the partial_update or overwrite the update method. Just use the patch method.
Make sure that you have "PATCH" in http_method_names. Alternatively you can write it like this:
#property
def allowed_methods(self):
"""
Return the list of allowed HTTP methods, uppercased.
"""
self.http_method_names.append("patch")
return [method.upper() for method in self.http_method_names
if hasattr(self, method)]
As stated in documentation:
By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the partial argument in order to allow partial updates.
Override update method in your view:
def update(self, request, *args, **kwargs):
instance = self.get_object()
serializer = TimeSerializer(instance, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save(customer_id=customer, **serializer.validated_data)
return Response(serializer.validated_data)
Or just override method partial_update in your view:
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
Serializer calls update method of ModelSerializer(see sources):
def update(self, instance, validated_data):
raise_errors_on_nested_writes('update', self, validated_data)
# Simply set each attribute on the instance, and then save it.
# Note that unlike `.create()` we don't need to treat many-to-many
# relationships as being a special case. During updates we already
# have an instance pk for the relationships to be associated with.
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
Update pushes the validated_data values to the given instance. Note that update should not assume all the fields are available. This helps to deal with partial updates (PATCH requests).
The patch method is worked for me using viewset in DRF. I'm changing you code:
class UserViewSet(viewsets.ModelViewSet):
queryset = TimeEntry.objects.all()
serializer_class = TimeSerializer
def perform_update(self, serializer):
user_instance = serializer.instance
request = self.request
serializer.save(**modified_attrs)
return Response(status=status.HTTP_200_OK)
Use ModelViewSet instead and override perform_update method from UpdateModelMixin
class UserViewSet(viewsets.ModelViewSet):
queryset = TimeEntry.objects.all()
serializer_class = TimeSerializer
def perform_update(self, serializer):
serializer.save()
# you may also do additional things here
# e.g.: signal other components about this update
That's it. Don't return anything in this method. UpdateModelMixin has implemented update method to return updated data as response for you and also clears out _prefetched_objects_cache. See the source code here.
I ran into this issues as well, I solved it redefining the get_serializer_method and adding custom logic to handle the partial update. Python 3
class ViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.action == "partial_update":
return PartialUpdateSerializer
Note: you may have to override the partial_update function on the serializer. Like so:
class PartialUpdateSerializer(serializers.Serializer):
def partial_update(self, instance, validated_data):
*custom logic*
return super().update(instance, validated_data)
Another posiblity is to make the request by URL. For example, I have a model like this
class Author(models.Model):
FirstName = models.CharField(max_length=70)
MiddleName = models.CharField(max_length=70)
LastName = models.CharField(max_length=70)
Gender = models.CharField(max_length=1, choices = GENDERS)
user = models.ForeignKey(User, default = 1, on_delete = models.CASCADE, related_name='author_user')
IsActive = models.BooleanField(default=True)
class Meta:
ordering = ['LastName']
And a view like this
class Author(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
So can enter http://127.0.0.1:8000/author/ to get or post authors. If I want to make a PATCH request you can point to http://127.0.0.1:8000/author/ID_AUTHOR from your client. For example in angular2, you can have something like this
patchRequest(item: any): Observable<Author> {
return this.http.patch('http://127.0.0.1:8000/author/1', item);
}
It suppose you have configured your CORS and you have the same model in back and front.
Hope it can be usefull.

Return current user on details

I am trying to build an API view, to handle user management using django rest framework version 2.3.10 with django 1.6. I tried to build a ModelViewSet which based on the URL pk value it would return either current user or public user.
I tried to add a dispatch function which will assigned pk to current user, but it seems like this function is running too soon that its always seeing the user as anonymous
class UserViewSet(viewsets.ModelViewSet):
"""
"""
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsOwnerOrCreateOnly, )
def dispatch(self, request, *args, **kwargs):
if kwargs.get('pk') == 'current' and not request.user.is_anonymous():
kwargs['pk'] = request.user.pk
resp = super(CurrentUserViewSet, self).dispatch(request, *args, **kwargs)
return resp
I tried to do the below, which works
class UserViewSet(viewsets.ModelViewSet):
"""
"""
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsOwnerOrCreateOnly, )
def retrieve(self, request, *args, **kwargs):
if self.kwargs.get('pk') == u'current' and not request.user.is_anonymous():
self.kwargs['pk'] = request.user.pk
return super(CurrentUserViewSet, self).retrieve(request, *args, **kwargs)
but, I don't want to override each and every function on several ModelViewSet classes I have, so, is there a way to use something similar to the dispatcher whereby I can check if the pk is equal to "current" and then assign current user to it?
Another question, how can I change the returned fields programmatically? for example when querying current user I want to include the first and last name from the user model, but when querying by primary key, I want first and last name to not return as response? any suggestions on how todo that?
I got the same problem I solved it by using method "initial" instead of "dispatch"
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsOwnerOrCreateOnly, )
def initial(self, request, *args, **kwargs):
# logic - code #
if kwargs.get('pk') == 'current' and not request.user.is_anonymous():
kwargs['pk'] = request.user.pk
# end #
resp = super(CurrentUserViewSet, self).initial(request, *args, **kwargs)
return resp
see " dispatch "
method in https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/views.py
for better understanding.
Override viewsets.ModelViewSet class with your pk check implementation and use that new class, something like this:
class GenericUserViewSet(viewsets.ModelViewSet):
def retrieve(self, request, *args, **kwargs):
if self.kwargs.get('pk') == u'current' and not request.user.is_anonymous():
self.kwargs['pk'] = request.user.pk
return super(CurrentUserViewSet, self).retrieve(request, *args, **kwargs)
class UserViewSet(GenericUserViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsOwnerOrCreateOnly, )
And for the second question, perhaps creating two serializers (public and current) and changing serializer_class to either one of them in init of GenericUserViewSet may do the trick, I haven't tested this but it's an idea:
class GenericUserViewSet(viewsets.ModelViewSet):
def __init__(self, *args, **kwargs):
if self.kwargs.get('pk') == u'current' and not request.user.is_anonymous():
self.serializer_class = UserSerializer
else:
self.serializer_class = PublicUserSerializer
super(GenericUserViewSet, self).__init__(*args, **kwargs)
I'm assuming that you want to save the current user to your DB model, yes?
If so this should be fairly easy to fix, just add this method to your views:
def pre_save(self, obj):
obj.user = self.request.user
This will execute just before the model is saved. I use this all the time and it works great.
The other thing you can do is write a mixin class in a generic way that does want you want then inherit it in each of the views you need it in. Assuming that is that you have a solution that works, but just don't want to mimic you code all over the place.