I have a product model that have a ManyToManyFiled to Category model.
category model has a ForeignKey to itself named parent, I want to add parent categories to product while saving but there is a problem.
it’s my code:
` def save(self, *args, **kwargs):
for cat in self.category.all():
if cat.parent:
self.category.add(cat.parent)
super(Product, self).save(*args, **kwargs)`
it doesn’t work but when I print cat it prints the parent but nothing changed in model.
Do you get any error or stack trace from this, or is this just not appearing to be saved?
How do you know nothing is being changed in the model? (Keep in mind that a many-to-many field assignment does not change either model - the entries are made in the “join table”.)
It might be helpful at this point if you posted the complete model, view and form(s) involved with this.
I checked the model in admin panel, nothing changed.
Product model:
class Product(models.Model):
STATUS_CHOICES = (
('s', 'show'),
('h', 'hide'),
)
title = models.CharField(max_length=150)
slug = models.SlugField(max_length=170, unique=True)
category = models.ManyToManyField(Category)
thumbnail = models.ImageField(upload_to='images/products', default='images/no-image-available.png')
image_list = ImageSpecField(source='thumbnail', processors=[ResizeToFill(400, 200)], format='JPEG',
options={'quality': 75})
image_detail = ImageSpecField(source='thumbnail', processors=[ResizeToFill(1000, 500)], format='JPEG',
options={'quality': 100})
description = models.TextField()
inventory = models.IntegerField()
features = models.ManyToManyField(Feature)
status = models.CharField(max_length=1, choices=STATUS_CHOICES, default='s')
def __str__(self):
return self.title
class Meta:
verbose_name = "product"
verbose_name_plural = "products"
def save(self, *args, **kwargs):
for cat in self.category.all():
if cat.parent:
self.category.add(cat.parent)
return super(Product, self).save(*args, **kwargs)
Category model:
class Category(models.Model):
parent = models.ForeignKey('self', default=None, null=True, blank=True, on_delete=models.SET_NULL,
related_name='children')
title = models.CharField(max_length=40)
slug = models.SlugField()
status = models.BooleanField(default=True)
def __str__(self):
return self.title
class Meta:
verbose_name = "category"
verbose_name_plural = "categories"
ordering = ["parent_id"]
objects = CategoryManager()
How is the first category assigned? (When the Product is first created, self.category.all is going to be an empty set.)
This is where it may be helpful to also see the views and forms involved.
so what can I do to do that ?
Do what? What are you trying to accomplish here?
I have a similar problems and solved with:
def save(self, *args, **kwargs):
super(Product, self).save(*args, **kwarg)
for cat in self.category.all():
if cat.parent:
self.category.add(cat.parent)
super(Product, self).save(*args, **kwarg)
I am not sure why but it worked. I think is because you need first a instance to reference your many to many relation, before iterate over it.
I want to add parents of selected category to product categories.
example :
digital appliance-> None __ Mobile-> digital appliance __ Samsung-> Mobile and…
when choose Samsung for category of a product, I want to add Mobile and digital appliance to category
thanks but it doesn’t work…
So this goes back to my earlier response. How are you selecting a category? What does the view and form being used look like?
for now, I’m doing it from admin panel.
this is an new project and doesn’t have forms or anything else.
So after you have assigned a category, have you verified that the assignment was made in the join table?
in template I made a section for showing categories, it shows here. and in admin panel.
Ok, we need to see all the code that’s part of this.
I have found the problem @smjt2000 .
Edited: Override the save method works, but doesn’t change the way that ManyToMay relations are managed in the Admin Site when save an object..
First /override the save()method of the models doesn’ t work in the Admin Site . Your code works for non Admin Forms and Views.
So you need the follow code in your admin.py file:
class ProductAdmin(admin.ModelAdmin):
model = Product
def save_related(self, request, form, formsets, change):
super().save_related(request, form, formsets, change)
for cat in form.instance.category.all():
form.instance.category.add(cat.parent)
admin.site.register(Product, ProductAdmin)
Try it, it will work, I have manually tested it.
PD: I think this is a anti-DRY behavior of the Admin Site, but that is what we have.
1 Like
Thank you. it’s working. but working just for one parent of each selected category.
for add all parents(parent of parent and…) :
class ProductAdmin(admin.ModelAdmin):
model = Product
def save_related(self, request, form, formsets, change):
super().save_related(request, form, formsets, change)
for cat in form.instance.category.all():
_cat = cat.parent
while _cat:
print(_cat)
form.instance.category.add(_cat)
_cat = _cat.parent
admin.site.register(Product, ProductAdmin)
1 Like
That is a dangerously wrong mischaracterization of the real situation.
Overriding save does work in the Admin. The issue is that saving entries in a ManyToMany relationship does not change the base table - that’s a distinction important to understand.
You are right. @KenWhitesell. I will edit my answer in order to avoid confusions.
Is there a better way or I should not do this?