I've been working through an ordered ManyToManyField widget, and have the front-end aspect of it working nicely:
alt text http://i45.tinypic.com/33e79c8.png
Unfortunately, I'm having a great deal of trouble getting the backend working. The obvious way to hook up the backend is to use a through
table keyed off a model with ForeignKey
s to both sides of the relationship and overwrite the save method. This would work great, except that due to idiosyncrasies of the content, it is an absolute requirement that this widget be placed in a fieldset (using the ModelAdmin fieldsets
property), which is apparently not possible.
I'm out of ideas. Any suggestions?
Thanks!
In regard to how to set up the models, you're right in that a through table with an "order" column is the ideal way to represent it. You're also right in that Django will not let you refer to that relationship in a fieldset. The trick to cracking this problem is to remember that the field names you specify in the "fieldsets" or "fields" of a
ModelAdmin
do not actually refer to the fields of theModel
, but to the fields of theModelForm
, which we are free to override to our heart's delight. With many2many fields, this gets tricky, but bear with me:Let's say you're trying to represent contests and competitors that compete in them, with an ordered many2many between contests and competitors where the order represents the competitors' ranking in that contest. Your
models.py
would then look like this:Hopefully, this is similar to what you're dealing with. Now, for the admin. I've written an example
admin.py
with plenty of comments to explain what's happening, but here's a summary to help you along:Since I don't have the code to the ordered m2m widget you've written, I've used a placeholder dummy widget that simply inherits from
TextInput
. The input holds a comma-separated list (without spaces) of contestant IDs, and the order of their appearance in the string determines the value of their "rank" column in theContestResults
model.What happens is that we override the default
ModelForm
for Contest with our own, and then define a "results" field inside it (we can't call the field "contestants", since there would be a name conflict with the m2m field in the model). We then override__init__()
, which is called when the form is displayed in the admin, so we can fetch any ContestResults that may have already been defined for the Contest, and use them to populate the widget. We also overridesave()
, so that we can in turn get the data from the widget and create the needed ContestResults.Note that for the sake of simplicity this example omits things like validation of the data from the widget, so things will break if you try to type in anything unexpected in the text input. Also, the code for creating the ContestResults is quite simplistic, and could be greatly improved upon.
I should also add that I've actually ran this code and verified that it works.
In case you're wondering, I had ran into this problem myself on a project I've been working on, so most of this code comes from that project. I hope you find it useful.