I did a little Django proof-of-concept (POC) on the fat posts with just Note and Article post types. I wanted to be sure I could have type-specific admin pages before doing the big lift on my site architecture.
The key seems to be using proxy models. You can’t register the same model in the admin multiple times. The proxy classes help get around that. For this very simple POC, my models.py
file looks like this
from django.db import models
class Post(models.Model):
NOTE = "NOTE"
ARTICLE = "ARTICLE"
TYPE_CHOICES = {
NOTE: "Note",
ARTICLE: "Article"
}
post_type = models.CharField(choices=TYPE_CHOICES, max_length=7, default='', blank=True)
content = models.TextField(default='', blank=True)
title = models.CharField(default='', blank=True, max_length=100)
summary = models.TextField(default='', blank=True)
def __str__(self):
if self.post_type == Post.ARTICLE:
return self.title
return self.content
class PostTypeManager(models.Manager):
def __init__(self, post_type):
super().__init__()
self.post_type=post_type
def get_queryset(self):
qs = super().get_queryset()
return qs.filter(post_type=self.post_type)
class Note(Post):
objects = PostTypeManager(Post.NOTE)
class Meta:
proxy = True
class Article(Post):
objects = PostTypeManager(Post.ARTICLE)
class Meta:
proxy = True
The Post
model has content
, title
, and summary
, as well as post_type
for declaring the type of post. The content
field is shared between Notes and Articles, while Articles also have the title
and a summary
fields.
The PostTypeManager
class lets the proxy classes query for records of just their post_type
. Each proxy model sets it’s objects
to the appropriate PostTypeManager
.
The admin.py
looks like
from django.contrib import admin
from django.forms import ModelForm, CharField,ChoiceField
from .models import Post, Note, Article
class PostTypeForm(ModelForm):
def __init__(self, post_type_initial, **kwargs):
super().__init__(**kwargs)
choices = dict()
choices.update({
'': '---------'
})
choices.update(Post.TYPE_CHOICES)
self.fields['post_type'] = ChoiceField(initial=post_type_initial, choices=choices, required=False)
class NoteModelForm(PostTypeForm):
def __init__(self, **kwargs):
super().__init__(Post.NOTE, **kwargs)
class Meta:
model = Note
fields = ["content", 'post_type']
class ArticleModelForm(PostTypeForm):
def __init__(self, **kwargs):
super().__init__(Post.ARTICLE, **kwargs)
class Meta:
model = Article
fields = ['title', 'content', 'summary', 'post_type']
class PostModelForm(ModelForm):
title = CharField(required=False)
class Meta:
model = Post
exclude = []
class NoteAdmin(admin.ModelAdmin):
form = NoteModelForm
class ArticleAdmin(admin.ModelAdmin):
form = ArticleModelForm
class PostAdmin(admin.ModelAdmin):
form = PostModelForm
# Register your models here.
admin.site.register(Post, PostAdmin)
admin.site.register(Note,NoteAdmin)
admin.site.register(Article,ArticleAdmin)
The PostTypeForm
class allows for a generic implementation to add the post_type
input on each proxy model admin page with the correct default selection. I couldn’t find a way for the ModelForm
s to declare a different default selection than what the model defined without declaring a whole new widget for it.
Each ModelForm
class for the proxy models declare their default post_type
in the constructor and which fields to show.
And then finally an admin for the full Post
model is registered to allow for editing any and all fields if desired.
Leaving the post_type
field available on all admin views allows for easy post_type
switching. Sometimes a Note is started and then becomes long enough to be an Article.
The admin screens end up looking like this
I think the idea is proved. Now, to decide if it’s worth the effort to refactor. (Spoiler: I’m usually up for a good refactor.)