How to configure ID parameters in django_rest_framework APIView - django

My goal is to specify behavior that allows me to enter an ID into my URL.
Currently, I am able to send GET, PUT, and PATCH requests to the URL 'localhost:8000/api/players/?id=#' where the hash represents player id
I would like to update my code to understand that if I send a GET/PUT/PATCH request to 'localhost:8000/api/players/2' that I am looking for a player with id=2
my models.py
class player(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
team = models.CharField(max_length=50)
position = models.CharField(max_length=50)
number = models.IntegerField()
def __str__(self):
return "{}, {}".format(self.first_name, self.last_name)
my serializer.py
class playerSerializer(serializers.ModelSerializer):
class Meta:
model = player
fields = ['id', 'first_name', 'last_name', 'team', 'position', 'number']
my views.py
from django.shortcuts import render
from django.http import HttpResponse, response
from django.shortcuts import get_object_or_404
from rest_framework import serializers
from rest_framework. serializers import Serializer
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from main.models import player
from .serializers import playerSerializer
import json
class playerList(APIView):
serializer_class = playerSerializer
throttle_scope = "main_app"
def get_queryset(self, *args, **kwargs):
players = player.objects.all()
return players
def get(self, request, *args, **kwargs):
try:
id = request.query_params["id"]
if id != None:
player_object = player.objects.get(id=id)
serializer = playerSerializer(player_object)
except:
players = self.get_queryset()
serializer = playerSerializer(players, many=True)
return Response(serializer.data)
def post(self, request):
player_data = request.data
new_player = player.objects.create(
first_name=player_data['first_name'],
last_name=player_data['last_name'],
team=player_data['team'],
position=player_data['position'],
number=player_data['number']
)
new_player.save()
serializer = playerSerializer(new_player)
return Response(serializer.data)
def put(self, request, *args, **kwargs):
id = request.query_params["id"]
player_object = player.objects.get(id=id)
data = request.data
player_object.first_name = data["first_name"]
player_object.last_name = data["last_name"]
player_object.team = data["team"]
player_object.position = data["position"]
player_object.number = data["number"]
player_object.save()
serializer = playerSerializer(player_object)
return Response(serializer.data)
def patch(self, request, *args, **kwargs):
id = request.query_params["id"]
player_object = player.objects.get(id=id)
data = request.data
player_object.first_name = data.get('first_name', player_object.first_name)
player_object.last_name = data.get('last_name', player_object.last_name)
player_object.team = data.get('team', player_object.team)
player_object.position = data.get('position', player_object.position)
player_object.number = data.get('number', player_object.number)
player_object.save()
serializer = playerSerializer(player_object)
return Response(serializer.data)
my urls.py
urlpatterns = [
url('players', playerList.as_view()),
]
I have been stuck on this problem for a few days... any guidance would be greatly appreciated.
Thanks,
Austin H

views.py
class playerList(APIView):
serializer_class = playerSerializer
throttle_scope = "main_app"
def get(self, request, *args, **kwargs):
try:
# id = request.query_params["id"]
id = self.kwargs["id"]
if id != None:
player_object = player.objects.get(id=id)
serializer = playerSerializer(player_object)
except:
players = self.get_queryset()
serializer = playerSerializer(players, many=True)
return Response(serializer.data)
def post(self, request):
player_data = request.data
new_player = player.objects.create(
first_name=player_data['first_name'],
last_name=player_data['last_name'],
team=player_data['team'],
position=player_data['position'],
number=player_data['number']
)
new_player.save()
serializer = playerSerializer(new_player)
return Response(serializer.data)
def put(self, request, *args, **kwargs):
#id = request.query_params["id"]
id = self.kwargs["id"]
player_object = player.objects.get(id=id)
data = request.data
player_object.first_name = data["first_name"]
player_object.last_name = data["last_name"]
player_object.team = data["team"]
player_object.position = data["position"]
player_object.number = data["number"]
player_object.save()
serializer = playerSerializer(player_object)
return Response(serializer.data)
def patch(self, request, *args, **kwargs):
#id = request.query_params["id"]
id = self.kwargs["id"]
player_object = player.objects.get(id=id)
data = request.data
player_object.first_name = data.get('first_name', player_object.first_name)
player_object.last_name = data.get('last_name', player_object.last_name)
player_object.team = data.get('team', player_object.team)
player_object.position = data.get('position', player_object.position)
player_object.number = data.get('number', player_object.number)
player_object.save()
serializer = playerSerializer(player_object)
return Response(serializer.data)
urls.py
urlpatterns = [
url('players/<int:id>/', playerList.as_view()),
]

I would highly suggest using DRF's viewsets as it will cover most of the things that you need here with little code. But if you want to play around it yourself with your current view:
First you have to define another url that accepts the player id, while keeping the old url to preserve the current behavior your want:
urlpatterns = [
url('players/<int:pk>', playerList.as_view()),
url('players', playerList.as_view()),
]
players/<int:pk> will support urls like players/1 and pass 1 as a keyword argument pk in the view.
You can then change your view to something like this:
from django.shortcuts import get_object_or_404
class playerList(APIView):
...
def get_object(self, id)
return get_object_or_404(self.get_queryset(), id=id)
def get(self, request, pk=None, *args, **kwargs):
id = pk or request.query_params.get('id')
if id:
serializer = playerSerializer(self.get_object(id))
else:
serializer = playerSerializer(self.get_queryset(), many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
...
def put(self, request, pk=None, *args, **kwargs):
player_object = self.get_object(pk or request.query_params.get('id'))
...
def patch(self, request, pk=None, *args, **kwargs):
player_object = self.get_object(pk or request.query_params.get('id'))
...
id = pk or request.query_params.get('id')
This means that if pk was not passed (i.e. the url used was not players/1) then the view will just do the old behavior you have by getting the id from request.query_params. This is also why pk=None is set, in case we get the id from the query params. Also note that post needs to also capture args and kwargs.

Related

UnboundLocalError at /api/registration/ even though I tried fixing it

Screenshot of the Error is at Error
The Error I face is:
UnboundLocalError at /api/registration/
local variable 'data' referenced before assignment
Request Method: POST
Request URL: http://217.160.170.83:81/api/registration/
Django Version: 3.2.12
Exception Type: UnboundLocalError
Exception Value:
local variable 'data' referenced before assignment
Exception Location: /var/www/LWD/userAccount/views.py, line 128, in get_response_data
Python Executable: /usr/bin/python3
Python Version: 3.8.10
Python Path:
['/var/www/LWD',
'/usr/lib/python38.zip',
'/usr/lib/python3.8',
'/usr/lib/python3.8/lib-dynload',
'/usr/local/lib/python3.8/dist-packages',
'/usr/lib/python3/dist-packages']
Server time: Sat, 26 Mar 2022 19:05:05 +0000
My Project Repository Link: https://github.com/Bilal815/LWD
LWD/userAccount/views.py:
from django.conf import settings
from django.shortcuts import get_object_or_404
from rest_framework.response import Response
from rest_framework import permissions, status, viewsets
from rest_framework.views import APIView
from rest_framework.generics import (
ListAPIView,
RetrieveAPIView,
CreateAPIView,
GenericAPIView,
RetrieveUpdateAPIView,
UpdateAPIView,
)
from rest_framework.exceptions import PermissionDenied, NotAcceptable, ValidationError
from allauth.account.views import ConfirmEmailView
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from rest_auth.registration.views import SocialConnectView, SocialLoginView
from rest_auth.social_serializers import TwitterConnectSerializer
from allauth.account.models import EmailAddress, EmailConfirmationHMAC
from rest_auth.views import (
LoginView,
PasswordResetView,
PasswordResetConfirmView,
PasswordChangeView,
LogoutView,
)
from rest_auth.serializers import PasswordResetConfirmSerializer
from rest_auth.registration.views import RegisterView, VerifyEmailView
from rest_auth.registration.serializers import VerifyEmailSerializer
from rest_auth.app_settings import JWTSerializer
from rest_auth.utils import jwt_encode
from django.views.decorators.debug import sensitive_post_parameters
from django.utils.decorators import method_decorator
from django.contrib.auth.models import User, Permission
from django.utils.translation import ugettext_lazy as _
from .models import Profile, Address, SMSVerification, DeactivateUser, NationalIDImage
from .serializers import (
ProfileSerializer,
UserSerializer,
AddressSerializer,
CreateAddressSerializer,
SMSVerificationSerializer,
SMSPinSerializer,
DeactivateUserSerializer,
PermissionSerializer,
PasswordChangeSerializer,
UserPermissionSerializer,
NationalIDImageSerializer,
)
from .send_mail import send_register_mail, send_reset_password_email
sensitive_post_parameters_m = method_decorator(
sensitive_post_parameters("password1", "password2")
)
class DeactivateUserView(CreateAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = DeactivateUserSerializer
def create(self, request, *args, **kwargs):
user = request.user
# TODO validation and try exception
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(user=user)
return Response("your account will deactivate after 30 days.")
class CanselDeactivateUserView(APIView):
permission_classes = [permissions.IsAuthenticated]
def post(self, request, *args, **kwargs):
user = request.user
# TODO validation and try exception
deactivate = DeactivateUser.objects.get(user=user)
deactivate.deactive = False
deactivate.save()
user.is_active = True
user.save()
return Response("your account will activated.")
class LoginAPIView(LoginView):
queryset = ""
def get_response(self):
serializer_class = self.get_response_serializer()
if getattr(settings, "REST_USE_JWT", False):
data = {"user": self.user, "token": self.token}
serializer = serializer_class(
instance=data, context={"request": self.request}
)
else:
serializer = serializer_class(
instance=self.token, context={"request": self.request}
)
response = Response(serializer.data, status=status.HTTP_200_OK)
deactivate = DeactivateUser.objects.filter(user=self.user, deactive=True)
if deactivate:
deactivate.update(deactive=False)
return response
def post(self, request, *args, **kwargs):
self.request = request
self.serializer = self.get_serializer(
data=self.request.data, context={"request": request}
)
self.serializer.is_valid(raise_exception=True)
self.login()
return self.get_response()
class RegisterAPIView(RegisterView):
#sensitive_post_parameters_m
def dispatch(self, *args, **kwargs):
return super(RegisterAPIView, self).dispatch(*args, **kwargs)
def get_response_data(self, user):
if getattr(settings, "REST_USE_JWT", False):
data = {"user": user, "token": self.token}
return JWTSerializer(data).data
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(
self.get_response_data(user),
status=status.HTTP_201_CREATED,
headers=headers,
)
def perform_create(self, serializer):
user = serializer.save(self.request)
if getattr(settings, "REST_USE_JWT", False):
self.token = jwt_encode(user)
email = EmailAddress.objects.get(email=user.email, user=user)
confirmation = EmailConfirmationHMAC(email)
key = confirmation.key
# TODO Send mail confirmation here .
# send_register_mail.delay(user, key)
print("account-confirm-email/" + key)
return user
class ResendSMSAPIView(GenericAPIView):
permission_classes = (permissions.AllowAny,)
serializer_class = SMSVerificationSerializer
allowed_methods = ("POST",)
def resend_or_create(self):
phone = self.request.data.get("phone")
send_new = self.request.data.get("new")
sms_verification = None
user = User.objects.filter(profile__phone_number=phone).first()
if not send_new:
sms_verification = (
SMSVerification.objects.filter(user=user, verified=False)
.order_by("-created")
.first()
)
if sms_verification is None:
sms_verification = SMSVerification.objects.create(user=user, phone=phone)
return sms_verification.send_confirmation()
def post(self, request, *args, **kwargs):
success = self.resend_or_create()
return Response(dict(success=success), status=status.HTTP_200_OK)
class VerifySMSView(APIView):
permission_classes = (permissions.AllowAny,)
allowed_methods = ("POST", "OPTIONS", "HEAD")
def get_serializer(self, *args, **kwargs):
return SMSPinSerializer(*args, **kwargs)
def post(self, request, pk):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
pin = int(request.data.get("pin"))
# TODO get user SMSVerification instead of below confirmation variable
confirmation = get_object_or_404(SMSVerification, pk=pk)
confirmation.confirm(pin=pin)
return Response("Your Phone Number Is Verfied.", status=status.HTTP_200_OK)
class ProfileAPIView(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request, pk):
profile = Profile.objects.get(pk=pk)
serializer = ProfileSerializer(profile, context={"request": request})
return Response(serializer.data, status=status.HTTP_200_OK)
class UserDetailView(RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = UserSerializer
queryset = User.objects.all()
lookup_field = "username"
class ListAddressAPIView(ListAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = AddressSerializer
def get_queryset(self):
user = self.request.user
queryset = Address.objects.filter(user=user)
return queryset
class AddressDetailView(RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = AddressSerializer
queryset = Address.objects.all()
def retrieve(self, request, *args, **kwargs):
user = request.user
address = self.get_object()
if address.user != user:
raise NotAcceptable("this addrss don't belong to you")
serializer = self.get_serializer(address)
return Response(serializer.data, status=status.HTTP_200_OK)
class createAddressAPIView(CreateAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = CreateAddressSerializer
queryset = ""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(user=request.user, primary=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)
class FacebookConnectView(SocialLoginView):
adapter_class = FacebookOAuth2Adapter
class TwitterConnectView(SocialLoginView):
serializer_class = TwitterConnectSerializer
adapter_class = TwitterOAuthAdapter
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
client_class = OAuth2Client
callback_url = "https://www.google.com"
class PasswordResetView(APIView):
def post(self, request, *args, **kwargs):
email = request.data.get("email", None)
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
raise NotAcceptable(_("Please enter a valid email."))
send_reset_password_email.delay(user)
return Response(
{"detail": _("Password reset e-mail has been sent.")},
status=status.HTTP_200_OK,
)
class PasswordResetConfirmView(GenericAPIView):
permission_classes = (permissions.AllowAny,)
serializer_class = PasswordResetConfirmSerializer
#sensitive_post_parameters_m
def dispatch(self, *args, **kwargs):
return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"detail": _("Password has been reset with the new password.")})
class PasswordChangeView(GenericAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = PasswordChangeSerializer
#sensitive_post_parameters_m
def dispatch(self, *args, **kwargs):
return super(PasswordChangeView, self).dispatch(*args, **kwargs)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"detail": _("Congratulations, password has been Changed.")})
class VerifyEmailView(APIView, ConfirmEmailView):
permission_classes = (permissions.AllowAny,)
allowed_methods = ("POST", "OPTIONS", "HEAD")
def get_serializer(self, *args, **kwargs):
return VerifyEmailSerializer(*args, **kwargs)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.kwargs["key"] = serializer.validated_data["key"]
confirmation = self.get_object()
confirmation.confirm(self.request)
return Response({"detail": _("ok")}, status=status.HTTP_200_OK)
class RetrievePermissionView(RetrieveAPIView):
serializer_class = UserPermissionSerializer
queryset = User.objects.all()
lookup_field = "username"
class UpdatePermissionView(UpdateAPIView):
serializer_class = UserPermissionSerializer
queryset = User.objects.all()
lookup_field = "username"
def partial_update(self, request, *args, **kwargs):
kwargs["partial"] = True
return self.update(request, *args, **kwargs)
# def update(self, request, *args, **kwargs):
# partial = True
# return super(UpdatePermissionView, self).update(request, *args, **kwargs)
class NationalIDImageViewSet(viewsets.ModelViewSet):
serializer_class = NationalIDImageSerializer
queryset = NationalIDImage.objects.all().select_related("user")
LWD/userAccounts/models.py:
import logging
from datetime import datetime, timezone, timedelta
from django.db import models
from django.contrib.auth import get_user_model
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.conf import settings
from django.core.cache import cache
from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
from rest_framework.authtoken.models import Token
from rest_framework.exceptions import NotAcceptable
from allauth.account.signals import user_signed_up
from phonenumber_field.modelfields import PhoneNumberField
from django_countries.fields import CountryField
from randompinfield import RandomPinField
import phonenumbers
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException
from .signals import register_signal
from .managers import NationalIDImageManager
from core.models import TimeStampedModel
from core.handle_images import compress_image
User = get_user_model()
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/users/<username>/<filename>
return "users/{0}/{1}".format(instance.user.username, filename)
def national_image_path(instance, filename):
return f"national/{instance.user.username}/images/{filename}"
class Profile(TimeStampedModel):
GENDER_MALE = "m"
GENDER_FEMALE = "f"
OTHER = "o"
GENDER_CHOICES = (
(GENDER_MALE, "Male"),
(GENDER_FEMALE, "Female"),
(OTHER, "Other"),
)
user = models.OneToOneField(User, related_name="profile", on_delete=models.CASCADE)
profile_picture = models.ImageField(upload_to=user_directory_path, blank=True)
phone_number = PhoneNumberField(blank=True)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES, blank=True)
about = models.TextField(blank=True, null=True)
birth_date = models.DateField(blank=True, null=True)
def __str__(self):
return "%s" % self.user.username
#property
def last_seen(self):
return cache.get(f"seen_{self.user.username}")
#property
def online(self):
if self.last_seen:
now = datetime.now(timezone.utc)
if now > self.last_seen + timedelta(minutes=settings.USER_ONLINE_TIMEOUT):
return False
else:
return True
else:
return False
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, *args, **kwargs):
if created:
Profile.objects.create(user=instance)
class Address(TimeStampedModel):
user = models.ForeignKey(User, related_name="address", on_delete=models.CASCADE)
country = CountryField(blank=False, null=False)
city = models.CharField(max_length=100, blank=False, null=False)
district = models.CharField(max_length=100, blank=False, null=False)
street_address = models.CharField(max_length=250, blank=False, null=False)
postal_code = models.CharField(max_length=20, blank=True, null=True)
primary = models.BooleanField(default=False)
phone_number = PhoneNumberField(null=True, blank=True)
building_number = models.IntegerField(
blank=True, null=True, validators=[MinValueValidator(1)]
)
apartment_number = models.IntegerField(
blank=True, null=True, validators=[MinValueValidator(1)]
)
class SMSVerification(TimeStampedModel):
user = models.OneToOneField(User, related_name="sms", on_delete=models.CASCADE)
verified = models.BooleanField(default=False)
pin = RandomPinField(length=6)
sent = models.BooleanField(default=False)
phone = PhoneNumberField(null=False, blank=False)
def send_confirmation(self):
logging.debug("Sending PIN %s to phone %s" % (self.pin, self.phone))
if all(
[
settings.TWILIO_ACCOUNT_SID,
settings.TWILIO_AUTH_TOKEN,
settings.TWILIO_FROM_NUMBER,
]
):
try:
twilio_client = Client(
settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN
)
twilio_client.messages.create(
body="Your forgeter activation code is %s" % self.pin,
to=str(self.user.profile.phone_number),
from_=settings.TWILIO_FROM_NUMBER,
)
self.sent = True
self.save()
return True
except TwilioRestException as e:
logging.error(e)
else:
logging.warning("Twilio credentials are not set")
def confirm(self, pin):
if pin == self.pin and self.verified == False:
self.verified = True
self.save()
else:
raise NotAcceptable("your Pin is wrong, or this phone is verified before.")
return self.verified
#receiver(post_save, sender=Profile)
def send_sms_verification(sender, instance, *args, **kwargs):
try:
sms = instance.user.sms
if sms:
pin = sms.pin
sms.delete()
verification = SMSVerification.objects.create(
user=instance.user,
phone=instance.user.profile.phone_number,
sent=True,
verified=True,
pin=pin,
)
except:
if instance.user.profile.phone_number:
verification = SMSVerification.objects.create(
user=instance.user, phone=instance.user.profile.phone_number
)
# TODO Remove send confirm from here and make view for it.
verification.send_confirmation()
# if instance.user.profile.phone_number:
# verification = SMSVerification.objects.create(user=instance.user, phone=instance.user.profile.phone_number)
# # TODO Remove send confirm from here and make view for it.
# verification.send_confirmation()
class DeactivateUser(TimeStampedModel):
user = models.OneToOneField(
User, related_name="deactivate", on_delete=models.CASCADE
)
deactive = models.BooleanField(default=True)
class NationalIDImage(models.Model):
user = models.ForeignKey(
User, related_name="national_ids", on_delete=models.CASCADE
)
image = models.ImageField(upload_to=national_image_path, blank=True)
is_deleted = models.BooleanField(default=False)
objects = NationalIDImageManager()
def __str__(self):
return self.user.username
def save(
self,
force_insert=False,
force_update=False,
using=None,
update_fields=None,
*args,
**kwargs,
):
# if size greater than 300kb then it will send to compress image function
image = self.image
if image and image.size > (0.3 * 1024 * 1024):
self.image = compress_image(image)
super(NationalIDImage, self).save(*args, **kwargs)
P.S. I tried fixing it by commenting on the data clean-up for phone number and birthdate but it did not work. Things work well on local but I have no idea of what happened here on the server. This is my first time on the server and I need it done as I want to add this to my portfolio.
Please help!
Error is in this part:
def get_response_data(self, user):
if getattr(settings, "REST_USE_JWT", False):
data = {"user": user, "token": self.token}
return JWTSerializer(data).data
You only create data when if getattr(settings, "REST_USE_JWT", False) is true.
So what happens if it's false? data is not created, but you reference it in JWTSerializer(data).
You need to provide something in else like:
def get_response_data(self, user):
if getattr(settings, "REST_USE_JWT", False):
data = {"user": user, "token": self.token}
else:
data = {# something goes here}
return JWTSerializer(data).data

How to calculate average of some field in Django models and send it to rest API?

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"""]

Django object on creation isn't stored in the database and the ID is returned as null

I'm creating a basic notes application where user can perform CRUD operation.
Following are my models, views and URLs.
from django.db import models
from accounts.models import User
from django.utils import timezone
class Notes(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
body = models.TextField()
created_on = models.DateTimeField(auto_now_add=True, editable=False)
last_updated = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
if not self.id:
self.created_on = timezone.now()
self.last_updated = timezone.now()
def __str__(self):
return self.title
Views.py
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework import viewsets, status
from rest_framework.response import Response
from . import serializers, models
class NotesViewSet(viewsets.ModelViewSet):
queryset = models.Notes.objects.all()
serializer_class = serializers.NotesSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def list(self, request):
notes = models.Notes.objects.all().filter(user=request.user)
serializer = serializers.NotesSerializer(notes, many=True)
return Response(serializer.data)
def create(self, request):
serializer = serializers.NotesSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
def retrieve(self, request, pk=None):
note = models.Notes.objects.filter(id=pk)
serializer = serializers.NotesSerializer(instance=note)
return Response(serializer.data)
def update(self, request, pk=None):
note = models.Notes.objects.get(id=pk)
serializer = serializers.NotesSerializer(
instance=note, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
def destroy(self, request, pk=None):
note = models.Notes.objects.get(id=pk)
note.delete()
return Response({"message": "Note deleted"},
status=status.HTTP_202_ACCEPTED)
urls.py
from django.urls import path
from .views import NotesViewSet
app_name = 'notes'
urlpatterns = [
path('get', NotesViewSet.as_view({
'get': 'list',
}), name='get'),
path('create', NotesViewSet.as_view({
'post': 'create',
}), name='create'),
path('get/<str:pk>', NotesViewSet.as_view({
'get': 'retrieve',
}), name='retrieve'),
path('update/<str:pk>', NotesViewSet.as_view({
'put': 'update',
}), name='update'),
path('delete/<str:pk>', NotesViewSet.as_view({
'delete': 'destroy',
}), name='delete'),
]
My question is, once I send a post request to create a new note something like this coming back as a response
{
"id": null,
"title": "title_1",
"body": "body_1",
"created_on": "2021-06-02T21:10:42.019236+05:30",
"last_updated": "2021-06-02T21:10:42.019257+05:30",
"user": 1
}
and the data isn't stored in the database as well. Please help
I believe the issue is in how you've overridden your save method- you still need to call the parent's save on it to get your expected behavior.
def save(self, *args, **kwargs):
if not self.id:
self.created_on = timezone.now()
self.last_updated = timezone.now()
super().save(*args, **kwargs)
Your code has some odd tabulation under your Notes class- I'm assuming this is just an issue in the code within the question.
super()
in save method is missing. You need to call super().save(*args, **kwargs) to make it work.

DRF: django rest framework case insensitive lookup in detail view

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.

How to pass Two templates in a same class based views

I am having one issue with the django template view.
I am having two urls
1) For Current User Profile
2) For His Team mates
urls.py
path('profile/', views.UserprofileIndex.as_view(), name='user-profile'),
path('profile/<int:pk>', views.TeamProfileIndex.as_view(), name='team-profile'),
It's views
class TeamProfileIndex(TemplateView):
template_name = 'accounts/profile/team_profile.html'
def get_context_data(self, **kwargs):
context = dict()
self.pk = self.kwargs.get("pk")
try:
user_profile = Profile.objects.get(user=self.pk)
except Profile.DoesNotExist:
logger.error("Requested user profile not available")
raise Http404
teams = User.objects.all()
context.update(
{
'user_profile': user_profile,
'teams' : teams
}
)
return context
class UserprofileIndex(TemplateView):
template_name = 'accounts/profile/index.html'
def get_context_data(self, **kwargs):
context = dict()
try:
user_profile = Profile.objects.get(user=self.request.user)
except Profile.DoesNotExist:
logger.error("Requested user profile not available")
raise Http404
teams = User.objects.all
context.update(
{
'user_profile': user_profile,
'teams' : teams
}
)
return context
In both views only one query is changing , that is 'user_profile'.
Is there any way to pass this in a single views to two diffrent html pages ?
I have tried following method
class UserprofileIndex(TemplateView):
# template_name = 'accounts/profile/index.html'
def initialize(self, *args, **kwargs):
self.pk = None
self.pk = kwargs.get('pk')
def get_template_names(self, *args, **kwargs):
if self.pk:
return 'accounts/profile/team_profile.html'
else:
return 'accounts/profile/index.html'
def get_context_data(self, **kwargs):
context = dict()
try:
if self.pk:
user_profile = UserProfile.objects.get(user=self.pk)
else:
user_profile = Profile.objects.get(user=self.request.user)
except Profile.DoesNotExist:
logger.error("Requested user profile not available")
raise Http404
teams = User.objects.all()
context.update(
{
'user_profile': user_profile,
'teams' : teams
}
)
return context
But its not working
This will work. But I would advise using DetailView
class UserProfile(TemplateView):
"""
User profile view
"""
user_profile = None
template_name = 'accounts/profile/index.html'
team_profile_template_name = 'accounts/profile/team_profile.html'
def get_user_profile(self):
"""
Get user
"""
if not self.user_profile:
user_profile_pk = self.kwargs.get('pk')
model, filter_kwargs = (UserProfile, dict(user=user_profile_pk)) \
if user_profile_pk else (Profile, dict(user=self.request.user))
self.user_profile = get_object_or_404(model, **filter_kwargs)
return self.user_profile
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(dict(user_profile=self.get_user_profile(),
teams=User.objects.all()))
return context
def get_template_names(self):
if self.kwargs.get('pk'):
return [self.team_profile_template_name]
return super().get_template_names()
If you want to use two template in signle class based view
Do something like this which i do for
DetailView
class PollDetail(DetailView):
model = Poll
context_object_name = "poll"
Instead mentioning template name in Class mention it in urls
ulrs.py
path("result/<pk>",PollDetail.as_view(template_name="results.html")),
path("vote/<pk>",PollDetail.as_view(template_name="vote.html"))