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‘
Related Field got invalid lookup: icontains
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