01: 重写Django admin
目录:
- 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
作者:学无止境
出处:https://www.cnblogs.com/xiaonq
生活不只是眼前的苟且,还有诗和远方。