#property doesn't store value in DB - django

My model is like this:
class Cart(models.Model):
id = models.AutoField(primary_key=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(null=False,blank=False)
total = models.FloatField(null=False,blank=False)
#property
def total(self):
return self.quantity * self.product.price
The total appears in my admin dashboard but when I inspect the db from the shell I don't get the total value, I get the other values:
<QuerySet [{'id': 42, 'product_id': 11, 'quantity': 2}]>
Expected output:
<QuerySet [{'id': 42, 'product_id': 11, 'quantity': 2, 'total': 29.00}]>

The #property decorator is a Python method, your database knows nothing about it. The property will still work in Python code and Django templates, but you won't be able to use DB operations against it like other fields (e.g. using .filter or .values with total).
If you need to query against the total, what you probably want is to use annotations instead.
Just omit the total from your model altogether and you can annotate your queries to have the DB calculate the total on-the-fly:
from django.db.models import ExpressionWrapper, F
queryset = Cart.objects.annotate(
total=ExpressionWrapper(
F("quantity") * F("product__price"), output_field=models.FloatField()
)
)
for cart in queryset:
print(cart.total)
ExpressionWrapper is used to define the output_field (so Django knows what type the result should be) which is needed when multiplying integerfields by floatfields.
If you want queries to always return the annotated total, you can override the model's default manager.
class CartManager(models.Manager):
def with_total(self):
qs = super().get_queryset()
return qs.annotate(
total=ExpressionWrapper(
F("quantity") * F("product__price"), output_field=models.FloatField()
)
)
def get_queryset(self): # override the default queryset
return self.with_total()
class Cart(models.Model):
objects = CartManager() # override the default manager
id = models.AutoField(primary_key=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(null=False, blank=False)

Django models save data once the save() method execuate.
Try this, it works very well. It's a best practice
class Cart(models.Model):
id = models.AutoField(primary_key=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(null=False,blank=False)
total = models.FloatField(null=False,blank=False)
def save(self, *args, **kwargs):
self.total = self.quantity * self.product.price
super(Cart, self).save(*args, **kwargs)

#property decorator is not for saving data in DB.
What the #property decorator does is declare that it can be accessed as a regular property.
This means you can call total as if it were a member your model instead of a function, so like this:
total = cart.total #in views
{{ cart.total }} #in templates
instead of
total = cart.total() #in views
{{ cart.total() }} #in templates
for more details on property, you can refer here
If you still want to store the value in total field, then save it to database instead of returning it in the method.
class Cart(models.Model):
id = models.AutoField(primary_key=True)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(null=False,blank=False)
total = models.FloatField(null=False,blank=False)
#property
def save_total(self):
total = self.quantity * self.product.price
self.total = total
return total
or
you can save the data to total in your views, while your cart is saved in view.py

Related

Django complex filter through ManyToManyField and ForeignKey

I have a number of database tabels that are connected via manytomany and foreign key fields.
from django.db import models
class User(models.Model):
name = models.CharField(max_length=20)
class Bookings(models.Model):
user = user = models.ForeignKey(
User,
blank=True,
null=True,
on_delete=models.SET_NULL)
class Event(models.Model):
bookings = models.ManyToManyField(
Booking,
related_name="event_bookings",
blank=True)
class ScheduleManager(models.Manager):
def for_user(self, user):
"""
Returns a Schedule queryset for a given user object.
Usage:
user= User.objects.first()
Schedule.objects.for_user(user)
"""
qs = self.get_queryset()
#need to extend this to return Schedule qs
return qs
class Schedule(models.Model):
event = models.ForeignKey(
Event,
on_delete=models.CASCADE
)
objects = ScheduleManager()
I would like to query the database to output a Schedule queryset for a given User object by calling Schedule.objects.for_user(User).
I have been playing with a combination of prefetch_related and select_related to no prevail.
I can get hold of the correct qs by using a bunch of chained queries and loops but its not the most elegant and I'm am hitting the db far too many times.
Any help will be appreciated.
You can traverse through the fields by seperating them wih __.
You want to reach User from Schedule, the route is as follows:
Schedule --> Event --> Bookings --> User
So, if you're starting from a Schedule, you can filter on users by doing so:
my_user = get_user_object().objects.get(pk=1)
user_schedules = Schedule.objects.filter(event__bookings__user=my_user)
So if you convert the above to fit in your function, you'll get this:
class ScheduleManager(models.Manager):
def for_user(self, user):
"""
Returns a Schedule queryset for a given user object.
Usage:
user= User.objects.first()
Schedule.objects.for_user(user)
"""
qs = self.get_queryset()
qs = qs.filter(event__bookings__user=user)
return qs

Accessing nested ids in Queryset Django Rest Framework

how can i access the category_id? I want to create a list of similar products based on category. So each time i make a get request f.e products/1052/similarproducts, i want to get all the ProductsInStore of the same category as ProductInStore(id=1052) and exclude the ProductInStore(id=1052), my current code gives me "ecommerce.models.ProductInStore.DoesNotExist: ProductInStore matching query does not exist."
Even though a productInStore of 1052 exists.
class ProductInStore(models.Model):
product = models.ForeignKey('Product', on_delete=models.CASCADE)
class Product(models.Model):
name = models.CharField(max_length=200)
product_category = models.ManyToManyField(EcommerceProductCategory)
class SimilarProductsListApiView(generics.ListAPIView):
queryset = ProductInStore.objects.all()
serializer_class = ProductInStoreSerializer
#products/954/similarproductsr
def get_queryset(self):
product_id = self.kwargs['pk']
category = ProductInStore.objects.values_list('product__product_category', flat=True).get(product_id=product_id)
return ProductInStore.objects.filter(product__product_category=category).exclude(product_id=product_id).all()
Your product_id were id of ProductInStore instance, not Product instance and because their ids most likely vary, you could not get instance
class SimilarProductsListApiView(generics.ListAPIView):
queryset = ProductInStore.objects.all()
serializer_class = ProductInStoreSerializer
def get_queryset(self):
product_in_store_id = self.kwargs['pk']
category = ProductInStore.objects.get(id=product_in_store_id).product.product_category
return ProductInStore.objects.filter(product__product_category=category).exclude(id=product_in_store_id)

getting number of days between twoo dates

I don't figure out how to get the number of days betwen twoo dates:
this is the models I have :
class Reservation(models.Model):
"""A typical class defining a reservation model."""
# Fields
id_reservation = models.BigAutoField(primary_key=True)
start_date = models.DateField(auto_now=False, auto_now_add=False)
end_date = models.DateField(auto_now=False, auto_now_add=False)
client_id = models.ForeignKey(Client, on_delete=models.CASCADE)
chambre_id = models.ForeignKey(Chamber, on_delete=models.CASCADE)
# Metadata
class Meta:
ordering = ['-end_date']
# Methods
def get_absolute_url(self):
"""Returns the url to access a particular instance of MyModelName."""
return reverse('model-detail-view', args=[str(self.id_reservation)])
def __str__(self):
return self.id_reservation
and try to calculate the days between the end_date and the start_date in my generic view:
class ReservationsListView(generic.ListView):
"""ReservationsListView: produce a list of all Reservations """
model = Reservation
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['periode'] = Reservation.end_date-Reservation.start_date
return context
can please help doing this.
You can subtract the two date objects, and then obtain the .days attribute, so:
class Reservation(models.Model):
# …
#property
def days(self):
return (self.end_date - self.start_date).days
This will thus return the difference in days, so that means that if we calculate the difference between today and yesterday, this will result in one day:
>>> (date(2021, 4, 28) - date(2021, 4, 27)).days
1
If you want to take both days into account, you increment the result with 1:
class Reservation(models.Model):
# …
#property
def days(self):
return (self.end_date - self.start_date).days + 1
You can thus retrieve the number of days from a Reservation object with:
my_reservation.days

get dictionary from queryset

I have below query where I want to fetch 'specs' which is a dictionary but the type of envi_dict is a Queryset class. How do I fetch the dictionary from this queryset? Any help is much appreciated.
envi_dict = Environment.objects.values('specs')
Result
<QuerySet [({u'CPU Model': u'Dell', u'RAM': 1000, u'CPU': 400},), ({u'CPU Model': u'Dell', u'RAM': 1000, u'CPU': 400},)]>, <class 'django.db.models.query.QuerySet'>, )
I tried Environment.objects.filter(title=item.title).values('specs') and also Environment.objects.get('specs') but I am still getting a queryset.
Edit: Below is models.py
class CommonModel(models.Model):
author = models.ForeignKey('auth.User',)
title = models.CharField(max_length=400)
comments = models.TextField(blank=True)
requirements = JSONField(default = {})
specs = JSONField(default= {})
created_date = models.DateTimeField(default=timezone.now)
updated_date = models.DateTimeField(blank=True, null=True)
class Meta:
abstract = True
def update(self):
self.updated_date = timezone.now()
self.save()
def __str__(self):
return self.title
class Estimate(CommonModel):
gp_code = models.TextField(default='Unknown')
inputs = models.TextField(blank=True)
...
def __str__(self):
return self.title
class Environment(CommonModel):
estimate = models.ForeignKey(Estimate,related_name='environments')
logic = PythonCodeField(blank=True, null=True)
...
Build a list of dicts with model instance.title as key and the specs as value by iterating over all Environment model instances.
[{i.title: i.specs} for i in Environment.objects.all()]
Use Django model_to_dict
If you need to convert a single queryset into a dictionary, use
model_to_dict.
If you need to convert all querysets into a dictionary use Model.objects.values() or django.core.serializer
using model_to_dict
from django.forms.models import model_to_dict
qs = Environment.objects.filter(title=item.title)
if qs.exists():
qs_dict = model_to_dict(qs) # {id:1,'estimate':'some-estimate-data','logic':'some-logic-data'}
# Do something here with qs_dict
else:
# qs=None -- do some here when qs is not found

How to bind custom function to model field?

Say this is my simple models.py
class Order(models.Model):
quantity = models.IntegerField()
item_price = models.FloatField()
I'd like to have a function to calculate the total price:
def calc_total(self):
return self.quantity * self.item_price
Now, how do I create model field total_price so it gets populated automatically in database? I know I can use calc_total method in template but I need this in db also.
Override the save method in your model to set the value each time the model is saved:
class Order(models.Model):
quantity = models.IntegerField()
item_price = models.FloatField()
total_price = models.FloatField()
def calc_total(self):
return self.quantity * self.item_price
def save(self, *args, **kwargs):
self.total_price = self.calc_total()
super(Order, self).save(*args, **kwargs)