I have an application for calculating diets based on the nutrients of each meal. In admin of this application I want to price of each meal to the Meal table, which I have managed to do by calculating the price when displaying it in admin:
# admin.py
class AdminMeal(admin.ModelAdmin):
list_display = ['name', 'meal_type_names', 'price']
#admin.display(description='Price')
def price(self, obj):
unit_prices = np.asarray(obj.mealingredient_set.order_by('id').values_list('ingredient__unit_price'))
amounts = np.asarray(obj.mealingredient_set.order_by('id').values_list('amount'))
to_return = float(np.matmul(np.transpose(unit_prices), amounts) / 1000)
return mark_safe(to_return)
Now my main question: I need to allow ordering of Meal table based on Price which I don't know how.
based on my search it seems I should use annotate instead of my current way of calculating the price to be able to sort my table, I found a solution in here
# admin.py
class AdminMeal(admin.ModelAdmin):
list_display = ['name', 'meal_type_names', 'price']
def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(_price=Sum('mealingredient__amount * mealingredient__ingredient__unit_price'))
But sadly it throws this error (I think it's because i'm trying to SUM over different tables):
Unsupported lookup 'amount * mealingredient' for AutoField or join on the field not permitted. Any help is appreciated, forgive me if I have missed something obvious, I'm a beginner in django.
Some of the relevant Models for Meal table:
# models.py
class Meal(models.Model):
id = models.AutoField(primary_key=True)
meal_type = models.ForeignKey(MealType, on_delete=models.CASCADE, null=True, blank=True, related_name='meal_type')
meal_types = models.ManyToManyField(MealType, blank=True)
name = models.CharField(max_length=255, null=True, blank=True)
class MealIngredient(models.Model):
id = models.AutoField(primary_key=True)
meal = models.ForeignKey(Meal, on_delete=models.CASCADE, null=True, blank=True)
user_meal = models.ForeignKey(UserMeal, on_delete=models.CASCADE, null=True, blank=True)
ingredient = models.ForeignKey(Ingredient, on_delete=models.SET_NULL, null=True, blank=True)
amount = models.DecimalField(max_digits=14, decimal_places=4, default=0)
class Ingredient(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255, null=True, blank=True)
state = models.CharField(max_length=255, null=True, blank=True)
unit_price = models.IntegerField(null=True, blank=True)
You need to use ExpressionWrapper:
from django.db.models import DecimalField, ExpressionWrapper, F, Sum
queryset = queryset.annotate(
_price=Sum(
ExpressionWrapper(
F('mealingredient__amount') *
F('mealingredient__ingredient__unit_price'),
output_field=DecimalField()
)
)
)
Related
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.
I am new to django. I wanted to know if it was possible to call a specific field in a model into another model.
The goal is to update a the quantity of a specific product in the database and generate a receipt for it.
Here's the example:
models.py:
class Product(models.Model):
name = models.CharField(max_length=300, null=True)
price = models.FloatField(null=True)
quantity = models.IntegerField(default='0', blank=True, null=True)
class UpdateStock(models.Model):
date = models.DateTimeField(default=timezone.now)
wayBill_Number = models.CharField(max_length=300, null=True)
product_Name = models.ForeignKey(Product.name, null=True, on_delete= models.SET_NULL)
quantity = models.ForeignKey(Product.quantity, null=True, on_delete= models.SET_NULL)
I just want to select the product and the quantity to update in the database but I am unable to do so.
A ForeignKey is supposed to refer to another model, not to one of its fields.
So the first thing you have to do it to edit your UpdateStock model this way :
class UpdateStock(models.Model):
date = models.DateTimeField(default=timezone.now)
wayBill_Number = models.CharField(max_length=300, null=True)
product = models.ForeignKey('Product', null=True, on_delete=models.SET_NULL) # Here
quantity = models.IntegerField(default='0', blank=True, null=True) # Here
So you can bound your product to your stock_update when you create it:
stock_update = UpdateStock.objects.create(..., product=your_product_obj, ...)
And you will then be able to access any product's field from this stock_update:
product = stock_update.product
print(product.quantity)
product.quantity = product.quantity + stock_update.quantity
print(product.quantity)
product.save()
But I strongly advise you to read more about many-to-one relationships in the docs.
I have following models, with many to many table, for which I would like to get first 20 news from every category in single response.
class Category(models.Model):
code = models.CharField(primary_key=True, max_length=45)
name = models.CharField(max_length=200, blank=True, null=True)
is_active = models.TextField(blank=True, null=True) # This field type is a guess.
class Meta:
managed = False
db_table = 'category'
verbose_name_plural = 'Categories'
class News(models.Model):
source_code = models.CharField(max_length=45, blank=True, null=True)
title = models.CharField(max_length=1000, blank=True, null=True)
image = models.CharField(max_length=2000, blank=True, null=True)
link = models.CharField(max_length=1000, blank=True, null=True)
published_at = models.DateTimeField(blank=True, null=True)
scraped_at = models.DateTimeField(blank=True, null=True)
is_active = models.TextField(blank=True, null=True) # This field type is a guess.
categories = models.ManyToManyField('Category', through='NewsCategory')
class Meta:
managed = False
db_table = 'news'
verbose_name_plural = 'News'
class NewsCategory(models.Model):
news_id = models.ForeignKey(News, db_column='news_id', on_delete=models.CASCADE)
category_code = models.ForeignKey(Category, db_column='category_code', on_delete=models.CASCADE)
class Meta:
managed = False
db_table = 'news_category'
unique_together = (('news_id', 'category_code'),)
verbose_name_plural = 'NewsCategory'
My view class looks like this, and here I would like to add some logic to return 20 rows for each category, for example if I have 5 categories it should return 100 news in single request.
class NewsViewSet(viewsets.ModelViewSet):
http_method_names = ['get']
serializer_class = NewsSerializer
def get_queryset(self):
queryset = News.objects.all().order_by('-published_at')
sources = self.request.query_params.getlist('sources')
if len(sources) > 0:
queryset = queryset.filter(source_code__in=sources)
return queryset
The typical way to do this is to use a window function. Django has support for them but I don't think they allow filtering on the output of them. I think this is further complicated by the m2m field. Given it's not too complex and doesn't seem to involve user input, you might just want to use a raw query.
Here's what it might look like:
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY c.code ORDER BY n.published_at DESC) AS row_num
FROM appname_news n
JOIN appname_newscategory nc
ON n.id = c.news_id
JOIN appname_category c
ON nc.category_code = c.code
) sub
WHERE
row_num <= 20
And see here for Django's guide on how to actually implement this in a view:
https://docs.djangoproject.com/en/3.2/topics/db/sql/#executing-custom-sql-directly
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 need to log the transaction of the item movement in a warehouse. I've 3 tables as shown in the below image. However Django response error:
ERRORS:
chemstore.ItemTransaction: (models.E007) Field 'outbin' has column name 'bin_code_id' that is used by another field.
which is complaining of multiple uses of the same foreign key. Is my table design problem? or is it not allowed under Django? How can I achieve this under Django? thankyou
DB design
[Models]
class BinLocation(models.Model):
bin_code = models.CharField(max_length=10, unique=True)
desc = models.CharField(max_length=50)
def __str__(self):
return f"{self.bin_code}"
class Meta:
indexes = [models.Index(fields=['bin_code'])]
class ItemMaster(models.Model):
item_code = models.CharField(max_length=20, unique=True)
desc = models.CharField(max_length=50)
long_desc = models.CharField(max_length=150, blank=True)
helper_qty = models.DecimalField(max_digits=10, decimal_places=4)
unit = models.CharField(max_length=10, blank=False)
def __str__(self):
return f"{self.item_code}"
class Meta:
verbose_name = "Item"
verbose_name_plural = "Items"
indexes = [models.Index(fields=['item_code'])]
class ItemTransaction(models.Model):
trace_code = models.CharField(max_length=20, unique=False)
item_code = models.ForeignKey(
ItemMaster, related_name='trans', on_delete=models.CASCADE, null=False)
datetime = models.DateTimeField(auto_now=False, auto_now_add=False)
qty = models.DecimalField(max_digits=10, decimal_places=4)
unit = models.CharField(max_length=10, blank=False)
action = models.CharField(
max_length=1, choices=ACTION, blank=False, null=False)
in_bin = models.ForeignKey(
BinLocation, related_name='in_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
out_bin = models.ForeignKey(
BinLocation, related_name='out_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
remarks = models.TextField(blank=True)
def __str__(self):
return f"{self.trace_code} {self.datetime} {self.item_code} {dict(ACTION)[self.action]} {self.qty} {self.unit} {self.in_bin} {self.out_bin}"
you have same db_column in two fields so change it
in_bin = models.ForeignKey(
BinLocation, related_name='in_logs', db_column='bin_code_id', on_delete=models.CASCADE, null=False)
out_bin = models.ForeignKey(
BinLocation, related_name='out_logs', db_column='other_bin_code', on_delete=models.CASCADE, null=False) /*change db_column whatever you want but it should be unique*/
If are linked to the same model name, You should use different related_name for each foreign_key filed . here is the exemple :
address1 = models.ForeignKey(Address, verbose_name=_("Address1"),related_name="Address1", null=True, blank=True,on_delete=models.SET_NULL)
address2 = models.ForeignKey(Address, verbose_name=_("Address2"),related_name="Address2", null=True, blank=True,on_delete=models.SET_NULL)
thank you for everyone helped. According to Aleksei and Tabaane, it is my DB design issue (broken the RDBMS rule) rather than Django issue. I searched online and find something similar: ONE-TO-MANY DB design pattern
In my case, I should store in bin and out bin as separated transaction instead of both in and out in a single transaction. This is my solution. thankyou.
p.s. alternative solution: I keep in bin and out bin as single transaction, but I don't use foreign key for bins, query both in bin and out bin for the bin selection by client application.