用django2.0来开发 后台会员管理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用django2.0来开发 后台会员管理相关的知识,希望对你有一定的参考价值。

【用django2.0来开发】 后台会员管理

项目地址:https://gitee.com/ccnv07/django_example
这一篇主要是要完成django 后台的会员管理功能, 会涉及到model, ModelAdmin, admin, Form等多个方面, 所以会讲的比较细

创建会员模块

cd cms
python manage.py startapp account

python manage.py startapp 是创建一个模块

至于模块的定义, 每个人都有不同的看法, 有些是按照功能来划分的, 比如会员模块, 订单模块等, 有些是按照不同场景划分的, web端, api, wap端等。这个无所谓, 咱们的项目是按照功能来划分

注册到项目中, 因为django的模块是可插拔的, 所以每个需要的模块都要在cms/settings.py中注册

# cms/settings.py
INSTALLED_APPS = [
    ‘django.contrib.admin‘,
    ‘django.contrib.auth‘,
    ‘django.contrib.contenttypes‘,
    ‘django.contrib.sessions‘,
    ‘django.contrib.messages‘,
    ‘django.contrib.staticfiles‘,
    ‘account‘ # 添加了我们刚创建的account
]

创建Model

通过django的model, 可以直接操作数据库的表结构等
打开account/models.py, 创建一个Account模型
技术分享图片

account = models.CharField(max_length=64, blank=True, verbose_name=‘用户名‘)
account: 字段名
models.CharField: 指定字段类型是char, 对应到数据库是varchar
max_length: 是字段长度
blank: 是否可以为空
verbose_name: 控制的是后台列表的展示

除此之外, 常见的字段选项还有
null: 字段的值是否可以是null, 设置为True是可以
choices: 字段的选项值, 一般会影响后台列表和编辑, form表单中字段的显示

status = models.IntegerField(
        default=1, blank=True, verbose_name=‘状态‘, choices=((1, ‘启用‘), (0, ‘禁用‘)))
# 这个choices=((值, 显示名),)

default: 默认值, 字段不存在时的插入
unique: 是否唯一
auto_now_add: DateTime和Date字段专属, 新增时自动插入时间
auto_now: 同auto_now_add, 但是是在任何变更时都变化, 如果存在auto_now, 就不用设置auto_now_add, 否则django提示你两个不能共存。
技术分享图片

Model数据迁移操作

基本操作流程:

  1. 创建迁移文件
  2. 执行迁移
python manage.py makemigrations

会有类似以下的提示
技术分享图片
这一步其实还没有迁移到数据库, 只是创建了迁移文件, 在account/migrations目录下, 打开就会发现是python的类
技术分享图片

接下来执行迁移

python manage.py migrate

技术分享图片
说明迁移成功

将Model注册到Admin后台中

注册后, 才能在后台看到相应的管理

# account/admin.py
from django.contrib import admin
from django.db import models
from .models import Account

admin.site.register(Account)

预览看看

先运行测试服务器 python manage.py runserver
在浏览器中输入http://127.0.0.1:8000/admin
技术分享图片

但是可能你看到的是英文的, 所以先将语言和时间本地化

# cms/settings.py
LANGUAGE_CODE = ‘zh-Hans‘
TIME_ZONE = ‘PRC‘

然后刷新, 就可以看到变成中文了, 但是Accounts那个还是英文, 挺丑的

# account/models.py
class Account(model.Model):
    ...忽略代码
    class Meta:
        ordering = [‘-id‘]
        verbose_name = ‘会员‘  # 指定在后台列表、添加、编辑等其他非菜单页显示的名称, 但是后面会加s
        verbose_name_plural = ‘会员管理‘  # 指定后台菜单显示的名称, 不会加s

修改管理界面的展示

# account/admin.py
from django.contrib import admin
from django.db import models
from .models import Account

@admin.register(Account)
class AccountAdmin(admin.ModelAdmin):
    date_hierarchy = ‘create_time‘
    # 控制列表页按钮显示位置
    actions_on_top = False
    actions_on_bottom = True

    # 是否显示列表页数据数量([选中了n个中的m个])
    actions_selection_counter = True

    # 控制新增、编辑页面显示的字段
    fields = (‘account‘, ‘nickname‘, ‘password‘, ‘email‘, ‘phone‘, ‘status‘)

    # 排除新增、编辑页面要显示的字段
    # exclude = (‘password‘, )

    # 控制列表页显示的字段
    list_display = (‘account‘, ‘nickname‘, ‘email‘, ‘phone‘, ‘status‘,
                    ‘create_time‘)
    list_display_links = (‘account‘, )

    # 指定字段是否可以在列表页直接编辑
    list_editable = (‘status‘, )

    # 列表页过滤条件
    list_filter = (‘status‘, )

    # 控制每页显示数量
    # list_per_page = 1

    # 列表页排序
    ordering = [‘-id‘]
    # 自定义操作
    actions = [‘make_published‘, ‘deleted_select‘]

这一块都是一些配置项, 大家可以直接复制, 然后一点点屏蔽了试试

修改默认操作

还记得我们有一个is_deleted字段么? 可以回头看看AccountModel. 因为django后台的删除时直接删掉数据, 而我们要做到的操作是删除数据就更新is_deleted字段为1

# account/admin.py
# 这个会直接将整个模块的删除操作都禁用掉
# 禁用默认的删除操作
admin.site.disable_action(‘delete_selected‘)

# 重写增加一个删除操作
def deleted_select(self, request, queryset):
    queryset.update(is_deleted=1)

# 将操作添加进去并指定显示名称
admin.site.add_action(deleted_select, ‘删除所选会员‘)

技术分享图片

同样的, 我们有一个status字段, 希望可以在列表中直接多选, 将用户禁用和启用

# 还是account/admin.py
# 这个操作可能只有会员管理有, 所以我们可以定在AccountAdmin中
@admin.register(Account)
class AccountAdmin(admin.ModelAdmin):
    def stop_account(self, request, queryset):
        queryset.update(status=0)
    stop_account.short_description = "禁用"

    def start_account(self, request, queryset):
        queryset.update(status=1)
    start_account.short_description = ‘启用‘

然后刷新页面就可以看到

现在我们创建个会员, 点击增加 会员+按钮, 输入指定的信息, 然后点击添加, 就会在列表显示
多增加几个
然后回到列表, 删除其中一个会员, 然后惊奇的发现, 我去, 为啥还会展示出来呢? 因为我们定义的删除是is_deleted=1, 而django默认的删除时真正的删除操作, 所以还是会显示出来
那么, 怎么办呢?

# 还是account/admin.py
@admin.register(Account)
class AccountAdmin(admin.ModelAdmin):
    # 过滤显示的数据, 只显示is_deleted=0的数据
    # 这个方法可以指定数据的查询条件
    # queryset就是获取到查询对象, filter()是指定查询is_deleted=0的数据
    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        return queryset.filter(is_deleted=0)

然后刷新页面, 就发现执行过删除操作的数据没有了

我还想更改字段新增编辑时的表单

然后我们也发现了几个新的问题

  1. 密码输入框居然是明文的
  2. 新增编辑时的验证条件也不符合我的要求阿
  3. 编辑会员信息时, 如果密码框为空, 就会把密码清空, 这太恐怖了

针对这几个问题, 我们就得创建一个ModelForm来控制表单了

# 创建个account/forms.py
# -*- coding: utf-8 -*-
from django import forms
from django.db.models import Q
from django.core.exceptions import ValidationError
from django.contrib.auth.hashers import make_password
from .models import Account

class AccountForm(forms.ModelForm):
    account = forms.CharField(required=True, error_messages={
            ‘required‘: ‘请输入用户名‘,
        }, label=‘用户名‘)
    password = forms.CharField(
        # 指定密码字段使用password输入框
        widget=forms.PasswordInput(),
        max_length=12,
        min_length=6,
        strip=True,
        error_messages={
            ‘max_length‘: ‘最大长度不可超过12个字符‘,
            ‘min_length‘: ‘最小长度不可少于6个字符‘
        },
        required=False,
        label=‘密码‘)
    email = forms.EmailField(
        required=True, error_messages={‘required‘: ‘请输入邮箱‘}, label=‘邮箱‘)
    phone = forms.CharField(
        required=True,
        error_messages={‘required‘: ‘请输入手机号‘},
        label=‘手机号‘)
    status = forms.ChoiceField(
        choices=((1, ‘启用‘), (0, ‘禁用‘)), label=‘用户状态‘)

    class Meta:
        # 指定关联的model
        model = Account
        # 使用自定义的Form, 就必须指定fields or exclude属性, 否则报错
        fields = (‘account‘, ‘password‘, ‘email‘, ‘phone‘, ‘status‘)

最上面的几个类属性 account,email,phone,status等, 都是指定表单的字段
required: 字段必须输入
error_messages: 指定错误提示信息
label: 是在表单中显示的中文名
help_text: 显示字段的帮助信息
max_length和min_length: 指定字段输入的最大/最小长度
strip: 自动过滤空

然后在AccountAdmin中指定使用的form

# account/admin.py
from .forms import AccountForm
@admin.register(Account)
class AccountAdmin(admin.ModelAdmin):
    ... 忽略代码
    form = AccountForm

然后就可以使用了, 但是其他验证的条件我们如何来做呢?
先重写AccountAdmin.get_form方法

@admin.register(Account)
class AccountAdmin(admin.ModelAdmin):
    ... 忽略代码
    def get_form(self, request, obj=None, **kwargs):
        form = super(AccountAdmin, self).get_form(request, obj=obj, **kwargs)

        # obj 保存的是models.Account的信息
        # 根据是否有pk, 来赋予form不同的场景, 根据不同的场景可以进行不同的验证
        if (hasattr(obj, ‘pk‘)):
            form.id = obj.pk
            form.scene = ‘update‘
        else:
            form.scene = ‘insert‘
        return form

然后在form中自定义验证规则

# -*- coding: utf-8 -*-
from django import forms
from django.db.models import Q
from django.core.exceptions import ValidationError
from django.contrib.auth.hashers import make_password
from .models import Account

class AccountForm(forms.ModelForm):
    ... 忽略代码
    def clean_account(self):
        # 自动验证account字段, 判断用户名是否唯一
        # self.scene 是AccountAdmin.get_form中定义的
        if self.scene == ‘insert‘:
            _info = Account.objects.filter(
                email=self.cleaned_data[‘account‘], is_deleted=0).values(‘id‘)
        elif self.scene == ‘update‘:
            _info = Account.objects.filter(
                ~Q(id=self.id),
                email=self.cleaned_data[‘account‘],
                is_deleted=0).values(‘id‘)

        if _info:
            raise ValidationError(‘用户已存在‘)
        return self.cleaned_data[‘account‘]

    def clean_password(self):
        # 自动验证密码字段
        if self.scene == ‘insert‘:
            if not self.cleaned_data[‘password‘]:
                raise ValidationError(‘请输入密码‘)
        elif self.scene == ‘update‘:
            if not self.cleaned_data[‘password‘]:
                return None
            else:
                return self.cleaned_data[‘password‘]
        return make_password(self.cleaned_data[‘password‘])

    def clean_email(self):
        # 自动验证email字段
        if self.scene == ‘insert‘:
            _info = Account.objects.filter(
                email=self.cleaned_data[‘email‘], is_deleted=0).values(‘id‘)
        elif self.scene == ‘update‘:
            _info = Account.objects.filter(
                ~Q(id=self.id), email=self.cleaned_data[‘email‘],
                is_deleted=0).values(‘id‘)

        if _info:
            raise ValidationError(‘邮箱已存在‘)

        return self.cleaned_data[‘email‘]

    def clean_phone(self):
        if self.scene == ‘insert‘:
            _info = Account.objects.filter(
                email=self.cleaned_data[‘phone‘], is_deleted=0).values(‘id‘)
        elif self.scene == ‘update‘:
            _info = Account.objects.filter(
                ~Q(id=self.id), email=self.cleaned_data[‘phone‘],
                is_deleted=0).values(‘id‘)

        if _info:
            raise ValidationError(‘手机号已存在‘)
        return self.cleaned_data[‘phone‘]

clean_字段名定义关于某个字段的自定义方法, 验证失败, 可以使用raise ValidateError抛出错误, 验证成功, 需要返回一个值, form会赋值为model, 接着进行后续的新增/编辑操作

然后刷新页面尝试下, 发现其他的问题都解决了, 但是编辑时密码为空还是无法解决
这时我们可以重写AccountAdmin.save_model方法, 来达到我们的目的

# account/admin.py
class AccountAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        # 如果form验证完成后赋值给obj的password字段是None, 则删除密码字段, 不进行更新
        if form.cleaned_data[‘password‘] is None:
            del obj.password
        super().save_model(request, obj, form, change)

好了, 至此, 我们的后台会员管理功能就做完了。

以上是关于用django2.0来开发 后台会员管理的主要内容,如果未能解决你的问题,请参考以下文章

四用django2.0来开发后台会员管理 ModelForm表单的使用方法以及数据验证

三用django2.0来开发会员注册登录

Django2.1集成xadmin管理后台错误解决

用django2.0来开发 环境部署和初始化项目

Django2.0.6-Xadmin后台源码安装流程(python 3.8+django 2.0)

django2.0和3.0的区别