Django: filter queryset by multiple ID - python

My query is quite simple, I have a model Vendor in my Django REST app. What I want is to use a get response with a few ID and get back all the respective models with these ID. The GET url pattern could be something like this: r'^api/vendors?id=1,2,3'.
What I'm thinking of right now is to use ListAPIView, and in the list method filter my queryset with all the id in the url. But I'm not sure exactly how to achieve this (filtering queryset with a list of id, I'm very new to both Python and Django), so if anyone can provide any advice on this it would be greatly appreciated.

(Unfortunately I do not know django REST, so here is a pure django solution)
Using the ListAPIView you can access the URL (or GET) params and modify the queryset.
class MyVendorView(ListAPIView):
# attributes
def get_queryset(self):
id_string = self.request.GET.get('id')
if id_string is not None:
ids = [int(id) for id in id_string.split(',')]
return Vendor.objects.filter(id__in=ids)
else:
return Vendor.objects.all()
# other methods
please note that I'm ingoring any attributes or other attributes needed
What's happening here then?
Overriding get_queryset will control what results we get from hitting the view
self.request.GET.get('id') Will extract the value of the id query parameter from the url like so localhost:8000/api/vendors?id=1,2,3 the result will be a string "1,2,3".
filter(id__in=ids) lets you say select stuff that has an value in this list of ids

Related

How to sort a queryset of different models in Wagtail/Django?

I have a Wagtail site and I'm making a listing page for a few different Page types/Content types. I filter by a snippet field first:
def highlights_list(category='Highlight'):
tag = ContentCategory.objects.get(name=category)
highlights = Page.objects.filter(
Q(event__categories=tag)|
Q(newspage__categories=tag)|
Q(contentpage__categories=tag)).live()
return highlights.order_by('-last_published_at')
In Wagtail all content types inherit from the base Page class which makes creating a queryset with all the content types I want really easy. But I can't work out how to sort nicely.
Sorting by last_published_at is fine for NewsPage and ContentPage but not for Event where I'd like to sort by the DateTimeField for the event.
I thought about making a #property on all the models called sort_date which uses the datetime field specific to each model that I'd like to sort on, but that just doesn't work.
Any suggestions are very welcome!
I can think of two solutions for this, but only one seems to be easier to do. I changed your function above to this:
def highlights_list(category='Highlight'):
tag = ContentCategory.objects.get(name=category)
highlights = Page.objects.filter(
Q(event__categories=tag)|
Q(newspage__categories=tag)|
Q(contentpage__categories=tag)
).live().specific()
return sorted(
highlights,
key=lambda p: getattr(p, 'date') or getattr(p, 'last_published_at'),
reverse=True
)
What this does is it takes all the pages from highlights and sorts them based on the value in date or last_published_at as if they're the same field. Because only the Event page has a date field, last_published_at is used as the fallback, a field all live pages have in common. If you leave out specific() in your queryset like in your question, it throws an AttributeError saying 'Page' object has no attribute 'date'.
As a bonus, the sorting part doesn't use any database queries since it is done in Python. Note that this returns a list based on the original QuerySet.
The second solution involves database transactions and Wagtail hooks and has more code, but that is something I don't want to share as I haven't thought of how to write that out.

And operator in a Django filter

I created a Django Rest Framework API endpoint. I would like this endpoint to retrieve all the records with the Status field set to Free, so i did this:
queryset = tst.objects.using('screener').filter(Status=Free)
Now, i want to retrieve not only the fields with the field set to Free, but also those with the status set to Pending.
I tried this:
class tstList(generics.ListCreateAPIView):
criterion1 = Q(Status="Free")
criterion2 = Q(Status="Pending")
queryset = tst.objects.using('screener').filter(criterion1&criterion2)
For some reason, this view will retrieve nothing. If i try the queries individually, though, they will work:
queryset = tst.objects.using('screener').filter(criterion1) #works
use this
queryset = tst.objects.using('screener').filter(criterion1|criterion2)
right now in filter you are using and condition but you need a or condition
for more information you can read this article
You probably don't want the & operator but the or (|)
https://docs.python.org/3.8/library/stdtypes.html#boolean-operations-and-or-not

django- how would I create a sort for a query to put one specific element first, then remaining by some field?

Specifically, I am looking to get a query of users. My User model has a first_name and last_name field. What I need to do is order the request.user at the top of the results, and the remaining users in alphabetical order by last_name, first_name. The last part is easy:
q = User.objects.all().order_by('last_name', 'first_name')
However I am not sure how to ensure that the request.user is the first result in the query. This is all being done for a django rest framework view, and thus (I believe) I need to have it done through a query which is passed on to the serializer.
First, it might be better design to not do this. Have some other endpoint that returns your own user object if you need it, and in the list view treat yourself no differently. But if you really neeed to.
You probably could use an annotation.
User.objects.annotate(is_me=Case(
When(pk=request.user.pk, then=Value(True)),
When(pk__ne=request.user.pk, then=Value(False)),
output_field=BooleanField())
).order_by('-is_me')
If you don't really need the result to be a queryset you can try the following:
import itertools
me = request.user
others = User.objects.exclude(id=me.pk)
users_list = itertools.chain([me], others)

When to use slug field and query string

I am new in web development in django i don't know when to use slug field and when to use query string parameters in the url.Can anyone suggest me practical differences between them.
Using slugs keep urls simple and clean, thereby easy to remember. Consider the following example:
example.com/post/hello-world/
v/s
example.com/?post=hello-world
Obviously, first one is cleaner.
But query string parameters have their uses too. For example, when you search for an object.
example.com/search/?q=hello-world
or when you need to pass multiple parameters
example.com/search/?q=hello+world&lang=en&something=else
In slug related django urls you have a url associated to a view. But you cannot pass querystring parameters to your views.
Ex -example.com/post/hello-world/ does not pass any parameter to your view function.
But if you want to pass additional parameters to your views, ex,
example.com/search/?q=hello-world
here q=hello-world is a query string parameter passed to your views.
And inside your views function you can get these parameters in request.GET
So your views function goes something like this
def helloworld():
qParams = request.GET.get('q', '')
....
....
Hope this helps.

Tastypie Dehydrate reverse relation count

I have a simple model which includes a product and category table. The Product model has a foreign key Category.
When I make a tastypie API call that returns a list of categories /api/vi/categories/
I would like to add a field that determines the "product count" / the number of products that have a giving category. The result would be something like:
category_objects[
{
id: 53
name: Laptops
product_count: 7
},
...
]
The following code is working but the hit on my DB is heavy
def dehydrate(self, bundle):
category = Category.objects.get(pk=bundle.obj.id)
products = Product.objects.filter(category=category)
bundle.data['product_count'] = products.count()
return bundle
Is there a more efficient way to build this query? Perhaps with annotate ?
You can use prefetch_related method of QuerSet to reverse select_related.
Asper documentation,
prefetch_related(*lookups)
Returns a QuerySet that will automatically
retrieve, in a single batch, related objects for each of the specified
lookups.
This has a similar purpose to select_related, in that both are
designed to stop the deluge of database queries that is caused by
accessing related objects, but the strategy is quite different.
If you change your dehydrate function to following then database will be hit single time.
def dehydrate(self, bundle):
category = Category.objects.prefetch_related("product_set").get(pk=bundle.obj.id)
bundle.data['product_count'] = category.product_set.count()
return bundle
UPDATE 1
You should not initialize queryset inside dehydrate function. queryset should be always set in Meta class only. Please have a look at following example from django-tastypie documentation.
class MyResource(ModelResource):
class Meta:
queryset = User.objects.all()
excludes = ['email', 'password', 'is_staff', 'is_superuser']
def dehydrate(self, bundle):
# If they're requesting their own record, add in their email address.
if bundle.request.user.pk == bundle.obj.pk:
# Note that there isn't an ``email`` field on the ``Resource``.
# By this time, it doesn't matter, as the built data will no
# longer be checked against the fields on the ``Resource``.
bundle.data['email'] = bundle.obj.email
return bundle
As per official django-tastypie documentation on dehydrate() function,
dehydrate
The dehydrate method takes a now fully-populated bundle.data & make
any last alterations to it. This is useful for when a piece of data
might depend on more than one field, if you want to shove in extra
data that isn’t worth having its own field or if you want to
dynamically remove things from the data to be returned.
dehydrate() is only meant to make any last alterations to bundle.data.
Your code does additional count query for each category. You're right about annotate being helpfull in this kind of a problem.
Django will include all queryset's fields in GROUP BY statement. Notice .values() and empty .group_by() serve limiting field set to required fields.
cat_to_prod_count = dict(Product.objects
.values('category_id')
.order_by()
.annotate(product_count=Count('id'))
.values_list('category_id', 'product_count'))
The above dict object is a map [category_id -> product_count].
It can be used in dehydrate method:
bundle.data['product_count'] = cat_to_prod_count[bundle.obj.id]
If that doesn't help, try to keep similar counter on category records and use singals to keep it up to date.
Note categories are usually a tree-like beings and you probably want to keep count of all subcategories as well.
In that case look at the package django-mptt.

Categories