Skip to content

Instantly share code, notes, and snippets.

@Greymalkin
Last active April 12, 2020 14:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Greymalkin/f892e52ec541a7220252ac31b6a2abb0 to your computer and use it in GitHub Desktop.
Save Greymalkin/f892e52ec541a7220252ac31b6a2abb0 to your computer and use it in GitHub Desktop.
Inline Admin aware of Parent Object and affecting Generic Foreign Key Content Type choices
# in models.py
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
class Screen(models.Model):
# Screen Model: Ties into all shows, drives instance selection when using generic foreign keys
name = models.CharField(max_length=50)
limit = (models.Q(
models.Q(app_label='case_story', model='casestoryshow') |
models.Q(app_label='commerce', model='commerceshow') |
models.Q(app_label='video', model='videoshow') |
models.Q(app_label='weather', model='weathershow') |
models.Q(app_label='factoid', model='factoidshow') |
models.Q(app_label='patent', model='patentshow') |
models.Q(app_label='tip', model='tipshow') |
models.Q(app_label='security', model='securityshow')))
ct_allowed = models.ManyToManyField(
ContentType,
related_name="screen_show_types",
verbose_name="Allowed Modules",
limit_choices_to=limit)
class Playlist(models.Model):
name = models.CharField(max_length=105, help_text="Name")
screen = models.ForeignKey(Screen, related_name="playlists")
class Slot(models.Model):
playlist = models.ForeignKey(
Playlist,
related_name="slots")
show_type = models.ForeignKey(
ContentType,
related_name="schedule_slot_show_types")
show_id = models.PositiveIntegerField(verbose_name="Show name")
show = GenericForeignKey("show_type", "show_id")
position = models.PositiveSmallIntegerField("Position")
# in admin.py
from . import models, forms
class SlotInline(GrappelliSortableHiddenMixin, admin.TabularInline):
model = models.Slot
form = forms.SlotAdminForm
formset = forms.SlotFormSet
extra = 1
autocomplete_lookup_fields = {"generic": [['show_type', 'show_id']]}
sortable_field_name = "position"
@admin.register(models.Playlist)
class PlaylistAdmin(admin.ModelAdmin):
list_display = ['name', 'screen', 'notes']
list_filter = ['screen']
change_list_template = "admin/change_list_filter_sidebar.html"
inlines = [SlotInline]
fields = ['name', 'screen', 'notes']
def get_readonly_fields(self, request, obj=None):
if obj is None:
return []
else:
return ('screen',)
def get_formsets_with_inlines(self, request, obj=None):
# If parent object has not been saved yet / If there's no screen
# hide inlines
if obj is None:
return []
return super(PlaylistAdmin, self).get_formsets_with_inlines(request, obj)
def get_fieldsets(self, request, obj=None):
# If parent object has not been saved yet / If there's no screen
# hide all other fields (including inlines) and display instructions.
# Instructions will not reappear after parent object has been saved.
fieldsets = super(PlaylistAdmin, self).get_fieldsets(request, obj)
if obj is None:
fieldsets = ((None, {
'fields': ('name', 'screen'),
'description': '''Enter a show name, select a screen, and click
<strong>Save and continue editing</strong>.'''}),
)
return fieldsets
def get_form(self, request, obj=None, **kwargs):
# This method made obsolete by the use of custom formset shown above
# Limit foreign key choices in an inline form:
# save obj reference to request scope for access in Inline
request.parent_object = obj
return super(PlaylistAdmin, self).get_form(request, obj, **kwargs)
# in forms.py
from django import forms
from django.contrib.admin.widgets import AdminDateWidget
from django.contrib.contenttypes.models import ContentType
from . import models
class SlotFormSet(forms.BaseInlineFormSet):
def get_form_kwargs(self, index):
# pass the parent object into the inline formset so it can be accessed from the inline form
kwargs = super().get_form_kwargs(index)
kwargs['parent_object'] = self.instance
return kwargs
class SlotAdminForm(forms.ModelForm):
pass
class Meta:
model = models.Slot
fields = '__all__'
def __init__(self, *args, parent_object, **kwargs):
self.parent_object = parent_object
super(SlotAdminForm, self).__init__(*args, **kwargs)
# read the screen of the parent playlist and restrict to only the content types available there
# print(self.instance, self.parent_object)
if self.parent_object:
self.fields['show_type'].queryset = self.parent_object.screen.ct_allowed.all()
def clean_show_id(self):
show_id = self.cleaned_data.get('show_id')
show_type = self.cleaned_data.get('show_type')
# lookup show
show = ContentType.objects.get(app_label=show_type.app_label,
model=show_type.model).get_object_for_this_type(id=show_id)
# throw error if selected show is not on same screen as parent playlist
if show.screen != self.instance.playlist.screen:
raise forms.ValidationError('''Show belongs to {} screen and cannot be selected
for this playlist.'''.format(show.screen))
return show_id
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment