您好,登錄后才能下訂單哦!
django amdin是django提供的一個(gè)后臺(tái)管理頁(yè)面,該管理頁(yè)面提供完善的html和css,使得你在通過(guò)Model創(chuàng)建完數(shù)據(jù)庫(kù)表之后,就可以對(duì)數(shù)據(jù)進(jìn)行增刪改查。
創(chuàng)建一個(gè)項(xiàng)目,或者是用已有的項(xiàng)目
使用下面的命令創(chuàng)建生成數(shù)據(jù)庫(kù),這里雖然還沒(méi)有創(chuàng)建任何的表結(jié)構(gòu),但是django本身是有一些庫(kù)要?jiǎng)?chuàng)建的
python manage.py migrate
這個(gè)命令一般是搭著 python manage.py makemigrations
之后用的,不過(guò)這里我們自己還一個(gè)表都還沒(méi)創(chuàng)建呢。
啟動(dòng) django 服務(wù),然后默認(rèn)用這個(gè)地址 http://127.0.0.1:8000/admin 就可以打開(kāi)登陸界面了。
使用下面的命令,創(chuàng)建超級(jí)管理員賬戶:
python manage.py createsuperuser
根據(jù)提示,輸入用戶名和密碼后,創(chuàng)建成功后,就可以去Web界面登錄了。
去settings.py文件里修改下面2項(xiàng),主要是改了 LANGUAGE_CODE ,這樣后臺(tái)管理能顯示中文了。時(shí)區(qū)的設(shè)置這里不影響,不過(guò)順便改一下吧。
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-hans'
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'
現(xiàn)在在去看看admin的管理頁(yè)面,已經(jīng)是中文的頁(yè)面的。
先創(chuàng)建2張簡(jiǎn)單的表,有一個(gè)簡(jiǎn)單的外鍵關(guān)聯(lián):
class UserInfo(models.Model):
name = models.CharField(max_length=32)
age = models.PositiveSmallIntegerField()
gender_choice = ((1, "男性"), (2, "女性"))
gender = models.SmallIntegerField(choices=gender_choice)
dept = models.ForeignKey('Dept', models.CASCADE)
class Dept(models.Model):
name = models.CharField(max_length=32)
創(chuàng)建完表后要執(zhí)行一下下面的命令,更新到數(shù)據(jù)庫(kù):
python manage.py makemigrations
python manage.py migrate
要在admin的管理界面里看到這些表,必須要在admin.py文件里注冊(cè)一下:
from app01 import models
admin.site.register(models.UserInfo)
admin.site.register(models.Dept)
現(xiàn)在已經(jīng),可以在管理頁(yè)面里看到我們的表的。但是英文看的不舒服。
修改一下之前的表結(jié)構(gòu),添加verbose_name參數(shù):
class UserInfo(models.Model):
name = models.CharField(max_length=32, verbose_name="員工姓名")
age = models.PositiveSmallIntegerField("年齡")
gender_choice = ((1, "男性"), (2, "女性"))
gender = models.SmallIntegerField("性別", choices=gender_choice)
dept = models.ForeignKey('Dept', models.CASCADE, verbose_name="部門")
class Meta:
verbose_name = "員工信息"
verbose_name_plural = "員工信息表"
class Dept(models.Model):
name = models.CharField(max_length=32, verbose_name="部門名稱")
class Meta:
verbose_name = "部門"
verbose_name_plural = "部門表"
字段里面,部分是有位置參數(shù)的,所以就省略了 verbose_name=
這些內(nèi)容。
表名有2個(gè)變量,verbose_name就是自定制表名了,但是顯示的時(shí)候會(huì)在表名后面加個(gè)“s”,這是英文的做法。verbose_name_plural這個(gè)參數(shù),就是替換掉英文加s的顯示的內(nèi)容了,這里我統(tǒng)一在后面加了個(gè)“表”。
現(xiàn)在添加好記錄的后,看到的是 UserInfo object (1)
或 Dept object (1)
這樣。這里其實(shí)和print打印出來(lái)的結(jié)果一一樣的,直接顯示了對(duì)象。為對(duì)象添加 __str__
方法后,就能正常顯示了。最后修改后的表結(jié)構(gòu)如下:
class UserInfo(models.Model):
name = models.CharField(max_length=32, verbose_name="員工姓名")
age = models.PositiveSmallIntegerField("年齡")
gender_choice = ((1, "男性"), (2, "女性"))
gender = models.SmallIntegerField("性別", choices=gender_choice)
dept = models.ForeignKey('Dept', models.CASCADE, verbose_name="部門")
class Meta:
verbose_name = "員工信息"
verbose_name_plural = "員工信息表"
def __str__(self):
return self.name
class Dept(models.Model):
name = models.CharField(max_length=32, verbose_name="部門名稱")
class Meta:
verbose_name = "部門"
verbose_name_plural = "部門表"
def __str__(self):
return self.name
這里是用admin的后臺(tái)管理頁(yè)面創(chuàng)建用戶,直接點(diǎn)擊默認(rèn)的“認(rèn)證和授權(quán)”下面的“用戶”表,就可以創(chuàng)建記錄。
這里要注意,輸入了新用戶的用戶名、密碼和確認(rèn)密碼后,就完成了用戶的創(chuàng)建,但是這個(gè)用戶并不能登錄。隨后會(huì)有一個(gè)修改用戶的界面,都是中文就不細(xì)說(shuō)了。就是這里要勾選一下 “職員狀態(tài)(指明用戶是否可以登錄到這個(gè)管理站點(diǎn)。)” 這個(gè)復(fù)選框,該賬號(hào)才可以登錄。
只勾選了上面的復(fù)選框,可以實(shí)現(xiàn)登錄,但是近來(lái)是什么也看不到的。在往下還有 “用戶權(quán)限” ,默認(rèn)所有的賬戶都是一張表的權(quán)限都沒(méi)有的,包括超級(jí)管理員。但是超級(jí)管理員的賬戶勾選了 “超級(jí)用戶狀態(tài)(指明該用戶缺省擁有所有權(quán)限。)” 所以無(wú)視這個(gè)設(shè)置。
普通用戶就需要在這里添加權(quán)限了。這里有包括django默認(rèn)的表以及我們自己創(chuàng)建的表。權(quán)限比較粗,基本上就是控制這個(gè)用戶可以操作那些表,我沒(méi)找到只讀權(quán)限。
這里只要給賬戶 “auth|用戶|Can change user” 這一個(gè)權(quán)限,它就可以為所欲為了,包括把自己變成超管或者把別的超管去掉。
這里除了注冊(cè)我們自己的表以外, 還可以通過(guò)繼承并重構(gòu) admin.ModelAdmin
里面的部分屬性,獲得更好的管理效果。
點(diǎn)開(kāi)員工信息表,能看到現(xiàn)在只顯示一列。默認(rèn)顯示的屬性是 list_display = ('__str__',)
,所以原本顯示的是對(duì)象,這里我們已經(jīng)在類里重構(gòu)了 __str__
方法,現(xiàn)在顯示的name了。但是只顯示name還不夠,現(xiàn)在要把所有的字段都顯示出來(lái):
from app01 import models
class UserInfoAdmin(admin.ModelAdmin):
list_display = ('name', 'age', 'gender', 'dept')
admin.site.register(models.UserInfo, UserInfoAdmin)
admin.site.register(models.Dept)
這里就是創(chuàng)建一個(gè)類,繼承admin.ModelAdmin這個(gè)類,然后用自己的 list_display
屬性覆蓋掉原來(lái)的。然后注冊(cè)的函數(shù)后面再把這個(gè)自己的類作為參數(shù)加上,就可以按照我們的設(shè)置顯示字段和內(nèi)容了。
繼續(xù)在類里添加下面的屬性:
search_fields = ('name', 'age')
添加了搜索的屬性以后,就會(huì)出現(xiàn)一個(gè)搜索框。直接搜內(nèi)容按搜索吧。這里沒(méi)把部門的字段加進(jìn)去所以不會(huì)按部門搜。另外這里也沒(méi)加性別的字段,如果有性別的字段,那么也只能搜索數(shù)據(jù)庫(kù)里的值,也就是數(shù)字1和2。
繼續(xù)在類里添加下面的屬性:
list_filter = ('gender', 'dept')
添加了過(guò)濾器后,右邊就會(huì)出現(xiàn)一個(gè)過(guò)濾器的部件,也可以幫助我們篩選記錄。選項(xiàng)特別適合用過(guò)濾器來(lái)篩選。
就是限制每頁(yè)顯示的記錄數(shù)
繼續(xù)在類里添加下面的屬性:
list_per_page = 3
防止記錄太多,記得設(shè)置分頁(yè)。
繼續(xù)在類里添加下面的屬性,這里只能把外鍵加進(jìn)去:
raw_id_fields = ('dept',)
原本外鍵的位置是一個(gè)下拉的select列表,現(xiàn)在變成了input框,里面是對(duì)應(yīng)的數(shù)據(jù)庫(kù)的值(即id)。不過(guò)后面有個(gè)搜索按鈕,可以點(diǎn)開(kāi)來(lái)選擇對(duì)象的選項(xiàng)。單選并且選項(xiàng)多的時(shí)候,可以提升使用的體驗(yàn)。
如果是多對(duì)多的外鍵,需要用這個(gè):
filter_horizontal = () # 這里并沒(méi)有多對(duì)多的字段,就空著吧
這個(gè)的效果可以參考用戶權(quán)限分配里的用戶組合用戶權(quán)限的操作,多選的情況這么設(shè)置可以有更好的體驗(yàn)。
這里的兩個(gè)方法,就是提供多選和單選操作的方便性。
繼續(xù)在類里添加下面的屬性:
list_display = ('name', 'age', 'gender', 'dept')
list_editable = ('age', 'gender', 'dept')
這里要搭配list_display一起用,就是顯示出來(lái)的列表中,哪些字段是可以直接在列表中修改的,這種就不用一個(gè)一個(gè)點(diǎn)進(jìn)去改了。不過(guò)list_display里的第一個(gè)元素是不能修改的,否則會(huì)報(bào)錯(cuò)。
默認(rèn)每張表都是一個(gè)Delete的action,另外這個(gè)action是可以自己定制的,現(xiàn)在在類里這么加:
actions = ('test_action',)
def test_action(self):
pass
此時(shí)再打開(kāi)表,查看Action的下拉列表就能看到自定制的方法的名稱了。至少選中1條記錄,然后點(diǎn)go,Web上會(huì)報(bào)這么個(gè)錯(cuò)誤 test_action() takes 1 positional argument but 3 were given
,需要3個(gè)參數(shù),但是只提供了一個(gè)。所以把上面的內(nèi)容這么改一下看看:
actions = ('test_action',)
def test_action(self, request, queryset):
print(self)
print(request)
print(queryset)
# 輸出如下:
# crm.CustomerAdmin
# <WSGIRequest: POST '/admin/crm/customer/'>
# <QuerySet [<Customer: 基佬_767676>, <Customer: 小哥_1123081>, <Customer: 姍姍_33312333>]>
參數(shù)分別是請(qǐng)求和QuerySet。
另外如果方法是公用的,也可以把方法寫到類外面去。并且是支持自定義顯示的名字的,如果沒(méi)有設(shè)置,默認(rèn)顯示的就是方法名。定義actions可以使用字符串的函數(shù)名,也可以直接引用函數(shù):
# 函數(shù)可以寫在類的外面,作為一個(gè)公共的方法
def test_action(self, request, queryset):
print(self)
print(request)
print(queryset)
test_action.short_description = "中文顯示自定義Actions"
# 下面是是在類里定義action,可以用函數(shù)名,或者直接引用函數(shù)
actions = (test_action,)
這部分內(nèi)容講的不是很系統(tǒng),下面是官網(wǎng)的文檔連接:
https://docs.djangoproject.com/zh-hans/2.0/topics/auth/customizing/#customizing-authentication-in-django
django有一張自己的認(rèn)證表 auth_user ,直接用這張表記錄用戶的基本認(rèn)證信息。更加詳細(xì)的用戶信息,就做一個(gè)一對(duì)一的外鍵,也就是下面的UserProfile表,來(lái)記錄自己的更加詳細(xì)的用戶信息。
這里另起爐灶,重新建2張表,和上面的講的每關(guān)系了。
下面是一種實(shí)現(xiàn)方法,并沒(méi)有細(xì)講,先貼上表結(jié)構(gòu):
ffrom django.contrib.auth.models import User
class UserProfile(models.Model):
"""賬號(hào)"""
user = models.OneToOneField(User, models.CASCADE)
name = models.CharField(max_length=32)
roles = models.ManyToManyField('Role', blank=True)
def __str__(self):
return self.name
class Role(models.Model):
"""角色表"""
name = models.CharField(max_length=32, unique=True)
def __str__(self):
return self.name
新創(chuàng)建的表結(jié)構(gòu),記得去執(zhí)行下面的兩個(gè)步驟:
這里創(chuàng)建了一張自己的表,就是存放用戶信息的,比如這里用戶信息就一個(gè)字段 name 。并且和django的User表做了一對(duì)一的關(guān)聯(lián)。也就是用戶的認(rèn)證信息和用戶其他信息拆開(kāi)來(lái),認(rèn)證信息直接使用django的User表。另外這里還有個(gè)角色表,留著做賬號(hào)的權(quán)限管理的。貌似沒(méi)什么用,但是作為一個(gè)結(jié)構(gòu)就一起放上來(lái)了。
還可以做更加深度的自定義,文檔后面還有很多內(nèi)容。再深入下去就是要用使用自己的表(比如:crm_myuser表)替代django提供的auth_user這張表了,需要注意下面幾點(diǎn):
AUTH_USER_MODEL = 'crm.MyUser'
。下面用的都是django幫我么封裝好的方法來(lái)實(shí)現(xiàn)賬號(hào)的登錄和驗(yàn)證等操作:
from django.contrib.auth import authenticate # 驗(yàn)證用戶名和密碼的方法
from django.contrib.auth import login # 登錄,上面只是驗(yàn)證,這個(gè)才是登錄的動(dòng)作,會(huì)幫我么創(chuàng)建session
下面是例子:
from django.contrib.auth import authenticate, login
def acc_login(request):
"""用戶登錄驗(yàn)證"""
if request.method == 'POST':
_username = request.POST.get('username')
_password = request.POST.get('password')
user_obj = authenticate(username=_username, password=_password)
if user_obj is not None:
print(user_obj) # 如果認(rèn)證通過(guò),返回的是用戶對(duì)象,否則是None
login(request, user_obj)
return redirect('/index/')
return render(request, 'login.html')
使用logout()方法就可以登出
from django.contrib.auth import logout
def logout_view(request):
logout(request)
return redirect('/accounts/login/')
登出后跳轉(zhuǎn)到登錄頁(yè)面,django默認(rèn)的登錄也的目錄是上面的 '/accounts/login/‘ 。
這個(gè)直接用就好了:
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
pass
只要沒(méi)有認(rèn)證就會(huì)跳轉(zhuǎn)到登錄頁(yè)面,默認(rèn)的登錄頁(yè)面的url是 '/accounts/login/‘ ,也可以通過(guò)設(shè)置改成別的。在settings.py里加一個(gè)參數(shù),指定登錄頁(yè)面的url:
LPGIN_URL = '/login/'
上面是全局的改變登錄頁(yè)面url方法,裝飾器本身也有參數(shù),可以指定url:
@login_required(login_url='/accounts/login/')
上面在跳轉(zhuǎn)到登錄頁(yè)面的同時(shí),也會(huì)保存當(dāng)前請(qǐng)求頁(yè)面的url,默認(rèn)是放在next參數(shù)里的。這需要我們寫登錄處理函數(shù)的時(shí)候,在認(rèn)證成功后,能夠用 request.GET.get('next')
獲取一下這個(gè)參數(shù),如果有就跳轉(zhuǎn)到參數(shù)指定的url。這個(gè)參數(shù)是一個(gè)get請(qǐng)求的參數(shù),名字默認(rèn)是next,也可以指定成別的名字:
@login_required(redirect_field_name='my_redirect_field')
登錄本身仍然是一個(gè)POST請(qǐng)求,但是依然可以帶GET請(qǐng)求的參數(shù),通過(guò)GET請(qǐng)求的參數(shù)獲取的方法是能夠通過(guò)解析url獲取到參數(shù)的。
默認(rèn)每張表都有 add、change、delete 這3個(gè)權(quán)限。也可以添加自定義的權(quán)限,隨便找個(gè)Model,一般就是用戶信息的Model。在Meta里定義Permissions,前面是權(quán)限的名字,后面的權(quán)限的描述:
class Task(models.Model):
...
class Meta:
permissions = (
("view_task", "允許瀏覽"),
("change_task_status", "Can change the status of tasks"),
("close_task", "Can remove a task by setting its status as closed"),
)
設(shè)置完Model后,需要同步一下數(shù)據(jù)庫(kù) makemigrations、migrate。之后自定義的權(quán)限會(huì)添加到django自己的權(quán)限表 auth_perssion 里,也可以的admin的權(quán)限設(shè)置里設(shè)置這個(gè)權(quán)限了。
permissions寫哪里
測(cè)試下來(lái)寫在任意一個(gè)Model里都是可以的,應(yīng)該可以寫一個(gè)空的class,或者放到用戶相關(guān)的class里。
驗(yàn)證權(quán)限
首先獲取到用戶對(duì)象(只有這個(gè)對(duì)象才有has_perm()方法),然后調(diào)用has_perm()方法,參數(shù)是[app名字].[權(quán)限名]。如果該用戶有這個(gè)權(quán)限,就返回True,如果用戶沒(méi)有這個(gè)權(quán)限,或者根本沒(méi)這個(gè)權(quán)限名,都是返回False。
user.has_perm('app.view_task')
下面是測(cè)試的驗(yàn)證,期間去admin里修改一下權(quán)限再看看:
D:\>python manage.py shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(id=2)
>>> user.has_perm('crm.view_task')
True
>>> user.has_perm('crm.close_task')
False
>>> user.has_perm('crm.close_task')
False
>>> User.objects.get(username='Adam').has_perm('crm.close_task')
True
>>> user.has_perm('crm.close_task')
False
>>> User.objects.get(username='Adam').has_perm('crm.close_task')
False
>>> User.objects.get(username='Adam').has_perm('crm.close_task')
True
>>> user.has_perm('crm.close_task')
False
>>>
獲取到user之后,權(quán)限都在里面了,這時(shí)候再去修改權(quán)限user里的權(quán)限也不會(huì)變,重新get一下user的話就能獲取到最新的權(quán)限了。這是django提供的方法,所以也支持用戶組。
只要能拿到User對(duì)象和權(quán)限名,就是獲取到一個(gè)True或False的結(jié)果。應(yīng)該也可以寫在if里選擇性的不給前端返回某些重要信息,模板語(yǔ)言里也可以判斷后不顯示這部分內(nèi)容和一些按鈕。
也可以給處理函數(shù)加上裝飾器,下面是django提供的裝飾器:
from django.contrib.auth.decorators import permission_required
@permission_required('crm.view_task')
def my_view(request):
...
裝飾器的文檔在這里:https://docs.djangoproject.com/zh-hans/2.0/topics/auth/default/
講師的博客:https://www.cnblogs.com/alex3714/articles/6661911.html
下面大概都是廢話了,另外博客里的最后一節(jié)是再提供一個(gè)自定義函數(shù)的鉤子,讓用戶可以通過(guò)自定義的函數(shù)實(shí)現(xiàn)動(dòng)態(tài)的業(yè)務(wù)邏輯的權(quán)限控制。
如果是用裝飾器來(lái)控制權(quán)限的話,django提供的裝飾器就是裝飾處理view函數(shù)的,有權(quán)限就可以進(jìn)入這個(gè)處理函數(shù),沒(méi)有權(quán)限就跳轉(zhuǎn)。
更加精細(xì)的權(quán)限是這樣的情況:
控制權(quán)限的維度:
不過(guò)也不是所有的權(quán)限都是可以用裝飾器來(lái)實(shí)現(xiàn)的。有些太精細(xì)的可能要放到業(yè)務(wù)邏輯里
繼續(xù)用django的自定義權(quán)限來(lái)分配權(quán)限。自己搞一個(gè)權(quán)限的數(shù)據(jù)結(jié)構(gòu),記錄更精細(xì)的權(quán)限設(shè)置。使用自己寫的裝飾器:
上面的3、4、5就是我們自己要寫的那個(gè)通用的權(quán)限控制的裝飾器。實(shí)現(xiàn)后給各個(gè)視圖裝飾上就好了。分配權(quán)限就是上面1、2那兩個(gè)步驟進(jìn)行設(shè)置。
可能需要用到下面這些:
request.path # 路徑的屬性,但是不包括get請(qǐng)求的參數(shù)部分
request.get_full_path() # 完整的路徑,報(bào)告get請(qǐng)求的參數(shù)
from urllib.parse import urlparse # 解析url的,get請(qǐng)求的參數(shù)要通過(guò)它來(lái)轉(zhuǎn)碼
from django.urls import resolve # 解析url,分解出各種參數(shù)
view, args, kwargs = resolve(urlparse(next)[2]) # 看下面的說(shuō)明
django的做法是,在跳轉(zhuǎn)到另外一個(gè)頁(yè)面做某些操作但是完成后需要跳轉(zhuǎn)回來(lái)的時(shí)候,會(huì)把當(dāng)前的url作為跳轉(zhuǎn)的get請(qǐng)求的next參數(shù)。當(dāng)完成操作需要跳回之前的頁(yè)面的時(shí)候,讀取這個(gè)next參數(shù)(這里肯定要轉(zhuǎn)碼),然后就知道該跳轉(zhuǎn)到哪里了。
# 如果你有request,就不需要調(diào)用resolve方法了,django已經(jīng)幫我們把結(jié)果封裝到了request.resolver_match里了。
# 下面2個(gè)的結(jié)果是一樣的
print(resolve(request.path))
print(request.resolver_match)
# 里面還有這些常用的屬性
print(request.resolver_match.app_name, request.resolver_match.namespace)
print(request.resolver_match.url_name, request.resolver_match.view_name)
到這里,課上要做一個(gè)自己的類似 django admin 那樣的后臺(tái)管理界面?;揪褪强粗?django admin 的樣子,反推它的實(shí)現(xiàn)方法,然后自己寫一個(gè)一模一樣的(差不多樣子的)。
首先,另外創(chuàng)建一個(gè)app:
python manage.py startapp [app的名字]
然后在app里建立自己的admin配置文件,默認(rèn)系統(tǒng)會(huì)自動(dòng)生成一個(gè)admin.py,所以我們的文件可以叫 [app的名字]_admin.py
。然后就是照著admin.py的樣子進(jìn)行注冊(cè)和配置,另外我們自己的admin的基類也放在這里把。
下面主要把其中的一些坑記錄下來(lái)
用下面的方法進(jìn)入django的python,然后在你的項(xiàng)目里測(cè)試,找到你要的東西。
(django) D:\PycharmProjects\LowCRM>python manage.py shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from crm import models
>>> models.UserProfile
<class 'crm.models.UserProfile'>
>>> models.UserProfile._meta
<Options for UserProfile>
>>> dir(models.UserProfile._meta)
['FORWARD_PROPERTIES', 'REVERSE_PROPERTIES', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribu
te__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__rep
r__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_expire_cache', '_forward_fields_map', '_get_fields', '_get_fields_cache
', '_ordering_clash', '_populate_directed_relation_graph', '_prepare', '_property_names', '_relation_tree', 'abstract', 'add_field', 'add_manager', 'app_c
onfig', 'app_label', 'apps', 'auto_created', 'auto_field', 'base_manager', 'base_manager_name', 'can_migrate', 'concrete_fields', 'concrete_model', 'contr
ibute_to_class', 'db_table', 'db_tablespace', 'default_apps', 'default_manager', 'default_manager_name', 'default_permissions', 'default_related_name', 'f
ields', 'fields_map', 'get_ancestor_link', 'get_base_chain', 'get_field', 'get_fields', 'get_latest_by', 'get_parent_list', 'get_path_from_parent', 'get_p
ath_to_parent', 'has_auto_field', 'index_together', 'indexes', 'installed', 'label', 'label_lower', 'local_concrete_fields', 'local_fields', 'local_manage
rs', 'local_many_to_many', 'managed', 'managers', 'managers_map', 'many_to_many', 'model', 'model_name', 'object_name', 'order_with_respect_to', 'ordering
', 'original_attrs', 'parents', 'permissions', 'pk', 'private_fields', 'proxy', 'proxy_for_model', 'related_fkey_lookups', 'related_objects', 'required_db
_features', 'required_db_vendor', 'select_on_save', 'setup_pk', 'setup_proxy', 'swappable', 'swapped', 'unique_together', 'verbose_name', 'verbose_name_pl
ural', 'verbose_name_raw']
>>> models.UserProfile._meta.app_label
'crm'
>>> models.UserProfile._meta.model_name
'userprofile'
所有的信息都在 _meta
里面。比如上面的app名稱 app_label
和表名 model_name
。測(cè)試出了要的信息的位置,就可以到代碼里放心用了。
下面是steed_admin.py里的內(nèi)容:
from crm import models
enabled_admins = {}
class BaseAdmin(object):
"""steed_admin的基類"""
list_display = []
list_filter = []
class CustomerAdmin(BaseAdmin):
list_display = ['qq', 'name']
class CustomerFollowUpAdmin(BaseAdmin):
list_display = ['customer', 'consultant', 'data']
def register(model_class, admin_class=None):
"""注冊(cè)要使用admin的表"""
app_name = model_class._meta.app_label # 通過(guò)表拿到app的name
model_name = model_class._meta.model_name # 通過(guò)表拿到表名
if app_name not in enabled_admins:
enabled_admins[app_name] = {}
# admin_class.model = model_class # 這樣往類里添加另一個(gè)類是有問(wèn)題的
# enabled_admins[app_name][model_name] = admin_class # 這2句用下面用下面的3句來(lái)實(shí)現(xiàn)
# 這里先要實(shí)例化一個(gè)對(duì)象,然后再往對(duì)象里添加。這樣可以正常返回給前端
# 如果只是類不實(shí)例化,后端打印沒(méi)問(wèn)題,但是前端取不到內(nèi)容
admin_obj = admin_class()
admin_obj.model = model_class
enabled_admins[app_name][model_name] = admin_obj # 現(xiàn)在存的是對(duì)象了,不是類。
register(models.Customer, CustomerAdmin)
register(models.CustomerFollowUp, CustomerFollowUpAdmin)
這里有個(gè)坑,根據(jù)上面的測(cè)試。處理函數(shù)像下面這樣如下:
def index(request):
# print(steed_admin.enabled_admins['crm']['customer'].model)
return render(request, 'steed_admin/index.html', {'table_list': steed_admin.enabled_admins})
前端的模板語(yǔ)言如下,這里要用到自定義函數(shù),所以引用了tags:
{% load tags %}
{% block panel_table %}
{% for app_name, app_tables in table_list.items %}
<table class="table table-hover">
<thead>
<tr>
<th>{{ app_name }}</th>
</tr>
</thead>
<tbody>
{% for table_name, admin in app_tables.items %}
<tr>
<td><a href="{% url 'table_objs' app_name table_name %}">{% render_app_name admin %}</a></td>
<td>添加</td>
<td>編輯</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
{% endblock %}
這里有個(gè)坑,按著上面的測(cè)試,要顯示表的名字,可以像下面這樣寫:
<td><a href="{% url 'table_objs' app_name table_name %}">{{ admin.model._meta.verbose_name_plural }}</a></td>
上面的用法在后端測(cè)試了,沒(méi)問(wèn)題。但是前端用不了,因?yàn)?code>_meta前端不只是下劃線開(kāi)頭。不過(guò)可以把這個(gè)寫到模板語(yǔ)言的自定義函數(shù)里,避免在前端用到_meta
。所以這里補(bǔ)上自定義函數(shù)的文件tags.py的內(nèi)容:
from django import template
register = template.Library()
@register.simple_tag
def render_app_name(admin_class):
return admin_class.model._meta.verbose_name_plural
在顯示選項(xiàng)的時(shí)候,需要拿著字段的名字(字符串),判斷一下是不是有選項(xiàng),如果是選項(xiàng),需要顯示出對(duì)應(yīng)的選項(xiàng)的內(nèi)容。否則顯示的只是選項(xiàng)的數(shù)字。
這里首先要通過(guò)字段名,在表里查到這個(gè)字段的類型,然后判斷一下里面的choices屬性:
(django) D:\PycharmProjects\LowCRM>python manage.py shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from crm import models
>>> models.Customer._meta.get_field('qq')
<django.db.models.fields.CharField: qq>
>>> print(models.Customer._meta.get_field('qq'))
crm.Customer.qq
>>> models.Customer._meta.get_field('qq').choices
[]
>>> models.Customer._meta.get_field('source').choices
((1, '轉(zhuǎn)介紹'), (2, 'QQ群'), (3, '官網(wǎng)'), (4, '51CTO'), (5, '市場(chǎng)推廣'))
>>>
這里要獲取到的是字段的類,然后看看里面的choices屬性。所有的表結(jié)構(gòu)的基類都是Field,并且都用choices屬性:
self.choices = choices or []
如果是空列表,表示定義類型的時(shí)候沒(méi)有給他choices屬性。否則,就不能顯示數(shù)據(jù)庫(kù)的記錄的值,而是要先出這個(gè)值所關(guān)聯(lián)的choices里的內(nèi)容。這時(shí)通過(guò) get_FOO_display
就可以拿到選項(xiàng)里的內(nèi)容了。
即{{ obj.get_level_display}},如果是在后端,這是個(gè)方法,最后要加()來(lái)調(diào)用一下。
在tags.py里的自定義函數(shù)如下:
@register.simple_tag
def build_table_row(obj, admin_class):
"""直接生成表格所有行的html返回
obj: admin_class.model.objects.all() 查詢到的所有數(shù)據(jù)
admin_class: 獲取要顯示哪些列 admin_class.list_display
"""
row_ele = ""
for column in admin_class.list_display:
# 判斷是否有關(guān)聯(lián),顯示關(guān)聯(lián)的內(nèi)容
field_obj = obj._meta.get_field(column)
if field_obj.choices: # field_obj.choices == []
column_data = getattr(obj, 'get_%s_display' % column)()
else:
column_data = getattr(obj, column)
# 判斷遇到的數(shù)據(jù)格式,如果是日期,轉(zhuǎn)化一下
if type(column_data).__name__ == 'datetime':
column_data = column_data.strftime("%Y-%m-%d %H:%M:%S")
row_ele += '<td>%s</td>' % column_data
return mark_safe(row_ele)
上面還有對(duì)時(shí)間日期格式做了轉(zhuǎn)化。
先去django的官網(wǎng)搜索一下:https://docs.djangoproject.com
搜一下分頁(yè)的關(guān)鍵字 “Pagination ” 。就照著例子寫就好了
下面是項(xiàng)目里的代碼:
def display_table_objs(request, app_name, table_name):
# print(app_name, table_name)
admin_class = steed_admin.enabled_admins[app_name][table_name]
# print(admin_class.model._meta.verbose_name_plural)
# 下面是分頁(yè)的實(shí)現(xiàn)
contact_list = admin_class.model.objects.all()
paginator = Paginator(contact_list, 3) # 每頁(yè)顯示3條
page = request.GET.get('page')
contacts = paginator.get_page(page)
return render(request, 'steed_admin/table_objs.html', {'admin_class': admin_class, 'contacts': contacts})
然后是前端的部分:
{% block panel_table %}
<table class="table table-hover">
<thead>
<tr>
{% for column in admin_class.list_display %}
<th>{{ column }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{# {% get_query_sets admin_class as query_sets %}#}
{# {% for obj in query_sets %}#}
{#上面的這個(gè)沒(méi)做分頁(yè),用下面的分頁(yè)來(lái)做#}
{% for obj in contacts %}
<tr>
{% build_table_row obj admin_class %}
</tr>
{% endfor %}
</tbody>
</table>
<!-- 下面是django給的翻頁(yè)的例子 -->
<div class="pagination">
<span class="step-links">
{% if contacts.has_previous %}
<a href="?page=1">? first</a>
<a href="?page={{ contacts.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
</span>
{% if contacts.has_next %}
<a href="?page={{ contacts.next_page_number }}">next</a>
<a href="?page={{ contacts.paginator.num_pages }}">last ?</a>
{% endif %}
</span>
</div>
{% endblock %}
上面django給的并不是我們要的,自己搞個(gè)稍微好點(diǎn)的。下面的例子里做了2種:
<!-- 下面是django給的翻頁(yè)的例子 -->
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="?page=1" aria-label="Previous">
<span aria-hidden="true">?</span>
</a>
</li>
{% if contacts.has_previous %}
<li><a href="?page={{ contacts.previous_page_number }}">上一頁(yè)</a></li>
{% else %}
<li class="disabled"><a href="#">上一頁(yè)</a></li>
{% endif %}
<li class="active"><a href="#">{{ contacts.number }}</a></li>
{% if contacts.has_next %}
<li><a href="?page={{ contacts.next_page_number }}">下一頁(yè)</a></li>
{% else %}
<li class="disabled"><a href="#">下一頁(yè)</a></li>
{% endif %}
<li>
<a href="?page={{ contacts.paginator.num_pages }}" aria-label="Next">
<span aria-hidden="true">?</span>
</a>
</li>
</ul>
<ul class="pagination">
<li>
<a href="?page=1" aria-label="Previous">
<span aria-hidden="true">?</span>
</a>
</li>
{% for loop_counter in contacts.paginator.page_range %}
{% if loop_counter|render_page_ele:contacts %}
{{ loop_counter|render_page_ele:contacts }}
{% endif %}
{% endfor %}
<li>
<a href="?page={{ contacts.paginator.num_pages }}" aria-label="Next">
<span aria-hidden="true">?</span>
</a>
</li>
</ul>
</nav>
上面第二個(gè)分頁(yè),用到了自定義的函數(shù)如下:
@register.filter
def render_page_ele(loop_counter, contacts):
"""前端的頁(yè)碼"""
if abs(contacts.number - loop_counter) <= 2:
if contacts.number == loop_counter:
return mark_safe('<li class="active"><a href="?page={0}">{0}</a></li>'.format(loop_counter))
return mark_safe('<li><a href="?page={0}">{0}</a></li>'.format(loop_counter))
這里因?yàn)樾枰胕f來(lái)判斷自定義的函數(shù)的結(jié)果,所以必須用 @register.filter
。好在只有2個(gè)參數(shù)。課上的方法是不做判斷,這樣不滿足條件的話最后會(huì)返回None,然后前端也會(huì)顯示這個(gè)None。然后簡(jiǎn)單粗暴的在自定義函數(shù)最后 return ''
就是返回一個(gè)空字符串,反正效果一樣的。
這里參考了 django/contrib/admin/filters.py
里面的用法,先導(dǎo)入這2個(gè)模塊:
from django.utils import timezone
from datetime import timedelta
其中timedelta模塊是用來(lái)做日期時(shí)間的加減法的。一般是配合datetime模塊來(lái)用的。這里不用原生的datetime模塊,而是用django的timezone。timezone源碼也是調(diào)用了datetime模塊,只是里面會(huì)讀取settings.py里面有關(guān)時(shí)區(qū)的設(shè)置,輸出的是一個(gè)按照配置文件里的時(shí)區(qū)設(shè)置的時(shí)間。
當(dāng)前時(shí)間
now = timezone.now() # 這個(gè)是UTC時(shí)間可以無(wú)視
# 下面在計(jì)算出來(lái)的就是當(dāng)前時(shí)間
if timezone.is_aware(now):
now = timezone.localtime(now)
今天、明天、下個(gè)月、明年
if isinstance(field, models.DateTimeField):
today = now.replace(hour=0, minute=0, second=0, microsecond=0)
else: # field is a models.DateField
today = now.date()
tomorrow = today + datetime.timedelta(days=1)
if today.month == 12:
next_month = today.replace(year=today.year + 1, month=1, day=1)
else:
next_month = today.replace(month=today.month + 1, day=1)
next_year = today.replace(year=today.year + 1, month=1, day=1)
總結(jié)一下上面的思路,先計(jì)算出當(dāng)前時(shí)間,這里就把時(shí)區(qū)的問(wèn)題解決了。
在當(dāng)前時(shí)間的基礎(chǔ)上,計(jì)算出今天,這里把 DateField 和 DateTimeField 的差別也解決了。
然后再今天today的基礎(chǔ)上,計(jì)算出其他各個(gè)需要的時(shí)間。源碼寫的就是好,值得借鑒。
上面之前都是沒(méi)轉(zhuǎn)碼的,因?yàn)闆](méi)發(fā)現(xiàn)問(wèn)題。正好做到了時(shí)間這里因?yàn)楫?dāng)前時(shí)間里有個(gè)“+”加號(hào),這里不轉(zhuǎn)碼會(huì)被瀏覽器認(rèn)作是空格。最后找到了原因。其實(shí)中文也是可能會(huì)遇到問(wèn)題的。知道會(huì)有這個(gè)問(wèn)題了,每次在自定義函數(shù)里寫標(biāo)簽的時(shí)候都轉(zhuǎn)一下就OK了。自己用的時(shí)候可能還需要str()一下,先轉(zhuǎn)成字符串再處理:
from urllib.parse import quote
format_html('<a href="{}">{}</a>', quote(obj_url), obj)
所以之前的部分代碼還要稍微修改一下
用type創(chuàng)建類的方法,這前在這篇里學(xué)過(guò):https://blog.51cto.com/steed/2048162
現(xiàn)在有了應(yīng)用場(chǎng)景。
首先不考慮動(dòng)態(tài),手動(dòng)的創(chuàng)建類是這樣的:
from django.forms import ModelForm
from crm import models
class CustomerModelForm(ModelForm):
class Meta:
model = models.Customer
fields = '__all__'
然后繼續(xù)看如何用上面的類,就是在Views.py里導(dǎo)入,然后實(shí)例化:
from crm import forms
def get_section(request):
form_obj = forms.CustomerModelForm()
return render(request, 'steed_admin/table_change.html', {'form_obj': form_obj})
現(xiàn)在的需求就是要為crm.models里的每一個(gè)類創(chuàng)建創(chuàng)建一個(gè)ModelForm類。其實(shí)不是為每個(gè)類創(chuàng)建ModelForm,而是在forms.py里只提供一個(gè)動(dòng)態(tài)創(chuàng)建類的方法,然后要用的時(shí)候調(diào)用這個(gè)方法,生成一個(gè)類,然后直接實(shí)例化使用。下面就是這個(gè)動(dòng)態(tài)的創(chuàng)建類的方法:
from django.forms import ModelForm
from crm import models
def create_model_form(request, admin_class):
"""動(dòng)態(tài)生成ModelForm"""
class Meta:
model = admin_class.model
fields = '__all__'
# 這里先寫個(gè)字典,下面再引用字典。之后這個(gè)類要添加什么方法都在這個(gè)字典里寫
members = {'Meta': Meta}
# 左邊是類名
# 右邊的參數(shù):類的類型名字,繼承哪些基類,類的所有成員
model_form_class = type('DynamicModelForm', (ModelForm,), members)
return model_form_class
上面只是提供了動(dòng)態(tài)創(chuàng)建ModelForm類的方法。然后還是去Views.py里使用,這次沒(méi)有線程的類可以用了,而是要用的時(shí)候就直接把類創(chuàng)建好,然后實(shí)例化:
from steed_admin import steed_admin
from steed_admin.forms import create_model_form
def change_table_obj(request, app_name, table_name, obj_id):
admin_class = steed_admin.enabled_admins[app_name][table_name] # 拿到要?jiǎng)?chuàng)建的crm.models里的類
model_form_class = create_model_form(request, admin_class) # 創(chuàng)建類
obj = admin_class.model.objects.get(id=obj_id) # 通過(guò)id,查到具體的一條記錄
form_obj = model_form_class(instance=obj) # 實(shí)例化,然后傳入默認(rèn)值
return render(request, 'steed_admin/table_change.html', {'admin_class': admin_class, 'form_obj': form_obj})
方法里的第一行是拿到了一個(gè)admin_class,這個(gè)在項(xiàng)目里其他地方已經(jīng)定義好了。admin_class.model這個(gè)屬性就是crm.models里某一個(gè)對(duì)應(yīng)的class的類。在動(dòng)態(tài)方法里,這個(gè)在Meta的model屬性里要賦值給model。
所有參數(shù)都準(zhǔn)備好了,就創(chuàng)建類。
然后實(shí)例化前,先通過(guò)id把對(duì)應(yīng)的記錄查到。
現(xiàn)在實(shí)例化,并且把查到的記錄傳給instance參數(shù)。
最后返回給前端,前端可以先簡(jiǎn)單的用 {{ form_obj.as_p }}
看到生成的form表單以及里面填入的默認(rèn)值。
接著上面的內(nèi)容,現(xiàn)在要為所有字段加上widgets屬性。里面加上class屬性,在前端可以顯示出樣式。問(wèn)題是動(dòng)態(tài)的怎么做。這里要用到 __new__
方法。new方法是在構(gòu)造函數(shù)執(zhí)行之前執(zhí)行的方法,可以用來(lái)定制我們的類。都是以前講過(guò)的內(nèi)容,但是不好理解,還是直接上結(jié)果吧。
添加了new方法的動(dòng)態(tài)創(chuàng)建ModelForm的函數(shù)如下:
def create_model_form(request, admin_class):
"""動(dòng)態(tài)生成ModelForm"""
class Meta:
model = admin_class.model
fields = '__all__'
def __new__(cls, *args, **kwargs):
# cls.base_fields['qq'].widget.attrs['class'] = 'form-control' # 指定價(jià)某一個(gè)是這么來(lái)加
# 下面是要?jiǎng)討B(tài)的把所有字段都加上
# print(cls.base_fields) # 先看看,這是一個(gè)序字典OrderedDict
for filed_name, field_obj in cls.base_fields.items():
field_obj.widget.attrs['class'] = 'form-control'
return ModelForm.__new__(cls)
# 這里先寫個(gè)字典,下面再引用字典。之后這個(gè)類要添加什么方法都在這個(gè)字典里寫
# 這里的成員會(huì)被繼承的屬性覆蓋掉
members = {'Meta': Meta, '__new__': __new__}
# 左邊是類名
# 右邊的參數(shù):類的類型名字,繼承哪些基類,類的所有成員
model_form_class = type('DynamicModelForm', (ModelForm,), members)
return model_form_class
下面放上把所有內(nèi)容都寫死的寫法:
from django.forms import ModelForm
from django.forms import widgets as my_widgets
from crm import models
class CustomerModelForm(ModelForm):
class Meta:
model = models.Customer
fields = '__all__'
widgets = {‘qq’: my_widgets.CharField(attrs: {'class': 'form-control'})}
現(xiàn)在還是不明白具體是如何把widgets放到Meta外面,在new里做的。而且new里的 cls 和 cls.base_fields 是什么。先照著這么用了再說(shuō)吧。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。