• 第九章Admin后台系统


    第九章 Admin后台系统

    默认启用的Admin后台系统
    'django.contrib.admin',
    创建账户密码之前,确保项目已执行数据迁移,在数据库中创建相应的数据表

    中文显示后台

    把该中间件位于settings.py中的第三个中间件的位置
    'django.middleware.locale.LocaleMiddleware',

    注册模型到后台的两种方法

    from django.contrib import admin
    from .models import *
    
    
    # 方法一:
    # 将模型直接注册到admin后台
    # admin.site.register(PersonInfo)
    
    # 方法二:
    # 自定义PersonInfoAdmin类并继承ModelAdmin
    # 注册方法一,使用装饰器将PersonInfoAdmin和Product绑定
    @admin.register(PersonInfo)
    class PersonInfoAdmin(admin.ModelAdmin): 
        # 设置显示的字段
        list_display = ['name', 'age'] # list_display显示人员信息
    # 注册方法二
    # admin.site.register(PersonInfo, PersonInfoAdmin)
    后台默认会有增加xxx(设置verbose_name即为该字段,否则为数据表名)
    

    源码分析 ModelAdmin

    位置:django/contrib/admin/options.py中
    ModelAdmin继承自BaseModelAdmin,而父类BaseModelAdmin的元类为MediaDefiningClass,因此Admin系统的属性和方法来及ModelAdmin和BaseModelAdmin
    列出日常开发中常用的属性和方法(关于admin后台该展示的字段和方法)

    fields 在数据新增或修改的页面设置可编辑的字段
    exclude 在数据新增或修改的页面设置不可编辑的字段
    fieldsets 改变新增或修改页面的网页布局
    
    # 在数据列表页设置日期选择器
        date_hierarchy = 'recordTime'
    # 设置可搜索的字段
        search_fields = ['job', 'title']
     # 为数据列表页的字段id和job设置编辑状态
        list_editable = ['job', 'title']
    

    实例models.py

    from django.db import models
    
    # Create your models here.
    class PersonInfo(models.Model):
        id = models.AutoField(primary_key=True)
        name = models.CharField(max_length=20)
        age = models.IntegerField()
    
        def __str__(self):
            return self.name
        class Meta:
            verbose_name = '人员信息'
    
    class Vocation(models.Model):
        JOB = (
            ('软件开发', '软件开发'),
            ('软件测试', '软件测试'),
            ('需求分析', '需求分析'),
            ('项目管理', '项目管理'),
        )
        id = models.AutoField(primary_key=True)
        job = models.CharField(max_length=20, choices=JOB)
        title = models.CharField(max_length=20)
        payment = models.IntegerField(null=True, blank=True)
        person = models.ForeignKey(PersonInfo, on_delete=models.CASCADE)
        recordTime = models.DateField(auto_now=True, null=True, blank=True)
    
        def __str__(self):
            return str(self.id)
        class Meta:
            verbose_name = '职业信息'
    

    admin.py

    from django.contrib import admin
    from .models import *
    
    
    # 方法一:
    # 将模型直接注册到admin后台
    # admin.site.register(PersonInfo)
    
    # 方法二:
    # 自定义PersonInfoAdmin类并继承ModelAdmin
    # 注册方法一,使用装饰器将PersonInfoAdmin和Product绑定
    @admin.register(PersonInfo)
    class PersonInfoAdmin(admin.ModelAdmin):
        # 设置显示的字段
        list_display = ['id', 'name', 'age']
    
    
    # 注册方法二
    # admin.site.register(PersonInfo, PersonInfoAdmin)
    
    
    @admin.register(Vocation)
    class VocationAdmin(admin.ModelAdmin):
        # 在数据新增或修改的页面设置可编辑的字段
        # fields = ['job','title','payment','person']
    
        # 在数据新增或修改的页面设置不可编辑的字段
        # exclude = []
    
        # 改变新增或修改页面的网页布局(admin后台的新增或修改,修改直接点解id号就可以)
        fieldsets = (
            ('职业信息', {
                'fields': ('job', 'title', 'payment')
            }),  
            ('人员信息', { # 第一项是显示信息,第二项是隐藏信息
                # 设置隐藏与显示
                'classes': ('collapse',),
                'fields': ('person',),
            }),
        )
    
        # 将下拉框改为单选按钮
        # admin.HORIZONTAL是水平排列
        # admin.VERTICAL是垂直排列
        radio_fields = {'person': admin.VERTICAL}
    
        # 在数据新增或修改的页面设置可读的字段,不可编辑
        # readonly_fields = ['job',]
    
        # 设置排序方式,['id']为升序,降序为['-id']
        ordering = ['id']
    
        # 设置数据列表页的每列数据是否可排序显示
        sortable_by = ['job', 'title']
    
        # 在数据列表页设置显示的模型字段
        list_display = ['id', 'job', 'title', 'payment', 'person']
    
        # 为数据列表页的字段id和job设置路由地址,该路由地址可进入数据修改页
        # list_display_links = ['id', 'job']
    
        # 设置过滤器,如有外键,应使用双下画线连接两个模型的字段
        list_filter = ['job', 'title', 'person__name']
    
        # 在数据列表页设置每一页显示的数据量
        list_per_page = 100
    
        # 在数据列表页设置每一页显示最大上限的数据量
        list_max_show_all = 200
    
        # 为数据列表页的字段id和job设置编辑状态
        list_editable = ['job', 'title']
    
        # 设置可搜索的字段
        search_fields = ['job', 'title']
    
        # 在数据列表页设置日期选择器
        date_hierarchy = 'recordTime'
    
        # 在数据修改页添加“另存为”功能
        save_as = True
    
        # 设置“动作”栏的位置
        actions_on_top = False
        actions_on_bottom = True
    
    

    admin首页设置

    修改title和header(登录界面),在admin.py中添加即可

    admin.site.site_title = 'MyDjango后台管理' # 这个是显示在url栏目的上方的,像是网页的title
    admin.site.site_header = 'MyDjango' # 只有这个显示在登录界面
    

    若想将app应用的英文改为中文(后台管理页面的),也就是修改app在后台的名称,在admin所在文件夹的__init__.py文件夹下设置即可
    当然下面方法可以复用到任何一个app下,但主项目文件夹下不可以
    只需要复写default_app_config,他的构成是前半部分是应用名,后半部分统一为IndexConfig。所以只需根据apps.py中的name属性的值更改。
    还需更改verbose_name,这个为真正的应用名

    from django.apps import AppConfig
    import os
    # 修改App在Admin后台显示的名称
    # default_app_config的值的前半部分是应用名,后半部分统一为IndexConfig
    default_app_config = 'index.IndexConfig'
    
    # 获取当前App的命名
    def get_current_app_name(_file):
        return os.path.split(os.path.dirname(_file))[-1]
    
    # 重写类IndexConfig
    class IndexConfig(AppConfig):
        name = get_current_app_name(__file__)
        verbose_name = 'index应用' # 改应用名字在这里修改
    

    admin后台系统首页的设置

    包括:

    项目应用的显示名称 :

    init.py中

    模型的显示名称:

    models.py中的 verbose_name和verbose_name_plural

    网页标题:

    admin.py中设置admin.site.site_title 和
    admin.site.site_head属性
    如果项目中有多个应用,只需在一个应用下的admin.py设置即可(网页标题)

    实现Admin的二次开发

    重写ModelAdmin的方法,以下介绍的函数均是

    get_readonly_fields

    get_readonly_fields函数是由BaseModelAdmin定义的,他获取readonly_fields的属性值,从而将模型字段设为只读属性,通过重写此函数可以自定义模型字段的只读属性,比如根据不同的用户角色来设置模型字段的只读属性。

    # 重写get_readonly_fields函数
    # 设置超级管理员和普通用户的权限
    def get_readonly_fields(self, request, obj=None):
        # request是当前的请求对象,obj是模型对象默认值为None,代表当前网页为数据新增页,否则为数据修改页
        if request.user.is_superuser: # 判断用户是否为超级管理员
            self.readonly_fields = [] # 只读字段为空
        else:
            self.readonly_fields = ['payment'] # readonly_fields设置为只读字段
        return self.readonly_fields
    

    设置字段样式

    数据列表页显示的模型字段是由list_display设置的,每个字段的数据都来自数据表,并且数据以固定的字体格式显示在网页上,若要对某些字段的数据进行特殊处理,如设置数据的字体颜色,。

    # 自定义函数,设置字体颜色
    # 但他不是模型字段,可在admin.py中用list_display.append('colored_name')加入模型的数据列表页
    def colored_name(self):
        if 'Lucy' in self.person.name: # 获取PersonInfo数据表的字段name,缘由person是外键
            color_code = 'red'
        else:
            color_code = 'blue'
        return format_html( # format_html内置的html转义
            '<span style="color: {};">{}</span>', # 行内样式
            color_code,
            self.person.name,
        )
    
    # 设置Admin的标题
    # short_description属性使该colored_name()函数以字段的形式显示在模型Vocation的数据列表页
    colored_name.short_description = '带颜色的姓名' # 带颜色的姓名是标题,
    
    # 捕获的Django内置函数和属性,即不需要导包的
    # short_description使该模型类中的函数(colored_name())以字段的形式显示在模型(Vocation)的数据列表页
    # format_html()内置的html转义,使用方法类似于format的f用法;f'name:{变量名}',这里变成了逗号分隔,这里是函数所以这样传参
    

    函数get_queryset()

    用于查询模型的数据信息,然后再Admin的数据列表页展示。默认情况下,该函数执行全表数据查询,若要改变数据的查询方式,则可重新定义该函数,比如根据不同的用户角色执行不同的数据查询。

    # 根据当前用户名设置数据访问权限
    def get_queryset(self, request):
        qs = super().get_queryset(request)  # 调用父类的该方法获取查询对象,用该查询对象查询模型Vocation的全部数据,否则返回模型字段id小于2的数据
        if request.user.is_superuser:
            return qs
        else:
            return qs.filter(id__lt=2)
    

    formfield_for_foreignkey

    新增或修改数据时,设置外键可选值,就是该字段为下拉框(外键通常都是一对多,一个person,对应着各种名字)
    formfield_for_foreignkey只适用于模型一对一或一对多;多对多为formfield_for_manytomany

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == 'person': # db_field模型的字段对象,因为一个模型可以定义多个外键字段,所以需要对特定的字段做判断
            if not request.user.is_superuser: # request为当前用户的请求对象
                v = Vocation.objects.filter(id__lt=2)
                # 将该模型字段id小于2的数据作为模型PersonInfo的查询条件参数
                kwargs['queryset'] = PersonInfo.objects.filter(id__in=v) # 这其实就是外键关联查询吧
                # 上述就是将整个查询得到的对象打包成字典对象发给父对象的方法
          # 传递形参给父类的方法,由父类的函数从形参kwargs中获取参数queryset,从而实现数据的过滤
        return super().formfield_for_foreignkey(db_field, request, **kwargs) # 不用父类试试,报错RecursionError: maximum recursion depth exceeded
    

    formfield_for_choice_field

    过滤下拉框数据

        # 内置函数formfield_for_choice_field虽然能新增下拉框的选项内容,
        # 但在保存数据的过程中,Django会提示新增的选项内容是无效的,因此该函数常用于过滤已存在的选项。
        # db_field.choices获取模型字段的属性choices的值 ;这里就是重新定义内置函数formfield_for_choice_field
        def formfield_for_choice_field(self, db_field, request, **kwargs):
            if db_field.name == 'job': # 过滤下拉框的字段,默认为全部已添加的字段,再models.py中以定义
                # 减少字段job可选的选项 
                kwargs['choices'] = (('软件开发', '软件开发'),
                                     ('软件测试', '软件测试'),)
            return super().formfield_for_choice_field(db_field, request, **kwargs)
    
        # 如果要对字段的下拉框’新增‘内容,可以重新定义formfield_for_dbfield函数
        # Django首先执行formfield_for_dbfield,然后再执行formfield_for_choice_field
        # 如果我们都重写了formfield_for_dbfield和formfield_for_choice_field
        # 最后下拉框的选项以formfield_for_choice_field为准
        def formfield_for_dbfield(self, db_field, request, **kwargs):
            if db_field.name == 'job':
                # 必须判断选项内容是否已存在db_field.choices,如果不判断,则会重复新增选项
                if ('网页设计', '网页设计') not in db_field.choices:
                    db_field.choices += (('网页设计', '网页设计'),)
            return super().formfield_for_dbfield(db_field, request, **kwargs)
    
    

    save_model()

    在后台页面单击添加(修改)XXX信息,单击保存按钮所触发的功能,该函数主要对输入的数据进行入库或修改处理。
    若想加入的是一些特殊功能,可进行复写,如:添加日志数据,把你需要修改的字段写入文件中

        def save_model(self, request, obj, form, change):
            if change: # change判断当前请求是来自数据修改页还是来自数据新增页,数据修改页则为true
                # 获取当前用户名
                user = request.user.username
                # 使用模型获取数据,’pk代表具有主键属性的字段‘
                job = self.model.objects.get(pk=obj.pk).job
                # 使用表单获取数据
                person = form.cleaned_data['person'].name
                # 写入日志文件
                f = open('d://log.txt', 'a')
                f.write(person + '职位:' + job + ',被' + user + '修改' + '\r\n')
                f.close()
            else:
                pass
            # 使用super在继承父类已有的功能下新增自定义功能
            # 不调用super()方法的话,程序只执行保存日志,但不执行数据入库和修改处理
            super().save_model(request, obj, form, change)
    

    同样的执行数据删除操作也是一样的,Django调用函数delete_model()实现,该函数设有的默认参数为self,request.可以仿照save_model()函数来进行修改。可以实现在你删除数据时候,把删除的字段添加到日志文件中(本地新建个)

    数据批量操作

    def get_datas(self, request, queryset):
        temp = []
        for d in queryset: # 从已被勾选的数据对象里获取模型字段的数据内容
            t = [d.job, d.title, str(d.payment), d.person.name] # 每行数据以列表t表示
            temp.append(t) # 将列表t加入列表
        f = open('d://data.txt', 'a')
        for t in temp:
            f.write(','.join(t) + '\r\n') # 写入数据\r\n \r\n,这样写就不会连在一起了
            
        f.close()
        # 设置提示信息
        self.message_user(request, '数据导出成功!')
    # 设置函数的显示名称
    get_datas.short_description = '导出所选数据'
    # 添加到“动作”栏
    actions = ['get_datas'] # 内置属性actions
    
    

    自定义Admin模板

    源码路径:django/contrib/admin/templates/admin
    这里使用继承admin模板,来自定义开发,最好别修改admin初始的模板,缘由是整个django的admin都会被更改,如果你没使用虚拟环境的话。
    过程:
    在模板文件templates下依次创建文件夹admin和index.
    1.文件夹admin代表该文件夹里的模板文件用于Admin后台系统,而且文件夹必须命名为admin
    2.文件夹index代表项目应用index,文件夹的命名必须与项目应用的命名一致。文件夹存放模板文件change_form.html,所有在项目应用index中定义的模型都会使用该模板文件生成网页信息。
    3.如果将模板文件change_form.html放在admin文件夹下,那么整个Admin后台系统都会使用该模板文件生成网页信息。
    重写的change_form.html来自Django内置模板文件admin/change_form.html
    该实例是判断用户角色不同,来显示后台对应的页面,当然是在原始admin后台基础上新增了一部分

    {% extends "admin/change_form.html" %}
    {% load i18n admin_urls static admin_modify %} <!--内置模板admin/change_form.html导入了该模板,所以自定义模板也需要-->
    {% block object-tools-items %}
        {# 判断当前用户角色 #}
        {% if request.user.is_superuser %}
            <li>
                {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
                <a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
            </li>
        {# 判断结束符 #}
        {% endif %}
        {% if has_absolute_url %}
            <li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>
        {% endif %}
    {% endblock %}
    

    自定义Admin后台系统

    Admin后台系统为每个网页设置了具体的路由地址(啥意思)
    重新定义Admin后台系统,比如常见的Xadmin和Django Suit,这些插件都是在Admin后台系统的基础上进行重新定义的
    admin后台系统由类AdminSite实例化创建而成的,换句话说,重新定义类AdminSite即可实现Admin后台系统的自定义开发(源码位置:django/contrib/admin/sites.py)

    admin后台系统的注册过程

    当运行django时,Admin后台系统会随之运行。Admin的系统注册过程在源码apps.py中定义。
    django.contrib.admin 中的 apps.py

    综上

    要实现Admin后台系统的自定义开发,就需要重新定义类AdminSite和改变Admin的系统注册过程。

    简单讲解如何更换Admin后台系统的登录页面

    在项目更目录下创建static文件,并加入所需的javascript文件和css文件,然后再把templates文件夹中放置登录页面login.html,最后在MyDjango文件夹创建文件myadmin.py和myapps.py.

    实例

    login.html

    <!DOCTYPE html>
    <html>
    <head>
        {% load static %}
    	<title>MyDjango后台登录</title>
    	<link rel="stylesheet" href="{% static "css/reset.css" %}">
    	<link rel="stylesheet" href="{% static "css/user.css" %}">
        <script src="{% static "js/jquery.min.js" %}"></script>
        <script src="{% static "js/user.js" %}"></script>
    </head>
    <body>
    <div class="page">
    	<div class="loginwarrp">
    		<div class="logo">用户登录</div>
            <div class="login_form">
    			<form id="Login" name="Login" method="post" action="">
                    {% csrf_token %}
    				<li class="login-item">
    					<span>用户名:</span>
    					<input type="text" name="username" class="login_input">
                        <span id="count-msg" class="error"></span>
    				</li>
    				<li class="login-item">
    					<span>密 码:</span>
    					<input type="password" name="password" class="login_input">
                        <span id="password-msg" class="error"></span>
    				</li>
    				<li class="login-sub">
    					<input type="submit" name="Submit" value="登录">
    				</li>				
               </form>
    		</div>
    	</div>
    </div>
    <script type="text/javascript">
    	window.onload = function() {
    		var config = {
    			vx : 4,
    			vy : 4,
    			height : 2,
    			width : 2,
    			count : 100,
    			color : "121, 162, 185",
    			stroke : "100, 200, 180",
    			dist : 6000,
    			e_dist : 20000,
    			max_conn : 10
    		};
    		CanvasParticle(config);
    	}
    </script>
    <script src="{% static "js/canvas-particle.js" %}"></script>
    </body>
    </html>
    

    myadmin.py
    在myadmin.py中定义类MyAdminSite,他继承父类admin.AdminSite并重写方法admin_view()和get_urls(),从而更改admin()后台的登录界面

    from django.contrib import admin
    from functools import update_wrapper
    from django.views.generic import RedirectView
    from django.urls import reverse
    from django.views.decorators.cache import never_cache
    from django.views.decorators.csrf import csrf_protect
    from django.http import HttpResponseRedirect
    from django.contrib.auth.views import redirect_to_login
    from django.urls import include, path, re_path
    from django.contrib.contenttypes import views as contenttype_views
    class MyAdminSite(admin.AdminSite):
        def admin_view(self, view, 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)
                    # 修改注销后重新登录的路由地址
                    return redirect_to_login(
                        request.get_full_path(),
                        '/login.html'
                    )
                return view(request, *args, **kwargs)
            if not cacheable:
                inner = never_cache(inner)
            if not getattr(view, 'csrf_exempt', False):
                inner = csrf_protect(inner)
            return update_wrapper(inner, view)
    
        def get_urls(self):
            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)
            urlpatterns = [
                path('', wrap(self.index), name='index'),
                # 修改登录界面的路由地址
                path('login/', RedirectView.as_view(url='/login.html')),
                path('logout/', wrap(self.logout), name='logout'),
                path('password_change/', wrap(self.password_change, cacheable=True), name='password_change'),
                path(
                    'password_change/done/',
                    wrap(self.password_change_done, cacheable=True),
                    name='password_change_done',
                ),
                path('jsi18n/', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
                path(
                    'r/<int:content_type_id>/<path:object_id>/',
                    wrap(contenttype_views.shortcut),
                    name='view_on_site',
                ),
            ]
            valid_app_labels = []
            for model, model_admin in self._registry.items():
                urlpatterns += [
                    path('%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 valid_app_labels:
                regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
                urlpatterns += [
                    re_path(regex, wrap(self.app_index), name='app_list'),
                ]
            return urlpatterns
    

    myapps.py

    from django.contrib.admin.apps import AdminConfig
    # 继承父类AdminConfig
    # 重新设置属性default_site的值,使它指向MyAdminSite类
    class MyAdminConfig(AdminConfig):
        default_site = 'MyDjango.myadmin.MyAdminSite'
    

    在settings.py中配置系统注册类MyAdminConfig.最后在配置静态资源文件static

    INSTALLED_APPS = [
        # 注释原有的admin
        # 'django.contrib.admin',
        # 指向myapps的MyAdminConfig
        'MyDjango.myapps.MyAdminConfig',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'index'
    ]
    
    # 配置静态资源文件static
    STATIC_URL = '/static/'
    STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
    

    笔记来源:Django Web应用开发实战
  • 相关阅读:
    不使用循环使用递归得到数组的值得求和
    将int转int数组并将int数组元素处理后转int,实现加密
    小程序总结
    见过的最好的Layout讲解,挺全的
    (转)Java 的swing.GroupLayout布局管理器的使用方法和实例
    三十二、Java图形化界面设计——布局管理器之CardLayout(卡片布局)
    二十六、Jcreator使用初步
    The Java™ Tutorials下载地址
    中间容器
    二十七、Java图形化界面设计——容器(JFrame)
  • 原文地址:https://www.cnblogs.com/wkhzwmr/p/15594615.html
Copyright © 2020-2023  润新知