Django---Admin
1. 站点标题显示
顶部标题和登录页面标题
效果图


实现方法
有admin访问路径的urls.py
admin.site.site_header = '打包机节能采集监控系统'
urlpatterns = [
    path('admin/', admin.site.urls),
]

有admin访问路径的urls.py
admin.site.site_title = '重庆万显昇节能科技有限公司'
urlpatterns = [
    path('admin/', admin.site.urls),
]
首页一级标题
效果图

实现方法
有admin访问路径的urls.py
admin.site.index_title = '打包机节能采集监控系统'
urlpatterns = [
    path('admin/', admin.site.urls),
]
2. 页面基本显示
2.1 模型注册
应用下的admin.py文件
from django.contrib import admin
from .baconfigforms import *
from .models import *
@admin.register(ParamConf)  # 将ParamConf表注册到admin页面中显示
class ParamConfAdmin(admin.ModelAdmin):
    pass
2.2 常用页面布局
# 注册表名到admin
@admin.register(air_compressor_station)
class air_compressor_station_Admin(admin.ModelAdmin):
    # HTML页面自定义的渲染字段,其实就是modelform渲染
    list_display = []
    # 过滤器 HTML右侧展示的过滤条件选择
    list_filter = [
        "name_str",
        "collector",   
    ]
    # 分页:一页显示多少条
    list_per_page = 20
    # admin展示字段位置,样式设置
    fieldsets = (
        # 标题名称
        ("基本", {
            # wide:页面直接显示
            'classes': ('wide',),
            "fields": (
                ("name_str", "auto_status"),  # 这两个字段在前端的一行,div标签的一行
                ("calculate_type", "shutdown_cascade"), # 这两个字段在前端的一行
                ("uninstall_record_save_time", "uninstall_cumulative_shutdown_time"), 
            ),
        }),
        # 标题名称
        ("高级选项", {
            # collapse:可以选择显示或隐藏
            'classes': ('collapse',),
            "fields": (
                ("max_range", "min_range"),
                ("max_value", "min_value"),
                ("pressure_feedback_frequency", "collector",),
                ("station_type",)
            ),
        }),)
    # 自定义渲染及数据校验规则
    form = StationForm
    # 外键关系表
    inlines = [stand_parameters_inline]
    # 上传图片
    license_image = show_image
    license_image.short_description = "营业执照"
    # 自定义queryset,如本例为:如果当前用户不是超级管理员并且有所属公司,才渲染当前公司名下所有采集器的数据
    def get_queryset(self, request):
        qs = super().get_queryset(request)   # qs为当前模型中每一条记录的实例对象
        this_user = get_this_user(request)
        try:
            if not this_user.is_superuser and this_user.company:  
                qs = qs.filter(agency_company=this_user.company)
            elif not this_user.is_superuser and not this_user.company:
                qs = client_collector.objects.none()
        except:
            pass
        return qs    # 返回query_set给前端,前端进行渲染
 
    # 将当前请求信息添加到表单
    def get_form(self, request, obj=None, change=False, **kwargs):
        def func(self):
            self.request = request
            self.this_user = get_this_user(request)
        CustomersForm.add_request = func
        return CustomersForm
    # admin提交数据的校验钩子
    def save_related(self, request, form, formsets, change):
        
    	# 先执行提交到数据库
        super().save_related(request, form, formsets, change)
    	# 提交完成后,再执行其他逻辑,如向另一张表提交数据
    
2.2.1 显示字段
ModelAdmin中控制展示页面的字段显示,ModelForm中控制新增,修改页面的字段显示
1. 展示页面
@admin.register(ParamConf)
class ParamConfAdmin(admin.ModelAdmin):
    # list_display 用来定义在admin页面显示数据模型中的哪些字段
    list_display = ["brand_mgt", "brand_series_mgt"]
2.新增/修改页面
# 1. ModelAdmin中注册form
@admin.register(ParamConf)
class ParamConfAdmin(admin.ModelAdmin):
    # list_display 用来定义在admin页面显示数据模型中的哪些字段
    list_display = ["brand_mgt", "brand_series_mgt"]
    form = CustomerForm
    
# 2. Form中定义显示字段
class CustomersForm(forms.ModelForm):
    class Meta:
        model = client_customers
        # fields = '__all__'
        exclude = ("agent",)
3. 外键关系嵌套页面显示
必须建立了外键关系的表才能显示
# 1. 需要嵌套的表
class StandParametersInline(admin.TabularInline):
    # 指定表名
    model = stand_parameters
    # 指定显示字段
    fields = ["air_compressor_station_id", "name_str", "code_str", "value_str"]
    extra = 0
   
# 2. 指定嵌套表
@admin.register(ParamConf)
class ParamConfAdmin(admin.ModelAdmin):
    list_display = ["brand_mgt", "brand_series_mgt"]
    form = CustomerForm
    inlines = [StandParametersInline]
2.2.2 字段标签页面布局
正常来说,是在一行中显示的,可以通过fieldsets参数来重构页面的显示
@admin.register(Collector)
class CollectorAdmin(admin.ModelAdmin):
    # fieldsets 属性用来控制字段标签位置
    fieldsets = (
        ("基本", {
            'classes': ('wide',),  # wide为显示,下面有两种模式的截图
            "fields": (
                ("brand_series_mgt", "coll_frequency"), 
            ),
        }),
        ("网络连接", {
            'classes': ('collapse',), # wide为折叠显示,下面有两种模式的截图
            "fields": (
                ("address1", "netmask1", "gateway1"), 
                ("address2", "netmask2", "gateway2"),
                ("wifi_management", "network_type")
            ),
        }),
    )
classes值为wide

classes值为collapse

fields布局

"fields": (
    ("address1", "netmask1", "gateway1"), 
    ("address2", "netmask2", "gateway2"),
    ("wifi_management", "network_type")
),
2.2.3 过滤器
1. 字段过滤器
@admin.register(air_compressor_station)
class air_compressor_station_Admin(admin.ModelAdmin):
    # 过滤器 HTML右侧展示的过滤条件选择
    list_filter = [
        "name_str",
        "collector",   
    ]
2. 自定义过滤器
admin.py
@admin.register(air_compressor_station)
class air_compressor_station_Admin(admin.ModelAdmin):
    # 过滤器 HTML右侧展示的过滤条件选择
    list_filter = [
        CompressorFilter,
        ExistenceFilter
    ]
filters.py
# 根据是否被删除筛选设备
class ExistenceFilter(admin.SimpleListFilter):
    """
    根据是否被删除筛选数据
    """
    # 过滤器名称
    title = "是否已删除"
    # 传递给url时的参数名
    parameter_name = "existence"
    # 筛选项
    def lookups(self, request, model_admin):
        """
        此函数返回一个二维元组,
        第二维度的每个元组将包含两个元素:用于筛选查询的值、展示给用户的筛选选项
        """
        return (
            (True,"未删除"),
            (False,"已删除设备")
        )
    
    # 数据查询
    def queryset(self, request, queryset):
        # 默认展示所有没有被删除的数据
        this_value = self.value()
        if not this_value:
            return queryset.filter(existence=True)
        else:
            return queryset.filter(existence=this_value)
2.2.4 分页控制
@admin.register(air_compressor_station)
class air_compressor_station_Admin(admin.ModelAdmin):
    # 分页:一页显示多少条
    list_per_page = 20
2.2.5 自定义主页标签列
1. admin页面效果展示

1. admin中添加自定义属性
@admin.register(client_collector)
class ClientCollectorAdmin(admin.ModelAdmin):
    list_display = ["collector_record",]                        # 前端展示字段中加入 collector_record 
    collector_record = show_file_by_field("file_path")          # 自定义标签的字段名
    collector_record.short_description = "采集器采购及分配记录"   # 自定义标签在admin的表格中的表头的名字
3. 自定义函数,构建html标签或文本内容
def show_file_by_field(field_name: str, file_suffix="yml"):
	def show_file(self, instance):
		this_field = instance.__dict__.get(field_name)
		if this_field:
			if file_suffix == "yml":
                # str_list = this_field.split("\\")[-2:]
                str_list = this_field.split("/")[-2:]
                this_field = "/".join(str_list)
			else:
                # this_field = this_field.split("\\")[-1]
                this_field = this_field.split("/")[-1]
                return format_html(
                    "<a href='/static/{}' target='_blank'>点击查看</a>",
                    this_field
                )
		 else:
			return "尚未生成文件"
		return show_file
2.2.6 自定义动作
1. 页面效果演示

2. 代码
# 自定义动作函数,批量操作:开启中层系统服务
def start_server_action(modeladmin, request, queryset):
    # 此时queryset为ServerConfig对象列表,列表的中对象为前端勾选的ServerConfig对象
    for obj in queryset:
        obj.start_server()  #会执行ServerConfig模型中的start_server()
    queryset.update(server_status="1",updated_time=datetime.now())
    modeladmin.message_user(request,"操作成功")   # # 执行成功后的前端提示信息 
@admin.register(ServerConfig)
class ServerConfigAdmin(admin.ModelAdmin):
    list_display = ["customers","domain_name", ]
    form = ServerConfigForm
    actions = [start_server_action]   # 添加自定义动作列表
    start_server_action.short_description = "开启服务"  # 动作名称
3. 执行完毕后的提示信息

def start_server_action(modeladmin, request, queryset):
    # 执行成功后的前端提示信息 
    modeladmin.message_user(request, "操作成功")
2.2.7 admin主页自定义左侧菜单栏

在项目的templates下创建admin文件夹,创建index.html,名字必须一致,django admin渲染时会先找项目下的templates,找不到会根据注册app的顺序找各app下的templates文件进行一一渲染
templates/admin/index.html
{% extends "admin/base_site.html" %}
{% load i18n static %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/dashboard.css" %}">{% endblock %}
{% block coltype %}colMS{% endblock %}
{% block bodyclass %}{{ block.super }} dashboard{% endblock %}
{% block breadcrumbs %}{% endblock %}
{% block nav-sidebar %}{% endblock %}
{% block content %}
<!-- 新增标签内容开始 -->
    <div style="background-color: #79aec8;height: 32px;line-height: 32px"><a style="margin-left: 8px;color: white" href="#">采集器分布图</a></div>
    <div style="margin-bottom: 30px;margin-top: 10px"><a href="/index">采集器全国位置分布图</a></div>
<div id="content-main">
<!-- 新增标签内容结束 -->
  {% include "admin/app_list.html" with app_list=app_list show_changelinks=True %}
</div>
{% endblock %}
{% block sidebar %}
<div id="content-related">
    <div class="module" id="recent-actions-module">
        <h2>{% translate 'Recent actions' %}</h2>
        <h3>{% translate 'My actions' %}</h3>
            {% load log %}
            {% get_admin_log 10 as admin_log for_user user %}
            {% if not admin_log %}
            <p>{% translate 'None available' %}</p>
            {% else %}
            <ul class="actionlist">
            {% for entry in admin_log %}
            <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">
                {% if entry.is_deletion or not entry.get_admin_url %}
                    {{ entry.object_repr }}
                {% else %}
                    <a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
                {% endif %}
                <br>
                {% if entry.content_type %}
                    <span class="mini quiet">{% filter capfirst %}{{ entry.content_type.name }}{% endfilter %}</span>
                {% else %}
                    <span class="mini quiet">{% translate 'Unknown content' %}</span>
                {% endif %}
            </li>
            {% endfor %}
            </ul>
            {% endif %}
    </div>
</div>
{% endblock %}
2.2.8 自定义文件上传位置
models.py
# 定义保存到media文件里的格式
def reconstruction_path(instance, filename):
    file_path = "middle_level_scheduling/coll_version/{cate_id}/{filename}".format(cate_id=instance.series_base_id.id,
                                                                                   filename=filename)
    return file_path
class collector_series_base_version(models.Model):
    class Meta:
        app_label = "machine"
        managed = True
        db_table = "machine_collector_series_base_version"
        verbose_name = "0 系列程序版本管理"
        verbose_name_plural = "0 系列程序版本管理"
    series_base_id = models.ForeignKey(collector_series_base, verbose_name="系列", on_delete=models.DO_NOTHING, null=True,
                                       blank=True)
    version_number = models.CharField("版本号", max_length=128, null=True, blank=True)
    file_path = models.FileField("文件目录", max_length=128, upload_to=reconstruction_path, null=True, blank=True)
    remark = models.CharField("备注", max_length=128, null=True, blank=True)
    created_time = models.DateTimeField("创建时间", default=timezone.now, null=True,
                                        blank=True)
    updated_time = models.DateTimeField("更新时间", default=timezone.now, null=True,
                                        blank=True)
admin.py
@admin.register(collector_series_base_version)
class CollectorSeriesBaseVersionAdmin(admin.ModelAdmin):
    list_display = [
        "series_base_id",
        "version_number",
        "file_path",
        "remark",
        "created_time"
    ]
    form = CollectorSeriesBaseVersionForm
forms.py
class CollectorSeriesBaseVersionForm(forms.ModelForm):
    class Meta:
        model = collector_series_base_version
        fields = "__all__"
    def clean_file_path(self):
        file_path = self.cleaned_data.get("file_path")
        if not os.path.exists(settings.SERIES_BASE_VERSION_PATH):
            try:
                os.mkdir(settings.SERIES_BASE_VERSION_PATH)
            except Exception:
                # windows下创建文件夹
                parent_dir = os.path.dirname(settings.SERIES_BASE_VERSION_PATH)
                os.mkdir(parent_dir)
        ser_path = os.path.join(settings.SERIES_BASE_VERSION_PATH, str(self.cleaned_data.get("series_base_id").id))
        if not os.path.exists(ser_path):
            try:
                os.mkdir(ser_path)
            except Exception:
                # windows下创建文件夹
                parent_dir = os.path.dirname(ser_path)
                os.mkdir(parent_dir)
        file_full_path = os.path.join(ser_path, file_path.name)
        # 将admin中上传的文件写入到指定位置
        with open(file_full_path, "wb") as f:
            for line in file_path.file:
                f.write(line)
        return file_full_path
2.2.9 将菜单栏显示在另一个应用菜单下
class SokcetMutual(models.Model):
    class Meta:
        app_label = "basic_config"    # 通过app_label 来控制,写的哪个应用,就显示在哪个应用下
        verbose_name = "62、交互中间表"
        verbose_name_plural = "62、交互中间表"
        db_table = "sokcet_mutual"
2.2.10 搜索器

class SokcetMutual(models.Model):
    class Meta:
        # 列表中第一个元素是外键字段的搜索,第二个是本表中的字段搜索
        search_fields = ["collector__collector_number","name"]
2.2.11 时间选择器

class SokcetMutual(models.Model):
    class Meta:
        date_hierarchy = "created_time"
2.3 常用页面操作控制
2.3.1 不允许操作某标签
通过添加 disable=True 属性来达到不允许操作标签的目的
# 客户服务器配置表单ServerConfig
class ServerConfigForm(forms.ModelForm):
    class Meta:
        model = ServerConfig
        # 修改前端展示字段,只展示服务域名
        fields = ["customers", "domain_name", "server_status"]
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.start_flag = False
        self.stop_flag = False
        self.verification_user()
    # 校验当前客户
    def verification_user(self):
        # 只要当前代理商不是超级用户,客户表单就不可以选择上级代理商
        if not self.this_user.is_superuser:
            self.fields["customers"].disabled = True   # 字段标签不允许操作
        try:
            # 如果当前表单是用于修改已有数据,就判断该客户是否属于当前代理商
            if self.instance.id:
                this_customer = client_customers.objects.get(id=self.instance.id)
                if this_customer.agent != self.this_user:
                    raise forms.ValidationError({"agent": "该客户不属于当前代理商"})
            # 当前表单用于创建新记录时将当前代理商填充到表单上级代理商选择框内
            else:
                self.fields["agent"].initial = self.this_user
        except:
            pass
2.3.2 禁用编辑删除功能
class YourModelAdmin(admin.ModelAdmin):
    ...
    def get_form(self, request, obj=None, **kwargs):
        form = super(YourModelAdmin, self).get_form(request, obj, **kwargs)
        field = form.base_fields["your_foreign_key_field"]
        field.widget.can_add_related = False
        field.widget.can_change_related = False
        field.widget.can_delete_related = False
        return form
2.3.3 自定义弹框提示信息

# 通过定义level,来展示前端返回的是提示信息还是错误信息,messages.ERROR为错误信息,默认为info提示信息
modeladmin.message_user(request, level=messages.ERROR, message=f"<<{obj.name_str}>>未审核通过不允许开机")
modeladmin.message_user(request, f"<<{obj.name_str}>>未审核通过不允许开机")
3. 数据操作
1. 列表页面筛选数据
@admin.register(client_customers)
class client_customers_admin(admin.ModelAdmin):
    list_display = ["name_str", ]
    
    # 筛选当前用户下的数据
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        this_user = get_this_user(request)
        try:
            if not this_user.is_superuser:
                qs = qs.filter(agent__company=this_user.company)
        except:
            pass
        return qs
2. 新增页面筛选数据
class CustomersForm(forms.ModelForm):
    class Meta:
        model = client_customers
        exclude = ("agent",)
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 自定义筛选方法
     	self.query_brand_mgt()
	def query_brand_mgt(self):
        self.fields["brand_series_mgt"].queryset = BrandSeries.objects.filter(property_type=2).all()
@admin.register(client_customers)
class client_customers_admin(admin.ModelAdmin):
    list_display = []
    form = CustomersForm
3. 新增数据时的调用
@admin.register(client_customers)
class client_customers_admin(admin.ModelAdmin):
    list_display = []
    
    def save_related(self, request, form, formsets, change):
		"""
		新增数据时,调用此方法
		"""
        # 先保存当前新增对象到数据库中
        super().save_related(request, form, formsets, change)
        # form.instance是保存到数据库中的那行数据
        this_obj = form.instance
        # change 是表示是否是修改,如果是修改则返回true
        if not change:
            print(ProductComponentsTemplate.objects.filter(ss_model_number_mgt=this_obj))
5. 添加reques和当前用户对象添到form对象中
# 添加当前request对象到form对象中
@admin.register(ServerConfig)
class ServerConfigAdmin(admin.ModelAdmin):
    # 1. 绑定form对象
    form = ServerConfigForm
    # 2. 将当前请求信息,和当前用户对象添加到form对象
    def get_form(self, request, obj=None, change=False, **kwargs):
        def func(self):
            self.request = request
            self.this_user = get_this_user(request)
        CustomersForm.add_request = func
        return CustomersForm
# form对象调用request对象
class ServerConfigForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.add_request()   # 1. 执行func()函数,封装request,this_user 到当前form对象中
        self.verification_user()
    def add_request(self):
        self.request = None
    
    def verification_user(self):
        # 2 调用request对象
        path = self.request.path
        # 3.调用self.this_user 进行校验
        if not self.this_user.is_superuser:
            self.fields["customers"].disabled = True
6. 扩展用户表
1. 扩展用户表
# 继承AbstractUser类,
class Agent(AbstractUser):
    class Meta:
        managed = True
        db_table = "agent"
        verbose_name = "代理商管理"
        verbose_name_plural = "代理商管理"
    objects = UserManager()
    # 新增字段
    contact_phone_str = models.CharField("联系电话", max_length=20, null=True, blank=True)
2. settings.py中配置用户表
# 指定admin站点的用户表
AUTH_USER_MODEL = 'agent.Agent'
3. 用户表在admin中字段展示
admin.py
from django.contrib.auth.admin import UserAdmin as OriginUserAdmin
from django.utils.translation import gettext_lazy as _
@admin.register(UserConfig)
class AgentAdmin(OriginUserAdmin):
    list_display = [
        "company", "username", "address_str", "contact_person_str", "contact_phone_str",
        "socket_address", "socket_port"
    ]
    list_filter = ["company"] + list(OriginUserAdmin.list_filter)
    form = AgentForm
    fieldsets = (
        ("账号密码", {
            "classes": ("wide",),
            "fields": (
                ("username", "password"),
            )
        }),
        # (None, {'fields': ('username', 'password')}),
        # (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
        # (_('Permissions'), {
        #     'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions'),
        # }),
        (None, {'fields': ('is_active', 'is_staff')}),
        (_('权限分配'), {'fields': ('groups',)}),
        # (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
        ("公司信息", {
            "classes": ("wide",),
            "fields": (
                ("company", 'address_str'),
                ("contact_person_str", "contact_phone_str"),
                ("socket_address", "socket_port")
            )
        })
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('username', 'password1', 'password2', "is_active", "is_staff"),
        }),
    )
7. 筛选列表页可展示字段
7.1 根据用户权限筛选
1. 动态删除展示字段
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ["name", "coding"] 
    
    # 1.定义 get_list_display() 方法来控制
    def get_list_display(self,request):
        list_display_list = super().get_list_display(request)   # 2.获取需要展示的字段列表
        # list_display_list = self.list_display    或者通过类属性来获取
        
        if not request.user.is_superuser:     # 如果不是超级用户,则不展示coding字段
            if "can_view_user" in list_display_list:
                list_display_list.remove("coding")
        return list_display_list
2. 动态添加展示字段
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ["name"] 
    # 1.定义 get_list_display() 方法来控制
    def get_list_display(self,request):
        list_display_list = super().get_list_display(request)   # 2.获取需要展示的字段列表
        # list_display_list = self.list_display    或者通过类属性来获取
        
        if request.user.is_superuser and "coding" not in list_display_list:     # 如果是超级用户,则展示coding字段,append的时候需要判断这个字段是否存在,否则每刷新一次都会往里面加一次
            list_display_list.append("coding")
        return list_display_list
8. 列表页多对多数据展示
class HomeownerAdmin(admin.ModelAdmin):
    list_display = [
        "房间号",    # 自定义前端列名称
    ]
    def 房间号(self, obj):  # 定义前端列名相同的方法
        return [i.room_number for i in obj.room.all()]   # 返回列表数据
4. 动作管理
4.1 内置的删除动作
“删除选定对象”动作使用 QuerySet.delete() 为了提高效率,它有一个重要的注意事项:你的模型的 delete() 方法将不会被调用。
如果你想覆盖这个行为,你可以覆盖 ModelAdmin.delete_queryset()或者写一个自定义的动作,以你喜欢的方式进行删除 -- 例如,通过为每个选定的项目调用 Model.delete()。
2. 删除动作的调用
@admin.register(CollectorAllocation)
class CollectorAllocationAdmin(admin.ModelAdmin):
    list_display = []
    def delete_queryset(self, request, queryset):
        """
        执行删除动作时,调用的方法
        """
4.2 新增动作
1. 编写动作函数
动作函数是常规函数,它有三个参数:
- 当前
ModelAdmin - 一个代表当前请求的 
HttpRequest - 一个包含用户所选择的对象集合的 
QuerySet 
@admin.register(CollectorAllocation)
class CollectorAllocationAdmin(admin.ModelAdmin):
	def make_published(self, request, queryset):    # 在ModelAdmin中定义动作函数
    	queryset.update(status='p')
2. 注册动作函数和动作名称
默认情况下,这个动作会在动作列表中显示为 “Make published” —— 函数名称,下划线用空格代替
@admin.register(CollectorAllocation)
class CollectorAllocationAdmin(admin.ModelAdmin):
    list_display = []
    actions = [make_published]     # 注册动作
	make_published.short_description = "修改状态为已发布"   # 修改动作名称
4.3 执行后的信息提示
如果在运行你的动作时可能出现可预见的错误情况,你应该优雅地告知用户问题。这意味着处理异常,并使用 django.contrib.admin.ModelAdmin.message_user() 在响应中显示一个用户友好的问题描述。
# 动作函数中,根据错误提示信息
@admin.register(CollectorAllocation)
class CollectorAllocationAdmin(admin.ModelAdmin):
    def make_published(self, request, queryset):  # 在ModelAdmin中定义动作函数
        try:
            queryset.update(status='p')
            self.message_user(request, ngettext(
                '%d 执行成功',
                '%d 执行成功', updated, ) % updated, messages.SUCCESS
        except Exception:
            self.message_user(request, "修改状态时,发生错误!")  # 调用message_user() 进行提示信息
4.4 中间页动作
默认情况下,在执行完一个操作后,用户会被重定向回原来的变更列表页面。但是,有些操作,尤其是比较复杂的操作,需要返回中间页面。例如,内置的删除操作在删除所选对象之前会要求确认。
要提供一个中间页,从你的操作中返回一个 HttpResponse(或子类)。例如,你可以写一个导出函数,使用 Django 的 序列化函数将一些选定的对象转储为 JSON,最好的办法是写一个小的动作,重定向到你的自定义导出视图:
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect
def export_selected_objects(modeladmin, request, queryset):
    selected = queryset.values_list('pk', flat=True)
    ct = ContentType.objects.get_for_model(queryset.model)
    return HttpResponseRedirect('/export/?ct=%s&ids=%s' % (
        ct.pk,
        ','.join(str(pk) for pk in selected),
    ))
4.5 新增全局动作
这使得 export_selected_objects 动作作为一个名为 “export_selected_objects” 的动作在全站范围内可用。你可以通过向 AdminSite.add_action() 传递第二个参数,来明确地给这个动作起一个名字 —— 如果你以后想以编程方式 移除此动作
from django.contrib import admin
admin.site.add_action(export_selected_objects,"export_selected")
4.6 禁用和重启全局动作
1. 禁用全局动作
admin.site.disable_action('delete_selected')
2. 为某个特定模型重启动作
# 全局禁用 delete_selected 动作
admin.site.disable_action('delete_selected')
class SomeModelAdmin(admin.ModelAdmin):
    actions = ['some_other_action']
    ...
# AnotherModelAdmin 重启delete_selected动作
class AnotherModelAdmin(admin.ModelAdmin):
    actions = ['delete_selected', 'a_third_action']
4.7 禁用特定ModelAdmin的所有动作
class MyModelAdmin(admin.ModelAdmin):
    actions = None     # 将actions,设置为None,则禁用了MyModelAdmin的所有动作
4.8 根据条件启用和禁止某动作
class MyModelAdmin(admin.ModelAdmin):
    ...
    def get_actions(self, request):
        actions = super().get_actions(request)   # 获取所有动作
        if request.user.username[0].upper() != 'J':
            if 'delete_selected' in actions:
                del actions['delete_selected']  # 根据条件禁用delete_selected动作
        return actions
4.9 自定义动作权限
``make_published() 动作将只提供给通过CollectorAllocationAdmin.has_change_permission() 检查的用户`
allowed_permissions 和相应的方法检查的可用值是:
'add':ModelAdmin.has_add_permission()'change':ModelAdmin.has_change_permission()'delete':ModelAdmin.has_delete_permission()'view':ModelAdmin.has_view_permission()
@admin.register(CollectorAllocation)
class CollectorAllocationAdmin(admin.ModelAdmin):
    list_display = []
    actions = [make_published]     # 注册动作
 	make_published.allowed_permissions = ('change',)   # 有change权限的用户都可以执行make_published动作
你可以指定任何其他的值,只要你在 ModelAdmin 上实现一个相应的 has_ 方法。
from django.contrib import admin
from django.contrib.auth import get_permission_codename
class ArticleAdmin(admin.ModelAdmin):
    actions = ['make_published']
    make_published.allowed_permissions = ('publish',)
    def make_published(self, request, queryset):
        queryset.update(status='p')
    def has_publish_permission(self, request):
        opts = self.opts
        codename = get_permission_codename('publish', opts)
        return request.user.has_perm('%s.%s' % (opts.app_label, codename))
4. 数据校验
5. 权限校验
5.1 什么是权限?
权限是能够约束用户行为和控制页面显示内容的一种机制。一个完整的权限应该包含3个要素: 用户,对象和权限,即什么用户对什么对象有什么样的权限。
假设我们有一个应用叫blog,其包含一个叫Article(文章)的模型。那么一个超级用户一般会有如下4种权限,而一个普通用户可能只有1种或某几种权限,比如只能查看文章,或者能查看和创建文章但是不能修改和删除。
- 查看文章(view)
 - 创建文章(add)
 - 更改文章(change)
 - 删除文章(delete)
 
我们在Django的admin中是可以很轻易地给用户分配权限的。
5.2 权限分配
Django中的用户权限分配,主要通过Django自带的Admin界面进行维护的。当你编辑某个user信息时, 你可以很轻易地在User permissions栏为其设置对某些模型查看, 增加、更改和删除的权限(如下图所示)。
Django的权限permission本质是djang.contrib.auth中的一个模型, 其与User的user_permissions字段是多对多的关系。当我们在INSTALLED_APP里添加好auth应用之后,Django就会为每一个你安装的app中的模型(Model)自动创建4个可选的权限:view, add,change和delete。(注: Django 2.0前没有view权限)。随后你可以通过admin将这些权限分配给不同用户。
5.3 权限分类
权限名一般有app名(app_label),权限动作和模型名组成。以blog应用为例,Django为Article模型自动创建的4个可选权限名分别为:
查看文章(view): blog.view_article
创建文章(add): blog.add_article
更改文章(change): blog.change_article
删除文章(delete): blog.delete_article
5.4 是否拥有某权限
在前例中,我们已经通过Admin给用户A(user_A)分配了创建文章和修改文章的权限。我们现在可以使用 user.has_perm() 方法来判断用户是否已经拥有相应权限
user_A.has_perm('blog.add_article')
user_A.has_perm('blog.change_article')
5.5 查看用户组权限
如果我们要查看某个用户所在用户组的权限或某个用户的所有权限(包括从用户组获得的权限),我们可以使用get_group_permissions()和get_all_permissions()方法。
user_A.get_group_permissions()
user_A.get_all_permissions()
5.6 手动定义和分配权限
有时django创建的4种可选权限满足不了我们的要求,这时我们需要自定义权限。实现方法主要有两种。下面我们将分别使用2种方法给Article模型新增了两个权限,一个是publish_article, 一个是comment_article。
方法1. 在Model的meta属性中添加permissions。
classArticle(models.Model):
    ...
    classMeta:
    permissions = (("publish_article","Can publish article"),("comment_article","Can comment article"),)
方法2. 使用ContentType程序化创建permissions。
from blog.models import Article
fromd jango.contrib.auth.models import Permission
fromd jango.contrib.content.types.models import ContentType
content_type = ContentType.objects.get_for_model(article)
permission1 = Permission.objects.create(codename='publish_article',name='Can publish articles',content_type=content_type,)
permission2 = Permission.objects.create(codename='comment_article',name='Can comment articles',content_type=content_type,)
当你使用python manage.py migrate命令后,你会发现Django admin的user permissions栏又多了两个可选权限。
5.7 代码手动设置权限
如果你不希望总是通过admin来给用户设置权限,你还可以在代码里手动给用户分配权限。这里也有两种实现方法。
方法1. 使用user.user_permissions.add()方法
myuser.user_permissions.add(permission1,permission2,...)
方法2. 通过user所在的用户组(group)给用户增加权限
5.8 代码手动删除权限
如果你希望在代码中移除一个用户的权限,你可以使用remove或clear方法。
myuser.user_permissions.remove(permission,permission,...)
myuser.user_permissions.clear()
5.9 权限的缓存机制
Django会缓存每个用户对象,包括其权限user_permissions。当你在代码中手动改变一个用户的权限后,你必须重新获取该用户对象,才能获取最新的权限。
比如下例在代码中给用户手动增加了change_blogpost的权限,如果不重新载入用户,那么将显示用户还是没有change_blogpost的权限。
from django.contrib.auth.models import Permission,User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import get_object_or_404
from myapp.models import BlogPost
defuser_gains_perms(request,user_id):
    user = get_object_or_404(User,pk=user_id)
    # any permission check will cache the current set of permissions
    user.has_perm('myapp.change_blogpost')
    content_type = ContentType.objects.get_for_model(BlogPost)
    permission = Permission.objects.get(
    codename='change_blogpost',
    content_type=content_type,
    )
    user.user_permissions.add(permission)
    # Checking the cached permission set
    user.has_perm('myapp.change_blogpost')# False
    # Request new instance of User
    # Be aware that user.refresh_from_db() won't clear the cache.
    user = get_object_or_404(User,pk=user_id)
    # Permission cache is repopulated from the database
    user.has_perm('myapp.change_blogpost')# True
5.10 用户权限的验证
我们前面讲解了用户权限的创建和设置,现在我们将进入关键一环,用户权限的验证。我们在分配好权限后,我们还需要在视图views.py和模板里验证用户是否具有相应的权限,否则前面设置的权限形同虚设。这就是为什么我们前面很多django实战案例里,没有给用户分配某个模型的add和change权限,用户还是还能创建和编辑对象的原因。
1. 视图中验证
在视图中你当然可以使用user.has_perm方法对一个用户的权限进行直接验证。当然一个更好的方法是使用@permission_required这个装饰器。
@permission_required(perm,login_url=None,raise_exception=False)
你如果指定了login_url, 用户会被要求先登录。如果你设置了raise_exception=True, 会直接返回403无权限的错误,而不会跳转到登录页面。使用方法如下所示:
如果你使用基于类的视图(Class Based View), 而不是函数视图,你需要继承PermissionRequiredMixin这个类,如下所示:
2. 模板中验证
在模板中验证用户权限主要需要学会使用perms这个全局变量。perms对当前用户的user.has_module_perms和user.has_perm方法进行了封装。当我们需要判断当前用户是否拥有blog应用下的所有权限时,我们可以使用:
{{ perms.blog }}
我们如果判断当前用户是否拥有blog应用下发表文章讨论的权限,则使用:
这样结合template的if标签,我们可以通过判断当前用户所具有的权限,显示不同的内容了.
{% if blog.article %}
You have permission to do something in this blog app.
{% if perms.blog.add_article %}
You can add articles.
{% endif %}
{% if perms.blog.comment_article %}
You can comment articles!
{% endif %}
{% else %}
You don't have permission to do anything in the blog app.
{% endif %}
5.11 用户组(Group)
用户组(Group)和User模型是多对多的关系。其作用在权限控制时可以批量对用户的权限进行管理和分配,而不用一个一个用户分配,节省工作量。将一个用户加入到一个Group中后,该用户就拥有了该Group所分配的所有权限。例如,如果一个用户组editors有权限change_article, 那么所有属于editors组的用户都会有这个权限。
将用户添加到用户组或者给用户组(group)添加权限,一般建议直接通过django admin进行。如果你希望手动给group添加或删除权限,你可以使用如下方法。
如果你要将某个用户移除某个用户组,可以使用如下方法。
Django自带权限机制的不足
Django自带的权限机制是针对模型的,这就意味着一个用户如果对Article模型有change的权限,那么该用户获得对所有文章对象进行修改的权限。如果我们希望实现对单个文章对象的权限管理,我们需要借助于第三方库比如django guardian。至于该库的使用,我们以后再详细介绍。
6. 可重写的方法
def delete_model(self, request, obj)
def delete_queryset(self, request, queryset)
def get_search_results(self, request, queryset, search_term) # Use other search engines Haystack
def save_related(self, request, form, formsets, change)
def get_autocomplete_fields(self, request) #  该方法返回list或tuple的字段名称,该字段名称将与自动完成小部件一起显示 。
def get_readonly_fields(self, request, obj=None) # 该方法返回将显示为只读的字段名称的list或tuple
 
def get_exclude(self, request, obj=None) # 该方法返回exclude的字段列表
 
def get_fields(self, request, obj=None) # 返回fields。
def get_fieldsets(request, obj=None) # 该方法返回一个二元列表,fieldsets
 
def get_list_filter(self, request) # 返回list_filter。
def get_list_display(self, request) # 该方法返回list或tuple的字段名称,该名称将显示在更改列表视图中
def get_list_select_related(self, request) # return a list or tuple
def get_list_display_links(request, list_display) # 返回将链接到变更视图的变更列表中的一个None或一个list或tuple多个字段名称
 
def get_sortable_by(self, request) # 返回一个集合(如list,tuple或set)字段名,通过该集合进行排序。
def get_inline_instances(self, request, obj=None) # return a list or tuple of InlinesModelAdmin
def get_inlines(self, request, obj) # You can override this method to dynamically add inlines
7.可重写的视图和权限
# Permissions related
def has_add_permission(self, request) # 是否具有添加数据的权限。如果允许添加对象,应返回 True,否则应返回 False。
def has_view_permission(self, request, obj=None) # 如果允许查看 obj,应返回 True,否则应返回 False。如果 obj 为 None,则应返回 True 或 False 以指示是否允许查看此类型的对象(例如,False 将解释为不允许当前用户查看此类型的任何对象),如果用户具有"更改"或"查看"权限,则默认实现将返回 True。
def has_change_permission(self, request, obj=None) # 是否具有change权限。 如果允许编辑 obj,应返回 True,否则应返回 False。如果 obj 为"无",则应返回 True 或 False 以指示通常是否允许编辑此类型的对象
def has_delete_permission(self, request, obj=None)  # 是否具有delete权限。 如果允许删除 obj,应返回 True,否则应返回 False。如果 obj 为"无",则应返回 True 或 False 以指示通常是否允许删除此类型的对象。
def has_module_permission(self, request)# 如果允许在管理页上显示模块并访问模块,则应返回 True,否则为 False。默认情况下使用 User.has_module_perms() 。虽然设置为True后不显示该模块,但是通过url依然可以访问。
 
 
# Other view methods
def add_view(self, request, form_url='', extra_context=None)
def change_view(self, request, object_id, form_url='', extra_context=None)
def changelist_view(self, request, extra_context=None)
def delete_view(self, request, object_id, extra_context=None)
def history_view(self, request, object_id, extra_context=None)
7.1页面展示数据的过滤
class BookModelAdmin(admin.ModelAdmin):
    
    '对展示的数据进行过滤'
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if request.user.is_superuser:
            return qs
        else:    #不是超级管理员,只展示跟跟自己场域一样的用户信息
            return qs.filter(belong=request.user.belong)
7.2 动态展示字段
class BookModelAdmin(admin.ModelAdmin):
    
    '对展示的字段进行判断'
    def get_list_display(self, request):
        # 如果是超级管理员,展示养殖场所用信息
        if request.user.is_superuser:
            return self.list_display
         # 不是超级管理员 则删除id,change 字段,并加入chakan字段
        else:  
            list_display=copy.deepcopy(self.list_display)
            list_display.remove('id')
            list_display.remove('change')
            list_display.insert(0,'chakan')   #在第一个位置插入‘查看’功能
            return list_display
7.3 自定义字段标签
from django.utils.html import format_html 
class BookModelAdmin(admin.ModelAdmin):
    
    list_display = ['delete','look','xiazai','custom_date']
 
    # model.py中create date = models.DateTimeField(auto now add=True)
    def custom_date(self, obj):
            return format_html(
                '<span style="color: red;">{}</span>',
                obj.create_date.strftime("%Y-%m-%d %H:%M:%S")
            )
 
 
    custom_date.short_description = '设置时间的格式和颜色'  
 
    def delete_button(self,obj):
            return format_html('<a href="/breed/breedinginfo/{}/delete/" ><p style="width:30px;padding:0px">删除</p><a/>',obj.id)
    
    delete_button.short_description = '删除'
 
    def look_button(self,obj):
    	return format_html('<a href="/animal/detail/?id={}" target="_blank" style="width:auto;padding:0px">查看详细</a>',obj.id)
    look_button.short_description='查看详细'
 
    def xiazai(self,obj):
        return format_html('<a href="/animal/xiazai/?id={}" target="_blank"><p style="width:30px;padding:0px">下载</p><a/>',obj.id)
    xiazai.short_description='下载'
 
    #自定义admin后台显示字段
    def view_birth_date(self, obj):  # 自定义的字段,obj为当前model对象
        # Upper是数据库函数,用于将字符串中的小写字母全部转换成大写字母输出
        return ("%s %s" % (obj.variety, obj.name)).upper()
7.4 自定义动作
class BookModelAdmin(admin.ModelAdmin):
    
    def 自定义名称(self,request,queryset):
        return TemplateResponse(request, 'actions/form.html', context)
    自定义名称.short_description = 'xxxx'
 
    actions = [自定义名称]
7.5 编辑保存添加按钮的功能逻辑
class BookModelAdmin(admin.ModelAdmin):
    
    def save_model(self, request, obj, form, change):
        '''如果直接写代码,这包含增加和修改两个功能,如果需要单独设置则按照下面的写'''
        if change: #点击修改执行的下面的代码
            obj.user = request.user  # 在保存之前执行的操作
            print(obj)
            super().save_model(request, obj, form, change)  # 执行保存
            # 此处写保存后需要执行的操作   
示例:
class BookModelAdmin(admin.ModelAdmin):
    
	def save_model(self, request, obj, form, change):
        print('查看sava_madel中的obj和self')
        print(obj)    #obj表示新增的对象,具体值由model中的  def __str__(self)   决定
        print(self)   #  animal.AnimalInfoAdmin
 
        if obj.father ==None:
            obj.father=AnimalInfo.objects.get(id=1)
        if obj.mother ==None:
            obj.mother=AnimalInfo.objects.get(id=2)
        super().save_model(request, obj, form, change)
        # 预警
        if not change:   #新增
            creat_list = []
 
            # 如果用户是超级管理员,belong为空
            belong = None if request.user.is_superuser else request.user.belong
            # if request.user.is_superuser:
            #     belong = None
            # else:
            #     request.user.belong
 
            warning_date=obj.birth_date+datetime.timedelta(days=60)
 
            '新加一个动物信息后,免疫预警信息会批量保存该动物的疫苗信息'
            for vaccine in range(1,7):
                creat_list.append(
                    Immunity_Alert_Info(      #免疫预警信息
                        animal=obj,       #self_num
                        immunity_name=vaccine,   #免疫名称
                        belong=belong,   #belong在上面的代码中做了判断,如果是超级管理员,belong为空
                        warning_date=warning_date   #预警日期
                    )
                )
                warning_date = warning_date + datetime.timedelta(days=60)
 
            # Django批量保存数据库 bulk_create 方法
            Immunity_Alert_Info.objects.bulk_create(creat_list)
            super().save_model(request, obj, form, change)
7.6 编辑删除和增加权限
class BookModelAdmin(admin.ModelAdmin):
    
	def has_add_permission(self, request):    #覆盖增加按钮
        return False
 
    def has_delete_permission(self, request, obj=None):    #覆盖删除按钮
        return False
 
7.7 详情页面设置只读字段

class BookModelAdmin(admin.ModelAdmin):
	# 在详情页面添加自定义的只读字段
    readonly_fields = ('address_report',)
 
    def address_report(self, instance):
        return 'xxx'
7.8 删除单条数据功能逻辑
class BookModelAdmin(admin.ModelAdmin):
    
    def delete_model(self, request, obj):
        """
        删除单条记录
        同时删除配方成分表中的数据
        """
        obj.delete()
7.9 删除一条或多条数据
class BookModelAdmin(admin.ModelAdmin):
    
    def delete_queryset(self, request, queryset):
        """Given a queryset, delete it from the database."""
        queryset.delete()
7.10 超管和普通用户对只读字段的修改权限
class BookModelAdmin(admin.ModelAdmin):
    '''
    超级管理员可以更改只读属性的字段,普通用户不可以!!!
    如果需要执行这个功能,则需要将上面readonly_fields = ['sheep_kind','name','weight',]这行代码注释
    def get_readonly_fields(self, request, obj=None):
        """  重新定义此函数,限制普通用户所能修改的字段  """
    '''
    readonly_fields = ('sheep_kind','name','weight',) 
    
     def get_readonly_fields(self, request, obj=None):
        if request.user.is_superuser:
            self.readonly_fields = []
        return self.readonly_fields
7.11 不同超管和普通用户的字段排序
class BookModelAdmin(admin.ModelAdmin):
    
    '''get_ordering方法将request作为参数,并预期返回一个列表或元组,以便进行类似于ordering属性的排序。'''
    def get_ordering(self, request):
        if request.user.is_superuser:  # 判断是不是超级用户
            print('----------')
            return ['variety','name',]
        else:
            return ['name']
8. 使用过程中的问题
8.1 表单保存时覆盖其他字段的值为空
 是由于 forms/modelform  有exclude字段,如下
class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        exclude = ["created_time", "updated_time"]
上面代码表示出了created_time和updated_time 其他字段全部展示和校验,此时,modelform 校验的时候会根据数据库的校验规则来校验所有字段,当前端某一个字段没有展示让用户得以进行前端输入框输入时,此时无论这个字段的原始值是多少,都相当于用户输入了空值,表示发生了修改,此字段的值为None,由于数据库设置了null=True,blank=True,所以可以写进数据库,从而置空这个字段的值

                
            
        
浙公网安备 33010602011771号