Django REST framework range filter - django

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.

Related

Use django_filters to query a list that contains any item in query params

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

How to create a customized filter search function in Django?

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 )

can't use lookup_expr list in django_filters class?

Url: /user?u=root works
class UserFilter(django_filters.rest_framework.FilterSet):
u = django_filters.rest_framework.CharFilter(name='username', lookup_expr='contains')
class Meta:
model = User
fields = ['username','u']
but when i changed it to
class UserFilter(django_filters.rest_framework.FilterSet):
u = django_filters.rest_framework.CharFilter(name='username', lookup_expr=['contains'])
class Meta:
model = User
fields = ['username','u']
url: /user?u__contains=root doesn't work.
django 1.11.1
django-filter 1.0.4
djangorestframework 3.6.3
Ykh is close, but incorrect. In your second example, the filter is still exposed as u, so filtering by u__contains is a no-op since it's not a recognized name. u__contains is not somehow translated into a u__contains__contains lookup.
Additionally, passing a list or tuple of lookups to lookup_expr might provide a different behavior than you would expect. It is not related to the automatic filter generation that you see with Meta.fields. Instead, it creates a multi-lookup filter (docs). This filter has two inputs:
a text input for the value to filter by
a select widget to select which lookup to use
It accomplishes this by using a django.forms.MultiWidget, so your query would need to be something like /user?u_0=root&u_1=contains.
In general, MultiWidgets are not that compatible with API usage, given the _0 and _1 suffixes.
If you're trying to expose a filter named u__contains, you should do something like:
class UserFilter(django_filters.rest_framework.FilterSet):
u = django_filters.rest_framework.CharFilter(name='username', lookup_expr='exact')
u__contains = django_filters.rest_framework.CharFilter(name='username', lookup_expr='contains')
class Meta:
model = User
fields = ['u', 'u__contains']
And there are several ways to use lookup_expr and the way you write it is incorrect would be "icontains" for contain and "iexact" for exact. Also another well used expressions would be gte and lte.
class UserFilter(django_filters.rest_framework.FilterSet):
u_contain = django_filters.rest_framework.CharFilter(
field_name='username',
lookup_expr='icontains'
)
l_exact = django_filters.rest_framework.CharFilter(
field_name='lastname',
lookup_expr='iexact'
)
class Meta:
model = User
fields = ['u_contain', 'l_exact']

How to filter multiple fields with list of objects

I want to build an webapp like Quora or Medium, where a user can follow users or some topics.
eg: userA is following (userB, userC, tag-Health, tag-Finance).
These are the models:
class Relationship(models.Model):
user = AutoOneToOneField('auth.user')
follows_user = models.ManyToManyField('Relationship', related_name='followed_by')
follows_tag = models.ManyToManyField(Tag)
class Activity(models.Model):
actor_type = models.ForeignKey(ContentType, related_name='actor_type_activities')
actor_id = models.PositiveIntegerField()
actor = GenericForeignKey('actor_type', 'actor_id')
verb = models.CharField(max_length=10)
target_type = models.ForeignKey(ContentType, related_name='target_type_activities')
target_id = models.PositiveIntegerField()
target = GenericForeignKey('target_type', 'target_id')
tags = models.ManyToManyField(Tag)
Now, this would give the following list:
following_user = userA.relationship.follows_user.all()
following_user
[<Relationship: userB>, <Relationship: userC>]
following_tag = userA.relationship.follows_tag.all()
following_tag
[<Tag: tag-job>, <Tag: tag-finance>]
To filter I tried this way:
Activity.objects.filter(Q(actor__in=following_user) | Q(tags__in=following_tag))
But since actor is a GenericForeignKey I am getting an error:
FieldError: Field 'actor' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation.
How can I filter the activities that will be unique, with the list of users and list of tags that the user is following? To be specific, how will I filter GenericForeignKey with the list of the objects to get the activities of the following users.
You should just filter by ids.
First get ids of objects you want to filter on
following_user = userA.relationship.follows_user.all().values_list('id', flat=True)
following_tag = userA.relationship.follows_tag.all()
Also you will need to filter on actor_type. It can be done like this for example.
actor_type = ContentType.objects.get_for_model(userA.__class__)
Or as #Todor suggested in comments. Because get_for_model accepts both model class and model instance
actor_type = ContentType.objects.get_for_model(userA)
And than you can just filter like this.
Activity.objects.filter(Q(actor_id__in=following_user, actor_type=actor_type) | Q(tags__in=following_tag))
What the docs are suggesting is not a bad thing.
The problem is that when you are creating Activities you are using auth.User as an actor, therefore you can't add GenericRelation to auth.User (well maybe you can by monkey-patching it, but that's not a good idea).
So what you can do?
#Sardorbek Imomaliev solution is very good, and you can make it even better if you put all this logic into a custom QuerySet class. (the idea is to achieve DRY-ness and reausability)
class ActivityQuerySet(models.QuerySet):
def for_user(self, user):
return self.filter(
models.Q(
actor_type=ContentType.objects.get_for_model(user),
actor_id__in=user.relationship.follows_user.values_list('id', flat=True)
)|models.Q(
tags__in=user.relationship.follows_tag.all()
)
)
class Activity(models.Model):
#..
objects = ActivityQuerySet.as_manager()
#usage
user_feed = Activity.objects.for_user(request.user)
but is there anything else?
1. Do you really need GenericForeignKey for actor? I don't know your business logic, so probably you do, but using just a regular FK for actor (just like for the tags) will make it possible to do staff like actor__in=users_following.
2. Did you check if there isn't an app for that? One example for a package already solving your problem is django-activity-steam check on it.
3. IF you don't use auth.User as an actor you can do exactly what the docs suggest -> adding a GenericRelation field. In fact, your Relationship class is suitable for this purpose, but I would really rename it to something like UserProfile or at least UserRelation. Consider we have renamed Relation to UserProfile and we create new Activities using userprofile instead. The idea is:
class UserProfile(models.Model):
user = AutoOneToOneField('auth.user')
follows_user = models.ManyToManyField('UserProfile', related_name='followed_by')
follows_tag = models.ManyToManyField(Tag)
activies_as_actor = GenericRelation('Activity',
content_type_field='actor_type',
object_id_field='actor_id',
related_query_name='userprofile'
)
class ActivityQuerySet(models.QuerySet):
def for_userprofile(self, userprofile):
return self.filter(
models.Q(
userprofile__in=userprofile.follows_user.all()
)|models.Q(
tags__in=userprofile.relationship.follows_tag.all()
)
)
class Activity(models.Model):
#..
objects = ActivityQuerySet.as_manager()
#usage
#1st when you create activity use UserProfile
Activity.objects.create(actor=request.user.userprofile, ...)
#2nd when you fetch.
#Check how `for_userprofile` is implemented this time
Activity.objects.for_userprofile(request.user.userprofile)
As stated in the documentation:
Due to the way GenericForeignKey is implemented, you cannot use such fields directly with filters (filter() and exclude(), for example) via the database API. Because a GenericForeignKey isn’t a normal field object, these examples will not work:
You could follow what the error message is telling you, I think you'll have to add a GenericRelation relation to do that. I do not have experience doing that, and I'd have to study it but...
Personally I think this solution is too complex to what you're trying to achieve. If only the user model can follow a tag or authors, why not include a ManyToManyField on it. It would be something like this:
class Person(models.Model):
user = models.ForeignKey(User)
follow_tag = models.ManyToManyField('Tag')
follow_author = models.ManyToManyField('Author')
You could query all followed tag activities per Person like this:
Activity.objects.filter(tags__in=person.follow_tag.all())
And you could search 'persons' following a tag like this:
Person.objects.filter(follow_tag__in=[<tag_ids>])
The same would apply to authors and you could use querysets to do OR, AND, etc.. on your queries.
If you want more models to be able to follow a tag or author, say a System, maybe you could create a Following model that does the same thing Person is doing and then you could add a ForeignKey to Follow both in Person and System
Note that I'm using this Person to meet this recomendation.
You can query seperately for both usrs and tags and then combine them both to get what you are looking for. Please do something like below and let me know if this works..
usrs = Activity.objects.filter(actor__in=following_user)
tags = Activity.objects.filter(tags__in=following_tag)
result = usrs | tags
You can use annotate to join the two primary keys as a single string then use that to filter your queryset.
from django.db.models import Value, TextField
from django.db.models.functions import Concat
following_actor = [
# actor_type, actor
(1, 100),
(2, 102),
]
searchable_keys = [str(at) + "__" + str(actor) for at, actor in following_actor]
result = MultiKey.objects.annotate(key=Concat('actor_type', Value('__'), 'actor_id',
output_field=TextField()))\
.filter(Q(key__in=searchable_keys) | Q(tags__in=following_tag))

Django Haystack - Search on non-indexed many to many field

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)