Django Multiple Choice Filter With Ranges - django

I am reading through django documentation for filters and came across the multiple choice filter.
class User(models.Model):
username = models.CharField(max_length=255)
first_name = SubCharField(max_length=100)
last_name = SubSubCharField(max_length=100)
status = models.IntegerField(choices=STATUS_CHOICES, default=0)
STATUS_CHOICES = (
(0, 'Regular'),
(1, 'Manager'),
(2, 'Admin'),
)
class F(FilterSet):
status = ChoiceFilter(choices=STATUS_CHOICES)
class Meta:
model = User
fields = ['status']
In my application I have a decimal field representing the price in my product model. I was wondering if there was a way I could use the multiple choice filter to select price ranges, e.g.,
(0-200, '$'),
(201-500, '$$'),
(501-1000, '$$$')
I am aware of the range filter but I need the functionality of the multiple choice filter and be able to select multiple ranges.

There isn't any standard behavior to extend here since you're trying to filter by multiple ranges. Your best bet is to use the method argument on a multiple choice filter to provide the custom behavior.
class F(django_filters.FilterSet):
price = MultipleChoiceFilter(choices=(('$': '$', ...)), method='filter_price')
def filter_price(self, queryset, name, value):
# value should be a list since it's multiple choice
for price in value:
if price == '$':
...
return ...

Related

How to filter on two different fields in on a Django query?

I have the following model in my Django project:
class History(models.Model):
item = models.ForeignKey(Item,on_delete=models.CASCADE)
timestamp = models.BigIntegerField(default=0)
price = models.FloatField()
And I want to filter on based on the item and the timestamp. The idea is I want to check if a model instance exist or not before I create a new instance.
Current I have the following:
History.objects.filter(timestamp=timestamp).exists() == False:
I filter based on the timestamp but I want to do something like filtering by both the item and the timestamp at the same time.
For example if I have item="phone", timestamp = 1640736000
item = Item
timestamp = 1640736000
History.objects.filter(timestamp=timestamp and item=item).exists() == False:
But the above doesn't seem to work.
How do i go about this?
Edited:
Below is the Item model,
class Item(models.Model):
name = models.CharField(default="", max_length=50)
description = models.TextField(default="No Description")
homepage = models.TextField(default="No Homepage")
def __str__(self):
return "{}-{}".format(self.Item,self.name)
To filter on multiple fields in the same filter you provide the lookups as separate keyword arguments. To filter on a relationship, like a ForeignKey, use double underscores to follow the relationship
item = "phone"
timestamp = 1640736000
History.objects.filter(timestamp=timestamp, item__name=item).exists()

django orm multiple filter on same many to many field

class Book(models.Model):
title = models.CharField(max_length=50)
authors = models.ManyToManyField(Author)
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
Suppose I want to get books having atleast two author's whose first names are Test and Tester.So I will proceed with Book.objects.filter(authors__first_name='Test').filter(authors__first_name='Tester')
What if I have multiple first name(long list) to check,apart from running a for loop and rawsqlquery are there other options?
queryset = Book,objects.all()
for i in ['test','tester']:
queryset = queryset.filter(authors__first_name=i)
You should filter with:
datas = ['test', tester']
qs = Book.objects.all()
for datum in datas:
qs = qs.filter(authors__first_name=datum)
But this will not scale well, and furthermore you will retrieve the same book.
What works better is to Count the number of Authors with test or tester as first name:
datas = set(['test', 'tester'])
Book.objects.filter(
authors__first_name__in=datas
).annotate(
nauthors=Count('authors')
).filter(nauthors=len(data))
or since django-3.2, we can make use of the .alias(…) [Django-doc] to prevent counting twice (once for the filter, and once for the SELECT clause):
datas = set(['test', 'tester'])
Book.objects.filter(
authors__first_name__in=datas
).alias(
nauthors=Count('authors')
).filter(nauthors=len(data))
Here we thus will make one JOIN with the table of the Author model, and simply count the number of authors that match.

Django intersection based on common field

I have chat and participant models.
class Chat(models.Model):
...
class Participant(models.Model):
chat = models.ForeignKey(Chat, on_delete=models.CASCADE, related_name='participants')
participant = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
...
I'm looking for a way to check if there exists a single chat with two specified users.
Edit: I've thought of using "in" on the nested objects but am unsure how to require both.
qs = Chat.objects.filter(participants__participant__in[user_a,user_b])
This query selects each chat the participant is in but I want only the chats where both participants are in
You can chain calls to filter which will and your filter conditions:
qs = Chat.objects.filter(
participants__participant=user_a
).filter(
participants__participant=user_b
)
Below is the query which can be used in the required view or model definition.
Filter the chat items with user A
Filter the chat user B with chat__pk in first query set
qs_first = Chat.objects.filter(
participants__participant = user_a).values('pk')
qs_second = Chat.objects.filter( pk__in = qs_first,
participants__participant = user_b)
This is considering user_a and user_b are a objects of the CustomUser class.

Django - Dynamically creating field names in a model (fields not saving)

I am trying to implement a voting system that keeps track of votes for each type of user on my site. My plan was to create a Vote model that keeps track of Up votes and Total votes for each user type and calculates the percentage of Up votes.
Hardcoded that looks something like this:
class Eduuser(AbstractUser):
TYPE_1 = 'TY1'
TYPE_2 = 'TY2'
...
USER_TYPE_CHOICES = (
(TYPE_1, 'Type 1'),
(TYPE_2, 'Type 2'),
...
)
user_type = models.CharField(max_length=3, choices=USER_TYPE_CHOICES)
class Vote(models.Model):
a = models.IntegerField(
default=0, name=getattr(Eduuser, 'TYPE_1')+'_up')
b = models.IntegerField(
default=0, name=getattr(Eduuser, 'TYPE_2')+'_up')
...
c = models.IntegerField(
default=0, name=getattr(Eduuser, 'TYPE_1')+'_votes')
d = models.IntegerField(
default=0, name=getattr(Eduuser, 'TYPE_2')+'_votes')
...
def perc(self):
perc_array = []
for user_type in getattr(Eduuser, 'USER_TYPE_CHOICES'):
up = float(getattr(self, user_type[0]+'_up')) #Prevent int division
votes = getattr(self, user_type[0]+'_votes')
if votes==0:
perc_array.append(0)
else:
perc_array.append(round(up/votes, 3))
return perc_array
Although I don't anticipate adding more types, I would like for the code to look cleaner. My best attempt at looping over the user types was:
class Eduuser(AbstractUser):
...
class Vote(models.Model):
for user_type in getattr(Eduuser, 'USER_TYPE_CHOICES'):
models.IntegerField(
default=0, name=user_type[0]+'_up')
models.IntegerField(
default=0, name=user_type[0]+'_votes')
def perc(self):
...
However this does not save the fields (I guess because of the lack of assignment operator).
So a couple of quick questions:
1) Is there a way to save fields without explicitly assigning them a name? Or can I convert the string name into a variable (from other posts I've read, this seems like a bad idea)?
2) Am I even approaching this voting idea logically? Part of me feels like there is a far easier approach to keeping track of votes for multiple types of users.
Any help is appreciated! Thanks!
django-model-utils can make this cleaner with it's Choices helper.
You could do a Vote model in the following way (untested):
from model_utils import Choices
class User(AbstractUser):
USER_CHOICES = Choices(
('one', 'Type 1'),
('two', 'Type 2'),
)
user_type = models.CharField(max_length=10, choices=USER_CHOICES)
class Vote(models.Model):
"""
A single vote on a `User`. Can be up or down.
"""
VOTE_CHOICES = Choices(
('upvote'),
('downvote'),
)
user = models.ForeignKey(User)
vote = models.CharField(max_length=10, choices=VOTE_CHOICES)
Example usage – get the number of positive votes for all “Type 1” Users:
# retrieve all of the votes
all_votes = Vote.objects.all()
all_votes_count = len(all_votes)
# now retrieve all of the votes for users of ‘Type 1’
type_one_votes = all_votes.filter(user__user_type=User.USER_CHOICES.one)
type_one_votes_count = len(type_one_votes)
# …and now filter the positive votes for ‘Type 1’ users
type_one_positives = type_one_votes.filter(vote=Vote.VOTE_CHOICES.upvote)
type_one_positive_vote_count = len(type_one_positives)
# etc.
Django uses some metaclass behavior to create fields based on what you declare, so this is not wholly trivial. There are some undocumented calls you can use to dynamically add fields to your model class - see this post:
http://blog.jupo.org/2011/11/10/django-model-field-injection/
That said, I would recommend a simpler approach. Create a model to hold your possible user types, then use it as a foreign key in the votes table:
class UserType(models.Model):
type_name = models.CharField()
class Vote(models.Model):
user_type = models.ForeignKey(UserType)
total = models.PositiveIntegerField()
Or track the individual votes and sum as needed, either recording the user who cast the vote or just the user's type at the time the vote was cast. Depending on what you want to do if a user changes classes after voting you might need to save the user's type anyway.
If you do just track the sums, you have to think more carefully about transaction issues - I'd say track the user and enforce a uniqueness constraint.

Django: Filter in multiple models linked via ForeignKey?

I'd like to create a filter-sort mixin for following values and models:
class Course(models.Model):
title = models.CharField(max_length=70)
description = models.TextField()
max_students = models.IntegerField()
min_students = models.IntegerField()
is_live = models.BooleanField(default=False)
is_deleted = models.BooleanField(default=False)
teacher = models.ForeignKey(User)
class Session(models.Model):
course = models.ForeignKey(Course)
title = models.CharField(max_length=50)
description = models.TextField(max_length=1000, default='')
date_from = models.DateField()
date_to = models.DateField()
time_from = models.TimeField()
time_to = models.TimeField()
class CourseSignup(models.Model):
course = models.ForeignKey(Course)
student = models.ForeignKey(User)
enrollment_date = models.DateTimeField(auto_now=True)
class TeacherRating(models.Model):
course = models.ForeignKey(Course)
teacher = models.ForeignKey(User)
rated_by = models.ForeignKey(User)
rating = models.IntegerField(default=0)
comment = models.CharField(max_length=300, default='')
A Course could be 'Discrete mathematics 1'
Session are individual classes related to a Course (e.g. 1. Introduction, 2. Chapter I, 3 Final Exam etc.) combined with a date/time
CourseSignup is the "enrollment" of a student
TeacherRating keeps track of a student's rating for a teacher (after course completion)
I'd like to implement following functions
Sort (asc, desc) by Date (earliest Session.date_from), Course.Name
Filter by: Date (earliest Session.date_from and last Session.date_to), Average TeacherRating (e.g. minimum value = 3), CourseSignups (e.g. minimum 5 users signed up)
(these options are passed via a GET parameters, e.g. sort=date_ascending&f_min_date=10.10.12&...)
How would you create a function for that?
I've tried using
denormalization (just added a field to Course for the required filter/sort criterias and updated it whenever changes happened), but I'm not very satisfied with it (e.g. needs lots of update after each TeacherRating).
ForeignKey Queries (Course.objects.filter(session__date_from=xxx)), but I might run into performance issues later on..
Thanks for any tipp!
In addition to using the Q object for advanced AND/OR queries, get familiar with reverse lookups.
When Django creates reverse lookups for foreign key relationships. In your case you can get all Sessions belonging to a Course, one of two ways, each of which can be filtered.
c = Course.objects.get(id=1)
sessions = Session.objects.filter(course__id=c.id) # First way, forward lookup.
sessions = c.session_set.all() # Second way using the reverse lookup session_set added to Course object.
You'll also want to familiarize with annotate() and aggregate(), these allow you you to calculate fields and order/filter on the results. For example, Count, Sum, Avg, Min, Max, etc.
courses_with_at_least_five_students = Course.objects.annotate(
num_students=Count('coursesignup_set__all')
).order_by(
'-num_students'
).filter(
num_students__gte=5
)
course_earliest_session_within_last_240_days_with_avg_teacher_rating_below_4 = Course.objects.annotate(
min_session_date_from = Min('session_set__all')
).annotate(
avg_teacher_rating = Avg('teacherrating_set__all')
).order_by(
'min_session_date_from',
'-avg_teacher_rating'
).filter(
min_session_date_from__gte=datetime.now() - datetime.timedelta(days=240)
avg_teacher_rating__lte=4
)
The Q is used to allow you to make logical AND and logical OR in the queries.
I recommend you take a look at complex lookups: https://docs.djangoproject.com/en/1.5/topics/db/queries/#complex-lookups-with-q-objects
The following query might not work in your case (what does the teacher model look like?), but I hope it serves as an indication of how to use the complex lookup.
from django.db.models import Q
Course.objects.filter(Q(session__date__range=(start,end)) &
Q(teacher__rating__gt=3))
Unless absolutely necessary I'd indeed steer away from denormalization.
Your sort question wasn't entirely clear to me. Would you like to display Courses, filtered by date_from, and sort it by Date, Name?