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

Django Admin Under The Hood

Ola Sitarska
November 30, 2015

Django Admin Under The Hood

Ola Sitarska

November 30, 2015
Tweet

More Decks by Ola Sitarska

Other Decks in Programming

Transcript

  1. ChangeList Makes sure that the list of objects in a

    model applies correct filters, orders and pagination
  2. AdminSite Encapsulates an instance of the Django admin application, ready

    to be hooked in to your URLconf • URLs • ModelAdmin registry • Basic site configuration
  3. 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'), ] …
  4. 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)), ]
  5. 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)
  6. 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')
  7. 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
  8. 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
  9. 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
  10. 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)
  11. 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)) ...
  12. 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)
  13. 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) ...
  14. 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 ...
  15. 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 ...
  16. 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
  17. 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')
  18. 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)
  19. 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
  20. 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)
  21. 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) ...
  22. 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
  23. 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
  24. ChangeList Filters • Filter is a class • Usually extended

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

    BooleanFieldListFilter • ChoicesFieldListFilter • DateFieldListFilter
  26. 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))
  27. 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 ...