Django - QuerySet inside another QuerySet when grouping based on a field? - django-queryset

My goal is to pair a QuerySet with a key (it is mostly a field of a model), putting all those pairs in a parent QuerySet. I have tried using QuerySet API (values, values_list) but haven't succeeded in achieving the result I want. To make thing easy, I will use an example to demonstrate.
Let's say I have a Item model:
choices = [
('A', 'A'),
('B', 'B'),
('C', 'C')
]
class Item(models.Model):
name = models.CharField()
type = models.CharField(choices=choices)
I want to write a query using Django ORM which groups the Item based on type and results in the following QuerySet:
<QuerySet [('A', <QuerySet [<Item>, ...]>), ('B', <QuerySet [<Item>, ...]>), ('C', <QuerySet [<Item>, ...]>)]>
Is it possible to achieve the above QuerySet ?

Related

how to format field name and round the value

I got a query "top 3 employees in terms of total invoice value" based on the following related tables:
[{
"customerid__supportrepid__id": 3,
"sells": 833.0400000000013
},
...]
I would like the first filed to be: "employee_id" and a sells field value
class CustomerViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Customer.objects.all()
serializer_class = CustomerSerializer
#action(detail=False, methods=['get'])
def top_three_employees(self, request):
total_sells_by_employees = Invoice.objects \
.select_related('customerid') \
.select_related('customerid__supportrepid') \
.values('customerid__supportrepid__id') \
.annotate(sells=Sum('total')) \
.order_by('-sells')
return Response(total_sells_by_employees)
You can rename fields in a Django queryset using the values() method [django-doc]:
MyModel.objects.values(renamed_field_name=F('original_field_name'))
This will thus construct a queryset where the original_field_name is renamed to renamed_field_name.
In a serializer, you can then rename the field with:
class MySerializer(serializers.Serializer):
renamed_field_name = serializers.CharField()
So by defining a field with the target name, and omitting a source, it will automatically take the field with the same name in the object that is serialized.
You can use .annotate() to create an aliased, transformed field, and then serialize that field:
from django.db.models.functions import Round
MyModel.objects.annotate(
rounded_field=Round('field_name', 2)
).values('rounded_field')
This will add a rounded_field to the queryset that is the field_name rounded to two digits.
You can then rename this in the serializer fields mapping, with:
class MyModelSerializer(serializers.ModelSerializer):
rounded_field = serializers.SerializerMethodField()
class Meta:
fields = ('rounded_field',)
def get_rounded_field(self, instance):
return instance.rounded_field
This will thus serialize the rounded_field as rounded_field, under the name you specified in the fields of the serializer.

How to write a dynamic django orm queryset based on the filters passed?

i need to apply filters dynamically on a queryset depending on the filters passed as a json from the front end.
for ex.
if json passed is : { id:[1,2,3] }
then queryset will be : Model.objects.filter(id__in=id)
if json is passed as: { id:[1,2,3],country:['india','australia'] }
then queryset will be : Model.objects.filter(id__in=id,country__in=country)
how to achieve this dynamically?
You could use an if/else statement to detect if the country list is empty:
if not country:
Model.objects.filter(id__in=id)
else:
Model.objects.filter(id__in=id, country__in=country)
Edit:
A comment pointed out that the amount of filters are dynamic. So in Django .filter() returns another QuerySet and Querysets are lazily evaluated, meaning they can be chained and be used in a loop.
So using unpacking (**), we can compose the kwargs to pass to .filter().
Because the exact datatype/schema of the filters is not provided, I will use a dictionary as example, filter_dictionary. In this dictionary the key is the filter that will be used and the value will be a list.
# Compose first Queryset
qs = Model.objects.all()
# Loop over dictionary
for key, value in filter_dictionary.items():
# Use unpacking to compose kwarg
qs.filter(**{'{0}__in'.format(key): value})
# do something with the QuerySet
Just use a django_filters
from django_filters import rest_framework as filters
class ContentFilter(filters.FilterSet):
id = filters.NumberFilter(lookup_expr="in")
country = filters.CharFilter(lookup_expr="in")
class Meta:
model = <your_model>
fields = ['id', 'country']
Then add to your view class
filterset_class = ContentFilter

django-filter (django rest framework) - method filter with 3 parameters

I'm using django rest framework to create an api and django-filter to provide nice way for users to see how filters work in the browsable api part of the site.
I have a need to filter queryset by a result of a method call. Unfortunately it needs 3 parameters to be provided by the user (calculate distance from centre point using lat, lng, radius).
I know I can declare a non model field in the filterset with a method to call but then just one parameter is passed to the method.
I can declare 3 non model fields but then I end with 3 different methods or calling the same one with 1 changing parameter 3 times.
example code:
class PersonFilter(FilterSet):
status = ChoiceFilter(field_name='status', choices=Person.STATUS_CHOICES)
# I show an example of what I need to achieve below, obviously it will not work as
# I need to give the user 3 fields to fill in and call the method only once with their values...
latitude = NumberFilter(label='latitude', method='check_if_in_range')
longitude = NumberFilter(label='longitude', method='check_if_in_range')
radius = NumberFilter(label='radius', method='check_if_in_range')
class Meta:
model = Person
fields = 'status', 'latitude', 'longitude', 'radius'
example method to filter by 3 parameters:
def check_if_in_range(self, queryset, name, value):
here I need access to the values from 3 non model form fields...
do calculation and filter the queryset
return <filtered queryset>
Is this even doable?
I want my users to be able to use:
<base_url>?longitude=234234&latitude=232342&radius=34
to filter persons through the API...
Thank you for your time & help!
Tomasz
You can do something like this:
class PersonFilter(FilterSet):
status = ChoiceFilter(field_name='status', choices=Person.STATUS_CHOICES)
radius = NumberFilter(label='radius', method='check_if_within_range')
class Meta:
model = Person
fields = 'status', 'radius'
def check_if_within_range(self, queryset, name, value):
base_point = Point(latitude=float(self.request.GET.get("latitude")),
longitude=float(self.request.GET.get("longitude")))
return queryset.distance_within(base_point, value)
Based on how you want to calculate the distance and filter queryset you need to have a custom method. Here I have assumed you'll have distance_within() method in your custom queryset manager.
You can refactor as per your need/structure.

rails 5 enum where "like"

I'm trying to query an activerecord model enum and use the like operator in the where method, and it just doesnt work. Is there some trick to allow me to query an enum this way? Works fine on regular columns. Here it is in the console.
Regular string column (title) works as shown below
irb(main):092:0> Proposal.select(:id,:department,:status).where('title like "test%"')
Proposal Load (0.3ms) SELECT "proposals"."id", "proposals"."department", "proposals"."status" FROM "proposals" WHERE (title like "test%") LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Proposal id: 7, department: 1, status: "In Progress">, #<Proposal id: 61, department: 2, status: "Won">]>
However, trying it on an enum, gives no results.
irb(main):094:0> Proposal.select(:department,:status).where('status like "Wo%"')
Proposal Load (0.3ms) SELECT "proposals"."department", "proposals"."status" FROM "proposals" WHERE (status like "Wo%") LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
Any idea why I can't use like operator on enum? I'm trying to use this to filter a view with datatables.net server side processing.
Enum stores data as integer like 0,1,2,3... Then rails map number to enum value defined in model. That is the reason why you doesn't get result

How can I add multiple instances to a Django reverse foreign key set in a minimal number of hits to the database?

For example, I have two models:
class Person(models.Model):
name = models.CharField(max_length=100)
class Job(models.Model):
title = models.CharField(max_length=100)
person = models.ForeignKey(Person)
I have a list of job ids--
job_ids = [1, 2, ....]
that are pks of Job model instances
I know I can do--
for id in job_ids:
person.jobs.add(job_id)
but this will be many more queries than if I could do--
person.jobs.add(job_ids)
where it would unpack the list and use bulk_create. How do I do this? Thanks.
Have you tried
person.jobs.add(*job_ids)
In my case I used a filter query and had a list of objects (as opposed to IDs). I was getting an error similar to
TypeError: 'MyModel' instance expected, got [<MyModel: MyModel Object>]
...before I included the asterisk.
credit (another SO question)
If you didn't create your jobs yet, you can create them by adding bulk=False
jobs_list=[
Job(title='job_1'),
Job(title='job_2')
[
person.jobs.add(*jobs_list, bulk=False) # your related_name should be 'jobs', otherwhise use 'job_sets'.

Resources