1 of 32

Advanced topics in Django admin - custom forms, fields, inlines, Grappelli, other amusing hacks

Michael “Sveder” Sverdlin

2 of 32

Respect ma authoritah!

3 of 32

Fear of the admin

4 of 32

And now we are BFFs

  • This is the story of how I stopped worrying and learned to use the admin.
  • Examples from the admin at fairfly.com that started as a vanilla naive interface and evolved into a beast of productivity.

5 of 32

Whirlwind tour

  • Edit data from the database
  • Basically a system of HTML forms
  • Autogenerated from models
  • That someone else wrote :)
  • Very customizable

6 of 32

Widgets

  • Responsible for rendering the right HTML - usually inputs

  • render - spits out the HTML to show on the form
  • value_from_datadict - try and get you data into a python value.

7 of 32

Form Fields

  • Turn data from a widget into an instance of a Python object (usually a Model)
  • to_python - widget data into an instance of models (usually)
  • validate - throw ValidationError that shows like a nice error

8 of 32

Forms

9 of 32

Formset

(+inlines)

10 of 32

Level 1 - The face lift

11 of 32

Grappelli

12 of 32

Not just a pretty face

  • Collapsible inlines
  • Reorderable inlines
  • String Customization (title!)
  • Autocomplete
  • Switch user

13 of 32

django-relatedadminwidget

  • Add, edit, remove foreign keys right from your main form!
  • Customizable.
  • Although the icons… They are kinda small?

14 of 32

Form Assets - Media class

class CalendarWidget(TextInput):

class Media:

css = {

'all': ('pretty.css',),

'print': ('print.css',)

}

js = ('animations.js', 'actions.js')

15 of 32

All together - bigger widgets

class BiggerIconsRelatedWidget(RelatedWidgetWrapperBase):

class Media:

css = { 'all': ('bigger_related_widget_icons.css',)}

.related-widget-wrapper-link > img {

width: 18px; height: 18px;

}

16 of 32

LogEntry.objects.log_action(user_id=user.id, content_type_id=ContentType.objects.get_for_model(model).pk, object_id=model.id, object_repr=repr(model), action_flag=ADDITION|CHANGE|DELETION, change_message=”Some message”)

17 of 32

Level 15 - Customization

18 of 32

Safer Logins

Prevent MITM attacks, prevent anything sniffing on the server. We REALLY don’t want to know your password.

from django.contrib import admin

admin.autodiscover()

admin.site.login_form = PasswordHashingAuthForm

19 of 32

class PasswordHashingForm(AuthenticationForm):

class Media:

js = ("password_hasher.js", "http://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/components/core-min.js",)

form.addEventListener("submit", hashPassword, false);

function hashPassword() {

password_input = document.getElementById("id_password");

password_input.value = CryptoJS.SHA1(password_input.value + "salt").toString(CryptoJS.enc.Hex);

}

20 of 32

Custom Fields and Widgets

  • Widget - render, value_from_datadict
  • Field - to_python, validate, clean
  • Try to reuse other widgets/fields as much as possible.
  • MoneyField -> NullableMoneyField

21 of 32

def render(self, name, value, attrs=None):

result = super(CheckBoxInputMoneyField, self).render(name, value, attrs)

name = get_checkbox_name(name)

attrs['id'] = 'id_' + name

# If the value is empty, it means that the amount was not given and thus the checkbox should be on:

if not value:

attrs["checked"] = "checked"

return self.pretty_check_box.render(name, False, attrs, label=self.label) + result

22 of 32

class PrettyCheckboxWidget(CheckboxInput):

def render(self, name, value, attrs=None, label=None):

final_attrs = self.build_attrs(attrs, type='checkbox', name=name)

if self.check_test(value):

final_attrs['checked'] = 'checked'

return format_html('<label style="float:None;display:inline;margin-right:10px" for="{0}"><input{1} /> {2}</label>',

attrs['id'], flatatt(final_attrs), label)

23 of 32

Boss Fight:

̸̨̡̹̫͈̺̩̬̙ͅT̻̦h̺̼̬̰͔̫̙͓͘͟è̛͈̫̲̲͚̩͈͖ ̻̀w̴̼̪i̶̡͈͞ͅd͓̘͔̳̯g͔̝̖͖͔̺͍͈̦͡e͘҉̜̲̞̜̘t̗͉̻ ̧̞͓͖̺̱̳̫͇f̵̗̝͈͎̺̺̯͞r̨̤̰̮͔͢o͏̛͔̱̘̲͓̮m͞҉̘̹̰̀ ̴̯̻̭h̗̯͖͕̹̗e̛͚̙̳̘͔l̨͚̜̪͈͖͕͚͡l͚̥!̴̜̩̮̠̤̲͘͢

̵̵͔̳̠̻̪

24 of 32

Non Ajax Autocomplete

  • A few selects with 10,000’s of options were bloating HTML to Megabytes.
  • Grinding server CPU to render huge templates.
  • Ajax solutions - still stress server and database on each letter.
  • No non Ajax solutions.

25 of 32

AutocompleteSelectWidget

  • On first render insert the list of choices into the DOM (window.airport_tags).
  • Use self.__class__ as a singleton to know that the list was inserted into DOM.
  • Spit out HTML that creates an input and make it autocomplete (jquery) from the DOM.

26 of 32

if name not in self.__class__.names_seen:

self.__class__.names_seen.append(name)

else:

self.__class__.names_seen = [name]

self.__class__.in_dom = False

tag_name = self.get_name()

if not self.__class__.in_dom:

autocomplete_tags = u",".join([u'"%s"' % choice for choice in self.choices])

tags_setup = u"""window.%s_tags = [%s];""" % (tag_name, autocomplete_tags)

self.__class__.in_dom = True

27 of 32

autocomplete_template = u"""<script>

$(function() {

%s

$( "#%s" ).autocomplete({

source: window.%s_tags

});});

</script>

<div class="ui-widget">

<input id="%s" name="%s" value="%s" width=200>

</div>""" % (tags_setup, name, tag_name, name, name, value)

return mark_safe(autocomplete_template)

28 of 32

And then we had * problem

  • Two airports with the same name
  • Crashes in this code
  • Error in other parts of the form
  • Adding first inline didn’t work

  • Oh, and the code

29 of 32

But it works™

  • Planning to release it as open source, after we clean all the bits we DIDN’T show you
  • All in all - saved a ton of time and frustration
  • (CPU time)
  • Hasn’t been a source of problems after initial cleanups

30 of 32

So what did we learn

  • Django admin is fun and definitely not scary
  • There are a lot of tools you can integrate without much fuss
  • Writing your own forms, fields, widgets is also not very hard, if you understand what you are doing

31 of 32

About me

  • Programming python for a decade
  • Programming Django for 3 years.

  • Working for Fairfly.com, we’re looking for great developers - jobs@fairfly.com

32 of 32

Q and A

  • Don’t be shy!
  • I won’t go until someone asks me something :)

  • Say hello:�m@sveder.com�@msveder