1. 程式人生 > >【用django2.0來開發】 後臺會員管理

【用django2.0來開發】 後臺會員管理

分享 == page one not ble -i 技術 信息

【用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來開發】 後臺會員管理