view count increase in DRF - django

I need to increase the view count with 1 on each refresh. I'm not sure about the method since I'm new to DRF. Thanks in advance.
models.py
class Module(models.Model):
name = models.CharField(max_length=250, default="")
view_count = models.IntegerField(default=0)
serializers.py
class ModuleSerializer(serializers.ModelSerializer):
class Meta:
model = Module
fields = "__all__"
views.py
class ModuleView(generics.ListAPIView):
queryset = Module.objects.all()
serializer_class = ModuleSerializer
def get(self, request):
obj = self.get_object
print(obj)
obj.view_count = obj.view_count + 1
obj.save(view_count="view_count")
return super().get(request)

i implemented view count with F expression because a view count is basicly a race condidition and the docs clearly state that
Avoiding race conditions using F()
Documentation

class ElonDetail(generics.RetrieveAPIView):
queryset = Elon.objects.all()
serializer_class = ElonDetailSerializer
lookup_field = 'slug'
#retrieve
def retrieve(self, request, *args, **kwargs):
obj = self.get_object()
print(obj)
obj.view = obj.view + 1
obj.save(update_fields=['view',])
serializer = self.get_serializer(obj)
return Response(serializer.data, status=200)
here i used RetrieveAPIView

Related

Django REST Serializers - passing a value when creating a new object

I'm having problems in passing an argument when creating an object with Django REST serializers.
models.py
class Project(models.Model):
name = models.CharField(max_length=200, unique=False)
description = models.TextField()
...
class Hypothesis(models.Model):
hypothesis = models.CharField(max_length=200, unique=False)
project = models.ManyToManyField(Project)
test_conducted = models.ManyToManyField('Interview', through='HypothesesFeedback')
...
serializers.py
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ['name','description','company_name']
def __init__(self, *args, **kwargs):
super(ProjectSerializer, self).__init__(*args, **kwargs)
class HypothesisSerializer(serializers.ModelSerializer):
class Meta:
model = Hypothesis
fields = ['hypothesis','area','details', 'project']
def get_alternate_name(self, obj):
project = self.context["project_id"]
views.py
class ProjectRestCreate(LoginRequiredMixin, generics.ListCreateAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
...
class HypothesisRestCreate(LoginRequiredMixin, generics.ListCreateAPIView):
queryset = Hypothesis.objects.all()
serializer_class = HypothesisSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context["project_id"] = 8 #self.kwargs['project_id']
return context
...
I'm currently unable to default the project id when creating a new object for class hypothesis. In the example above, I'm hardcoding a value just for test purposes, but what I'd need to reach is that when I create a new hypothesis starting from a given project page, the project is automatically filled, rather than the user having to manually select it.
Using Django, rather than Django REST, I'd be able to achieve that using the code below:
class HypothesisCreate(generic.CreateView):
model = Hypothesis
form_class = HypothesisForm
template_name = 'new_hypothesis.html'
def form_valid(self, form):
obj = form.save()
project = form.data['project']
p = Project.objects.filter(id=project)
obj.project.set(p)
return super(HypothesisCreate, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(HypothesisCreate, self).get_context_data(**kwargs)
context['p_id'] = self.kwargs['project']
return context
def get_success_url(self, **kwargs):
return reverse('project_detail', kwargs={'pk': self.kwargs['project']})
Any idea on how to reach the same with Django REST serializers?
EDIT #1
models.py
class Project(models.Model):
name = models.CharField(max_length=200, unique=False)
description = models.TextField()
...
class Hypothesis(models.Model):
hypothesis = models.CharField(max_length=200, unique=False)
project = models.ForeignKey(Project, on_delete= models.CASCADE)
test_conducted = models.ManyToManyField('Interview', through='HypothesesFeedback')
...
using Django rather than Django REST, I achieve the defaulting of the project when creating a new hypothesis, using get_context_data:
VIEW:
class HypothesisCreate(generic.CreateView):
model = Hypothesis
form_class = HypothesisForm
template_name = 'new_hypothesis.html'
def form_valid(self, form):
obj = form.save()
project = form.data['project']
p = Project.objects.filter(id=project)
obj.project.set(p)
return super(HypothesisCreate, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(HypothesisCreate, self).get_context_data(**kwargs)
context['p_id'] = self.kwargs['project']
return context
def get_success_url(self, **kwargs):
return reverse('project_detail', kwargs={'pk': self.kwargs['project']})
FORM:
class HypothesisForm(ModelForm):
class Meta:
model = Hypothesis
fields = ['hypothesis','area','details']
def __init__(self, *args, **kwargs):
super(HypothesisForm, self).__init__(*args, **kwargs)
self.fields["project"] = forms.CharField(widget=forms.HiddenInput())
I tried doing the same with the serializer, but without success.
VIEW:
class HypothesisRestCreate(LoginRequiredMixin, generics.ListCreateAPIView):
queryset = Hypothesis.objects.all()
serializer_class = HypothesisSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context["project_id"] = 8 #self.kwargs['project_id']
return context
SERIALIZER:
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ['name','description','company_name']
def __init__(self, *args, **kwargs):
super(ProjectSerializer, self).__init__(*args, **kwargs)
class HypothesisSerializer(serializers.ModelSerializer):
class Meta:
model = Hypothesis
fields = ['hypothesis','area','details', 'project'] #
def get_alternate_name(self, obj):
project = self.context["project_id"]
any idea what should I do differently?
Modify the HypothesisRestCreate as following
class HypothesisRestCreate(LoginRequiredMixin, generics.ListCreateAPIView):
queryset = Hypothesis.objects.all()
serializer_class = HypothesisSerializer
def create(self, request, *args, **kwargs):
request.data['project'] = self.kwargs['project_id']
return super(HypothesisRestCreate, self).create(request, *args, **kwargs)
# def get_serializer_context(self): -- dont need for this purpose
and HypothesisSerializer as following
class HypothesisSerializer(serializers.ModelSerializer):
class Meta:
model = Hypothesis
fields = ['hypothesis','area','details', 'project']
# def get_alternate_name(self, obj): --dont need for this purpose

Reducing queries on serialization with SerializerMethodField

I currently have a serializer which uses two SerializerMethodField that access the same nested object, resulting in two db calls:
# models.py
class Onboarding(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
is_retailer_created = models.BooleanField(default=False)
is_complete = models.BooleanField(default=False)
# views.py
class StateView(RetrieveAPIView):
serializer_class = serializers.UserSerialiser
def get_object(self):
return self.request.user
# serializers.py
class UserSerialiser(serializers.ModelSerializer):
is_onboarded = serializers.SerializerMethodField()
is_loyalty_onboarded = serializers.SerializerMethodField()
class Meta:
model = models.User
fields = ('is_onboarded', 'is_loyalty_onboarded')
def get_is_onboarded(self, obj):
onboarding = obj.onboarding_set.first()
if onboarding:
return onboarding.is_retailer_created
return False
def get_is_loyalty_onboarded(self, obj):
onboarding = obj.onboarding_set.first()
if onboarding:
return onboarding.is_complete
return False
I'd like to roll this into one call if possible. Normally it would be possible to just use prefetch_related, but since the get_object is returning the specific user (and not a queryset) I don't think that solution works here.
Is there a way to prefetch the Onboarding model with the user? Or failing that have a single call to Onboarding instead of two?
Turns out it was a little easier than anticipated. Just needed to save the Onboarding object in __init__.
# serializers.py
class UserSerialiser(serializers.ModelSerializer):
is_onboarded = serializers.SerializerMethodField()
is_loyalty_onboarded = serializers.SerializerMethodField()
class Meta:
model = models.User
fields = ('is_onboarded', 'is_loyalty_onboarded')
def __init__(self, *args, **kwargs):
super(UserSerialiser, self).__init__(*args, **kwargs)
self.onboarding = self.instance.onboarding_set.first()
def get_is_onboarded(self, obj):
if self.onboarding:
return self.onboarding.is_retailer_created
return False
def get_is_loyalty_onboarded(self, obj):
if self.onboarding:
return self.onboarding.is_complete
return False

How to return specific field queryset DRF

I want to create a custom queryset class that returns different fields to pre-define two cases.
when DateField is greater than today
when it's less than today.
In case it's greater return all fields, else return only date_to_open and post_name fields.
views.py
class GroupDetail(generics.RetrieveAPIView):
serializer_class = serializers.GroupDetailsSerializer
permission_classes = (IsAuthenticated, )
def greater(self):
return models.Group.objects.filter(shared_to=self.request.user,
date_to_open__gt=timezone.now()).exists()
def get_queryset(self, *args, **kwargs):
if self.greater():
query_set = models.Group.objects.filter(shared_to=self.request.user,
date_to_open__gt=timezone.now())
else:
query_set = SPECIFIC FIELDS
return query_set
serializers.py
class GroupDetailsSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.name')
images = GroupImageSerializer(many=True, read_only=True)
shared_to = serializers.SlugRelatedField(queryset=models.UserProfile.objects.all(),
slug_field='name', many=True)
class Meta:
model = models.Group
fields = ('id', 'group_name', 'owner', 'group_text', 'created_on', 'date_to_open', 'shared_to',
'images', )
Ok. Thanks to #ArakkalAbu comment I've just overridden get_serializer_class()
views.py
class GroupDetail(generics.RetrieveAPIView):
queryset = models.Group.objects.all()
serializer_class = serializers.GroupDetailsSerializer
permission_classes = (IsAuthenticated, )
def greater(self):
return models.Group.objects.filter(shared_to=self.request.user, date_to_open__gt=timezone.now()).exists()
def get_serializer_class(self):
if self.greater():
return serializers.GroupDetailsSerializer
else:
return serializers.ClosedGroupDetailsSerializer
You can keep using the same logic and use values_list to return specific values out of the Query set. The returned values is also a query set
def get_queryset(self, *args, **kwargs):
if self.greater():
return models.Group.objects.filter(shared_to=self.request.user, date_to_open__gt=timezone.now())
else:
return models.Group.objects.filter(shared_to=self.request.user, date_to_open__lt=timezone.now()).values_list('date_to_open', 'post_name' , flat = True)

Increasing the view by 1 each time some url hits?

Here I want to increase the number of views by 1 each time the detail view is called.How can I do it ?
class Package(models.Model):
name = models.CharField(max_length=255,unique=True)
slug = AutoSlugField(populate_from='name')
package_desc = models.TextField()
views = models.IntegerField(default=0) #want to increase views by 1 when detail_package url hits
views.py
class DetailPackage(generics.RetrieveAPIView):
serializer_class = PackageDetailSerializer
lookup_field = 'slug'
queryset = Package.objects.all()
urls.py
path('<slug>/detail/', DetailPackage.as_view(), name='detail_package'),
I am very new to django rest framework.So am I going the right way by using the generics API view for such cases ?
You can override the retrieve method on (generics.RetrieveAPIView)..take a look at this http://www.cdrf.co/3.9/rest_framework.generics/RetrieveAPIView.html#retrieve
Here is an example
from django.db.models import F
class DetailPackage(generics.RetrieveAPIView):
...
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
# increment the value
type(instance).objects.filter(pk=instance.pk).update(
views=F('views') + 1,
)
return Response(serializer.data)
As #Aprimus suggested I solved this by overriding the retrieve() method like this:
Please correct me if I didn't do it correctly.
class DetailPackage(generics.RetrieveAPIView):
serializer_class = PackageDetailSerializer
lookup_field = 'slug'
queryset = Package.objects.all()
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
instance.views += 1
instance.save()
serializer = self.get_serializer(instance)
return Response(serializer.data)

Check a field in model in generics.RetrieveDestroyAPIView

I am writing a rest API. this is my view:
class OrderDeleteAPIView(generics.RetrieveDestroyAPIView):
queryset = Order.objects.all()
serializer_class = OrderDeleteSerializer
# permission_classes = (OwnerCanManageOrReadOnly,)
lookup_field = 'id'
and this is its model:
class Order(models.Model):
product = models.ForeignKey(Product)
customer = models.ForeignKey(Customer, null=True)WAITING = 'WA'
PREPARATION = 'PR'
READY = 'RD'
DELIVERED = 'DV'
STATUS_CHOICES = (
(WAITING, 'waiting'),
(PREPARATION, 'preparation'),
(READY, 'ready'),
(DELIVERED, 'delivered'),
)
status = models.CharField(
max_length=2,
choices=STATUS_CHOICES,
default=WAITING,
)
and:
class Customer(models.Model):
name = models.CharField(max_length=40)
customer_email = models.EmailField()
def __str__(self):
return self.name
and this is its serializer:
class OrderDeleteSerializer(ModelSerializer):
class Meta:
model = Order
fields = '__all__'
What should I do if I want the object(order) can be deleted, only when the status field is 'waiting' ?
You can implement some checks, in the destroy and return an error message as response in case the constraints are not met:
class OrderDeleteAPIView(generics.RetrieveDestroyAPIView):
queryset = Order.objects.all()
serializer_class = OrderDeleteSerializer
# permission_classes = (OwnerCanManageOrReadOnly,)
lookup_field = 'id'
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if instance.status != Order.WAITING:
return JsonResponse(
status=412,
data={'status':'false',
'message': 'status should be WAITING'}
)
super(OrderDeleteAPIView, self).destroy(request, *args, **kwargs)
Of course you can return any sort of answer (not per se a JSON response, nor does the status has to be 412). Usually HTTP status code 412 means that 412 Precondion failed.
The question is not about permissions, but it is a close situation.
If you want to check permissions, there is a way to do this via overridden check_permissions:
from functools import lru_cache
from rest_framework.exceptions import PermissionDenied
class OrderDeleteAPIView(generics.RetrieveDestroyAPIView):
serializer_class = OrderDeleteSerializer
permission_classes = (OwnerCanManageOrReadOnly,)
lookup_field = 'id'
#lru_cache
def get_object(self, *args, **kwargs):
return Order.objects.get(self.kwargs['order_id'])
def check_permissions(self, request):
# this method will call OwnerCanManageOrReadOnly first
super().check_permissions(request)
# and then do other checks
instance = self.get_object()
if instance.owner != request.user:
raise PermissionDenied()
As you can notice, I use #lru_cache to cache get_object. The reason to use it is because after check_permissions there will be another method (in this case retrieve or destroy) which will call this method again. To reduce database requests count I use caching.