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)])
Related
I'm creating an app in which notifications model exists. That is why I need a behavior like this: when a person requests the notification page, the field is_read which is boolean turns from default FALSE to TRUE. The trouble is that there could be many of objects, so how to set to TRUE all of them?
Model:
class Notification(models.Model):
is_read = models.BooleanField(default=False)
notification_from = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="notiffrom")
notification_to = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="notifto")
View:
class UserNotificationView(ListModelMixin, GenericAPIView, CreateModelMixin):
serializer_class = NotificationSerializer
def get_queryset(self):
notification_to = self.kwargs["notification_to"]
return Notification.objects.filter(notification_to=notification_to)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
I know that there are bulk requests in Django but struggle with making them with DRF. Maybe there are some other ways?
You could add the update function in the get_queryset function.
class UserNotificationView(ListModelMixin, GenericAPIView, CreateModelMixin):
serializer_class = NotificationSerializer
def get_queryset(self):
notification_to = self.kwargs["notification_to"]
queryset = Notification.objects.filter(notification_to=notification_to)
queryset.update(is_read = True)
return queryset
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
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
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.
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.
I have a model like so:
class GiveAbsolute(serializers.Field):
def to_native(self,value):
# this where it give an error (self doesn't have request)
# what i want it to give full url
# like: http://www.blabla.com/othermodel/1
return reverse('link_to_othermodel',
args=[value],
request=self.request)
class SomethingSerializer(serializers.ModelSerializer):
# field with foreign key
othermodel = GiveAbsolute(source="othermodel.id")
class Meta:
model=Something
fields("fields1","othermodel")
is there a way to achieve this ?
thanks
From the source
The request object is an entry of the context dictionary. ie.
request = self.context.get('request')
In your case, just do:
self.request = self.context.get('request')
then build the url
self.request.build_absolute_uri(reverse('some_url_name'))
Based on the answer of mariodev, here is a reusable solution for Models ; I use it to provide URLs to service (see them as metheds) on django models.
Reusable components
serializers.py
class RequestAwareSerializer(serializers.ModelSerializer):
"""
A serializer which fields can access the request object.
"""
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(RequestAwareSerializer, self).__init__(*args, **kwargs)
class APIMethodField(serializers.Field):
""" To get the absolute URL of a method accessible via the API
"""
def __init__(self, url_action_name, *args, **kwargs):
self._url_name = url_action_name
super(APIMethodField, self).__init__(source='*', *args, **kwargs)
def to_native(self, obj):
"""
#param objid the ID of the object
#param method_url_name, the name of the url, as in urls.py
"""
return reverse_lazy(self._url_name, args=[obj.id],
request=self.parent.request)
views.py
class ChattyModelViewSet(ModelViewSet):
""" ModelViewSet which informs the serializer about the request
(abstract)
"""
def get_serializer(self, instance=None, data=None,
files=None, many=False, partial=False):
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
return serializer_class(instance, data=data, files=files, many=many,
partial=partial, context=context,
request=self.request)
Example use
urls.py
url(r'^v1/maildomain/(?P<maildomain_id>\d+)/check/$',
views.MailDomainDetail.as_view(), name='maildomain_dns_check')
serializers.py
class MailDomainSerializer(RequestAwareSerializer):
checkdns_url = APIMethodField(url_action_name='maildomain_dns_check')
class Meta:
model = MailDomain()
fields = ('name', 'checkdns_url')
views.py
class MailDomainView(ChattyModelViewSet):
model = MailDomain
serializer_class = MailDomainSerializer
The only thing in DRF, that has an access to request object is the view, so you need to figure out how to pass your request from view to serializer, for example in generic ListView you can use get_serializer.
Then, when you already have it in your serializer, you can use self.parent (which is a parent serializer) to capture it from the field itself:
class GiveAbsolute(serializers.Field):
def to_native(self,value):
return reverse('link_to_othermodel',
args=[value],
request=self.parent.request)
class SomethingSerializer(serializers.ModelSerializer):
# field with foreign key
othermodel = GiveAbsolute(source="othermodel.id")
class Meta:
model=Something
fields=("fields1","othermodel")
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(SomethingSerializer, self).__init__(*args, **kwargs)
class SomethingView(generics.ListAPIView):
model = Something
serializer_class = SomethingSerializer
def get_serializer(self, instance=None, data=None,
files=None, many=False, partial=False):
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
return serializer_class(instance, data=data, files=files, many=many,
partial=partial, context=context, request=self.request)