Any way to update a ManyToMany field via a queryset? - django

Suppose I have an object:
survey = Survey.objects.all().first()
and I want to create a relationship between it and a group of objects:
respondents = Respondent.objects.all()
for r in respondents:
r.eligible_for.add(survey)
where eligible_for is an M2M field in the Respondent model.
Is there any way to do it in one pass, or do I need to loop over the queryset?
models.py
class Questionnaire(models.Model):
label = models.CharField(max_length=48)
organization = models.ForeignKey('Home.Organization', on_delete=models.PROTECT, null=True)
class Respondent(models.Model):
user = models.OneToOneField('Home.ListenUser', on_delete=models.CASCADE)
organization = models.ForeignKey('Home.Organization', on_delete=models.PROTECT)
eligible_for = models.ManyToManyField('Questionnaire', related_name='eligible_users', blank=True)

.add(…) [Django-doc] can take a variable number of items, and will usually add these in bulk.
You can thus add all the rs with:
survey.eligible_users.add(*respondents)
We here thus add the respondents to the relation in reverse. Notice the asterisk (*) in front of respondents that will thus perform iterable unpacking.

https://docs.djangoproject.com/en/2.2/ref/models/relations/
survey.eligible_users.set(respondents) is another way to do this without having to unpack the list.

Related

Django: How to create a queryset from a model relationship?

I have been struggling with grasping relations for some time and would be very grateful if someone can help me out on this issue.
I have a relation that connects the User model to a ProcessInfo model via one to many and then I have a relation that connects the ProcessInfo to the ProcessAssumptions as One to one
Is there a way to use the User id to get all ProcessAssumptions related to all processes from that user.
I would like to retrieve a queryset of all ProcessAssumptions related to a user id
Here is the model relation :
class ProcessInfo(models.Model):
process_name = models.CharField(max_length=120, null=True)
user_rel = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
class ProcessAssumptions(models.Model):
completion_time = models.FloatField(default='0')
process_rel_process = models.OneToOneField(ProcessInfo, primary_key = True, on_delete=models.CASCADE)
Using field referencing for foreign keys.
process_assumption_objects = ProcessAssumptions.objects.filter(process_rel_process__user_rel=<user_id>)
Replace <user_id> with the id you wish to query for.
When you define a relationship to model X in another model Y, all related Ys can be accessed from an instance of X by X_instance.Y_set.all(). You can even perform the regular filter or get operations on that. X_instance.Y_set is the default object manager for Y (same as Y.objects), but it's filtered to only contain the objects that are related to X_instance.
So in this specific case, you can get all ProcessInfo objects for a certain user like this:
user = User.objects.get(the_user_id)
required_assumptions = [proc_info.process_assumptions for proc_info in user.process_info_set.all()]
This might be a bit hard to read with _set suffix, so you can define a related_name argument while defining the relation on the model.
like:
# in class ProcessInfo
user_rel = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, related_name='processes')
# and now you can do
some_user.processes.all()

Best and fastest method to add only unique records based on certain fields in Django

I have a model with multiple fields where data is text type for the most part. I want to put a validation that from now on if a new record is being added, it should be unique based on 3 fields. Here one of the fields is a one-to-many field as is in another table.
I can try fetching all the records and start checking them using for-loop. I tried using annotate but it seems to help if the record is already in the table, but I want to do this validation before adding. I need a fast method for this validation.
class Product(models.Model):
brand = models.TextField(default='{}', null=True)
sub_brand = models.CharField(null=True)
.
.
.
class ProductNetWeight(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='product_net_weight')
net_weight = models.CharField(null=True)
First you modify your model to guarantee uniqueness like this:
class Product(models.Model):
brand = models.TextField(default='{}', null=True)
sub_brand = models.CharField(null=True)
.
.
.
class Meta:
unique_together = ['brand', 'sub_brand']
Then when inserting a new product for example un can use get_or_create like this:
product, created = Product.objects.get_or_create(brand='my brand', sub_brand='my sub brand')
If the product already exists, it will be returned in product and created is going to be False, otherwise it will return a new product and created is going to be True

Getting all objects with equal M2M fields

I am looking for a way to filter for all objects of the same type that have the same querysets for a M2M field.
class Comment(models.Model):
user = models.ForeignKey(User, related_name='comment_user')
content = models.CharField(max_length=5000, null=True)
private_to = models.ManyToManyField(User, null=True, related_name='private_to')
Given a comment object, I want to retrieve all other comments who have an equal M2M field (i.e. if the private_to field returns User 1 and User 2 for the comment, it will find all the other comments that contain exactly both of those users in the private_to field.)
Is there a concise, built-in way to do this?
Try something from this post:
comment = Comment.objects.all()[0] # Or the comment you want to filter by.
private_to_ids = list(comment.private_to.all().values_list('id', flat=True))
comments = Comment.objects.filter(private_to__exact=private_to_ids)

How to retrieve a set of objects, filtered and ordered by fields of other objects, for which the desired object is a foreign key?

To rephrase the title to the context of my problem: How to retrieve a set of foods, filtered and ordered by fields of other objects, for which the food object is a foreign key?
I have the following models:
class Food(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=200)
description = models.CharField(max_length=200, blank=True)
class DayOfFood(models.Model):
user = models.ForeignKey(User)
date = models.DateField()
unique_together = ("user", "date")
class FoodEaten(models.Model):
day = models.ForeignKey(DayOfFood, on_delete=models.CASCADE)
food = models.ForeignKey(Food, on_delete=models.CASCADE)
servings = models.FloatField(default=1)
I want to be able to retrieve the foods that a given user ate most recently. This collection of foods will be passed to a template so it must be a QuerySet to allow the template to loop over the food objects.
This is how far I got
days = DayOfFood.objects.filter(user=request.user)
foodeatens = FoodEaten.objects.filter(day__in=days)
foodeatens = foodeatens.order_by('day__date')
Now it feels like I am almost there, all the foods I want are contained in the FoodEaten objects in the resulting QuerySet. I do not know how to use "for ... in:" to get the food objects and still have them stored in a QuerySet. Is there a way to perform foreach or map to a QuerySet?
I do not want to rewrite the template to accept FoodEaten objects instead because the template is used by other views which do simply pass food objects to the template.
The solution
The answer from Shang Wang helped me write code that solves my problem:
days = DayOfFood.objects.filter(user=request.user)
foods = Food.objects.filter(
foodeaten__day__in=days,
foodeaten__day__user=request.user) \
.order_by('-foodeaten__day__date')
That could be done using chain of relations:
Food.objects.filter(foodeaten__day__in=days,
foodeaten__day__user=request.user) \
.order_by('foodeaten__day__date')
By the way, I'm not sure why do you have user on multiple models Food and DayOfFood. If you really need user relations on both of them, maybe make the field name more explicit with the user's role in each model, otherwise you will get confused very quickly.

Selecting distinct nested relation in Django

To describe the system quickly, I have a list of Orders. Each Order can have 1 to n Items associated with it. Each Item has a list of ItemSizes. Given the following models, which have been abbreviated in terms of fields for this question, my goal is to get a distinct list of ItemSize objects for a given Order object.
class ItemSize(models.Model):
name = models.CharField(max_length=10, choices=SIZE_CHOICES)
class Item(models.Model):
name = models.CharField(max_length=100)
sizes = models.ManyToManyField(ItemSize)
class OrderItem(models.Model):
order = models.ForeignKey(Order)
item = models.ForeignKey(Item)
class Order(models.Model):
some_field = models.CharField(max_length=100, unique=True)
So... if I have:
o = Order.objects.get(id=1)
#how do I use the ORM to do this complex query?
#i need o.orderitem_set.items.sizes (pseudo-code)
In your current set up, the answer by #radious is correct. However, OrderItems really shouldn't exist. Orders should have a direct M2M relationship with Items. An intermediary table will be created much like OrderItems to achieve the relationship, but with an M2M you get much simpler and more logical relations
class Order(models.Model):
some_field = models.CharField(max_length=100, unique=True)
items = models.ManyToManyField(Items, related_name='orders')
You can then do: Order.items.all() and Item.orders.all(). The query you need for this issue would be simplified to:
ItemSize.objects.filter(item__orders=some_order)
If you need additional data on the Order-Item relationship, you can keep OrderItem, but use it as a through table like:
class Order(models.Model):
some_field = models.CharField(max_length=100, unique=True)
items = models.ManyToManyField(Items, related_name='orders', through=OrderItem)
And you still get your simpler relationships.
ItemSize.objects.filter(items__orderitems__order=some_order)
Assuming you have reverse keys like:
ItemSize.items - reverse fk for all items with such size
Item.orderitems - reverse for all orderitems connected to item
Item.orders - you can guess ;)
(AFAIR that names would be choose by default, but I'm not sure, you have to test it)
More informations about reverse key queries are available in documentation.