django admin many-to-many intermediary models using through= and filter_horizontal
There are ways to do this
- As provided by @obsoleter in the comment below : set
QuestionTagM2M._meta.auto_created = True
and deal w/ syncdb matters. Dynamically add
date_added
field to the M2M model ofQuestion
model in models.pyclass Question(models.Model): # use auto-created M2M model tags = models.ManyToMany(Tag, related_name='questions') # add date_added field to the M2M model models.DateTimeField(auto_now_add=True).contribute_to_class( Question.tags.through, 'date_added')
Then you could use it in admin as normal
ManyToManyField
.
In Python shell, useQuestion.tags.through
to refer the M2M model.Note, If you don't use
South
, thensyncdb
is enough; If you do,South
does not like this way and will not freezedate_added
field, you need to manually write migration to add/remove the corresponding column.Customize ModelAdmin:
- Don't define
fields
inside customized ModelAdmin, only definefilter_horizontal
. This will bypass the field validation mentioned in Irfan's answer. - Customize
formfield_for_dbfield()
orformfield_for_manytomany()
to make Django admin to usewidgets.FilteredSelectMultiple
for thetags
field. - Customize
save_related()
method inside your ModelAdmin class, like
- Don't define
def save_related(self, request, form, *args, **kwargs):
tags = form.cleaned_data.pop('tags', ())
question = form.instance
for tag in tags:
QuestionTagM2M.objects.create(tag=tag, question=question)
super(QuestionAdmin, self).save_related(request, form, *args, **kwargs)
- Also, you could patch
__set__()
of theReverseManyRelatedObjectsDescriptor
field descriptor of ManyToManyField fordate_added
to save M2M instance w/o raise exception.
From https://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-intermediary-models
When you specify an intermediary model using the through argument to a ManyToManyField, the admin will not display a widget by default. This is because each instance of that intermediary model requires more information than could be displayed in a single widget, and the layout required for multiple widgets will vary depending on the intermediate model.
However, you can try including the tags field explicitly by using fields = ('tags',)
in admin. This will cause this validation exception
'QuestionAdmin.fields' can't include the ManyToManyField field 'tags' because 'tags' manually specifies a 'through' model.
This validation is implemented in https://github.com/django/django/blob/master/django/contrib/admin/validation.py#L256
if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
raise ImproperlyConfigured("'%s.%s' "
"can't include the ManyToManyField field '%s' because "
"'%s' manually specifies a 'through' model." % (
cls.__name__, label, field, field))
I don't think that you can bypass this validation unless you implement your own custom field to be used as ManyToManyField.
The docs may have changed since the previous answers were posted. I took a look at the django docs link that @Irfan mentioned and it seems to be a more straight forward then it used to be.
Add an inline class to your admin.py
and set the model to your M2M model
class QuestionTagM2MInline(admin.TabularInline):
model = QuestionTagM2M
extra = 1
set inlines
in your admin class to contain the Inline you just defined
class QuestionAdmin(admin.ModelAdmin):
#...other stuff here
inlines = (QuestionTagM2MInline,)
Don't forget to register this admin class
admin.site.register(Question, QuestionAdmin)
After doing the above when I click on a question I have the form to do all the normal edits on it and below that are a list of the elements in my m2m relationship where I can add entries or edit existing ones.