I have 3 models (simplified):
class Product(models.Model):
category = models.ForeignKey('Category', related_name='products', to_field='category_name')
brand = models.ForeignKey('Brand', related_name='products', to_field='brand_name')
class Brand(models.Model):
brand_name = models.CharField(max_length=50)
categories = models.ManyToManyField('Category', related_name='categories')
class Category(models.Model):
category_name = models.CharField(max_length=128)
I want to change a Category in admin to a bunch of products, i have a custom admin function written for that. After that I need to update Brand-Categories Many-to-Many relation to check if that Category is still available for a specific Brand. I have written this function:
def brand_refresh():
brands = Brand.objects.all().prefetch_related('shops', 'categories')
products = Product.objects.select_related('shop', 'brand', 'category')
for brand in list(brands):
for category in brand.categories.all():
if not products.filter(category=category).exists():
brand.categories.remove(category)
for product in list(products.filter(brand=brand).distinct('category')):
if product.category not in [None, category]:
brand.categories.add(product.category)
Seems to me this monstro is working, but it takes 2 hours to loop over all cycles (i have ~220k products, 4k+ brands, and ~500 categories). I there any better way to update M2M relation here? I think .prefetch_related() should help here, but what I have now seems have no effect.
Here's a solution for the first part of your loop:
You should try this on a disposable local copy of your database and check that everything works well before running these in production:
from django.db.models import Count
# get a list of all categories which have no products
empty_categories = Category.objects.annotate(product_count=Count('products')).filter(product_count=0).values_list('id', flat=True)
# delete association of empty categories in all brands
Brand.categories.through.objects.filter(category_id__in=list(empty_categories)).delete()
For the second part, perhaps you can do something like this, though I'm not convinced if it's any faster (or even correct as it is):
for brand in Brand.objects.all():
# get a list of categories of all products in the brand
brand_product_categories = brand.products.all().value_list('category__id', flat=True).distinct()
# get the brand's categories
brand_categories = Category.objects.filter(category__brand=brand).value_list('id', flat=True)
# get elements from a not in b
categories_to_add = set(brand_product_categories) - set(brand_categories)
for category_id in categories_to_add:
brand.categories.add(category_id)
Related
i want to calculate the total sales and display the total price, i dont know how to write the function to do this. I have tried writing a little function to do it in models.py but it working as expected. This is what i want, i have a model named UserCourse which stored all the purchased courses, now i want to calculate and sum up all the price of a single course that was sold.
models.py
class Course(models.Model):
course_title = models.CharField(max_length=10000)
slug = models.SlugField(unique=True)
price = models.IntegerField(default=0)
class UserCourse(models.Model):
user = models.ForeignKey(User , null = False , on_delete=models.CASCADE)
course = models.ForeignKey(Course , null = False , on_delete=models.CASCADE)
date = models.DateTimeField(auto_now_add=True)
and also how do i filter this models in views.py so i can display the total price of courses a particular creator have sold.
Try this approach using aggregate where you filter all UserCourse objects from a particular course, then run an aggregate on the price for that course:
my_course = Course.objects.first()
total_amount_sold = UserCourse.objects.filter(
course=my_course,
).aggregate(total_amount_sold=Sum("course__price"))["total_amount_sold"]
You can use the aggregate functionality in your view:
from django.db.models import Sum
price_total = UserCourse.objects.filter(
course__title="a title"
).aggregate(
aggregate_price = Sum("course__price")
)["aggregate_price"]
To break it down:
First we get filter all the UserCourse objects where the course has a title of "a title"
Then we aggregate them, finding the sum of the course prices for all those courses picked up by the filter
By naming a variable in our Sum expression, the queryset will now have a named value that we can get by the name ['aggregate_price'] and assign to price_total
Now you can pass price_total via context to use in your template.
You can specify other filters to get price totals for different querysets. So if your creator is the user field in your UserCourse model, you can repeat the process with user__username = "my_username" as the filter
I am creating my model in Django and I have a many to many relationship between supplies and van kits. The idea is that an "item" can belong to many "van kits" and a "van kit" can have many " items. I created an intermediary model that will hold the relationship, but I am struggling to figure out a way to relate the quantity in the van kit table to the quantity in the main supplies table. For example, if I wanted to mark an item in the van kit as damaged and reduce the quantity of that supply in the van kit, I would also want to reduce the total count of that supply in the main "supplies" table until it has been replenished. I am thinking that maybe I'll have to create a function in my views file to carry out that logic, but I wanted to know if it could be implemented in my model design instead to minimize chances of error. Here's my code:
class supplies(models.Model):
class Meta:
verbose_name_plural = "supplies"
# limit the user to selecting a pre-set category
choices = (
('CREW-GEAR','CREW-GEAR'),
('CONSUMABLE','CONSUMABLE'),
('BACK-COUNTRY','BACK-COUNTRY')
)
supplyName = models.CharField(max_length=30, blank=False) # if they go over the max length, we'll get a 500 error
category = models.CharField(max_length=20, choices = choices, blank=False)
quantity = models.PositiveSmallIntegerField(blank=False) # set up default
price = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) # inputting price is optional
def __str__(self):
return self.supplyName
class van_kit(models.Model):
supply_name = models.ManyToManyField(supplies, through='KitSupplies',through_fields=('vanKit','supplyName'), related_name="supplies")
van_kit_name = models.CharField(max_length=100)
vanName = models.ForeignKey(vans, on_delete=models.CASCADE)
def __str__(self):
return self.van_kit_name
class KitSupplies(models.Model):
supplyName = models.ForeignKey(supplies, on_delete=models.CASCADE)
vanKit = models.ForeignKey(van_kit, on_delete=models.CASCADE)
quantity = models.PositiveSmallIntegerField(blank=False)
def __str__(self):
return str(self.supplyName)
class Meta:
verbose_name_plural = 'Kit Supplies'
I am fairly new to django, I have to learn it for a class project so if my logic is flawed or if a better way to do it is obvious, please respectfully let me know. I'm open to new ways of doing it. Also, I've read through the documentation on using "through" and "through_fields" to work with the junction table, but I'm worried I may not be using it correctly. Thanks in advance.
One option would be to drop/remove the field quantity from your supplies model and just use a query to get the total quantity.
This would be a bit more expensive, as the query would need to be run each time you want to know the number, but on the other hand it simplifies your design as you don't need any update logic for the field supplies.quantity.
The query could look as simple as this:
>>> from django.db.models import Sum
>>> supplies_instance.kitsupplies_set.aggregate(Sum('quantity'))
{'quantity__sum': 1234}
You could even make it a property on the model for easy access:
class supplies(models.Model):
...
#property
def quantity(self):
data = self.kitsupplies_set.aggregate(Sum('quantity'))
return data['quantity__sum']
I have being playing with this for hours with out find a solution.
I have the following models:
class Category(models.Model):
name = models.CharField(max_length=100)
class Item(models.Model):
name = models.CharField(max_length=250)
category = models.ForeignKey(Category)
Then I filter the Items and the categories related to that item:
items = Item.objects.filter(name=name)
categories = Category.filter(id__in = items.values_list('category__id'))
Now I want to get the number of the items with a category and save that number in a field in categories with annotate
How can I do it?
Try the following code
from django.db.models import Count
items = Item.objects.filter(name=name)
categories = Category.objects.filter(
id__in=items.values_list('category_id')
).annotate(items_count=Count("item"))
for cat in categories:
print cat.name, cat.items_count
for reference: https://docs.djangoproject.com/en/1.11/topics/db/aggregation/
Then I filter the Items and the categories related to that item.
If each item can point to several categories, I think you need ManyToMany field.
Now I want to get the number of the items with a category
category = Category.objects.filter(name=name)
num_items = Item.objects.filter(category=category).count()
I want to create one dynamic field value for my class in Django using PyCharm.
CATEGORY_CHOICES = (
('on','one'),
('tw','two'),
('th','three'),
('fo','four'),
('fi','five'),
)
class art(models.Model):
Title=models.CharField(max_length=300)
Desciption=models.TextField()
Category=models.CharField(max_length=2, choices=CATEGORY_CHOICES)
I want the category field in my class to take more than one option, maybe two or more.
Any help would be appreciated.
If you want one python model to have multiple categories, then you need django ManyToManyField. Basically one model object could have multiple choices, one choice can also belong to multiple models objects:
class Category(models.Model):
category_name = models.CharField(max_length=10, unique=True)
class Art(models.Model):
title = models.CharField(max_length=300)
description = models.TextField()
category = models.ManyToManyField('Category', blank=True)
Note that I put unique=True for category_name to avoid creating duplicate categories.
Something not related, you shouldn't use lower fist in model name, and upper first for field name, that's really BAD naming convention and might confuse others who read your code.
Example:
# create your category in code or admin
one = Category.objects.create(category_name='one')
two = Category.objects.create(category_name='two')
three = Category.objects.create(category_name='three')
# create a new art obj
new_art = Art.objects.create(title='foo', description='bar')
# add category to Art obj
new_art.category.add(one)
new_art.category.add(two)
# category for new art obj
new_art_category = new_art.category.all()
# get only a list of category names
category_names = new_art_category.values_list('category_name', flat=True)
# create another Art obj
new_art2 = Art.objects.create(title="test", description="test")
# assign category to new_art2
new_art2.category.add(two)
new_art2.category.add(three)
Django doc for many to many and python pep8 doc.
I have a system for composing items from parts in certain categories
For instance take the following categories:
1: (Location)
2: (Material)
And the following parts:
Wall (FK=1)
Roof (FK=1)
Roof (FK=1)
Brick (FK=2)
Tile (FK=2)
Wood (FK=2)
To compose these items:
Wall.Brick, Roof.Wood, Wall.Wood
class Category(models.Model):
ordering = models.IntegerField()
desc = models.CharField()
class Part:
name = models.CharField()
category = models.ForeignKey(Category)
class Meta:
unique_together = ('name', 'category')
ordering = ['category','name']
class Item:
parts = ManyToManyField(Part)
def __unicode__(self):
return ".".join([p.name for p in self.parts.all()])
Now the question: how do i order the Items? I'd prefer to have them ordered ascending by the composed name, but dont know how.
One way of doing things could be an extra field for the name, that gets updated on the save() method. That would mean denormalizing the model...
If I understand correctly, sort key do not exist in database, so database cannot sort it (or at least on trivially, like using Django ORM).
Under those conditions, yes - denormalize.
It's no shame. As said, normalized dataset is for sissies...