Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Django Admin Under The Hood

Ce99ccbfed462de9b46953827df21fa5?s=47 Ola Sitarska
November 30, 2015

Django Admin Under The Hood

Ce99ccbfed462de9b46953827df21fa5?s=128

Ola Sitarska

November 30, 2015
Tweet

Transcript

  1. Hallo!

  2. How Django Admin Works

  3. • AdminSite • ModelAdmin • ChangeList

  4. AdminSite Encapsulates an instance of the Django admin application, ready

    to be hooked in to your URLconf
  5. ModelAdmin Encapsulates all admin options and functionality for any given

    model
  6. ChangeList Makes sure that the list of objects in a

    model applies correct filters, orders and pagination
  7. None
  8. AdminSite

  9. AdminSite Encapsulates an instance of the Django admin application, ready

    to be hooked in to your URLconf
  10. AdminSite Encapsulates an instance of the Django admin application, ready

    to be hooked in to your URLconf • URLs • ModelAdmin registry • Basic site configuration
  11. AdminSite Autodiscover admin.autodiscover()

  12. AdminSite URLs url(r'^admin/', include(admin.site.urls)),

  13. AdminSite URLs site = AdminSite()

  14. AdminSite URLs class AdminSite(object): … @property def urls(self): return self.get_urls(),

    'admin', self.name
  15. AdminSite URLs class AdminSite(object): … @property def get_urls(self): # Admin-site-wide

    urls urlpatterns = [ url(r'^$', wrap(self.index), name='index'), url(r'^login/$', self.login, name='login'), url(r'^logout/$', wrap(self.logout), name='logout'), ] …
  16. AdminSite URLs # Add in each model's views, and create

    a list of # valid URLS for the app_index for model, model_admin in self._registry.items(): urlpatterns += [ url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)), ]
  17. AdminSite ModelAdmin registry from django.contrib import admin admin.site.register(Author, AuthorAdmin)

  18. AdminSite ModelAdmin registry from django.contrib import admin admin.site.register(Author, AuthorAdmin) or

    @admin.register(Author) class AuthorAdmin(admin.ModelAdmin):
  19. AdminSite ModelAdmin registry from django.contrib import admin admin.site.register(Author, AuthorAdmin) or

    @admin.register(Author) class AuthorAdmin(admin.ModelAdmin): or admin.site.register(Author)
  20. AdminSite ModelAdmin registry admin.register(Author, list_display=('name', 'age'))

  21. AdminSite Configuration class AdminSite(object): # Text to put at the

    end of each page's <title>. site_title = ugettext_lazy('Django site admin') # Text to put in each page's <h1>. site_header = ugettext_lazy('Django administration') # Text to put at the top of the admin index page. index_title = ugettext_lazy('Site administration')
  22. AdminSite Configuration class AdminSite(object): login_form = None index_template = None

    app_index_template = None login_template = None logout_template = None password_change_template = None password_change_done_template = None
  23. None
  24. ModelAdmin

  25. ModelAdmin Encapsulates all admin options and functionality for any given

    model.
  26. None
  27. Ouch

  28. ModelAdmin Custom views def get_urls(self): urlpatterns = [ url(r'^$', wrap(self.changelist_view),

    name='%s_%s_changelist' % info), url(r'^add/$', wrap(self.add_view), name='%s_%s_add' % info), url(r'^(.+)/history/$', wrap(self.history_view), name='%s_%s_history' % info), url(r'^(.+)/delete/$', wrap(self.delete_view), name='%s_%s_delete' % info), url(r'^(.+)/change/$', wrap(self.change_view), name='%s_%s_change' % info), # For backwards compatibility (was the change url before 1.9) url(r'^(.+)/$', wrap(RedirectView.as_view( pattern_name='%s:%s_%s_change' % ((self.admin_site.name,) + info) ))), ] return urlpatterns
  29. ModelAdmin Custom views def wrap(view): def wrapper(*args, **kwargs): return self.admin_site.admin_view(view)(*args,

    **kwargs) wrapper.model_admin = self return update_wrapper(wrapper, view)
  30. ModelAdmin Custom views class MyModelAdmin(admin.ModelAdmin): def get_urls(self): urlpatterns = super(MyModelAdmin,

    self).get_urls() my_urls = [ url(r'^$', wrap(self.custom_view), name='app_model_custom'), ] return my_urls + urlpatterns
  31. ModelAdmin Custom views class ModelAdmin(object): ... def add_view(self, request, form_url='',

    extra_context=None): return self.changeform_view(request, None, form_url, extra_context) def change_view(self, request, object_id, form_url='', extra_context=None): return self.changeform_view(request, object_id, form_url, extra_context)
  32. ModelAdmin Custom views class ModelAdmin(object): ... def has_change_permission(self, request, obj=None):

    opts = self.opts codename = get_permission_codename('change', opts) return request.user.has_perm("%s.%s" % (opts.app_label, codename)) ...
  33. ModelAdmin Templates overriding if add and self.add_form_template is not None:

    form_template = self.add_form_template else: form_template = self.change_form_template return TemplateResponse(request, form_template or [ "admin/%s/%s/change_form.html" % (app_label, opts.model_name), "admin/%s/change_form.html" % app_label, "admin/change_form.html" ], context)
  34. ModelAdmin Saving changeform_view form class ModelAdmin(object): def changeform_view(self): ... ModelForm

    = self.get_form(request, obj) if request.method == 'POST': form = ModelForm(request.POST, request.FILES, instance=obj) if form.is_valid(): new_object = self.save_form(request, form, change=not add) else: new_object = form.instance if all_valid(formsets) and form_validated: self.save_model(request, new_object, form, not add) self.save_related(request, form, formsets, not add) ...
  35. ModelAdmin Writing your own custom view, maybe?

  36. ModelAdmin Custom views: Permissions class ModelAdmin(object): def has_change_permission(self, request, obj=None):

    opts = self.opts codename = get_permission_codename('change', opts) return request.user.has_perm("%s.%s" % (opts.app_label, codename)) class EventAdmin(admin.ModelAdmin): ... def stats_view(self, request, obj=None): if not self.has_change_permission(request, obj): raise PermissionDenied ...
  37. ModelAdmin Custom views: Permissions class EventAdmin(admin.ModelAdmin): ... def has_change_permission(request, obj):

    if request.user in obj.team and super(EventAdmin, self).has_change_permission(request, obj): return True else: return False def stats_view(self, request, obj=None): if not self.has_change_permission(request, obj): raise PermissionDenied ...
  38. ModelAdmin Gotcha! Admin is initialized on process startup admin.site.register(Model)

  39. ModelAdmin Gotcha! Admin is initialized on process startup Don't do

    this: def has_change_permission(request, obj): if request.user in obj.team and super(EventAdmin, self).has_change_permission(request, obj): self.can_access = True
  40. ModelAdmin pip install django-admin-views class TestAdmin(AdminViews): admin_views = ( ('Redirect

    to BBC', 'redirect_to_bbc'), ('Go to example.com', 'http://www.example.com'), ) def redirect_to_bbc(self, *args, **kwargs): return redirect('http://www.bbc.co.uk')
  41. None
  42. ModelAdmin Gotcha! ModelAdmin.list_editable class UserAdmin(admin.ModelAdmin): list_editable = ('is_staff',)

  43. None
  44. ModelAdmin ModelAdmin.form class UserAdmin(admin.ModelAdmin): form = UserChangeForm

  45. ModelAdmin ModelAdmin.form & ModelAdmin.get_form() class UserAdmin(admin.ModelAdmin): form = UserChangeForm add_form

    = UserCreationForm def get_form(self, request, obj=None, **kwargs): if obj is None: defaults['form'] = self.add_form return super(UserAdmin, self).get_form(request, obj)
  46. ModelAdmin Gotcha! ModelAdmin.raw_id_fields class UserAdmin(admin.ModelAdmin): raw_id_fields = ("group",)

  47. ModelAdmin ModelAdmin.readonly_fields & ModelAdmin.get_readonly_fields() class EventAdmin(admin.ModelAdmin): readonly_fields = () def

    get_readonly_fields(self, request, obj=None): if obj and not request.user.is_superuser: return self.readonly_fields + ('email', 'team') return self.readonly_fields
  48. ModelAdmin ModelAdmin.get_queryset() class EventAdmin(admin.ModelAdmin): def get_queryset(self, request): qs = super(EventAdmin,

    self).get_queryset(request) if request.user.is_superuser: return qs return qs.filter(team=request.user)
  49. ModelAdmin

  50. ChangeList

  51. ModelAdmin ! ChangeList ModelAdmin.changelist_view def changelist_view(self, request, extra_context=None): ... ChangeList

    = self.get_changelist(request) cl = ChangeList(request, self.model, list_display, list_display_links, list_filter, self.date_hierarchy, search_fields, list_select_related, self.list_per_page, self.list_max_show_all, self.list_editable, self) ...
  52. ModelAdmin ! ChangeList ModelAdmin.get_changelist def get_changelist(self, request, **kwargs): """ Returns

    the ChangeList class for use on the changelist page. """ from django.contrib.admin.views.main import ChangeList return ChangeList
  53. Role of ChangeList • figure out what are the filter,

    search, order and pagination parameters from query string • verify that lookups on these parameters are allowed • filter & search the queryset, apply order and paginate
  54. ChangeList Filters • Filter is a class • Usually extended

    from SimpleListFilter or FieldListFilter
  55. ChangeList Filters class MyModelAdmin(admin.ModelAdmin): list_filter = ('type',) • RelatedFieldListFilter •

    BooleanFieldListFilter • ChoicesFieldListFilter • DateFieldListFilter
  56. ChangeList Filters FieldListFilter.register(lambda f: isinstance(f, (models.BooleanField, models.NullBooleanField)), BooleanFieldListFilter)

  57. ChangeList Filters class MyModelAdmin(admin.ModelAdmin): list_filter = (('type', BooleanFieldListFilter),)

  58. ChangeList Filters class MyModelAdmin(admin.ModelAdmin): list_filter = ('type', DecadeBornListFilter)

  59. ChangeList Custom Filter class DecadeBornListFilter(admin.SimpleListFilter): title = _('decade born') parameter_name

    = 'decade' def lookups(self, request, model_admin): return ( ('80s', _('in the eighties')), ('90s', _('in the nineties')), ) def queryset(self, request, queryset): if self.value() == '80s': return queryset.filter(birthday__gte=date(1980, 1, 1), birthday__lte=date(1989, 12, 31)) if self.value() == '90s': return queryset.filter(birthday__gte=date(1990, 1, 1), birthday__lte=date(1999, 12, 31))
  60. ChangeList.get_queryset() • ChangeList.get_results() • ModelAdmin.get_paginator() • ChangeList.get_queryset() • ChangeList.get_filters() •

    ChangeList.get_filters_params() • ModelAdmin.lookup_allowed() • Apply filters
  61. ChangeList.get_queryset() Applying filters def get_queryset(self, request): ... qs = self.root_queryset

    for filter_spec in self.filter_specs: new_qs = filter_spec.queryset(request, qs) if new_qs is not None: qs = new_qs ...
  62. BAM!

  63. ChangeList.get_queryset() • ChangeList.get_ordering() • ModelAdmin.get_search_results() • !

  64. PEW PEW! ⚡

  65. None
  66. None
  67. Sprints on Saturday! ! "

  68. Dankjewel! ! "