Filter by multiple years in DRF - django

So I'm trying to do filter by year and so it could use multiple values like this
filter?year=2000,2021
And with this I should get all objects with year 2000 or 2021
My filter.py
from django_filters import(
BaseInFilter,
NumberFilter,
FilterSet,
CharFilter,
)
from .models import Research
class YearInFilter(BaseInFilter, NumberFilter):
pass
class ResarchFilter(FilterSet):
year = YearInFilter(field_name='date', lookup_expr='year')
category = CharFilter(field_name='category', lookup_expr='iexact')
class Meta:
model = Research
fields = ['date', 'category']
It looks almost like example from django-filter.
But when I'm trying to use it i've got an error Field 'None' expected a number but got [Decimal('2000'), Decimal('2021')].
My views.py
class ResarchCategoryYear(generics.ListCreateAPIView):
queryset = Research.objects.all()
serializer_class = ResearchSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = ResarchFilter
So what's wrong here? Why here is Decimal? Why it's not number? And how make it work like expected?

If you want all data between this two year, use range
if you want only the data that is exists in those years, use in
example:
year = YearInFilter(field_name='date', lookup_expr='range')
year = YearInFilter(field_name='date', lookup_expr='in')
Please check how you can exactly apply those filters in Django_filter
edit:
you can make a custom search method in this way:
def custom_filter(self, queryset, value, *args, **kwargs):
if not value:
return queryset
handleValue = value.split(',')
queryset = YourModel.objects.filter(your_field__in=handleValue).distinct()
return queryset
use it like this:
rest_framework.CharFilter(method=custom_filter)
and you can replace CharFilter based on what you want
Best of luck

Related

check user have liked msg or not from list of messages

suppose
class Msg(models.Model):
...
likes = models.ManyToManyField(User,...)
channelname = models.CharField(...)
Now my queryset is
queryset = Msg.objects.filter(channelname='home')
What should i do after this to get somelike
[{id:xyz,liked=true},{id:tuv,liked=true},{id:abc,liked:false}]
You can annotate an Exists() subquery using the through model of your many to many field:
from django.db.models import Exists, OuterRef
liked_subquery = Msg.likes.through.objects.filter(
msg=OuterRef('pk'), # Filter for outer queries Msg instance
user=some_user_instance # Filter for user whose like we are checking for
)
queryset = Msg.objects.filter(
channelname='home'
).annotate(
liked=Exists(liked_subquery)
).values('id', 'liked')
print(list(queryset))

Django filter exact match for multi field: ManyToManyField using ModelMultipleChoiceFilter

I'm using Django filters (django-filter) in my project. I have the models below, where a composition (Work) has a many-to-many instrumentations field with a through model. Each instrumentation has several instruments within it.
models.py:
class Work(models.Model):
instrumentations = models.ManyToManyField(Instrument,
through='Instrumentation',
blank=True)
class Instrument(models.Model):
name = models.CharField(max_length=100)
class Instrumentation(models.Model):
players = models.IntegerField(validators=[MinValueValidator(1)])
work = models.ForeignKey(Work, on_delete=models.CASCADE)
instrument = models.ForeignKey(Instrument, on_delete=models.CASCADE)
views.py:
import django_filters
class WorkFilter(django_filters.FilterSet):
instrument = django_filters.ModelMultipleChoiceFilter(
field_name="instrumentation__instrument",
queryset=Instrument.objects.all())
My filter works fine: it grabs all the pieces where there is the instrument selected by the user in the filter form.
However, I'd like to add the possibility of filtering the compositions with those exact instruments. For instance, if a piece contains violin, horn and cello and nothing else, I'd like to get that, but not a piece written for violin, horn, cello, and percussion. Is it possible to achieve that?
I'd also like the user to choose, from the interface, whether to perform an exact search or not, but that's a secondary issue for now, I suppose.
Update: type_of_search using ChoiceFilter
I made some progress; with the code below, I can give the user a choice between the two kinds of search. Now, I need to find which query would grab only the compositions with that exact set of instruments.
class WorkFilter(django_filters.FilterSet):
# ...
CHOICES = {
('exact', 'exact'), ('not_exact', 'not_exact')
}
type_of_search = django_filters.ChoiceFilter(label="Exact match?", choices=CHOICES, method="filter_instruments")
def filter_instruments(self, queryset, name, value):
if value == 'exact':
return queryset.??
elif value == 'not_exact':
return queryset.??
I know that the query I want is something like:
Work.objects.filter(instrumentations__name='violin').filter(instrumentations__name='viola').filter(instrumentations__name='horn')
I just don't know how to 'translate' it into the django_filters language.
Update 2: 'exact' query using QuerySet.annotate
Thanks to this question, I think this is the query I'm looking for:
from django.db.models import Count
instrument_list = ['...'] # How do I grab them from the form?
instruments_query = Work.objects.annotate(count=Count('instrumentations__name')).filter(count=len(instrument_list))
for instrument in instrument_list:
instruments_query = instruments_query.filter(instrumentations__name=instrument_list)
I feel I'm close, I just don't know how to integrate this with django_filters.
Update 3: WorkFilter that returns empty if the search is exact
class WorkFilter(django_filters.FilterSet):
genre = django_filters.ModelChoiceFilter(
queryset=Genre.objects.all(),
label="Filter by genre")
instrument = django_filters.ModelMultipleChoiceFilter(
field_name="instrumentation__instrument",
queryset=Instrument.objects.all(),
label="Filter by instrument")
CHOICES = {
('exact', 'exact'), ('not_exact', 'not_exact')
}
type_of_search = django_filters.ChoiceFilter(label="Exact match?", choices=CHOICES, method="filter_instruments")
def filter_instruments(self, queryset, name, value):
instrument_list = self.data.getlist('instrumentation__instrument')
if value == 'exact':
queryset = queryset.annotate(count=Count('instrumentations__name')).filter(count=len(instrument_list))
for instrument in instrument_list:
queryset = queryset.filter(instrumentations__name=instrument)
elif value == 'not_exact':
pass # queryset = ...
return queryset
class Meta:
model = Work
fields = ['genre', 'title', 'instrument', 'instrumentation']
You can grab instrument_list with self.data.getlist('instrument').
This is how you would use instrument_list for the 'exact' query:
type_of_search = django_filters.ChoiceFilter(label="Exact match?", choices=CHOICES, method=lambda queryset, name, value: queryset)
instrument = django_filters.ModelMultipleChoiceFilter(
field_name="instrumentation__instrument",
queryset=Instrument.objects.all(),
label="Filter by instrument",
method="filter_instruments")
def filter_instruments(self, queryset, name, value):
if not value:
return queryset
instrument_list = self.data.getlist('instrument') # [v.pk for v in value]
type_of_search = self.data.get('type_of_search')
if type_of_search == 'exact':
queryset = queryset.annotate(count=Count('instrumentations')).filter(count=len(instrument_list))
for instrument in instrument_list:
queryset = queryset.filter(instrumentations__pk=instrument)
else:
queryset = queryset.filter(instrumentations__pk__in=instrument_list).distinct()
return queryset

Django-filter get all records when a specific value for a filter_field is passed

I am using django-filter to filter my Queryset on the basis of url params.
class WorklistViewSet(ModelViewSet):
serializer_class = MySerializer
queryset = MyModel.objects.all()
filter_backends = [DjangoFilterBackend, ]
filterset_fields = ['class', ]
# possible values of *class* which is allowed to be passed in the url params are ['first', 'second', 'ALL'].
class MyModel(BaseModel):
CLASS_CHOICES = (
(FIRST_CLASS, 'first'),
(SECOND_CLASS, 'second'),
)
class = models.CharField(choices=CLASS_CHOICES, max_length=3, )
URLs http://127.0.0.1:8000?class=first and http://127.0.0.1:8000?class=first are giving the expected results.
I want that when http://127.0.0.1:8000?class=ALL is called, all the records in my table should be listed i.e without filtering.
How can i do this while using django-filter ?
You may want to use Filter.method, as explained in the docs.
In your case, I would do as follows:
class F(django_filters.FilterSet):
klass = CharFilter(method='my_custom_filter')
class Meta:
model = MyModel
fields = ['klass']
def my_custom_filter(self, queryset, name, value):
if value == 'ALL':
return queryset
return queryset.filter(**{
name: value,
})
Be also reminded that class is a reserved word in Python and cannot be used as a variable name. I've used klass instead, although that's used as something else in many Python books and may be confusing.

Why isn't the filter working?

I want to filter my product by country and brand, for that reason I have created a view:
class CategoryView(ListView):
template_name = '__index.html'
context_object_name = 'products'
paginate_by = 20
def get_queryset(self):
queryset = Product.objects.filter(category__slug=self.kwargs.get('slug')).order_by('-created')
request = self.request
# Filter By Brand and Country
if request.GET.get('country'):
print(request.GET.get('country'))
queryset.filter(brand__country__slug=request.GET.get('country'))
if request.GET.get('brand'):
print(request.GET.get('brand'))
queryset.filter(brand__slug=request.GET.get('brand'))
print(queryset[0].brand.slug)
print(queryset[0].brand.country.slug)
return queryset
But products isn't filtering when i pass querystring like that: ?brand=astra-gold&country=chehiya and print function show me:
chehiya
astra-gold
veneto
italiya
As you can see chehiya != italiya and astra-gold != veneto. Bun why this happen?
When you call filter() on a queryset, it creates a new queryset. The original queryset is unchanged. For your filter to have an effect, you need to assign the result back to queryset, for example:
if request.GET.get('country'):
print(request.GET.get('country'))
queryset = queryset.filter(brand__country__slug=request.GET.get('country'))
I forgot this
queryset = queryset.filter(brand__country__slug=request.GET.get('country'))

Ignore null values in descending order using Django Rest Framework

I am using Django for my website, and hence decided to use Django Rest Framework for building my REST APIs. For a particular model, i want to filter on a text field (using SearchFilter for that), filter on a few categorical fields (FilterBackend with a FilterSet defined) and be able to order data based on some fields (OrderingFilter for this).
class StatsAPI(generics.ListAPIView):
model = Stats
queryset = Stats.objects.all()
serializer_class = StatsSerializer
filter_backends = (filters.DjangoFilterBackend, filters.OrderingFilter, filters.SearchFilter)
filter_class = StatsFilter
pagination_class = StatsPagination
ordering_fields = ('__all__')
search_fields = ('display_name')
The issue i am facing is with my ordering fields as they also contain nulls. Ordering in ascending order works fine. However ordering in descending order (www.example.com/api/stats/?ordering=-appearance), pushes the null values to the top.
How do i ignore the null values when using descending order? The number of fields on which ordering can be performed are roughly 20 in number.
This is a slightly different solution -- rather than filtering null out, this replacement for filters.OrderingFilter just always makes sure they sort last:
class NullsAlwaysLastOrderingFilter(filters.OrderingFilter):
""" Use Django 1.11 nulls_last feature to force nulls to bottom in all orderings. """
def filter_queryset(self, request, queryset, view):
ordering = self.get_ordering(request, queryset, view)
if ordering:
f_ordering = []
for o in ordering:
if not o:
continue
if o[0] == '-':
f_ordering.append(F(o[1:]).desc(nulls_last=True))
else:
f_ordering.append(F(o).asc(nulls_last=True))
return queryset.order_by(*f_ordering)
return queryset
You can custom your own OrderingFilter:
# Created by BaiJiFeiLong#gmail.com at 2022/8/13
from django.db.models import F, OrderBy
from django_filters import rest_framework as filters
class MyOrderingFilter(filters.OrderingFilter):
def get_ordering_value(self, param):
value = super().get_ordering_value(param)
return OrderBy(F(value.lstrip("-")), descending=value.startswith("-"), nulls_last=True)
Will ordering exclude the null values,
assuming your field name is stats here you can do as follows :
Stats.objects.exclude(stats__isnull=True).exclude(stats__exact='')
BaiJiFeiLong's solution almost worked for me. With some tweaks, this ended up doing the trick:
from django.db.models import F, OrderBy
from rest_framework.filters import OrderingFilter
class NullsLastOrderingFilter(OrderingFilter):
def get_ordering(self, request, queryset, view):
values = super().get_ordering(request, queryset, view)
return (OrderBy(F(value.lstrip("-")), descending=value.startswith("-"), nulls_last=True) for value in values)