Skip to content

Instantly share code, notes, and snippets.

@mkoistinen
Last active August 29, 2015 13:58
Show Gist options
  • Save mkoistinen/10019911 to your computer and use it in GitHub Desktop.
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…
# -*- 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
# -*- 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