Optimise calculs with table fields and deal with possible None values - python

I have 3 differents tables : Price, Recipe and Item
class Item(models.Model):
def __str__(self):
return self.name
name = models.CharField(max_length=200, unique=True)
image = models.URLField()
class Recette(models.Model):
item = models.OneToOneField(Item, on_delete=models.CASCADE)
qte_ressource_1 = models.IntegerField(null=True)
ressource_1 = models.ForeignKey(Item, on_delete=models.SET_NULL, blank=True, null=True, related_name='ressource_1')
qte_ressource_2 = models.IntegerField(null=True)
ressource_2 = models.ForeignKey(Item, on_delete=models.SET_NULL, blank=True, null=True, related_name='ressource_2')
qte_ressource_3 = models.IntegerField(null=True)
ressource_3 = models.ForeignKey(Item, on_delete=models.SET_NULL, blank=True, null=True, related_name='ressource_3')
qte_ressource_4 = models.IntegerField(null=True)
ressource_4 = models.ForeignKey(Item, on_delete=models.SET_NULL, blank=True, null=True, related_name='ressource_4')
qte_ressource_5 = models.IntegerField(null=True)
ressource_5 = models.ForeignKey(Item, on_delete=models.SET_NULL, blank=True, null=True, related_name='ressource_5')
qte_ressource_6 = models.IntegerField(null=True)
ressource_6 = models.ForeignKey(Item, on_delete=models.SET_NULL, blank=True, null=True, related_name='ressource_6')
qte_ressource_7 = models.IntegerField(null=True)
ressource_7 = models.ForeignKey(Item, on_delete=models.SET_NULL, blank=True, null=True, related_name='ressource_7')
qte_ressource_8 = models.IntegerField(null=True)
ressource_8 = models.ForeignKey(Item, on_delete=models.SET_NULL, blank=True, null=True, related_name='ressource_8')
class Prix_x1(models.Model):
def __str__(self):
return self.item.name
prix = models.IntegerField(null=True)
saved_at = models.DateTimeField()
item = models.ForeignKey(Item, on_delete=models.CASCADE)
Each Item can have a Recipe and a recipe is composed of 1 to 8 items with a quantity associated to each ingredient.
And all of the items have a price.
Here is what i want :
For all item that have a recipe, i want to calcul the total price of this recipe, that mean to get the price of all the ingredients. The problem is that i defined my recipe model to have a maximum of of 8 ingredients but a recipe can have only 2 for exemple, so the rest of the ingredient is set to None.
I tried something like that :
items = Item.objects.all().select_related('recette').exclude(recette__isnull=True).annotate(
prix1=Subquery(
Prix_x1.objects.filter(
item=OuterRef('pk')
).values('prix').order_by('-saved_at')[:1]
)
)
for item in items:
prix_craft, is_recette_ok = item.recette.get_recette_price()
prix = item.prix1
if is_recette_ok and prix:
gain = prix - prix_craft
prices.append({'name': item.name,
'key': item.pk,
'prix_hdv': prix,
'prix_craft': prix_craft,
'gain': gain,
'pourcentage_gain': np.around((gain / prix_craft) * 100)})
and i created a get_recette_price() like that :
def get_fields(self):
values = []
for field in Recette._meta.fields:
if field.value_to_string(self) != 'None':
if field.name not in ('item', 'id'):
values.append(int(field.value_to_string(self)))
return values
def get_recette_price(self):
fields = self.get_fields()
quantites = fields[::2]
ressources = Item.objects.filter(id__in=fields[1::2]).annotate(
prix1=Subquery(
Prix_x1.objects.filter(item=OuterRef('pk')).values('prix').order_by('-saved_at')[:1]
)
).values_list('prix1', flat=True)
is_recette_ok = True
total = 0
for quantite, ressource in zip(quantites, ressources):
if quantite and ressource:
total += quantite * ressource
else:
is_recette_ok = False
break
return total, is_recette_ok
But the code is really long to execute.
I don't know how to optimise it and deal with the None value if the recipe doesnt have 8 ingredients.
Thanks for your help !

Related

How to select multiple items with multiple quantities in django rest framework?

Models.py
class BaseModel(models.Model):
branch = models.ForeignKey(Branch, on_delete=models.PROTECT, blank=True, null=True)
company = models.ForeignKey(
Company, on_delete=models.PROTECT, blank=True, null=True
)
class Meta:
abstract = True
class MealMenu(BaseModel):
employee = models.ForeignKey(
Employee, on_delete=models.PROTECT, null=True, blank=True
)
item_name = models.CharField(max_length=50, null=True, blank=True)
quantity = models.PositiveIntegerField()
price = models.FloatField()
def __str__(self):
return f"{self.item_name} {self.price}"
class MealOrder(BaseModel):
RECEIVED = "Received"
PENDING = "Pending"
REJECTED = "Rejected"
MEAL_CHOICES = (
("Breakfast", "Breakfast"),
("Lunch", "Lunch"),
("Dinner", "Dinner"),
)
STATUS_CHOICES = (
(RECEIVED, "Received"),
(PENDING, "Pending"),
(REJECTED, "Rejected"),
)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, null=False)
total_items = models.IntegerField(null=True, default=0)
total_amounts = models.FloatField(default=0.0)
menu = models.ForeignKey(MealMenu, on_delete=models.PROTECT)
quantity = models.PositiveIntegerField(default=1, blank=False)
meal_time = models.CharField(max_length=25, choices=MEAL_CHOICES)
employee = models.ForeignKey(Employee, on_delete=models.PROTECT)
date = models.DateField(auto_now=True)
status = models.CharField(max_length=25, choices=STATUS_CHOICES, default=PENDING)
I have two models. In First Model i have created a menu item_name,price and quantity.
In MealOrder i have foreign key MealMenu Model and created quantity field separately.
I want to select multiple items with their multiple quantities. But i can't understand the scenario.
So you could have a separate model to handle the quantity for different items in an order.
Like this:
class MealOrderItem(BaseModel):
order = models.ForeignKey(
MealOrder, on_delete=models.PROTECT, null=True, blank=True
)
quantity = models.PositiveIntegerField()
meal = ForeignKey(
MealMenu, on_delete=models.PROTECT, null=True, blank=True
)
This will help you create multiple meal menu selections for an order with each having its own quantity.

How to count reviews for a product in django?

I am building an ecommerce website with django. In my models I have a Product and review model. How should i connect the two for the number of reviews and average rating attribute?
This is my current models file
class Product(models.Model):
name = models.CharField(max_length=200, null=True, blank=True)
brand = models.CharField(max_length=200, null=True, blank=True)
image = models.ImageField(null=True, blank=True, default='placeholder.png')
description = models.TextField(null=True, blank=True)
rating = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)
price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)
countInStock = models.IntegerField(null=True, blank=True, default=0)
id = models.UUIDField(default=uuid.uuid4, max_length=36, unique=True, primary_key=True, editable=False)
numReviews = [Count the number of reviews where product.id matches self.id]
averageRating = [Sum up the ratings in reviews for this product and divide them by their count]
def __str__(self):
return str(self.name)
class Review(models.Model):
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
rating = models.IntegerField(null=True, blank=True, default=0)
comment = models.TextField(null=True, blank=True)
createdAt = models.DateTimeField(auto_now_add=True)
id = models.UUIDField(default=uuid.uuid4, max_length=36, unique=True, primary_key=True, editable=False)
def __str__(self):
return f'{self.user} review for {self.product}'
As you can see the numReviews and average rating columns are meant to connect both tables. I have been trying to figure out how to do it correctly with no success.
Any help would be greatly appreciated
I would make them into model methods.. I don't think there will be any issues that the Review object is defined below the method
and for the Avg I used a Django command aggregate which forces the DB to do the work.
models.py
class Product(models.Model):
name = models.CharField(max_length=200, null=True, blank=True)
brand = models.CharField(max_length=200, null=True, blank=True)
image = models.ImageField(null=True, blank=True, default='placeholder.png')
description = models.TextField(null=True, blank=True)
rating = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)
price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)
countInStock = models.IntegerField(null=True, blank=True, default=0)
id = models.UUIDField(default=uuid.uuid4, max_length=36, unique=True, primary_key=True, editable=False)
def __str__(self):
return str(self.name)
def num_of_reviews(self):
return Review.objects.filter(product=self).count()
def average_rating(self):
from django.db.models import Avg
return Review.objects.filter(product=self).aggregate(Avg('rating'))['rating__avg']
Use
obj = Product.objects.all().first()
obj.num_of_reviews()
obj.average_rating()
Edit
Reverse relationship per #NixonSparrow
def num_of_reviews(self):
return self.review_set.count()
def average_rating(self):
from django.db.models import Avg
return self.review_set.aggregate(Avg('rating'))['rating__avg']

How can I split orders by attributes in my API

I have a method that creates orders from the user's cart. For the courier to take an order from different restaurants, the order is divided into several. But at the moment I'm splitting the order just by the dish in the cart. How to make an order split by restaurants? that is, if a user orders 5 dishes from two different restaurants, then 2 orders were formed.
views.py
#action(methods=['PUT'], detail=False, url_path='current_customer_cart/add_to_order')
def add_cart_to_order(self, *args, **kwargs):
cart = Cart.objects.get(owner=self.request.user.customer)
cart_meals = CartMeal.objects.filter(cart=cart)
data = self.request.data
for cart_meal in cart_meals:
order = Order.objects.create(customer=self.request.user.customer,
cart_meal=cart_meal,
first_name=data['first_name'],
last_name=data['last_name'],
phone=data['phone'],
address=data.get('address', self.request.user.customer.home_address),
restaurant_address=cart_meal.meal.restaurant.address,
)
order.save()
return response.Response({"detail": "Order is created", "added": True})
models.py
class Order(models.Model):
"""User's order"""
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='related_orders')
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
phone = models.CharField(max_length=20)
cart_meal = models.ForeignKey(CartMeal, on_delete=models.CASCADE, null=True, blank=True)
restaurant_address = models.CharField(max_length=1024, null=True)
address = models.CharField(max_length=1024)
status = models.CharField(max_length=100, choices=STATUS_CHOICES, default=STATUS_NEW)
created_at = models.DateTimeField(auto_now=True)
delivered_at = models.DateTimeField(null=True, blank=True)
courier = models.OneToOneField('Courier', on_delete=models.SET_NULL, null=True, blank=True)
class CartMeal(models.Model):
"""Cart Meal"""
user = models.ForeignKey('Customer', on_delete=models.CASCADE)
cart = models.ForeignKey('Cart', verbose_name='Cart', on_delete=models.CASCADE, related_name='related_meals')
meal = models.ForeignKey(Meal, verbose_name='Meal', on_delete=models.CASCADE)
qty = models.IntegerField(default=1)
final_price = models.DecimalField(max_digits=9, decimal_places=2)
class Meal(models.Model):
"""Meal"""
title = models.CharField(max_length=255)
description = models.TextField(default='The description will be later')
price = models.DecimalField(max_digits=9, decimal_places=2)
discount = models.IntegerField(default=0)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE, null=True)
slug = models.SlugField(unique=True)
class Restaurant(models.Model):
"""Restaurant"""
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
address = models.CharField(max_length=1024)
owner = models.ForeignKey('Restaurateur', on_delete=models.CASCADE, null=True)
meals = models.ManyToManyField('Meal', related_name='related_restaurant', blank=True)
How can I do this, please help me
You can group your meals with respect to resturants.
import itertools
from core.models import CartMeal, Order
for restaurant, cart_meals in itertools.groupby(CartMeal.objects.order_by('meal__restaurant'), lambda s: s.meal.restaurant):
order = Order.objects.create(
customer=self.request.user.customer,
first_name=data['first_name'],
last_name=data['last_name'],
phone=data['phone'],
address=data.get('address', self.request.user.customer.home_address),
restaurant_address=cart_meal.meal.restaurant.address,
)
order.cart_meal.set([cart_meal for cart_meal in cart_meals])
Ref: The answer is formulated by taking help from following answer.
https://stackoverflow.com/a/57897654/14005534

How to ORDER BY max no of same records in DJANGO

I want to order by movies in the Movies model according to the max number of occurrences of a tuple in the MyMovies model.
models.py
class Movies(models.Model):
mid = models.CharField(max_length=255, primary_key=True)
title = models.CharField(max_length=255, null=True, blank=True)
rating = models.CharField(max_length=5, null=True, blank=True)
type = models.CharField(max_length=255, null=True, blank=True)
genre = models.CharField(max_length=255, null=True, blank=True)
rdate = models.CharField(max_length=255, null=True, blank=True)
language = models.CharField(max_length=255, null=True, blank=True)
cover = models.CharField(max_length=255, null=True, blank=True)
description = models.TextField(null=True, blank=True)
sequal = models.CharField(max_length=255, null=True, blank=True)
trailer = models.CharField(max_length=255, null=True, blank=True)
year = models.CharField(max_length=5, null=True, blank=True)
objects = models.Manager()
def __str__(self) -> str:
return self.title
class MyMovies(models.Model):
mid = models.ForeignKey(Movies, on_delete=CASCADE)
uid = models.ForeignKey(User, on_delete=CASCADE, null=True, blank=True)
watched = models.BooleanField()
date = models.DateTimeField(auto_now_add=True)
objects = models.Manager()
view.py
def showIndexPage(request):
trending = list(MyMovies.objects.all().annotate(max_mid=Max(COUNT(mid))).order_by('-max_mid'))
return render(request, 'index.html', {'trending': trending})
In the above code, MyMovies is my model with a foreign key mid referencing the Movie model.
So, if in MyMovies there are 2 movies with mid 1, 4 movies with mid 2 and 1 movie with mid 3
Then the result should be a list (trending) of attributes of Movies which is ordered by no. of occurrences of a particular movie id:
trending = [2, 1, 3]
I would start the other way around and annotate the count to your Movie class:
from django.db.models import Count
trending = Movies.objects.all().annotate(mymovie_count=Count("mymovies")).order_by("-mymovie_count").values_list("id", flat=True)
You can work with a .annotate() [Django-doc] and then .order_by(…) [Django-doc]:
from django.db.models import Count
Movies.objects.annotate(
noccurence=Count('mymovies')
).order_by('-noccurence')
The Movies that arise from this QuerySet will have an extra attribute .noccurence that has the number of related MyMovies.
Since django-3.2 you can work with .alias(…) [Django-doc] to prevent calculating this both as column and in the ORDER BY clause:
from django.db.models import Count
Movies.objects.alias(
noccurence=Count('mymovies')
).order_by('-noccurence')
Here is how the problem was resolved.
from django.db.models import Count
trending_m = MyMovies.objects.annotate(noccurence=Count('mid')).order_by('-noccurence').values_list('mid', flat='True')
trending = list(Movies.objects.filter(mid__in=trending_m))
If there is any other shorter way, please suggest. Thanks

I want to only fetch a specific field data

Hello in my order model i have these fields :
class Order(models.Model):
product = models.ForeignKey(
Product, on_delete=models.CASCADE, related_name="product")
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
fname = models.CharField(max_length=100, null=True)
address = models.CharField(max_length=1000, null=True)
phone = models.CharField(max_length=12, null=True)
price = models.IntegerField()
date = models.DateField(datetime.datetime.today, null=True)
status = models.ForeignKey(
Status, on_delete=models.CASCADE, blank=True, null=True)
payment_method = models.ForeignKey(
PaymentMethod, on_delete=models.CASCADE, blank=True, null=True)
total = models.IntegerField(null=True, blank=True)
From here i have total field where i want to create a result depend on quantity and price.For an example price * quantity = total
After that i want to fetch only the total
I am new in django really confused about this Please help
You suppose to correct your total field indentation. I do not know whether it is mistake or you are trying to do that way.
what you suppose to do is.
class Order:
total = models.IntegerField(blank=True, null=True)
For that you can overload Save method of the model. This is the way you can do is:
def save(self, *args, **kwargs):
self.total = self.quantity * self.price
return super().save(self,*args, **kwargs)
class Order(models.Model):
quantity = models.IntegerField(default=1)
price = models.IntegerField()
#property
def get_total(self):
total = self.price * self.quantity
return total

Categories

Resources