Add count whenever any entity is fetched Django REST Framework - python

My model looks like
class Article(models.Model):
article_type = models.ForeignKey(
ArticleType,
on_delete=models.CASCADE,
related_name='articles'
)
title = models.CharField(
max_length=100,
verbose_name='Article Title'
)
count = models.IntegerField(
verbose_name='Frequency Count'
)
def __str__(self):
return self.title
and my urls.py
router = DefaultRouter()
router.register('article', ArticleViewSet, basename='article')
urlpatterns = [
path('viewset/', include(router.urls)),
]
Now I wan't to add functionality such that whenever any article is fetched i.e
http://127.0.0.1:8000/viewset/article/{pk}
than 'count' of article of id=pk becomes count = count+1
so that I can sort them according to this count.

This can be achieved by sending a request like fetch=true or seen=true from client side whenever the api been fetched by the client i.e the client will sent you
fetch=true whenever fetch the api and from backend you have to catch that flag and have to check if fetch=true and increase instance.count += 1 and save the change in your model.
CODE: First change your model by providing count field a default value, this time its 0.
class Article(models.Model):
article_type = models.ForeignKey(
ArticleType,
on_delete=models.CASCADE,
related_name='articles'
)
title = models.CharField(
max_length=100,
verbose_name='Article Title'
)
count = models.IntegerField(
verbose_name='Frequency Count',
default=0
)
def __str__(self):
return self.title
and then do migration.
and then VIEW
class ArticleViewSet(viewsets.ViewSet):
def retrieve(self, request, pk=None):
queryset = Article.objects.all()
fetch = request.GET.get('fetch', False)
article = get_object_or_404(queryset, pk=pk)
if fetch:
article.count += 1
article.save()
serializer = ArticleSerializer(article)
return Response(serializer.data)
and then request with article/1/?fetch=true
you can also do it without fetch flag too,
class ArticleViewSet(viewsets.ViewSet):
def retrieve(self, request, pk=None):
queryset = Article.objects.all()
article = get_object_or_404(queryset, pk=pk)
article.count += 1
article.save()
serializer = ArticleSerializer(article)
return Response(serializer.data)
now reqeust with article/1/

I figured out from #Tanvir's answer
When we retrieve object we can update the count there itself i.e
def retrieve(self, request):
obj = self.get_object()
obj.count = obj.count+1
obj.save()
serializer = self.get_serializer(obj)
return Response(serializer.data)

Related

creating post on a review model

I need help leaving a review to a another model as a user, ive been having errrors and tried a lot of solutions.
error
FieldError at /reviews/1
Cannot resolve keyword 'user' into field. Choices are: id, pet_owner, pet_owner_id, rating, review, sitter, sitter_id
This is my view function:
def post(self, request, pk):
user = Review(pet_owner = request.user)
sitter = get_object_or_404(Sitter, pk=pk)
data = request.data
review = Review.objects.create(
pet_owner_id = user,
sitter= sitter,
rating=data['rating'],
review=data['review']
)
return Response('Review Added', status=status.HTTP_400_BAD_REQUEST)
and these are the models:
class Review(models.Model):
review = models.CharField(max_length=500)
rating = models.DecimalField(max_digits=7, decimal_places=2)
sitter = models.ForeignKey(Sitter,on_delete=models.CASCADE,null=True)
pet_owner = models.ForeignKey(get_user_model(),on_delete=models.CASCADE,null=True)
class Sitter(models.Model):
first_name = models.CharField(max_length=255, default='Jane')
last_name = models.CharField(max_length=255, default='Doe')
zipcode = models.CharField(max_length = 5, default='12345')
supersitter = models.BooleanField(null=True, blank=True)
price = models.DecimalField(max_digits=7, decimal_places=2)
numReviews = models.IntegerField(null=True, blank=True, default=0)
rating = models.DecimalField(max_digits=7, decimal_places=2, default=0)
this is my review model and sitter model
def post(self, request):
"""Create request"""
print(request.data)
# Add user to request data object
# Serialize/create review
review_user = request.user
review_data = Review(pet_owner = review_user)
review = ReviewSerializer(review_data, data=request.data)
# If the review data is valid according to our serializer...
if review.is_valid():
# Save the created review & send a response
r = review.save()
return Response({ 'review': review.data }, status=status.HTTP_201_CREATED)
# # If the data is not valid, return a response with the errors
return Response(review.data, status=status.HTTP_400_BAD_REQUEST)
we also tried this
user should be a simple User object, so:
def post(self, request, pk):
sitter = get_object_or_404(Sitter, pk=pk)
data = request.data
review = Review.objects.create(
pet_owner=request.user,
sitter=sitter,
rating=data['rating'],
review=data['review']
)
return Response('Review Added', status=status.HTTP_400_BAD_REQUEST)
You can slightly optimize the method by using the pk immediately:
def post(self, request, pk):
review = Review.objects.create(
pet_owner=request.user,
sitter_id=pk,
rating=data['rating'],
review=data['review']
)
return Response('Review Added', status=status.HTTP_400_BAD_REQUEST)
but it might be better to work with a serializer to properly validate and clean the request data.

Efficient Count of Related Models in Django

In my Django app, when I add the host_count field to my serializer to get the number of hosts for each domain, the performance of the API response suffers dramatically.
Without host_count: 300ms
With host_count: 15s
I tried adding 'host_set' to the prefetch_related method but it did not help.
Would an annotation using Count help me here? How can I optimize the fetching of this value?
serializers.py
class DomainSerializer(serializers.Serializer):
name = serializers.CharField(read_only=True)
org_name = serializers.CharField(source='org.name', read_only=True)
created = serializers.DateTimeField(read_only=True)
last_host_search = serializers.DateTimeField(read_only=True)
host_count = serializers.SerializerMethodField()
def get_host_count(self, obj):
return Host.objects.filter(domain=obj).count()
views.py
class DomainList(generics.ListAPIView):
def get(self, request, format=None):
domains = Domain.objects.prefetch_related('org').all()
serializer = DomainSerializer(domains, many=True)
return Response(serializer.data)
models.py
class Domain(models.Model):
created = models.DateTimeField(auto_now_add=True)
last_host_search = models.DateTimeField(auto_now=True)
name = models.CharField(unique=True, max_length=settings.MAX_CHAR_COUNT, blank=False, null=False)
org = models.ForeignKey(Org, on_delete=models.CASCADE, blank=True, null=True)
You can work with .annotate(…) [Django-doc] to count the related objects in the same query:
from django.db.models import Count
class DomainList(generics.ListAPIView):
def get(self, request, format=None):
domains = Domain.objects.prefetch_related('org').annotate(
host_count=Count('host')
)
serializer = DomainSerializer(domains, many=True)
return Response(serializer.data)
In the serializer, you then simply retrieve the corresponding attribute:
class DomainSerializer(serializers.Serializer):
name = serializers.CharField(read_only=True)
org_name = serializers.CharField(source='org.name', read_only=True)
created = serializers.DateTimeField(read_only=True)
last_host_search = serializers.DateTimeField(read_only=True)
host_count = serializers.IntegerField(read_only=True)
This will make a query that looks like:
SELECT domain.*, org.*, COUNT(host.id)
FROM domain
LEFT OUTER JOIN org ON domain.org_id = org.id
LEFT OUTER JOIN host ON host.domain_id = domain.id
GROUP BY domain.id, org.id

how to add foriegnkey object and connnect it a post object through a single endpoint in Django rest Framework

models.py
class PostAdvertisment(models.Model):
# post=models.ForeignKey(Post,on_delete=models.CASCADE,null=True,blank=True)
created_at=models.DateTimeField(auto_now_add=True)
title=models.CharField(max_length=255,null=True,blank=True)
url=models.URLField(null=True,blank=True)
advertizing_content= models.TextField(null =True ,blank=True)
def __str__(self):
return f'{self.title}'
class Post(models.Model):
# created_at=models.DateTimeField(efault=datetime.now, blank=True)
created_at=models.DateTimeField(auto_now_add=True)
author=models.ForeignKey(User,on_delete=models.CASCADE,related_name="post")
title=models.CharField(max_length=128,null=True,blank=True)
rate=models.IntegerField(validators=[MinValueValidator(1),MaxValueValidator(5)],default=True,null=True,blank=True)
# rating=models.IntegerField(null=True,blank=True)
content=models.TextField(null=True,blank=True)
review=models.CharField(max_length=250,null=True,blank=True)
url=models.URLField(null=True,blank=True)
voters = models.ManyToManyField(settings.AUTH_USER_MODEL,blank=True,related_name="post_voters")
tags = TaggableManager(blank=True)
comments=models.ManyToManyField('Comment',blank=True,related_name="comments_post")
anonymous = models.BooleanField(default=False, blank=True)
fake = models.BooleanField(default=False, blank=True)
genuine = models.ManyToManyField(settings.AUTH_USER_MODEL , blank=True, related_name="post_genuines")
spam = models.ManyToManyField(settings.AUTH_USER_MODEL , blank=True, related_name="post_spames")
advertisement=models.ForeignKey(PostAdvertisment,on_delete=models.CASCADE,null=True,blank=True)
def __str__(self):
return f'{self.content}'
def get_absolute_url(self):
return reverse('post:post_detail' , kwargs={'post_id':Post.id})
so here is my serializers.py
class PostSerializer(TaggitSerializer,serializers.ModelSerializer):
tags = TagListSerializerField()
author = serializers.StringRelatedField(read_only=True)
comments = CommentSerializer(many=True, required=False, read_only=True)
# title = serializers.CharField()
advertisement = PostAdvertisementSerializer()
# advertisement = serializers.SlugRelatedField(
# queryset=PostAdvertisment.objects.all(),
# slug_field='advertisement'
# )
# category_name = serializers.CharField(source='advertisement.title')
class Meta:
model = Post
fields = ('id','title','rate','author','content','review','url','tags', 'fake','comments', 'created_at', 'anonymous','advertisement')
# def create(self, validated_data):
# tag = validated_data.pop('advertisement')
# tag_instance, created =PostAdvertisment.objects.get_or_create(title=tag)
# article_instance = Post.objects.create(**validated_data, advertisement=tag_instance)
# return article_instance
# def create(self, validated_data):
# serializer = self.get_serializer(data=self.request.data)
# advertisment = self.request.data.pop('advertisement')
# company_instance = PostAdvertisment.objects.filter(id=advertisment).first()
# if not serializer.is_valid():
# print(serializer.errors)
# data = serializer.validated_data
# serializer.save(PostAdvertisment=company_instance)
# headers = self.get_success_headers(serializer.data)
# return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def create(self,validated_data):
advertisement=validated_data.pop('advertisement')
post= Post.objects.create(**validated_data)
for advertise in advertisement:
PostAdvertisment.object.create(**advertise)
return post
so the commented part of the code is something which I've Tried
differnt appraoches gave me differnt kidn of error but none of them have worked
https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers
I've followeed this
but its no use when ever i try to post an object either that advertisment might be null or it gives me some kind of strange error depening on the create method i have used
"advertisement":[]
this is what the error
{
"advertisement": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got list."
]
}
}
when i changed it to {}
"advertisement": null
but when i tried to give data to it
AttributeError at /api/post/
type object 'PostAdvertisment' has no attribute 'object'
Request Method: POST
Request URL: http://localhost:8000/api/post/
im not sure how to add data to nested objects
You missprinted, not PostAdvertisment.object.create(**advertise) but PostAdvertisment.objects.create(**advertise) you missed "s".
Advice you to read the error(traceback).
First, I don't think the advertisement is expected to be a list. Based on the Post model and PostSerializer, advertisement is only one.
After creating a PostAdvertisement you also have to update post.advertisement/post.advertisement_id. This is how I think it would be:
advertisement=validated_data.pop('advertisement')
post = Post.objects.create(**validated_data)
post.advertisement = PostAdvertisment.objects.create(**advertisement)
post.save()
return post
You could also create the PostAdvertisement first then create the Post so there is only 2 db queries instead of 3:
validated_data["advertisement"] = PostAdvertisment.objects.create(**validated_data["advertisement"])
post = Post.objects.create(**validated_data)
return post

Adding a row to an m2m table using DRF

I have the below representation in my models.py
class Agent(models.Model):
name = models.CharField(max_length=200)
user = models.OneToOneField(SampignanUser, on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Project(models.Model):
client = models.ForeignKey(Client, related_name='projects', on_delete=models.CASCADE)
agent = models.ManyToManyField(Agent)
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
I want to create a rest endpoint where in i can have an agent apply for a particular project (i.e - create a row in the Project-agents table). Is there a particular way i can do this? Right now , i've tried the below approach
urls.py
urlpatterns = [
path('projects/<int:project_id>/apply/', views.project_application, name='apply')
]
views.py
#api_view(['GET','POST'])
def project_application(request, project_id):
if request.method == 'GET':
serializer = ProjectApplicationSerializer()
// show an empty form to the user
return Response(serializer.data)
elif request.method == 'POST':
serializer = ProjectApplicationSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
My serializers.py
class ProjectApplicationSerializer(serializers.ListSerializer):
agent = AgentSerializer
project = ProjectSerializer
It doesnt seem to work however , i get the below error from Django
`child` is a required argument.
I can advice you to use serializers.ModelSierializer. So it will look like:
class ProjectModelSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = [..., 'agent',....] # you can use exclude also
def to_representation(self, instance):
self.fields['agent'] = AgentSerializer(many=True)
return super().to_representation(instance)
Here, ModelSerializer automatically handle many to many field. Moreover, in showing your results you can return agent as an object in defining it in to_representation(self, instance) method of serializer. So it will not only return id of agents in array, but extra information as defined AgentSerializer. If you want to create many projects you should use many=True keyword in ProjectModelSerializer( ProjectModelSerializer(data=request.data, many=True)), and request body will change like this:
[
{
.... # project data,
agents = [1,2,3,4,5,...], # primary keys of Agents
},
{
.... # project data,
agents = [1,2,3,4,5,...], # primary keys of Agents
},
]

Django rest framework if serializer returns empty

I have am implementing a follower and followers system in my drf api.
My relationship model and serializer:
models.py
class Relationship(models.Model):
user = models.ForeignKey(User, related_name="primary_user", null=True, blank=True)
related_user = models.ForeignKey(User, related_name="related_user", null=True, blank=True)
incoming_status = models.CharField(max_length=40, choices=RELATIONSHIP_CHOICE, default=RELATIONSHIP_CHOICE[0])
def __str__(self):
return self.user.username + " " + self.incoming_status + " " + self.related_user.username
serializers.py
class RelationshipSerializer(serializers.ModelSerializer):
outgoing_status = serializers.SerializerMethodField()
class Meta:
model = models.Relationship
fields = (
'user',
'related_user',
'incoming_status',
'outgoing_status',
)
def get_outgoing_status(self, obj):
related_user = obj.related_user
user = obj.user
try:
query = models.Relationship.objects.get(user=related_user, related_user=user)
user_id = query.incoming_status
except models.Relationship.DoesNotExist:
user_id = "none"
return user_id
views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = models.User.objects.all()
serializer_class = serializers.UserSerializer
#detail_route(methods=['get'], url_path='relationship/(?P<related_pk>\d+)')
def relationship(self, request, pk, related_pk=None):
user = self.get_object()
query = models.Relationship.objects.filter(user=user)&\
models.Relationship.objects.filter(related_user__pk=related_pk)
serializer = serializers.RelationshipSerializer(query, many=True)
return Response(serializer.data)
Incoming status is your relationship to the user and outgoing status is the users relationship to you to so for example this can return
[
{
"user": 2,
"related_user": 1,
"incoming_status": "follows",
"outgoing_status": "none"
}
]
This means you follow the user and the user doesn't follow you back
When you follow a user my code works fine because it returns "incoming_status": "follows" and it then checks for the outgoing status in the serializers. However when the:
query = models.Relationship.objects.filter(user=user) &
models.Relationship.objects.filter(related_user__pk=related_pk)
query in views.py returns null the meaning that the incoming status is none the serializer outputs [] because the query is empty. How do I make incoming_status = "none" if the query is empty?
Thanks in advance

Categories

Resources