Specifying a custom Form display using Django’s admin.py

Upon starting a recent Django project I quickly ran into an issue where a model I created was intended to hold username/passwords for ssh authentications to various servers. But by default by just calling the admin.site.register(Server) method resulted in the password being displayed in plane text. So lets start jumping through some rings of fire to get a pretty little password field in it’s place.

First off lets take an inventory of what we are working with with this “Server” model:

So this first section of code is the model that will describe our Server /home/user/yourproject/servers/models.py

from django.db import models
from django.forms import ModelForm, PasswordInput

class ServerType(models.Model):
  type = models.CharField(max_length=200)
  version = models.CharField(max_length=20)

  def __unicode__(self):
    return self.type + '(' + self.version + ')'

class ServerInfo(models.Model):
  type = models.ForeignKey(ServerType)
  hostname = models.CharField(max_length=200)
  username = models.CharField(max_length=20)
  password = models.CharField(max_length=255)
  created = models.DateTimeField('date created')

  def __unicode__(self):
    return self.hostname

As you can see it essentially consists of a Type which will represent the operating system type and version. Within the ServerInfo Class we have holders for the type, hostname, username and password

To add the server model to the administration site all we need to do is create the file /home/user/yourproject/servers/admin.py

from ServerMonitor.servers.models import ServerInfo, ServerType
from django.contrib import admin

admin.site.register(ServerType)
admin.site.register(ServerInfo)

But as I mentioned before this will add all field/form elements in basic plain text form, useful but not what I need. So to add a little more sophistication to this we must create a ModelAdmin class within our admin.py file and attach it to ServerInfo as follows

from ServerMonitor.servers.models import ServerInfo, ServerType
from ServerMonitor.servers.forms import ServerCreationForm, ServerViewForm
from django.contrib import admin

class ServerAdmin(admin.ModelAdmin):
  fieldsets = [
    (None, {'fields': ['type', 'hostname', 'username', 'password']}),
    ('Date Information', {'fields': ['created'], 'classes': ['collapse']}),
  ]

  add_form = ServerCreationForm
  view_form = ServerViewForm

 
  def get_form(self, request, obj=None, **args):
    defaults = {}
    if obj is None:
      defaults.update({
        'form': self.add_form,
        'fields': admin.util.flatten_fieldsets(self.add_fieldsets),
      })
    else:
      defaults.update({
        'form': self.view_form
      })
    defaults.update(args)
    return super(ServerAdmin, self).get_form(request, obj, **defaults)


admin.site.register(ServerType)
admin.site.register(ServerInfo, ServerAdmin)

Holy crap, that’s quite a difference from the initial admin.py we created, I’ll break it down into sections for you and then attempt to explain them.

Line #2: Importing the Forms that will be used for our custom display
Line #6-9: Specifying fieldsets simply for the purpose of making it look better.
Line #11: Specifying the Form to be used when adding a new server
Line #12: Specifying the Form to be used when viewing a server
Line #24-26: Here we are overriding the get_form method to do the actual switching of the form
Line #29: We pass our ServerAdmin class along with the ServerInfo class as part of the admin page registration.

So you can see here that the bulk of the work is done within the get_form override, but in order for this to work we must create the ServerCreationForm and the ServerViewForm. Both of these classes need to be created in the /home/user/yourproject/servers/forms.py file:

from django import forms
from django.utils.translation import ugettext_lazy as _

class ServerCreationForm(forms.ModelForm):
  '''
  Creates a server, from the given username and password.
  '''
  hostname = forms.RegexField(label=_('Hostname'), max_length=200, regex=r'^.*',
    help_text = _('Required. Please entery the FQDN of the server'),
    error_message = {'invalid': _('This value may contain only letters, numbers and . characters')})

  username = forms.RegexField(label=_('Username'), max_length=30, regex=r'^[\w.@+-]+$',
    help_text = _('Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.'),
    error_messages = {'invalid': _('This value may contain only letters, numbers and @/./+/-/_ characters.')})

  password = forms.CharField(label=_('Password'), widget=forms.PasswordInput)

class ServerViewForm(forms.ModelForm):
  '''
  Creates a server, from the given username and password.
 '''
  hostname = forms.RegexField(label=_('Hostname'), max_length=200, regex=r'^.*',
    help_text = _('Required. Please entery the FQDN of the server'),
    error_message = {'invalid': _('This value may contain only letters, numbers and . characters')})

  username = forms.RegexField(label=_('Username'), max_length=30, regex=r'^[\w.@+-]+$',
    help_text = _("Required. 30 characters or fewer. Letters, digits and @ /./+/-/_ only."),
    error_messages = {'invalid': _('This value may contain only letters, numbers and @ / . / + / - /_ characters.')})

  password = forms.CharField(label=_('Password'), widget=forms.PasswordInput)

So, first thing that should be noticed is that the ServerCreationForm and the ServerViewForm classes are identical. (maybe you’re more creative than I am)
The declarations for hostname and username are fairly straight forward but you can read more about ModelForms over at the Django website if they aren’t clear.

The magic of the password field happens by setting the widget parameter on the forms CharField on lines 16 and 30

So to boil it all down we have created our Form class in the forms.py file then imported and added reference to them in admin.py (add_form = ServerCreationForm) and then finally swapped them in by overriding the get_form method within admin.py

Presto chango, you should now be able to run python manage.py runserver navigate to your admin section and see that the password fields are now typical html password fields where the text is obscured.

One thought on “Specifying a custom Form display using Django’s admin.py

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>