亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python自動化開發學習-Django Admin

發布時間:2020-08-02 15:04:17 來源:網絡 閱讀:3854 作者:騎士救兵 欄目:編程語言

django amdin是django提供的一個后臺管理頁面,該管理頁面提供完善的html和css,使得你在通過Model創建完數據庫表之后,就可以對數據進行增刪改查。

準備工作

創建一個項目,或者是用已有的項目
使用下面的命令創建生成數據庫,這里雖然還沒有創建任何的表結構,但是django本身是有一些庫要創建的

python manage.py migrate

這個命令一般是搭著 python manage.py makemigrations 之后用的,不過這里我們自己還一個表都還沒創建呢。
啟動 django 服務,然后默認用這個地址 http://127.0.0.1:8000/admin 就可以打開登陸界面了。

創建超級管理員

使用下面的命令,創建超級管理員賬戶:

python manage.py createsuperuser

根據提示,輸入用戶名和密碼后,創建成功后,就可以去Web界面登錄了。

本地化配置

去settings.py文件里修改下面2項,主要是改了 LANGUAGE_CODE ,這樣后臺管理能顯示中文了。時區的設置這里不影響,不過順便改一下吧。

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-hans'

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'

現在在去看看admin的管理頁面,已經是中文的頁面的。

創建表結構

先創建2張簡單的表,有一個簡單的外鍵關聯:

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)

創建完表后要執行一下下面的命令,更新到數據庫:

python manage.py makemigrations
python manage.py migrate

要在admin的管理界面里看到這些表,必須要在admin.py文件里注冊一下:

from app01 import models

admin.site.register(models.UserInfo)
admin.site.register(models.Dept)

現在已經,可以在管理頁面里看到我們的表的。但是英文看的不舒服。

顯示自定制的表名和字段名

修改一下之前的表結構,添加verbose_name參數:

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 = "部門表"

字段里面,部分是有位置參數的,所以就省略了 verbose_name= 這些內容。
表名有2個變量,verbose_name就是自定制表名了,但是顯示的時候會在表名后面加個“s”,這是英文的做法。verbose_name_plural這個參數,就是替換掉英文加s的顯示的內容了,這里我統一在后面加了個“表”。

正確顯示記錄

現在添加好記錄的后,看到的是 UserInfo object (1)Dept object (1) 這樣。這里其實和print打印出來的結果一一樣的,直接顯示了對象。為對象添加 __str__ 方法后,就能正常顯示了。最后修改后的表結構如下:

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的后臺管理頁面創建用戶,直接點擊默認的“認證和授權”下面的“用戶”表,就可以創建記錄。
這里要注意,輸入了新用戶的用戶名、密碼和確認密碼后,就完成了用戶的創建,但是這個用戶并不能登錄。隨后會有一個修改用戶的界面,都是中文就不細說了。就是這里要勾選一下 “職員狀態(指明用戶是否可以登錄到這個管理站點。)” 這個復選框,該賬號才可以登錄。
只勾選了上面的復選框,可以實現登錄,但是近來是什么也看不到的。在往下還有 “用戶權限” ,默認所有的賬戶都是一張表的權限都沒有的,包括超級管理員。但是超級管理員的賬戶勾選了 “超級用戶狀態(指明該用戶缺省擁有所有權限。)” 所以無視這個設置。
普通用戶就需要在這里添加權限了。這里有包括django默認的表以及我們自己創建的表。權限比較粗,基本上就是控制這個用戶可以操作那些表,我沒找到只讀權限。
這里只要給賬戶 “auth|用戶|Can change user” 這一個權限,它就可以為所欲為了,包括把自己變成超管或者把別的超管去掉。

admin.py的設置

這里除了注冊我們自己的表以外, 還可以通過繼承并重構 admin.ModelAdmin 里面的部分屬性,獲得更好的管理效果。

設定默認顯示

點開員工信息表,能看到現在只顯示一列。默認顯示的屬性是 list_display = ('__str__',) ,所以原本顯示的是對象,這里我們已經在類里重構了 __str__ 方法,現在顯示的name了。但是只顯示name還不夠,現在要把所有的字段都顯示出來:

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)

這里就是創建一個類,繼承admin.ModelAdmin這個類,然后用自己的 list_display 屬性覆蓋掉原來的。然后注冊的函數后面再把這個自己的類作為參數加上,就可以按照我們的設置顯示字段和內容了。

開啟搜索功能

繼續在類里添加下面的屬性:

    search_fields = ('name', 'age')

添加了搜索的屬性以后,就會出現一個搜索框。直接搜內容按搜索吧。這里沒把部門的字段加進去所以不會按部門搜。另外這里也沒加性別的字段,如果有性別的字段,那么也只能搜索數據庫里的值,也就是數字1和2。

開啟過濾器

繼續在類里添加下面的屬性:

    list_filter = ('gender', 'dept')

添加了過濾器后,右邊就會出現一個過濾器的部件,也可以幫助我們篩選記錄。選項特別適合用過濾器來篩選。

開啟分頁

就是限制每頁顯示的記錄數
繼續在類里添加下面的屬性:

    list_per_page = 3

防止記錄太多,記得設置分頁。

修改外鍵字的的管理方法

繼續在類里添加下面的屬性,這里只能把外鍵加進去:

    raw_id_fields = ('dept',)

原本外鍵的位置是一個下拉的select列表,現在變成了input框,里面是對應的數據庫的值(即id)。不過后面有個搜索按鈕,可以點開來選擇對象的選項。單選并且選項多的時候,可以提升使用的體驗。
如果是多對多的外鍵,需要用這個:

    filter_horizontal = ()  # 這里并沒有多對多的字段,就空著吧

這個的效果可以參考用戶權限分配里的用戶組合用戶權限的操作,多選的情況這么設置可以有更好的體驗。
這里的兩個方法,就是提供多選和單選操作的方便性。

在顯示列表中直接修改

繼續在類里添加下面的屬性:

    list_display = ('name', 'age', 'gender', 'dept')
    list_editable = ('age', 'gender', 'dept')

這里要搭配list_display一起用,就是顯示出來的列表中,哪些字段是可以直接在列表中修改的,這種就不用一個一個點進去改了。不過list_display里的第一個元素是不能修改的,否則會報錯。

自定義action

默認每張表都是一個Delete的action,另外這個action是可以自己定制的,現在在類里這么加:

    actions = ('test_action',)

    def test_action(self):
        pass

此時再打開表,查看Action的下拉列表就能看到自定制的方法的名稱了。至少選中1條記錄,然后點go,Web上會報這么個錯誤 test_action() takes 1 positional argument but 3 were given ,需要3個參數,但是只提供了一個。所以把上面的內容這么改一下看看:

    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>]>

參數分別是請求和QuerySet。
另外如果方法是公用的,也可以把方法寫到類外面去。并且是支持自定義顯示的名字的,如果沒有設置,默認顯示的就是方法名。定義actions可以使用字符串的函數名,也可以直接引用函數:

# 函數可以寫在類的外面,作為一個公共的方法
def test_action(self, request, queryset):
    print(self)
    print(request)
    print(queryset)

test_action.short_description = "中文顯示自定義Actions"

# 下面是是在類里定義action,可以用函數名,或者直接引用函數
    actions = (test_action,)

使用admin的認證來做網站的認證

這部分內容講的不是很系統,下面是官網的文檔連接:
https://docs.djangoproject.com/zh-hans/2.0/topics/auth/customizing/#customizing-authentication-in-django

django有一張自己的認證表 auth_user ,直接用這張表記錄用戶的基本認證信息。更加詳細的用戶信息,就做一個一對一的外鍵,也就是下面的UserProfile表,來記錄自己的更加詳細的用戶信息。
這里另起爐灶,重新建2張表,和上面的講的每關系了。
下面是一種實現方法,并沒有細講,先貼上表結構:

ffrom django.contrib.auth.models import User

class UserProfile(models.Model):
    """賬號"""
    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

新創建的表結構,記得去執行下面的兩個步驟:

  • 執行2條命令,同步到數據庫
  • admin.py 文件里注冊這個新建的表

這里創建了一張自己的表,就是存放用戶信息的,比如這里用戶信息就一個字段 name 。并且和django的User表做了一對一的關聯。也就是用戶的認證信息和用戶其他信息拆開來,認證信息直接使用django的User表。另外這里還有個角色表,留著做賬號的權限管理的。貌似沒什么用,但是作為一個結構就一起放上來了。

還可以做更加深度的自定義,文檔后面還有很多內容。再深入下去就是要用使用自己的表(比如:crm_myuser表)替代django提供的auth_user這張表了,需要注意下面幾點:

  • 去setting.py里設置一下自定義的表,加上這么一行: AUTH_USER_MODEL = 'crm.MyUser'
  • 去admin.py里注冊你自己的這個表

登錄驗證

下面用的都是django幫我么封裝好的方法來實現賬號的登錄和驗證等操作:

from django.contrib.auth import authenticate  # 驗證用戶名和密碼的方法
from django.contrib.auth import login  # 登錄,上面只是驗證,這個才是登錄的動作,會幫我么創建session

下面是例子:

from django.contrib.auth import authenticate, login

def acc_login(request):
    """用戶登錄驗證"""
    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)  # 如果認證通過,返回的是用戶對象,否則是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/')

登出后跳轉到登錄頁面,django默認的登錄也的目錄是上面的 '/accounts/login/‘ 。

登錄驗證裝飾器

這個直接用就好了:

from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    pass

只要沒有認證就會跳轉到登錄頁面,默認的登錄頁面的url是 '/accounts/login/‘ ,也可以通過設置改成別的。在settings.py里加一個參數,指定登錄頁面的url:

LPGIN_URL = '/login/'

上面是全局的改變登錄頁面url方法,裝飾器本身也有參數,可以指定url:

@login_required(login_url='/accounts/login/')

上面在跳轉到登錄頁面的同時,也會保存當前請求頁面的url,默認是放在next參數里的。這需要我們寫登錄處理函數的時候,在認證成功后,能夠用 request.GET.get('next') 獲取一下這個參數,如果有就跳轉到參數指定的url。這個參數是一個get請求的參數,名字默認是next,也可以指定成別的名字:

@login_required(redirect_field_name='my_redirect_field')

登錄本身仍然是一個POST請求,但是依然可以帶GET請求的參數,通過GET請求的參數獲取的方法是能夠通過解析url獲取到參數的。

自定義權限

默認每張表都有 add、change、delete 這3個權限。也可以添加自定義的權限,隨便找個Model,一般就是用戶信息的Model。在Meta里定義Permissions,前面是權限的名字,后面的權限的描述:

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"),
        )

設置完Model后,需要同步一下數據庫 makemigrations、migrate。之后自定義的權限會添加到django自己的權限表 auth_perssion 里,也可以的admin的權限設置里設置這個權限了。
permissions寫哪里
測試下來寫在任意一個Model里都是可以的,應該可以寫一個空的class,或者放到用戶相關的class里。

驗證權限
首先獲取到用戶對象(只有這個對象才有has_perm()方法),然后調用has_perm()方法,參數是[app名字].[權限名]。如果該用戶有這個權限,就返回True,如果用戶沒有這個權限,或者根本沒這個權限名,都是返回False。

user.has_perm('app.view_task')

下面是測試的驗證,期間去admin里修改一下權限再看看:

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之后,權限都在里面了,這時候再去修改權限user里的權限也不會變,重新get一下user的話就能獲取到最新的權限了。這是django提供的方法,所以也支持用戶組。
只要能拿到User對象和權限名,就是獲取到一個True或False的結果。應該也可以寫在if里選擇性的不給前端返回某些重要信息,模板語言里也可以判斷后不顯示這部分內容和一些按鈕。
也可以給處理函數加上裝飾器,下面是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
下面大概都是廢話了,另外博客里的最后一節是再提供一個自定義函數的鉤子,讓用戶可以通過自定義的函數實現動態的業務邏輯的權限控制。

如果是用裝飾器來控制權限的話,django提供的裝飾器就是裝飾處理view函數的,有權限就可以進入這個處理函數,沒有權限就跳轉。
更加精細的權限是這樣的情況:

  • 同一個處理函數,可能對應多個頁面。比如用戶信息,只能修改自己的信息,不如進入別人的信息修改頁面
  • 同一個處理函數,可能對應多個方法。比如只能發GET請求,不能發POST請求

控制權限的維度:

  • 限制url的訪問
  • 限制url里的參數(貌似沒有這個,或者不需要控制這個?)
  • 限制請求的方法
  • 限制請求使用的參數:必須包含某系參數,但是不關心值是多少,把參數記錄在一個列表里
  • 限制請求使用的參數和值:必須包含特定的參數,并且值也必須匹配,把鍵值對記錄在一個字典里

不過也不是所有的權限都是可以用裝飾器來實現的。有些太精細的可能要放到業務邏輯里
繼續用django的自定義權限來分配權限。自己搞一個權限的數據結構,記錄更精細的權限設置。使用自己寫的裝飾器:

  1. 按上面自定義權限里說的(寫在Meta里的),先在django里自定義一些權限,然后可以把這些權限分配給用戶或組
  2. 自己的精細權限字典里,你的key就要是上面django里面定義的權限的名字(或者能對應上的),value就是上面說的控制權限的4個維度
  3. 首先認證用戶是否登錄
  4. 拿到用戶請求的url,解析到各個參數,去自定義的權限字典里匹配,找到一個匹配的名字(Key)
  5. 把權限字典里匹配到的名字轉成django里的權限名,以這個權限名作為參數,調用ser.has_perm(),看看是否給用戶分配了這個權限

上面的3、4、5就是我們自己要寫的那個通用的權限控制的裝飾器。實現后給各個視圖裝飾上就好了。分配權限就是上面1、2那兩個步驟進行設置。
可能需要用到下面這些:

request.path  # 路徑的屬性,但是不包括get請求的參數部分
request.get_full_path()  # 完整的路徑,報告get請求的參數
from urllib.parse import urlparse  # 解析url的,get請求的參數要通過它來轉碼
from django.urls import resolve  # 解析url,分解出各種參數
view, args, kwargs = resolve(urlparse(next)[2])  # 看下面的說明

django的做法是,在跳轉到另外一個頁面做某些操作但是完成后需要跳轉回來的時候,會把當前的url作為跳轉的get請求的next參數。當完成操作需要跳回之前的頁面的時候,讀取這個next參數(這里肯定要轉碼),然后就知道該跳轉到哪里了。

# 如果你有request,就不需要調用resolve方法了,django已經幫我們把結果封裝到了request.resolver_match里了。
# 下面2個的結果是一樣的
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)

king_admin 開發

到這里,課上要做一個自己的類似 django admin 那樣的后臺管理界面。基本就是看著 django admin 的樣子,反推它的實現方法,然后自己寫一個一模一樣的(差不多樣子的)。
首先,另外創建一個app:

python manage.py startapp [app的名字]

然后在app里建立自己的admin配置文件,默認系統會自動生成一個admin.py,所以我們的文件可以叫 [app的名字]_admin.py 。然后就是照著admin.py的樣子進行注冊和配置,另外我們自己的admin的基類也放在這里把。
下面主要把其中的一些坑記錄下來

通過表名獲取app的name

用下面的方法進入django的python,然后在你的項目里測試,找到你要的東西。

(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 。測試出了要的信息的位置,就可以到代碼里放心用了。
下面是steed_admin.py里的內容:

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):
    """注冊要使用admin的表"""
    app_name = model_class._meta.app_label  # 通過表拿到app的name
    model_name = model_class._meta.model_name  # 通過表拿到表名
    if app_name not in enabled_admins:
        enabled_admins[app_name] = {}
    # admin_class.model = model_class  # 這樣往類里添加另一個類是有問題的
    # enabled_admins[app_name][model_name] = admin_class  # 這2句用下面用下面的3句來實現
    # 這里先要實例化一個對象,然后再往對象里添加。這樣可以正常返回給前端
    # 如果只是類不實例化,后端打印沒問題,但是前端取不到內容
    admin_obj = admin_class()
    admin_obj.model = model_class
    enabled_admins[app_name][model_name] = admin_obj  # 現在存的是對象了,不是類。

register(models.Customer, CustomerAdmin)
register(models.CustomerFollowUp, CustomerFollowUpAdmin)

在模板語言里顯示上面的app的name

這里有個坑,根據上面的測試。處理函數像下面這樣如下:

def index(request):
    # print(steed_admin.enabled_admins['crm']['customer'].model)
    return render(request, 'steed_admin/index.html', {'table_list': steed_admin.enabled_admins})

前端的模板語言如下,這里要用到自定義函數,所以引用了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 %}

這里有個坑,按著上面的測試,要顯示表的名字,可以像下面這樣寫:

        <td><a href="{% url 'table_objs' app_name table_name %}">{{ admin.model._meta.verbose_name_plural }}</a></td>

上面的用法在后端測試了,沒問題。但是前端用不了,因為_meta前端不只是下劃線開頭。不過可以把這個寫到模板語言的自定義函數里,避免在前端用到_meta。所以這里補上自定義函數的文件tags.py的內容:

from django import template

register = template.Library()

@register.simple_tag
def render_app_name(admin_class):
    return admin_class.model._meta.verbose_name_plural

把選項的內容顯示出來

在顯示選項的時候,需要拿著字段的名字(字符串),判斷一下是不是有選項,如果是選項,需要顯示出對應的選項的內容。否則顯示的只是選項的數字。
這里首先要通過字段名,在表里查到這個字段的類型,然后判斷一下里面的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, '轉介紹'), (2, 'QQ群'), (3, '官網'), (4, '51CTO'), (5, '市場推廣'))
>>>

這里要獲取到的是字段的類,然后看看里面的choices屬性。所有的表結構的基類都是Field,并且都用choices屬性:

        self.choices = choices or []

如果是空列表,表示定義類型的時候沒有給他choices屬性。否則,就不能顯示數據庫的記錄的值,而是要先出這個值所關聯的choices里的內容。這時通過 get_FOO_display 就可以拿到選項里的內容了。
{{ obj.get_level_display}},如果是在后端,這是個方法,最后要加()來調用一下。
在tags.py里的自定義函數如下:

@register.simple_tag
def build_table_row(obj, admin_class):
    """直接生成表格所有行的html返回
    obj: admin_class.model.objects.all() 查詢到的所有數據
    admin_class: 獲取要顯示哪些列 admin_class.list_display
    """
    row_ele = ""
    for column in admin_class.list_display:
        # 判斷是否有關聯,顯示關聯的內容
        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)
        # 判斷遇到的數據格式,如果是日期,轉化一下
        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)

上面還有對時間日期格式做了轉化。

Django的分頁

先去django的官網搜索一下:https://docs.djangoproject.com
搜一下分頁的關鍵字 “Pagination ” 。就照著例子寫就好了
下面是項目里的代碼:

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)
    # 下面是分頁的實現
    contact_list = admin_class.model.objects.all()
    paginator = Paginator(contact_list,  3)  # 每頁顯示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 %}#}
{#上面的這個沒做分頁,用下面的分頁來做#}
    {% for obj in contacts %}
        <tr>
        {% build_table_row obj admin_class %}
        </tr>
    {% endfor %}
    </tbody>
    </table>
    <!-- 下面是django給的翻頁的例子 -->
    <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給的并不是我們要的,自己搞個稍微好點的。下面的例子里做了2種:

    <!-- 下面是django給的翻頁的例子 -->
    <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 }}">上一頁</a></li>
            {% else %}
                <li class="disabled"><a href="#">上一頁</a></li>
            {% endif %}
            <li class="active"><a href="#">{{ contacts.number }}</a></li>
            {% if contacts.has_next %}
                <li><a href="?page={{ contacts.next_page_number }}">下一頁</a></li>
            {% else %}
                <li class="disabled"><a href="#">下一頁</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>

上面第二個分頁,用到了自定義的函數如下:

@register.filter
def render_page_ele(loop_counter, contacts):
    """前端的頁碼"""
    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))

這里因為需要用if來判斷自定義的函數的結果,所以必須用 @register.filter 。好在只有2個參數。課上的方法是不做判斷,這樣不滿足條件的話最后會返回None,然后前端也會顯示這個None。然后簡單粗暴的在自定義函數最后 return '' 就是返回一個空字符串,反正效果一樣的。

在Django中使用datetime模塊

這里參考了 django/contrib/admin/filters.py 里面的用法,先導入這2個模塊:

from django.utils import timezone
from datetime import timedelta

其中timedelta模塊是用來做日期時間的加減法的。一般是配合datetime模塊來用的。這里不用原生的datetime模塊,而是用django的timezone。timezone源碼也是調用了datetime模塊,只是里面會讀取settings.py里面有關時區的設置,輸出的是一個按照配置文件里的時區設置的時間。
當前時間

now = timezone.now()  # 這個是UTC時間可以無視
# 下面在計算出來的就是當前時間
if timezone.is_aware(now):
    now = timezone.localtime(now)

今天、明天、下個月、明年

        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)

總結一下上面的思路,先計算出當前時間,這里就把時區的問題解決了。
在當前時間的基礎上,計算出今天,這里把 DateField 和 DateTimeField 的差別也解決了。
然后再今天today的基礎上,計算出其他各個需要的時間。源碼寫的就是好,值得借鑒。

輸出標簽的href屬性的時候要轉碼

上面之前都是沒轉碼的,因為沒發現問題。正好做到了時間這里因為當前時間里有個“+”加號,這里不轉碼會被瀏覽器認作是空格。最后找到了原因。其實中文也是可能會遇到問題的。知道會有這個問題了,每次在自定義函數里寫標簽的時候都轉一下就OK了。自己用的時候可能還需要str()一下,先轉成字符串再處理:

from urllib.parse import quote

format_html('<a href="{}">{}</a>', quote(obj_url), obj)

所以之前的部分代碼還要稍微修改一下

動態的創建類(type)

用type創建類的方法,這前在這篇里學過:https://blog.51cto.com/steed/2048162
現在有了應用場景。
首先不考慮動態,手動的創建類是這樣的:

from django.forms import ModelForm
from crm import models

class CustomerModelForm(ModelForm):
    class Meta:
        model = models.Customer
        fields = '__all__'

然后繼續看如何用上面的類,就是在Views.py里導入,然后實例化:

from crm import forms

def get_section(request):
    form_obj = forms.CustomerModelForm()
    return render(request, 'steed_admin/table_change.html', {'form_obj': form_obj})

現在的需求就是要為crm.models里的每一個類創建創建一個ModelForm類。其實不是為每個類創建ModelForm,而是在forms.py里只提供一個動態創建類的方法,然后要用的時候調用這個方法,生成一個類,然后直接實例化使用。下面就是這個動態的創建類的方法:

from django.forms import ModelForm
from crm import models

def create_model_form(request, admin_class):
    """動態生成ModelForm"""
    class Meta:
        model = admin_class.model
        fields = '__all__'

    # 這里先寫個字典,下面再引用字典。之后這個類要添加什么方法都在這個字典里寫
    members = {'Meta': Meta}
    # 左邊是類名
    # 右邊的參數:類的類型名字,繼承哪些基類,類的所有成員
    model_form_class = type('DynamicModelForm', (ModelForm,), members)
    return model_form_class

上面只是提供了動態創建ModelForm類的方法。然后還是去Views.py里使用,這次沒有線程的類可以用了,而是要用的時候就直接把類創建好,然后實例化:

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]  # 拿到要創建的crm.models里的類
    model_form_class = create_model_form(request, admin_class)  # 創建類
    obj = admin_class.model.objects.get(id=obj_id)  # 通過id,查到具體的一條記錄
    form_obj = model_form_class(instance=obj)  # 實例化,然后傳入默認值
    return render(request, 'steed_admin/table_change.html', {'admin_class': admin_class, 'form_obj': form_obj})

方法里的第一行是拿到了一個admin_class,這個在項目里其他地方已經定義好了。admin_class.model這個屬性就是crm.models里某一個對應的class的類。在動態方法里,這個在Meta的model屬性里要賦值給model。
所有參數都準備好了,就創建類。
然后實例化前,先通過id把對應的記錄查到。
現在實例化,并且把查到的記錄傳給instance參數。
最后返回給前端,前端可以先簡單的用 {{ form_obj.as_p }} 看到生成的form表單以及里面填入的默認值。

給所有的字段加上樣式(new)

接著上面的內容,現在要為所有字段加上widgets屬性。里面加上class屬性,在前端可以顯示出樣式。問題是動態的怎么做。這里要用到 __new__ 方法。new方法是在構造函數執行之前執行的方法,可以用來定制我們的類。都是以前講過的內容,但是不好理解,還是直接上結果吧。
添加了new方法的動態創建ModelForm的函數如下:

def create_model_form(request, admin_class):
    """動態生成ModelForm"""
    class Meta:
        model = admin_class.model
        fields = '__all__'

    def __new__(cls, *args, **kwargs):
        # cls.base_fields['qq'].widget.attrs['class'] = 'form-control'  # 指定價某一個是這么來加
        # 下面是要動態的把所有字段都加上
        # print(cls.base_fields)  # 先看看,這是一個序字典OrderedDict
        for filed_name, field_obj in cls.base_fields.items():
            field_obj.widget.attrs['class'] = 'form-control'
        return ModelForm.__new__(cls)

    # 這里先寫個字典,下面再引用字典。之后這個類要添加什么方法都在這個字典里寫
    # 這里的成員會被繼承的屬性覆蓋掉
    members = {'Meta': Meta, '__new__': __new__}
    # 左邊是類名
    # 右邊的參數:類的類型名字,繼承哪些基類,類的所有成員
    model_form_class = type('DynamicModelForm', (ModelForm,), members)
    return model_form_class

下面放上把所有內容都寫死的寫法:

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'})}

現在還是不明白具體是如何把widgets放到Meta外面,在new里做的。而且new里的 cls 和 cls.base_fields 是什么。先照著這么用了再說吧。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

滦平县| 昌邑市| 鄄城县| 红桥区| 邹平县| 太湖县| 海城市| 平定县| 察隅县| 松江区| 西城区| 西林县| 漳州市| 庆城县| 民权县| 铜鼓县| 离岛区| 雷州市| 华坪县| 深水埗区| 西昌市| 杭锦后旗| 习水县| 且末县| 阿图什市| 新巴尔虎左旗| 新丰县| 刚察县| 中山市| 砚山县| 金沙县| 祥云县| 宝丰县| 云和县| 大连市| 沙雅县| 渭南市| 雷州市| 东海县| 黄大仙区| 张家港市|