1-Django - Admin&XAdmin

about

Django2.2

Django内置了一个强大的组件叫Admin,提供给网站管理员快速开发运营后台的管理站点。而xadmin则是第三方组件,它对admin进行了扩展。
我们分别来看。
before
这里我新建一个Django项目叫做admin_demo,有一个应用叫做student,在student应用中,创建模型类:

from django.db import models

# Create your models here.


class Classes(models.Model):
    """ 班级表 """
    title = models.CharField(max_length=32, verbose_name='班级名称')

    def __str__(self):
        return self.title

class Students(models.Model):
    """ 学生表 """
    username = models.CharField(max_length=128, default=None, null=True, verbose_name='学生姓名')
    mobile = models.CharField(max_length=32, null=True, unique=True, verbose_name='手机号', help_text='手机号')
    wxchat = models.CharField(max_length=100, null=True, unique=True, verbose_name='微信', help_text='微信')
    qq_number = models.CharField(max_length=16, null=True, unique=True, verbose_name='QQ', help_text='QQ')
    nickname = models.CharField(max_length=128, default=None, null=True, verbose_name='昵称')
    age = models.SmallIntegerField(verbose_name='年龄')
    email = models.CharField(max_length=128, default=None, null=True, verbose_name='邮箱')
    en_date = models.DateField(verbose_name='入学日期')
    addr = models.CharField(max_length=32, default=None, null=True, verbose_name='地址')
    to_classes = models.ForeignKey('Classes', default=1, verbose_name='所属班级', on_delete=models.CASCADE)

    def __str__(self):
        return self.username

在项目根目录下创建test.py文件,进行批量生成一些数据:

import os
import django
import random
import faker  # pip install faker

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "admin_demo.settings")  # MB:项目名称
django.setup()

from student import models

fk = faker.Faker(locale='zh_CN')


def create():
    # models.Classes.objects.bulk_create((models.Classes(i, random.randrange(100, 500)) for i in range(10)))
    gen = (
        models.Students(
            i,
            fk.name(),
            fk.phone_number(),
            fk.phone_number(),
            fk.phone_number(),
            fk.user_name(),
            random.randrange(15, 20),
            fk.email(),
            fk.date(),
            fk.city_name(),
            random.randrange(0, 9)
        ) for i in range(1, 1001)
    )
    models.Students.objects.bulk_create(gen)


if __name__ == '__main__':
    create()

现在,项目有了,数据也有了,就可以开搞。

admin

https://docs.djangoproject.com/zh-hans/2.2/ref/contrib/admin/

想要使用admin,就先要创建一个超级管理员:

(django2.2) D:\tmp\admin_demo>python manage.py createsuperuser   # 创建管理员命令
Username (leave blank to use 'anthony'): root                    # 管理员账号
Email address:                                                   # 我直接回车,省略输入邮箱步骤
Password:                                                        # 123
Password (again):                                                # 123
This password is too short. It must contain at least 8 characters. # 提示密码太短了,最少需要8个字符,这里忽略
This password is too common.
This password is entirely numeric.
Bypass password validation and create user anyway? [y/N]: y      # 绕过密码验证,直接创建
Superuser created successfully.

那,现在我们就可以访问admin站点了。
admin站点的路由,在总路由文件中,已经提前帮我们写好了:

# admin_demo/urls.py

from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

所以,直接运行项目,然后使用刚才创建的超级用户访问admin路径就行了,即http://127.0.0.1:8000/admin,登录成功,后台站点默认长这样。

可以看到,全部是英文,想要中文,需要在settings中配置:

# 修改使用中文界面
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-Hans'

# 修改时区
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'

OK了:

列表页配置

关于列表页配置,主要针对项目中各子应用里面的models.py里面的模型,根据这些模型自动生成后台运营站点的管理功能。
在我们的student应用下的admin.py中,进行用户模型的注册:

from django.contrib import admin
from student import models

class StudentModelAdmin(admin.ModelAdmin):  # 必须继承 admin.ModelAdmin
    """ 用户模型管理类,用于在admin后台对Students模型类进行扩展 """
    pass

admin.site.register(models.Students, StudentModelAdmin)  # 必须进行注册


@admin.register(models.Classes)  # 或者使用装饰器进行注册
class ClassesModelAdmin(admin.ModelAdmin):
    pass

效果如下:

但显示的title仍然是英文,要换成中文应该怎么办?

# admin_demo/student/__init__.py
default_app_config = "student.apps.StudentConfig"  # 这么配置

# admin_demo/student/apps.py
from django.apps import AppConfig

class StudentConfig(AppConfig):
    name = 'student'
    verbose_name = "学生信息管理"  # 添加这一行

现在就好了:

你可以点击Student或者班级表进入其列表页,这里以学生表为例:

列表页默认显示的是每个学生记录的模型对象,现在非常的单调。

基础配置

我们在admin.py中进行其他配置:

from django.contrib import admin
from student import models


class StudentModelAdmin(admin.ModelAdmin):  # 必须继承 admin.ModelAdmin
    """ 用户模型管理类,用于在admin后台对Students模型类进行扩展 """
    list_per_page = 20  # 列表页每页展示数据的条数控制

    # 在列表页展示哪些字段
    # 注意,对于外键字段直接写字段名即可,但是外键字段对应的记录必须存在,比如to_classes_id的值是2,那么对应的外键表中,必须有id为2的这条记录,否则的话,页面不报错,但是不显示内容,排查起来就操蛋了
    list_display = ['id', 'username', 'age', 'email', 'mobile', 'nickname', 'to_classes']  

    # 设置点击指定字段跳转到编辑页面
    list_display_links = ['id', 'username', 'age']

    # 默认是以id进行排序,你也可以通过ordering,设置指定字段排序
    # ordering = ['-age']  # ['age']: 升序; ['-age']: 降序
    ordering = ['-age', 'id']  # 年龄相同的,就按照id升序排序

    # actions_on_bottom = True  # 下方操作栏是否显示,False表示隐藏
    # actions_on_top = True     # 上方操作栏是否显示,False表示隐藏

    list_filter = ['to_classes', 'age']  # 过滤器,按指定字段进行筛选过滤, 可以写多个过滤器,在右侧展示
    date_hierarchy = "en_date"  # 按照入学时间不同,可以进行过滤,在上方展示,这个筛选必须是日期时间相关的字段

    search_fields = ['username', 'nickname']  # 指定字段搜索
    preserve_filters = True  # 默认为False,在详情页面做了操作,再返回列表页后,是否保留搜索条件

    # 允许在列表页直接编辑哪些字段
    # 注意,要编辑的字段必须在list_display中,但是也必须不在list_display_links中
    # list_editable = ['email', 'mobile']


admin.site.register(models.Students, StudentModelAdmin)  # 必须进行注册

inlines

在我们的当前示例项目中,添加班级时,可以通过设置inlines字段同时为该班级添加一些学生,因为学生表ForeignKey的班级表。
before:

after:

from django.contrib import admin
from student import models

@admin.register(models.Classes)
class ClassesModelAdmin(admin.ModelAdmin):
    class StudentInline(admin.StackedInline):  # 必须这么继承,当然这个类也可以写在ClassesModelAdmin外面
        """
        这两个就样式不同而已
            admin.StackedInline: 竖着
            admin.TabularInline: 横着
        """
        extra = 1  # 默认一次性添加几条记录
        model = models.Students

    inlines = [StudentInline]

action

action即在列表页,可操作的一些功能,它默认实现了一个删除操作:

我们也可以定制实现,来看示例:

from django.contrib import admin
from student import models

class StudentModelAdmin(admin.ModelAdmin):  # 必须继承 admin.ModelAdmin
    def my_edit(self, request, queryset):
        """ 将选中的记录,年龄设置为10 """
        # print(request.POST.getlist("_selected_action"))  # ['5', '12']
        pk_list = request.POST.getlist("_selected_action")
        models.Students.objects.filter(pk__in=pk_list).update(age=10)

    my_edit.short_description = "自定义编辑功能"
    actions = [my_edit]

admin.site.register(models.Students, StudentModelAdmin)  # 必须进行注册

效果:

定制HTML模板

默认的,admin后台使用的是自己的相关模板进行渲染数据,如果你有兴趣的话,可以去这个路径查看:解释器下的Lib\site-packages\django\contrib\admin\templates\admin中。
我们也可以选择使用自己定制的模板,通过如下几个参数来制定相关页面使用我们自己定制的模板:

from django.contrib import admin
from student import models

@admin.register(models.Classes)
class ClassesModelAdmin(admin.ModelAdmin):

    add_form_template = ['add_form.html']  # 添加页面
    change_form_template = ['change_form.html']  # 修改页面
    change_list_template = ['change_list.html']  # 列表页
    delete_confirmation_template = ['delete_confirmation.html']  # 删除时确认页面
    delete_selected_confirmation_template = ['delete_selected_confirmation.html']  # 选择操作中的删除确认页面
    object_history_template = ['object_history.html']  # 操作中删除确认页面中的对象记录页面

上面中括号中的HTML文件,直接在Django的templates中实现即可。

编辑页配置

在列表页点击某一条记录,跳转到对应的编辑页面。
我们在编辑页面也可以做些定制配置。

自定义字段

django3.2 + admin

今天遇到一个需求就是在admin后台的列表中,对于文件字段的展示,要带下载功能,也就是这个字段要渲染成一个下载连接,点击就能直接浏览器下载到本地。
那么配置如下:
admin.py中要使用方法定义:

@admin.register(SignUp)
class SignUpAdmin(admin.ModelAdmin):
    list_display = ('id', 'name','phone', 'idc', 'art', 'download_attachment')  # download_attachment完事之后注册到这里
    # art是外键,直接写报错Related Field got invalid lookup: icontains
    # search_fields = ['art']
    # 改成下面这样
    search_fields = ['art__title']

    # 满50条数据就自动分页
    list_per_page = 20
    list_editable = ['phone', 'idc']

    ordering = ('id',)
    def download_attachment(self, obj):
        """ 自定义附件下载 """
        # reverse中用到了路由别名,所以,这里你也需要注意
        url = reverse('attachment', args=(obj.pk, ))
        title = '下载附件'
        # mark_safe保证页面渲染成a标签
        return mark_safe('<a href="{}">{}</a>'.format(url, title))

    download_attachment.short_description = '下载'
    download_attachment.allow_tags = True

然后路由也展示下:

from django.contrib import admin
from django.urls import path, include, re_path
from backstage.views import index, list, show, search, asset, attachment_download
from django.conf import settings
from django.views.static import serve

urlpatterns = [
    path('admin/', admin.site.urls),
    path('ckeditor/', include('ckeditor_uploader.urls')),
    re_path('^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
    path('ueditor/', include('DjangoUeditor.urls')),
    path('', index, name='index'),  # 网站首页
    path('list-<int:lid>.html', list, name='list'),  # 列表页
    path('show-<int:sid>.html', show, name='show'),  # 内容页
    path('s/', search, name='search'),  # 搜索列表页
    # path('captcha', include('captcha.urls')),

    re_path(r'^asset/(?P<pk>\d+)/$', asset, name='asset'),  # 用户上传报名信息
    re_path(r'^attachment/download/(?P<pk>\d+)/$', attachment_download, name='attachment'),  # 用户上传的附件下载地址

]

然后views.py中:

import os
from django.http import FileResponse
from django.utils.encoding import escape_uri_path  # 导入这个家伙
from django.conf import settings
from backstage.models import SignUp

def attachment_download(request, pk):
    file = SignUp.objects.filter(pk=pk).first().attachment
    file_name = file.name.split("/")[-1]
    file_path = os.path.join(settings.MEDIA_ROOT, file.name)
    file_obj = open(file_path, 'rb')
    response = FileResponse(file_obj)
    response['Content-Type'] = 'application/octet-stream'
    response['Content-Disposition'] = 'attachment;filename="{}"'.format(escape_uri_path(file_name))
    return response

models.py也贴一下把:

from django.db import models
from django.core import validators


class Article(models.Model):
    title = models.CharField('标题', max_length=70)
    excerpt = models.TextField('摘要', max_length=200, blank=True)
    category = models.ForeignKey(Category, on_delete=models.DO_NOTHING,
                                 verbose_name='分类', blank=True, null=True)
    # 使用外键关联分类表与分类是一对多关系
    body = RichTextUploadingField(verbose_name='正文内容')
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='作者')
    tui = models.ForeignKey(Tui, on_delete=models.DO_NOTHING,
                            verbose_name='推荐位', blank=True, null=True)
    """
    文章作者,这里User是从django.contrib.auth.models导入的。
    这里我们通过 ForeignKey 把文章和 User 关联了起来。
    """
    views = models.PositiveIntegerField('阅读量', default=0)
    created_time = models.DateTimeField('发布时间', auto_now_add=True)
    modified_time = models.DateTimeField('修改时间', auto_now=True)

    class Meta:
        verbose_name = '文章'
        verbose_name_plural = '文章'

    def __str__(self):
        return self.title


class SignUp(models.Model):
    """ 我要报名上传文件
    姓名、手机号、身份证号,附件
    """
    name = models.CharField(max_length=32, verbose_name='姓名')
    phone = models.CharField(max_length=11, verbose_name='手机号',
                             validators=[validators.RegexValidator("^1[3-9]\d{9}$", '手机号格式不正确')]
                             )
    idc = models.CharField(max_length=20, verbose_name='身份证号码',
                           validators=[validators.RegexValidator(
                               '^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9xX]$',
                               '身份证格式不正确')]
                           )
    attachment = models.FileField(upload_to='attachment/', verbose_name='用户上传的附件', )
    art = models.ForeignKey(to='Article', on_delete=models.CASCADE, verbose_name='所属文章')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '报名表'
        verbose_name_plural = '用户报名信息'

效果:

第三方admin框架:Simple UI

django3.2 + admin

如果你觉得django的admin后台样式太难看,那么这里你可以参考下面这个连接:

搞个漂亮的样式。
它的demo页面,连接:https://www.88cto.com/admin/

xadmin

https://github.com/sshwsfc/xadmin
https://xadmin.readthedocs.io/en/latest/index.html

xadmin是Django的第三方扩展,使Django的admin站点使用更方便。
xadmin在很多地方都跟admin的配置一样,非常好理解。

install

不同的Django版本安装命令稍有不同,这里以Django2.2版本为例,只需要终端执行:

pip install https://codeload.github.com/sshwsfc/xadmin/zip/django2

安装完后,我们需要在配置文件中进行配置:

# 注册这三个应用
INSTALLED_APPS = [
    'xadmin',
    'crispy_forms',
    'reversion'
]

# 修改使用中文界面
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-Hans'

# 修改时区
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'

xadmin有建立自己的数据库模型类,所以还需要重新执行数据库迁移命令:

python manage.py makemigrations
python manage.py migrate

如果你在执行数据库迁移命令时报了错:

ImportError: cannot import name 'SKIP_ADMIN_LOG'
(django2.2) D:\tmp\admin_demo>python manage.py makemigrations
Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    main()
  File "manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\django\core\management\__init__.py", line 381, in execute_from
_command_line
    utility.execute()
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\django\core\management\__init__.py", line 357, in execute
    django.setup()
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\django\__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\django\apps\registry.py", line 122, in populate
    app_config.ready()
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\xadmin\apps.py", line 14, in ready
    self.module.autodiscover()
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\xadmin\__init__.py", line 49, in autodiscover
    register_builtin_plugins(site)
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\xadmin\plugins\__init__.py", line 41, in register_builtin_plug
ins
    [import_module('xadmin.plugins.%s' % plugin) for plugin in PLUGINS if plugin not in exclude_plugins]
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\xadmin\plugins\__init__.py", line 41, in <listcomp>
    [import_module('xadmin.plugins.%s' % plugin) for plugin in PLUGINS if plugin not in exclude_plugins]
  File "c:\python36\lib\importlib\__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\xadmin\plugins\importexport.py", line 48, in <module>
    from import_export.admin import DEFAULT_FORMATS, SKIP_ADMIN_LOG, TMP_STORAGE_CLASS
ImportError: cannot import name 'SKIP_ADMIN_LOG'

请参考下面的解决方案:点我

解决之后,继续往下走。

然后在总路由中配置:

# /admin_demo/admin_demo/urls.py

# --------- xadmin相关配置 ---------------
import xadmin

xadmin.autodiscover()
from xadmin.plugins import xversion

xversion.register_models()

# --------- 以上 ---------------


from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('xadmin/', xadmin.site.urls),  # 注册xadmin路由
]

完事之后,就可以使用超级管理员账号和密码访问了,地址:http://127.0.0.1:8000/xadmin/,效果如下:

接下来,就可以进行一系列的配置了。

adminx

xadmin为了区分Django的admin,所以,相关配置就不在admin.py中写了,而是在相应的app下,新建一个adminx.py文件,在这个文件中写配置;另外,xadmin的站点管理类也不在继承admin.ModelAdmin,直接继承object对象即可。

全局配置

我们可以在adminx.py中,配置站点的全局的一些配置:

import xadmin
from xadmin import views


class BaseSettings(object):
    """ 站点的基本配置 """
    # 开启主题切换功能
    enable_themes = True
    use_bootswatch = True

class GlobalSettings(object):
    """ 站点的全局配置 """
    site_title = "学生管理系统"  # 设置站点标题
    site_footer = "底部信息栏"
    menu_style = "accordion"  # 设置菜单栏折叠


xadmin.site.register(views.BaseAdminView, BaseSettings)
xadmin.site.register(views.CommAdminView, GlobalSettings)

基本效果如下:

可能的报错

ImportError: cannot import name 'SKIP_ADMIN_LOG'

Django2.2 + xadmin2.0.1

当下载完xadmin,然后要执行数据库迁移命令时,发现报错:

ImportError: cannot import name 'SKIP_ADMIN_LOG'
(django2.2) D:\tmp\admin_demo>python manage.py makemigrations
Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    main()
  File "manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\django\core\management\__init__.py", line 381, in execute_from
_command_line
    utility.execute()
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\django\core\management\__init__.py", line 357, in execute
    django.setup()
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\django\__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\django\apps\registry.py", line 122, in populate
    app_config.ready()
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\xadmin\apps.py", line 14, in ready
    self.module.autodiscover()
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\xadmin\__init__.py", line 49, in autodiscover
    register_builtin_plugins(site)
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\xadmin\plugins\__init__.py", line 41, in register_builtin_plug
ins
    [import_module('xadmin.plugins.%s' % plugin) for plugin in PLUGINS if plugin not in exclude_plugins]
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\xadmin\plugins\__init__.py", line 41, in <listcomp>
    [import_module('xadmin.plugins.%s' % plugin) for plugin in PLUGINS if plugin not in exclude_plugins]
  File "c:\python36\lib\importlib\__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "D:\mydata\demo\all_env\django2.2\lib\site-packages\xadmin\plugins\importexport.py", line 48, in <module>
    from import_export.admin import DEFAULT_FORMATS, SKIP_ADMIN_LOG, TMP_STORAGE_CLASS
ImportError: cannot import name 'SKIP_ADMIN_LOG'

解决方案是,打开对应的报错文件,如果是PyCharm,可以直接点击链接,找到源码文件,你也可以手动去找,它在你解释器的\Lib\site-packages\xadmin\plugins\importexport.py中,修改相应的源码:

# 第一处,大约48行左右
# from import_export.admin import DEFAULT_FORMATS, SKIP_ADMIN_LOG, TMP_STORAGE_CLASS  # 注释掉原来的报错代码
from import_export.admin import DEFAULT_FORMATS, ImportMixin, ImportExportMixinBase   # 修改后的代码

# 下拉,大约90行左右,找到ImportBaseView类,找到类中get_skip_admin_log和get_tmp_storage_class方法,分别修改
class ImportBaseView(ModelAdminView):

    def get_skip_admin_log(self):
        if self.skip_admin_log is None:
            # 第二处
            # return SKIP_ADMIN_LOG  # 注释掉原来的报错代码
            return ImportMixin(ImportExportMixinBase).get_skip_admin_log()  # 修改后的代码
        else:
            return self.skip_admin_log

    def get_tmp_storage_class(self):
        if self.tmp_storage_class is None:
            # 第三处
            # return TMP_STORAGE_CLASS  # 注释掉原来的报错代码
            return ImportMixin(ImportExportMixinBase).get_tmp_storage_class()  # 修改后的代码
        else:
            return self.tmp_storage_class

修改后,重新执行数据库迁移指令,就OK了。

参考:【xadmin】报错:annot import name ‘SKIP_ADMIN_LOG‘ from | 【xadmin】报错:cannot import name ‘SKIP_ADMIN_LOG‘

django3.2


这个报错是在admin.py中,指定的搜索字段是外键字段,如果是外键字段的话就不能直接写字段名,而是单独处理。

@admin.register(SignUp)
class SignUpAdmin(admin.ModelAdmin):
    list_display = ('id', 'name','phone', 'idc', 'art', 'attachment')
    # art是外键,直接写报错Related Field got invalid lookup: icontains
    # search_fields = ['art']
    # 改成下面这样就好了
    search_fields = ['art__title']

参考:https://blog.csdn.net/qq_42780731/article/details/108862591


that's all
posted @ 2018-06-20 18:54  听雨危楼  阅读(560)  评论(0编辑  收藏  举报