Django: access model instances - python

I have a model called OrderItem
class OrderItem(models.Model):
customer = models.ForeignKey(
User, on_delete=models.SET_NULL, blank=True, null=True)
product = models.ForeignKey(
Product, on_delete=models.SET_NULL, blank=True, null=True)
order = models.ForeignKey(
Order, on_delete=models.SET_NULL, blank=True, null=True)
quantity = models.IntegerField(default=0, null=True, blank=True)
date_added = models.DateTimeField(auto_now_add=True)
and a model called Order
class Order(models.Model):
customer = models.ForeignKey(
User, on_delete=models.SET_NULL, blank=True, null=True)
date_ordered = models.DateTimeField(auto_now_add=True)
complete = models.BooleanField(default=False, null=True, blank=False)
transaction_id = models.CharField(max_length=200, null=True)
Now in my views.py I have an OrderItem model order instance. And in my Order model I need to set its order instance to complete. I'm trying something as follows:
orderID = OrderItem.objects.filter(order_id=4)
#lets say there are more than one order_id with the value of 4
for order in orderID:
order = Order.objects.get(id=orderID)
order.complete=True
order.save()
this code gives me:
ValueError: The QuerySet value for an exact lookup must be limited to one result using slicing.
How to fix this?

orderID in your example can be list, possibly of size greater than one.
This makes the following to fail:
orderID=OrderItem.objects.filter(order_id=4)
#lets say there are more than one order_id with the value of 4
for order in orderID:
order = Order.objects.get(id=orderID) # FAIL: list passed as ID
order.complete=True
order.save()
You should pass order as an argument to .get() function or use related model, like
order_items=OrderItem.objects.filter(order_id=4)
for order_item in order_items:
order = order_item.order
order.complete=True
order.save()

orderID = OrderItem.objects.filter(order_id=4)
Here you are selecting all OrderItem instances related to an Order with id=4.
Then you are cycling on all OrderItem objects and you are trying to set a flag complete=True on the order associated to each OrderItem.
But the query is constructed such as all OrderItem instances will be related to the same Order. So the following will suffice and it's a single query on the DB:
Order.objects.filter(id=4).update(complete=True)

Related

Django sales campaign model with list of foreign keys that can only appear in one sales campaign?

I want to create a sales campaign which stores a list of books (Items). But the book should only be applied to one sales campaign — ie. it should not be able to appear in two campaigns.
I have the following model:
class Campaign(models.Model):
campaign_name = models.CharField(max_length=50, null=False, blank=False, default='Special Offer')
included_items = models.ManyToManyField(Item, blank=True)
active = models.BooleanField(default=True)
fixed_price = models.DecimalField(max_digits=6, blank=True, null=True, decimal_places=2)
But I think the included_items field might be of the wrong field type. From reading other questions and the Django manual, I think I may be approaching this back to front. Perhaps I should be tackling this from the Item model instead? (Shown below for reference)
class Item(models.Model):
sku = models.CharField(
max_length=10, null=True, blank=True,
default=create_new_sku)
title = models.CharField(max_length=254)
genre = models.ManyToManyField('Genre', blank=True)
author = models.ManyToManyField('Author', blank=True)
description = models.TextField()
age_range = models.ManyToManyField(
'Age_range')
image_url = models.URLField(max_length=1024, null=True, blank=True)
image = models.ImageField(null=True, blank=True)
price = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)
discount = models.DecimalField(max_digits=2, decimal_places=0, default=0)
set_sale_price = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)
original_sale_price = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True)
final_price = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=False, editable=False)
If each item can only belong to one campaign you should be using a ForeignKey to campaign in the Item model to create your many-to-one relationship
Actually your design is fine.
Using a ForeignKey from #Henty's answer on the Item model still runs into issues where if a campaign becomes inactive (active=False), the Item model needs to nullify the ForeignKey to allow other campaigns to offer it. In addition, an Item model's data should only have information about that instance. To me I think it's weird having a foreign key to a campaign in an Item model since that data is irrelevant to a book. You probably want to read up on data normalization.
That being said, you should manually specify your many-to-many intermediary table as you'll need to apply a unique constraint in it so that only one active campaign can offer the book.
This intermediary table will also have fields such as the discount or sale_price of the book since different campaigns can offer different discounts/prices. I'm assuming price in your Item model is the base retail price on your book so discount, set_sale_price, original_sale_price, and final_price are not needed - again, refer back to data normalization.
In addition, you will need to copy over the active field from your Campaign instance to your intermediary table because it will be needed for the unique constraint. This is done by overriding the save method on your intermediary table to copy over the active value from the Campaign instance.
You'll also have to override the save method on your Campaign model to update the active field on your intermediary instances when you activate/deactivate a campaign.
class Campaign(models.Model):
campaign_name = ...
included_items = models.ManyToManyField(
Item,
through='CampaignItem',
through_fields=('campaign', 'item')
)
active = ...
fixed_price = ...
def save(self, *args, **kwargs):
# pre-save, update associated campaign-item instances
CampaignItem.objects.filter(campaign=self).update(active=self.active)
super().save(*args, **kwargs)
class CampaignItem(models.Model):
campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
active = models.BooleanField(editable=False)
discount = ...
sale_price = ...
# add any other relevant fields related to this campaign item
class Meta:
# add constraint where item can only be in one active campaign
constraints = [
UniqueConstraint(fields=['item'],
condition=Q(active=True),
name='unique_active_item')
]
def save(self, *args, **kwargs):
# copy over the `active` value from campaign
self.active = self.campaign.active
super().save(*args, **kwargs)

How to join tables with O2M and M2M relationship?

I have the following related data models
class Cart(models.Model):
products = models.ManyToManyField('assortment.Product', through='CartProduct')
order = models.OneToOneField('Order', on_delete=models.CASCADE, related_name='order_cart', null=True)
user = models.ForeignKey('account.Profile', on_delete=models.CASCADE, related_name='user_carts', blank=True, null=True)
class CartProduct(models.Model):
product = models.ForeignKey('assortment.Product', related_name='product_cartproducts', null=True, on_delete=models.SET_NULL)
cart = models.ForeignKey('Cart', related_name='cart_cartproducts', null=True, on_delete=models.SET_NULL)
count = models.IntegerField(blank=False, null=False, default=1)
class Order(models.Model):
pay_date = models.DateField(auto_now_add=True)
is_paid = models.BooleanField(default=False)
My code below gives an error: invalid argument "products" in prefetch_related:
Order.objects.all().select_related('order_cart').prefetch_related('products')
How can I join all three models in one request?
The query builder continues with the original table (Order), thus you have to specify the fields relative to that or relative to the previously mentioned field. Try one of the following:
'order_cart__products'
'order_cart__cart_cartproducts'
(Notice the double underscore.)

Queryset foreign key models

I have two models below which one of the models inherits a foreign key from the model
class Services(models.Model):
name = models.CharField(max_length=200, null=True)
price = models.FloatField(null=True)
class Order(models.Model):
customer = models.ForeignKey(Customer, null=True, on_delete = models.SET_NULL)
service = models.ForeignKey(Service, null=True, on_delete = models.SET_NULL)
I want to get the value of the total value of orders per service (per instance)
So for example
if the id = 1 of service has made orders of £10, £33, etc. I would like to get the total value of that of that service based on how many orders it has been made.
So far I have made an queryset below
Order.objects.all().values_list('service__price')
Which provides me the list of prices, but I would like the total per instance of service.
How would one achieve this?
Your Question is wrong due to the wrong model design.
If you want to have a service but with different prices you need to put price on you order table.
Change your models to :
class Service(models.Model):
name = models.CharField(max_length=200, null=True)
class Order(models.Model):
customer = models.ForeignKey(Customer, null=True, on_delete = models.SET_NULL)
service = models.ForeignKey(Service, null=True, on_delete = models.SET_NULL, related_name='orders')
price = models.FloatField(null=True)
Then you can query your sum of prices for one service:
from django.db.models import Sum
s = Service.objects.get(id=1)
s.orders.all().aggregate(Sum("price"))

filter manytoone relationship, exclude childs on condition

I have a one to many relationship with category and products, category and product have active fields, if any of them are not active I want to exclude them from list.
categories = Category.objects.filter(is_active=True)
But now category can have many products, and some of them are inactive, how could I filter and exclude inactive products from all of categories?
Models:
class Category(MPTTModel):
name = models.CharField(max_length=50, blank=False, unique=True)
is_active = models.BooleanField(default=True)
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children', db_index=True)
class Product(models.Model):
name = models.CharField(max_length=50, blank=False, unique=True)
is_active = models.BooleanField(default=True)
category = TreeForeignKey('Category', on_delete=models.CASCADE, null=True, blank=True, db_index=True)
If you need to filter related pruducts you can use prefetc_related with Prefetch object:
from django.db.models import Prefetch
categories = Category.objects.filter(is_active=True).prefetch_related(Prefetch('product_set', queryset=Produc.objects.filter(is_active=True)))
in this case for each category from categories this code
category.product_set.all()
will return only active product. Moreover this queryset will not hit DB, since related product will be cached by first query.

Django models, set unqiue attribute for M2M field per object

Currently, have a database with an Item table and a Stock table. There is a many to many relationship between the two. A single item object can have many sizes. The next step is to assign an 'inStock' options to the item per size.
Any thoughts on acheiving this?
Current models.py
class Stock(models.Model):
size = models.CharField(max_length=30)
stock = models.BooleanField(default=False)
def __str__(self):
return self.size
class Item(models.Model):
title = models.CharField(max_length=250, null=True, unique=True)
price = models.DecimalField(max_digits=8, decimal_places=2)
aw_product_id = models.CharField(max_length=11, null=True) # Removed because multiple products has similar identifer
url = models.URLField(max_length=250) # Removed 'unique=True'as the aw_prod_id will throw an integrity error
image = models.CharField(max_length=1000)
retailer = models.CharField(max_length=250)
category = models.CharField(max_length=100)
featured = models.CharField(max_length=10, default='NO')
gender = models.CharField(max_length=100, null=True)
sizes = models.ManyToManyField(Stock)
uniq_id = models.CharField(max_length=11, null=True, unique=True) # Removed because multiple products has similar identifer
def __str__(self):
return self.title
You can use the through argument to ManyToManyField to specify another model to use for the relationship, with additional fields instead of the autogenerated model that django creates by default.

Categories