How to update m2m connection in overloaded save - python

I have library project. i have book and order model. the user can order many books at once, bu i have error.
this is my code ->
book model ->
class Book(models.Model):
name=models.CharField(max_length=100, verbose_name=_('name'))
Condition = models.IntegerField(default=10,verbose_name=_('condition'))
author=models.CharField(max_length=100, verbose_name=_('author'))
quantity=models.IntegerField(default=100, verbose_name=_('quantity'))
branch = models.ManyToManyField(Branch, verbose_name=_('branch'))
def __str__(self):
return self.name
class Order(models.Model):
book=models.ManyToManyField(Book, verbose_name=_('book'))
user=models.ForeignKey(User,on_delete=models.CASCADE, verbose_name=_('user'))
branch=models.ForeignKey(Branch,on_delete=models.CASCADE, verbose_name=_('branch'))
start_date=models.DateField(verbose_name=_('start'))
end_date=models.DateField(verbose_name=_('finish'))
def __str__(self):
return str(self.user)
def save(self, *args, **kwargs):
today = datetime.datetime.now()
if not self.pk:
# self.book.quantity -= 1
for i in self.book:
i.quantity -= 1
i.save()
self.end_date = today + datetime.timedelta(days=14)
super(Order, self).save(*args, **kwargs)
when i save i have this error ->"<Order: d#gmail.com>" needs to have a value for field "id" before this many-to-many relationship can be used.

Are you using .add() method before you save the Order model?
If so then try saving the model object first before adding m2m objects.
For eg: order = Order(user=<user>, branch=<branch>, start_date=<start_date>, end_date=<end_date>) then order.save() and finally order.book.add(<book1>, <book2>) and so on.
Also i see in save() method you have used for i in self.book but you need to use self.book.all()

Related

how to autoupdate category by the amount of products in django model

I have a model of the category with title and count and I also have another model called Products which has category as one of its foreign-key. How can I auto-update the category count field by the number of products that comes under the same category?
class Category(models.Model):
title = models.CharField(max_length = 20)
count = models.IntegerFIeld()
class Product(models.Model):
title = models.CharField(max_length = 20)
category = models.ForeignKey(Category, on_delete = models.CASCADE)
You can either override save method or write a signal to auto-update the field, i.e.:
class Category(models.Model):
def save(self, *args, **kwargs):
self.count = self.product_set.count()
super().save(*args, **kwargs)
or with signals:
from django.dispatch import receiver
#receiver(post_save, sender=Category)
def category_post_save(sender, instance, *args, **kwargs):
instance.count = instance.product_set.count()
instance.save()
But I would suggest you using a calculated property:
class Category(models.Model):
...
#property
def count(self):
return self.product_set.count()
If you want to keep the field count I recommend you use signals. You should use post_signal and pre_signal (to get the previous category from a query) on the model Product.
Here are the signals:
#receiver(signals.pre_save, sender=Product)
def product_pre_save_signal(sender, instance, *args, **kwargs):
if instance.id:
previous_instance = Product.objects.get(id=instance.id)
if previous_instance.category != instance.category:
instance.previous_category = previous_instance.category
#receiver(signals.post_save, sender=Product)
def product_post_save_signal(sender, instance, created, *args, **kwargs):
if not created:
if hasattr(instance, "previous_category"):
if instance.previous_category:
instance.previous_category.count -= 1
instance.previous_category.save()
if instance.category:
instance.category.count += 1
instance.category.save()
else:
if instance.category:
instance.category.count += 1
instance.category.save()
Another recommendation for this situation is, make it a read-only field(add editable=False to the field declaration), so no one can edit it from Django admin or serializer.
But I highly recommend you remove the field because it's hard to keep this field sync with the table Product, so calculate the value each time.
You can have almost any query that is related field count over Category or Product, so don't worry:)

how to get ManyToMany field data in model save method

class Product(models.Model):
varient_property = models.ManyToManyField(to='store.AttributeValue', blank=True)
def save(self, *args, **kwargs):
super(Product, self).save(*args, **kwargs)
# here varient_propery is manytomany field
# here i am saving this from django admin panel
print(self.varient_property.all())
many to many class
class AttributeValue(models.Model):
value = models.CharField(max_length=30)
available = models.BooleanField(default=True)
def __str__(self):
return self.value
the print statement returns none
how can I get many to many data after my model gets save?
I want all set many-to-many relations.
thanks in advance.
You can try this:
If you want all the records from AttributeValue model you need to use the all() method on the class not on the instance that you obtain from varient_property in Product model.
class Product(models.Model):
varient_property = models.ManyToManyField(to='store.AttributeValue', blank=True)
def save(self, *args, **kwargs):
super(Product, self).save(*args, **kwargs)
print(AttributeValue.objects.all()) #like this

How to override how many-to-many saves on 'through' field in Django?

Summary
I want to auto-increment an order field on Item defined with m2m fields within a Set (bad name I know) so I can easily change their order. The many-to-many field is defined with a through model called SetMeta, which contains just item, set, and the new field order. I've tried over riding save() but this is never called. How can I programatically set order to something other than a default each time an item is added to set?
Detail
Every time a new item is added to set, I want to set it's order to len(set.items.all()). To do this, I've redefined save() on SetMeta, but it's never called. What do I have to override to ensure this works the way I'd like?
models.py
class Item(models.Model, AdminVideoMixin):
title = models.TextField(max_length=5000)
....
class Set(Item):
items = models.ManyToManyField(Item, related_name='in_sets', through='SetMeta', max_length=5000,)
def get_absolute_url(self):
return reverse('curate:set_detail',kwargs={'slug':self.slug})
def get_queryset(self):
return self.items.all().order_by('-itemOrder__order')
def __str__(self):
return self.title
def auto_increment_order(self):
print('IS THIS WORKING') <----never prints.
length_of_set = len(self.set.items.all())
return length_of_set
class SetMeta(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='itemOrder', max_length=5000)
set = models.ForeignKey(Set, on_delete=models.CASCADE, related_name='SetOrder', max_length=5000)
order = models.IntegerField(default=0, null=True,)
def up(self):
self.order += 1
self.save()
print(self.order)
def down(self):
self.order -= 1
self.save()
print(self.order)
def save(self, *args, **kwargs):
if self.order == 0:
self.order = auto_increment_order(self)
super(SetMeta, self).save(*args, **kwargs)
print(f"{self.item.title} order is {self.order}") < ---- this never fires.
It will be fired if you create the object like this:
print(set.items.all().count()) #
item = Item.objects.first()
set = Set.objects.first()
icount = set.items.count() + 1
SetMeta.objects.create(item=item, set=set, order=icount)
print(set.items.all().count())
Please check the documentation for more details.

django: limiting model instance based on foregin key

I have a models like these
class Campaign(models.Model):
campaign_name= models.CharField(max_length=30)
user=models.ForeignKey(User)
rows=models.IntegerField(default=3)
columns=models.IntegerField(default=1)
def __str__(self): # __unicode__ on Python 2
return self.campaign_name+": "+self.campaign_desc
def save(self, *args, **kwargs):
print("Doing Custom Task ")
super(Campaign, self).save(*args, **kwargs) # Call the "real" save() method.
class Item(models.Model):
campaign=models.ForeignKey(Campaign)
item_name=models.CharField(max_length=70)
item_ID=models.CharField(max_length=400)
def __str__(self):
return self.item_name
I have registered Campaign in admin using admin.site.register(Campaign,CampaignAdmin) and want the number of items in each campaign to be rows X columns of campaign (Foreign key).
Q1) If I validate it using the save override method in the place of print("Doing Custom Task ") , I couldn't save the instance when it's created.
Q2) It would be also nice to have the number of items to be filled to show up appropriately. Now I have
class ItemInline(admin.TabularInline):
model = Item
#extra = 4
in admin.py Basically I want the extra parameter to be rows X columns
If you want to do some custom validation, you'd better do it via forms, or (as a last resort), using the Model.clean* methods family.
class Campaign(models.Model):
def clean(self):
if self.pk:
if self.item_set.count() > 5 # Or whatever number you need
raise ValidationError(_('Too much items for me.'))
Override the get_extra() method for your inline:
class ItemInline(admin.TabularInline):
extra = 4
def get_extra(self, request, obj=None, **kwargs):
if obj:
return obj.rows * obj.columns
return self.extra

Can a Django model field's default value be defined by a function dependent on a foreign parent model?

I'm trying to have the default value of Report's fee be based on a parent model's attributes. I don't want to do this in save(), because the field needs to be presented to the user if they choose to override the value before saving.
Here are the three methods I've tried, in addition to passing only function pointers (i.e. without ()). The errors are raised when I run python manage.py shell.
#1
from django.db import models
class Job(models.Model):
veryImportant = models.IntegerField()
def get_fee(self):
return 2 * self.veryImportant
class Report(models.Model):
job = models.ForeignKey(Job)
overridableFee = models.DecimalField(default=job.get_fee(), max_digits=7, decimal_places=2)
#gives...
#AttributeError: 'ForeignKey' object has no attribute 'get_fee'
#2
from django.db import models
class Job(models.Model):
veryImportant = models.IntegerField()
class Report(models.Model):
job = models.ForeignKey(Job)
overridableFee = models.DecimalField(default=self.set_fee(), max_digits=7, decimal_places=2)
def set_fee(self):
overridableFee = 2 * self.job.veryImportant
#gives...
#NameError: name 'self' is not defined
#3
from django.db import models
class Job(models.Model):
veryImportant = models.IntegerField()
def get_fee():
return 2 * veryImportant
class Report(models.Model):
job = models.ForeignKey(Job)
overridableFee = models.DecimalField(max_digits=7, decimal_places=2)
def __init__(self, *args, **kwargs):
self.overridableFee = self.job.get_fee()
super(models.Model, self).__init__(*args, **kwargs)
#gives...
#TypeError: object.__init__() takes no parameters
The error with #3 could be that I just have no idea how to override init properly. I copied something out of another answer and gave up after it didn't work.
If none of these will work, I can always revert to just setting the fee after I create each Report in the view, but I'd rather do it automatically when the Report is created, for sanity.
EDIT:
Ended up going with #3, fixed with Yuji's answer and modified to make sure any possible fee set from the shell overrides what job.get_fee() wants.
def __init__(self, *args, **kwargs):
super(Report, self).__init__(*args, **kwargs)
if self.overridableFee == None and self.job and not self.pk:
self.overridableFee = self.job.get_fee()
Your last example could potentially work with some work:
First all, you need to __init__ on your class, not models.Model
You need to set your attribute after the model has been initialized
You need check if the model has been saved, or else your model will revert to the overridable fee every time you load it.
-
class Job(models.Model):
veryImportant = models.IntegerField()
def get_fee():
return 2 * veryImportant
class Report(models.Model):
job = models.ForeignKey(Job)
overridableFee = models.DecimalField(max_digits=7, decimal_places=2)
def __init__(self, *args, **kwargs):
super(Report, self).__init__(*args, **kwargs)
if not self.id:
self.overridableFee = self.job.get_fee()

Categories

Resources