When using DjangoListObjectType from graphene_django_extras, I can define a custom qs property on a SearchFilter.
The qs function has the object as its only argument, and through that I can get the request, and in turn the query string which includes the queried fields.
Before hacking together my own parser to get these fields, am I going about this the wrong way? Or is there something else out there?
The idea is to have quite a rigid approach, as we have 7 types of paginated list types with fields that result in a few unnecessary database hits, so we want to prefetch a few fields.
Graphene has a dataloader approach which kind of looks right, but more complicated than just prefetching them at the qs stage.
Re the dataloader approach, I tried something like this
class UserLoader(DataLoader):
def batch_load_fn(self, keys):
users = {user.id: user for user in User.objects.filter(id__in=keys)}
return Promise.resolve([users.get(user_id) for user_id in keys])
user_loader = UserLoader()
class BookType(DjangoObjectType):
...
author = graphene.Field(UserType)
def resolve_author(self, *args, **kwargs):
return user_loader.load(self.author.id)
Which kind of obviously feels completely wrong, but is what they do in the docs.
Using a DataLoader is the correct approach but do self.author_id instead of self.author.id.
By calling self.author.id you're fetching the author for each book (one SQL query per author) then getting the id attribute from each author.
When you add the author ForeignKey to your Book model, behind the scenes, Django adds a column to the books table—author_id. You can pass this value to your DataLoader and have it fetch all the corresponding Authors in a single SQL query using a SELECT IN.
https://docs.djangoproject.com/en/3.2/ref/models/fields/#database-representation
You'll notice that the Graphene Docs are doing the same thing with best_friend_id in their example.
Related
In my django website, I have 3 classes: Thing, Category and SubCategory.
Thing has 2 ForeignKeys: "Category" and "SubCategory" (such as Car and Ferrari).
SubCategory has 1 ForeighKey: "Category" (Ferrari is in the category Car)
When I create an instance of Thing in the Admin part and when I choose a Category, I would like that the "SubCategory" field only shows the SubCategories linked to the Category I chose. Is that possible?
I saw the possibility to change the AdminForm like:
class ThingFormAdmin(forms.ModelForm):
def __init__(self,Category,*args,**kwargs):
super (ThingFormAdmin,self ).__init__(*args,**kwargs) # populates the post
self.fields['sub_category'].queryset = SubCategory.objects.filter(category= ... )
But I don't know what to write on the ...
Thanks for the help!
in general always this solution would work:
you need some javascript to catch what has been selected for first selection. then do filtering agin using javascript.
but in django admin, there is autocomplete_fields available. using this would create a kind of selection-input that uses ajax to do some magic filtering on choices when user types some characters. it uses the get_search_results method of the related models admin.ModelAdmin class. overriding that method and giving some extra data to that method could help. but it's the longest way to walk.
Thanks! I will look in the 1st answer after the 2nd, because the fact that I don't have to write JS is very nice, as I am very bad in it.
I manage to use the autocomplete_field to work, but I am stuck with the redefinition of the get_search_results method. If I understood correctly the doc, it will be something like:
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
try:
cat = search_term
except ValueError:
queryset |= self.model.objects.all()
else:
queryset |= self.model.objects.filter(category=cat)
return queryset, use_distinct
But I don't understand from where this search_term comes from, and how I can specify it. Any ideas?
https://simpleisbetterthancomplex.com/tutorial/2018/01/29/how-to-implement-dependent-or-chained-dropdown-list-with-django.html
This tutorial will walk you through every step of doing whatever I presume you need to do.
There are hundreds of questions here on various django annotate/aggregate constructions and filters but I couldn't find this simple use-case asked (or answered).
I have a "Payments" model and associated ListAPIView ViewSet endpoint with nicely setup DjangoFilters so the client can filter on created__lte, created__gte, company= etc.
Now I want to add an endpoint that derives from the above but only returns the sum of some fields and count of the total filtered objects.
I know exactly how to do this if I would just write the View "from scratch" (I can just hack the DRF View into executing get_queryset().aggregate() then feeding into a serializer and returning), but I want to do it in a "Django-way" if possible.
For example, combined with a serializer that defines "total_amount" and "nbr", this (almost) works:
queryset = models.Payment.objects.values('company').annotate(total_amount=Sum('amount'),
nbr=Count('id'))
The values() call groups by "company" (a sub-field of Payment), which in combination with annotate() performs a sum by all company payments and annotates with total_amount/nbr. Adding filtering query parameters magically adjusts what goes into the annotation properly.
The problem is, what if I don't want to group (or even filter) by "company", I just want to "group by all"? Is there a way to do that?
I realize this is already a bit magical but the Django-esque way of doing grouping for annotation is this as far as I know.
I also realize I'm probably really better off just by hijacking .retrieve() to evaluate the queryset with .aggregate() tacked at the end and on-the-fly creating the response... still curious though :)
I ended up with the "hack", overriding list() in the View with an .aggregate call and the packing it for the Response through a serializer. This was the most canonical way I could figure out (I mean, re-using as many of the moving parts of Django/DRF as possible like automatic filtering of the queryset, serializing etc).
Bonus: note the Coalesce() wrap which is needed because Sum() doesn't return 0 if the set is empty.
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
stats = queryset.aggregate(total_amount=Coalesce(Sum('amount'), 0),
total_vat=Coalesce(Sum('vat'), 0),
nbr_payments=Count('id'))
# .aggregate() returns a dict of the results, not a QuerySet. Wrap it
# into a response through the serializer.
sclass = self.get_serializer_class()
return Response(sclass(stats).data)
Today I have written a DRF view method using prefetch_related:
def post(self, request, post_uuid, format=None):
post = Post.objects.prefetch_related('postimage_set').get(uuid=post_uuid)
postimage_set = post.postimage_set.all()
for image in postimage_set:
...
return Response('', status.HTTP_200_OK)
And I fear that I am using prefetch_related wrongfully with this. Does it make sense to use prefetch_related here or will this fetch all posts as well as all postimages and then filter this set to just one instance? I'm super thankful for any help on this.
Looks kinda unnatural. Without looking at your database structure I can only guess, that what you really want to do is:
PostImage.objects.filter(post__uuid=post_uuid) (mind the usage of a dunder between post and uuid - that simple trick follow the relation attribute) which should result in a single query.
Moreover, if you are uncertain of a number of queries that will hit the database, you can write a very precise test with one of the assertions, that is available since Django 1.3: assertNumQueries
with the new django-filter v2 coming out. I was wondering how i could replicate the below functionality with it.
Before my filterset was as so
class PostFilterSet(filters.FilterSet):
date = filters.DateFilter(
name='date',
lookup_expr=DATETIME_LOOKUPS,
method='filter_date')
class Meta:
# Meta Options here
def filter_date(self, queryset, name, value):
# filter logic
What this would essentially let me do on the api is make filter request like this 'https://example.com/api/post/?date__gt=exampledate' but with the latest update i can no longer keep the list of DATETIME_LOOKUPS and have to use the Lookup choice filter which changed the request to 'https://example.com/api/post/?date=exampledate&date_lookup=exact' is there a way to make it work like it did before in a painless way ?
I would like to create a dict or dict-like object based on a django orm query involving prefetched objects. Due to the number of objects involved, I would like to do as much of this in the database as possible.
To use the familiar example structure, if I have an Author and Books with the usual ForeignKey from Book to Author, and I also have an Edition with a ForeignKey back to Book, I would like to do something like
Author.objects.prefetch_related(Prefetch('book_set', queryset=Book.objects.filter(cycle__id=3).select_related('edition__binding'), to_attr="bindings")) # clearly this is wrong
and ultimately call .values() or something of the sort to get a dict-like result consisting of rows of authors, which should include an entry "bindings", which contains a list of bindings that each author has been published in. The stretch goal would be to have bindings be a semicolon-separated list, ie [{"Author": "Daniel Dennett", "bindings": "paper; hardback; cloth"}, {"Author": "Jemiah Jefferson", "bindings": "paper; zine"}]
So far, I have been able to get a field like "bindings" attached to the queryset using prefetch_related and select_related, as above, but this field is not included in the result of a call to .values(). This means I have to loop over the objects, which simply takes too long for my purposes (there are many objects, and the request times out)
Create custom a Concat annotation which will mimic MySQL GROUP_CONCAT function. Then you can use .values on the annotated bindings.
For Django 1.8 your Concat class can be something like this:
from django.db import models
class Concat(models.Aggregate):
# supports GROUP_CONCAT(DISTINCT field SEPARATOR str_val)
# do not support order_by
function = 'GROUP_CONCAT'
template = '%(function)s(%(distinct)s%(expressions)s SEPARATOR "%(separator)s")'
def __init__(self, expression, distinct=False, separator=None, **extra):
super(Concat, self).__init__(
expression,
distinct='DISTINCT ' if distinct else '',
separator=separator or '',
output_field=models.CharField(),
**extra)
While in Django 1.7
class Concat(models.Aggregate):
def add_to_query(self, query, alias, col, source, is_summary):
#we send source=CharField to prevent Django from casting string to int
aggregate = SQLConcat(col, source=models.CharField(), is_summary=is_summary, **self.extra)
query.aggregates[alias] = aggregate
#for mysql
class SQLConcat(models.sql.aggregates.Aggregate):
sql_function = 'group_concat'
#property
def sql_template(self):
if self.extra.get('separator'):
return '%(function)s(%(field)s SEPARATOR "%(separator)s")'
else:
return '%(function)s(%(field)s)'
Now you can do:
Author.objects.annotate(bindings=Concat('book__edition__binding')).values('name', 'bindings')
unfortunately this wont filter your books by cycle__id=3, but you can apply the filter before the annotation happens.
Author.objects.filter(book__cycle__id=3).annotate(bindings=Concat('book__edition__binding')).values('name', 'bindings')
This will strip from the result authors without book with cycle__id=3.