Is there a way of having only a filtered part of a model as a SeachQuerySet?
Something like:
query = SearchQuerySet().models(Entry.filter(categories__name='something'))
instead of
query = SearchQuerySet().models(Entry)
The field I want to filter by is a manytomany field and non indexed.
The search index doesn't store any relations, it is therefore 'flat'. You can only add your categories' IDs to the index for Entry (note that you have to use a prepare_-method for this):
class EntryIndex(indexes.SearchIndex, indexes.Indexable):
# your other fields
categories = MultiValueField()
def prepare_categories(self, obj):
return [category.pk for category in obj.categories.all()]
The you can do something like:
category = Category.objects.get(name='something')
sqs = SearchQuerySet().models(Entry).filter(categories=category.pk)
Related
I'm having a Django model that contains a JSON field to store a list, like so:
class Movie(models.Model):
tags = JSONField()
As such, a movie mymovie contains a list of tags, such as:
mymovie.tags = ["horror", "love", "adventure"].
And now, I want a query to fetch all movies having at least one tag of a list of tags, like so:
GET /movies?tags=horror,cowboy
In my previous example, the query will get mymovie, because it has horror in its tag, even if it doesn't have cowboy.
Using *django_filters, I managed to do it with the following implementation:
class CharInFilter(BaseInFilter, CharFilter):
pass
class MovieFilter(FilterSet):
class Meta:
model = Movie
fields = ["tags"]
tags = CharInFilter(method="tags_filter")
def tags_filter(self, queryset, name, value):
# value will contain a list of tags, and we want any movie that at least one of
# its tag is in the list of provided tags
query = Q()
for tag in value:
query |= Q(
tags__icontains=tag
)
if query:
queryset = queryset.filter(query)
return queryset
It works, but it feels very hacky, and I won't be surprised if an ad hoc implementation of this exists.
Does someone have a better idea ?
Thank youuuu ^^
I'm querying a ManyToMany field (tags). The values come from a list:
tag_q = Q()
tag_list = ["work", "friends"]
for tag in tag_list:
tag_q &= Q(tags__tag=tag)
Post.objects.filter(tag_q)
When I have only one value, it works flawlessly, but when more objects are stacked it always return an empty queryset.
I didn't used tags__tag__in=tag_list because it returned any post that contained any of the tags in the tag list (an OR filter), and I need an AND filter here.
This is my models:
class Tag(models.Model):
tag = models.CharField(max_length=19, choices=TagChoices.choices())
class Post(models.Model):
tags = models.ManyToManyField(Tag, related_name='posts', blank=True)
This is the Q object that is being passed in the filter query:
(AND: ('tags__tag', 'work'), ('tags__tag', 'friends')
You can not work with a Q object like that: a filter is an existential quantifier, not a universal. It means you are looking for a single Tag that has as tag name work and friends at the same time.
What you can do is work with a list of tags, and then count if the number of Tags is the same as the number of items to search for, like:
tag_list = ['work', 'friends']
Post.objects.filter(tags__tag__in=tag_list).annotate(
ntags=Count('tags')
).filter(ntags=len(set(tag_list))
since django-3.2, you can work with .alias(…) [Django-doc] instead of .annotate(…) [Django-doc]:
tag_list = ['work', 'friends']
Post.objects.filter(tags__tag__in=tag_list).alias(
ntags=Count('tags')
).filter(ntags=len(set(tag_list))
I am trying to create a filter search bar that I can customize. For example, if I type a value into a search bar, then it will query a model and retrieve a list of instances that match the value. For example, here is a view:
class StudentListView(FilterView):
template_name = "leads/student_list.html"
context_object_name = "leads"
filterset_class = StudentFilter
def get_queryset(self):
return Lead.objects.all()
and here is my filters.py:
class
StudentFilter(django_filters.FilterSet):
class Meta:
model = Lead
fields = {
'first_name': ['icontains'],
'email': ['exact'],
}
Until now, I can only create a filter search bar that can provide a list of instances that match first_name or email(which are fields in the Lead model). However, this does now allow me to do more complicated tasks. Lets say I added time to the filter fields, and I would like to not only filter the Lead model with the time value I submitted, but also other Lead instances that have a time value that is near the one I submitted. Basically, I want something like the def form_valid() used in the views where I can query, calculate, and even alter the values submitted.
Moreover, if possible, I would like to create a filter field that is not necessarily an actual field in a model. Then, I would like to use the submitted value to do some calculations as I filter for the list of instances. If you have any questions, please ask me in the comments. Thank you.
You can do just about anything by defining a method on the filterset to map the user's input onto a queryset. Here's one I did earlier. Code much cut down ...
The filter coat_info_contains is defined as a CharFilter, but it is further parsed by the method which splits it into a set of substrings separated by commas. These substrings are then used to generate Q elements (OR logic) to match a model if the substring is contained in any of three model fields coating_1, coating_2 and coating_3
This filter is not implicitly connected to any particular model field. The connection is through the method= specification of the filter to the filterset's method, which can return absolutely any queryset on the model that can be programmed.
Hope I haven't cut out anything vital.
import django_filters as FD
class MemFilter( FD.FilterSet):
class Meta:
model = MyModel
# fields = [fieldname, ... ] # default filters created for these. Not required if all declarative.
# fields = { fieldname: [lookup_expr_1, ...], ...} # for specifying possibly multiple lookup expressions
fields = {
'ft':['gte','lte','exact'], 'mt':['gte','lte','exact'],
...
}
# declarative filters. Lots and lots of
...
coat_info_contains = FD.CharFilter( field_name='coating_1',
label='Coatings contain',
method='filter_coatings_contains'
)
...
def filter_coatings_contains( self, qs, name, value):
values = value.split(',')
qlist = []
for v in values:
qlist.append(
Q(coating_1__icontains = v) |
Q(coating_2__icontains = v) |
Q(coating_3__icontains = v) )
return qs.filter( *qlist )
How can I do a range filter for dates and number in Django REST Framework? Other filters (lt, gt etc.) work fine. I tried many variants such as:
import rest_framework_filters as filters
class OrderFilter(filters.FilterSet):
total_price__range = filters.RangeFilter(name='total_price')
created_at__range = filters.DateFromToRangeFilter(name='created_at')
....
class Meta:
model = Order
fields = {
'created_at__range': ['__all__'],
'total_price__range': ['__all__'],
...
}
class OrderViewSet(BaseViewSet, viewsets.ModelViewSet):
filter_class = OrderFilter
....
In the browsable api there are to fields when I click on bottom "Filters", Then url looks like:
/orders/?created_at__range_0=2017-05-22&created_at__range_1=2017-05-22
and it doesn't work. I need something like
/orders/?created_at__range=2017-05-22,2017-05-24
and same with integer:
/orders/?total_price__range=1000,2000
It was described here .
What am I doing wrong?
First, you've got total_price as your field name but you're URL says total_cost.
Second, remove the __range suffix from your filter names. Anything with __ is part of the Django filtering. For example, if you were querying a model in Django for a value greater than something you'd do:
MyModel.objects.filter(price__gte=50)
Note the __gte suffix; That's how Django does filter modifiers. So your class should be something like:
class OrderFilter(filters.FilterSet):
total_price = filters.RangeFilter(name='total_price')
# ...
Then you can apply range filtering on that field in the query.
I have a third party Django App (Satchmo) which has a model called Product which I make extensive use of in my Django site.
I want to add the ability to search for products via color. So I have created a new model called ProductColor. This model looks roughly like this...
class ProductColor(models.Model):
products = models.ManyToManyField(Product)
r = models.IntegerField()
g = models.IntegerField()
b = models.IntegerField()
name = models.CharField(max_length=32)
When a store product's data is loaded into the site, the product's color data is used to create a ProductColor object which will point to that Product object.The plan is to allow a user to search for a product by searching a color range.
I can't seem to figure out how to put this query into a QuerySet. I can make this...
# If the color ranges look something like this...
r_range, g_range, b_range = ((3,130),(0,255),(0,255))
# Then my query looks like
colors_in_range = ProductColor.objects.select_related('products')
if r_range:
colors_in_range = colors_in_range.filter(
Q(r__gte=r_range[0])
| Q(r__lte=r_range[1])
)
if g_range:
colors_in_range = colors_in_range.filter(
Q(g__gte=g_range[0])
| Q(g__lte=g_range[1])
)
if b_range:
colors_in_range = colors_in_range.filter(
Q(b__gte=b_range[0])
| Q(b__lte=b_range[1])
)
So I end up with a QuerySet which contains all of the ProductColor objects in that color range. I could then build a list of Products by accessing the products ManyToMany attribute of each ProductColor attribute.
What I really need is a valid QuerySet of Products. This is because there is going to be other logic which is performed on these results and it needs to operate on a QuerySet object.
So my question is how can I build the QuerySet that I really want? And failing that, is there an efficient way to re-build the QuerySet (preferably without hitting the database again)?
If you want to get a Product queryset you have to filter the Product objects and filter via the reverse relation for product color:
products = Product.objects.filter(productcolor_set__r__gte=x).distinct()
You can use the range field lookup:
You can use range anywhere you can use
BETWEEN in SQL -- for dates, numbers
and even characters.
your query:
r_range, g_range, b_range = ((3,130),(0,255),(0,255))
products = Product.objects.filter(productcolor_set__r__range=r_range,
productcolor_set__g__range=g_range,
productcolor_set__b__range=b_range).distinct()