Last active
August 29, 2015 13:58
-
-
Save mkoistinen/10019911 to your computer and use it in GitHub Desktop.
Effectively adds a "ForeignKey" to a 3rd-party application's model (Post in this example) without monkey-patching the code. This is accomplished by adding a ManyToMany field instead on the other side of the desired relationship on the Item model (in this example), then, removing it from Item's admin and adding a ModelChoice field and Select widg…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# Django 1.6 | |
from django.contrib import admin | |
from django.forms import ModelChoiceField | |
from django.forms.models import ModelForm | |
from blog.models import Post | |
from .models import Item | |
class MyPostAdminForm(ModelForm): | |
''' | |
We're forcing Admin to display the opposite side of the M2M: | |
items.Item.posts as an editable [single] ModelChoice field. Remember, | |
Post.items is already present as the related_name. | |
''' | |
items = ModelChoiceField( | |
queryset=Item.objects.all(), | |
# The default widget for a ModelChoiceField is Select. Let it ride. | |
required=False, | |
) | |
# Let's re-label this. Leaving the plural form would be rather confusing. | |
items.label = 'item' | |
def __init__(self, *args, **kwargs): | |
''' | |
If this post exists yet, set the initial item. Remember, we're | |
using an M2M to fake a ModelChoice field, so we only need to choose a | |
single item, if any. We'll just grab the first one we find. | |
''' | |
super(MyPostAdminForm, self).__init__(*args, **kwargs) | |
if self.instance.pk and self.instance.items.count(): | |
self.fields['items'].initial = self.instance.items.all()[0] | |
def save(self, *args, **kwargs): | |
''' | |
Save the selected item on this post in the Item model's M2M. | |
''' | |
# Saves the form and create the post object. | |
post = super(MyPostAdminForm, self).save(*args, **kwargs) | |
# | |
# If this is a new post, save it so that Django can setup the virtual | |
# relationship: items. | |
# | |
if not post.pk: | |
post.save() | |
# Get the (single) Item the user selected from the cleaned form. | |
item = self.cleaned_data['items'] | |
# | |
# Clear out any old entries (that aren't the desired one), otherwise, | |
# they'll just accumulate, since this is really a M2M field rather | |
# than a ModelChoiceField. | |
# | |
for old in post.items.all(): | |
if old.id != item: | |
post.items.remove(old) | |
# | |
# Add this new one, if any, no harm if it already existed, we | |
# won't end up with duplicates. | |
# | |
if item: | |
post.items.add(item) | |
return post | |
class MyPostAdmin(PostAdmin): | |
form = MyPostAdminForm | |
def get_fieldsets(self, request, obj=None): | |
''' | |
Alter the existing fieldsets to include the field: items | |
''' | |
fieldsets = super(MyPostAdmin, self).get_fieldsets(request, obj) | |
fieldsets[1][1]['fields'] = ['items', ... ] | |
return fieldsets |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# Django 1.6 | |
from django.db import models | |
class Item(models.Model): | |
label = models.CharField('label', blank=False, default='') | |
... | |
posts = models.ManyToMany('blog_app.Post', | |
blank=True, | |
null=True, | |
related_name='items', # This is the default, but let's be explicit here. | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment