filter objects using foreignkeys - python

So I have a form where user can post Parent details also form for Kid for a different model.
what i need is to allow users to access list of Parent objects by filtering their related objects Kid
let's say a filter to list Parents objects that has children named X older than Z live in Y city AND has no children named X who is younger than Z live in Y city .
models.py :
class Parent(models.Model):
title = models.CharField(max_length=250)
class Kid(models.Model):
cities = (
('city1', city1),
('city2', city2)
)
family = models.ForeignKey(Parent)
title = models.CharField(max_length=250)
age = models.CharField(max_length=250)
city = models.CharField(choices=cities)
any idea how to do it or where/what to look for the answer?

You can do it in reversed logic. Firstly you need to change age to IntegerField otherwise you wouldn't be able to compare it's values
class Kid(models.Model):
family = models.ForeignKey(Parent)
title = models.CharField(max_length=250)
age = models.IntegerField()
city = models.CharField(choices=cities)
Then you can filter all kids that comply with your filter and get ids of parents to filter on later
filter_ids = Kid.objects.filter(name=X, age__gte=Z, city=Y).values_list('parents_id', flat=True).distinct()
exclude_ids = Kid.objects.filter(name=X, age__lt=Z, city=Y).values_list('parents_id', flat=True).distinct()
parents = Parent.objects.filter(id__in=filter_ids).exclude(id__in=exclude_ids)
Answering comment
Same logic you firstly fillter all parents with such kids, then you exclude parents that have other kids.
filter_ids = Kid.objects.filter(my_pattern).values_list('parents_id', flat=True).distinct()
exclude_ids = Kid.objects.exclude(my_pattern).values_list('parents_id', flat=True).distinct()
parents = Parent.objects.filter(id__in=filter_ids).exclude(id__in=exclude_ids)

Related manager seems like a feasible option.
Django offers a powerful and intuitive way to “follow” relationships
in lookups, taking care of the SQL JOINs for you automatically, behind
the scenes. To span a relationship, just use the field name of related
fields across models, separated by double underscores, until you get
to the field you want.
It works backwards, too. To refer to a “reverse” relationship, just use the lowercase name of the model.
parents = Parent.objects.filter(kid__name=X,kid__age__gte=Y,kid__city=Z)
PS: I have not tested this code. The intention is to give a suggestion on approach.
Edit 1: Addressing the exclude exception as pointed in the comments. Link to Django documentation. Refer to the note in this section
Exclude behavior is different than filter. Exclude in related manager doesn't use a combination of conditions instead excludes both eg. Parent.objects.exclude(kid__name=X,kid__city=Z) will exclude kids with name X and kids from city Z instead of kids with name X who are from city Z
Django suggested approach is:
Parent.objects.exclude(kid__in=Kid.objects.filter(name=X,city=Z))

Related

Django - Filter the prefetch_related queryset

I am trying to reduce my complexity by doing the following. I am trying to get all the teachers in active classrooms.
teacher/models.py:
Teacher(models.Model):
name = models.CharField(max_length=300)
classroom/models.py:
Classroom(models.Model):
name = models.CharField(max_length=300)
teacher = models.ForeignKey(Teacher)
students = models.ManyToManyField(Student)
status = models.CharField(max_length=50)
admin/views.py
teachers = Teacher.objects.prefetch_related(Prefetch('classroom_set',queryset=Classroom.objects.filter(status='Active'))
for teacher in teachers:
classrooms = teacher.all()
# run functions
By doing this I get teachers with classrooms. But it also returns teachers with no active classrooms(empty list) which I don't want. Because of this, I have to loop around thousands of teachers with empty classroom_set. Is there any way I can remove those teachers whose classroom_set is [ ]?
This is my original question -
Django multiple queries with foreign keys
Thanks
If you want all teachers with at least one related active class, you do not need to prefetch these, you can filter on the related objects, like:
Teacher.objects.filter(class__status='Active').distinct()
If you want to filter the classroom_set as well, you need to combine filtering as well as .prefetch_related:
from django.db.models import Prefetch
Teacher.objects.filter(
class__status='Active'
).prefetch_related(
Prefetch('class_set', queryset=Class.objects.filter(status='Active'))
).distinct()
Here we thus will filter like:
SELECT DISTINCT teacher.*
FROM teacher
JOIN class on class.teacher_id = teacher.id
WHERE class.status = 'Active'

Django chain multiple queries in view

I have three models:
Course
Assignment
Term
A course has a ManyToManyField which accesses Django's default User in a field called student, and a ForeignKey with term
An assignment has a ForeignKey with course
Here's the related models:
class Assignment(models.Model):
title = models.CharField(max_length=128, unique=True)
points = models.IntegerField(default=0, blank=True)
description = models.TextField(blank=True)
date_due = models.DateField(blank=True)
time_due = models.TimeField(blank=True)
course = models.ForeignKey(Course)
class Course(models.Model):
subject = models.CharField(max_length=3)
number = models.CharField(max_length=3)
section = models.CharField(max_length=3)
professor = models.ForeignKey("auth.User", limit_choices_to={'groups__name': "Faculty"}, related_name="faculty_profile")
term = models.ForeignKey(Term)
students = models.ManyToManyField("auth.User", limit_choices_to={'groups__name': "Student"}, related_name="student_profile")
When a user logs in to the page, I would like to show them something like this bootstrap collapse card where I can display each term and the corresponding classes with which the student is enrolled.
I am able to access all of the courses in which the student is enrolled, I'm just having difficulty with figuring out the query to select the terms. I've tried using 'select_related' with no luck although I may be using it incorrectly. So far I've got course_list = Course.objects.filter(students = request.user).select_related('term'). Is there a way to acquire all of the terms and their corresponding courses so that I can display them in the way I'd like? If not, should I be modeling my database in a different way?
https://docs.djangoproject.com/en/1.11/ref/models/querysets/#values
You could use values or values_list here to get the fields of the related model Term.
For example expanding on your current request:
To retrieve all the Terms' name and duration for the Courses in your queryset
Course.objects.filter(students = request.user).values('term__name', 'term__duration')
I am not sure what the fields are of your Term model, but you would replace name or duration with whichever you are trying to get at.
I think it helps you
terms = Terms.objects.filter(....) # terms
cources0 = terms[0].course_set.all() # courses for terms[0]
cources0 = terms[0].course_set.filter(students=request.user) # courses for terms[0] for user

How to query django m2m for a single instance that satisfies multiple requirements?

For the sake of example, I have the following Django models:
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class Book(models.Model):
name = models.CharField(max_length=30)
authors = models.ManyToManyField(Author)
(because books regularly have multiple authors, and authors regularly write multiple books)
Now say I have a list of authors' names as tuples:
author_list = [('clarke', 'arthur'), ('herbert', 'frank'), ('asimov', 'isaac')]
Is there a way to query for all Books whose authors m2m field has at least one Author instance that satisfies
author.last_name = pair[0]
author.first_name = pair[1]
for some pair in author_list? I.e. to get all books contributed to by at least one of a given list of authors?
The query I *want* to write looks like this:
Book.objects.filter(
authors__first_name__in=[pair[1] for pair in author_list],
authors__last_name__in=[pair[0] for pair in author_list]
)
but that doesn't work, because then all permutations (Arthur Herbert, Frank Asimov, Isaac Clarke, etc...) are allowed. But I'm not sure how to say, essentially:
'Select all books who have an author that satisfies these two things at once.'
or really:
'Select all books who have an author whose pair of (last_name, first_name) is in this list of tuples.'
Is there a clean way to do that search in django?
You can build a query using Q objects.
Based on your answer, but without using reduce and operator:
query = Q()
for last_name, first_name in author_list:
Q |= Q(authors__last_name=last_name, authors__first_name=first_name)
Book.objects.filter(query)
I think you could build a query using q objects
Q(last_name=pair[0], first_name=pair[1]) | Q(last_name=pair2[0], first_name=pair2[1])
The above query should generate get books with last name clark AND first name arthur OR last_name herbert and first_name frank.
There are quite a few answers on how to do this dynamically:
How to dynamically compose an OR query filter in Django?
You will probably have to twiddle with the query and inspect the query django generates to verify that it is what you intend
Ok I think this works:
query = []
for last_name, first_name in author_list:
query.append(Q(authors__last_name=last_name, authors__first_name=first_name))
query = reduce(operator.or_, query)
Book.objects.filter(query)
but please still answer if you see something better!

Django smart/chained select manytomanyfield

here is the deal, this is my model:
class Health_plan(models.Model):
a = models.IntegerField ()
b = models.IntegerField ()
c = models.IntegerField ()
class Doctors_list(models.Model):
specialty = models.CharField(max_length=15)
name = models.CharField(max_length=30)
hp_id = models.ManyToManyField(Health_plan)
location = models.CharField(max_length=15)
def __unicode__(self):
return self.name
So, i have a table named Doctors_list, a doctor has one specialty, one location, one name, and a list of health plans covered.
To manage this, i have a table with the health plans on the columns (a,b,c). The doctors are identified in this list by their id, and the rows contain 1 or 0, 1 for health plan covered, 0 for not covered. like this:
ID A B C
1 0 0 1
2 1 0 0
3 1 0 1
Is there a better way to make this relation???
The user first chooses the specialty, my form:
class SpecForm(ModelForm):
a = Doctors_list.objects.values_list('specialty', flat=True)
unique = [('---------------','---------------')] + [(i,i) for i in set(a)]
specialty = forms.ChoiceField(choices=unique)
class Meta:
model = Doctors_list
The big thing is: a smart select for the relation specialty/health_plans.
The user chooses the specialty.
The specialty is covered by one or more doctors.
I use those doctor(s) id, and go check on health_plan table wich plans are available.
The user select's the plan.
I will keep researching but any tip is gratefully welcomed.
I hope i made myself clear.
What you describe is a misuse of the ManyToMany relation.
If you want to create a many-to-many relation between A and B, all you have to do is add a ManyToMany field in the definion of one of the models (A or B) and django will take care of the rest. Specifically, it will create a 3rd table where it will store the associations in the form of (A.id, B.id). This is standard rational database procedure for this type of relations, and Django is implementing it.
See http://en.wikipedia.org/wiki/Junction_table for the general definition of a many to many relation.
https://docs.djangoproject.com/en/1.5/ref/models/fields/#manytomanyfield - for the Django implementation.

Django Combine a Variable Number of QuerySets

Is there a way to concatenate a unknown number of querysets into a list?
Here are my models:
class Item(models.Model):
name = models.CharField(max_length=200)
brand = models.ForeignKey(User, related_name='brand')
tags = models.ManyToManyField(Tag, blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ['-id']
class Tag(models.Model):
name = models.CharField(max_length=64, unique=True)
def __unicode__(self):
return self.name
I have two types of queries that I'm working with:
items = Item.objects.filter(brands__in=brands)
items = Item.objects.filter(tags__name='80s').filter(tags__name='comedy')
With regards to the second type of query, users can save searches (for example "80s comedy"), and can save multiple searches at the same time, so I will need to create a query for each search that they have saved.
I originally wanted to try and construct a single query that will handle both cases (see Django Combining AND and OR Queries with ManyToMany Field ), but I now think the best way to do this would be to combine all queries into a list.
I like what #akaihola suggests here:
How to combine 2 or more querysets in a Django view? but I can't figure out how to use itertools.chain with a variable number of queries.
Does anyone know the best way to accomplish that?
EDIT: Forgot to mention, what I'm looking for are items that have a certain brand OR have all of the required tags.
Slightly unorthodox, but you could use recursion. So in your example:
def recursive_search(tags, results_queryset):
if len(tags) > 0:
result_qs = result_queryset.filter(tags_name=tags[0])
if result_queryset.exists():
return filter_recursion(tags[1:],result_queryset)
else:
return None
return result_queryset
tags = ["comedy", "80s", "action", "thriller"] # This can be variable
result_queryset = Item.objects.filter(brands__in=brands) # Could be Item.objects.all()
print recursive_search(tags, result_queryset)
So you start off with a list of the tags you are searching for, and a queryset of ALL of your items that could possibly fit your criteria (in this case we start with the list of items of a particular brand)
You then recursively go through the list of tags one by one and cut the queryset down. For each level, you re-filter the entire queryset to only those items which have all the mentioned tags.
so:
the first call/level would be for all the items that have the tag favourite,
the second call/level would be for all the items that have the tags favourite and loudest,
etc.
If the queryset returned by the filter is None, it means there are no items that have all the required tags, and the method will quit and return None (i.e. it quits at the first possible instance of failure). Furthermore, there should only be a single hit to the database (I think!)
I've tested this out and it should work, so give it a shot
EDIT
To concatonate the queryset returned from the brands (q1) and the queryset created above using itertools (q2):
list = []
for item in itertools.chain(q1, q2):
list.append(item)
EDIT 2
does this not accomplish what you need in one query?
# list of tags = ['comedy','80s']
qs = Item.objects.all( Q(brand__iexact="brand name") | Q(tags__name__in=[tag for tag in list_of_tags]) )

Categories