Last active
April 12, 2020 14:35
-
-
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
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
# 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