Django: from request.GET to QuerySet - django

I know how to create a html form with Django and I know how to access the values in request.GET.
I know how to use the ORM: MyModel.objects.filter(...)
Now I a missing how get from request.GET to a matching QuerySet
I could not find this in the Django docs.
Example:
class MyModel(models.Model):
name=models.CharField(max_length=1024)
if request.GET contains name=foo I would like to have a filter like MyModel.objects.filter(name__icontains=request.GET['name']).
I could code this on my own, but I think this is re-inventing the wheel. It feels too complicated to me.
Above is just one example. I prefer a solution which is more configuring than coding.

We can here construct a Q object:
from django.db.models import Q
MyModel.objects.filter(
Q([('{}__icontains'.format(k), v) for k, vs in request.GET.lists() for v in vs])
)
This will work as well given a certain key occurs multiple times in the querystring. Note that the fields you query over should support an __icontains [Django-doc] lookup.
Furthermore it might be unsafe to allow that, since for example a hacker could try to use user__password to make guesses on the (hashed) password of a user.

You should do it this way
filters = {}
for param, value in request.GET.items():
filters['{}_icontains'.format(param)] = value
queryset = MyModel.objects.filter(**filters)
Reference https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.QueryDict.items

You can use django-tables2 and django-filter
Explained here:
Django-filter can be used for generating interfaces similar to
the Django admin’s list_filter interface. It has an API very similar
to Django’s ModelForms. For example, if you had a Product model you
could have a filterset for it with the code:
import django_filters
class ProductFilter(django_filters.FilterSet):
class Meta:
model = Product
fields = ['name', 'price', 'manufacturer']
And then in your view you could do:
def product_list(request):
filter = ProductFilter(request.GET, queryset=Product.objects.all())
return render(request, 'my_app/template.html', {'filter': filter})
How to use django-filter in django-tables2, see django-filter in django-table2

Related

Django Admin: list_display() order by count of related object

Imagine you have a model Person which can have N Book instances.
I want to display the number of books a person has in the django admin change-list view.
It is easy to display the number (write a custom method on PersonAdmin).
But I could not find a way to make it sortable by the number of books a person has.
I read the docs for list_display, but could not find an answer.
According to the docs a query expression could be used. Is it possible to solve it this way?
After applying the answer, this is what it looks like. In this case each instance has N log entries:
Override the get_queryset() method of model admin with the help of annoate() and order_by() methods. Then, define a callable function(book_count()) to return the value to the change-list page
from django.db.models.aggregates import Count
class PersonAdmin(admin.ModelAdmin):
list_display = ('book_count', 'other_field')
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.annotate(book_count=Count('book')).order_by('-book_count')
return qs
def book_count(self, person_instance):
return person_instance.book_count
Why I didn't add ordering ?
While we calling the super(), Django will throw exceptions

Using django-filters, how can you do a lookup on multiple fields using OR?

Say I want to filter a built in django User model, but I only want to do so in 1 filter field, instead of a filter per field. That is, I want to emulate behaviour that django admin's search_fields (django admin search_fields docs), directly in the filter field.
Hence, for instance, instead of having a filter for field_name='first_name', then another filter for field_name'last_name' and so forth, I want to do something like field_name=['first_name', 'last_name', 'email', 'username'], where the same lookup_expr='icontains' can be used. Then the query is a simple OR lookup. Is this built in? I couldn't find it in the django-filter docs.
Or do I have to make custom filter for this. It seems like a very common use case.
I did it with a custom filter using Q objects.
import django_filters
from django.db.models import Q
class UserFilter(django_filters.FilterSet):
multi_name_fields = django_filters.CharFilter(method='filter_by_all_name_fields')
class Meta:
model = User
fields = []
def filter_by_all_name_fields(self, queryset, name, value):
return queryset.filter(
Q(first_name__icontains=value) | Q(last_name__icontains=value) | Q(username__icontains=value) | Q(email__icontains=value)
)
Then make sure to filter against the new filter field (multi_name_fields) and that should return what you are looking for.
I have done something similar. This should help:
def filter_by_all_name_fields(self, queryset, name, value):
"""
Split the filter value into separate search terms and construct a set of queries from this. The set of queries
includes an icontains lookup for the lookup fields for each of the search terms. The set of queries is then joined
with the OR operator.
"""
lookups = ['first_name__icontains', 'last_name__icontains', 'username__icontains', 'email__icontains',]
or_queries = [Q(**{lookup: value}) for lookup in lookups]
return queryset.filter(reduce(operator.or_, or_queries))

New Urls patterns and multiple variables with the same name

I'm using Django 2.0 type of urls, and I have urls with multiple variables in them, with the same name. I'm using also ClassBasedView
path('/companies/<int:pk>/products/<int:pk>/', AccountCompanyProductDetailView.as_view()
I'm using pk because is the primary key and CBV will know how to use it (similar for other Model fields).
If I use other names, CBV will not know what to search.
In a CBV how can I get the parameters and know which is which. ?
How Django knows pk from which Model I need in each position ?
Django does not know how to handle this. You need to rename your parameters and access them in your CBV.
This could look like the following:
urls.py:
path('/companies/<int:pk1>/products/<int:pk2>/', AccountCompanyProductDetailView.as_view())
views.py:
class AccountCompanyProductDetailView(DetailView):
model = Product
def get_object(self):
pk1 = self.kwargs['pk1']
pk2 = self.kwargs['pk2']
company = get_object_or_404(Company, pk=pk1)
product = get_object_or_404(Product, pk=pk2)
return product
You would need to do this in other views too. Override the according methods like get_queryset. Access the kwargs as shown above.

Rename RelatedField ordering filter in django rest framework

In a django rest framework APIView we specify ordering fields using the same method as the search filters and therefore we can specify ordering using related names.
ordering_fields = ('username', 'email', 'profile__profession')
The route would look like this: https://example.com/route?ordering=profile__profession
However we would rather avoid to display the relation between the models in the api and then specify profession instead of profile__profession. Such as https://example.com/route?ordering=profession
Can this be achieved without having to implement the sorting in the APIView's def get_queryset(self):?
It can be achieved with a minor change in def get_queryset(self).
def get_queryset(self):
query = super(UserListView, self).get_queryset().annotate(profession=F('profile__profession'))
return query
You will need to write your own OrderingFilter based on the Django REST framework one by overriding get_ordering and using a dictionary to map the "short name" to the full queryset string.

django: filtering a object-list

I've got a list of objects (properties for rent, in this case) that I am listing, the list needs to be filterable by a handful of criteria (max price, area, n_bedrooms ...) and I figured I could do it like this:
(r'^price:(?P<price_min>\d+)?-(?P<price_max>\d+)?/$', property_list)
This works, and allows urls like price:300-600/ to do the sensible thing.
However, it becomes unwieldy when there's around half a dozen attributes one could be filtering by, and ideally I would like clean urls (i.e. not including attributes for which we're not currently filtering in the url)
Is there a "standard" way to handle this in Django?
The right way to do this in django is Alex Gaynor, err django-filter by Alex Gaynor
It lets you get the filter parameters as http get and filters your queryset on those constraints.
From the docs:
import django_filters
class ProductFilterSet(django_filters.FilterSet):
class Meta:
model = Product
fields = ['name', 'price', 'manufacturer']
And then in your view you could do::
def product_list(request):
filterset = ProductFilterSet(request.GET or None)
return render_to_response('product/product_list.html',
{'filterset': filterset})
In case you don't need to reverse these urls you may use optional groups:
urls.py:
#the regex might need some work, it's just a concept
(r'^(price(/(?P<price_min>\d+))?(/to/(?P<price_max>\d+))?/$
views.py:
def view(request,min_price=None,max_price=None):
...
(django_filters is very nice though)