Fat Posts in Django

In reply to https://orangegnome.com/posts/3633/team-fat-posts

I did a little Django proof-of-concept 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.

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 ModelForms 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

A screenshot of the Django admin for adding an article. It shows fields for Title, Content, Summary, and Post Type

A screenshot of the Django admin for adding a note. It shows fields for Content and Post Type

A screenshot of the Django admin for adding a note. It shows fields for Post Type, Content, Title, and Summary

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.)