Django queryset models through an intermediate model - python

I have a a model Meeting that is related to Signup which in turn is related Attendee.
Models:
class Meeting(models.Model):
date = models.DateTimeField(auto_now=False)
class Signup(models.Model):
meeting = models.ForeignKey(Meeting, on_delete=models.CASCADE, default=1)
...
class Attendee(models.Model, PrintNameTag):
signup = models.ForeignKey(Signup, on_delete=models.CASCADE, related_name="attendee")
attendee = models.CharField(blank=False, max_length=200)
...
How do I make a queryset on Meeting.objects that will also return the other fields in Signup and Attendee? I need all data for a report. I'd prefer not to change my models because this seems like this should be doable the way they are. I'd also like to query Meeting because I have some other stuff in the query that Meeting is needed for (removed for simplicity).
Here's what I currently have:
qs = Meeting.objects.filter(id__in=queryset).prefetch_related('signup').annotate(attendee_signup=F('signup__attendee_signup'))
I keep getting "cannot resolve keyword attendee_signup into field"
I also have tried variations of:
qs = Meeting.objects.filter(id__in=queryset).select_related('signup').filter(attendee__signup=signup_id)
I get "name 'signup_id' is not defined"
I'm brand new to python and Django and I don't know if I need to use F and/or annotate, prefech_related or select_related. I've looked up what each does, but I can't find the right syntax to get this working. I feel like I've tried every combo and I'm just mashing keys at this point. Could someone help please?

If you add related_name='signups' to the Signup.meeting field, then this should work:
meetings = Meeting.objects.filter(id__in=queryset).prefetch_related('signups__attendee')
The above will fetch all meetings, and all signups for those meetings, and all attendees for those signups. From there you can iterate over everything and it will all have already been fetched from the database:
for m in meetings:
for s in m.signups:
for a in s.attendee:
# whatever
(Incidentally, I would recommend changing that related_name from "attendee" to "attendees" as the plural form tends to be more intuitive.)

Related

Django Admin Change View is stuck because of __str__ method of related field

I have this model in accounts.models:
from django.contrib.auth.models import User
class UserProfile(BaseModel):
user = models.OneToOneField(
User, related_name="user_profile", on_delete=models.CASCADE
)
# ...
def __str__(self) --> str:
return self.user.username
And the following in memberships.models:
class ExternalServiceProfileMembership(BaseModel):
created_at = models.DateTimeField()
expires_at = models.DateTimeField()
profile = models.ForeignKey(
"accounts.UserProfile",
on_delete=models.CASCADE,
related_name="ext_memberships",
)
plan = models.ForeignKey("memberships.MembershipPlan", on_delete=models.CASCADE)
ext_subscription_id = models.CharField(max_length=128)
When I try to access the admin view of an individual ExternalServiceProfileMembership object (for example: http://localhost:8000/admin/memberships/externalserviceprofilemembership/1/change/), the site gets stuck, eventually returning a 503. So I started out commenting out fields in the AdminModel, and once I remove profile, the object change view loaded fine.
I brought back profile into AdminModel but removed UserProfile's __str__() method, and it also worked. Which makes me think the whole issue is with this method; but I have no idea why.
Any help is appreciated!
On the change page for ExternalServiceProfileMembership, the profile dropdown displays the name of every user. This causes one extra query for every user in the dropdown.
The quick fix is to add 'profile' to readonly_fields, autocomplete_fields or raw_id_fields. These three options mean that a single profile is displayed on the change form, so there is only one extra query to fetch the user.
Another approach, which is more complicated, is to create a custom form that overrides the queryset to use select_related to fetch all of the users, then use that form in your model admin.

Filtering django queryset by a value of the through table of a m2m relationship

I'm trying to do this:
queryset.filter(m2m_related_lookup__through_table_field=value)
These are the models, simplified:
class User(models.Model):
name = models.CharField("nom", max_length=100)
surname = models.CharField("cognoms", max_length=100)
class Activity(models.Model):
name = models.CharField("Títol", max_length=200)
date_start = models.DateField("Dia inici")
date_end = models.DateField("Dia finalització")
enrolled = models.ManyToManyField(User, related_name='enrolled_activities', through='ActivityEnrolled')
class ActivityEnrolled(models.Model):
class Meta:
db_table = 'main_activity_personetes_enrolled'
activity = models.ForeignKey(Activity, on_delete=models.CASCADE)
user = models.ForeignKey(Personeta, on_delete=models.CASCADE)
date_enrolled = models.DateTimeField("data d'inscripció")
confirmed = models.BooleanField("confirmada", default=False)
I guess is quite simple, just a many 2 many with a custom through table, so I can store the enrollment date and some other things there.
This relationship is set at Activity, with a related_name of 'enrolled_activities'.
So, how can I query "all the users where the ActivityEnrolled.enrollment_date is in 2019" using Django's ORM?
This is for a custom filter (with admin.SimpleListFilter) for a change_list view in the Django Admin, which is listing the User items. In other words, is like I'm doing User.objects.filter(blabla).
Trying: queryset.filter(enrolled_activities__date_enrolled__year=2019) obviously throws the error Related Field got invalid lookup: date_enrolled, because enrolled_activities does not refer to the through table but to the related table (this is: Activity), and this field does not exist there.
Is the only solution to query the through table instead of Users?
Like: ActivityEnrolled.objects.filter(date_enrolled__year=2019) + grouping the results so it only returns one row per each User. I know I can do that but it's quite nasty, I've been trying to find a cleaner way to avoid it but with no success.
Thank you very much!!
So, how can I query "all the users where the ActivityEnrolled.enrollment_date is in 2019" using Django's ORM?
A many-to-many relation is in fact just a combination of two one-to-many tables. We thus can filter on the one-to-many relation with:
User.objects.filter(activityenrolled__enrollment_date__year=2019).distinct()
The .distinct() will prevent yielding the same user, if th user has multiple activities for which he/she was enrolled in 2019.

Django: Query multiple models based on parent model

I'm creating a blog in Django where I have a base model PostType which I then extend in to several subclasses for different types of content on the website. For example CodeSnippet and BlogPost.
The idea is that these content types are mostly the same, they all have an author, a title, a slug, etc, but they also have a few unique fields. For example a blog post has a field for the text content, while a code snippet has a related field for programming language.
Something like this:
class PostType(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
title = models.CharField(
max_length=255,
unique=True,
)
class Meta:
abstract = True
class BlogPost(PostType):
content = models.TextField(
default='',
)
class GitHubRepo(PostType):
url = models.URLField(
unique=True
)
class CodeSnippet(PostType):
language = models.ForeignKey(
to=Language,
on_delete=models.CASCADE,
)
Now what I want to know is if there's any good/prefered way to query all objects in the database that are based on the parent class PostType?
For the site's search I am currently querying each of the different content types, and then merging the result. This is the code for the search view:
class Search(View):
def get(self, request):
context = {}
try:
query = request.GET.get('s')
blog_list = models.BlogPost.objects.filter(title__icontains=query)
projects_list = models.Project.objects.filter(title__icontains=query)
code_list = models.CodeSnippet.objects.filter(title__icontains=query)
from itertools import chain
context['result_list'] = list(chain(blog_list, projects_list, code_list))
except KeyError:
query = ''
context['title'] = 'Result for "{}"'.format(query)
return render(request, 'blog/search.html', context)
This all works fine, but I would like to know if there's any way to query all children of PostType at the same time?
Is Django somehow aware of what child models exist? And can I use that somehow?
Like a PostType.child_objects.get() or something similar.
Even a way to programmatically get all the children so that I could loop through them and get all the objects would be fine too.
For the time being I just have a few models, but the number of child models were to increase, it would be great if I could be assured that all the models would be included in the site search automatically based on their relationship to their parent model.
PostType is an abstract Model (So, it does not create physical table. It's just to use inheritance feature in Django). As far as i understand you want to generate list of QuerySet's merge it in a single list and iterate over list/QuerySet later.
get_qs_list = [model.objects.filter(title__icontains=query) for model in PostType.__subclasses__()] # This contains QuerySet instances now.
for qs in get_qs_list:
# qs iterator returns QuerySet instance
for instance in qs:
# instance iterator is single Model instance from QuerySet collection
print(instance.title)
Hope, it helps you.
If PostType is not an abstract model then you should be able to query it directly to get all those subclass results
PostType.objects.filter(title__icontains=query)
Otherwise, you cannot really do this with a single query.
Even a way to programmatically get all the children so that I could
loop through them and get all the objects would be fine too.
This is possible --- to get the subclasses programmatically, you would do
PostType.__subclasses__()

Select2 and django_filters not querying foreign keys

I'm using django_filters for an advanced search and select2Widget to display the options of a foreign key field.
The proper values load but whenever I submit the form I get an error message: Select a valid choice. That choice is not one of the available choices.
The error might seem pretty obvious but I can't find out how to solve it. Any suggestions?
filters.py
class MyFilter(django_filters.FilterSet):
b = django_filters.ModelChoiceFilter(
queryset=ModelA.objects.values_list('b__name', flat=True)
widget=Select2Widget()
)
class Meta:
model = ModelA
fields = ('b',)
models.py
class ModelA(models.Model):
b = models.ForeignKey('ModelB', on_delete=models.CASCADE)
class ModelB(models.Model):
name = models.CharField(max_length=100, unique=True)
def __str__(self):
return self.name
AS user #dirkgroten pointed out in a comment to the question, the following line looks strange:
queryset=ModelA.objects.values_list('b__name', flat=True)
This way the widget has no way of knowing the pk of each element of the list (since it only return the names). That might couse that the view cannot save a selected ModelB instance, since it does not know the selected pk.
Ah, you might also want to use ModelB instead of ModelA
Try changing it to something like this
queryset=ModelB.objects.values('pk', 'b__name')
or even this
queryset=ModelB.objects.all()
and let us know if that works.

How to check if user is in a certain table

I have my user table in django, and to differ all the users I created two tables, (Teacher and Student).
Both tables are getting an fk from user
So, in order to make authorization how do I check if one's user is in a certain table.
I need to check it this way
def test_func(self):
return self.request.user.check..if..it..exists..in..table
My models are like this.
class Teacher(models.Model):
User = models.OneToOneField(settings.AUTH_USER_MODEL)
This depends on how your models are set up.
If your Teacher model looks something like this;
class Teacher(models.Model):
user = models.ForeignKey(User)
Then you should be able to check if the user is a teacher by using the implicit backref;
self.request.user.teacher_set.exists()
As the question has been updated to show that the model is slightly different than I anticipated, here is an update.
class Teacher(models.Model):
user = models.OneToOneField(User)
Which means that the backref will be a little different.
hasattr(self.request.user, "teacher")
As you've mentioned that you are doing this inside a django template, I'm pretty sure that the following will work:
{% if user.teacher %}
Since you haven't posted your models, I am giving you a rough idea how to do it.
in your views.py -
from .models import Teacher,Student
def test_func(request):
user = request.user
if (Teacher.objects.filter(user=user).count() > 0) or (Student.objects.filter(user=user).count > 0):
#do your stuffs here..
One way is to query both tables:
teacher = Teacher.objects.filter(user=self.request.user)
student = Student.objects.filter(user=self.request.user)
if teacher or student:
# do what you want.
If you put in your relation the argument "related_name" you can do it using inverse relationship
class SomeTable(models.Model):
user = models.ForeignKey(
User, #Your user model or Django one
verbose_name = "User",
related_name = "inverse_relation_name"
)
Then you have to call using keyword arguments for the filters:
SomeTable.inverse_relation_name.filter(id=self.request.user.id) #You will get a queryset
Or
SomeTable.inverse_relation_name.get(id=self.request.user.id) # You will get the object or a exception

Categories

Resources