How to retrieve all many_to_many relations for each objects in a single query - python-3.x

I have four tables :
class Recipe(models.Model):
item_recipe = models.OneToOneField(Item, on_delete=models.CASCADE, related_name='item_recipe')
items = models.ManyToManyField(Item, through='RecipeItem')
class RecipeItem(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
class Item(models.Model):
def __str__(self):
return self.name
name = models.CharField(max_length=200, unique=True)
effects = ArrayField(models.CharField(max_length=200), blank=True, default=list)
pods = models.IntegerField(null=True)
level = models.IntegerField(null=True)
type = models.CharField(max_length=200, null=True, blank=True, default="Ressource")
image = models.URLField()
class Prix(models.Model):
def __str__(self):
return self.item.name
prix_x1 = models.IntegerField(null=True)
prix_x10 = models.IntegerField(null=True)
prix_x100 = models.IntegerField(null=True)
saved_at = models.DateTimeField()
item = models.ForeignKey(Item, on_delete=models.CASCADE)
A recipe is composed of 1 to 8 Items with a certain quantity indicated is the RecipeItem table.
I would like a query that gives me the price of every Recipe.
In other word, a query that get all items and its prices for every recipe and sum it.
I don't find a way to do that without a for loop.
EDIT
Here is what i have currently, it's not pretty and not effective ..
items = Recipe.objects.all().select_related('item').annotate(
prix1_ressource=Subquery(
Prix.objects.filter(
item=OuterRef('items')
).values('prix_x1').exclude(prix_x1__isnull=True).order_by('-saved_at')[:1]
),
prix1_item=Subquery(
Prix.objects.filter(
item=OuterRef('item_recipe')
).values('prix_x1').exclude(prix_x1__isnull=True).order_by('-saved_at')[:1]
)
).exclude(prix1_item__isnull=True).values('id', 'item_recipe__name', 'prix1_ressource', 'items__name',
'recipeitem__quantity', 'prix1_item',
'item_recipe__type', 'item_recipe')
for id in np.unique(items.values_list('id', flat=True)):
item = items.filter(id=id)
try:
prix_craft = sum([i['recipeitem__quantity'] * i['prix1_ressource'] for i in item])
gain = item[0]['prix1_item'] - prix_craft
except TypeError:
continue

There is really not a nice SQL way. At some point either you or Django will have to iterate over all the items in a query and sum them. Perhaps you could make it a little more efficient by using an Inner join, as documented here, using it to preload the items. But resolving the item's current Prix might require a separate query each time.

Related

how do I get foreighkey of foreighkey without a loop in django?

It is a Django project, I am trying to create a wishlist (many-to-many will not help because I need DateTime of getting that wished item in the wishlist).
class Client(models.Model):
name = models.CharField(max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField()
class WishItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
client = models.ForeignKey(Client, related_name="wishlist", on_delete=models.CASCADE)
added_at = models.DateTimeField(auto_now_add=True)
What I could do is only this:
wishlist = Client.objects.wishlist.select_related('product').all()
wish_products = [item.product for item in wishlist]
But I need something like this, without a loop but with a single SQL query and single line
wishlist = Client.objects.wishlist.product.all()
When I try to run this code I get an error AttributeError: 'RelatedManager' object has no attribute 'product'
You can .filter(…) [Django-doc] and then .order_by(…) [Django-doc] with:
Product.objects.filter(wishitem__client__user=my_user).order_by('wishitem__added_at')
You can make it more covenient to query by spanning a ManyToManyField with your WishItem:
class Client(models.Model):
# …
wishlist = models.ManyToManyField(
'Product',
through='WishItem'
)
class Product(models.Model):
# …
pass
class WishItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
client = models.ForeignKey(Client, related_name='wishitems', on_delete=models.CASCADE)
added_at = models.DateTimeField(auto_now_add=True)
then you can query with:
Product.objects.filter(client__user=my_user).order_by('wishitem__added_at')
It will also make querying for the .wishlist of the Client more covenient, of the Products where the .client_set is a manager that manages the Clients that have that Product on the wishlist.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Many to many relationship will fix the problem you can add extra fields to your WishItem class you can try this :
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField()
class Client(models.Model):
name = models.CharField(max_length=100)
user = models.ForeignKey(User, on_delete=models.CASCADE)
WishProducts = models.ManyToManyField(Product,through='WishItem')
class WishItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
client = models.ForeignKey(Client, on_delete=models.CASCADE)
added_at = models.DateTimeField(auto_now_add=True)

retrieve data from manytomany field in the sequence of it was saved django

I Have a Invoice system where employee or staff can create invoice and can add multiple product and quantity for the specific customer . as i am using mysql i cant take json data or an array data .so i was taking the quantity and price( after discount and other modificaion) as a string and then when showing or printing the invoice i used regex to find the quantity and price .i added product ids in a manytomay field from where i am getting the product name and selling price. while showing the data on printing page in when i use zip the products are showing as the id of the product so i want to retrive the data the way it is being saved . or could you tell me any way to do it more easier way?
Here is my models.py
class Invoice(models.Model):
customers = models.ForeignKey(Customer, on_delete=models.CASCADE)
products = models.ManyToManyField(Product)
total = models.FloatField()
date = models.DateField(auto_now_add=True)
amounts = models.CharField(max_length=500, default="00")
quantity = models.CharField(max_length=500, blank=True)
def save(self, *args, **kwargs):
if not Invoice.objects.count():
self.id = 20210001
else:
self.id = Invoice.objects.last().id + 1
super(Invoice, self).save(*args, **kwargs)
Here is my views.py of printing page function
def final_billing(request, id=None):
pk = id
obj = Invoice.objects.get(id=pk)
products = obj.products.all()
customer = Customer.objects.get(id=obj.customers.id)
amn = obj.amounts
qt = obj.quantity
list_of_comma = re.findall("[\d+.+\d]+", amn)
amnts = [float(n) for n in list_of_comma]
list_of_quantity = re.findall('[0-9]+', qt)
qty = [int(n) for n in list_of_quantity if n.isdigit()]
products = list(products)
both = zip(products,amnts,qty)
return render(request, 'finalbill.html',{'bills': obj, "due": customer, "both": both})
I want it to be retrieved the product objects in the sequence of it was saved
The query can only be sorted with a specific field, Django cannot guess otherwise, so in your case the best case is to sort your products by the date they were created, for example :
obj.products.all().order_by("created")
This suppose that you have a "created" field that is added each time a product is save in your database.
Another way of doing it is to specify the through option, from the documentation :
you can use the through option to specify the Django model that represents the intermediate table that you want to use.
The most common use for this option is when you want to associate extra data with a many-to-many relationship.
The through table contains an the primary key of the relation, you can use that to retrieve the sequence in which your objects were added.
for example :
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership',
through_fields=('group', 'person'),
)
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(
Person,
on_delete=models.CASCADE,
related_name="membership_invites",
)
invite_reason = models.CharField(max_length=64)
Through Field

How to get value of ForeignKey in views.py?

I have below models.py and views.py in Django(This is only a example). How can i print the value of ForeignKey in views.py?
models.py
class student(models.Model):
name = models.CharField(max_length=50)
desc = models.CharField(max_length=100, blank=True, null=True)
phone_number = models.ForeignKey(tell,on_delete=models.SET_NULL, null = True,blank=True)
def __str__(self):
return self.name
class tell(models.Model):
phone_number = models.CharField(max_length=20, blank=True, null=True)
def __str__(self):
return self.phone_number
views.py
phones = student.objects.values('phone_number')
phone = list(phones)
for ob in phone:
print(ob)
This prints only id but i want the value of foreign key.
This is one of the (many) reasons not to use .values(…) [Django-doc]: it erodes the model logical layer of a Django model.
You can work with:
tell.objects.filter(student__isnull=False)
this will retrieve all tell ojects that have at least a related Student.
You can also use .select_related(…) [Django-doc] to select the telephone number while retrieving the students, and thus avoid an N+1 problem:
students = student.objects.select_related('phone_number'):
for stud in students:
print(stud.phone_number)
If you really want to work with the phone_number of the tell model, you can work with .values_list(…) [Django-doc], and work with:
phones = student.objects.values_list('phone_number__phone_number', flat=True)
for phone in phones:
print(phone)
but this are thus not tell objects, and thus updating, refrehing, a record is not possible in that case.

How can I join two child tables using Django Querset API?

I have a model with two child tables and one parent table. Here is the sample model classes.
# Stores list of unique category names
class Category(models.Model):
category = models.CharField(max_length=20, unique=True, validators=[MinLengthValidator(limit_value=5)])
name = models.CharField(max_length=20, validators=[MinLengthValidator(limit_value=8)])
# Parent class for the next two child classes
class DailyLog(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=100, validators=[MinLengthValidator(limit_value=8)])
code = models.CharField(max_length=4, validators=[MinLengthValidator(limit_value=3)])
suggested_values = models.CharField(max_length=100, null=True, blank=True)
# First child class defines display order for dailylog items
class DailyLogDisplayOrder(models.Model):
category_item = models.ForeignKey(DailyLog, on_delete=models.CASCADE)
display_order = models.PositiveIntegerField()
# Second Child class publishes dailylog on a daily bases
class DailyLogCheckList(models.Model):
daily_task = models.ForeignKey(DailyLog, on_delete=models.CASCADE)
publish_date = models.DateField(auto_now=True)
daily_task = DailyTaskCategoryManager() # Log manager to get records per category
How do I perform a cartesian product query? The last column comes from the first child table Dailylogdisplayorder. Here is the raw sql.
select daily_task_id, checklist.publish_date, disporder.display_order
from dailylogchecklist checklist, compliance_dailylogdisplayorder disporder
where checklist.daily_task_id = disporder.category_item_id and checklist.publish_date='2020-07-12'
I have tried using cursor.execute() method per Django documentation. However, I am not able to figure out how to return results in QuyerySet. And also looking for a better way to combine child columns using QuerySet. The return queryset is assigned to a formset.
class DailyTaskCategoryManager(models.Manager):
def with_displayorder(self, user):
from django.db import connection
dtrange = datetime.today().date()
with connection.cursor() as cursor:
cursor.execute("select daily_task_id, checklist.publish_date, disporder.display_order
from dailylogchecklist checklist, compliance_dailylogdisplayorder disporder
where checklist.daily_task_id = disporder.category_item_id and
checklist.publish_date=%s", [dtrange])
result_list = []
for row in cursor.fetchall():
p = self.model(id=row[0], daily_task_id=row[1], publish_date=row[2])
p.display_order = row[3]
result_list.append(p)
return result_list
I already answered to a similar question, You can use prefetch_related() to get the related child table data. Check this answer
https://stackoverflow.com/a/71571509/9561654

Django Haystack return related insances

Hi I´m using django with haystack search. I have one model for Events. Thats the model I'm doing the search on. I have a second model to count the hits/views for the Events. I wan't to return the number of hits for every event additional to the search results.
my view:
def event_search(request):
if request.method == 'POST':
query = str(request.POST['search'])
events = SearchQuerySet().auto_query(query).models(Event).order_by('date')
return render_to_response('event_search.html', {"events": events}, context_instance=RequestContext(request))
else:
return render_to_response('event_search.html', context_instance=RequestContext(request))
my models:
class Event(models.Model):
name = models.CharField(max_length = 70)
date = models.DateTimeField()
description = models.TextField()
active = models.BooleanField(default=True, editable=False)
featured = models.BooleanField(default=False)
class EventHitcount(models.Model):
hit = models.ForeignKey(Event)
ip = models.CharField(max_length=40)
session = models.CharField(max_length=40)
created = models.DateTimeField(default=datetime.datetime.now())
By giving the ForeignKey field a related name it can call and count the related objects.
class Foo(models.Model):
fk = models.ForeignKey(Event,related_name='foofk')
some more fields...
In the template:
{{ foo.foofk.count }}

Resources