Skip to content

Instantly share code, notes, and snippets.

@kofic
Last active March 16, 2019 21:16
Show Gist options
  • Select an option

  • Save kofic/aa99fe01287375afb140b7b8af37dcc6 to your computer and use it in GitHub Desktop.

Select an option

Save kofic/aa99fe01287375afb140b7b8af37dcc6 to your computer and use it in GitHub Desktop.
Customizing Django Admin

Initial Model and Admin code

Let's say I have a simple Content model.py that looks like this:

# Content model
class Content(models.Model):
    TYPE_CHOICES = (
        ('text', 'Text'),
        ('html', 'HTML'),
        ('image', 'Image'),
    )
    name = models.CharField(max_length=200)
    type = models.CharField(
        max_length=7, 
        default="text", 
        choices=TYPE_CHOICES,
    )
    text = models.TextField(null=True, blank=True)
    html = models.TextField(null=True, blank=True)
    image = models.ImageField(upload_to='content_images', null=True, blank=True)
    is_published = models.BooleanField(default=False)
    published_at = models.DateTimeField(null=True, blank=True)

And an admin.py that looks like this:

from django.contrib import admin
from .models import Content


class ContentAdmin(admin.ModelAdmin):
    search_fields = ['name', 'text', 'html']
    list_display = ('id', 'name', 'type')
    list_display_links = ('id', 'name')
    list_filter = ('type',)

The code above will yield a 'Add Content' admin page that looks like this:

cms-content1

Improvements to be mode

Now - let us make the following improvements to the admin page.

  • Add an entry at the top of the type select field and let it say "Select a type".
  • Hide the text, html and image fields until a type is selected.
  • Hide the published_at field
  • Add these validations:
    • If type="text" then the text field is required
    • If type="html" then the html field is required
    • If type="image" then the image field is required

Step 1: Add a form

The first thing we want to do is add a forms.py file with a ContentForm class that will handle all our form validations. This is a Django best practice so please always use Django Forms to handle your form submissions.

Here is what our form will look like:

from django import forms
from cms.models import Content


class ContentForm(forms.ModelForm):
    # Add a 'Select a type' entry in first spot
    type_choices = list(Content.TYPE_CHOICES)
    type_choices.insert(0, ("", "Select a type"))
    type_choices = tuple(type_choices)

    type = forms.ChoiceField(choices=type_choices)

    class Meta:
        model = Content
        # This will hide the 'published_at' field
        exclude = ['published_at'] # exclude or fields is a required meta field

    def clean(self):
        cleaned_data = super(ContentForm, self).clean()
        content_type = cleaned_data.get("type")

        if content_type == "text" and not cleaned_data.get("text"):
            self.add_error(
                'text', "Please provide text content"
            )

        if content_type == "html" and not cleaned_data.get("html"):
            self.add_error(
                'html', "Please provide html content"
            )

        if content_type == "image" and not cleaned_data.get("image"):
            self.add_error(
                'image', "Please select an image"
            )

        return cleaned_data

The type = forms.ChoiceField(choices=type_choices) line resets the type field with our custom type_choices.

The code in the form's clean method will handle our required validations and ensure that if type="text" then the text field is required - and the same for html and image fields.

Step 2: Setup the admin to use the form

Now that we have created a form that has a customized type field and validitions - let us tell our admin to use the form by modifying the get_form method. Our new admin.py will look like this:

from django.contrib import admin
from .models import Content
from .forms import ContentForm


class ContentAdmin(admin.ModelAdmin):
    search_fields = ['name', 'text', 'html']
    list_display = ('id', 'name', 'type')
    list_display_links = ('id', 'name')
    list_filter = ('type',)

   # Import the ContentForm and tell admin to use it
   def get_form(self, request, obj=None, **kwargs):
        form = ContentForm
        return form

Step 3: Setup your javascript and CSS files

We will use CSS to initially hide the text, html and image fields. The static/css/cms/admin/content.css file should look like this:

.field-text{
  display: none;
}
.field-html{
  display: none;
}
.field-image{
  display: none;
}

We will use javascript to hide or show the fields when a type field is selected. The static/js/cms/admin/content.js file should look like this:

function hideAllContentFields(){
    $(".field-text").hide();
    $(".field-html").hide();
    $(".field-image").hide();
}
function showContentFields(){
    hideAllContentFields();

    var type = $("#id_type").val();
    if (type == "text"){
        $(".field-text").show();
    }
    else if (type == "html"){
        $(".field-html").show();
    }
    else if (type == "image"){
        $(".field-image").show();
    }
}

$(function() {
    $( "#id_type" ).change(function( event ) {
        showContentFields();
    });
    showContentFields();
});

Step 4: Add the javascript and CSS files to your admin page

Add the javascript and CSS files to the Media class portion of the admin.py code. Your final admin.py should look like this:

from django.contrib import admin
from .models import Content
from .forms import ContentForm


class ContentAdmin(admin.ModelAdmin):
    search_fields = ['name', 'text', 'html']
    list_display = ('id', 'name', 'type')
    list_display_links = ('id', 'name')
    list_filter = ('type',)

    class Media:
        js = (
            'js/jquery-3.3.1.min.js',
            'js/cms/admin/content.js',
        )
        css = {
            'all': ('css/cms/admin/content.css',)
        }
        
   # Import the ContentForm and tell admin to use it
   def get_form(self, request, obj=None, **kwargs):
        form = ContentForm
        return form

Now your 'Add Content' admin page should look like this:

cms-content2

And when you select type="text" you should see:

cms-content3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment