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.
Related
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"""]
I need the following URLs to work where ticker would stand for a stock ticker such as AAPL or AMZN, and is stands for income_statement.
localhost:8000/stocks/
localhost:8000/stocks/<TICKER>/
localhost:8000/stocks/<TICKER>/is/
in the views.py file below I am using a viewset which and router which automatically configures the first two urls above, and for the third url I am using the action decorator with methods=['get'] and url_path="is" to achieve the localhost:8000/stocks/<TICKER>/is/ path.
the third URL is configured, but I get a key error for ticker when entering the following url in the browser: localhost:8000/stocks/AAPL/is/
what am I doing wrong here, I believe the error is in the views.py get_income_statement function?
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"]
class IncomeStatement(models.Model):
ticker = models.ForeignKey(
Stock, on_delete=models.CASCADE, related_name="income_statements"
)
date = models.DateField(default=datetime.date.today)
PERIODICITY_CHOICES = [("ANNUAL", "ANNUAL"), ("QUARTERLY", "QUARTERLY")]
periodicity = models.CharField(
max_length=10, choices=PERIODICITY_CHOICES, default="annually"
)
net_income_continuous_operations = models.DecimalField(
max_digits=20, decimal_places=2
views.py
class StockViewSet(viewsets.ModelViewSet):
queryset = Stock.objects.all()
serializer_class = StockSerializer
# lookup_field = "slug"
#action(detail=True, methods=["get"], url_path="is", url_name="is")
def get_income_statement(self, request, *args, **kwargs):
income_statement = self.queryset.get(ticker=kwargs["ticker"]).select_related(
"income_statements"
)
serializer = IncomeStatementSerializer(data=income_statement)
if serializer.is_valid():
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
urls.py
router = DefaultRouter()
router.register(r"stocks", views.StockViewSet)
urlpatterns = router.urls
Set lookup_field = "ticker"
class StockViewSet(viewsets.ModelViewSet):
# rest of your code
lookup_field = "ticker"
Update-1
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)
I am working on a model and I want to get the details of the employees in an organization along with their designations. I cannot think of anything at this point of time. This is my first time asking a question here so any help would mean a lot and if I havent provided the exact details please tell me. I think these should be sufficient enough for the answe.
Here is my organization and userorgdetail models:
class Organization(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=256)
employees = models.ManyToManyField(InSocialUser, related_name='organizations', blank=True,
through="UserOrgDetail")
description=models.TextField(blank=True)
class UserOrgDetail(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(InSocialUser, related_name='org_details', on_delete=models.CASCADE)
org = models.ForeignKey(Organization, related_name='employee_details', on_delete=models.CASCADE)
designation = models.CharField(max_length=256)
REQUIRED_FIELDS = ['user','org']
Here is my serializers file:
class OrgSerializer(serializers.ModelSerializer):
class Meta:
model = Organization
fields = ('id','name','employees')
class UserOrgDetailSerializer(serializers.ModelSerializer):
class Meta:
model = UserOrgDetail
fields = '__all__'
class UserOrgDetailReadSerializer(UserOrgDetailSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True)
org = OrgSerializer(read_only=True)
views.py
class OrganizationAPIView(
mixins.CreateModelMixin,
generics.ListAPIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
serializer_class = OrgSerializer
filter_backends = [filters.OrderingFilter,filters.SearchFilter]
search_fields = ['name']
ordering_fields = ['name']
ordering = ['name']
queryset = Organization.objects.all()
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save()
class UserOrgDetailAPIView(
mixins.CreateModelMixin,
generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = UserOrgDetailReadSerializer
filter_backends = [filters.OrderingFilter,filters.SearchFilter]
search_fields = ['designation']
ordering_fields = ['name']
ordering = ['name']
def get_queryset(self, *args, **kwargs):
id = self.kwargs.get("id",None)
"""
This view should return a list of all the Organization Details of the logged in user
"""
queryset = UserOrgDetail.objects.filter(user__id=id)
return queryset
def post(self, request, *args, **kwargs):
id = self.kwargs.get("id",None)
#user = get_object_or_404(User, id=id)
user = User.objects.get(id=id)
orgId = request.data.get('org_id')
org = Organization.objects.get(pk=orgId)
designation = request.data.get('designation')
org_detail = UserOrgDetail.objects.create(user=user,org=org,designation=designation)
user.save()
return Response({"status": "Success","result":UserOrgDetailReadSerializer(org_detail).data})
class UserOrgDetailDetailAPIView(
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = UserOrgDetailReadSerializer
queryset = UserOrgDetail.objects.all()
lookup_field = 'id'
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def patch(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
def get_serializer_class(self):
if self.request.method == 'GET':
return UserOrgDetailReadSerializer
else:
return self.serializer_class
I want that when I fire the get request for userorgdetail I get the employees working in the company along with their designation. Any help would be very useful, Thank you
Hey so I am new to Django and I am writing a REST API with the following model .
# models.py
class Order(models.Model):
order_name = models.CharField(max_length=10, unique=True, default="")
def __str__(self):
return '{0}'.format(self.order_name).
class LineItem(models.Model):
order = models.ForeignKey(Order, on_delete=models.SET_NULL, null=True)
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
class Meta:
unique_together = ('order', 'product')
def __str__(self):
return '{} - {}'.format(self.order.order_name, self.product.product_name).
# views.py
class OrderList(APIView):
def get(self, request):
orderlist = Order.objects.all()
serializer = OrderSerializer(orderlist, many=True)
return Response(serializer.data)
def post(self, request):
serializer = OrderSerializer(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)
class OrderDetail(APIView):
def get_object(self, order):
try:
return Order.objects.get(order_name=order_name)
except Order.DoesNotExist:
raise Http404
def get(self, request, order_name):
snippet = Order.objects.get(order_name=order_name)
snippet = snippet.lineitem_set.all()
serializer = OrderSerializer(snippet)
return Response(serializer.data)
So I am trying to code the OrderDetail(APIView)'s get method so that at /api/order/OrderA/ I get JSON with all the lineitems in that order. I've been struggling for a while with this now. Any suggestions?
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ['order_name']
Change your serializer as below,
class LineItemSerializer(serializers.ModelSerializer):
class Meta:
model = LineItem
fields = ('id', 'product')
depth = 1
class OrderSerializer(serializers.ModelSerializer):
line_items = LineItemSerializer(many=True, source='lineitem_set')
class Meta:
model = Order
fields = ['order_name', 'line_items']
and then in your OrderDetail view as
class OrderDetail(APIView):
# your code
def get(self, request, order_name):
snippet = Order.objects.get(order_name=order_name)
serializer = OrderSerializer(snippet)
return Response(serializer.data)
DRF gives you a class to get the details of an object. Have a look here: http://www.django-rest-framework.org/api-guide/generic-views/#retrieveapiview
This an example code that should work if the order_name is passed as param in the url using a GET method.
from django.views.decorators.cache import never_cache
from django.utils.decorators import method_decorator
from rest_framework.exceptions import NotFound
from rest_framework.generics import RetrieveAPIView
# the decorator avoid to cache the object, maybe you don't need that.
#method_decorator(never_cache, name="dispatch")
class OrderDetail(RetrieveAPIView):
serializer_class = OrderSerializer
def get_object(self):
try:
return Order.objects.get(order_name=self.kwargs.get('order_name'))
except Order.DoesNotExist:
raise NotFound()
I have these models
class Office(models.Model):
name = models.CharField(max_length=255)
class User(models.Model):
name = models.CharField(max_length=255)
status = models.SmallIntegerField()
office = models.ForeignKey(Office, on_delete=models.SET_NULL, null=True)
class Inspection(models.Model):
place = models.CharField(max_length=255, null=True, blank=True)
class Jobs(models.Model):
inspection = models.ForeignKey(Inspection, on_delete=models.CASCADE)
inspector = models.ForeignKey(User, on_delete=models.SET_NULL)
cost = models.DecimalField(max_digits=20, decimal_places=2)
I have different users: common users(status=1) and admins(status=2).
Common users can make and see Jobs only with Inspectors from the same office.
Admins can put any user into Inspectors and see all Jobs.
I can filter job list for users in the same office:
class JobAdmin(admin.StackedInline):
model = Job
extra = 0
def get_queryset(self, request):
qs = super(JobAdmin, self).get_queryset(request)
if request.user.status in [1]:
qs = qs.filter(inspector__office=request.user.office)
return qs
class InspectionAdmin(ModelAdmin):
list_display = ['place']
inlines = [JobAdmin]
But how can I add additional filters for editing?
If it was not an inline form I would do it this way:
class JobAdminForm(ModelForm):
def __init__(self, *args, **kwargs):
super(JobAdminForm, self).__init__(*args, **kwargs)
if self.user.status == 1: #common user
self.fields['inspector'].queryset = Inspector.objects.filter(office=self.user.office)
class Meta:
model = Jobs
fields = '__all__'
class JobAdmin(ModelAdmin):
form = JobAdminForm
def get_form(self, request, obj=None, **kwargs):
form = super(JobAdmin, self).get_form(request, **kwargs)
form.user = request.user
return form
Maybe this code will be helpfull:
class JobAdmin(admin.StackedInline):
model = Job
extra = 0
def get_queryset(self, request):
qs = super(JobAdmin, self).get_queryset(request)
if request.user.status in [1]:
qs = qs.filter(inspector__office=request.user.office)
return qs
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
if db_field.name == "inspector" and request.user.status == 1:
kwargs['queryset'] = Inspector.objects.filter(office=request.user.office)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
P.S. Link to Django documentation: formfield_for_foreignkey