Django QuerysSet for finding related foreign key fields - django

I'm trying to query a related field to a Catalog class in which many items are related to by foreign key. I'm currently trying:
article = forms.ModelChoiceField(queryset=Catalog.objects.select_related(
'article_products'))
It seems to do the same query as:
queryset = Catalog.objects.all()
Can anyone help steer me in the right direction? Here is the model I'm working with.
class Catalog(models.Model):
products = models.CharField(max_length=200)
def __unicode__(self):
return self.products
class Article(models.Model):
catalog = models.ForeignKey(Catalog, related_name='article_products')
title = models.CharField(max_length=200)
abstract = models.TextField(max_length=1000, blank=True)
full_text = models.TextField(blank=True)
proquest_link = models.CharField(max_length=200, blank=True, null=True)
ebsco_link = models.CharField(max_length=200, blank=True, null=True)
def __unicode__(self):
return self.title
My goal is to have a form select field with all of the articles related to the Catalog. It currently just displays the name of the Catalog.

I do not think the select_related method will accomplish the goal you have set out to achieve with this ModelChoiceField. You are quite correct that the two queries below return the same resulting queryset:
Catalog.objects.all().select_related('article_products'))
Catalog.objects.all()
The select_related method of Django querysets serves a different function, specifically as a performance booster to reduce the number of database accesses required to obtain the data you want to retrieve from a model instance. The Django reference about this method contains very good documentation, with examples explaining why you would use the select_related method for performance purposes.
With that being said, your original purpose remains: The form field would display all of the articles related to a given catalog.
In order to achieve this goal, it seems best to filter the queryset of the Article objects being given to the form field. First of all, if we want to display Article objects within the ModelChoiceField, we should certainly give the ModelChoiceField a queryset containing Article objects rather than Catalog objects, like so:
article = forms.ModelChoiceField(queryset=Article.objects.all())
But this queryset argument is not quite right, either. We are still passing the queryset of all Article objects that exist in the database. Instead, we want to pass only the articles that are associated with a given Catalog object. To achieve this goal, we can filter the Article queryset to obtain only the Article objects that are related to a certain Catalog object, like so:
# cat is some catalog object
article = forms.ModelChoiceField(queryset=Article.objects.filter(catalog=cat))
In this example, the queryset filter returns only Article objects which contain a reference to the given Catalog object. This queryset will be used to populate the ModelChoiceField.
For more information about filtering by field lookup, see the Django documentation here.

Related

Filter distinct objects from ManyToMany within ManyToMany

I have following Models in my Django DRF app.
class FilterValue(models.Model):
code = models.Charfield(…)
class ProductVariant(models.Model):
filters = models.ManyToManyField("FilterValue", blank=True, …)
class Product(models.Model):
variants = models.ManyToManyField("ProductVariant", blank=True, …)
category = models.ForeignKey("Category", blank=True)
And I’m trying to define function on Category model which will return all objects of FilterValue attached to all ProductVariants of all Products that are assigned in the category.
Since I’ll have loads of different ProductVarints I can’t get away with nested loops since it’d be really slow.
I had multiple attempts to solve it.
I can of course get all Products within a Category using:
products = Product.objects.filter(category=self)
But then I get stuck on the fact that I actually need to filter nested ManyToMany object since I need all ProductVariants of all Products in the QuerySet and then - in the another level I need all ManyToMany FilterValue objects of each ProductVariant.
Thank you.
Here is the documentation on how to query many-to-many relationships.
Try this to solve your problem:
filters = FilterValues.objects.filter(productvariant_set__in = ProductVariant.objects.filter(product_set__in = Product.objects.filter(category=your_category)))

Use of select_related in simple query in django

I have a model in Django in which a field has a fk relationship with the teacher model. I have came across select_related in django and want to use it in my view. However, I am not sure whether to use it in my query or not.
My models:
class Teacher(models.Model):
name = models.OneToOneField(max_length=255, default="", blank=True)
address = models.CharField(max_length=255, default="", blank=True)
college_name = models.CharField(max_length=255, default="", blank=True)
class OnlineClass(models.Model):
teacher = models.ForeignKey(Teacher,on_delete=models.CASCADE)
My view:
def get(self, request,*args, **kwargs):
teacher = self.request.user.teacher
classes = Class.objects.filter(teacher=teacher) #confusion is here..............
serializer_class = self.get_serializer_class()
serializer = serializer_class(classes,many=True)
return Response(serializer.data,status=status.HTTP_200_OK)
I have commented on the line or the section of the problem. So I wanted to list all the classes of that teacher. Here I have used filter. But can we use select_related here?? What I understood is if I want to show another fields of teacher model as well, for eg name or college_name, then I have to use it. Otherwise the way I have done it is correct. Also, select_related is only used for get api not for post api, is that correct??
First, the easiest way to get all classes per teacher is by using the related_name attribute (https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.ForeignKey.related_name).
class OnlineClass(models.Model):
teacher = models.ForeignKey(
Teacher,
on_delete=models.CASCADE,
related_name='classes'
)
# All classes of a teacher
teacher.classes.all()
When select_related is used, new sql joins are added to the Django internals SQL query. It is useful to reduce the workload in the database engine, getting the data quickly, and yes, is only for reading.
for obj in OnlineClass.objects.all():
# This hits the database every cycle to get the teacher data,
# with a new query like: select * from teacher_table where id = ...
print(obj.teacher)
for obj in OnlineClass.objects.select_related('teacher').all():
# This don'ts hits the database.
# Previously, the Django ORM joined the
# OnlineClass and Teacher data with a single SQL query.
print(obj.teacher)
I think that, in your example, with only one teacher, using "select_related" or not don't make big difference.
select_related is used to select additional data from related objects when the query is executed. It results in a more complex query. But it boosts performance if you have to access related data, since no additional database queries will be required.
See documentation here.
In your code it would be possible to use select_related, but it would be inefficient, because you're not accessing related objects of the queried classes. So using select_related would result in a more complex query without any advantage.
If you wanted to use select_related, the syntax would be classes = Class.objects.select_related('teacher').filter(teacher=teacher)

Django project architecture advice

I have a django project and I have a Post model witch look like that:
class BasicPost(models.Model):
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
published = models.BooleanField(default=False)
created_date = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=False)
body = models.TextField(max_length=999)
media = models.ImageField(blank=True)
def get_absolute_url(self):
return reverse('basic_post', args=[str(self.pk)])
def __str__(self):
return self.title
Also, I use the basic User model that comes with the basic django app.
I want to save witch posts each user has read so I can send him posts he haven't read.
My question is what is the best way to do so, If I use Many to Many field, should I put it on the User model and save all the posts he read or should I do it in the other direction, put the Many to Many field in the Post model and save for each post witch user read it?
it's going to be more that 1 million + posts in the Post model and about 50,000 users and I want to do the best filters to return unread posts to the user
If I should use the first option, how do I expand the User model?
thanks!
On your first question (which way to go): I believe that ManyToMany by default creates indices in the DB for both foreign keys. Therefore, wherever you put the relation, in User or in BasicPost, you'll have the direct and reverse relationships working through an index. Django will create for you a pivot table with three columns like: (id, user_id, basic_post_id). Every access to this table will index through user_id or basic_post_id and check that there's a unique couple (user_id, basic_post_id), if any. So it's more within your application that you'll decide whether you filter from a 1 million set or from a 50k posts.
On your second question (how to overload User), it's generally recommended to subclass User from the very beginning. If that's too late and your project is too far advanced for that, you can do this in your models.py:
class BasicPost(models.Model):
# your code
readers = models.ManyToManyField(to='User', related_name="posts_already_read")
# "manually" add method to User class
def _unread_posts(user):
return BasicPost.objects.exclude(readers__in=user)
User.unread_posts = _unread_posts
Haven't run this code though! Hope this helps.
Could you have a separate ReadPost model instead of a potentially large m2m, which you could save when a user reads a post? That way you can just query the ReadPost models to get the data, instead of storing it all in the blog post.
Maybe something like this:
from django.utils import timezone
class UserReadPost(models.Model):
user = models.ForeignKey("auth.User", on_delete=models.CASCADE, related_name="read_posts")
seen_at = models.DateTimeField(default=timezone.now)
post = models.ForeignKey(BasicPost, on_delete=models.CASCADE, related_name="read_by_users")
You could add a unique_together constraint to make sure that only one UserReadPost object is created for each user and post (to make sure you don't count any twice), and use get_or_create() when creating new records.
Then finding the posts a user has read is:
posts = UserReadPost.objects.filter(user=current_user).values_list("post", flat=True)
This could also be extended relatively easily. For example, if your BasicPost objects can be edited, you could add an updated_at field to the post. Then you could compare the seen_at of the UserReadPost field to the updated_at field of the BasicPost to check if they've seen the updated version.
Downside is you'd be creating a lot of rows in the DB for this table.
If you place your posts in chronological order (by created_at, for example), your option could be to extend user model with latest_read_post_id field.
This case:
class BasicPost(models.Model):
# your code
def is_read_by(self, user):
return self.id < user.latest_read_post_id

Django M2M with a large table

I have a typical M2M scenario where promotion activities are related to our retailers. However we have a large number of retailers (over 10k) and therefore I can't use the normal multiple select widget.
What I would aim to do is have an 'activity' instance page with a 'retailer' sub-page which would have a table listing all those retailers currently related to the activity. In addition there would be a 'delete' checkbox next to each retailer so they could be removed from the list if necessary. (Naturally, I would also have another search/results page where users could select which retailers they want to add to the list, but I'm sure I can sort that out myself).
Could someone point me in the right direction regarding modelforms and formset factories as I'm not sure where to go from here. It would seem obvious to directly manipulate the app_activity_associated_retailers table but I don't think I can do this with the existing functions. Is there was a pattern for doing this.
class Activity(models.Model):
budget = models.ForeignKey('Budget')
activity_type = models.ForeignKey('ActivityType')
amount = models.DecimalField(max_digits=8, decimal_places=2)
associated_retailers = models.ManyToManyField('Retailer', related_name='associated_retailers')
class Retailer(models.Model):
name = models.CharField(max_length=50)
address01 = models.CharField(max_length=50)
address02 = models.CharField(max_length=50, blank=True)
postcode = models.CharField(max_length=5)
city = models.CharField(max_length=20)
All ManyToManyFields have a through model, whether you define one yourself or not. In your case, it'll have an id, an activity field and a retailer field. You can access the table with Activity.associated_retailers.through -- one "obvious" way is to just expose it as a "model" like
ActivityRetailer = Activity.associated_retailers.through
You can now manipulate these relationships like they were any ol' Django model, so you can i.e. generate querysets like
retailer_records_for_activity = ActivityRetailer.objects.filter(activity_id=1234)
... and you can also create model formsets (complete with that delete checkbox if so configured) for these pseudo-models.

Django Models Manager for saving custom processed data in a field

A noob here. I have a model class where I want to save something processed in one of the fields of that table. am trying to use a ModelManager for that but do not know if it is possible or how to.
I want to save a custom url for each post here. So I want to have a method in PostManager class which will calculate hash of something (say current time) and save it as a url. I could not find any syntax help so asking it here.
class Post (models.Model):
name = models.CharField(max_length=1000, help_text="required, name of the post")
description = models.TextField(blank=True)
created_datetime = models.DateTimeField(auto_now_add=True, editable=False)
modified_datetime = models.DateTimeField(auto_now=True, editable=False)
custom_hashed_url = models.CharField(unique=True, max_length=1000, editable=False)
def save(self, *args, **kwargs):
#How to refer to the custom_hashed_url in the Post class?
super(Model, self).save()
If you want the url to be saved in the database with the rest of the information, it will need to appear in the model as a field.
Change the url to an appropriate field type and set its 'editable' attribute to False, as you've done with the datetime fields. This will stop it appearing in forms.
You could then override the model's save method (see Django docs) so that it calculates the post's url and adds it automatically as the instance is saved!
Model managers are used for 'model level' interactions that work with many instances, or sets of instances. In this case you are trying to manipulate a single instance. We use a field to store the information in the database for the record and a method (in this case overriding a built-in method to hook into the default behaviours) to calculate the field's value.
Good luck!