# http://blog.abiss.gr/mgogoulos/feed/entries/atom 2009-01-10T06:53:15-08:00 Apache Roller (incubating) http://blog.abiss.gr/mgogoulos/entry/many_to_many_relationships_and django:many-to-many relationships and admin markos 2008-10-04T12:10:28-07:00 2008-10-04T12:10:28-07:00 <p>A few weeks ago I started playing aroung with <a href="http://www.djangoproject.com/" title="django Web framework">django</a> Web framework in order to create some cool web applications. I was quite lucky as these days django version 1 was released, full of new functionalities and goodies that will definitely make more django sites appear!<br /><br />Learning django proved to be an easy going experience, as most things are quite well documented and there's a healthy and enthusiastic community willing to help. I'm writing this post to assist other people face the following problem: <b>how to enable edits on both sides on the admin site</b>, for ManyToManyField models. On <a href="http://groups.google.com/group/django-users/" title="django-user">django-user</a> I see it's quite a frequent question over there, in fact I have myself posted for this issue before I resulted in this solution!<br /></p><p><img hspace="0" vspace="0" border="0" align="baseline" alt="django Web framework!" src="http://blog.abiss.gr/mgogoulos/resource/django.png" style="width: 400px; height: 182px;" /><br /></p> <p>A few weeks ago I started playing aroung with <a title="django Web framework" href="http://www.djangoproject.com/">django</a> Web framework in order to create some cool web applications. I was quite lucky as these days django version 1 was released, full of new functionalities and goodies that will definitely make more django sites appear!<br /><br />Learning django proved to be an easy going experience, as most things are quite well documented and there's a healthy and enthusiastic community willing to help. I'm writing this post to assist other people face the following problem: <b>how to enable edits on both sides on the admin site</b>, for ManyToManyField models. On <a title="django-user" href="http://groups.google.com/group/django-users/">django-user</a> I see it's quite a frequent question over there, in fact I have myself posted for this issue before I resulted in this solution!</p><p><img hspace="0" vspace="0" border="0" align="baseline" alt="django Web framework!" src="http://blog.abiss.gr/mgogoulos/resource/django.png" style="width: 400px; height: 182px;" /><br /><br />Let's start with an example. Say you have two simple models, <b>Software</b> and <b>Category</b>. A software can belong to many categories, and a category can have many software, so you have a Many To Many relationship:<br /><br /><font color="#0003ff">#models.py</font><br /><font color="#ff0000">class</font> Software(models.Model):<br />&nbsp;&nbsp;&nbsp; name = models.CharField(max_length=<font color="#fc00ff">50</font>)<br />&nbsp;&nbsp;&nbsp; categories = models.ManyToManyField(Category, blank=<font color="#fc00ff">True</font>)<br /><br /><font color="#ff0000">class</font> Category(models.Model):<br />&nbsp;&nbsp;&nbsp; name = models.CharField(max_length=<font color="#fc00ff">50</font>)<br /><br /><br />Now if you register these models on admin.py and visit the admin panel, when you create (or edit) a Software object, you get categories it belongs to, and can add the object to more categories, or remove it from existing ones. Nothing unusual here, just the powerful and lovely django admin interface :)<br /><br /><i>On the other side, when you create/edit a Category object, you might also want to add what Software belongs there, or remove existing entries.</i> This is not possible at the moment out of the box, because you can have the objects of the ManyToMany relationship only on the side that contains the actual reference. If you're working on the django InteractiveConsole you can add/remove software objects on a category object (in this example) through the attribute <b>software_set.all()</b>. So I guess for django newcomers it might be frustrating why you can't do it on the admin, on the other side of the Many To Many relationship (at least it has been for myself)!<br /><br />Since the model has all the information we need for a Many To Many relationship, we only have to find a way to pass it to the related form. In order to do so, we will use a forms.ModelForm to build the form for our model and add a <b>forms.ModelMultipleChoiceField</b> that represents the reverse relation. <br /><br /><br />In our Software-Category example this is:<br />&nbsp; <br /><font color="#0003ff">#admin.py<br />#set up ModelForm for Category</font><br /><font color="#ff0000">class</font> CategoryAdminForm(forms.ModelForm):<br />&nbsp;&nbsp;&nbsp; software_set = forms.ModelMultipleChoiceField(label=<font color="#fc00ff">'...'</font>, queryset=Software.objects.all(), required=<font color="#fc00ff">False</font>, help_text=<font color="#fc00ff">'...'</font>)<br /><br />&nbsp;&nbsp;&nbsp; <font color="#ff0000">class</font> Meta:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; model = Category<br /><br /><font color="#0003ff">#set up ModelAdmin for Category</font><br /><font color="#ff0000">class</font> CategoryAdmin(admin.ModelAdmin):<br />&nbsp;&nbsp;&nbsp; form = CategoryAdminForm<br /><br />&nbsp;&nbsp;&nbsp; fields = [<font color="#fc00ff">'name'</font>, <font color="#fc00ff">'software_set'</font>]<br /><br />&nbsp;&nbsp;&nbsp; <font color="#ff0000">def</font> save_model(<font color="#00fffd">self</font>, request, obj, form, change):&nbsp;&nbsp; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj.software_set.clear()<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#ff0000">for</font> software <font color="#ff0000">in</font> form.cleaned_data[<font color="#fc00ff">'software_set'</font>]:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj.software_set.add(software)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj.save()<br /><br />&nbsp;&nbsp;&nbsp; <font color="#ff0000">def</font> get_form(<font color="#00fffd">self</font>, request, obj=<font color="#fc00ff">None</font>, **kwargs):<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#ff0000">if</font> obj:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#00fffd">self</font>.form.base_fields[<font color="#fc00ff">'software_set'</font>].initial = obj.software_set.all()<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#ff0000">return</font> <font color="#00fffd">super</font>(CategoryAdmin, <font color="#00fffd">self</font>).get_form(request, obj)<br /><br /><br /><font color="#0003ff">#finally register Category</font><br />admin.site.register(Category, CategoryAdmin)<br /><br /><b>save_model()</b> is called when Save button is pressed and makes sure we save whatever values we select. <br /><b>get_form()</b> sets the initial values for our custom widget with the existing values (if there are any). If we don't set the initial values, the widget will appear with no values selected, so depending on our code in save_model() we might lose any existing values! <br /><br />Thus we can now edit many-to-many relationships from both sides on the admin, which is pretty handy in some cases!<br /><br />As I don't think this is the only solution, I would be glad to hear of others!<br /><br />&nbsp;</p>