一 、 前言 

  Django 提供了admin 组件
为项目提供基本的管制后台功能(对数据表的增删改查)。

  本篇小说通过 admin源码 轻巧分析admin 内部原理
,增加使用情势,为日后进行定制和协和开支组件做铺垫。

二、 轻易利用

  1.在app 目录下的admin.py
中经过注册表

from django.contrib import admin
from blog01.models import *

admin.site.register([UserInfo,User,Blog])
# 或者通过 @admin.register 装饰器实现

  2. 创建root用户

python manage.py createsuperuser
#输入用户名
#输入密码
#再次输入密码

  3. 登入admin后台进行田间管理

浏览器访问 http://127.0.0.1/admin/ 

三、admin轻松深入分析

  1. admin 是贰个Django
提供的后台管理app,成效也正如强硬,在高速开采的进度中能够虚拟直接利用。

  可是面前碰到纷纭的作业境况,要兑现越来越高的定制,必然要求大家达成协和的admin组件,那样面前曰镪各类情况大家工夫游刃有余。

  2. admin 
是经过”注册“类自动生成url,实施相应的视图函数,提供本身可视化分界面,完结增加和删除改查功效。

  3. admin  内部 url 列表

url(r'^$', wrap(self.index), name='index'),
url(r'^login/$', self.login, name='login'),
url(r'^logout/$', wrap(self.logout), name='logout'),
url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'),
url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True),name='password_change_done'),
url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut),name='view_on_site'),#将我们表格生成url

   4. 注册类生成的url

127.0.0.1/admin/appname/classname/           #查看数据

127.0.0.1/admin/appname/classname/add        #增加数据

127.0.0.1/admin/appname/classname/id/delete  #删除数据

127.0.0.1/admin/appname/classname/id/change  #更新数据

127.0.0.1/admin/appname/classname/id/history #历史记录

  

 四、 admin 流程剖判之sites.py 解析

  1.从目录起先

    下图是django.contrib.admin
目录。能够望见熟习的static,templates,views,migrations目录,表明admin
是三个app。

图片 1

             2. 从
admin.site.register( model_or_iterable, admin_class=None,)
分析

    admin  是什么?

      是一个后台管理app

     site 是什么?

      点开垦现是来自sites.py
中的多个实例,代表当前admin站点,也正是经过模块导入格局贯彻的单例形式。上面为site.py
中源码,后续如不表明,均为admin源码材质。

# This global object represents the default admin site, for the common case.
# You can instantiate AdminSite in your own code to create a custom admin site.
# 这个全局对象代表了在一般情况下的默认admin 站点
# 你可以在你自己的代码中实例化AdminSite来创造一个自定义的admin 站点
site = AdminSite()

 

     register 是什么? 

      是site的八个方法,约等于site的类AdminSite的三个办法,

def register(self, model_or_iterable, admin_class=None, **options):
     '''Registers the given model(s) with the given admin class.

        The model(s) should be Model classes, not instances.

        If an admin class isn't given, it will use ModelAdmin (the default
        admin options). If keyword arguments are given -- e.g., list_display --
        they'll be applied as options to the admin class.

        If a model is already registered, this will raise AlreadyRegistered.

        If a model is abstract, this will raise ImproperlyConfigured.'''

'''用提供的admin 类给model(我们的表格)注册,必须给Model类,而不是实例
  如果没有指定admin类,会用默认的ModelAdmin,如果给了关键词参数,如list_display,他们会被作为选项应用在admin类中
  如果一个model 已经被注册了,会报AlreadyRegistered异常
  如果一个model是抽象的,这会引起ImproperlyConfigured异常。'''

 

     小结:

     所以大家做的事是将   代表大家表格的类   传给 site.py中 
AdminSite类 实例化的site对象  的register 方法    举行登记,暗中认可是 用 
 ModelAdmin 管理 。

    site 对象就是变化的admin 站点。

     

   3. 进入 sites.py

图片 2

    看名就能够知道意思是生成站点的文本,一共五个指标,八个类

    第三个对象是”弱集结“,

    第一个对象是大家供给的站点

    第两个类是一度登记的不得了,继承了Exception,第二个类是尚未登记的极度,同样一连了Exception,无内容,四个用来抛非凡的类。

    第四个是器重关怀,生成站点的类AdminSite。

class AdminSite(object):
    """
    An AdminSite object encapsulates an instance of the Django admin application, ready
    to be hooked in to your URLconf. Models are registered with the AdminSite using the
    register() method, and the get_urls() method can then be used to access Django view
    functions that present a full admin interface for the collection of registered
    models.

    一个AdminSite对象封装了Django管理应用程序的一个实例,准备被挂钩到你的URLconf。
    使用register()方法向AdminSite注册模型,
    然后可以使用get_urls()方法访问为注册模型集合提供完整管理界面的Django视图函数。
    """

    # Text to put at the end of each page's <title>.
    # 放在每页<title>的文本
    site_title = ugettext_lazy('Django site admin')

    # Text to put in each page's <h1>.
    # 放在每页<h1>的文本
    site_header = ugettext_lazy('Django administration')

    # Text to put at the top of the admin index page.
    # 放在admin 主页顶部的文本
    index_title = ugettext_lazy('Site administration')

    # URL for the "View site" link at the top of each admin page.
    # 根url
    site_url = '/'

    _empty_value_display = '-'

    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

  3. 登陆admin后台进行保管。 

 下边来看 AdminSite 的 23个主意和有关内容

 def __init__(self, name='admin'):
        self._registry = {}                                                 # model_class class -> admin_class instance 将model_class类转为admin_class实例,也就是我们的表放的地方
        self.name = name                                                    # 站点名
        self._actions = {'delete_selected': actions.delete_selected}        # 默认行为,删除选中,在actions.py 中只有这一个方法
        self._global_actions = self._actions.copy()                         # 全局行为,复制默认行为
        all_sites.add(self)                                                 # 将实例加入all_sites 这个’弱集合’

 解释:
初步化一些变量,一些艺术如 delete_  3. 登陆admin后台进行保管。selected,一时不钻探之中怎样促成。

 

    def check(self, app_configs):
        """
        Run the system checks on all ModelAdmins, except if they aren't customized at all.

        如果没有自定义,就对所有ModelAdmins进行系统检查
        """
        if app_configs is None:
            app_configs = apps.get_app_configs()                                           # 没有传配置,就去apps对象中拿配置信息 
        app_configs = set(app_configs)                                                     # Speed up lookups below 加速下面查找(去重)

        errors = []
        modeladmins = (o for o in self._registry.values() if o.__class__ is not ModelAdmin)#生成器加递归检查,将不是ModelAdmin的对象放入erros列表
        for modeladmin in modeladmins:
            if modeladmin.model._meta.app_config in app_configs:
                errors.extend(modeladmin.check())
        return errors

 

 解释:apps 是django.apps.register.py 中 Apps
类实例的一个对象,存款和储蓄已安装应用程序配置的注册表。它也追踪模型,比如。
提供反向关系。后续有的时候间斟酌。

  3. 登陆admin后台进行保管。    那些措施主要得到安排消息和错误对象。

    def register(self, model_or_iterable, admin_class=None, **options):
        """
        Registers the given model(s) with the given admin class.
        用提供的admin 类 注册给的表 model
        The model(s) should be Model classes, not instances.
        必须给Model类,而不是实例
        If an admin class isn't given, it will use ModelAdmin (the default
        admin options). If keyword arguments are given -- e.g., list_display --
        they'll be applied as options to the admin class.
        如果没有指定admin类,会用默认的ModelAdmin,如果给了关键词参数,如list_display,
        他们会被作为选项应用在admin类中
        If a model is already registered, this will raise AlreadyRegistered.
        如果一个model 已经被注册了,会报AlreadyRegistered异常
        If a model is abstract, this will raise ImproperlyConfigured.
        如果一个model是抽象的,这会引起ImproperlyConfigured异常。
        """
        if not admin_class:
            admin_class = ModelAdmin                                              # 如果没指定,就用ModelAdmin 

        if isinstance(model_or_iterable, ModelBase):                              # 如果输入的是一个代表表格的类,就把它变成列表,所以能传类或者列表,ModelBase是Model的元类
            model_or_iterable = [model_or_iterable]
        for model in model_or_iterable:                                           # 判断列表中每个类是不是抽象类,如果是,抛出异常,背后比较复杂,在ModelBase中实现,有空研究
            if model._meta.abstract:                                           
                raise ImproperlyConfigured(
                    'The model %s is abstract, so it cannot be registered with admin.' % model.__name__
                )

            if model in self._registry:
                raise AlreadyRegistered('The model %s is already registered' % model.__name__) #如果已经注册,抛出异常

            # Ignore the registration if the model has been 
            # swapped out.
            if not model._meta.swapped:                                                     #如果没有被 swapped,继续,同样在ModelBase 中属性,不太明白
                # If we got **options then dynamically construct a subclass of              #生成自定义配置
                # admin_class with those **options.
                if options:
                    # For reasons I don't quite understand, without a __module__            # 作者也不知道为什么,就是要加__model__属性
                    # the created class appears to "live" in the wrong place,
                    # which causes issues later on.
                    options['__module__'] = __name__
                    admin_class = type("%sAdmin" % model.__name__, (admin_class,), options) # 用type函数将自定义属性添加到默认的ModelAdmin 中,生成新的类

                # Instantiate the admin class to save in the registry                       # 将表格的类作为键,将ModelAdmin或自定义后的ModelAdmin 用 该类和site实例 生成的
                self._registry[model] = admin_class(model, self)                            # 作为键值

 

 

表明:1. 该函数目标是将我们的报表和管制的类结合一一对应下来,

      2. ype函数有三种用法:

    type(object) -> the object's type
    type(name, bases, dict) -> a new type

   3. **options  是可扩张的效劳,在admin 的options.py
中有详尽列出,之后在高端定制中商讨。

 

    def unregister(self, model_or_iterable):
        """
        Unregisters the given model(s).

        If a model isn't already registered, this will raise NotRegistered.
        """
        if isinstance(model_or_iterable, ModelBase):
            model_or_iterable = [model_or_iterable]
        for model in model_or_iterable:
            if model not in self._registry:
                raise NotRegistered('The model %s is not registered' % model.__name__)
            del self._registry[model]
  

   def is_registered(self, model):
        """
        Check if a model class is registered with this `AdminSite`.
        """
        return model in self._registry

 

 解释: 打消注册和判别是或不是注册,本质正是推断指标是否在大家转换的字典中

 

    def add_action(self, action, name=None):
        """
        Register an action to be available globally.
     注册新的操作
        """
        name = name or action.__name__
        self._actions[name] = action
        self._global_actions[name] = action

    def disable_action(self, name):
        """
        Disable a globally-registered action. Raises KeyError for invalid names.
        删除已有操作
        """
        del self._actions[name]

    def get_action(self, name):
        """
        Explicitly get a registered global action whether it's enabled or
        not. Raises KeyError for invalid names.
     返回全局操作,无论是否运行 ,
        """
        return self._global_actions[name]

    @property
    def actions(self):
        """
        Get all the enabled actions as an iterable of (name, func).
     获得所有运行的操作组成的可迭代的元组,如(name,func),property装饰器将方法变为属性调用
        """
        return six.iteritems(self._actions)

  3. 登陆admin后台进行保管。 解释:1. 操作增加和删除改查的作为,暗中认可是去除选中这一种,

    2. six.iteritems  目标, 兼容py2达成    将目的字典转为 迭代器

    @property
    def empty_value_display(self):
        return self._empty_value_display

    @empty_value_display.setter
    def empty_value_display(self, empty_value_display):
        self._empty_value_display = empty_value_display

 

 解释:暗中认可空值展现 ’-‘, 
可以自定义空值符号,调用property的setter方法完毕

 

 def has_permission(self, request):
        """
        Returns True if the given HttpRequest has permission to view  #检查登录权限
        *at least one* page in the admin site.
        """
        return request.user.is_active and request.user.is_staff
  
  
 def admin_view(self, view, cacheable=False):
    """
    Decorator to create an admin view attached to this ``AdminSite``. This
    wraps the view and provides permission checking by calling
    ``self.has_permission``.

    You'll want to use this from within ``AdminSite.get_urls()``:

        class MyAdminSite(AdminSite):

            def get_urls(self):
                from django.conf.urls import url

                urls = super(MyAdminSite, self).get_urls()
                urls += [
                    url(r'^my_view/$', self.admin_view(some_view))
                ]
                return urls

    By default, admin_views are marked non-cacheable using the
    ``never_cache`` decorator. If the view can be safely cached, set
    cacheable=True.

  用来创造添在这个"AdminSite"的视图函数的装饰器,其中调用 self.has_permission 检查权限,
  我们也可以用此函数来自定义我们需要在admin后台出现的视图
  默认是不缓存,如果确认是安全缓存的,就设置  cacheable = False
    """
    def inner(request, *args, **kwargs):
        if not self.has_permission(request):                                              #如果没有权限,
            if request.path == reverse('admin:logout', current_app=self.name):        #如果为登出,就转到首页
                index_path = reverse('admin:index', current_app=self.name)
                return HttpResponseRedirect(index_path)
            # Inner import to prevent django.contrib.admin (app) from                     # 在此处导入而不是开头是因为要防止从无关的用户认证组件导入
            # importing django.contrib.auth.models.User (unrelated model).
            from django.contrib.auth.views import redirect_to_login
            return redirect_to_login(
                request.get_full_path(),
                reverse('admin:login', current_app=self.name)                              #记录想去的页面之后,跳转登录页面,登录成功进入想去页面
            )
        return view(request, *args, **kwargs)
    if not cacheable:
        inner = never_cache(inner)                                                         # 通过 never_cache 闭包函数在request上加header 设置不缓存
    # We add csrf_protect here so this function can be used as a utility
    # function for any view, without having to repeat 'csrf_protect'.
    if not getattr(view, 'csrf_exempt', False):                                            # 如果没有明确说 取消"csrf"机制,那就通过 csrf_poctect 闭包添加
        inner = csrf_protect(inner)
    return update_wrapper(inner, view)

 解释:用来创立admin自身的视图函数。

 def get_urls(self):
        from django.conf.urls import url, include
        # Since this module gets imported in the application's root package,
        # it cannot import models from other applications at the module level,
        # and django.contrib.contenttypes.views imports ContentType.
    """
    这个模块在app 根包里导入了,它无法在其他app 里从模块水平导入,

       """

        from django.contrib.contenttypes import views as contenttype_views

        def wrap(view, cacheable=False):
            def wrapper(*args, **kwargs):
                return self.admin_view(view, cacheable)(*args, **kwargs)
            wrapper.admin_site = self
            return update_wrapper(wrapper, view)

        # Admin-site-wide views.
        urlpatterns = [
            url(r'^$', wrap(self.index), name='index'),
            url(r'^login/$', self.login, name='login'),
            url(r'^logout/$', wrap(self.logout), name='logout'),
            url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'),
            url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True),
                name='password_change_done'),
            url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
            url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut),
                name='view_on_site'),
        ]

        # Add in each model's views, and create a list of valid URLS for the app_index
     # 生成每一个表的视图函数和url列表,appname/modelname/   开头,

        valid_app_labels = []
        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)),
            ]
            if model._meta.app_label not in valid_app_labels:
                valid_app_labels.append(model._meta.app_label)

        # If there were ModelAdmins registered, we should have a list of app
        # labels for which we need to allow access to the app_index view,
     # 如果有注册的表,生成到显示某个app内所有表格信息的页面。

        if valid_app_labels:
            regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
            urlpatterns += [
                url(regex, wrap(self.app_index), name='app_list'),
            ]
        return urlpatterns

  @property
  def urls(self):
      return self.get_urls(), 'admin', self.name

表达:很猛烈,那么些函数是生成url的主导函数,url列表富含:固定的(login
logout 等),根据注册表拼接的(app名/表名/),还应该有某一app(app名/)

 

 

    def each_context(self, request):
        """
        Returns a dictionary of variables to put in the template context for
        *every* page in the admin site.

        For sites running on a subpath, use the SCRIPT_NAME value if site_url
        hasn't been customized.
     
        返回一个每页都有的变量组成的字典,在子路径的页面,如果没有定制,就用SCRIPT_NAME 的值

        """
        script_name = request.META['SCRIPT_NAME']
        site_url = script_name if self.site_url == '/' and script_name else self.site_url
        return {
            'site_title': self.site_title,
            'site_header': self.site_header,
            'site_url': site_url,
            'has_permission': self.has_permission(request),
            'available_apps': self.get_app_list(request),
        }

表明: 用来传递通用变量

 def password_change(self, request, extra_context=None):
        """
        Handles the "change password" task -- both form display and validation.
     解决改密码任务, 表单展示和验证
        """
        from django.contrib.admin.forms import AdminPasswordChangeForm                          
        from django.contrib.auth.views import PasswordChangeView
        url = reverse('admin:password_change_done', current_app=self.name)
        defaults = {
            'form_class': AdminPasswordChangeForm,
            'success_url': url,
            'extra_context': dict(self.each_context(request), **(extra_context or {})),
        }
        if self.password_change_template is not None:
            defaults['template_name'] = self.password_change_template
        request.current_app = self.name
        return PasswordChangeView.as_view(**defaults)(request)                    #as_view  完整性检查

    def password_change_done(self, request, extra_context=None):
        """
        Displays the "success" page after a password change.
     展示修改密码成功界面
        """
        from django.contrib.auth.views import PasswordChangeDoneView
        defaults = {
            'extra_context': dict(self.each_context(request), **(extra_context or {})),
        }
        if self.password_change_done_template is not None:
            defaults['template_name'] = self.password_change_done_template
        request.current_app = self.name
        return PasswordChangeDoneView.as_view(**defaults)(request)

    def i18n_javascript(self, request, extra_context=None):
        """
        Displays the i18n JavaScript that the Django admin requires.

        `extra_context` is unused but present for consistency with the other
        admin views.
     展示 Django admin 需要的多语言js
     
        """
        return JavaScriptCatalog.as_view(packages=['django.contrib.admin'])(request)

解释: 
逻辑同样,先安装暗中同意字典,有成功后url,当前表单,额外上下文变量(在暗中同意中加多),模板名(私下认可或自定义),

    传入cbv的PasswordChangeView,达成修改密码,等视图函数

 

 @never_cache
    def logout(self, request, extra_context=None):
        """
        Logs out the user for the given HttpRequest.

        This should *not* assume the user is already logged in.
        """
        from django.contrib.auth.views import LogoutView
        defaults = {
            'extra_context': dict(
                self.each_context(request),
                # Since the user isn't logged out at this point, the value of
                # has_permission must be overridden.
                has_permission=False,
                **(extra_context or {})
            ),
        }
        if self.logout_template is not None:
            defaults['template_name'] = self.logout_template
        request.current_app = self.name
        return LogoutView.as_view(**defaults)(request)

    @never_cache
    def login(self, request, extra_context=None):
        """
        Displays the login form for the given HttpRequest.
        """
        if request.method == 'GET' and self.has_permission(request):
            # Already logged-in, redirect to admin index
            index_path = reverse('admin:index', current_app=self.name)
            return HttpResponseRedirect(index_path)

        from django.contrib.auth.views import LoginView
        # Since this module gets imported in the application's root package,
        # it cannot import models from other applications at the module level,
        # and django.contrib.admin.forms eventually imports User.
        from django.contrib.admin.forms import AdminAuthenticationForm
        context = dict(
            self.each_context(request),
            title=_('Log in'),
            app_path=request.get_full_path(),
            username=request.user.get_username(),
        )
        if (REDIRECT_FIELD_NAME not in request.GET and
                REDIRECT_FIELD_NAME not in request.POST):
            context[REDIRECT_FIELD_NAME] = reverse('admin:index', current_app=self.name)
        context.update(extra_context or {})

        defaults = {
            'extra_context': context,
            'authentication_form': self.login_form or AdminAuthenticationForm,
            'template_name': self.login_template or 'admin/login.html',
        }
        request.current_app = self.name
        return LoginView.as_view(**defaults)(request)

解释:login logout 同上

   def _build_app_dict(self, request, label=None):
        """
        Builds the app dictionary. Takes an optional label parameters to filter
        models of a specific app.
        """
        app_dict = {}

        if label:
            models = {
                m: m_a for m, m_a in self._registry.items()
                if m._meta.app_label == label
            }
        else:
            models = self._registry

        for model, model_admin in models.items():
            app_label = model._meta.app_label

            has_module_perms = model_admin.has_module_permission(request)
            if not has_module_perms:
                continue

            perms = model_admin.get_model_perms(request)

            # Check whether user has any perm for this module.
            # If so, add the module to the model_list.
            if True not in perms.values():
                continue

            info = (app_label, model._meta.model_name)
            model_dict = {
                'name': capfirst(model._meta.verbose_name_plural),
                'object_name': model._meta.object_name,
                'perms': perms,
            }
            if perms.get('change'):
                try:
                    model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
                except NoReverseMatch:
                    pass
            if perms.get('add'):
                try:
                    model_dict['add_url'] = reverse('admin:%s_%s_add' % info, current_app=self.name)
                except NoReverseMatch:
                    pass

            if app_label in app_dict:
                app_dict[app_label]['models'].append(model_dict)
            else:
                app_dict[app_label] = {
                    'name': apps.get_app_config(app_label).verbose_name,
                    'app_label': app_label,
                    'app_url': reverse(
                        'admin:app_list',
                        kwargs={'app_label': app_label},
                        current_app=self.name,
                    ),
                    'has_module_perms': has_module_perms,
                    'models': [model_dict],
                }

        if label:
            return app_dict.get(label)
        return app_dict

    def get_app_list(self, request):
        """
        Returns a sorted list of all the installed apps that have been
        registered in this site.
        """
        app_dict = self._build_app_dict(request)

        # Sort the apps alphabetically.
        app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())

        # Sort the models alphabetically within each app.
        for app in app_list:
            app['models'].sort(key=lambda x: x['name'])

        return app_list

解释: 建立app的字典 在排序

 

    @never_cache
    def index(self, request, extra_context=None):
        """
        Displays the main admin index page, which lists all of the installed
        apps that have been registered in this site.
        """
        app_list = self.get_app_list(request)

        context = dict(
            self.each_context(request),
            title=self.index_title,
            app_list=app_list,
        )
        context.update(extra_context or {})

        request.current_app = self.name

        return TemplateResponse(request, self.index_template or 'admin/index.html', context)

    def app_index(self, request, app_label, extra_context=None):
        app_dict = self._build_app_dict(request, app_label)
        if not app_dict:
            raise Http404('The requested admin page does not exist.')
        # Sort the models alphabetically within each app.
        app_dict['models'].sort(key=lambda x: x['name'])
        app_name = apps.get_app_config(app_label).verbose_name
        context = dict(
            self.each_context(request),
            title=_('%(app)s administration') % {'app': app_name},
            app_list=[app_dict],
            app_label=app_label,
        )
        context.update(extra_context or {})

        request.current_app = self.name

        return TemplateResponse(request, self.app_index_template or [
            'admin/%s/app_index.html' % app_label,
            'admin/app_index.html'
        ], context)

表达: index 好理解,正是将以前的拍卖数据渲染主页模板,app_index 就是突显全体app 的页面

计算:25 种办法
完结了admin站点的基本成效和接口,有登记方面,操作方面,私下认可空值符,生成url,修改密码,登入登出,主页。

里面含有了广大编制程序观念和艺术,值得持续深切商讨。

 

五、总结

 在那篇小说中,通过着力选拔,剖析了admin组件第一步相关的sites源码,精通了site
这些指标的结构格局和带有方法。 

 

 

 

 

 

 

 

相关文章