Django queryset verify if fields exists - django

I want to implement a custom ordering backend (using rest_framework.filters.OrderingFilter) and I basically want to check if the field exists for my queryset first before filtering it without evaluating the queryset.
something like
if queryset.is_available_field(my_field):
queryset = queryset.order_by(my_field)
I cannot try / except because I don't know exactly when my queryset will be evaluated.

I found one solution that works for my case but might not cover some edge cases. Also it is not very elegant.
def has_field(queryset, field):
model_fields = [f.name for f in queryset.model._meta.fields]
annotations = list(queryset.query.annotations.keys())
return field in model_fields + annotations

Related

Django: from request.GET to QuerySet

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

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))

prefetch_related and Prefetch object issue, filtering thru reverse Foreign Key

I have 2 models, company and Product.
class Product(Meta):
company = models.ForeignKey(Company, related_name='products', on_delete=models.CASCADE)
form the database I'm trying to get the Company data and the corresponding products.
From the products I want to get only the name and to be ordered descending by updated_at, created_at.
I'm working with Prefetch object and prefetch_related and definitively I have multiple misunderstandings how they work.
def get_queryset(self):
qs = Company.objects.prefetch_related(
Prefetch('products', queryset=Product.objects.only('name').order_by('-updated_at', '-created_at'))).get()
return qs
The error that I receive is:
get() returned more than one Company
Because I closed the prefetch_related method/function with ))) :
I thought get() will act over the Company object and get it using the pk/slug from the url(as get do by default in DetailView). Seems that is not the case.
I'm already using 'products' the related name in the Prefetch object, why in queryset is necessary to tell again the model queryset=Product.objects.... ?
I was looking at the following example in django documentation:
Question.objects.prefetch_related(Prefetch('choice_set')).get().choice_set.all()
If there is 'choice_set' in Prefetch object why is called at the end choice_set.all() ?
Isn't Django attached to the quesryset in prefetch_related the products to the queryset (question.choice_set) ?
I think my problem is that I don't understand the order of execution, and I'm confused how methods are chained, even if are closed by ')'
queryset.get() will only work if the queryset has a single object. If it contains zero or more than one objects, you'll get an error.
You should return a queryset from the get_queryset object. In the class based view, the code that filters on the pk/slug is in get_object.
The prefetch_related method is useful if you want to fetch the products for multiple countries. The way that the Django docs use get() is confusing in my opinion - if the queryset has a single item then prefetch_related is over-complicated.
If you have a single company, then there is no advantage, and the code will be simpler if you fetch the countries separately, e.g. in get_context_data.
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['products'] = Product.objects.filter(company=self.object).Product.objects.only('name').order_by('-updated_at', '-created_at')))
return context
I've removed the only('name') call. It's an optimisation that you probably don't need.
If you really want to use prefetch_related, then remove the get().
qs = Company.objects.prefetch_related(
Prefetch('products', queryset=Product.objects.order_by('-updated_at', '-created_at')))
By specifying the queryset above, you are able to change the order (you could filter it if you like). If you don't want to customize the queryset you can simply do:
Company.objects.prefetch_related('products')
When you use Question.objects.prefetch_related(...), the queryset is still a list of questions. You need to call choice_set.all() on the individual instances to access their choices. This won't cause any additional queries, because Django has already prefetched the choices.
queryset = Question.objects.prefetch_related(Prefetch('choice_set'))
for question in queryset:
print(question) # the question
print(question.choice_set.all()) # the related choices

Get a queryset's current order_by ordering

I'd like to find adjacent items in a queryset given a primary key, as asked here. However I want this to work for any given queryset, that i haven't set the ordering for myself. How can I find the current ordering of a queryset for __gt and __lt filtering?
For example:
queryset = MyModel.objects.all().order_by('some_field')
next_obj = get_next(queryset, after_pk, 10)
...
def get_next(queryset, pk, n):
#ordering is unknown here
obj = queryset.get(pk=pk)
field = queryset.get_order_by_field_name() #???
return queryset.filter(**{field+'__gt': getattr(obj, field)})[:n]
The model may define a default ordering and I can check if the queryset is ordered, but I don't think either are helpful in this case.
You can retrieve the list of columns from order_by clause by accessing queryset.query.order_by property.
From django docs:
The query attribute is an opaque object. It represents the internals of the query construction and is not part of the public API. However, it is safe (and fully supported) to pickle and unpickle the attribute’s contents as described here.

django rest framework search filter all fields

I have a base model view set which i can handle order, pagination and search functionalities.
When i have proper filter backends, i can set ordering_fields, search_fields but my question is that:
like i can set ordering_fields = '__all__' isn't it possible to set search_fields for all?
I've looked for deep inside filtering mechanism and came here:
def filter_queryset(self, request, queryset, view):
search_fields = getattr(view, 'search_fields', None)
if not search_fields:
return queryset
orm_lookups = [self.construct_search(six.text_type(search_field))
for search_field in search_fields]
for search_term in self.get_search_terms(request):
or_queries = [models.Q(**{orm_lookup: search_term})
for orm_lookup in orm_lookups]
queryset = queryset.filter(reduce(operator.or_, or_queries))
return queryset
drf fails on generate search_fields if it is set search_fields='__all__'
Is there any other way to provide search by all functionality?
Not possible and it's not a good idea anyway..
One reason is because there can be different types of searching query (__istartswith, __iexact, etc.), which you can set up for each field name.
You can also have different field types which won't necessary support text-base search.
More important - it will result in much slower queries, while you keep adding new fields into the model. It's always better to be explicit, so you know which fields are actually searched upon.
If your searching feature relies on multiple fields, maybe you should consider haystack or look for any other dedicated searching engine.