How can I update specific field after retrieved in django rest framework
# Models.py
class Article(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
view = models.IntegerField(default=0)
def __str__(self):
return self.title
I want to update view after read a specific data.
# Views.py
class ArticleDetail(generics.RetrieveUpdateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# Update view + 1
# serializers.py
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = "__all__"
Please help me
If you want your field to be incremented only on a GET request, you can update it in the retrieve method:
# views.py
class ArticleDetail(generics.RetrieveUpdateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
instance.view = instance.view + 1
instance.save()
serializer = self.get_serializer(instance)
return Response(serializer.data)
If you want it to be incremented with both GET and PATCH, you could update it in get_object instead:
# views.py
class ArticleDetail(generics.RetrieveUpdateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def get_object(self):
instance = super(ArticleDetail, self).get_object()
instance.view = instance.view + 1
instance.save()
return instance
I was able to automaticcaly create a userProfile everytime a user is created but I want to be able to modify somefields in the userprofile.
So my Model.py
def upload_path(instance,filename):
return 'users/avatars/{0}/{1}'.format(instance.user.username, filename)
class UserProfile(models.Model):
user= models.OneToOneField(User,on_delete=models.CASCADE, related_name='userprofile')
Profileimage= models.ImageField(upload_to=upload_path, blank=True, null=True, default='user/avatar.jpeg')
def __str__(self):
return self.user.username
# receiver(post_save,sender=User)
def create_user_profile(sender,instance,created,**kwargs):
if created:
UserProfile.objects.create(user=instance)
My Serializer.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields =['id', 'username','password','email']
extra_kwargs={'password':{'write_only':True, 'required':True}}
def create(self,validated_data):
user =User.objects.create_user(**validated_data)
Token.objects.create(user=user)
return user
class UserProfileSerializer(serializers.ModelSerializer):
user=serializers.SlugRelatedField(queryset=models.User.objects.all(), slug_field='username')
class Meta:
model =models.UserProfile
#lookup_field = 'username'
fields= '__all__'
My view.py
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_field = 'username'
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
Using postman i get the error : django.db.utils.IntegrityError: UNIQUE constraint failed: api_userprofile.user_id
So after adding
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
I was able to change the fields that I want to.
I want to count the average of ratings ( in Reviews model ) and send it to my API.
Models.py
from django.db import models
from adminuser.models import Categories
from accounts.models import UserAccount as User
from django.core.validators import MaxValueValidator, MinValueValidator
# Create your models here.
class Gigs(models.Model):
title = models.CharField(max_length=255)
category = models.ForeignKey(Categories , on_delete=models.CASCADE)
price = models.DecimalField(max_digits=6, decimal_places=2)
details = models.TextField()
seller = models.ForeignKey(User,default=None, on_delete=models.CASCADE)
class Reviews(models.Model):
rating = models.SmallIntegerField( default=0,validators=[MaxValueValidator(5),MinValueValidator(1)])
comment = models.CharField(max_length=500)
item = models.ForeignKey(Gigs , on_delete=models.CASCADE)
buyer = models.ForeignKey(User ,default=None, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
Views.py
from django.shortcuts import render
from .models import Gigs,Reviews
from .serializers import GigsSerializer,ReviewsSerializer
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin, CreateModelMixin , RetrieveModelMixin , DestroyModelMixin, UpdateModelMixin
from rest_framework.permissions import AllowAny
# Create your views here.
#List and create (pk not required)
class GigsListAPI(GenericAPIView, ListModelMixin ):
def get_queryset(self):
username = self.kwargs['user']
return Gigs.objects.filter(seller=username)
serializer_class = GigsSerializer
permission_classes = (AllowAny,)
def get(self, request , *args, **kwargs):
return self.list(request, *args, **kwargs)
class GigsListCategorywise(GenericAPIView, ListModelMixin ):
def get_queryset(self):
SearchedCategory = self.kwargs['category']
return Gigs.objects.filter(category=SearchedCategory)
serializer_class = GigsSerializer
permission_classes = (AllowAny,)
def get(self, request , *args, **kwargs):
return self.list(request, *args, **kwargs)
class GigsListAll(GenericAPIView, ListModelMixin ):
queryset = Gigs.objects.all()
serializer_class = GigsSerializer
permission_classes = (AllowAny,)
def get(self, request , *args, **kwargs):
return self.list(request, *args, **kwargs)
class GigsCreateAPI(GenericAPIView, CreateModelMixin):
queryset = Gigs.objects.all()
serializer_class = GigsSerializer
permission_classes = (AllowAny,)
def post(self, request , *args, **kwargs):
return self.create(request, *args, **kwargs)
# Retrieve, update and delete (pk required)
class RUDGigsAPI(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Gigs.objects.all()
serializer_class = GigsSerializer
permission_classes = (AllowAny,)
def get(self, request , *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request , *args, **kwargs):
return self.update(request, *args, **kwargs)
def put(self, request , *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def delete(self, request , *args, **kwargs):
pk = kwargs.get('pk')
p = Gigs.objects.get(id=pk)
if p.images:
p.images.delete()
return self.destroy(request, *args, **kwargs)
# VIEWS FOR REVIEWS MODEL
class ReviewsListAPI(GenericAPIView, ListModelMixin ):
def get_queryset(self):
item = self.kwargs['item']
return Reviews.objects.filter(item=item)
serializer_class = ReviewsSerializer
permission_classes = (AllowAny,)
def get(self, request , *args, **kwargs):
return self.list(request, *args, **kwargs)
class ReviewsCreateAPI(GenericAPIView, CreateModelMixin):
queryset = Reviews.objects.all()
serializer_class = ReviewsSerializer
permission_classes = (AllowAny,)
def post(self, request , *args, **kwargs):
return self.create(request, *args, **kwargs)
Serializers.py
from rest_framework import serializers
from .models import Gigs, Reviews
class GigsSerializer (serializers.ModelSerializer):
class Meta:
model = Gigs
fields = ['id','title','category','price','details','seller','images']
class ReviewsSerializer (serializers.ModelSerializer):
class Meta:
model = Reviews
fields = ['id','rating','comment','item','buyer','created_at']
I want to calculate average of the ratings of some gigs or item in reviews table and then send it to API. but I am confused where to calculate it (models.py or views.py) and then how to send it to my API.
Well I am gonna explain this in details, average rating could be considered as a virtual field in Gigs, so it make sense to put it in there, so lets try that:
class Gigs(models.Model):
...
#property
def average_rating(self):
return self.reviews.aggregate(Avg('rating'))['rating_avg']
so when you gonna retrieve a single Gigs, this is good and everything, but the problem is if you need the average in the list api, this is gonna make alot of extra queries(1 for each Gig). in that case it is better to do it in bulk and in the view, so:
class GigsListAll(ListModelMixin, GenericAPIView): # you should put the mixin before the main class :D
serializer_class = GigsSerializer
permission_classes = (AllowAny,)
def get_queryset(self):
return Gigs.objects.all().annotate(_average_rating=Avg('reviews__rating') # pay attention, it was annotated as _average_rating
and now we gonna change the virtual field in the model, and check if we have it precalculated, so:
class Gigs(models.Model):
...
#property
def average_rating(self):
if hasattr(self, '_average_rating'):
return self._average_rating
return self.reviews.aggregate(Avg('rating'))
finally to use it in your serializer:
class GigsSerializer (serializers.ModelSerializer):
average_rating = serializers.SerializerMethodField()
def get_average_rating(self, obj):
return obj.average_rating
class Meta:
model = Gigs
fields = ['id','title','category','price','details','seller','images','average_rating']
p.s. It is a best practice to set the related name for foreign keys, so change your reviews model like this:
class Reviews(models.Model):
...
item = models.ForeignKey(Gigs , on_delete=models.CASCADE, related_name='reviews')
First give your foreign key a name so you can reverse it:
class Reviews(models.Model):
...
item = models.ForeignKey(Gigs, on_delete=models.CASCADE, related_name='reviews')
...
Then you can do this in your serializer:
from rest_framework import serializers
from .models import Gigs, Reviews
from django.db.models import Avg
class GigsSerializer (serializers.ModelSerializer):
class Meta:
model = Gigs
fields = ['id','title','category','price','details','seller','images','avg_rating']
avg_rating = serializers.SerializerMethodField()
def get_avg_rating(self, ob):
# reverse lookup on Reviews using item field
return ob.reviews.all().aggregate(Avg('rating'))['rating__avg']
class ReviewsSerializer (serializers.ModelSerializer):
class Meta:
model = Reviews
fields = ['id','rating','comment','item','buyer','created_at']
It's redundant but I post it also here.
Objects properties have to be serialized using SerializerMethodField.
To get values for these type of fields serializers look for methodsnamed get_ and raises errors if they are not defined. The get_ method must accept an object as a parameter that the serializer use to pass in the current serialized object, so you can access its properties.
In this case it have to be:
class GigsSerializer(serializers.MethodSerializer):
average_rating = serializers.SerializerMethodField()
def get_average_rating(self, obj):
return obj.average_rating
class Meta:
model = Gigs
fields = ["""your fields here"""]
How to make urls case insensitive with certain parameters passed
For example, assuming Stock model has a ticker. All links below should find the same ticker content, right now they are case sensitive and try to search for different values:
/stocks/AAPL
/stocks/aapl
/stocks/AaPl
views.py
class StockViewSet(viewsets.ModelViewSet):
queryset = Stock.objects.all()
serializer_class = StockSerializer
lookup_field = "ticker"
#action(detail=True, methods=["get"], url_path="is", url_name="is")
def get_income_statement(self, request, *args, **kwargs):
is_qs = IncomeStatement.objects.filter(ticker=self.get_object())
serializer = IncomeStatementSerializer(is_qs, many=True)
return Response(serializer.data)
#action(detail=True, methods=["get"], url_path="bs", url_name="bs")
def get_balance_sheet(self, requests, *args, **kwargs):
bs_qs = BalanceSheet.objects.filter(ticker=self.get_object())
serializer = BalanceSheetSerializer(bs_qs, many=True)
return Response(serializer.data)
#action(detail=True, methods=["get"], url_path="cf", url_name="cf")
def get_cashflows_statement(self, requests, *args, **kwargs):
cf_qs = CashflowsStatement.objects.filter(self.get_object())
serializer = CashflowsStatementSerializer(cf_qs, many=True)
return Response(serializer.data)
class IncomeStatementDetail(viewsets.ModelViewSet):
queryset = IncomeStatement.objects.all()
serializer_field = IncomeStatementSerializer
class BalanceSheetDetail(viewsets.ModelViewSet):
queryset = BalanceSheet.objects.all()
serializer_field = BalanceSheetSerializer
class CashflowsStatementDetail(viewsets.ModelViewSet):
queryset = CashflowsStatement.objects.all()
serializer_field = CashflowsStatementSerializer
urls.py
router = DefaultRouter()
router.register(r"stocks", views.StockViewSet)
urlpatterns = router.urls
models.py
class Stock(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
ticker = models.CharField(max_length=10, unique=True, primary_key=True)
slug = models.SlugField(default="", editable=False)
def save(self, *args, **kwargs):
value = self.ticker
self.slug = slugify(value, allow_unicode=True)
super().save(*args, **kwargs)
def __str__(self):
return self.ticker
class Meta:
verbose_name = "stock"
verbose_name_plural = "stocks"
ordering = ["ticker"]
Use lookup_url_kwarg and lookup_field as
from rest_framework import viewsets
class StockViewSet(viewsets.ModelViewSet):
lookup_url_kwarg = 'ticker'
lookup_field = 'ticker__iexact'
# rest of your code
You can refer the source code of get_object(self) to see how DRF fetching the model object in the detail view.
I'm new to creating REST API so I might misunderstand something.
I'm creating REST API using Django Rest Framework. And I'm trying to create an object and send it from my mobile app.
However, API returns 400. I think it still cannot associate the object with the request user and I'm wondering how to do it.
models.py
class Item(models.Model):
item_name = models.CharField()
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('item_name', 'created_by')
and views.py
class ListItems(generics.ListCreateAPIView):
queryset = Item.objects.all()
serializer_class = ItemSerializer
What I want to know is how to associate the object with the request user when posting the object like as we do like
if form.is_valid():
item = form.save(commit=False)
item.created_by = request.user
item.save()
I think the easiest approach is like this:
class ItemSerializer(serializers.ModelSerializer):
created_by = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
Reference can be found here
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('item_name',)
class ListItems(generics.ListCreateAPIView):
...
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
you can do this way
One of the possible way to overwrite serializer_create method. As user is not associated with request.data first we need to make sure, this is write_only field and also need to assign current user from modelSerializer's self.context.request.user. Following addition should solve the problem.
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('item_name', 'created_by')
extra_kwargs = {'created_by': {'write_only': True}}
def create(self, validated_data):
item = Item(
item_name=validated_data['item_name'],
created_by=self.context.request.user
)
item.save()
return item
Reference link
It works for me
models.py
class Category(models.Model):
name = models.CharField('Category', max_length=200, unique=True, help_text='Name of the category')
slug = models.SlugField('Slug', max_length=100, db_index=True, unique=True, help_text='Name of the category in format URL')
def __str__(self):
return (self.name)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = [
'id', 'name', 'slug'
]
read_only_fields = [
'slug',
]
Finally, I get the user in the view, before to save the post.
views.py
class CategoryList(APIView):te a new category instance.
permission_classes = (IsAuthenticatedOrReadOnly,)
def get(self, request, format=None):
categories = Category.objects.all()
serializer = CategorySerializer(categories, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request, format=None):
serializer = CategorySerializer(data=request.data)
if serializer.is_valid():
serializer.save(created_by=self.request.user)
Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)