We have following models:
class Publisher(models.Model):
name = models.CharField(max_length=32)
local_name = models.CharField(max_length=32)
url_pattern = models.CharField(max_length=128)
enabled = models.BooleanField(default=True)
home_page = models.BooleanField(default=False)
category = models.ForeignKey(NewspaperCategory, null=True)
class Newspaper(models.Model):
class Meta:
unique_together = ("publisher", "date")
ordering = ("-date",)
publisher = models.ForeignKey(Publisher)
image = models.ImageField(upload_to=newspaper_upload_to)
thumbnail = models.ImageField(upload_to=newspaper_thumbnail_upload_to)
date = models.DateField()
I have a APIView (Django rest framework) and there are several different query parameters which filter the output of API. I'd like to have a "latest" query parameter which lists only the latest version of each publisher. Also, I need to be able to do further filtering and slicing on that QuerySet before evaluating it.
But the result of query should be Newspaper instances not dict so I can feed them to my serializer.
My view looks like this at the moment :
class Newspapers(APIView):
def get(self, request):
queryset = models.Newspaper.objects.filter(deleted=False).prefetch_related('publisher')
if "latest" in request.GET:
# doing something here with queryset
if "publisher_id" in request.GET:
queryset = queryset.filter(publisher_id=request.GET['publisher_id'])
if "category" in request.GET:
queryset = queryset.filter(publisher__category_id=request.GET['category'])
if "before" in request.GET:
queryset = queryset.filter(date__lt=request.GET['before'])
if "after" in request.GET:
queryset = queryset.filter(date__gte=request.GET['after'])
if "home_page" in request.GET:
queryset = queryset.filter(publisher__home_page=request.GET['home_page'].lower() == 'true')
queryset = queryset.order_by('-date', 'publisher__order')
return PagingResponse(
request,
serializers.NewspaperVersionSerializer,
query=queryset,
identifier_name='date'
)
We can do this with two queries:
if "latest" in request.GET:
latest_ids = list( # <-- first query
queryset.order_by('id')
.values('publisher_id')
.annotate(id=Max('id'))
.values_list('id', flat=True)
)
queryset = queryset.filter(id__in=latest_ids) # <-- second query
the first query gets list of latest newspaper group by publisher, and the second query gets a queryset of those newspaper for further filtering, slicing and ...
Related
how can i access the category_id? I want to create a list of similar products based on category. So each time i make a get request f.e products/1052/similarproducts, i want to get all the ProductsInStore of the same category as ProductInStore(id=1052) and exclude the ProductInStore(id=1052), my current code gives me "ecommerce.models.ProductInStore.DoesNotExist: ProductInStore matching query does not exist."
Even though a productInStore of 1052 exists.
class ProductInStore(models.Model):
product = models.ForeignKey('Product', on_delete=models.CASCADE)
class Product(models.Model):
name = models.CharField(max_length=200)
product_category = models.ManyToManyField(EcommerceProductCategory)
class SimilarProductsListApiView(generics.ListAPIView):
queryset = ProductInStore.objects.all()
serializer_class = ProductInStoreSerializer
#products/954/similarproductsr
def get_queryset(self):
product_id = self.kwargs['pk']
category = ProductInStore.objects.values_list('product__product_category', flat=True).get(product_id=product_id)
return ProductInStore.objects.filter(product__product_category=category).exclude(product_id=product_id).all()
Your product_id were id of ProductInStore instance, not Product instance and because their ids most likely vary, you could not get instance
class SimilarProductsListApiView(generics.ListAPIView):
queryset = ProductInStore.objects.all()
serializer_class = ProductInStoreSerializer
def get_queryset(self):
product_in_store_id = self.kwargs['pk']
category = ProductInStore.objects.get(id=product_in_store_id).product.product_category
return ProductInStore.objects.filter(product__product_category=category).exclude(id=product_in_store_id)
I have a query that returns data that will be displayed in the browser as a line chart.
Depending on the period chosen, this can represent a rather huge number of results (~25K max).
Most of the time these values do not change, on average on 25 000 results I have about 8000 different values. I think it would be a real optimization if I only return these 8000 values instead of the 25 0000.
My Model:
class TechnicalData(models.Model):
name = models.CharField(_('Name'), max_length=80)
value = models.CharField(_('Value'), max_length=80)
value_type = models.CharField(_('Value type'), max_length=20)
date_time = models.DateTimeField(_("timestamp"))
machine = models.ForeignKey("machine.Machine",
verbose_name = _("machine"),
related_name="technical_data_machine",
on_delete=models.CASCADE)
class Meta:
verbose_name = _("TechnicalData")
verbose_name_plural = _("TechnicalDatas")
ordering = ["-date_time"]
def __str__(self):
return self.name
If the "value" field does not change during a given period (date_time), I would like to remove the "duplicate/same" values.
Today I have this view:
class TechnicalDataViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving technical data.
"""
permission_classes = [permissions.IsAuthenticated]
pagination_class = LargeResultsSetPagination
def list(self, request):
id_machine = self.request.query_params.get('id_machine')
name = self.request.query_params.get('name')
only_last = self.request.query_params.get('only_last')
names = self.request.query_params.get('name__in')
date_start = self.request.query_params.get('date_start')
date_end = self.request.query_params.get('date_end')
queryset = TechnicalData.objects.all()
if id_machine:
queryset = queryset.filter(machine__id=id_machine)
if name:
queryset = queryset.filter(name=name)
if names:
names = names.split(',')
queryset = queryset.filter(name__in=names)
if date_start:
queryset = queryset.filter(date_time__date__gte=date_start)
if date_end:
queryset = queryset.filter(date_time__date__lte=date_end)
if only_last:
queryset = queryset.order_by('name', '-date_time').distinct("name")
pagination = LargeResultsSetPagination()
qs = pagination.paginate_queryset(queryset, request)
serializer = TechnicalDataSerializer(qs, many=True)
return Response(serializer.data)
Is it possible to send only the different results?
if you are looking for a way to return distinct values of your queryset, django has a good method on queryset conveniently named distinct. here is a documentation on how to use it.
django distinct
I have a site that grabs all the records that is listed under the current user.
Right now it grabs and filters it to all the available records and then filters it to the current user. Is there a way to make it so it doesn't have to do the initial filtering?
class PromiseView(SingleTableView):
queryset = Promise.objects.filter(deleted__isnull=True)
table_class = PromiseTable
template_name = 'promise.html'
def get_queryset(self):
return self.queryset.filter(
windows_user=self.request.user, # There has to be a better way for this one.
)
I would want to make it so the initial query set is equal to
queryset = Promise.objects.filter(windows_user=self.request.user,deleted__isnull=True)
and get rid of the get_queryset function.
It makes no sense to query the entire database first and then query it again to filter it to how I want it.
Here is the model
class Promise(models.Model):
windows_user = models.CharField('Windows', max_length=20)
event_date = models.DateTimeField('Date')
notes = models.CharField('note', max_length=100)
deleted = models.DateTimeField('Deleted', null=True)
class Meta:
db_table = 'form_promise'
indexes = [
models.Index(fields=['windows_user','deleted', ], name='index01'),
]
You don't need to include the first queryset = ..., you can just use the get_queryset() to populate the data. If you'd like to be explicit then you could initialize an empty queryset with qs.none() which won't hit the db, and then return the actual data from get_queryset().
class PromiseView(SingleTableView):
queryset = Promise.objects.none() # optional
table_class = PromiseTable
template_name = 'promise.html'
def get_queryset(self):
return Promise.objects.filter(
deleted__isnull=True,
windows_user=self.request.user.username, # I think you need user.username here.
)
We want to let possibility to find in all tables the records by the fields in related tables. Actually we would like to search through all of them.
For example the code:
/models.py
class Tourney(models.Model):
tourney = models.CharField()
tourney_1 = models.CharField()
tourney_2 = models.CharField()
class Stage(models.Model):
tourney = models.ForeignKey(Tourney, on_delete=models.CASCADE)
stage = models.CharField()
stage_1 = models.CharField()
stage_2 = models.CharField()
class Group(models.Model):
stage = models.ForeignKey(Stage, on_delete=models.CASCADE)
group = models.CharField()
group_1 = models.CharField()
group_2 = models.CharField()
Group has relation on Stage which has relation on Tourney.
So now we want to set API for them. Imagine we have simple serializers for them which include all their fields and called TourneySerializer, StageSerializer and GroupSerializer.
Now let's try to filter the Group model.
/views.py
class GroupViewSet(viewsets.ModelViewSet):
queryset = Group.objects.all()
serializer_class = serializers.GroupSerializer
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
if 'tourney' in request.GET:
queryset = queryset.filter(stage__tourney__tourney=request.GET['tourney'])
if 'tourney_1' in request.GET:
queryset = queryset.filter(stage__tourney__tourney_1=request.GET['tourney_1'])
if 'tourney_2' in request.GET:
queryset = queryset.filter(stage__tourney__tourney_2=request.GET['tourney_2'])
if 'stage' in request.GET:
queryset = queryset.filter(stage__stage=request.GET['stage'])
if 'stage_1' in request.GET:
queryset = queryset.filter(stage__stage_1=request.GET['stage_1'])
if 'stage_2' in request.GET:
queryset = queryset.filter(stage__stage_2=request.GET['stage_2'])
if 'group' in request.GET:
queryset = queryset.filter(group=request.GET['group'])
if 'group_1' in request.GET:
queryset = queryset.filter(group_1=request.GET['group_1'])
if 'group_2' in request.GET:
queryset = queryset.filter(group_2=request.GET['group_2'])
serializer = self.get_serializer_class()(
queryset,
many=True)
return Response(serializer.data)
Here we have one ViewSet with a bunch of obvious code and there will be more if there are more tables and more fields in the tables. I have up to 20 tables and the last table in this related chain can be able to filter though about 40 fields. So it's possible to have about 400 filter rules on all models so it's about 800 lines of stupid code just for one last model. Not good at all.
So is there any right known way to do this? This problems looks common so maybe are there embedded solutions from Django or any libraries of it?
The fastest way it is change the parameters name you pass to that view GET['tourney_2'] should available as GET['stage__tourney__tourney_2'].
Than the whole filtering will be:
queryset = self.get_queryset().filter(**request.GET)
Of course you should check parameters name to avoid SQL injection like request.GET['deleted'] = True
In real project you can not use **request.GET. You should cast it to normal Python dict filters = dict(request.GET) and check filters against sql injection attacks.
queryset = self.get_queryset().filter(**filters)
Please look how to build query dynamically.
How to dynamically provide lookup field name in Django query?
I have two routes in my api looking like that :
http://mywebsite/websites/website_1/annonces/
http://mywebsite/websites/website_2/annonces/
I need to make an ListAPIView merging these 2 routes but each route call its own database.
The two databases are made with the same django Model. I made two databases because it suits my architecture better.
The problem is that I have no column in my databases which indicates the website the records are from. The records are only differentiated by the names of their database.
I want to get all record in a single route but also being able to tell from which database they are from in the json response.
class Annonce(models.Model):
annonce_type = models.CharField(max_length=200, blank=True, null=True)
annonce_id = models.CharField(unique=True, max_length=200, blank=True, null=True)
url = models.TextField(blank=True, null=True)
region = models.TextField(blank=True, null=True)
class AnnoncesList(generics.ListAPIView):
authentication_classes = ()
permission_classes = ()
serializer_class = AnnonceListSerializer
pagination_class = LargeResultsSetPagination
filter_backends = (DjangoFilterBackend,)
filterset_fields = ('advert_type', 'asset_type', 'sales_type', 'price', 'area', 'department', 'department_id', 'city', 'postal_code')
def get_queryset(self):
queryset = Annonce.objects.using(self.kwargs["website_name"]).all()
return queryset
Make the queryset for each database, then use annotate() to add a column website_name for each record on queryset. Concatenate the querysets into a list (check this) (will hit all items on database), make sure the querysets have already been properly filtered.
from itertools import chain
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from django.db.models import Value, CharField
class AnnonceMergedList(ListAPIView):
serializer_class = AnnonceMergedListSerializer
queryset = Annonce.objects.all()
def list(self, request, **kwargs):
# Make the querysets for each database
q1 = self.get_queryset().using('website_1').annotate(website_name=Value('website_1', CharField()))
q2 = self.get_queryset().using('website_2').annotate(website_name=Value('website_2', CharField()))
# Filtering the querysets
q1 = self.filter_queryset(q1)
q2 = self.filter_queryset(q2)
# Merge (hit items on database)
data = list(chain(q1, q2))
serializer = self.get_serializer(data, many=True)
return Response(serializer.data)
The serializer for this view must receive the website_name to display the website the records are from
class AnnonceMergedListSerializer(serializers.ModelSerializer):
website_name = serializers.CharField(read_only=True) # Field from annotation
class Meta:
model = Annonce
fields = ('website_name', ...)