目錄:
- 1.1 重寫Django admin項目各文件作用 #
- 1.2 重寫Django admin用戶認證
- 1.3 將要顯示的表注冊到我們自己的kind_admin.py中
- 1.4 項目首頁:顯示注冊的app名、表名(kind_admin_index.html 頁面1)
- 1.5 展示表中各條數據(display_table_obj.html 頁面2)
- 1.6 添加數據(table_obj_add.html 頁面3)
- 1.7 修改數據(table_obj_change.html 頁面4)
- 1.8 刪除數據(table_obj_delete.html 頁面5)
- 1.9 用戶登錄(login.html 頁面6)
- 1.10 添加用戶(rewrite_add_user_page.html)
- 1.11 用戶重置密碼
- 1.12 實現用戶對各表增刪改查的權限管理
1.1 重寫Django admin項目各文件作用 返回頂部

C: └─DjangoAdminUser_0922 │ db.sqlite3 #數據庫 │ manage.py #Django管理 │ ├─DjangoAdminUser_0922 │ │ settings.py #指定使用哪個表代替Django Admin的User表,指定注銷后跳轉頁面 │ │ urls.py #項目總url路徑 │ ├─crm │ │ admin.py #原生Django Admin管理配置 │ │ models.py #使用自定義的UserProfile表代替Django Admin原生的User表 │ ├─kind_admin │ │ forms.py #動態生成ModelForm類,用來對數據做驗證 │ │ register_admin.py #定義register函數,注冊"xxxAdmin" │ │ kind_admin.py #重寫Django Admin │ │ urls.py #kind_admin這個APP中自己的url路徑 │ │ utils.py #更具過濾,搜索,排序條件返回執行內容 │ │ views.py #kind_admin增刪改查,登錄注銷的管理函數 │ │ │ ├─permissions │ │ │ permission.py #判斷是否有權限 │ │ │ permission_list.py #定義權限字典 │ │ │ ├─templatetags │ │ │ tags.py #展示表中數據,分頁,在前端生成搜索,過濾框 │ ├─static │ ├─css │ │ bootstrap-theme.css #對 bootstrap.css 基礎樣式的重定義(可以沒有) │ │ bootstrap.css #bootstrap全局css樣式 │ │ dashboard.css #下載bootstrap后台模板時的css樣式 │ │ │ ├─fonts #bootstrap圖標樣式,必須有但不必在文件中引入 │ │ glyphicons-halflings-regular.eot │ │ glyphicons-halflings-regular.svg │ │ glyphicons-halflings-regular.ttf │ │ glyphicons-halflings-regular.woff │ │ glyphicons-halflings-regular.woff2 │ │ │ └─js │ bootstrap.js #bootstrap的js文件 │ jquery-1.12.4.js #jquery的js文件,引入時必須顯示引入jQuery的js文件 │ └─templates └─kind_admin base.html #僅引入必要的css和js文件路徑 kind_admin_index.html #展示APP名和表名 display_table_obj.html #展示表中具體信息 table_obj_add.html #增加 table_obj_change.html #修改 table_obj_delete.html #刪除 login.html #kind_admin登錄界面 password_reset.html #修改Django Admin用戶表的密碼 rewrite_add_user_page.html #添加Django Admin用戶時密碼明文,用獨立頁面解決

from django.conf.urls import url,include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^kind_admin/', include('kind_admin.urls')), ]

from django.conf.urls import url from kind_admin import views urlpatterns = [ url(r'^$', views.kind_admin_index,name='kind_admin_index'), url(r'^(\w+)/(\w+)/$', views.display_table_obj,name='display_table_obj'), url(r'^(\w+)/(\w+)/add/$', views.table_obj_add,name='table_obj_add'), url(r'^(\w+)/(\w+)/(\d+)/change/$', views.table_obj_change,name='table_obj_change'), url(r'^(\w+)/(\w+)/(\d+)/change/password/$', views.password_reset,name='password_reset'), #點擊某條進行修改 url(r'^(\w+)/(\w+)/(\d+)/delete/$', views.table_obj_delete,name='table_obj_delete'), url(r'^login/$', views.acc_login), url(r'^lougout/$', views.acc_logout, name='acc_logout'), ]
1.2 重寫Django admin用戶認證 返回頂部
1、說明
1. Django Admin中通過python manage.py createsuperuser創建的用戶默認存儲在自己的User表中
2. 很多時候我們想要借助這個用戶認證,但是Django中自帶的User表我們是無法添加其他字段的
3. 所以為了更方便的使用Django admin的認證功能,可以使用我們自己的UserProfile表代替Django Admin的User表
2、重寫步驟
1、models.py中定義表結構
2、admin.py中注冊UserProfile表,並定制UserProfileAdmin
注:如果只定義表結構而沒有定制UserProfileAdmin,在頁面創建的用戶密碼為明文,無法登陸admin后台
3、一定要記得到settings.py指定使用我們自定義的UserProfile表做登錄驗證
4、執行創建表命令
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
5、此時就可以登陸admin后台創建用戶,修改面等操作了

from django.db import models from django.utils.translation import ugettext_lazy as _ #國際化 from django.utils.safestring import mark_safe from django.contrib.auth.models import ( BaseUserManager, AbstractBaseUser,PermissionsMixin ) #1. 創建用戶時調用這個類 class UserProfileManager(BaseUserManager): #這個方法用來創建普通用戶 def create_user(self, email, name, password=None): if not email: raise ValueError('Users must have an email address') user = self.model( #驗證email email=self.normalize_email(email), name=name, ) user.set_password(password) #讓密碼更安全,設置密碼,給密碼加鹽 self.is_active = True #指定創建用戶默認是active user.save(using=self._db) #保存創建信息 return user def create_superuser(self, email, name, password): #這個方法用來創建超級用戶 user = self.create_user( email, password=password, name=name, ) user.is_active = True user.is_admin = True user.save(using=self._db) return user #2 創建UserProfile表替代Django admin中的user表做用戶登錄 class UserProfile(AbstractBaseUser,PermissionsMixin): email = models.EmailField( verbose_name='email address', max_length=255, unique=True, null=True ) password = models.CharField(_('password'), max_length=128,help_text=mark_safe('''<a href='password/'>修改密碼</a>''')) name = models.CharField(max_length=32) is_active = models.BooleanField(default=True) is_admin = models.BooleanField(default=False) #roles = models.ManyToManyField("Role",blank=True) objects = UserProfileManager() #創建用戶時會調用這里類 USERNAME_FIELD = 'email' #自己指定那個字段作為用戶名 REQUIRED_FIELDS = ['name'] #那些字段是必須的 # 下面這些是默認方法不必修改它 def get_full_name(self): # The user is identified by their email address return self.email def get_short_name(self): # The user is identified by their email address return self.email def __str__(self): # __unicode__ on Python 2 return self.email def has_perm(self, perm, obj=None): #對用戶授權(如果注釋掉用戶登錄后沒任何表權限) return True def has_module_perms(self, app_label): #對用戶授權(如果注釋掉用戶登錄后沒任何表權限) return True @property def is_staff(self): #return self.is_admin #這個必須是指定admin才能登陸Django admin后台 return self.is_active #這個只要用戶時is_active的即可登陸Django admin后台

#解決我們改寫的Django admin 中user表驗證時密碼明文問題 from django.contrib import admin from django import forms from django.contrib.auth.models import Group from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.forms import ReadOnlyPasswordHashField from app01 import models #1、不必改什么(創建用戶時調用這個類) class UserCreationForm(forms.ModelForm): password1 = forms.CharField(label='Password', widget=forms.PasswordInput) password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) class Meta: model = models.UserProfile fields = ('email', 'name') def clean_password2(self): password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: raise forms.ValidationError("Passwords don't match") return password2 def save(self, commit=True): # Save the provided password in hashed format user = super(UserCreationForm, self).save(commit=False) user.set_password(self.cleaned_data["password1"]) if commit: user.save() return user #2、不必改什么(修改用戶時調用這個類) class UserChangeForm(forms.ModelForm): password = ReadOnlyPasswordHashField() class Meta: model = models.UserProfile fields = ('email', 'password', 'name', 'is_active', 'is_admin') def clean_password(self): return self.initial["password"] #3、定制UserProfile表 class UserProfileAdmin(BaseUserAdmin): # The forms to add and change user instances form = UserChangeForm add_form = UserCreationForm list_display = ('email', 'name', 'is_admin',"is_staff",'password') list_filter = ('is_admin',) fieldsets = ( (None, {'fields': ('email', 'password')}), ('Personal', {'fields': ('name',)}), ('Permissions', {'fields': ('is_admin',"is_active","user_permissions",'groups')}), # ('Permissions', {'fields': ('is_admin',"roles","is_active","user_permissions",'groups')}), ) #Permissions后的字典記得加上,is_admin,is_active否則我們無法再前端勾選,那么我們自己新建的用戶無法登陸Django Admin后台 add_fieldsets = ( (None, { 'classes': ('wide',), 'fields': ('email', 'name', 'password1', 'password2')} # 'fields': ("roles",'email', 'name', 'password1', 'password2')} ), ) search_fields = ('email',) ordering = ('email',) filter_horizontal = ("user_permissions",'groups',) #顯示多對多的選項框 admin.site.register(models.UserProfile, UserProfileAdmin) admin.site.unregister(Group)

AUTH_USER_MODEL = 'app01.UserProfile' #app名.表名
1.3 將要顯示的表注冊到我們自己的kind_admin.py中 返回頂部
1、說明
1、Django admin中默認提供了將model表注冊功能
2、注冊過的表就可以通過 http://127.0.0.1:8001/admin/ 登錄后台進行管理
3、但是Django admin默認后台管理界面樣式很單一,只能進行一些簡單定制,無法很好嵌入到我們的項目中
4、在這里我就自己寫了一個register函數,動態將model表注冊到我們的kind_admin中

from django.contrib import admin from app01 import models class UserAdmin(admin.ModelAdmin): list_display = ('username','pwd','ut','ctime',) list_filter = ('source','consultant','date') #過濾字段 search_fields = ('qq','name') #搜索匹配字段 raw_id_fields = ('consult_course',) filter_horizontal = ('tags',) #多對多字段顯示 list_per_page = 1 #每頁顯示幾條數據 list_editable = ('source',) #可編輯的字段 readonly_fields = ('qq',) #只讀字段 exclude = ('name',) # 添加和修改時那些界面不顯示 date_hierarchy = 'ctime' # 詳細時間分層篩選 actions = ['test_action',] #之定義的action函數 def test_action(self, request, arg2): # 自定義action函數 ''' :param self: crm.CustomerAdmin類本身 :param request: 客戶端request請求 :param arg2: 前端選中的數據實例 ''' admin.site.register(models.User,UserAdmin) admin.site.site_header = '重寫DjangoAdmin管理系統' # 修改系統顯示名稱 admin.site.site_title = '我的后台管理界面' # 修改頁面 title
2、重寫步驟

from crm import models from django.shortcuts import render,HttpResponse,redirect enabled_admins = {} class BaseAdmin(object): using_add_func = True #如果需要有單獨的添加頁面繼承時改為false using_change_func = True #如果需要有單獨的修改頁面繼承時改為false list_display = [] readonly_fields = [] list_filter = [] search_fields = [] actions = ['delete_selected_objs'] list_per_page = 5 modelform_exclude_fields = [] readonly_table = False filter_horizontal =[] def delete_selected_objs(self, request, selected_ids): app_name = self.model._meta.app_label table_name = self.model._meta.model_name if self.readonly_table: errors = {"readonly_table": "table is readonly,cannot be deleted" } else: errors = {} if request.POST.get("delete_confirm") == "yes": if not self.readonly_table: #整張表readonly時不能刪除 objs = self.model.objects.filter(id__in=selected_ids).delete() return redirect("/kind_admin/%s/%s/"%(app_name,table_name)) #刪除后返回到/kind_admin/crm/customer/頁面 # 這里是通過table_obj_delete.html頁面向 /kind_admin/crm/customer/ 的url傳送post請求 return render(request, 'kind_admin/table_obj_delete.html', {'app_name': app_name, 'table_name': table_name, 'selected_ids': ','.join(selected_ids), 'action': request._admin_action}) def default_form_validation(self): # clean鈎子對整體驗證 ''' 每個class_admin都可以重寫這個方法來對整體驗證''' def register(model_class,admin_class=BaseAdmin): app_name = model_class._meta.app_label table_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][table_name] = admin_class ''' enabled_admins = {'crm':{ 'customer': "<class 'king_admin.kind_admin.CustomerAdmin'>", 'customerfollowup': "<class 'king_admin.kind_admin.CustomerFollowUpAdmin'>"} } '''

from kind_admin.register_admin import BaseAdmin from kind_admin.register_admin import register from kind_admin.register_admin import enabled_admins from django.shortcuts import render,HttpResponse,redirect from crm import models class UserProfileAdmin(BaseAdmin): using_add_func = False list_display = ('email','name','is_admin','enroll') readonly_fields = ('password',) modelform_exclude_fields = ["last_login",] filter_horizontal = ('user_permissions',) search_fields = ['name',] modelform_exclude_fields = ['last_login','groups','roles','is_superuser'] def rewrite_add_page(self,request,app_name,table_name,model_form_class): errors = {} if request.method == 'POST': _password1 = request.POST.get("password") _password2 = request.POST.get("password2") if _password1 == _password2: if len(_password2) > 5: form_obj = model_form_class(request.POST) if form_obj.is_valid(): obj = form_obj.save() print('obj',obj,type(obj)) obj.set_password(_password1) # obj.save() return redirect(request.path.replace("/add/", '/')) else: errors['password_too_short'] = "muset not less than 6 letters" else: errors['invalid_password'] = "passwords are not the same" form_obj = model_form_class() return render(request, 'kind_admin/rewrite_add_user_page.html', {'form_obj': form_obj, 'app_name': app_name, 'table_name': table_name, 'table_name_detail': self.model._meta.verbose_name_plural, 'admin_class': self, 'errors': errors, }) # 在前端顯示數據庫中不存在的字段 def enroll(self): if self.instance == 1: link_name = "報名新課程" else: link_name = "報名" return '''<a href="/crm/customer/%s/enrollment/"> %s </a>''' %(self.instance.id,link_name) enroll.display_name = "報名鏈接" def clean_name(self): # clean_字段名 是字段鈎子(每個字段都有對應的這個鈎子) print("name clean validation:", self.cleaned_data["name"]) if not self.cleaned_data["name"]: self.add_error('name', "cannot be null") else: return self.cleaned_data["name"] register(models.UserProfile,UserProfileAdmin)

from django.shortcuts import render,HttpResponse #1 將表注冊到我們的kind_admin中 from kind_admin import kind_admin def kind_admin_index(request): '''在前端展示app和表名''' print('kind_admin',kind_admin.enabled_admins) return HttpResponse('ok')
3、使用models類獲取表名和APP名稱

pyton manage.py shell #進入有當前Django環境的Python交互器 from crm import models #在這種環境下就可以導入APP crm的models文件了 # 1. 通過表格models類獲取app名稱 >>> models.UserProfile._meta.app_label # 'crm' # 2. 通過表格models類獲取對應表名 >>> models.UserProfile._meta.model_name # 'userprofile' # 3. 顯示Meta中重新定義的 表名 >>> models.UserProfile._meta.verbose_name_plural # '賬號表' # admin_class.model._meta.verbose_name_plural #獲取Meta中定義的中文表名 # 4. 根據字符串的app名字反射導入app中的models模塊 >>> import importlib >>> m = importlib.import_module('crm.models') #使用字符串即可導入模塊 <module 'crm.models' from 'C:\\Users\\tom\\PycharmProjects\\MyCRM\\crm\\models.py'> >>> m.UserProfile <class 'crm.models.UserProfile'> def display_table_objs(request,app_name,table_name): models_module =importlib.import_module('%s.models'%(app_name)) model_obj = getattr(models_module,table_name) #通過字符串的表名反射出表對象
1.4 項目首頁:顯示注冊的app名、表名(kind_admin_index.html 頁面1) 返回頂部
1、首頁效果圖
2、將app名、表名顯示到頁面中

urlpatterns = [ url(r'^$', views.kind_admin_index,name='kind_admin_index'), ]

from django.shortcuts import render #1 將表注冊到我們的kind_admin中 from kind_admin import kind_admin def kind_admin_index(request): '''在前端展示app和表名''' return render(request,'kind_admin/kind_admin_index.html',{"table_list":kind_admin.enabled_admins})

<div style="width: 80%;margin-left: 100px; margin-top: 40px"> {% block container %} <div class="bs-example" data-example-id="simple-table"> {% for app_name,app_tables in table_list.items %} <table class="table table-hover"> <thead> <tr> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">{{ app_name }}</h3> </div> </div> </tr> </thead> <tbody> {% for table_name,admin_class in app_tables.items %} <tr> <td><a href="{% url 'display_table_obj' app_name table_name %}">{% render_table_name admin_class %}</a></td> <td><a href="{% url 'table_obj_add' app_name table_name %}">ADD</a></td> <td><a href="{% url 'display_table_obj' app_name table_name %}">CHANGE</a></td> </tr> {% endfor %} </tbody> </table> {% endfor %} </div> {% endblock %} </div>

# 顯示app中表的詳細名稱 @register.simple_tag def render_table_name(admin_class): # admin_class.model = models.UserProfile # 找到對應表的類 return admin_class.model._meta.verbose_name_plural
1.5 展示表中各條數據(display_table_obj.html 頁面2) 返回頂部
1、功能點&效果圖

from django.shortcuts import render,HttpResponse,redirect from crm import models import os #1 將表注冊到我們的kind_admin中 from kind_admin import kind_admin #2 過濾 排序 from kind_admin.utils import select_filter,search_filter,table_sort #3 分頁 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger #4 動態生成ModelForm類 from kind_admin.forms import create_model_form #5 這里是登錄和注銷需要的模塊 from django.contrib.auth import login,authenticate,logout from django.contrib.auth.decorators import login_required #裝飾器,用來驗證用戶是否登錄 #6 導入我們自己寫的 通用權限管理組件 from kind_admin.permissions import permission @login_required def kind_admin_index(request): '''在前端展示app和表名''' return render(request,'kind_admin/kind_admin_index.html',{"table_list":kind_admin.enabled_admins}) # @permission.check_permisssion @login_required def display_table_obj(request,app_name,table_name): '''分頁展示每個表中具體內容''' admin_class = kind_admin.enabled_admins[app_name][table_name] #1. 對select下拉菜單過濾 obj_list,filter_conditions = select_filter(request,admin_class) #2. 對search搜索過濾 obj_list = search_filter(request,admin_class,obj_list) #3. 對表頭排序 obj_list,orderby_key = table_sort(request,admin_class,obj_list) search_text = request.GET.get('_q','') if not search_text: #沒有搜索時顯示能夠匹配搜索的字段 search_text = "serach by: %s"%(','.join(admin_class.search_fields)) if request.method == 'POST': #分發Django admin的action操作 action = request.POST.get('action') selected_ids = request.POST.get('selected_ids') if selected_ids: selected_ids = selected_ids.split(',') selected_objs = admin_class.model.objects.filter(id__in=selected_ids) else: raise KeyError("No object selected.") if hasattr(admin_class,action): action_func = getattr(admin_class,action) request._admin_action = action return action_func(admin_class,request,selected_ids) #分頁 paginator = Paginator(obj_list, admin_class.list_per_page) # Show 25 contacts per page page = request.GET.get('page') try: contacts = paginator.page(page) except PageNotAnInteger: # 頁數為負數(或不是整數)返回第一頁 contacts = paginator.page(1) except EmptyPage: contacts = paginator.page(paginator.num_pages) # 頁數超出范圍返回最后一頁 return render(request,'kind_admin/display_table_obj.html', {'admin_class':admin_class, 'obj_list': contacts, 'filter_conditions':filter_conditions, 'app_name':app_name, 'table_name':table_name, 'table_name_detail':admin_class.model._meta.verbose_name_plural, 'search_text':search_text, 'search_filter_text':request.GET.get('_q',''), 'orderby_key':orderby_key, 'previous_orderby':request.GET.get('o',''), }) # @permission.check_permisssion @login_required def table_obj_add(request,app_name,table_name): admin_class = kind_admin.enabled_admins[app_name][table_name] admin_class.is_add_form = True model_form_class = create_model_form(request,admin_class) if not admin_class.using_add_func: rewrite_add_page = admin_class.rewrite_add_page(admin_class,request,app_name,table_name,model_form_class) return rewrite_add_page if request.method == 'POST': form_obj = model_form_class(request.POST) if form_obj.is_valid(): form_obj.save() return redirect(request.path.replace("/add/",'/')) form_obj = model_form_class() return render(request,'kind_admin/table_obj_add.html', {'form_obj':form_obj, 'app_name':app_name, 'table_name':table_name, 'table_name_detail': admin_class.model._meta.verbose_name_plural, 'admin_class': admin_class, }) # @permission.check_permisssion @login_required def table_obj_change(request,app_name,table_name,obj_id): admin_class = kind_admin.enabled_admins[app_name][table_name] model_form_class = create_model_form(request,admin_class) obj = admin_class.model.objects.get(id=obj_id) form_obj = model_form_class(instance=obj) if request.method == 'POST': form_obj = model_form_class(request.POST,instance=obj) if form_obj.is_valid(): form_obj.save() else: print('errors',form_obj.errors) return render(request,'kind_admin/table_obj_change.html', {'form_obj':form_obj, 'app_name':app_name, 'table_name':table_name, 'table_name_detail': admin_class.model._meta.verbose_name_plural, 'admin_class':admin_class, 'obj_id':obj_id}) @login_required def password_reset(request,app_name,table_name,obj_id): admin_class = kind_admin.enabled_admins[app_name][table_name] model_form_class = create_model_form(request,admin_class) obj = admin_class.model.objects.get(id=obj_id) errors = {} if request.method == 'POST': _password1 = request.POST.get("password1") _password2 = request.POST.get("password2") if _password1 == _password2: if len(_password2) >5: print('obj reset',obj,type(obj)) obj.set_password(_password1) # obj.save() return redirect(request.path.rstrip('password/')) else: errors['password_too_short'] = "muset not less than 6 letters" else: errors['invalid_password'] = "passwords are not the same" return render(request,'kind_admin/password_reset.html',{'obj':obj, 'errors':errors, 'app_name':app_name, 'table_name':table_name, 'table_name_detail': admin_class.model._meta.verbose_name_plural, 'obj_id':obj_id }) # @permission.check_permisssion @login_required def table_obj_delete(request,app_name,table_name,obj_id): admin_class = kind_admin.enabled_admins[app_name][table_name] obj = admin_class.model.objects.get(id=obj_id) if request.method == "POST": obj.delete() return redirect("/kind_admin/%s/%s/"%(app_name,table_name)) return render(request,'kind_admin/table_obj_delete.html', {'app_name':app_name, 'table_name':table_name, 'table_name_detail': admin_class.model._meta.verbose_name_plural, 'obj_id':obj_id}) ############################# 下面這一塊用來做用戶登錄注銷功能 ##################################### def acc_login(request): errors = {} if request.method == 'POST': _email = request.POST.get('email') _password = request.POST.get('password') user = authenticate(username= _email, password=_password) #通過驗證會返回一個user對象 print('user',user) if user: login(request,user) #Django自動登錄,然后創建session next_url = request.GET.get("next","/kind_admin/") #登錄成功后會跳轉的頁面,沒有next時是/kind_admin/ #未登錄時直接輸入url時跳轉到登錄界面是會加上"next"參數 return redirect(next_url) else: errors['error'] = "Wrong username or password!" return render(request,'kind_admin/login.html',{'errors':errors}) def acc_logout(request): logout(request) return redirect("/kind_admin/login/")
2、功能1:對表中一對多字段下拉菜單過濾(list_filter)

<form> {% if admin_class.list_filter %} <div class="select-filter-div"> {% for filter_field in admin_class.list_filter %} <div> <div class="select-label">{{ filter_field }}: </div> {% render_filter_ele filter_field admin_class filter_conditions %} </div> {% endfor %} <button type="submit" class="btn btn-default btn-format">檢索</button> </div> {% endif %} </form>

#搜索功能(下拉框) @register.simple_tag def render_filter_ele(filter_field,admin_class,filter_conditions): ''' :param filter_field: 一對多字段名(如:user_type) :param admin_class: 通過admin_class.model可以獲取到表對應的類 :param filter_conditions: 其他過濾信息的字典格式 ''' select_ele = '<select name="{filter_field}" class="form-control select-width">' select_ele += '''<option value=''>-----------</ option >''' field_obj = admin_class.model._meta.get_field(filter_field) if field_obj.choices: # 一對多的choices類型 for choice_item in field_obj.choices: selected = '' if filter_conditions.get(filter_field) == str(choice_item[0]): selected = 'selected' option_ele = '''<option value='%s' %s>%s</ option >'''%(choice_item[0],selected,choice_item[1]) select_ele += option_ele if type(field_obj).__name__ == 'ForeignKey': #一對多字段 for choice_item in field_obj.get_choices()[1:]: selected = '' if filter_conditions.get(filter_field) == str(choice_item[0]): selected = 'selected' option_ele = '''<option value='%s' %s>%s</ option >'''%(choice_item[0],selected,choice_item[1]) select_ele += option_ele if type(field_obj).__name__ in ['DateTimeField', 'DateField']: # 時間類型 date_els = [] today_ele = datetime.now().date() date_els.append(['今天', datetime.now().date()]) date_els.append(['昨天', today_ele - timedelta(days=1)]) date_els.append(['近七天', today_ele - timedelta(days=7)]) date_els.append(['本月', today_ele.replace(day=1)]) date_els.append(['近30天', today_ele - timedelta(days=30)]) date_els.append(['近90天', today_ele - timedelta(days=90)]) date_els.append(['近180天', today_ele - timedelta(days=180)]) date_els.append(['本年', today_ele.replace(month=1, day=1)]) date_els.append(['近365天', today_ele - timedelta(days=365)]) selected = '' for item in date_els: if filter_conditions.get('date__gte') == str(item[1]): # choice_item[0]選中的choices字段值的id selected = 'selected' select_ele += '''<option value='%s' %s>%s</option>''' % (item[1], selected, item[0]) selected = '' filter_field_name = "%s__gte" % filter_field else: filter_field_name = filter_field print('') select_ele += '</select>' select_ele = select_ele.format(filter_field=filter_field_name) return mark_safe(select_ele)

#2 過濾 from kind_admin.utils import select_filter def display_table_obj(request,app_name,table_name): '''分頁展示每個表中具體內容''' admin_class = kind_admin.enabled_admins[app_name][table_name] #1. 對select下拉菜單過濾 obj_list,filter_conditions = select_filter(request,admin_class)

# 將前端下拉菜單過濾條件變成字典形式(並去除關鍵字) def select_filter(request,admin_class): filter_conditions = {} keywords = ['_q','page','o'] for k,v in request.GET.items(): if k in keywords: continue if v: filter_conditions[k]=v return admin_class.model.objects.filter(**filter_conditions),filter_conditions # 返回的是過濾條件字典:{'user_type_id': '1'}

<div> <div class="select-label">user_type_id: </div> <select name="user_type_id" class="form-control select-width"> <option value="">-----------</option> <option value="1">超級用戶</option> <option value="2">普通用戶</option> <option value="6">普普通用戶</option> </select> </div>

>>> c = models.Customer.objects.all()[0] # 必須先獲取一條數據,通過字符串字段名獲取值 # 2.1 通過字符串的字段名獲取到對應 普通字段的值 >>> getattr(c,'name') 'alex' # 2.2 通過字符串的字段名獲取到對應 choces字段 的值(s1.choices不為空就是choices字段) >>> s1 = c._meta.get_field('source') >>> s1.choices ((1, '轉介紹'), (2, 'qq群'), (3, '官網'), (4, '百度推廣'), (5, '51CTO'), (6, '市場推廣')) >>> getattr(c,'get_source_display')() #找到choices字段中選中的值:source是字段名 'QQ群' # 2.3 判斷多對多字段 >>> m2m = getattr(c, 'tags') #判斷字段是多對多字段 >>> hasattr(m2m,"select_related") True >>> m2m.all() #獲取多對多選中的值 <QuerySet [<Tag: tag01>]> # 2.4 通過字符串的字段名獲取到對應 時間字段 的值 >>> type(c.date).__name__ #判斷表中的date字段是否是日期字段 'datetime' >>> c.date.strftime("%Y-%m-%d") #如果是日期字段就轉換成中文的時間格式 '2017-08-11'
3、功能2:指定對表的那些字段進行匹配搜索(search_fields)

<form> {% if admin_class.search_fields %} <div class="select-filter-div"> <div style="float: left;margin-left: 47px;line-height: 30px"> <span class="glyphicon glyphicon-search" aria-hidden="true"></span> </div> <div class="form-group" style="width: 30%;margin-left: 10px;float: left;"> <input type="text" name="_q" class="form-control" placeholder="{{ search_text }}" value="{{ search_filter_text }}"> </div> <button type="submit" class="btn btn-default btn-format" style="float: left">搜索</button> </div> {% endif %} </form>

from django.shortcuts import render,HttpResponse,redirect #1 將表注冊到我們的kind_admin中 from kind_admin import kind_admin #2 過濾 排序 from kind_admin.utils import select_filter,search_filter def display_table_obj(request,app_name,table_name): '''分頁展示每個表中具體內容''' admin_class = kind_admin.enabled_admins[app_name][table_name] #1. 對select下拉菜單過濾 obj_list,filter_conditions = select_filter(request,admin_class) #2. 對search搜索過濾 obj_list = search_filter(request,admin_class,obj_list)

from django.db.models import Q # 返回input搜索框過濾后的內容 def search_filter(request,admin_class,obj_list): search_key = request.GET.get('_q','') q_obj = Q() q_obj.connector = "OR" for column in admin_class.search_fields: q_obj.children.append(('%s__contains'%column,search_key)) return obj_list.filter(q_obj)
4、功能3:定義action對選中多條數據操作:如刪除(actions)

<form method="post" onsubmit="return ActionSubmit(this);"> <div class="select-filter-div"> <div style="float: left;margin-left: 20px;line-height: 30px"> Action: </div> <div class="form-group" style="width: 30%;margin-left: 10px;float: left;"> <select class="form-control" name="action" id="action_choosed"> <option value="">-----------</option> {% for action in admin_class.actions %} <option value="{{ action }}">{{ action }}</option> {% endfor %} </select> </div> <button type="submit" class="btn btn-default btn-format" style="float: left">GO</button> </div> </form> <script> // 提交前生成:<input name="selected_ids" value="1,2" type="hidden"> append到form中 function ActionSubmit(ths) { var selected_ids = []; $('input[tag="obj_checkbox"]:checked').each(function () { selected_ids.push($(this).val()) }); var action_choosed = $("#action_choosed").val(); if (!selected_ids.length){ alert('必須選擇操作條目!'); return false; } if (!action_choosed.length){ alert('必須選擇要操作的action!'); return false; } var inp = document.createElement('input'); inp.value = selected_ids.toString(); inp.setAttribute('name','selected_ids'); inp.setAttribute('type','hidden') $(ths).append(inp); return true; } </script>

<table class="table table-hover"> <thead> <tr> <th><input type="checkbox" onclick="CheckAllSelect(this);"></th> {% for column in admin_class.list_display %} {% build_table_header_column column admin_class filter_conditions search_filter_text orderby_key %} {% endfor %} </tr> </thead> <tbody> {% for obj in obj_list %} <tr> <td><input tag="obj_checkbox" class="obj_ck" type="checkbox" name="choose_name" value="{{ obj.id }}"></td> {% build_table_row request obj admin_class search_filter_text %} </tr> {% endfor %} </tbody> </table> <script> function CheckAllSelect(ths) { if ($(ths).prop('checked')) { $('input[tag="obj_checkbox"]').prop('checked',true); } else { $('input[tag="obj_checkbox"]').prop('checked',false); } } </script>

from django.shortcuts import render,HttpResponse,redirect #1 將表注冊到我們的kind_admin中 from kind_admin import kind_admin def display_table_obj(request,app_name,table_name): admin_class = kind_admin.enabled_admins[app_name][table_name] if request.method == 'POST': #分發Django admin的action操作 action = request.POST.get('action') selected_ids = request.POST.get('selected_ids') if selected_ids: selected_ids = selected_ids.split(',') selected_objs = admin_class.model.objects.filter(id__in=selected_ids) else: raise KeyError("No object selected.") if hasattr(admin_class,action): action_func = getattr(admin_class,action) request._admin_action = action return action_func(admin_class,request,selected_ids)

from django.shortcuts import render,HttpResponse,redirect class BaseAdmin(object): actions = ['delete_selected_objs'] def delete_selected_objs(self, request, selected_ids): app_name = self.model._meta.app_label table_name = self.model._meta.model_name if self.readonly_table: errors = {"readonly_table": "table is readonly,cannot be deleted" } else: errors = {} if request.POST.get("delete_confirm") == "yes": if not self.readonly_table: #整張表readonly時不能刪除 objs = self.model.objects.filter(id__in=selected_ids).delete() return redirect("/kind_admin/%s/%s/"%(app_name,table_name)) #刪除后返回到/kind_admin/crm/customer/頁面 # 這里是通過table_obj_delete.html頁面向 /kind_admin/crm/customer/ 的url傳送post請求 return render(request, 'kind_admin/table_obj_delete.html', {'app_name': app_name, 'table_name': table_name, 'selected_ids': ','.join(selected_ids), 'action': request._admin_action})
5、功能4:將表中數據展示到頁面

<table class="table table-hover"> <thead> <tr> <th><input type="checkbox" onclick="CheckAllSelect(this);"></th> {% for column in admin_class.list_display %} {% build_table_header_column column admin_class filter_conditions search_filter_text orderby_key %} {% endfor %} </tr> </thead> <tbody> {% for obj in obj_list %} <tr> <td><input tag="obj_checkbox" class="obj_ck" type="checkbox" name="choose_name" value="{{ obj.id }}"></td> {% build_table_row request obj admin_class search_filter_text %} </tr> {% endfor %} </tbody> </table>

# 顯示表字段名字 與 點擊字段名進行排序 @register.simple_tag def build_table_header_column(column,admin_class,filter_conditions,search_filter_text,orderby_key,): filters = '' for k,v in filter_conditions.items(): filters += "&%s=%s"%(k,v) if orderby_key: if orderby_key.startswith('-'): sort_icon = '''<span class="glyphicon glyphicon-menu-up" aria-hidden="true"></span>''' else: sort_icon = '''<span class="glyphicon glyphicon-menu-down" aria-hidden="true"></span>''' if orderby_key.strip('-') == column: orderby_key = orderby_key else: orderby_key = column sort_icon = '' else: sort_icon = '' orderby_key = column try: #這里的try是因為顯示數據庫未定義字段時會報錯 column_verbose_name = admin_class.model._meta.get_field(column).verbose_name.upper() ele = '''<th><a href="?o={orderby_key}&_q={search_filter_text}&{filters}">{column_verbose_name}</a>{sort_icon}</th>'''\ .format(orderby_key=orderby_key,filters=filters,column_verbose_name=column_verbose_name,sort_icon=sort_icon,search_filter_text=search_filter_text) except FieldDoesNotExist as e: # 在前端顯示數據庫中不存在的字段 column_verbose_name = getattr(admin_class, column).display_name.upper() ele = ''' <th><a href="javascript:void(0);">%s</a></th>''' % column_verbose_name return mark_safe(ele)

# 顯示list_display要顯示字段具體內容 @register.simple_tag def build_table_row(request,obj,admin_class,search_filter_text): ele = '' for index,colunm in enumerate(admin_class.list_display): try: #這里的try是因為顯示數據庫未定義字段時會報錯 field_obj = obj._meta.get_field(colunm) field_val = getattr(obj,colunm) if field_obj.choices: column_data = getattr(obj,'get_%s_display'%colunm)() elif hasattr(field_val,"select_related"): data_list = [] column_data = field_val.all().values_list() for data in column_data: data_list.append(data[1]) column_data = ' '.join(data_list) else: column_data = getattr(obj, colunm) if type(column_data).__name__ == 'datetime': column_data = column_data.strftime("%Y-%m-%d") if len(str(column_data)) > 20: column_data = str(column_data[0:20])+'...' if colunm in admin_class.search_fields: if column_data: column_data = re.sub(search_filter_text,'''<span style="color: red">%s</span>'''%search_filter_text,column_data) if index == 0: ele += '''<td><a href="{request_path}{obj_id}/change/">{column_data}</a></td>'''.format(request_path=request.path,obj_id=obj.id,column_data=column_data) else: ele += '''<td>{column_data}</td>'''.format(column_data=column_data) except FieldDoesNotExist as e: # 在前端顯示數據庫中不存在的字段 if hasattr(admin_class, colunm): column_func = getattr(admin_class, colunm) admin_class.instance = obj admin_class.request = request column_data = column_func() ele += "<td>%s</td>" % column_data return mark_safe(ele)
6、功能5: 根據字段名進行排序

<table class="table table-hover"> <thead> <tr> <th><a href="?o=email&_q=tom&user_type_id=1">EMAIL ADDRESS</a></th> <th><a href="?o=name&_q=tom&user_type_id=1">NAME</a></th> <th><a href="javascript:void(0);">報名鏈接</a></th> </tr> </thead> </table>

from django.shortcuts import render,HttpResponse,redirect from kind_admin import kind_admin from kind_admin.utils import select_filter,search_filter,table_sort def display_table_obj(request,app_name,table_name): '''分頁展示每個表中具體內容''' admin_class = kind_admin.enabled_admins[app_name][table_name] #1. 對select下拉菜單過濾 obj_list,filter_conditions = select_filter(request,admin_class) #2. 對search搜索過濾 obj_list = search_filter(request,admin_class,obj_list) #3. 對表頭排序 obj_list,orderby_key = table_sort(request,admin_class,obj_list)

# 將過濾完成的數據進行排序 def table_sort(request,admin_class,obj_list): orderby_key = request.GET.get('o','') if orderby_key: res = obj_list.order_by(orderby_key) if orderby_key.startswith("-"): orderby_key = orderby_key.strip("-") else: orderby_key = "-%s"%(orderby_key) else: res = obj_list.order_by('-id') return res,orderby_key
7、功能6:分頁

from django.shortcuts import render,HttpResponse,redirect from crm import models from kind_admin import kind_admin # 分頁 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger def display_table_obj(request,app_name,table_name): '''分頁展示每個表中具體內容''' admin_class = kind_admin.enabled_admins[app_name][table_name] #分頁 paginator = Paginator(obj_list, admin_class.list_per_page) # Show 25 contacts per page page = request.GET.get('page') try: contacts = paginator.page(page) except PageNotAnInteger: # 頁數為負數(或不是整數)返回第一頁 contacts = paginator.page(1) except EmptyPage: contacts = paginator.page(paginator.num_pages) # 頁數超出范圍返回最后一頁

<div class="pagination"> <nav aria-label="Page navigation"> <ul class="pagination"> {% if obj_list.has_previous %} <li><a href="?page={{ obj_list.previous_page_number }}&_q={{ search_filter_text }}&o={{ previous_orderby }}{% render_filter_conditions filter_conditions %}">上一頁</a></li> {% endif %} {% for loop_counter in obj_list.paginator.page_range %} {% render_page_ele loop_counter obj_list filter_conditions search_filter_text previous_orderby %} {% endfor %} {% if obj_list.has_next %} <li><a href="?page={{ obj_list.next_page_number }}&_q={{ search_filter_text }}&o={{ previous_orderby }}{% render_filter_conditions filter_conditions %}">下一頁</a></li> {% endif %} </ul> </nav> </div>

#獲取下拉菜單過濾后拼接的url格式字符串格式 @register.simple_tag def render_filter_conditions(filter_conditions): filters = '' for k, v in filter_conditions.items(): filters += "&%s=%s" % (k, v) return filters

#分頁 @register.simple_tag def render_page_ele(loop_counter,query_sets,filter_conditions,search_text,previous_orderby): #query_sets.number 獲取當前頁 #loop_counter循環到第幾頁 filters = '' for k,v in filter_conditions.items(): filters += "&%s=%s"%(k,v) if abs(query_sets.number -loop_counter) <= 3: ele_class = "" if query_sets.number == loop_counter: ele_class = 'active' ele = '''<li class="%s"><a href="?page=%s&_q=%s&o=%s&%s">%s</a></li>'''\ %(ele_class,loop_counter,search_text,previous_orderby,filters,loop_counter) return mark_safe(ele) return ''
8、功能7:顯示表中不存在字段

<thead> <tr> <th><input type="checkbox" onclick="CheckAllSelect(this);"></th> {% for column in admin_class.list_display %} {% build_table_header_column column admin_class filter_conditions search_filter_text orderby_key %} {% endfor %} </tr> </thead>

# 顯示表字段名字 與 點擊字段名進行排序 @register.simple_tag def build_table_header_column(column,admin_class,filter_conditions,search_filter_text,orderby_key,): filters = '' for k,v in filter_conditions.items(): filters += "&%s=%s"%(k,v) if orderby_key: if orderby_key.startswith('-'): sort_icon = '''<span class="glyphicon glyphicon-menu-up" aria-hidden="true"></span>''' else: sort_icon = '''<span class="glyphicon glyphicon-menu-down" aria-hidden="true"></span>''' if orderby_key.strip('-') == column: orderby_key = orderby_key else: orderby_key = column sort_icon = '' else: sort_icon = '' orderby_key = column try: #這里的try是因為顯示數據庫未定義字段時會報錯 column_verbose_name = admin_class.model._meta.get_field(column).verbose_name.upper() ele = '''<th><a href="?o={orderby_key}&_q={search_filter_text}&{filters}">{column_verbose_name}</a>{sort_icon}</th>'''\ .format(orderby_key=orderby_key,filters=filters,column_verbose_name=column_verbose_name,sort_icon=sort_icon,search_filter_text=search_filter_text) except FieldDoesNotExist as e: # 在前端顯示數據庫中不存在的字段 column_verbose_name = getattr(admin_class, column).display_name.upper() ele = ''' <th><a href="javascript:void(0);">%s</a></th>''' % column_verbose_name return mark_safe(ele)
1.6 添加數據(table_obj_add.html 頁面3) 返回頂部
1、功能點&效果圖
1、功能1:動態生成ModelForm類
2、功能2:使用ModelForm類生成表單
3、功能3:判斷如果是多對多字段特殊處理
4、功能4:如果整張表只讀、使用默認鈎子驗證數據是否修改
2、功能1:動態生成ModelForm類

def table_obj_add(request,app_name,table_name): admin_class = kind_admin.enabled_admins[app_name][table_name] admin_class.is_add_form = True model_form_class = create_model_form(request,admin_class) if not admin_class.using_add_func: rewrite_add_page = admin_class.rewrite_add_page(admin_class,request,app_name,table_name,model_form_class) return rewrite_add_page if request.method == 'POST': form_obj = model_form_class(request.POST) if form_obj.is_valid(): form_obj.save() return redirect(request.path.replace("/add/",'/')) form_obj = model_form_class() return render(request,'kind_admin/table_obj_add.html', {'form_obj':form_obj, 'app_name':app_name, 'table_name':table_name, 'table_name_detail': admin_class.model._meta.verbose_name_plural, 'admin_class': admin_class, })

from django.forms import ModelForm,ValidationError from django.utils.translation import ugettext as _ #國際化 #創建動態生成ModelForm類的函數 def create_model_form(request,admin_class): ''' 創建動態生成ModelForm類的函數 :param request: :param admin_class: :return: ''' def default_clean(self): '''給所有form默認加一個clean驗證:readonly字段驗證,對整張只讀表驗證,clean鈎子對整體驗證''' error_list = [] if self.instance.id: #這是一個修改的表單,如果為空就是一個添加表單,才判斷字段字段值是否改變 for field in admin_class.readonly_fields: field_val = getattr(self.instance,field) #從數據庫中取到對應字段的值 if hasattr(field_val,"select_related"): #多對多字段只讀 m2m_objs = getattr(field_val,"select_related")().select_related() m2m_vals = [i[0] for i in m2m_objs.values_list('id')] set_m2m_vals = set(m2m_vals) # print("cleaned data",self.cleaned_data) set_m2m_vals_from_frontend = set([i.id for i in self.cleaned_data.get(field)]) if set_m2m_vals != set_m2m_vals_from_frontend: #1 判斷多對多字段是否修改 self.add_error(field,"readonly field") continue field_val_from_frontand = self.cleaned_data.get(field) if field_val != field_val_from_frontand: #2 判斷非多對多字段是否修改 error_list.append( ValidationError( _('Field %(field)s is readonly,data should be %(val)s'), code='invalid', params={'field':field,'val':field_val}, )) #readonly_table check # if admin_class.readonly_table: #3 防止黑客自己寫提交按鈕提交整張表都是只讀權限的表 if admin_class.readonly_table: #3 防止黑客自己寫提交按鈕提交整張表都是只讀權限的表 raise ValidationError( _('Table is readonly,cannot be modified ro added'), code='invalid', ) self.ValidationError = ValidationError #這樣用戶自己驗證時就可以不必導入了 #在這個cleaned方法中定義一個允許用戶自己定義的方法做驗證 response = admin_class.default_form_validation(self) #4 clean鈎子對整體驗證 if response: error_list.append(response) if error_list: raise ValidationError(error_list) def __new__(cls,*args,**kwargs): '''在創建form時添加樣式,為每個字段預留鈎子''' for field_name,field_obj in cls.base_fields.items(): field_obj.widget.attrs['class'] = "form-control" if not hasattr(admin_class,"is_add_form"): if field_name in admin_class.readonly_fields: field_obj.widget.attrs['disabled'] = "disabled" # clean_字段名 是字段鈎子(每個字段都有對應的這個鈎子) if hasattr(admin_class, "clean_%s" % field_name): # 用戶自定義字段驗證 field_clean_func = getattr(admin_class, "clean_%s" % field_name) setattr(cls, "clean_%s" % field_name, field_clean_func) return ModelForm.__new__(cls) #調用一下ModelForm的__new__方法否則不往下走 '''動態生成ModelForm''' class Meta: model = admin_class.model fields = "__all__" exclude = admin_class.modelform_exclude_fields # 那些字段不顯示 # exclude = ("qq",) attrs = {'Meta':Meta} _model_form_class = type("DynamicModelForm",(ModelForm,),attrs) setattr(_model_form_class,"__new__",__new__) setattr(_model_form_class,'clean',default_clean) #動態將_default_clean__函數添加到類中 return _model_form_class
3、功能2:使用ModelForm類生成表單

<div style="margin-bottom: 200px"> <!-- 1、顯示ModelForm驗證錯誤信息 --> <span style="color: red;"> {{ form_obj.errors }} </span> <form method="POST"> <!-- 2、循環ModelForm類生成表單 --> {% for field in form_obj %} <!-- 3、必填字段顏色標記 --> <label> {% if field.field.required %} <span style="font-weight: bold;color: gold">{{ field.label }}</span> {% else %} {{ field.label }} {% endif %} </label> <div class="col-sm-10"> <!-- 4、如果是多對多字段特殊處理 --> {% if field.name in admin_class.filter_horizontal %} <div> 若果是多對多字段需要特殊處理 </div> <!-- 5、不是多對多字段直接顯示 --> {% else %} {{ field }} {# 非多對多字段復選框顯示 #} <span style="color: gray">{{ field.help_text }}</span> <span style="color: red">{{ field.errors.as_text }}</span> {% endif %} </div> {% endfor %} <!-- 6、整張表只讀隱藏delete/save按鈕 --> {% if not admin_class.readonly_table %} <div style="margin-top: 50px;margin-left: 200px"> {% block table_obj_delete %} <button type="submit" class="btn btn-danger pull-left"> <a href="{% url 'table_obj_delete' app_name table_name obj_id %}">Delete</a> {% endblock %} </button> <button type="submit" class="btn btn-success pull-right">Save</button> </div> {% endif %} </form> </div>
4、功能3:判斷如果是多對多字段特殊處理

<form class="form-horizontal" method="POST" onsubmit="return SelectAllChosenData();"> {% for field in form_obj %} <div class="col-sm-10"> {% if field.name in admin_class.filter_horizontal %} <div class="col-md-5" > {% get_m2m_obj_list admin_class field form_obj as m2m_obj_list %} <select multiple class="filter-select-box" id="id_{{ field.name }}_from"> {% if field.name in admin_class.readonly_fields and not admin_class.is_add_form %} {% for obj in m2m_obj_list %} <!-- 如果多對多是readonly字段就不綁定點擊事件 --> <option value="{{ obj.id }}" disabled>{{ obj }}</option> {% endfor %} {% else %} {% for obj in m2m_obj_list %} <option ondblclick="MoveElementTo(this,'id_{{ field.name }}_to','id_{{ field.name }}_from');" value="{{ obj.id }}">{{ obj }}</option> {% endfor %} {% endif %} </select> </div> <div class="col-md-1"> 箭頭 </div> <div class="col-md-5"> <!-- 多對多字段顯示已經選中的 --> {% get_m2m_selected_obj_list form_obj field as selected_obj_list %} <select multiple class="filter-select-box" id="id_{{ field.name }}_to" tag="chosen_list" name="{{ field.name }}"> {% if field.name in admin_class.readonly_fields %} {% for obj in selected_obj_list %} <!-- 如果多對多是readonly字段就不綁定點擊事件 --> <option disabled value="{{ obj.id }}">{{ obj }}</option> {% endfor %} {% else %} {% for obj in selected_obj_list %} <option ondblclick="MoveElementTo(this,'id_{{ field.name }}_from','id_{{ field.name }}_to');" value="{{ obj.id }}">{{ obj }}</option> {% endfor %} {% endif %} {{ field.error }} </select> </div> <span style="color: red">{{ field.errors.as_text }}</span> {% else %} {{ field }} {# 非多對多字段復選框顯示 #} <span style="color: gray">{{ field.help_text }}</span> <span style="color: red">{{ field.errors.as_text }}</span> {% endif %} </div> </div> {% endfor %} </form> <script> function SelectAllChosenData() { $("select[tag='chosen_list'] option").each(function () { $(this).prop('selected',true); // 提交表單前將右邊復選框內容全部選中 }); //remove all disabled attrs var tag = $('form').find("[disabled]").removeAttr('disabled'); return true } function MoveElementTo(ele,target_id,new_target_id) { var opt_ele = "<option ondblclick='MoveElementTo(this," + '"' + new_target_id + '",' + '"' + target_id + '",' + ")' value=" + $(ele).val() + ">" + $(ele).text() + "</option>"; $("#" + target_id).append(opt_ele); $(ele).remove(); } </script>

# 返回已選中的m2m數據(filter_horizontal) @register.simple_tag def get_m2m_selected_obj_list(form_obj,field): ''' :param form_obj: 要修改的那條ModelForm實例 :param field: ModelForm對應字段類(field.name獲取字段名) :return: ''' if form_obj.instance.id: field_obj = getattr(form_obj.instance,field.name) return field_obj.all() # 返回所有未選中m2m數據(filter_horizontal) @register.simple_tag def get_m2m_obj_list(admin_class,field,form_obj): ''' :param admin_class: :param field: 多選select標簽(field.name獲取字段名) :param form_obj: 自動生成的ModelForm類 :return: 返回m2m字段所有為選中數據 ''' field_obj = getattr(admin_class.model, field.name) #獲取表結構中的字段類 all_obj_list = field_obj.rel.to.objects.all() #取出所有數據(多對多字段) #單條數據的對象中的某個字段 if form_obj.instance.id: #這樣就要求我們創建的表必須有id字段 obj_instance_field = getattr(form_obj.instance,field.name) #獲取單條數據的某個字段instance selected_obj_list = obj_instance_field.all() #取出所有已選數據(多對多字段) else: #代表這是創建一條心數據 return all_obj_list standby_obj_list = [] for obj in all_obj_list: if obj not in selected_obj_list: #selected_obj_list:所有已選數據 standby_obj_list.append(obj) return standby_obj_list
1.7 修改數據(table_obj_change.html 頁面4) 返回頂部
注:修改數據和添加數據html頁面相同、僅僅是視圖函數中處理有些許差別

@login_required def table_obj_change(request,app_name,table_name,obj_id): admin_class = kind_admin.enabled_admins[app_name][table_name] model_form_class = create_model_form(request,admin_class) obj = admin_class.model.objects.get(id=obj_id) form_obj = model_form_class(instance=obj) if request.method == 'POST': form_obj = model_form_class(request.POST,instance=obj) if form_obj.is_valid(): form_obj.save() else: print('errors',form_obj.errors) return render(request,'kind_admin/table_obj_change.html', {'form_obj':form_obj, 'app_name':app_name, 'table_name':table_name, 'table_name_detail': admin_class.model._meta.verbose_name_plural, 'admin_class':admin_class, 'obj_id':obj_id})
1.8 刪除數據(table_obj_delete.html 頁面5) 返回頂部
1、功能點&效果圖
2、刪除數據code

{% if not admin_class.readonly_table %} <div style="margin-top: 50px;margin-left: 200px"> {% block table_obj_delete %} <button type="submit" class="btn btn-danger pull-left"> <a href="{% url 'table_obj_delete' app_name table_name obj_id %}">Delete</a> {% endblock %} </button> <button type="submit" class="btn btn-success pull-right">Save</button> </div> {% endif %}

def table_obj_delete(request,app_name,table_name,obj_id): admin_class = kind_admin.enabled_admins[app_name][table_name] obj = admin_class.model.objects.get(id=obj_id) if request.method == "POST": obj.delete() return redirect("/kind_admin/%s/%s/"%(app_name,table_name)) return render(request,'kind_admin/table_obj_delete.html', {'app_name':app_name, 'table_name':table_name, 'table_name_detail': admin_class.model._meta.verbose_name_plural, 'obj_id':obj_id})

{% extends 'kind_admin/kind_admin_index.html' %} {% load tags %} {% block url_block %} <span> > </span> <a href="/kind_admin/" style="color: white;font-weight: bold">{{ app_name }}</a> <span> > </span> <a href="/kind_admin/{{ app_name }}/{{ table_name }}/" style="color: white;font-weight: bold">{{ table_name_detail }}</a> <span> > delete</span> {% endblock %} {% block container %} <div style="margin-bottom: 200px"> <form class="form-horizontal" method="POST"> <h1>此條數據有多項關聯,確定刪除?</h1> <input type="hidden" name="selected_ids" value="{{ selected_ids }}"> <input type="hidden" value="yes" name="delete_confirm"> <input type="hidden" value="{{ action }}" name="action"> <input type="submit" class="btn btn-danger" value="Yes, I'am sure"> <a class="btn btn-info" href="{% url 'display_table_obj' app_name table_name %}">No, Tack me back</a> </form> </div> {% endblock %}
1.9 用戶登錄(login.html 頁面6) 返回頂部
1、功能點&效果圖
2、相關code

############################# 下面這一塊用來做用戶登錄注銷功能 ##################################### def acc_login(request): errors = {} if request.method == 'POST': _email = request.POST.get('email') _password = request.POST.get('password') user = authenticate(username= _email, password=_password) #通過驗證會返回一個user對象 if user: login(request,user) #Django自動登錄,然后創建session next_url = request.GET.get("next","/kind_admin/") #登錄成功后會跳轉的頁面,沒有next時是/kind_admin/ #未登錄時直接輸入url時跳轉到登錄界面是會加上"next"參數 return redirect(next_url) else: errors['error'] = "Wrong username or password!" return render(request,'kind_admin/login.html',{'errors':errors}) def acc_logout(request): logout(request) return redirect("/kind_admin/login/")

LOGIN_URL = '/kind_admin/login/' # 告訴Django注銷后挑傳到那個路徑

{% extends 'kind_admin/base.html' %} {% block body %} <div class="container col-lg-offset-4"> <form class="form-signin col-md-4" method="post" action=""> <h2 class="form-signin-heading">My CRM </h2> <label for="inputEmail" class="sr-only">Email address</label> <input name="email" type="email" id="inputEmail" class="form-control" placeholder="Email address" required="" autofocus=""> <label for="inputPassword" class="sr-only">Password</label> <input name="password" type="password" id="inputPassword" class="form-control" placeholder="Password" required=""> {% if errors %} <span style="color: red">{{ errors.error }}</span> {% endif %} <div class="checkbox"> <label> <input type="checkbox" value="remember-me"> Remember me </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">Login</button> </form> </div> <!-- /container --> {% endblock %}
1.10 添加用戶(rewrite_add_user_page.html) 返回頂部
說明:由於上面的table_obj_add.html是通用的表管理、這種有密碼的用戶注冊專門寫了一個頁面

from kind_admin.register_admin import BaseAdmin from kind_admin.register_admin import register from kind_admin.register_admin import enabled_admins from django.shortcuts import render,HttpResponse,redirect from crm import models class UserProfileAdmin(BaseAdmin): using_add_func = False def rewrite_add_page(self,request,app_name,table_name,model_form_class): errors = {} if request.method == 'POST': _password1 = request.POST.get("password") _password2 = request.POST.get("password2") if _password1 == _password2: if len(_password2) > 5: form_obj = model_form_class(request.POST) if form_obj.is_valid(): obj = form_obj.save() print('obj',obj,type(obj)) obj.set_password(_password1) # obj.save() return redirect(request.path.replace("/add/", '/')) else: errors['password_too_short'] = "muset not less than 6 letters" else: errors['invalid_password'] = "passwords are not the same" form_obj = model_form_class() return render(request, 'kind_admin/rewrite_add_user_page.html', {'form_obj': form_obj, 'app_name': app_name, 'table_name': table_name, 'table_name_detail': self.model._meta.verbose_name_plural, 'admin_class': self, 'errors': errors, })

class UserProfileAdmin(BaseAdmin): using_add_func = False

{% extends 'kind_admin/kind_admin_index.html' %} {% load tags %} {% block css %} <style> .filter-select-box{ height: 300px!important; width: 100%; border-radius: 5px; } </style> {% endblock %} {% block url_block %} <span> > </span> <a href="/kind_admin/" style="color: white;font-weight: bold">{{ app_name }}</a> <span> > </span> <a href="/kind_admin/{{ app_name }}/{{ table_name }}/" style="color: white;font-weight: bold">{{ table_name_detail }}</a> <span> > change</span> {% endblock %} {% block container %} <div style="margin-bottom: 200px"> <span style="color: red;"> {{ form_obj.errors }} </span> <form class="form-horizontal" method="POST" onsubmit="return SelectAllChosenData();"> {% for field in form_obj %} <div class="form-group"> <label for="inputEmail3" class="col-sm-2 control-label" style="font-weight: normal"> {% if field.field.required %} <span style="font-weight: bold;color: gold">{{ field.label }}</span> {% else %} {{ field.label }} {% endif %} </label> <div class="col-sm-10"> {% if field.name in admin_class.filter_horizontal %} <div class="col-md-5" > {% get_m2m_obj_list admin_class field form_obj as m2m_obj_list %} <select multiple class="filter-select-box" id="id_{{ field.name }}_from"> {% if field.name in admin_class.readonly_fields and not admin_class.is_add_form %} {% for obj in m2m_obj_list %} <!-- 如果多對多是readonly字段就不綁定點擊事件 --> <option value="{{ obj.id }}" disabled>{{ obj }}</option> {% endfor %} {% else %} {% for obj in m2m_obj_list %} <option ondblclick="MoveElementTo(this,'id_{{ field.name }}_to','id_{{ field.name }}_from');" value="{{ obj.id }}">{{ obj }}</option> {% endfor %} {% endif %} </select> </div> <div class="col-md-1"> 箭頭 </div> <div class="col-md-5"> <!-- 多對多字段顯示已經選中的 --> {% get_m2m_selected_obj_list form_obj field as selected_obj_list %} <select multiple class="filter-select-box" id="id_{{ field.name }}_to" tag="chosen_list" name="{{ field.name }}"> {% if field.name in admin_class.readonly_fields %} {% for obj in selected_obj_list %} <!-- 如果多對多是readonly字段就不綁定點擊事件 --> <option disabled value="{{ obj.id }}">{{ obj }}</option> {% endfor %} {% else %} {% for obj in selected_obj_list %} <option ondblclick="MoveElementTo(this,'id_{{ field.name }}_from','id_{{ field.name }}_to');" value="{{ obj.id }}">{{ obj }}</option> {% endfor %} {% endif %} {{ field.error }} </select> </div> <span style="color: red">{{ field.errors.as_text }}</span> {% else %} {% if field.name == 'password' %} <div class="form-group"> <div class="col-sm-12"> <input placeholder="輸入密碼" type="password" name="password" required="" id="id_password" class="form-control" maxlength="128"> <span style="color: red">{{ field.errors.as_text }}</span> </div> </div> <div class="form-group"> <div class="col-sm-12"> <input placeholder="重復密碼" type="password" name="password2" required="" id="id_password" class="form-control" maxlength="128"> <span style="color: red"></span> </div> </div> {% else %} {{ field }} {# 非多對多字段復選框顯示 #} <span style="color: gray">{{ field.help_text }}</span> <span style="color: red">{{ field.errors.as_text }}</span> {% endif %} {% endif %} </div> </div> {% endfor %} {% if not admin_class.readonly_table %} <div style="margin-top: 50px;margin-left: 200px"> <button type="submit" class="btn btn-success pull-right">Save</button> </div> {% endif %} <div> <ul> {% for k,v in errors.items %} <li style="color: red;">{{ k }}:{{ v }}</li> </ul> {% endfor %} </div> </form> </div> {% endblock %} {% block js %} <script> function SelectAllChosenData() { $("select[tag='chosen_list'] option").each(function () { $(this).prop('selected',true); // 提交表單前將右邊復選框內容全部選中 }); //remove all disabled attrs var tag = $('form').find("[disabled]").removeAttr('disabled'); return true } function MoveElementTo(ele,target_id,new_target_id) { var opt_ele = "<option ondblclick='MoveElementTo(this," + '"' + new_target_id + '",' + '"' + target_id + '",' + ")' value=" + $(ele).val() + ">" + $(ele).text() + "</option>"; $("#" + target_id).append(opt_ele); $(ele).remove(); } </script> {% endblock %}
1.11 用戶重置密碼 返回頂部
1、功能點&效果圖
2、相關code

class UserProfile(AbstractBaseUser,PermissionsMixin): password = models.CharField(_('password'), max_length=128,help_text=mark_safe('''<a href='password/'>修改密碼</a>'''))

def password_reset(request,app_name,table_name,obj_id): admin_class = kind_admin.enabled_admins[app_name][table_name] model_form_class = create_model_form(request,admin_class) obj = admin_class.model.objects.get(id=obj_id) errors = {} if request.method == 'POST': _password1 = request.POST.get("password1") _password2 = request.POST.get("password2") if _password1 == _password2: if len(_password2) >5: print('obj reset',obj,type(obj)) obj.set_password(_password1) # obj.save() return redirect(request.path.rstrip('password/')) else: errors['password_too_short'] = "muset not less than 6 letters" else: errors['invalid_password'] = "passwords are not the same" return render(request,'kind_admin/password_reset.html',{'obj':obj, 'errors':errors, 'app_name':app_name, 'table_name':table_name, 'table_name_detail': admin_class.model._meta.verbose_name_plural, 'obj_id':obj_id })

{% extends 'kind_admin/kind_admin_index.html' %} {% load tags %} {% block url_block %} <span> > </span> <a href="/kind_admin/" style="color: white;font-weight: bold">{{ app_name }}</a> <span> > </span> <a href="/kind_admin/{{ app_name }}/{{ table_name }}/" style="color: white;font-weight: bold">{{ table_name_detail }}</a> <span> > </span> <a href="/kind_admin/{{ app_name }}/{{ table_name }}/{{ obj_id }}/change" style="color: white;font-weight: bold">change</a> <span> > 修改密碼</span> {% endblock %} {% block container %} <div style="width: 1000px;margin: 0 auto"> <h3>重置【{{ obj.name }}】密碼</h3> <div class="panel-body col-md-10"> <form method="post" class="form-horizontal" style="color: gold;font-weight: bolder;font-size: 16px"> <div class="form-group"> <label class="col-sm-2 control-label"> 用戶名: </label> <div class="col-sm-6"> <input class="form-control" type="text" name="name" value="{{ obj.email }}" disabled> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label"> 密碼: </label> <div class="col-sm-6"> <input class="form-control" type="password" name="password1"> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label"> 密碼(重復): </label> <div class="col-sm-6"> <input class="form-control" type="password" name="password2"> </div> </div> <div> <ul> {% for k,v in errors.items %} <li style="color: red;">{{ k }}:{{ v }}</li> </ul> {% endfor %} </div> <input type="submit" class="btn btn-info center-block" value="提交"> </form> </div> </div> {% endblock %}
1.12 實現用戶對各表增刪改查的權限管理 返回頂部
1、原理說明
1. 由於1.2中重寫了Django admin用戶認證,使用UserProfile表代替原有的的User表進行認證
2. Django中默認有一張auth_permission表,在這張表中可以對Django默認的表進行權限管理
3. 我們自定義的權限字典只要寫到任意表的Meta屬性里(必須與權限名相同),就可以顯示到auth_permisson表中管理
2、使用方法
1、在kind_admin\permissions\permission_list.py文件中定義權限字典
2、在任意表的Meta中定義權限與權限名對應關系
3、在kind_admin\permissions\permission.py文件中定義裝飾器,並在裝飾器函數中調用perm_check方法判斷權限
在perm_check方法中獲取request.path和request.method,是否在權限字典中
如果權限字典中有這個條目用request.user.has_perm(permission_name) #判斷當前用戶是否有這個權限
4、在kind_admin/views.py中需要權限認證的視圖函數都使用這個裝飾器裝飾即可實現表的權限管理

#url type: 0 = related, (代表是相對路徑) #url type: 1 = absolute(代表是絕對路徑) # 用戶與權限如何關聯 #1. 將perm_dic里的權限名,寫到任意表的Meta屬性里(必須與權限名相同) #2. 然后執行 python manage.py makemigrations 命令更新數據庫中的表 #3. 更新完成后就可以在我們重寫的Django Admin的User表(UserProfile表)中看到可選權限了 perm_dic = { 'crm.can_access_userprofile_list': { #可以看到UserProfile表條目列表 'url_type': 1, # 標識url為絕對路徑 'url': '/kind_admin/crm/userprofile/', # url路徑(相對路徑就是路徑別名) 'method': 'GET', 'args': [], # 接收參數 # 'args': ['enroll_id','step'] }, 'crm.can_add_userprofile_get': { #可以看到UserProfile表的添加頁面 'url_type': 0, #0代表使用相對路徑名 'url': 'table_obj_add', # url路徑(相對路徑就是路徑別名) 'method': 'GET', 'args': [], # 接收參數 }, 'crm.can_add_userprofile_post': { #可以真正添加UserProfile表條目 'url_type': 0, 'url': 'table_obj_add', 'method': 'POST', 'args': [], }, 'crm.can_change_userprofile_get': { 'url_type': 0, 'url': 'table_obj_change', 'method': 'GET', 'args': [], }, 'crm.can_change_userprofile_post': { 'url_type': 0, 'url': 'table_obj_change', 'method': 'POST', 'args': [], }, 'crm.can_delete_userprofile_get': { 'url_type': 0, 'url': 'table_obj_delete', 'method': 'GET', 'args': [], }, 'crm.can_delete_userprofile_post': { 'url_type': 0, 'url': 'table_obj_delete', 'method': 'POST', 'args': [], }, }

class UserProfile(AbstractBaseUser,PermissionsMixin): class Meta: verbose_name_plural = '用戶表' # 這里是定義權限管理組件 permissions = ( ('can_access_userprofile_list','可以訪問用戶表'), ('can_add_userprofile_get','可以訪問添加用戶表界面'), ('can_add_userprofile_post','可以添加用戶表記錄'), ('can_change_userprofile_get','可以訪問修改用戶表記錄'), ('can_change_userprofile_post','可以正真修改用戶表記錄'), ('can_delete_userprofile_get', '可以訪問刪除用戶表記錄'), ('can_delete_userprofile_post','可以正真刪除用戶表記錄'), )

from django.shortcuts import HttpResponse,render,redirect from kind_admin.permissions import permission_list #定義權限 from django.core.urlresolvers import resolve #將絕對url轉成相對url def perm_check(*args,**kwargs): '''被裝飾器中調用的函數,用來做權限檢測的邏輯''' request = args[0] if request.user.is_authenticated(): #判斷用戶是否登錄 for permission_name, v in permission_list.perm_dic.items(): url_matched = False if v['url_type'] == 1: # v['url_type'] == 1 代表是絕對路徑 if v['url'] == request.path: #絕對url匹配上 url_matched = True else: # 否則匹配的就是相對路徑 #把request.path中的絕對url請求轉成相對url名字 resolve_url_obj = resolve(request.path) if resolve_url_obj.url_name == v['url']: #相對url別名匹配上了 url_matched = True if url_matched: #如果url路徑匹配上了才會走這步 if v['method'] == request.method: #請求方法也匹配上了(如:POST/GET) arg_matched = True for request_arg in v['args']: #當某些url中要求必須有某些參數,判斷這些必須參數沒有為空的 request_method_func = getattr(request,v['method']) #獲取: request.POST 或 request.GET方法 if not request_method_func.get(request_arg): #只要有一個參數沒有數據就返回FALSE arg_matched = False if arg_matched: #走到這里,僅僅代表這個請求和這條權限的定義規則 匹配上了 if request.user.has_perm(permission_name): #判斷當前用戶是否有這個權限 #能走到這里代表:有權限 return True else: #用戶未登錄返回到登錄界面 return redirect("/account/login/") def check_permisssion(func): '''檢測權限的裝飾器''' def inner(*args,**kwargs): # print("---permissson:",*args,**kwargs) perm_check(*args,**kwargs) print('perm_check(*args,**kwargs)',perm_check(*args,**kwargs)) if perm_check(*args,**kwargs) is True: #如果返回True代表有權限 return func(*args,**kwargs) else: return HttpResponse('沒權限') return func(*args,**kwargs) return inner

# 導入我們自己寫的 通用權限管理組件 from kind_admin.permissions import permission @permission.check_permisssion def display_table_obj(request,app_name,table_name): pass @permission.check_permisssion def table_obj_add(request,app_name,table_name): pass @permission.check_permisssion def table_obj_change(request,app_name,table_name,obj_id): pass @permission.check_permisssion def table_obj_delete(request,app_name,table_name,obj_id): pass

C:\Users\tom\Desktop\rewriteDjangoAdmin>python manage.py shell >>> from crm import models >>> tom = models.UserProfile.objects.get(name='tom') >>> tom.has_perm('crm.can_access_userprofile_list') True