I have 2 Models Product and Category with - ManyToMany Relation Product-Category and self ForeignKey for Category
class Category(models.Model):
parent = models.ForeignKey('self', blank=True, null=True, verbose_name='parent category', on_delete=models.CASCADE)
class Product(models.Model):
categories = models.ManyToManyField(Category)
name = models.CharField(max_length=255)
and the Product creation form:
class ProductModelForm(ModelForm):
class Meta:
model = Product
fields = ['categories', 'name', 'short_description', 'description']
widgets = {
'categories': MyWidget,
}
By default ManyToMany becomes ModelMultipleChoiceField and coresponds to SelectMultiple Widget.
I'm creating a new widget inheriting from SelectMultiple.
My issue is that in all widgets by default Django send from the record just the value defined inside def __str__
on the Model. In case of Select also pk.
(None, [{'name': 'categories', 'value': 7, 'label': 'Category 2', 'selected': True, 'index': '1', 'attrs': {'selected': True}, 'type': 'select', 'template_name': 'django/forms/widgets/select_option.html'}]
I need to have in the Widget also other information like the parent value.
Off course I can make another query and get the data from the database, but that means I have 2 queries one done by me and one by Django.
Is there any way to make Django To send all record data to the widget instead of just what was defined in __str__
?
Changing __str__
is not an option because it use also in other places and in admin.
That's not very easy: A ModelChoiceField
(and ModelMultipleChoiceField
) takes a queryset
parameter that by default is the model's default_manager
(Category.objects.all()
). The queryset is consumed when the choices
are produced for the widget, using the ModelChoiceIterator
. That indeed only returns a 2-tuple of the object's id
and label
for each object in the queryset.
So to make sure there's only one query to the database you'd have to subclass ModelMultipleChoiceField
to use a subclass of ModelChoiceIterator
which returns a 3-tuple (or more) with the parameters you need. Then make sure you also subclass the __init__()
method of ChoiceWidget
(in your case SelectMultiple
) to split the tuple again into a 2-tuple to assign to self.choices
and the other values to set as widget attributes (self.attrs
) so you can use them in your template.
Not sure this is worth the optimisation you get, especially if you use a good caching mechanism you wouldn't actually hit your db twice if you perform Category.objects.all()
two times.