西游之路——python全栈——CRM用户关系管理

 

 

 

 

Day1:项目分析

  一:需求分析

二:CRM角色功能介绍

 

三:业务场景分析

复制代码
销售:
    1.销售A 从百度推广获取了一个客户,录入了CRM系统,咨询了Python课程,但是没有报名
    2.销售B 从qq群获取一个客户,成功使他报名Python班,然后给他发送了报名连接,等待用户填写完毕后,将他添加到Python具体的学习班级中
    3.销售C 打电话给之前的一个客户,说服他报名Python课程,但是没有成功,更新了跟踪记录
    4.销售D 获取了一个客户,录入信息时,发现此客户已经存在,不允许重复录入,随后通知相应的原负责人跟进
    5.销售E 从客户库中获取了,超过一个月未跟进的客户,进行再次跟进
    6.销售主管 查看了部门本月的销售报表,包括来源分析,成单率分析,班级报名数量分析,销售额环比,同比
复制代码
复制代码
学员:
    1.客户A 填写了销售发来的报名连接,上传了个人的证件信息,提交,之后收到邮件,告知报名成功,并为他开通了学员账号,升级为学员A
    2.学员A 登录学员系统,看到自己的合同,报名的班级,课程大纲
    3.学员A 提交了Python课程当时课时作业
    4.学员A 查看自己的Python课程成绩,排名
    5.学员A 搜索问题,未找到答案,录入一条问题
    6.学员A 转介绍学员,录入其信息
复制代码
复制代码
讲师:
    1.讲师A 登录CRM系统,查看自己管理的班级列表
    2.讲师A 进入Python 5期课程,创建第3节的上课记录,填写了本节课内容,作业要求
    3.讲师A 在课程中点名,对点名情况进行录入,标记相关状态
    4.讲师A 批量下载所有学员的课时作业,给每个学员在线批注了成绩+状态
    
复制代码
复制代码
管理员:
    1.创建课程 C++,Python..
    2.创建校区 上海,北京..
    3.创建班级 C++35期,Python27期
    4.创建账号 ABCD
    5.创建了销售,讲师,学员角色
    6.为账号分配到对应的角色,将ABCD分配给销售
    7.创建相关权限
    8.为销售角色分配了相关权限
复制代码

四:表结构设计

  数据库关联模型(待完善)

 

Django表结构实现

  1 from django.contrib.auth.models import User
  2 from django.utils.translation import ugettext_lazy as _  # 语言国际化
  3 # from django.utils import timezone   # 开发国际软件时
  4 # 395     date_joined = models.DateTimeField( verbose_name="创建时间", default=timezone.now )
  5 
  6 from django.utils.safestring import mark_safe
  7 from django.db import models
  8 from django.contrib.auth.models import (
  9     BaseUserManager, AbstractBaseUser,PermissionsMixin
 10 )
 11 
 12 class UserProFileManager(BaseUserManager):
 13     def create_user(self, email, name, password=None):
 14         """
 15         Creates and saves a User with the given email, name and password.
 16         """
 17         if not email:
 18             raise ValueError('Users must have an email address')
 19 
 20         user = self.model(
 21             email=self.normalize_email(email),  #验证邮箱格式
 22             name=name,
 23         )
 24 
 25         user.set_password(password)  #加密
 26         user.save(using=self._db)
 27         return user
 28 
 29     def create_superuser(self, email, name, password):
 30         """
 31         Creates and saves a superuser with the given email, name and password.
 32         """
 33         user = self.create_user(
 34             email,
 35             password=password,
 36             name=name,
 37         )
 38         user.is_superuser = True
 39         user.save(using=self._db)
 40         return user
 41 
 42 class UserProFile(AbstractBaseUser,PermissionsMixin):
 43     """
 44     用户信息表
 45         含有销售、讲师、管理员人员
 46     使用自定义用户认证
 47     """
 48     email = models.EmailField(
 49         verbose_name='email address',
 50         max_length=255,
 51         unique=True,
 52         blank=True,
 53         null=True
 54     )
 55     password = models.CharField(  #重写Admin密码,修改
 56         _('password'),
 57         max_length=128,
 58         help_text=mark_safe("<a class='btn-link'href='password'>重置密码</a>"),
 59     )
 60     name = models.CharField(max_length=32, verbose_name='姓名')
 61     role = models.ManyToManyField('Role', null=True, blank=True)
 62     is_active = models.BooleanField(default=True)
 63     is_staff = models.BooleanField(default=True)
 64     is_superuser = models.BooleanField(default=False)
 65 
 66     # ————————60PerfectCRM实现CRM学生上课记录————————
 67     # stu_account = models.ForeignKey("Customer",
 68     #                                 verbose_name='关联学员帐号',
 69     #                                 blank=True,
 70     #                                 null=True,
 71     #                                 on_delete=models.CASCADE,
 72     #                                 help_text = '报名成功后创建关联帐户' )
 73     # ————————60PerfectCRM实现CRM学生上课记录————————
 74 
 75     objects = UserProFileManager()
 76 
 77     USERNAME_FIELD = 'email'
 78     REQUIRED_FIELDS = ['name']
 79 
 80     def get_full_name(self):
 81         # The user is identified by their email address
 82         return self.email
 83 
 84     def get_short_name(self):
 85         # The user is identified by their email address
 86         return self.email
 87 
 88     def __str__(self):              # __unicode__ on Python 2
 89         return self.email
 90 
 91     class Meta:
 92         # verbose_name = 'CRM账户'
 93         verbose_name_plural = 'CRM账户'
 94         permissions = (
 95             ('crm_table_list', '可以查看kingadmin所有表的数据'),
 96             ('crm_table_list_view', '可以查看kingadmin所有表里数据的修改页'),
 97             ('crm_table_list_change', '可以修改kingadmin所有表数据'),
 98             ('crm_table_list_add_view', '可以查看kingadmin所有表添加页'),
 99             ('crm_table_list_add', '可以在kingadmin所有表添加数据'),
100             ('crm_personal_password_reset_view', '可以在kingadmin查看自己的秘密修改页'),
101             ('crm_personal_password_reset', '可以在kingadmin修改自己的密码'),
102         )
103 
104 # ————————74PerfectCRM实现CRM权限和权限组限制URL————————
105 """
106     class Meta:
107         verbose_name_plural = '10CRM账户表'
108         permissions = (
109             ('crm_010101_all_table_data_list_GET', '010101_全部查看数据_GET'),
110             ('crm_010102_all_table_data_list_POST', '010102_全部查看数据_POST'),
111             ('crm_010103_all_table_add_GET', '010103_全部添加数据_GET'),
112             ('crm_010104_all_table_add_POST', '010104_全部添加数据_POST'),
113             ('crm_010105_all_table_change_GET', '010105_全部修改数据_GET'),
114             ('crm_010106_all_table_change_POST', '010106_全部修改数据_POST'),
115             ('crm_010107_all_table_delete_GET', '010107_全部删除数据_GET'),
116             ('crm_010108_all_table_delete_POST', '010108_全部删除数据_POST'),
117             ('crm_010109_all_password_reset_GET', '010109_全部密码重置_GET'),
118             ('crm_010110_all_password_reset_POST', '010110_全部密码重置_POST'),
119 
120             ('crm_010201_only_view_Branch_GET', '010201_只能查看校区表_GET'),
121             ('crm_010202_only_view_Branch_POST', '010202_只能查看校区表_POST'),
122             ('crm_010203_only_add_Branch_GET', '010203_只能添加校区表_GET'),
123             ('crm_010204_only_add_Branch_POST', '010204_只能添加校区表_POST'),
124             ('crm_010205_only_change_Branch_GET', '010205_只能修改校区表_GET'),
125             ('crm_010206_only_change_Branch_POST', '010206_只能修改校区表_POST'),
126             ('crm_010207_only_delete_Branch_GET', '010207_只能删除校区表_GET'),
127             ('crm_010208_only_delete_Branch_POST', '010208_只能删除校区表_POST'),
128 
129             ('crm_010301_only_view_ClassList_GET', '010301_只能查看班级表_GET'),
130             ('crm_010302_only_view_ClassList_POST', '010302_只能查看班级表_POST'),
131             ('crm_010303_only_add_ClassList_GET', '010303_只能添加班级表_GET'),
132             ('crm_010304_only_add_ClassList_POST', '010304_只能添加班级表_POST'),
133             ('crm_010305_only_change_ClassList_GET', '010305_只能修改班级表_GET'),
134             ('crm_010306_only_change_ClassList_POST', '010306_只能修改班级表_POST'),
135             ('crm_010307_only_delete_ClassList_GET', '010307_只能删除班级表_GET'),
136             ('crm_010308_only_delete_ClassList_POST', '010308_只能删除班级表_POST'),
137 
138             ('crm_010401_only_view_Course_GET', '010401_只能查看课程表_GET'),
139             ('crm_010402_only_view_Course_POST', '010402_只能查看课程表_POST'),
140             ('crm_010403_only_add_Course_GET', '010403_只能添加课程表_GET'),
141             ('crm_010404_only_add_Course_POST', '010404_只能添加课程表_POST'),
142             ('crm_010405_only_change_Course_GET', '010405_只能修改课程表_GET'),
143             ('crm_010406_only_change_Course_POST', '010406_只能修改课程表_POST'),
144             ('crm_010407_only_delete_Course_GET', '010407_只能删除课程表_GET'),
145             ('crm_010408_only_delete_Course_POST', '010408_只能删除课程表_POST'),
146 
147             ('crm_010501_only_view_Customer_GET', '010501_只能查看客户表_GET'),
148             ('crm_010502_only_view_Customer_POST', '010502_只能查看客户表_POST'),
149             ('crm_010503_only_add_Customer_GET', '010503_只能添加客户表_GET'),
150             ('crm_010504_only_add_Customer_POST', '010504_只能添加客户表_POST'),
151             ('crm_010505_only_change_Customer_GET', '010505_只能修改客户表_GET'),
152             ('crm_010506_only_change_Customer_POST', '010506_只能修改客户表_POST'),
153             ('crm_010507_only_delete_Customer_GET', '010507_只能删除客户表_GET'),
154             ('crm_010508_only_delete_Customer_POST', '010508_只能删除客户表_POST'),
155 
156             ('crm_010601_only_view_CustomerFollowUp_GET', '010601_只能查看跟进表_GET'),
157             ('crm_010602_only_view_CustomerFollowUp_POST', '010602_只能查看跟进表_POST'),
158             ('crm_010603_only_add_CustomerFollowUp_GET', '010603_只能添加跟进表_GET'),
159             ('crm_010604_only_add_CustomerFollowUp_POST', '010604_只能添加跟进表_POST'),
160             ('crm_010605_only_change_CustomerFollowUp_GET', '010605_只能修改跟进表_GET'),
161             ('crm_010606_only_change_CustomerFollowUp_POST', '010606_只能修改跟进表_POST'),
162             ('crm_010607_only_delete_CustomerFollowUp_GET', '010607_只能删除跟进表_GET'),
163             ('crm_010608_only_delete_CustomerFollowUp_POST', '010608_只能删除跟进表_POST'),
164 
165             ('crm_010701_only_view_Enrollment_GET', '010701_只能查看报名表_GET'),
166             ('crm_010702_only_view_Enrollment_POST', '010702_只能查看报名表_POST'),
167             ('crm_010703_only_add_Enrollment_GET', '010703_只能添加报名表_GET'),
168             ('crm_010704_only_add_Enrollment_POST', '010704_只能添加报名表_POST'),
169             ('crm_010705_only_change_Enrollment_GET', '010705_只能修改报名表_GET'),
170             ('crm_010706_only_change_Enrollment_POST', '010706_只能修改报名表_POST'),
171             ('crm_010707_only_delete_Enrollment_GET', '010707_只能删除报名表_GET'),
172             ('crm_010708_only_delete_Enrollment_POST', '010708_只能删除报名表_POST'),
173 
174             ('crm_010801_only_view_Payment_GET', '010801_只能查看缴费表_GET'),
175             ('crm_010802_only_view_Payment_POST', '010802_只能查看缴费表_POST'),
176             ('crm_010803_only_add_Payment_GET', '010803_只能添加缴费表_GET'),
177             ('crm_010804_only_add_Payment_POST', '010804_只能添加缴费表_POST'),
178             ('crm_010805_only_change_Payment_GET', '010805_只能修改缴费表_GET'),
179             ('crm_010806_only_change_Payment_POST', '010806_只能修改缴费表_POST'),
180             ('crm_010807_only_delete_Payment_GET', '010807_只能删除缴费表_GET'),
181             ('crm_010808_only_delete_Payment_POST', '010808_只能删除缴费表_POST'),
182 
183             ('crm_010901_only_view_CourseRecord_GET', '010901_只能查看上课表_GET'),
184             ('crm_010902_only_view_CourseRecord_POST', '010902_只能查看上课表_POST'),
185             ('crm_010903_only_add_CourseRecord_GET', '010903_只能添加上课表_GET'),
186             ('crm_010904_only_add_CourseRecord_POST', '010904_只能添加上课表_POST'),
187             ('crm_010905_only_change_CourseRecord_GET', '010905_只能修改上课表_GET'),
188             ('crm_010906_only_change_CourseRecord_POST', '010906_只能修改上课表_POST'),
189             ('crm_010907_only_delete_CourseRecord_GET', '010907_只能删除上课表_GET'),
190             ('crm_010908_only_delete_CourseRecord_POST', '010908_只能删除上课表_POST'),
191 
192             ('crm_011001_only_view_StudyRecord_GET', '011001_只能查看学习表_GET'),
193             ('crm_011002_only_view_StudyRecord_POST', '011002_只能查看学习表_POST'),
194             ('crm_011003_only_add_StudyRecord_GET', '011003_只能添加学习表_GET'),
195             ('crm_011004_only_add_StudyRecord_POST', '011004_只能添加学习表_POST'),
196             ('crm_011005_only_change_StudyRecord_GET', '011005_只能修改学习表_GET'),
197             ('crm_011006_only_change_StudyRecord_POST', '011006_只能修改学习表_POST'),
198             ('crm_011007_only_delete_StudyRecord_GET', '011007_只能删除学习表_GET'),
199             ('crm_011008_only_delete_StudyRecord_POST', '011008_只能删除学习表_POST'),
200 
201             ('crm_011101_only_view_UserProfile_GET', '011101_只能查看账号表_GET'),
202             ('crm_011102_only_view_UserProfile_POST', '011102_只能查看账号表_POST'),
203             ('crm_011103_only_add_UserProfile_GET', '011103_只能添加账号表_GET'),
204             ('crm_011104_only_add_UserProfile_POST', '011104_只能添加账号表_POST'),
205             ('crm_011105_only_change_UserProfile_GET', '011105_只能修改账号表_GET'),
206             ('crm_011106_only_change_UserProfile_POST', '011106_只能修改账号表_POST'),
207             ('crm_011107_only_delete_UserProfile_GET', '011107_只能删除账号表_GET'),
208             ('crm_011108_only_delete_UserProfile_POST', '011108_只能删除账号表_POST'),
209 
210             ('crm_011201_only_view_Role_GET', '011201_只能查看角色表_GET'),
211             ('crm_011202_only_view_Role_POST', '011202_只能查看角色表_POST'),
212             ('crm_011203_only_add_Role_GET', '011203_只能添加角色表_GET'),
213             ('crm_011204_only_add_Role_POST', '011204_只能添加角色表_POST'),
214             ('crm_011205_only_change_Role_GET', '011205_只能修改角色表_GET'),
215             ('crm_011206_only_change_Role_POST', '011206_只能修改角色表_POST'),
216             ('crm_011207_only_delete_Role_GET', '011207_只能删除角色表_GET'),
217             ('crm_011208_only_delete_Role_POST', '011208_只能删除角色表_POST'),
218 
219             ('crm_011301_only_view_Tag_GET', '011301_只能查看标签表_GET'),
220             ('crm_011302_only_view_Tag_POST', '011302_只能查看标签表_POST'),
221             ('crm_011303_only_add_Tag_GET', '011303_只能添加标签表_GET'),
222             ('crm_011304_only_add_Tag_POST', '011304_只能添加标签表_POST'),
223             ('crm_011305_only_change_Tag_GET', '011305_只能修改标签表_GET'),
224             ('crm_011306_only_change_Tag_POST', '011306_只能修改标签表_POST'),
225             ('crm_011307_only_delete_Tag_GET', '011307_只能删除标签表_GET'),
226             ('crm_011308_only_delete_Tag_POST', '011308_只能删除标签表_POST'),
227 
228             ('crm_011401_only_view_FirstLayerMenu_GET', '011401_只能查看一层菜单_GET'),
229             ('crm_011402_only_view_FirstLayerMenu_POST', '011402_只能查看一层菜单_POST'),
230             ('crm_011403_only_add_FirstLayerMenu_GET', '011403_只能添加一层菜单_GET'),
231             ('crm_011404_only_add_FirstLayerMenu_POST', '011404_只能添加一层菜单_POST'),
232             ('crm_011405_only_change_FirstLayerMenu_GET', '011405_只能修改一层菜单_GET'),
233             ('crm_011406_only_change_FirstLayerMenu_POST', '011406_只能修改一层菜单_POST'),
234             ('crm_011407_only_delete_FirstLayerMenu_GET', '011407_只能删除一层菜单_GET'),
235             ('crm_011408_only_delete_FirstLayerMenu_POST', '011408_只能删除一层菜单_POST'),
236 
237             ('crm_011501_only_view_SubMenu_GET', '011501_只能查看二层菜单_GET'),
238             ('crm_011502_only_view_SubMenu_POST', '011502_只能查看二层菜单_POST'),
239             ('crm_011503_only_add_SubMenu_GET', '011503_只能添加二层菜单_GET'),
240             ('crm_011504_only_add_SubMenu_POST', '011504_只能添加二层菜单_POST'),
241             ('crm_011505_only_change_SubMenu_GET', '011505_只能修改二层菜单_GET'),
242             ('crm_011506_only_change_SubMenu_POST', '011506_只能修改二层菜单_POST'),
243             ('crm_011507_only_delete_SubMenu_GET', '011507_只能删除二层菜单_GET'),
244             ('crm_011508_only_delete_SubMenu_POST', '011508_只能删除二层菜单_POST'),
245 
246             ('crm_011601_only_view_Groups_GET', '011601_只能查看权限组_GET'),
247             ('crm_011602_only_view_Groups_POST', '011602_只能查看权限组_POST'),
248             ('crm_011603_only_add_Groups_GET', '011603_只能添加权限组_GET'),
249             ('crm_011604_only_add_Groups_POST', '011604_只能添加权限组_POST'),
250             ('crm_011605_only_change_Groups_GET', '011605_只能修改权限组_GET'),
251             ('crm_011606_only_change_Groups_POST', '011606_只能修改权限组_POST'),
252             ('crm_011607_only_delete_Groups_GET', '011607_只能删除权限组_GET'),
253             ('crm_011608_only_delete_Groups_POST', '011608_只能删除权限组_POST'),
254 
255             ('crm_011701_own_password_reset_GET', '011701_自己密码重置_GET'),
256             ('crm_011702_own_password_reset_POST', '011702_自己密码重置_POST'),
257 
258             ('crm_020101_all_not_audit_GET', '020101_销售查看全部的客户未审核_GET'),
259             ('crm_020103_all_enrollment_GET', '020103_销售给全部的客户报名课程_GET'),
260             ('crm_020104_all_enrollment_POST', '020104_销售给全部的客户报名课程_POST'),
261             ('crm_020105_all_contract_review_GET', '020105_销售给全部的客户审核合同_GET'),
262             ('crm_020116_all_contract_review_POST', '020116_销售给全部的客户审核合同_POST'),
263 
264             ('crm_020201_own_enrollment_GET', '020201_销售给自己的客户报名课程_GET'),
265             ('crm_020202_own_enrollment_POST', '020202_销售给自己的客户报名课程_POST'),
266             ('crm_020203_own_contract_review_GET', '020203_销售给自己的客户审核合同_GET'),
267             ('crm_020204_own_contract_review_POST', '020204_销售给自己的客户审核合同_POST'),
268 
269             ('crm_030101_all_not_payment_GET', '030101_财务查看全部的客户未缴费_GET'),
270             ('crm_030102_all_not_payment_POST', '030102_财务查看全部的客户未缴费_POST'),
271             ('crm_030103_all_already_payment_GET', '030103_财务查看全部的客户已缴费_GET'),
272             ('crm_030104_all_already_payment_POST', '030104_财务查看全部的客户已缴费_POST'),
273             ('crm_030105_all_payment_GET', '030105_财务进行全部的客户缴费_GET'),
274             ('crm_030106_all_payment_POST', '030106_财务进行全部的客户缴费_POST'),
275 
276             ('crm_040101_own_student_course_GET', '040101_学生查看自己的课程_GET'),
277             ('crm_040102_own_student_course_POST', '040102_学生查看自己的课程_POST'),
278             ('crm_040103_own_studyrecords_GET', '040103_学生自己的上课记录_GET'),
279             ('crm_040104_own_studyrecords_POST', '040104_学生自己的上课记录_POST'),
280             ('crm_040105_own_homework_detail_GET', '040105_学生自己的作业详情_GET'),
281             ('crm_040106_own_homework_detail_POST', '040106_学生自己的作业详情_POST'),
282 
283             ('crm_050101_own_teacher_class_GET', '050101_讲师查看自己的班级_GET'),
284             ('crm_050102_own_teacher_class_POST', '050102_讲师查看自己的班级_POST'),
285             ('crm_050103_own_teacher_class_detail_GET', '050103_讲师查看自己的课节详情_GET'),
286             ('crm_050104_own_teacher_class_detail_POST', '050104_讲师查看自己的课节详情_POST'),
287             ('crm_050105_own_teacher_lesson_detail_GET', '050105_讲师查看自己的课节学员_GET'),
288             ('crm_050106_own_teacher_lesson_detail_POST', '050106_讲师查看自己的课节学员_POST'),
289             ('crm_050107_own_howk_down_GET', '050107_讲师自己的学员作业下载_GET'),
290             ('crm_050108_own_howk_down_POST', '050108_讲师自己的学员作业下载_POST'),
291 
292             ('crm_060101_own_coursetop_details_GET', '060101_讲师查看自己的班级排名详情_GET'),
293             ('crm_060102_own_coursetop_details_POST', '060102_讲师查看自己的班级排名详情_POST'),
294             ('crm_060103_own_coursetop_score_GET', '060103_讲师查看自己的班级分数排行_GET'),
295             ('crm_060104_own_coursetop_score_POST', '060104_讲师查看自己的班级排分数排行_POST'),
296             ('crm_060105_own_coursetop_homework_GET', '060105_讲师查看自己的班级作业排行_GET'),
297             ('crm_060106_own_coursetop_homework_POST', '060106_讲师查看自己的班级作业排行_POST'),
298             ('crm_060107_own_coursetop_attendance_GET', '060107_讲师查看自己的班级出勤排行_GET'),
299             ('crm_060108_own_coursetop_attendance_POST', '060108_讲师查看自己的班级出勤排行_POST'),
300 
301         )
302 # ————————74PerfectCRM实现CRM权限和权限组限制URL————————
303 # ————————74PerfectCRM实现CRM权限和权限组限制URL————————
304 '''15权限组'''
305 from django.contrib.auth.models import Group
306 class Groups(Group):
307     class Meta:
308         verbose_name_plural = '15权限组'
309 # ————————74PerfectCRM实现CRM权限和权限组限制URL————————
310 # ————————75PerfectCRM实现CRM扩展权限————————
311 from django.contrib.auth.models import Permission
312 class Permissions(Permission):
313     dic_name = models.CharField(_('dic_name'), max_length=255)
314     class Meta:
315         verbose_name_plural = "16扩展权限"
316 # ————————75PerfectCRM实现CRM扩展权限————————
317 """
318 
319 
320 """自带验证"""
321 # class UserProFile(models.Model):
322 #     """用户信息表"""
323 #     user = models.OneToOneField(User,on_delete=models.CASCADE)
324 #     name = models.CharField(max_length=32, verbose_name='姓名')
325 #     role = models.ManyToManyField('Role',null=True,blank=True)
326 #
327 #     class Meta:
328 #         verbose_name = '用户信息表'
329 #         verbose_name_plural = '用户信息表'
330 #
331 #     def __str__(self):  # __unicode__
332 #         return self.name
333 
334 class Role(models.Model):
335     """角色表"""
336     name = models.CharField(max_length=64,unique=True)
337     menus = models.ManyToManyField('Menus', verbose_name='菜单',blank=True)
338 
339     class Meta:
340         verbose_name = '角色表'
341         verbose_name_plural = '角色表'
342 
343     def __str__(self):
344         return self.name
345 
346 class CustomerInfo(models.Model):
347     """客户信息"""
348     name = models.CharField(max_length=32,default=None)  #CharField定长文本
349     contact_type_choices = (
350         (0,'qq'),
351         (1,'微信'),
352         (2,'手机'),
353     )
354     contact_type = models.SmallIntegerField(choices=contact_type_choices,default=0)
355     contact = models.CharField(max_length=64,unique=True)
356     source_choices = (
357         (0,'QQ群'),
358         (1,'51CTO'),
359         (2,'百度推广'),
360         (3,'知乎'),
361         (4,'转介绍'),
362         (5,'其他'),
363     )
364     source = models.SmallIntegerField(choices=source_choices)
365     referral_from = models.ForeignKey('self',null=True,blank=True,verbose_name='转介绍',on_delete=models.CASCADE)
366 
367     consult_courses = models.ManyToManyField('Course',verbose_name='咨询课程')
368     consult_content = models.TextField(verbose_name='咨询内容')   #TextField无限制长度的文本
369     status_choices = (
370         (0,'未报名'),
371         (1,'已报名'),
372         (2,'已退学'),
373     )
374     status = models.SmallIntegerField(choices=status_choices)
375     consultant = models.ForeignKey('UserProFile',verbose_name='课程顾问',on_delete=models.CASCADE)
376     id_num = models.CharField(max_length=128,blank=True,null=True)
377     emergency_contact = models.PositiveIntegerField(blank=True,null=True)
378     sex_choice = (
379         (0,'Man'),
380         (1,'Woman'),
381     )
382     sex = models.SmallIntegerField(choices=sex_choice)
383     date = models.DateField(auto_now_add=True)
384 
385     class Meta:
386         verbose_name = '客户信息表'
387         verbose_name_plural = '客户信息表'
388 
389     def __str__(self):
390         return self.name
391 
392 class Student(models.Model):
393     """学员表(已报名的客户)"""
394     customer = models.OneToOneField('CustomerInfo',on_delete=models.CASCADE)
395     class_grade = models.ManyToManyField('ClassList')
396 
397     class Meta:
398         verbose_name = '学员表'
399         verbose_name_plural = '学员表'
400 
401     def __str__(self):
402         return self.customer.name
403 
404 class CustomerFollowUp(models.Model):
405     """客户跟踪记录表"""
406     customer = models.ForeignKey('CustomerInfo',on_delete=models.CASCADE)
407     content = models.TextField(verbose_name='跟踪内容')
408     user = models.ForeignKey('UserProFile',verbose_name='跟进人',on_delete=models.CASCADE)
409     status_choices = (
410         (0,'近期无报名计划'),
411         (1,'一个月内报名'),
412         (2,'2周内报名'),
413         (3,'已报名'),
414     )
415     status = models.SmallIntegerField(choices=status_choices)
416     date = models.DateField(auto_now_add=True)
417 
418     class Meta:
419         verbose_name = '客户跟踪记录表'
420         verbose_name_plural = '客户跟踪记录表'
421 
422     def __str__(self):
423         return self.content
424 
425 class Course(models.Model):
426     """课程表"""
427     name = models.CharField(verbose_name='课程名称',max_length=64,unique=True)
428     price = models.PositiveSmallIntegerField()   # 必须为正
429     period = models.PositiveSmallIntegerField(verbose_name='课程周期(月)',default=5)
430     outline = models.TextField(verbose_name='大纲')
431 
432     class Meta:
433         verbose_name = '课程表'
434         verbose_name_plural = '课程表'
435 
436     def __str__(self):
437         return self.name
438 
439 class ClassList(models.Model):
440     """班级列表"""
441     branch = models.ForeignKey('Branch',on_delete=models.CASCADE)
442     # CASCADE从父表删除或更新且自动删除或更新子表中匹配的行
443     course = models.ForeignKey('Course',on_delete=models.CASCADE)
444     contract = models.ForeignKey('ContractTemplate',blank=True,null=True,on_delete=models.CASCADE)
445     class_type_choices = (  #choices是Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
446         (0,'脱产'),
447         (1,'周末'),
448         (2,'网络班')
449     )
450     class_type = models.SmallIntegerField(choices=class_type_choices)
451     semester = models.SmallIntegerField(verbose_name='学期')
452     teachers = models.ManyToManyField('UserProFile',verbose_name='讲师')
453     start_date = models.DateField('开班日期')
454     graduate_date = models.DateField('毕业日期',blank=True,null=True)
455 
456     class Meta:
457         verbose_name = '班级列表'
458         verbose_name_plural = '班级列表'
459         unique_together = (
460             'course',
461             'semester',
462             'branch',
463             'class_type',
464         )
465 
466     def __str__(self):
467         return '%s(%s)期' %(self.course.name, self.semester)
468 
469 class CourseRecord(models.Model):
470     """上课记录"""
471     class_grade = models.ForeignKey('ClassList',verbose_name='上课班级',on_delete=models.CASCADE)
472     day_num = models.PositiveSmallIntegerField('课程节次')
473     teacher = models.ForeignKey('UserProFile',on_delete=models.CASCADE)
474     title = models.CharField('本节主题',max_length=64)
475     content = models.TextField('本节内容')
476     has_homework = models.BooleanField('本节有作业',default=True)
477     homework = models.TextField('作业需求',blank=True,null=True)
478     date = models.DateTimeField(auto_now_add=True)
479 
480     class Meta:
481         verbose_name = '上课记录'
482         verbose_name_plural = '上课记录'
483         unique_together = (
484             'class_grade',
485             'day_num',
486         )
487 
488     def __str__(self):
489         return '%s第(%s)节' %(self.class_grade, self.day_num)
490 
491 class StudyRecord(models.Model):
492     """学习记录"""
493     course_record = models.ForeignKey('CourseRecord',on_delete=models.CASCADE)
494     student = models.ForeignKey('Student',on_delete=models.CASCADE)
495 
496     score_choices = (
497         (100,'A+'),
498         (90,'A'),
499         (85,'B+'),
500         (80,'B'),
501         (75,'B-'),
502         (70,'C+'),
503         (60,'C'),
504         (40,'C-'),
505         (-50,'D'),
506         (0,'N/A'), # not avaliable
507         (-100,'COPY'),
508     )
509     score = models.SmallIntegerField(verbose_name='成绩',choices=score_choices,default=0)
510     show_choices = (
511         (0,'缺勤'),
512         (1,'已签到'),
513         (2,'迟到'),
514         (3,'早退'),
515     )
516     show_status = models.SmallIntegerField(choices=show_choices)
517     note = models.TextField('成绩备注',blank=True,null=True)
518     date = models.DateTimeField(auto_now_add=True)
519 
520     # ————————63PerfectCRM实现CRM讲师下载作业————————
521     # delivery = models.BooleanField(default=False, verbose_name="交作业")  # 有没有交付作业
522     # ————————63PerfectCRM实现CRM讲师下载作业————————
523     # ————————61PerfectCRM实现CRM学生上传作业————————
524     #  作业链接 #TextField无限制长度的文本#Django可空#数据库可以为空
525     # homework_link = models.TextField(blank=True, null=True)
526     # ————————61PerfectCRM实现CRM学生上传作业————————
527 
528     class Meta:
529         verbose_name = '学习记录'
530         verbose_name_plural = '学习记录'
531 
532     def __str__(self):
533         return '%s %s %s'  %(self.course_record,self.student,self.score)
534 
535 class Branch(models.Model):
536     """校区"""
537     name = models.CharField(max_length=64,unique=True)
538     addr = models.CharField(max_length=128,blank=True,null=True)
539 
540     class Meta:  #通过一个内嵌类 "class Meta" 给你的 model 定义元数据
541         verbose_name = '校区'
542         verbose_name_plural = '校区'  #verbose_name_plural给你的模型类起一个更可读的名字
543 
544     # __str__()是Python的一个“魔幻”方法,这个方法定义了当object调用str()时应该返回的值
545     def __str__(self):
546         return self.name
547 
548 class Menus(models.Model):
549     """动态菜单"""
550     name = models.CharField('菜单名',max_length=32)
551     url_type_choices = (
552         (0,'absolute'),
553         (1,'dynamic'),
554     )
555     url_type = models.SmallIntegerField(choices=url_type_choices,default=0)
556     url_name = models.CharField('连接',max_length=128)
557 
558 
559     class Meta:
560         verbose_name = '菜单'
561         verbose_name_plural = '菜单'
562         unique_together = ('name','url_name')
563 
564     def __str__(self):
565         return self.name
566 
567 # ————————72PerfectCRM实现CRM动态菜单和角色————————
568 '''
569 """13一层菜单名"""
570 class FirstLayerMenu(models.Model):
571     """第一层侧边栏菜单"""
572     name = models.CharField('一层菜单名',max_length=64)
573     url_type_choices = ((0,'相关的名字'),(1,'固定的URL'))
574     url_type = models.SmallIntegerField(choices=url_type_choices,default=0)
575     url_name = models.CharField(max_length=64,verbose_name='一层菜单路径')
576     order = models.SmallIntegerField(default=0,verbose_name='菜单排序')
577     sub_menus = models.ManyToManyField('SubMenu',blank=True)
578 
579     def __str__(self):
580         return self.name
581 
582     class Meta:
583         verbose_name_plural = "13第一层菜单"
584 
585 """14二层菜单名"""
586 class SubMenu(models.Model):
587     """第二层侧边栏菜单"""
588     name = models.CharField('二层菜单名', max_length=64)
589     url_type_choices = ((0,'相关的名字'),(1,'固定的URL'))
590     url_type = models.SmallIntegerField(choices=url_type_choices,default=0)
591     url_name = models.CharField(max_length=64, verbose_name='二层菜单路径')
592     order = models.SmallIntegerField(default=0, verbose_name='菜单排序')
593 
594     def __str__(self):
595         return self.name
596 
597     class Meta:
598         verbose_name_plural = "14第二层菜单"
599 '''
600 # ————————72PerfectCRM实现CRM动态菜单和角色————————
601 
602 class ContractTemplate(models.Model):
603     """存储合同模板"""
604     name = models.CharField(max_length=64)
605     content = models.TextField()
606     date = models.DateTimeField(auto_now_add=True)
607 
608     class Meta:
609         verbose_name = '合同'
610         verbose_name_plural = '合同'
611 
612     def __str__(self):
613         return self.name
614 
615 class StudentEnrollment(models.Model):
616     """学员报名表"""
617     customer = models.ForeignKey('CustomerInfo',on_delete=models.CASCADE)
618     consultant = models.ForeignKey('UserProFile',on_delete=models.CASCADE)
619     class_grade = models.ForeignKey('ClassList',on_delete=models.CASCADE)
620 
621     contract_agreed = models.BooleanField(default=False,verbose_name="学员已经同意合同")  #学员看合同
622     contract_signed_date = models.DateTimeField('合同签订时间',blank=True,null=True)
623     contract_approved = models.BooleanField(default=False,verbose_name="合同已经审核")  #谁审核
624     contract_approved_date = models.DateTimeField('合同审核时间',blank=True, null=True)
625 
626     # ————————53PerfectCRM实现CRM客户报名流程缴费————————
627     # Pay_cost = models.BooleanField(default=False, verbose_name="缴费")  # 缴费状态#是不是交定金
628     # ————————53PerfectCRM实现CRM客户报名流程缴费————————
629 
630     class Meta:
631         verbose_name = '学员报名表'
632         verbose_name_plural = '学员报名表'
633         unique_together = ('customer','class_grade')
634 
635     def __str__(self):
636         return self.customer.name
637 
638 class PaymentRecord(models.Model):
639     """存储学员缴费记录"""
640     enrollment = models.ForeignKey('StudentEnrollment',on_delete=models.CASCADE)
641     payment_type_choice = (
642         (0,'报名费'),
643         (1,'学费'),
644         (2,'退费'),
645     )
646     payment_type = models.SmallIntegerField(choices=payment_type_choice,default=0)
647     amount = models.IntegerField('费用',default=500)
648     consultant = models.ForeignKey('UserProFile', on_delete=models.CASCADE)
649     date = models.DateTimeField(auto_now_add=True)
650 
651     class Meta:
652         verbose_name = '学员缴费记录'
653         verbose_name_plural = '学员缴费记录'
654 
655     def __str__(self):
656         return self.enrollment
657     
数据库表结构

 Day2:主要实现功能kingadmin为各个应用实现一个类似于Django自带的数据库管理功能     


  kingadmin目录        

    销售目录

    学员目录

1.首先我们需要在项目启动后(进入Kingadmin模块中view视图后,能够自动采集所有的应用中需要我们采集的数据库信息)

 (1)先设置采集方法:在每个需要我们采集的应用模块中添加上kingadmin.py文件(类似于后台admin会在应用模块的admin.py中采集信息一样)。如上面目录结构,在其中添加了kingadmin.py

 1 from crm import models
 2 """
 3 虽然说,每个APP:
 4     sale,student都去导入了一次site,
 5     但是在python项目中对于同一个模块只会导入一次,
 6     所以这本身就是单例模式(使用的是内存中存在的那个)
 7 """
 8 from kingadmin.sites import site
 9 from kingadmin.admin_base import BaseKingAdmin
10 from crm.forms import UserChangeForm,UserCreationForm
11 
12 # 1
13 print("Sale.kingadmin")
14 
15 class UserProFileAdmin(BaseKingAdmin):
16     # The forms to add and change user instances
17     form = UserChangeForm
18     add_form = UserCreationForm
19 
20     # The fields to be used in displaying the User model.
21     # These override the definitions on the base UserAdmin
22     # that reference specific fields on auth.User.
23     list_display = ('email', 'name', 'is_superuser')
24     list_filter = ('is_superuser',)
25     fieldsets = (
26         (None, {'fields': ('email', 'password')}),
27         ('Personal info', {'fields': ('name',)}),
28         ('Permissions', {'fields': ('is_active','is_staff','is_superuser','role','user_permissions','groups',)}),
29     )
30     # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
31     # overrides get_fieldsets to use this attribute when creating a user.
32     add_fieldsets = (
33         (None, {
34             'classes': ('wide',),
35             'fields': ('email', 'name', 'password1', 'password2')}
36         ),
37     )
38     search_fields = ('email',)
39     ordering = ('email',)
40     filter_horizontal = ('role','groups','user_permissions')
41     readonly_fields = ['password']
42 
43 class CustomerAdmin(BaseKingAdmin):
44     list_display = ['name', 'source', 'contact_type', 'contact', 'consultant', 'consult_content', 'status', 'date']
45     list_filter = ['source', 'consultant', 'status', 'date']
46     search_fields = ['name','contact','source']
47     readonly_fields = ['contact','status']
48     filter_horizontal = ['consult_courses']
49     actions = ['change_status', ]
50 
51     def change_status(self, request, querysets):
52         print(self, request, querysets)
53         querysets.update(status=0)
54 
55 site.register(models.CustomerInfo,CustomerAdmin)
56 site.register(models.Menus)
57 site.register(models.UserProFile,UserProFileAdmin)
58 site.register(models.StudyRecord)
59 site.register(models.CustomerFollowUp)
60 site.register(models.Course)
61 site.register(models.ClassList)
62 site.register(models.CourseRecord)
Sale模块中kingadmin

===================

 1 from student import models
 2 from kingadmin.sites import site
 3 from kingadmin.admin_base import BaseKingAdmin
 4 
 5 print("Student.kingadmin")
 6 
 7 class TestAdmin(BaseKingAdmin):
 8     list_display = ['name']
 9 
10 site.register(models.Test,TestAdmin)
student模块中kingadmin

从中发现需要用到一个基类BaseKingAdmin来自于kingadmin模块:是为了防止注册事件时出现为空的现象,而且在基类中添加功能更加方便

 

 1 from django.shortcuts import render
 2 import json
 3 
 4 class BaseKingAdmin(object):
 5     def __init__(self):
 6         self.actions.extend(self.default_action)
 7     list_display = []
 8     list_filter = []
 9     search_fields = []
10     readonly_fields = []
11     filter_horizontal = []
12     list_per_page = 10
13     default_action = ['delete_selected_objs']
14     actions = []
15 
16     def delete_selected_objs(self,request,querysets):
17         print('delete_selected_objs',self,request,querysets)
18 
19         queryset_ids = json.dumps([i.id for i in querysets])  # 获取所有id
20         return render(request, 'kingadmin/table_obj_delete.html',{
21             'selected_objs':querysets,
22             'admin_class':self,
23             'queryset_ids':queryset_ids,
24         })
admin_base.py中BaseKingAdmin基类

还需要from kingadmin.sites import site,使用到site方法(类似于admin.site.register(模型,自定义模型显示类)):功能是将各个模块中的数据模型统一添加在一个数据结构中,方便调用

 1 from kingadmin.admin_base import BaseKingAdmin
 2 
 3 class AdminSite(object):
 4     def __init__(self):
 5         self.enabled_admins = {}
 6 
 7     def register(self,model_class,admin_class=None):
 8         """注册admin表"""
 9         # 根据model中类名获取APP名
10         app_name = model_class._meta.app_label
11         # 根据model中类名获取表名,类名的小写
12         model_name = model_class._meta.model_name
13 
14         # 不传值时默认BaseKingAdmin,并实例化
15         if not admin_class:
16             admin_class = BaseKingAdmin()
17         else:
18             # 每次实例化,防止使用同一内存地址
19             admin_class = admin_class()
20         # admin_class为对象才能 .model添加存入对象
21         admin_class.model = model_class  # 把model_class赋值给admin_class为了能关联起来
22         if app_name not in self.enabled_admins:
23             self.enabled_admins[app_name] = {}
24         self.enabled_admins[app_name][model_name] = admin_class
25 
26         # 2
27         # print(model_class,admin_class)
28 
29 site = AdminSite()   # 只实例化一次,后面的导入为调用对象
sites.py中的site方法

将数据统一放入self.enabled_admins{}中,形式为self.enabled_admins[模块][表名] = 自定义模型显示类(默认BaseKingAdmin)

注意:虽然在每个模块中都导入了一次sites模块,使用一次site对象,实际上使用的是同一个site对象

可以使用id(site)查看内存,因为python机制中将一个模块导入后,会将其保存在内存中,下次导入数据的时候,会直接从内存中获取数据(所以大家使用的是一个site对象)
所以说:python模块本身就是单例模式

 (2)从settings.py中获取各个模块。创建app_setup.py文件,在项目进入view时去调用该文件,并执行,获取到所有模块的信息

进入views.py自动调用app_setup.kingadmin_auto_discover()方法

 1 from kingadmin.sites import site  #发现只导入模块一次,site对象只有一个
 2 
 3 from kingadmin import app_setup
 4 #用来导入所有含Kingadmin的模块,模块中会去调用相应的Kingadmin文件去注册事件
 5 app_setup.kingadmin_auto_discover()
 6 
 7 # 测试是否site只实例化一次
 8 for k,v in site.enabled_admins.items():
 9     for model_name,admin_class in v.items():
10         pass
11         # print(model_name,id(admin_class))
views.py进入后,顺序执行,首先去调用app_setup.kingadmin_auto_discover()方法采集信息

看如何采集各个模块信息:从配置文件中settings的INSTALLED_APPS中获取所有模块信息

 1 INSTALLED_APPS = [
 2     'django.contrib.admin',
 3     'django.contrib.auth',
 4     'django.contrib.contenttypes',
 5     'django.contrib.sessions',
 6     'django.contrib.messages',
 7     'django.contrib.staticfiles',
 8     'repository.apps.RepositoryConfig',
 9     'kingadmin',
10     'Student',
11     'Sale',
12 ]
settings文件INSTALLED_APPS

views调用了app_setup中的kingadmin_auto_discover()方法自动采集信息,下面看看app_setup文件:实现方法。反向查找 

复制代码
from django import conf  #实现动态获取配置文件,而不是以目录形式
import importlib

def kingadmin_auto_discover():
    for module in conf.settings.INSTALLED_APPS:
        try:
            # md = importlib.import_module('.kingadmin',module)  #这个也可以
            md = __import__('%s.kingadmin'%module)  #导入Kingadmin,然后回去执行该文件中的数据,去注册事件(模块导入后,会自动使用site.register方法注册事件)
        except ImportError as e:
            pass
复制代码

(3)上面将数据采集完毕,把site对象传入模板中,使用app_index视图方法,可以实现后台管理admin首页功能

def app_index(request):return render(request,"kingadmin/app_index.html",{'site':site})

=====================================

 1 {% extends 'kingadmin/index.html' %}
 2 {% load kingadmin_tag %}
 3 {% block right-content-container %}
 4     <h1 class="page-header">Site Administration</h1>
 5 
 6     <div>
 7         {% for app_name,app_tables in site.enabled_admins.items %}
 8         <table class="table table-striped">
 9             <thead>
10                 <tr class="info">
11                     <th>{{app_name.upper}}</th>
12                 </tr>
13             </thead>
14             <tbody>
15                 {% for model_name,admin_class in app_tables.items %}
16                 <tr>
17                     <td><a href="{% url 'table_obj_list' app_name model_name %}">{% get_model_verbose_name admin_class %}</a></td>
18                     <td>
19                         <a href="{{app_name}}/{{model_name}}/add">
20                             <span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span>
21                             增加
22                         </a>
23                         |
24                         <a href="{{app_name}}/{{model_name}}">
25                             <span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
26                             修改
27                         </a>
28                     </td>
29                 </tr>
30                 {% endfor %}
31             </tbody>
32         </table>
33         {% endfor %}
34     </div>
35 
36 {% endblock %}
前端主要代码

(4)实现点击表名,查看数据的功能

 

 1 @check_permission
 2 @login_required()
 3 def table_obj_list(request,app_name,model_name):
 4     """取出指定model里的数据返回给前端"""
 5     admin_class = site.enabled_admins[app_name][model_name]
 6 
 7     if request.method == "POST":
 8         # print(request.POST)
 9         selected_action = request.POST.get('action')
10         selected_ids = json.loads(request.POST.get('selected_ids'))
11 
12         if selected_action: # 如果有action参数代表为正常的action
13             selected_objs = admin_class.model.objects.filter(id__in=selected_ids).all()
14             admin_action_func = getattr(admin_class, selected_action)
15             # print(admin_action_func,selected_objs)
16             return admin_action_func(request,selected_objs)
17         elif not selected_action:  # 如果没有action,代表可以是一个删除动作
18             if selected_ids:  # 选中的数据都被删除
19                 admin_class.model.objects.filter(id__in=selected_ids).delete()
20 
21     # 调用函数过滤处理,并返回数据跟需过滤字段(用于选中)
22     querysets,filter_conditions = get_filter_result(admin_class, request)
23     # 把filter_conditions存入admin_class对象
24     admin_class.filter_conditions = filter_conditions
25 
26     # 搜索,searched querysets
27     querysets = get_search_result(request,admin_class,querysets)
28     admin_class.search_key = request.GET.get('_q','')
29 
30     # 全局排序,分页前,sorted_querysets
31     querysets, sorted_column = get_orderby_result(request, admin_class, querysets)
32 
33     paginator = Paginator(querysets, admin_class.list_per_page)  # Show 2 contacts per page
34     page = request.GET.get('_page')
35     querysets = paginator.get_page(page)
36 
37     return render(request, 'kingadmin/table_obj_list.html',{
38         'querysets':querysets,
39         'admin_class':admin_class,
40         'sorted_column':sorted_column,
41         'app_name':app_name,
42         'model_name':model_name,
43     })
table_obj_list方法根据模块和表名去获取site对象中的数据

 =====================

  1 {% extends 'kingadmin/index.html' %}
  2 {# 导入标签模块 #}
  3 {% load kingadmin_tag %}  {# build_table_row' is not a registered tag library 重启即可 #}
  4 
  5 {% block right-content-container %}
  6     <ol class="breadcrumb">
  7       <li><a href="/kingadmin/">Home</a></li>
  8       <li><a href="/kingadmin/{{app_name}}/">{{app_name.title}}</a></li>
  9       <li class="active">{% get_model_verbose_name admin_class %}</li>
 10     </ol>
 11 
 12     <h4 class="page-header">{{app_name.upper}} Administration</h4>
 13 
 14     <div class="row">
 15         <form>
 16             <div class="row">
 17               <div class="col-md-6">
 18                 <div class="input-group">
 19                   <span class="input-group-btn">
 20                     <button class="btn btn-default" type="button">
 21                         <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
 22                     </button>
 23                   </span>
 24                   <input type="search" class="form-control" name="_q" value="{{admin_class.search_key}}"
 25                        placeholder="{% for s in admin_class.search_fields %}{{s}},{% endfor %}">
 26                 </div>
 27               </div>
 28             <input class="btn btn-success" type="submit" value="Search">
 29             {% for k,v in admin_class.filter_conditions.items %}
 30                 <input type="hidden" name="{{k}}" value="{{v}}">
 31             {% endfor %}
 32             </div>
 33         </form>
 34     </div>
 35 <hr>
 36 <div class="pull right">
 37     <h2><a href="add">ADD</a></h2>
 38 </div>
 39 <hr>
 40     <div class="row">
 41         {% if admin_class.list_filter %}
 42             <form class="navbar-form navbar-left">
 43                 {% for filter_column in admin_class.list_filter %}
 44                     {% build_filter_ele filter_column admin_class %}
 45                 {% endfor %}
 46                 <input type="hidden" name="_o" value="{% get_current_sorted_column_index sorted_column %}">
 47                 <input class="btn btn-success" type="submit" value="过滤">
 48             </form>
 49         {% endif %}
 50     </div>
 51 
 52     <form onsubmit="return ActionCheck(this);" method="post"> {% csrf_token %}
 53         <div class="row">
 54             <div class="col-lg-3">
 55                 <select class="form-control" name="action">
 56                     <!--若无value则默认为值-&#45;&#45;&#45;&#45;,只有value=""值才为空 -->
 57                     <option value="">------------</option>
 58                     {% for action in admin_class.actions %}
 59                         <option value="{{action}}">{{action}}</option>
 60                     {% endfor %}
 61                 </select>
 62            </div>
 63             <div class="col-lg-2">
 64                 <input class="btn btn-success" type="submit" value="GO">
 65             </div>
 66         </div>
 67     </form>
 68 
 69     <div>
 70         <table class="table table-striped">
 71             <thead>
 72                 <tr>
 73                     <th><input onclick="SelectAllObjs(this);" type="checkbox"></th>
 74                     {% if admin_class.list_display %}
 75                         {% for column in admin_class.list_display %}
 76                             {# counter0 从0开始,方便后台取列表数据 #}
 77                             <th>
 78                                 <a href="?_o={% get_sorted_column column sorted_column forloop.counter0 %}{% render_filtered_args admin_class %}">
 79                                     {{column}}
 80                                     {% render_sorted_arrow column sorted_column %}
 81                                 </a>
 82                             </th>
 83                         {% endfor %}
 84                     {% else %}
 85                         <!--还可以直接后台传入model_name-->
 86                         <th>{% get_model_name admin_class %}</th>
 87                     {% endif %}
 88                     <th>报名连接</th>
 89                 </tr>
 90             </thead>
 91             <tbody>
 92                 {% for obj in querysets %}
 93                     <tr>
 94                         <td><input row-select='true' type="checkbox" value="{{obj.id}}"></td>
 95                         {% build_table_row obj admin_class %}
 96                         <td><a href="{% url 'stu_enrollment' %}">连接</a></td>
 97                     </tr>
 98                 {% endfor %}
 99             </tbody>
100         </table>
101 
102         {% render_paginator querysets admin_class sorted_column %}
103 
104     </div>
105 
106 <script>
107     function ActionCheck(ele) {
108         var selected_action = $("select[name='action']").val();
109         var selected_objs = $('input[row-select]').filter(':checked');
110         console.log(selected_action);
111         if(!selected_action) {
112             alert('no action selected!');
113             // ?????
114             return false
115         }
116         if(selected_objs.length == 0) {
117             alert('no object selected!');
118             return false
119         }else{
120             var selected_ids = [];
121             $.each(selected_objs,function() {
122                 console.log($(this));
123                 selected_ids.push($(this).val());
124             });
125             console.log(selected_ids);
126             // JSON.stringify(selected_ids)之后为字符串,无需再加引号
127             var input_ele = '<input type="hidden" name="selected_ids" value=' + JSON.stringify(selected_ids) + '>'
128             $(ele).append(input_ele);
129         }
130         // 返回则不提交数据
131         //return false
132     }
133 
134 
135     function SelectAllObjs(ele) {
136         if($(ele).prop('checked')) {
137             $('input[row-select]').prop('checked',true);
138         }else{
139             $('input[row-select]').prop('checked',false);
140         }
141     }
142 </script>
143 
144 {% endblock %}
前端

前端使用了自定义模板函数

  1 from django.template import Library
  2 from django.utils.safestring import mark_safe
  3 import datetime
  4 
  5 register = Library()
  6 
  7 @register.simple_tag
  8 def build_table_row(obj,admin_class):
  9     """生成一条记录html element"""
 10     ele = ''
 11     if admin_class.list_display:
 12         for index,column_name in enumerate(admin_class.list_display):
 13             column_obj = admin_class.model._meta.get_field(column_name)
 14             if column_obj.choices:  # 判断字段是否为choice
 15                 column_data = getattr(obj, 'get_%s_display' %column_name)()   # 获取choice的值
 16             else:
 17                 column_data = getattr(obj, column_name)  # 反射
 18             if index == 0:
 19                 ele += '<td><a href="%s/change">%s</a></td>' % (obj.id,column_data)
 20             else:
 21                 ele += '<td>%s</td>' % column_data
 22     else:
 23         ele += '<td><a href="%s/change">%s</a></td>' % (obj.id,obj)   # 执行obj对象中的__str__方法
 24     return mark_safe(ele)
 25 
 26 @register.simple_tag
 27 def get_model_name(admin_class):
 28     """获取表名"""
 29     return admin_class.model._meta.model_name.upper()
 30 
 31 @register.simple_tag
 32 def build_filter_ele(filter_column,admin_class):
 33     """过滤"""
 34     #获取字段对象
 35     column_obj = admin_class.model._meta.get_field(filter_column)
 36     filter_column_ele = "<span>%s:</span>" % filter_column
 37     try:
 38         filter_select = '<div class="form-group">%s<select class="form-control" name=%s>' % (filter_column_ele,filter_column)
 39         for choice in column_obj.get_choices():
 40             selected = ''
 41             # if filter_column in admin_class.filter_conditions:  # 当前字段被过滤了
 42             if str(choice[0]) == admin_class.filter_conditions.get(filter_column):  # 当前值被选中
 43                 selected = 'selected'
 44             option = "<option value='%s' %s>%s</option>" % (choice[0],selected,choice[1])
 45             filter_select += option
 46     except AttributeError as e:
 47         print('err',e)
 48         filter_select = '<div class="form-group">%s<select class="form-control" name=%s__gte>' % (filter_column_ele,filter_column)
 49         # 判断字段属性
 50         if column_obj.get_internal_type() in ('DateField','DateTimeField'):
 51             time_obj = datetime.datetime.now()
 52             time_list = [
 53                 ['--------------',''],
 54                 ['Today',time_obj],
 55                 ['Past 7 day',time_obj-datetime.timedelta(7)],
 56                 ['This month',time_obj.replace(day=1)],
 57                 ['Past 3 month',time_obj-datetime.timedelta(90)],
 58                 ['This year',time_obj.replace(month=1,day=1)],
 59                 ['Any day',''],
 60             ]
 61             for i in time_list:
 62                 selected = ''
 63                 time_to_str = '' if not i[1] else '%s-%s-%s' %(i[1].year,i[1].month,i[1].day)
 64                 if time_to_str == admin_class.filter_conditions.get('%s__gte' %filter_column):  # 当前值被选中
 65                     selected = 'selected'
 66                 option = '<option value=%s %s>%s</option>' % (time_to_str,selected,i[0])
 67                 filter_select += option
 68 
 69     filter_select += '</select></div>'
 70     return mark_safe(filter_select)
 71 
 72 @register.simple_tag
 73 def render_filtered_args(admin_class,render_html=True):
 74     """拼接筛选的字段"""
 75     ele = ''
 76     if admin_class.filter_conditions:
 77         for k,v in admin_class.filter_conditions.items():
 78             ele += '&%s=%s' %(k,v)
 79     return mark_safe(ele) if render_html else ele
 80 
 81 @register.simple_tag
 82 def render_paginator(querysets,admin_class,sorted_column):
 83     """分页"""
 84     # 保存排序状态
 85     sorted_column_ele = ''
 86     if sorted_column:
 87         sorted_column_ele = '&_o=%s' % list(sorted_column.values())[0]
 88 
 89     ele = """
 90         <nav aria-label="Page navigation">
 91           <ul class="pagination">
 92             <li>
 93               <a href="#" aria-label="Previous">
 94                 <span aria-hidden="true">&laquo;</span>
 95               </a>
 96             </li>
 97     """
 98     for page in querysets.paginator.page_range:
 99         if abs(page - querysets.number) < 2:   # 展示1*1+1页
100             active = ''
101             if page == querysets.number:
102                 active = 'active'
103             # 分页时保存筛选状态
104             filtered_ele = render_filtered_args(admin_class)
105             ele += '<li class="%s"><a href="?_page=%s%s%s">%s</a></li>' % (active,page,sorted_column_ele,filtered_ele,page)
106 
107     ele += """
108         <li>
109               <a href="#" aria-label="Next">
110                 <span aria-hidden="true">&raquo;</span>
111               </a>
112             </li>
113           </ul>
114         </nav>
115     """
116     return mark_safe(ele)
117 
118 @register.simple_tag
119 def get_sorted_column(column,sorted_column,forloop):
120     this_time_sort_index = forloop
121     if column in sorted_column:  # 这一列被排序了
122         # 判断上次排序方式,本次取反
123         last_sorted_index = sorted_column[column]
124         if last_sorted_index.startswith('-'):
125             this_time_sort_index = last_sorted_index.strip('-')
126         else:
127             this_time_sort_index = '-%s' %last_sorted_index
128     return this_time_sort_index
129 
130 @register.simple_tag
131 def render_sorted_arrow(column,sorted_column):
132     ele = ''
133     if column in sorted_column:
134         last_sorted_index = sorted_column[column]
135         if last_sorted_index.startswith('-'):
136             arrow_direction = 'top'
137         else:
138             arrow_direction = 'bottom'
139         ele += """<span class="glyphicon glyphicon-triangle-%s" aria-hidden="true"></span>""" % arrow_direction
140     return mark_safe(ele)
141 
142 @register.simple_tag
143 def get_current_sorted_column_index(sorted_column):
144     """过滤时保存排序状态"""
145     return list(sorted_column.values())[0] if sorted_column else ''
146 
147 @register.simple_tag
148 def get_field_val(admin_class,form_obj,field):
149     """返回具体字段的值"""
150     field_obj = admin_class.model._meta.get_field(field)
151     if field_obj.choices:  # 判断字段是否为choice
152         field_val = getattr(form_obj.instance, 'get_%s_display' % field)()  # 获取choice的值
153     else:
154         field_val = getattr(form_obj.instance, field)
155     return field_val
156 
157 @register.simple_tag
158 def get_available_m2m_data(field_name,admin_class,form_obj):
159     """返回m2m字段关联表的未选中的数据"""
160     field_obj = admin_class.model._meta.get_field(field_name)  # 获取该字段字段对象
161     obj_all_list = set(field_obj.related_model.objects.all())
162     obj_selected_list = []
163     try:
164         obj_selected_list = getattr(form_obj.instance, field_name).all()
165     except TypeError:
166         pass
167     obj_available_list = obj_all_list - set(obj_selected_list)
168     return obj_available_list
169 
170 @register.simple_tag
171 def get_selected_m2m_data(field_name,form_obj):
172     """返回m2m字段关联表的选中数据"""
173     obj_selected_list = []
174     try:
175         obj_selected_list = set(getattr(form_obj.instance,field_name).all())
176     except TypeError:
177         pass
178     return obj_selected_list
179 
180 @register.simple_tag
181 def get_display_all_related_objs(obj):
182     """显示被删除对象的所有关联对象"""
183     ele = '<ul>'
184 
185     M2M_obj_list = obj._meta.many_to_many
186     for M2M in M2M_obj_list:
187         # 该字段关联的所有对象
188         for i in getattr(obj,M2M.name).all():
189             ele += '<li>%s:<a href="/kingadmin/%s/%s/%s/change">%s</a>——记录里与[%s]相关的数据将被影响</li>' \
190                    % (M2M.name, i._meta.app_label, i._meta.model_name, i.id, i, obj)
191     reversed_fk_list = obj._meta.related_objects
192     for reversed_fk_obj in reversed_fk_list:
193         reversed_table_name = reversed_fk_obj.name
194         if reversed_fk_obj.one_to_one:  # OneToOne关系
195             related_lookup_key = reversed_table_name
196         else:
197             related_lookup_key = '%s_set' %reversed_table_name
198         related_objs = getattr(obj,related_lookup_key).all()  # 反向关联的所有数据
199         if reversed_fk_obj.many_to_many:  # M2M则不深入
200             for i in related_objs:
201                 ele += '<li>%s:<a href="/kingadmin/%s/%s/%s/change">%s</a>——记录里与[%s]相关的数据将被影响</li>' \
202                        % (reversed_table_name,i._meta.app_label,i._meta.model_name,i.id,i,obj)
203         elif reversed_fk_obj.one_to_many:  # reversed_fk关系
204             for i in related_objs:
205                 ele += '<li>%s:<a href="/kingadmin/%s/%s/%s/change">%s</a>——记录里与[%s]相关的数据将被删除</li>' \
206                        % (reversed_table_name,i._meta.app_label,i._meta.model_name,i.id,i,obj)
207                 ele += get_display_all_related_objs(i)
208 
209     ele += '</ul>'
210     return ele
211 
212 @register.simple_tag
213 def get_model_verbose_name(admin_class):
214     """显示中文"""
215     return admin_class.model._meta.verbose_name
216 
217 @register.simple_tag
218 def get_field_objs(stu_rec_objs):
219     """返回字段对象"""
220     return stu_rec_objs[0]._meta.fields
自定义模板函数

 Day3:对上面的功能添加分页,筛选,排序,搜索功能(功能之间的url需要重组)

一:分页实现(在Django自带分页组件下进行扩展)

 1 from django.core.paginator import Paginator
 2 
 3 # Django内置分页拓展
 4 class CustomPaginator(Paginator):  # 修改类
 5     def __init__(self,current_page, per_page_num,*args,**kwargs):
 6         # 当前页
 7         self.current_page = int(current_page)
 8         # 最多显示页码数 11
 9         self.per_page_num = int(per_page_num)
10         super(CustomPaginator, self).__init__(*args,**kwargs)
11 
12     @property
13     def pager_num_range(self):
14         # 当前页
15         # self.current_page
16         # 最多显示页码数 11
17         # self.per_page_num
18         # 总页数
19         # self.num_pages
20         if self.per_page_num > self.num_pages:  # 显示页码个数大于总页数时
21             # 根据当前页动态生成,设置页面显示数
22             return range(1, self.num_pages + 1)  # 全部显示
23         # 总页数特别多时
24         part = int(self.per_page_num/2)
25 
26         if part >= self.current_page:
27             return range(1, self.per_page_num + 1)
28         # 判断最大极值
29         if (self.current_page + part) >= self.num_pages:
30             return range(self.num_pages - self.per_page_num + 1, self.num_pages + 1)
31         return range(self.current_page - part, self.current_page + part +1)
自定义分页类

===========

 1 # 使用Django自带分页,可进行拓展
 2     querysets = get_paghinator_result(request,admin_class,querysets)
 3     print('queryset.paginator:',querysets.paginator)  # CustomPaginator object
 4 
 5 
 6 
 7 def get_paghinator_result(request,admin_class,querysets):
 8     current_page = request.GET.get('_page',1)  # 加 1 防止报错
 9     # per_page: 每页显示条目数量
10     # count:数据总个数
11     # num_pages: 总页数
12     # page_range: 总页数的索引范围,页码范围,如:(1,10),(1,200)
13     # page:page对象(是否具有上一页,是否有下一页)
14     list_per_page = admin_class.list_per_page  # 显示页码个数
15     per_page_num = admin_class.per_page_num    # 每页显示条目
16     # 当前页 显示页码个数 总数据 每页显示条目
17     paginator = CustomPaginator(current_page,list_per_page ,querysets,per_page_num)
18     try:
19         posts = paginator.page(current_page)
20         # has_next              是否有下一页
21         # next_page_number      下一页页码
22         # has_previous          是否有上一页
23         # previous_page_number  上一页页码
24         # object_list           分页之后的数据列表
25         # number                当前页
26         # paginator             paginator对象
27     except PageNotAnInteger:
28         posts = paginator.page(1)
29     except EmptyPage:
30         posts = paginator.page(paginator.num_pages)
31     return posts
分页类的使用

===========

1 <div>
2       {% render_paginator querysets admin_class sorted_column %}
3  </div>
使用模板函数对分页数据进行url组合

===========

 1 @register.simple_tag
 2 def render_filtered_args(admin_class,render_html=True):
 3     """拼接筛选的字段"""
 4     ele = ''
 5     if admin_class.filter_conditions:
 6         for k,v in admin_class.filter_conditions.items():
 7             ele += '&%s=%s' %(k,v)
 8     return mark_safe(ele) if render_html else ele
 9 
10 ==================
11 
12 @register.simple_tag
13 def render_paginator(querysets,admin_class,sorted_column):
14     """分页"""
15     filter_conditions = ''
16     # 生成排序条件
17     if sorted_column:
18         filter_conditions += '&_o=%s' % list(sorted_column.values())[0]
19     # 生成筛选条件
20         filter_conditions += render_filtered_args(admin_class)
21 
22     # 生成搜索条件
23     if admin_class.search_condition:
24         filter_conditions += "&_q=%s" %admin_class.search_condition
25 
26     page_str = """
27         <nav aria-label="Page navigation">
28           <ul class="pagination">
29     """
30     # 为什么可以用d???
31     page_str += '<li><a href="?_page=%d%s">首页</a></li>' % (1,filter_conditions)
32 
33     if querysets.has_previous():
34         previous_page = querysets.previous_page_number()
35         page_str += """<li>
36                             <a href="?_page=%d%s" aria-label="Previous">
37                                 <span aria-hidden="true">&laquo;</span>
38                             </a>
39                         </li>
40             """ % (previous_page,filter_conditions)
41 
42     for page in querysets.paginator.pager_num_range:
43         active = ''
44         if page == querysets.number:
45             active = 'active'
46         page_str += '<li class="%s"><a href="?_page=%d%s">%d</a></li>' % (active,page,filter_conditions,page)
47 
48 
49     if querysets.has_next():
50         next_page = querysets.next_page_number()
51         page_str += """<li>
52                             <a href="?_page=%d%s" aria-label="Next">
53                                 <span aria-hidden="true">&raquo;</span>
54                             </a>
55                         </li>
56             """ % (next_page, filter_conditions)
57 
58     page_str += '<li><a href="?_page=%d%s">尾页</a></li>' % (querysets.paginator.num_pages,filter_conditions)
59 
60     page_str += """ 
61                 <li><a>第%s页/共%s页</a></li>     
62               </ul>
63             </nav>
64         """  %(querysets.number,querysets.paginator.num_pages)
65     return mark_safe(page_str)
render_paginator模板函数

二:对各个字段筛选(对kingadmin中list_filter字段进行筛选)

1:前端显示

 1 <div class="row">
 2         {% if admin_class.list_filter %}
 3             <form class="navbar-form navbar-left">
 4                 {% for filter_column in admin_class.list_filter %}
 5                     {% build_filter_ele filter_column admin_class %}
 6                 {% endfor %}
 7                 <input type="hidden" name="_o" value="{% get_current_sorted_column_index sorted_column %}">
 8                 <input class="btn btn-success" type="submit" value="过滤">
 9             </form>
10         {% endif %}
11     </div>
build_filter_ele模板函数去获取数据生成标签

2.模板函数去定制标签,在form表单中加入隐藏标签(表示排序和搜索条件)

 1 """过滤处理"""
 2 @register.simple_tag
 3 def build_filter_ele(filter_column,admin_class):
 4     #获取字段对象
 5     column_obj = admin_class.model._meta.get_field(filter_column)
 6     label = "<label class='control-label'>%s:</label>" % filter_column
 7     try:
 8         # 第一种(更全面,逻辑更清晰)
 9         filter_select = '<div class="form-group">%s<select class="form-control" name=%s>' % (label,filter_column)
10         data_list = column_obj.get_choices()  # choices或外键字段
11 
12         # 第二种方式
13         # for choice in column_obj.get_choices():  #获取choices选项和外键
14         #     selected = ''
15         #     # if filter_column in admin_class.filter_conditions:  # 当前字段被过滤了
16         #     if str(choice[0]) == admin_class.filter_conditions.get(filter_column):  # 当前值被选中
17         #         selected = 'selected'
18         #     option = "<option value='%s' %s>%s</option>" % (choice[0],selected,choice[1])
19         #     filter_select += option
20     except AttributeError as e:
21         print('err',e)
22         # filter_select = '<div class="form-group">%s<select class="form-control" name=%s__gte>' % (label,filter_column)
23         # 判断字段属性
24         if column_obj.get_internal_type() in ('DateField','DateTimeField'):  # 处理date字段
25 
26             filter_column = '%s__gte' % filter_column  # 特殊处理,方便日后选中
27 
28             filter_select = '<div class="form-group">%s<select class="form-control" name=%s>' % (label, filter_column)
29             time_obj = datetime.datetime.now()
30             time_list = [
31                 ['','--------------'],
32                 [time_obj,'Today'],
33                 [time_obj-datetime.timedelta(7),'Past 7 day'],
34                 [time_obj.replace(day=1),'This month'],
35                 [time_obj-datetime.timedelta(90),'Past 3 month'],
36                 [time_obj.replace(month=1,day=1),'This year'],
37                 ['','Any day'],
38             ]
39 
40             def turn_date(date_list):
41                 if date_list[0]:
42                     date_list[0] = date_list[0].strftime("%Y-%m-%d")
43                 return date_list
44 
45             # 不修改原来的列表,对表里的值逐一处理,并返回,形成新列表,Python3需list
46             data_list = map(turn_date,time_list)
47 
48             # for i in time_list:
49             #     selected = ''
50             #     time_to_str = '' if not i[0] else '%s-%s-%s' %(i[0].year,i[0].month,i[0].day)
51             #     if time_to_str == admin_class.filter_conditions.get('%s__gte' %filter_column):  # 当前值被选中
52             #         selected = 'selected'
53             #     option = '<option value=%s %s>%s</option>' % (time_to_str,selected,i[1])
54             #     filter_select += option
55         else:  # 处理布尔值或其他字段
56             filter_select = '<div class="form-group">%s<select class="form-control" name=%s>' % (label, filter_column)
57             data_list = admin_class.model.objects.values_list('id',filter_column)
58             data_list_filtered = [('','-------'),]
59             for item in data_list:
60                 if column_obj.get_internal_type() == 'BooleanField':  # 处理布尔值
61                     if item[1] == True:   # 为什么item[0] == True也可以?
62                         list_filtered = (1,True)
63                     else:
64                         list_filtered = (0, False)
65                 else:  # 处理其他字段
66                     list_filtered = (item[1],item[1])  # 提交值  显示值
67                 if list_filtered in data_list_filtered:continue
68                 data_list_filtered.append(list_filtered)
69             data_list = data_list_filtered
70 
71     for item in data_list:
72         selected = ''
73         if str(item[0]) == admin_class.filter_conditions.get(filter_column, None):
74             selected = 'selected'
75         option = "<option value='%s' %s>%s</option>" % (str(item[0]),selected,item[1])
76 
77         filter_select += option
78 
79     filter_select += '</select></div>'
80     return mark_safe(filter_select)
build_filter_ele模板函数对日期筛选进行自定义,外键或者choices字段使用字段对象获取数据,对于其他的字段使用model获取所有的值,组成select框进行筛选

3.在views中将url中的各个条件,放置到admin_class中,方便模板标签的使用

 1 # @check_permission
 2 @login_required()
 3 def table_obj_list(request,app_name,model_name):
 4     """取出指定model里的数据返回给前端"""
 5     admin_class = site.enabled_admins[app_name][model_name]
 6 
 7     if request.method == "POST":
 8         # print(request.POST)
 9         selected_action = request.POST.get('action')
10         selected_ids = json.loads(request.POST.get('selected_ids'))
11 
12         if selected_action: # 如果有action参数代表为正常的action
13             selected_objs = admin_class.model.objects.filter(id__in=selected_ids).all()
14             admin_action_func = getattr(admin_class, selected_action)
15             # print(admin_action_func,selected_objs)
16             return admin_action_func(request,selected_objs)
17         elif not selected_action:  # 如果没有action,代表可以是一个删除动作
18             if selected_ids:  # 选中的数据都被删除
19                 admin_class.model.objects.filter(id__in=selected_ids).delete()
20 
21     # 调用函数过滤处理,并返回数据跟需过滤字段(用于选中)
22     querysets,filter_conditions = get_filter_result(admin_class, request)  # 过滤后的数据
23     # 把filter_conditions存入admin_class对象
24     admin_class.filter_conditions = filter_conditions    #也可以传值给前端,但是这样也不错
25 
26     # 搜索,searched querysets
27     querysets = get_search_result(request,admin_class,querysets)   # 搜索后的数据
28 
29     # 全局排序,分页前,sorted_querysets
30     querysets, sorted_column = get_orderby_result(request, admin_class, querysets)  # 排序后的数据
31 
32     # 使用Django自带分页,可进行拓展
33     querysets = get_paghinator_result(request,admin_class,querysets)  # 分页后的数据
34     print('queryset.paginator:',querysets.paginator)  # CustomPaginator object
35 
36     return render(request, 'kingadmin/table_obj_list.html',{
37         'querysets':querysets,
38         'admin_class':admin_class,
39         'sorted_column':sorted_column,
40         'app_name':app_name,
41         'model_name':model_name,
42     })
注意:我在views中将各个url条件放在admin_class中,方便查询对比(也可以放在变量中分发出来)

4.在views中的url数据获取时将其他_q搜索,o排序,_p分页数据过滤,获取所有数据

 1 def get_filter_result(admin_class,request):
 2     filter_conditions = {}
 3     for key,val in request.GET.items():
 4         # 排除与过滤关键字的干扰
 5         if key in ('_page','_o','_q'):continue
 6         # val为空时不过滤
 7         if val:
 8             filter_conditions[key] = val
 9     filtered_querysets = admin_class.model.objects.filter(**filter_conditions).all().order_by('-id')
10     return filtered_querysets,filter_conditions
get_filter_result过滤条件,获取querysets数据

推文:python---Django中模型类中Meta元对象了解,可以知道数据模型中的字段对象或者其他所需要的内容

 三:对各个字段进行排序(list_display)

1.前端传递排序数据,对于table中的th加上url

1 <th>
2                                 <a href="?_q={{admin_class.search_key}}&_o={% get_sorted_column column sorted_column forloop.counter0 %}
3                                              {% render_filtered_args admin_class %}">
4                                     {{column}}
5                                     {% render_sorted_arrow column sorted_column %}
6                                 </a>
7                             </th>
使用模板函数处理

2.模板函数get_sorted_column去生成value值

========第一种方式================

 1 @register.simple_tag
 2 def get_sorted_column(column,sorted_column,forloop):
 3     this_time_sort_index = forloop
 4     if column in sorted_column:  # 这一列被排序了
 5         # 判断上次排序方式,本次取反
 6         last_sorted_index = sorted_column[column]
 7         if last_sorted_index.startswith('-'):
 8             this_time_sort_index = last_sorted_index.strip('-')
 9         else:
10             this_time_sort_index = '-%s' %last_sorted_index
11     return this_time_sort_index
get_sorted_column
 1 """生成箭头"""
 2 @register.simple_tag
 3 def render_sorted_arrow(column,sorted_column):
 4     ele = ''
 5     if column in sorted_column:
 6         last_sorted_index = sorted_column[column]
 7         if last_sorted_index.startswith('-'):
 8             arrow_direction = 'top'
 9         else:
10             arrow_direction = 'bottom'
11         ele += """<span class="glyphicon glyphicon-triangle-%s" aria-hidden="true"></span>""" % arrow_direction
12     return mark_safe(ele)
render_sorted_arrow生成箭头

========第二种方式================

 1 @register.simple_tag
 2 def build_title_row(admin_class):
 3     #先生成过滤条件
 4     filter_conditions = ''
 5     for k, v in admin_class.filter_conditions.items():
 6         filter_conditions += "&" + k + '=' + v;
 7 
 8     #再生成搜索条件
 9     if admin_class.search_conditions:
10         filter_conditions += "&_q="+admin_class.search_conditions
11 
12     icon = """<span class="glyphicon glyphicon-triangle-%s" aria-hidden="true"></span>"""
13     title = ''
14     th = "<th><a href='?o=%s%s'>%s%s</a></th>"
15 
16     try:
17         sort_cond = list(admin_class.sort_conditions.keys())[0]
18         sort_val = list(admin_class.sort_conditions.values())[0]
19     except IndexError:
20         sort_cond = None
21         sort_val = None
22     for counter,field in enumerate(admin_class.list_display):
23         if field == sort_cond:
24             if sort_val.startswith("-"):
25                 title += th%(sort_val.strip("-"),filter_conditions,field,icon%"top")
26             else:
27                 title += th%("-"+sort_val,filter_conditions,field,icon%"bottom")
28         else:
29             title += th%(counter,filter_conditions,field,"")
30 
31     return mark_safe(title)
32 
33 build_title_row中先将过滤和搜索条件组合,再生成排序url(符号倒序,数字代表在admin_class中list_display字段中的索引顺序)
build_title_row中先将过滤和搜索条件组合,再生成排序url(符号倒序,数字代表在admin_class中list_display字段中的索引顺序)

3.views中对我们获取的所有数据,根据前端传递的排序方法进行排序处理

 1 def get_orderby_result(request,admin_class,querysets):
 2     """排序"""
 3     orderby_index = request.GET.get('_o')
 4     current_sorted_column = {}
 5     # 如果有则进行排序,没有则直接返回querysets
 6     if orderby_index:
 7         orderby_column = admin_class.list_display[abs(int(orderby_index))]  # 取绝对值,确保取相同值
 8         current_sorted_column[orderby_column] = orderby_index   # 为了让前端知道当前排序的列
 9         # 判断显示方式
10         if orderby_index.startswith('-'):  # 判断排序方式,字段索引为负,则倒序
11             orderby_column = '-%s' % orderby_column
12         querysets = querysets.order_by(orderby_column)
13     return querysets,current_sorted_column
get_order_result获取排序结果

四:对字段进行搜索(search_fields)

1.前端生成标签时,form表单中需要一起传递其他条件的input隐藏框

 1 <!--关键字搜索-->
 2     <div class="row">
 3         <form>
 4             <div class="row">
 5               <div class="col-md-6">
 6                 <div class="input-group">
 7                   <span class="input-group-btn">
 8                     <button class="btn btn-default" type="button">
 9                         <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
10                     </button>
11                   </span>
12                   <input type="search" class="form-control" name="_q" value="{{admin_class.search_key}}"
13                        placeholder="{% for s in admin_class.search_fields %}{{s}},{% endfor %}">
14                 </div>
15               </div>
16             <input class="btn btn-success" type="submit" value="Search">
17 
18             <!--其他条件impute隐藏框-->
19             {% for k,v in admin_class.filter_conditions.items %}
20                 <input type="hidden" name="{{k}}" value="{{v}}">
21             {% endfor %}
22             {% for k,v in admin_class.sorted_column.items %}
23                 <input type="hidden" name="_o" value="{{v}}">
24             {% endfor %}
25             </div>
26         </form>
27     </div>
前端数据forms(含有各个条件)

2.后端处理搜索条件,生成querysets数据

 1 """返回搜索结果数据"""
 2 def get_search_result(request,admin_class,querysets):
 3     search_key = request.GET.get('_q','')
 4     admin_class.search_condition = search_key
 5     admin_class.search_key = search_key
 6     if search_key:
 7         q = Q()
 8         q.connector = 'OR'
 9         for search in admin_class.search_fields:
10             # 添加搜索条件
11             q.children.append(('%s__contains' % search, search_key))
12         querysets = querysets.filter(q)
13     return querysets
views处理搜索字段,注意使用OR,需要用到Q方法

 推文:python---django中orm的使用(2)

Day4:动态生成任意表的CURD

1.如何在前端动态生成标签?使用form验证可以针对model生成所有的字段控件

推文:python---django中form组件(2)自定制属性以及表单的各种验证,以及数据源的实时更新,以及和数据库关联使用ModelForm和元类

 1 from django.forms import ModelForm
 2 from repository import models
 3 
 4 class CustomerForm(ModelForm):
 5     class Meta:
 6         model = models.CustumerInfo #将表与元类中的数据关联
 7         fields = "__all__"
 8 
 9     def __new__(cls, *args, **kwargs):
10         print(cls.base_fields)
11         #OrderedDict([('name', <django.forms.fields.CharField object at 0x00000000047FE9E8>), ('contact_type', <django.forms.fields.TypedChoiceField object at 0x00000000047FEBA8>), ('contact', <django.forms.fields.CharField object at 0x00000000047FECC0>), ('source', <django.forms.fields.TypedChoiceField object at 0x00000000047FEE10>), ('referral_from', <django.forms.models.ModelChoiceField object at 0x00000000047FEEF0>), ('consult_courses', <django.forms.models.ModelMultipleChoiceField object at 0x000000000480B048>), ('consult_content', <django.forms.fields.CharField object at 0x000000000480B0B8>), ('status', <django.forms.fields.TypedChoiceField object at 0x000000000480B208>), ('consultant', <django.forms.models.ModelChoiceField object at 0x000000000480B2E8>)])
12         #这张表中的所有字段对象
13         for field_name,field_obj in dict(cls.base_fields).items():
14             field_obj.widget.attrs.update({'class':"form-control"})
15 
16         return ModelForm.__new__(cls)
17 
18 实验:使用固定的数据模型去生成对应的form验证类,可以用来在前端之间生成控件
实验:使用固定的数据模型去生成对应的form验证类,可以用来在前端之间生成控件

2.如何针对每张表动态生成一个Form类?需要用到type方法去动态生成类

推文:python---基础知识回顾(三)(面向对象)

 

 1 from django.forms import ModelForm,ValidationError
 2 
 3 def create_dynamic_model_form(admin_class,request,flag=False):
 4     """动态生成modelform"""
 5     class Meta:
 6         # 绑定model_class
 7         model = admin_class.model
 8         # 所有字段
 9         fields = '__all__'
10         # 判断操作方式,默认false为change,true为添加
11         if not flag:  # change
12             # 排除只读字段
13             # exclude = admin_class.readonly_fields
14             # 由于admin_class实例一直未变,当修改值时会一直保持状态不变,所以需改变状态需重新赋值
15             admin_class.form_add = False
16         else: # add
17             if request.user.__class__ == admin_class.model:
18                exclude = ['password']
19             admin_class.form_add = True
20 
21     def __new__(cls,*args,**kwargs):
22         for field_name in cls.base_fields:
23             field_obj = cls.base_fields[field_name]
24             field_obj.widget.attrs.update({'class':'form-control'})
25             if not flag:  # change
26                 if field_name in admin_class.readonly_fields:
27                     # 当属性disabled为True时,form表单提交时不会在提交数据
28                     field_obj.widget.attrs.update({'disabled': 'true'})
29         return ModelForm.__new__(cls)
30 
31     def clean(self):
32         """from default clean method"""
33         print('cleaned_data',self.cleaned_data)
34         if self.errors:  # 表单级错误
35             raise ValidationError(('Please fix errors before re-submit.'))
36         if self.instance.id is not None: # means this is a change form, should check the readonly fields
37             for field in admin_class.readonly_fields:
38                 old_field_val = getattr(self.instance,field)  # 数据库里的数据
39                 form_val = self.cleaned_data.get(field)
40                 print('old_field_val',old_field_val,'form_val',form_val)
41                 if old_field_val != form_val:  # 添加字段错误
42                     self.add_error(field,"Readonly Field: field should be '{value}' ,not'{new_value}'".format(**{'value':old_field_val,'new_value':form_val}))
43 
44     # 动态生成model_class相关form类
45     dynamic_form = type('DynamicModelForm',(ModelForm,),{'Meta':Meta, '__new__':__new__,'clean':clean})
46 
47     return dynamic_form
form_handle.py中去创建方法,动态创建类

3.在修改页面中动态创建Form类(需要传递原来数据)

 1 """kingadmin数据修改页"""
 2 # @check_permission
 3 @login_required
 4 def table_obj_change(request,app_name,model_name,obj_id):
 5     # from crm.forms import CustomerInfoForm
 6     # form = CustomerInfoForm()
 7     admin_class = site.enabled_admins[app_name][model_name]
 8 
 9     # 调用函数,并传入model_class动态生成form类
10     model_form = create_dynamic_model_form(admin_class,request)
11     obj = admin_class.model.objects.get(id=obj_id)
12 
13     if request.method == "GET":
14         form_obj = model_form(instance=obj)
15     elif request.method == "POST":
16         form_obj = model_form(instance=obj,data=request.POST)
17         if form_obj.is_valid():
18             # ModelForm自动更新
19             form_obj.save()
20             return redirect('/kingadmin/%s/%s' %(app_name,model_name))
21     return render(request, 'kingadmin/table_obj_change.html',{
22         'form_obj':form_obj,
23         'admin_class':admin_class,
24         'app_name':app_name,
25         'model_name':model_name,
26     })
table_obj_change方法去创建form类,传递到前端

 =================================

1 re_path('^(\w+)/(\w+)/(\d+)/change/$', views.table_obj_change,name='table_obj_change'),
url中对于修改的匹配

4.在添加页面动态创建Form类

 1 """kingadmin数据增加页"""
 2 # @check_permission
 3 @login_required
 4 def table_obj_add(request,app_name,model_name):
 5     admin_class = site.enabled_admins[app_name][model_name]
 6     model_form = create_dynamic_model_form(admin_class,request,flag=True)
 7     if request.method == "GET":
 8         form_obj = model_form()
 9     elif request.method == "POST":
10         form_obj = model_form(data=request.POST)
11         if form_obj.is_valid():
12             form_obj.save()
13             return redirect('/kingadmin/%s/%s' % (app_name, model_name))
14     return render(request, 'kingadmin/table_obj_add.html', {
15         'form_obj': form_obj,
16         'admin_class': admin_class,
17     })
table_obj_add方法

==================

1  re_path('^(\w+)/(\w+)/add', views.table_obj_add,name='table_obj_add'),
url.py中对于添加的匹配

添加的url

5.修改和添加的HTML和公共部分

 1 {% extends 'kingadmin/index.html' %}
 2 {# 导入标签模块 #}
 3 {% load kingadmin_tag %}  {# build_table_row' is not a registered tag library 重启即可 #}
 4 
 5 {% block right-content-container %}
 6     <h1 class="page-header">{% get_model_name admin_class %}</h1>
 7     <h4 class="page-header">ADD {% get_model_name admin_class %}</h4>
 8 
 9 {% include 'kingadmin/table_obj_component.html' %}
10 
11 {% endblock %}
table_obj_add.html

===================

 1 {% extends 'kingadmin/index.html' %}
 2 {# 导入标签模块 #}
 3 {% load kingadmin_tag %}  {# build_table_row' is not a registered tag library 重启即可 #}
 4 
 5 {% block right-content-container %}
 6     <h1 class="page-header">{% get_model_name admin_class %}</h1>
 7     <ol class="breadcrumb">
 8       <li><a href="/kingadmin/">Home</a></li>
 9       <li><a href="/kingadmin/{{app_name}}/">{{app_name}}</a></li>
10       <li><a href="/kingadmin/{{app_name}}/{{model_name}}/">{% get_model_verbose_name admin_class %}</a></li>
11       <li class="active">{{form_obj.instance}}</li>
12     </ol>
13     <h4 class="page-header">CHANGE {{form_obj.instance.name.upper}}</h4>
14 
15 {% include 'kingadmin/table_obj_component.html' %}
16 
17 {% endblock %}
table_obj_change.html

========================

  1  {% load kingadmin_tag %}
  2 
  3 <form class="form-horizontal" method="post" onsubmit="VerificationBeforeFormSubmit()" novalidate>{% csrf_token %}
  4 
  5     {% for field in form_obj %}
  6         <div class="form-group">
  7             <label class="col-sm-2 control-label">{{field.label}}:</label>
  8             <div class="col-sm-6">
  9                 <!--处理horizontal字段-->
 10                 {% if field.name in admin_class.filter_horizontal %}
 11                     <!--左选框-->
 12                     <div class="col-lg-5">
 13 
 14                         <!--模糊匹配-->
 15                         <input type="search" class="form-control" oninput="FuzzSearch(this);">
 16 
 17                         <!--使用动态字段,保证跳转唯一ID-->
 18                         <select id="id_{{field.name}}_from"  class="form-control" multiple>
 19                             <!--调用并返回available_m2m_data-->
 20                             {% get_available_m2m_data field.name admin_class form_obj as available_m2m_data %}
 21                                 {% for obj in available_m2m_data %}
 22                                     <!--双击事件,传入this跟目标ID-->
 23                                     <option ondblclick="MoveSelectedOption(this,'id_{{field.name}}_to');" value="{{obj.id}}">{{obj}}</option>
 24                                 {% endfor %}
 25                         </select>
 26 
 27                         <!--实现一键全选-->
 28                         <a onclick="MoveAllOption('id_{{field.name}}_from','id_{{field.name}}_to');" href="#">Choose All</a>
 29                     </div>
 30 
 31                     <!--右选框-->
 32                     <div class="col-lg-5">
 33                         <select tag="selected_m2m" id="id_{{field.name}}_to" name="{{field.name}}" class="form-control" multiple>
 34                             {% get_selected_m2m_data field.name form_obj as selected_m2m_data %}
 35                             {% for obj in selected_m2m_data %}
 36                                 <option ondblclick="MoveSelectedOption(this,'id_{{field.name}}_from');" value="{{obj.id}}">{{obj}}</option>
 37                             {% endfor %}
 38                         </select>
 39                         <a onclick="MoveAllOption('id_{{field.name}}_to','id_{{field.name}}_from');" href="#">Remove All</a>
 40                     </div>
 41 
 42                 <!--一般字段-->
 43                 {% else %}
 44                     {{field}}
 45                 {% endif %}
 46 
 47                 <!--错误提醒-->
 48                 <span style="color:red;">{{field.errors.0}}</span>
 49             </div>
 50         </div>
 51     {% endfor %}
 52 
 53     <div class="form-group">
 54         <!--判断是否为修改页面,删除按钮显示与否-->
 55         {% if form_obj.instance.id %}
 56             <div class="col-sm-10">
 57                 <a href="{% url 'table_obj_delete' app_name model_name form_obj.instance.id %}" class="btn btn-danger">Delete</a>
 58             </div>
 59         {% endif %}
 60         <div class="col-sm-offset-7 col-sm-10">
 61             <button type="submit" class="btn btn-info">Save</button>
 62         </div>
 63     </div>
 64  </form>
 65 
 66  <script>
 67     // 双击事件
 68      function MoveSelectedOption(this_ele,target_id) {
 69         // 获取跳转目标的ID
 70         var new_target_id = $(this_ele).parent().attr('id');
 71         var new_val = $(this_ele).val();
 72         var new_text = $(this_ele).text();
 73 
 74         // 创建option标签,ondbclick=MoveSelectedOption 无需引号,有引号时则无法返回
 75         var option = "<option ondblclick=MoveSelectedOption(this,'" + new_target_id + "'); value='" + new_val + "'>"
 76                             + new_text + "</option>";
 77 
 78         // 添加,为什么$("#target_id").append(option)不行????,因为target_id为传的字符串参数,而不是属性名
 79         $("#" + target_id).append(option);
 80 
 81         // 删除
 82         $(this_ele).remove();
 83      }
 84 
 85      // form表单提交前的检测
 86      function VerificationBeforeFormSubmit() {
 87         // 移除所有‘disabled’属性
 88         $(':disabled').removeAttr('disabled');
 89 
 90         // 选中有tag属性的select下所有option标签
 91         $("select[tag] option").prop('selected',true);
 92      }
 93 
 94      // 一键全选
 95      function MoveAllOption(from_id,to_id) {
 96             $('#' + from_id).children().each(function() {
 97             MoveSelectedOption(this,to_id);
 98         });
 99      }
100 
101      // 模糊匹配
102      function FuzzSearch(ele) {
103         var search_text = $(ele).val().toUpperCase();
104 
105         // ‘ele’标签后一个的所有孩子标签循环
106         $(ele).next().children().each(function() {
107             if($(this).text().toUpperCase().search(search_text) != -1) {
108                 // 匹配有则显示
109                 $(this).show();
110             }else{
111                 $(this).hide();
112             }
113         });
114      }
115  </script>
table_obj_change_component.html公共部分

6.处理在add和change中对于readonly_fileds字段的不同(使用其他方式处理readonly_fields可跳过)

 1 {% if not admin_class.add_flag %}
 2                 {% for field_name in admin_class.readonly_fields %}
 3                      <div class="form-group">
 4                         <label class="col-sm-2 control-label">{{ field_name }}</label>
 5                         <div class="col-sm-10">
 6                                 <p class="text-left">{% get_field_value_p field_name form_obj %}</p>
 7                         </div>
 8                      </div>
 9                 {% endfor %}
10             {% endif %}
11 
12 在动态生成ModelForm修改,并且向admin_class.add_flag加入标识,前端进行判别,决定是否去显示只读字段
在动态生成ModelForm修改,并且向admin_class.add_flag加入标识,前端进行判别,决定是否去显示只读字段

7.对于filter_horizontal字段我们在模板函数中进行获取选中值与未选中值,分别显示在不同的选择框

 1 <!--处理horizontal字段-->
 2                 {% if field.name in admin_class.filter_horizontal %}
 3                     <!--左选框-->
 4                     <div class="col-lg-5">
 5 
 6                         <!--模糊匹配-->
 7                         <input type="search" class="form-control" oninput="FuzzSearch(this);">
 8 
 9                         <!--使用动态字段,保证跳转唯一ID-->
10                         <select id="id_{{field.name}}_from"  class="form-control" multiple>
11                             <!--调用并返回available_m2m_data-->
12                             {% get_available_m2m_data field.name admin_class form_obj as available_m2m_data %}
13                                 {% for obj in available_m2m_data %}
14                                     <!--双击事件,传入this跟目标ID-->
15                                     <option ondblclick="MoveSelectedOption(this,'id_{{field.name}}_to');" value="{{obj.id}}">{{obj}}</option>
16                                 {% endfor %}
17                         </select>
18 
19                         <!--实现一键全选-->
20                         <a onclick="MoveAllOption('id_{{field.name}}_from','id_{{field.name}}_to');" href="#">Choose All</a>
21                     </div>
22 
23                     <!--右选框-->
24                     <div class="col-lg-5">
25                         <select tag="selected_m2m" id="id_{{field.name}}_to" name="{{field.name}}" class="form-control" multiple>
26                             {% get_selected_m2m_data field.name form_obj as selected_m2m_data %}
27                             {% for obj in selected_m2m_data %}
28                                 <option ondblclick="MoveSelectedOption(this,'id_{{field.name}}_from');" value="{{obj.id}}">{{obj}}</option>
29                             {% endfor %}
30                         </select>
31                         <a onclick="MoveAllOption('id_{{field.name}}_to','id_{{field.name}}_from');" href="#">Remove All</a>
32                     </div>
前端实现

============================

 1 """返回m2m字段关联表的未选中的数据"""
 2 @register.simple_tag
 3 def get_available_m2m_data(field_name,admin_class,form_obj):
 4     field_obj = admin_class.model._meta.get_field(field_name)  # 获取该字段字段对象
 5     # 通过字段对象,退出关联model,并获取该字段关联的所有数据
 6     obj_all_list = set(field_obj.related_model.objects.all())
 7     obj_selected_list = []
 8     try:
 9         obj_selected_list = getattr(form_obj.instance, field_name).all()
10     except TypeError:  # 异常处理
11         pass
12     obj_available_list = obj_all_list - set(obj_selected_list)   # 求差集
13     return obj_available_list
get_available_m2m_data模板函数,返回为选中值

===========================

1 """返回m2m字段关联表的选中数据"""
2 @register.simple_tag
3 def get_selected_m2m_data(field_name,form_obj):
4     obj_selected_list = []
5     try:
6         obj_selected_list = set(getattr(form_obj.instance,field_name).all())
7     except TypeError:
8         pass
9     return obj_selected_list
get_selected_m2m_data模板函数,返回已选中值
8.实现js双击option,在两个select之间跳转

           

 

1 <option ondblclick="MoveSelectedOption(this,'id_{{field.name}}_to');" value="{{obj.id}}">{{obj}}</option>
2 
3 <option ondblclick="MoveSelectedOption(this,'id_{{field.name}}_from');" value="{{obj.id}}">{{obj}}</option>
为两个select标签绑定同一个MoveSelectedOption方法

==========================

 1 // 双击事件
 2      function MoveSelectedOption(this_ele,target_id) {
 3         // 获取跳转目标的ID
 4         var new_target_id = $(this_ele).parent().attr('id');
 5         var new_val = $(this_ele).val();
 6         var new_text = $(this_ele).text();
 7 
 8         // 创建option标签,ondbclick=MoveSelectedOption 无需引号,有引号时则无法返回
 9         var option = "<option ondblclick=MoveSelectedOption(this,'" + new_target_id + "'); value='" + new_val + "'>"
10                             + new_text + "</option>";
11 
12         // 添加,为什么$("#target_id").append(option)不行????,因为target_id为传的字符串参数,而不是属性名
13         $("#" + target_id).append(option);
14 
15         // 删除
16         $(this_ele).remove();
17      }
方法实现:通过判断父标签select的id,将当前option转移append到对方的select中
9.实现在点击保存时,form表单自动将右侧select中的数据全部选中。注意:加上name为select标签,name="字段名" 
1 <form class="form-horizontal" method="post" onsubmit="VerificationBeforeFormSubmit()" novalidate>{% csrf_token %}
为form表单绑定方法VerificationBeforeFormSubmit

==================

1 // form表单提交前的检测
2      function VerificationBeforeFormSubmit() {
3         // 移除所有‘disabled’属性
4         $(':disabled').removeAttr('disabled');
5 
6         // 选中有tag属性的select下所有option标签
7         $("select[tag] option").prop('selected',true);
8      }
VerificationBeforeFormSubmit实现

10.为filter_horizontal完善功能,添加全选,全部移除,模糊匹配

1 <!--实现一键全选-->
2                         <a onclick="MoveAllOption('id_{{field.name}}_from','id_{{field.name}}_to');" href="#">Choose All</a>
3 
4                         <a onclick="MoveAllOption('id_{{field.name}}_to','id_{{field.name}}_from');" href="#">Remove All</a>
前端HTML

===============

1 // 一键全选
2      function MoveAllOption(from_id,to_id) {
3             $('#' + from_id).children().each(function() {
4             MoveSelectedOption(this,to_id);
5         });
6      }
MoveAllOption函数js代码

11.为filter_horizontal完善功能之模糊匹配

1 <!--模糊匹配-->
2 <input type="search" class="form-control" oninput="FuzzSearch(this);">
前端HTML

================

 1 // 模糊匹配
 2      function FuzzSearch(ele) {
 3         var search_text = $(ele).val().toUpperCase();
 4 
 5         // ‘ele’标签后一个的所有孩子标签循环
 6         $(ele).next().children().each(function() {
 7             if($(this).text().toUpperCase().search(search_text) != -1) {
 8                 // 匹配有则显示
 9                 $(this).show();
10             }else{
11                 $(this).hide();
12             }
13         });
14      }
FuzzSearch函数js实现代码

 

Day5:删除功能开发和action方法实现

1.删除功能开发

 1 {% extends 'kingadmin/index.html' %}
 2 {# 导入标签模块 #}
 3 {% load kingadmin_tag %}  {# 'build_table_row' is not a registered tag library 重启即可 #}
 4 
 5 {% block right-content-container %}
 6     <!--app_name and model_name可传可求-->
 7     <span>{% get_model_name admin_class as model_name%}</span>
 8     <h1 class="page-header">{{model_name.upper}}</h1>
 9 
10     <!--展示提醒删除信息-->
11     {% for obj in selected_objs %}
12         <div>
13             <h4 class="page-header alert-danger">注意:以下与{{obj}}相关的将被删除</h4>
14                 {% get_display_all_related_objs obj as all_related_eles %}
15                 {{ all_related_eles|safe }}
16         </div>
17     {% endfor %}
18 <hr>
19     <form method="post">{% csrf_token %}
20         <input type="submit" class="btn btn-danger" value="Yes,I'm sure">
21         <!--判断是否为批量删除-->
22         {% if queryset_ids %}
23             <!--隐藏input标签,用于区别是否为批量删除,传递数据-->
24             <input type="hidden" name="selected_ids" value="{{queryset_ids}}">
25             <a class="btn btn-info" href="">No,take me back</a>
26         {% else %}
27             <a class="btn btn-info" href="/kingadmin/{{app_name}}/{{model_name}}/{{selected_objs.0.id}}/change">No,take me back</a>
28         {% endif %}
29     </form>
30 
31 {% endblock %}
前端HTML代码

============

 1 """显示被删除对象的所有关联对象"""
 2 @register.simple_tag
 3 def get_display_all_related_objs(obj):
 4     """
 5     关联:只需列出M2M
 6     反向关联:
 7         M2M   列出,影响
 8         O2O   列出,删除,递归
 9         FK    列出,删除,递归
10     :param obj:
11     :return:
12     """
13     ele = '<ul>'
14 
15     # M2M只列出,不递归,只影响,不删除
16     M2M_obj_list = obj._meta.many_to_many
17     for M2M in M2M_obj_list:
18         # 该字段关联的所有对象
19         for i in getattr(obj,M2M.name).all():
20             ele += '<li>%s:<a href="/kingadmin/%s/%s/%s/change">%s</a>——记录里与[%s]相关的数据将被影响</li>' \
21                    % (M2M.name, i._meta.app_label, i._meta.model_name, i.id, i, obj)
22 
23     # 反向关联数据
24     reversed_fk_list = obj._meta.related_objects
25     for reversed_fk_obj in reversed_fk_list:
26         reversed_table_name = reversed_fk_obj.name
27 
28         # 区别M2M、FK和O2O的反向查找
29         if reversed_fk_obj.one_to_one:  # OneToOne关系,删除,并递归
30             # 如果没有对象赋值给这个关联关系,Django 将引发一个DoesNotExist异常
31             related_objs = [getattr(obj, reversed_table_name),]
32         else:
33             related_lookup_key = '%s_set' %reversed_table_name
34             related_objs = getattr(obj,related_lookup_key).all()  # 反向关联的所有数据
35 
36         # 区别M2M和O2O、FK的反向查找后是否递归
37         if reversed_fk_obj.many_to_many:  # M2M则不深入
38             for i in related_objs:
39                 ele += '<li>%s:<a href="/kingadmin/%s/%s/%s/change">%s</a>——记录里与[%s]相关的数据将被影响</li>' \
40                        % (reversed_table_name,i._meta.app_label,i._meta.model_name,i.id,i,obj)
41         else:  # reversed_fk(one_to_many) or reversed_o2o 关系
42             for i in related_objs:
43                 ele += '<li>%s:<a href="/kingadmin/%s/%s/%s/change">%s</a>——记录里与[%s]相关的数据将被删除</li>' \
44                        % (reversed_table_name,i._meta.app_label,i._meta.model_name,i.id,i,obj)
45                 ele += get_display_all_related_objs(i)
46 
47     ele += '</ul>'
48     return ele
模板函数,去递归生成标签

============

 1 """kingadmin数据删除页"""
 2 # @check_permission
 3 @login_required
 4 def table_obj_delete(request,app_name,model_name,obj_id):
 5     admin_class = site.enabled_admins[app_name][model_name]
 6     obj = admin_class.model.objects.get(id=obj_id)
 7     if request.method == "POST":
 8         obj.delete()
 9         return redirect('/kingadmin/{app_name}/{model_name}/'.format(app_name=app_name,model_name=model_name))
10     return render(request, 'kingadmin/table_obj_delete.html',{
11         'admin_class':admin_class,
12         'app_name':app_name,
13         'selected_objs':[obj,],
14 
15     })
views后台删除代码

 

2.action字段功能完善
 1 class CustomerAdmin(BaseKingAdmin):
 2     list_display = ['name', 'source', 'contact_type', 'contact', 'consultant', 'consult_content', 'status', 'date']
 3     list_filter = ['source', 'consultant', 'status', 'date']
 4     search_fields = ['name','contact','source']
 5     readonly_fields = ['contact','status']
 6     filter_horizontal = ['consult_courses']
 7     actions = ['change_status', ]
 8     list_per_page = 2
 9     per_page_num = 2
10 
11     def change_status(self, request, querysets):
12         print(self, request, querysets)
13         querysets.update(status=0)
kingadmin.py中放置action字段,包含有自定义方法

==============================

 1 class BaseKingAdmin(object):
 2     def __init__(self):
 3         self.actions.extend(self.default_action)
 4     list_display = []
 5     list_filter = []
 6     search_fields = []
 7     readonly_fields = []
 8     filter_horizontal = []
 9     list_per_page = 10
10     per_page_num = 10
11     default_action = ['delete_selected_objs']
12     actions = []
13 
14     def delete_selected_objs(self,request,querysets):
15         print('delete_selected_objs',self,request,querysets)
16 
17         queryset_ids = json.dumps([i.id for i in querysets])  # 获取所有id
18         return render(request, 'kingadmin/table_obj_delete.html',{
19             'selected_objs':querysets,
20             'admin_class':self,
21             'queryset_ids':queryset_ids,
22         })
admin_base.py中需要去设置action默认数据

 

(1)设置form表单布局

 1     <!--action批量操作-->
 2     <form onsubmit="return ActionCheck(this);" method="post"> {% csrf_token %}
 3         <div class="row">
 4             <div class="col-lg-3">
 5                 <select class="form-control" name="action">
 6                     <!--若无value则默认为值-&#45;&#45;&#45;&#45;,只有value=""值才为空 -->
 7                     <option value="">------------</option>
 8                     {% for action in admin_class.actions %}
 9                         <option value="{{action}}">{{action}}</option>
10                     {% endfor %}
11                 </select>
12            </div>
13             <div class="col-lg-2">
14                 <input class="btn btn-success" type="submit" value="GO">
15             </div>
16         </div>
17     </form>
Form表单

(2)设置复选框完成全选功能

1 <th><input onclick="SelectAllObjs(this);" type="checkbox"></th>
2 
3 
4 <td><input row-select='true' type="checkbox" value="{{obj.id}}"></td>
HTML代码

===========

1 // 实现一键CheckBox全选
2     function SelectAllObjs(ele) {
3         if($(ele).prop('checked')) {
4             $('input[row-select]').prop('checked',true);
5         }else{
6             $('input[row-select]').prop('checked',false);
7         }
8     }
SelectAllObjes方法js完成全选

(3)提交表单前先生成隐藏表单去获取数据集

 1 // form表单提交前检测
 2     function ActionCheck(ele) {
 3         var selected_action = $("select[name='action']").val();
 4         var selected_objs = $('input[row-select]').filter(':checked');
 5         console.log(selected_action);
 6         // 未选中action
 7         if(!selected_action) {
 8             alert('no action selected!');
 9             // ?????
10             return false
11         }
12 
13         if(selected_objs.length == 0) {
14             // 未选中CheckBox
15             alert('no object selected!');
16             return false
17         }else{
18             // 把所有选中行的ID打包
19             var selected_ids = [];
20             $.each(selected_objs,function() {
21                 selected_ids.push($(this).val());
22             });
23             /*
24                 创建input隐藏标签
25                 JSON.stringify(selected_ids)之后为字符串,无需再加引号
26             */
27             var input_ele = '<input type="hidden" name="selected_ids" value=' + JSON.stringify(selected_ids) + '>'
28             $(ele).append(input_ele);
29         }
30         // 返回则不提交数据
31         //return false
32     }
ActionCheck方法生成一个人input标签

(4)传递到后端进行处理

 1 """取出指定model里的数据返回给前端"""
 2 # @check_permission
 3 @login_required()
 4 def table_obj_list(request,app_name,model_name):
 5     admin_class = site.enabled_admins[app_name][model_name]
 6 
 7     # print('before action', admin_class.actions)
 8     """
 9         1.去重
10             因为实例化admin_class就会在actions中加一个默认值
11         2.为什么未定制actions时会添加?而且实例化的类都不一样?
12             因为自定义的类都继承了KingAdminBase基类
13         3.定制时也一样都会添加,为什么最后只有一个默认值呢?
14             原因是会被定制的actions里的值覆盖,
15             然后在添加自己实例化的那个默认值
16     """
17     admin_class.actions = list(set(admin_class.actions))
18     # print('after action',admin_class.actions)
19 
20     if request.method == "POST":
21         # print(request.POST)
22         selected_action = request.POST.get('action')
23         selected_ids = json.loads(request.POST.get('selected_ids'))
24 
25         """
26             判断为action时可以跳转到其他视图操作会更简洁
27         """
28 
29         if selected_action: # 如果有action参数代表为正常的action
30             selected_objs = admin_class.model.objects.filter(id__in=selected_ids).all()
31             admin_action_func = getattr(admin_class, selected_action)
32             # print(admin_action_func,selected_objs)
33             # 必须要return返回,不然无法渲染
34             return admin_action_func(request,selected_objs)  # 第一次POST提交
35         elif not selected_action:  # 如果没有action,代表可以是一个删除动作
36             if selected_ids:  # 选中的数据都被删除
37                 admin_class.model.objects.filter(id__in=selected_ids).delete()  # 第二次POST提交
在table_obj_list方法添加上post方法

 3.处理action中的默认行为delete批量删除

(1)提交的url不是上面的table_obj_delete,而是本页面和change_status一起作为action传递入当前url

1  def delete_selected_objs(self,request,querysets):
2         print('delete_selected_objs',self,request,querysets)
3 
4         queryset_ids = json.dumps([i.id for i in querysets])  # 获取所有id
5         return render(request, 'kingadmin/table_obj_delete.html',{
6             'selected_objs':querysets,
7             'admin_class':self,
8             'queryset_ids':queryset_ids,
9         })
delete_selected_objs的action方法

(2)获取delete_selected_objs在table_obj_list方法中返回

 1     if request.method == "POST":
 2         # print(request.POST)
 3         selected_action = request.POST.get('action')
 4         selected_ids = json.loads(request.POST.get('selected_ids'))
 5 
 6         """
 7             判断为action时可以跳转到其他视图操作会更简洁
 8         """
 9 
10         if selected_action: # 如果有action参数代表为正常的action
11             selected_objs = admin_class.model.objects.filter(id__in=selected_ids).all()
12             admin_action_func = getattr(admin_class, selected_action)
13             # print(admin_action_func,selected_objs)
14             # 必须要return返回,不然无法渲染
15             return admin_action_func(request,selected_objs)  # 第一次POST提交
16         elif not selected_action:  # 如果没有action,代表可以是一个删除动作
17             if selected_ids:  # 选中的数据都被删除
18                 admin_class.model.objects.filter(id__in=selected_ids).delete()  # 第二次POST提交
table_obj_list中对于post的处理

若是执行完action方法后没有返回值则是正常执行,如果有返回值,则是代表我们接下来是执行删除操作。需要返回

(3)我们还是调用的上面的table_obj_delete.html页面,但是其中的模板标签函数,是针对一个数据对象,而现在是一个数据集,我们需要再次处理

 1 """显示被删除对象的所有关联对象"""
 2 @register.simple_tag
 3 def get_display_all_related_objs(obj):
 4     """
 5     关联:只需列出M2M
 6     反向关联:
 7         M2M   列出,影响
 8         O2O   列出,删除,递归
 9         FK    列出,删除,递归
10     :param obj:
11     :return:
12     """
13     ele = '<ul>'
14 
15     # M2M只列出,不递归,只影响,不删除
16     M2M_obj_list = obj._meta.many_to_many
17     for M2M in M2M_obj_list:
18         # 该字段关联的所有对象
19         for i in getattr(obj,M2M.name).all():
20             ele += '<li>%s:<a href="/kingadmin/%s/%s/%s/change">%s</a>——记录里与[%s]相关的数据将被影响</li>' \
21                    % (M2M.name, i._meta.app_label, i._meta.model_name, i.id, i, obj)
22 
23     # 反向关联数据
24     reversed_fk_list = obj._meta.related_objects
25     for reversed_fk_obj in reversed_fk_list:
26         reversed_table_name = reversed_fk_obj.name
27 
28         # 区别M2M、FK和O2O的反向查找
29         if reversed_fk_obj.one_to_one:  # OneToOne关系,删除,并递归
30             # 如果没有对象赋值给这个关联关系,Django 将引发一个DoesNotExist异常
31             related_objs = [getattr(obj, reversed_table_name),]
32         else:
33             related_lookup_key = '%s_set' %reversed_table_name
34             related_objs = getattr(obj,related_lookup_key).all()  # 反向关联的所有数据
35 
36         # 区别M2M和O2O、FK的反向查找后是否递归
37         if reversed_fk_obj.many_to_many:  # M2M则不深入
38             for i in related_objs:
39                 ele += '<li>%s:<a href="/kingadmin/%s/%s/%s/change">%s</a>——记录里与[%s]相关的数据将被影响</li>' \
40                        % (reversed_table_name,i._meta.app_label,i._meta.model_name,i.id,i,obj)
41         else:  # reversed_fk(one_to_many) or reversed_o2o 关系
42             for i in related_objs:
43                 ele += '<li>%s:<a href="/kingadmin/%s/%s/%s/change">%s</a>——记录里与[%s]相关的数据将被删除</li>' \
44                        % (reversed_table_name,i._meta.app_label,i._meta.model_name,i.id,i,obj)
45                 ele += get_display_all_related_objs(i)
46 
47     ele += '</ul>'
48     return ele
简单改变模板函数get_del_obj,将原来单个对象也改写为可迭代

(4)我们提交数据,也不再是table_obj_delete方法,而是table_obj_list方法,所以我们需要传递一个数据代表要删除的数据id集合,同时一个一个标识

=======  方式一 =======

1 <!--隐藏input标签,用于区别是否为批量删除,传递数据-->
2             <input type="hidden" name="selected_ids" value="{{queryset_ids}}">
加入隐藏标签

======= 方式二 ========

1 <input type="hidden" name="delete_ids" value="{% get_del_objs_id obj %}">
在显示的table_obj_delete.html页面加入隐藏标签,收集所有id集合
1 @register.simple_tag
2 def get_del_objs_id(objs):
3     obj_ser = []
4     for obj in objs:
5         obj_ser.append(obj.id)
6     return json.dumps(obj_ser)
7 
8 get_del_objs_id模板函数收集所有的数据对象的id,json序列化返回给前端
get_del_objs_id模板函数收集所有的数据对象的id,json序列化返回给前端

(5)views页面根据post传递过来的隐藏标签的name,判断是不是执行删除数据操作

1         if selected_action: # 如果有action参数代表为正常的action
2             selected_objs = admin_class.model.objects.filter(id__in=selected_ids).all()
3             admin_action_func = getattr(admin_class, selected_action)
4             # print(admin_action_func,selected_objs)
5             # 必须要return返回,不然无法渲染
6             return admin_action_func(request,selected_objs)  # 第一次POST提交
7         elif not selected_action:  # 如果没有action,代表可以是一个删除动作
8             if selected_ids:  # 选中的数据都被删除
9                 admin_class.model.objects.filter(id__in=selected_ids).delete()  # 第二次POST提交
获取前端input表单名selected_ids,判断是否有数据,来决定是否删除

 4.实现面包屑导航

 

 

1  <h1 class="page-header">Django administration</h1>
2         <ol class="breadcrumb">
3           <li><a href="/kingadmin/">Home</a></li>
4           <li><a href="/kingadmin/{{app_name}}/">{{app_name}}</a></li>
5           <li><a href="/kingadmin/{{app_name}}/{{model_name}}/">{% get_model_verbose_name admin_class %}</a></li>
6           <li class="active">Add {% get_model_name admin_class %}</li>
7         </ol>
8     <h1 class="page-header">Add {% get_model_name admin_class %}</h1>
add页面导航

============

1 <!--面包屑-->
2     <h1 class="page-header">Django administration</h1>
3         <ol class="breadcrumb">
4           <li><a href="/kingadmin/">Home</a></li>
5           <li><a href="/kingadmin/{{app_name}}/">{{app_name.title}}</a></li>
6           <li class="active">{% get_model_verbose_name admin_class %}</li>
7         </ol>
8     <h4 class="page-header">Select {% get_model_verbose_name admin_class %} to change</h4>
list页面导航

===========

1  <h1 class="page-header">Django administration</h1>
2         <ol class="breadcrumb">
3           <li><a href="/kingadmin/">Home</a></li>
4           <li><a href="/kingadmin/{{app_name}}/">{{app_name}}</a></li>
5           <li><a href="/kingadmin/{{app_name}}/{{model_name}}/">{% get_model_verbose_name admin_class %}</a></li>
6           <li class="active">{{form_obj.instance}}</li>
7         </ol>
8     <h4 class="page-header">Change {% get_model_verbose_name admin_class %}</h4>
change页面导航

===========

1 """显示中文"""
2 @register.simple_tag
3 def get_model_verbose_name(admin_class):
4     return admin_class.model._meta.verbose_name
模板函数获取对象的中文名

===========

1 <h1 class="page-header">Django administration</h1>
2         <ol class="breadcrumb">
3           <li><a href="/kingadmin/">Home</a></li>
4           <li><a href="/kingadmin/{{app_name}}/">{{app_name}}</a></li>
5           <li><a href="/kingadmin/{{app_name}}/{{model_name}}/">{% get_model_verbose_name admin_class %}</a></li>
6           <li class="active">{% get_selected_del selected_objs %}</li>
7           <li class="active">Delete</li>
8         </ol>
delete页面导航

==========

1 """返回删除组合对象名列表"""
2 @register.simple_tag
3 def get_selected_del(selected_objs):
4     obj_names = []
5     for obj in selected_objs:
6         obj_names.append(obj.name)
7     print('obj_names',obj_names)
8     return ' | '.join(obj_names)
模板函数组合对象名

 5.左侧菜单状态

 1           <!--左侧菜单栏-->
 2         <div class="col-sm-3 col-md-2 sidebar">
 3           <ul class="nav nav-sidebar">
 4               {% for role in request.user.role.select_related %}
 5                 {% for menu in role.menus.select_related %}
 6                     {% url menu.url_name as dynamic_url %}
 7                     <!--高亮显示,刷新页面的情况用request.path判断,否则可以js-->
 8                     {% if menu.url_name == request.path %}
 9                         <li class="active"><a href="{% if menu.url_type == 0 %}{{menu.url_name}}{% else %}{{dynamic_url}}{% endif %}">{{menu.name}}</a></li>
10                     {% elif dynamic_url == request.path %}
11                         <li class="active"><a href="{% if menu.url_type == 0 %}{{menu.url_name}}{% else %}{{dynamic_url}}{% endif %}">{{menu.name}}</a></li>
12                     {% else %}
13                         <li><a href="{% if menu.url_type == 0 %}{{menu.url_name}}{% else %}{{dynamic_url}}{% endif %}">{{menu.name}}</a></li>
14                     {% endif %}
15                 {% endfor %}
16               {% endfor %}
17           </ul>
18         </div>
index页面在生成url时,对其进行判断。要分辨动态和绝对

Day6:学员报名流程开发

 1 class ContractTemplate(models.Model):
 2     """存储合同模板"""
 3     name = models.CharField(max_length=64)
 4     content = models.TextField()
 5     date = models.DateTimeField(auto_now_add=True)
 6 
 7     class Meta:
 8         verbose_name = '合同'
 9         verbose_name_plural = '合同'
10 
11     def __str__(self):
12         return self.name
13 
14 class StudentEnrollment(models.Model):
15     """学员报名表"""
16     customer = models.ForeignKey('CustomerInfo',on_delete=models.CASCADE)
17     consultant = models.ForeignKey('UserProFile',on_delete=models.CASCADE)
18     class_grade = models.ForeignKey('ClassList',on_delete=models.CASCADE)
19 
20     contract_agreed = models.BooleanField(default=False,verbose_name="学员已经同意合同")  #学员看合同
21     contract_signed_date = models.DateTimeField('合同签订时间',blank=True,null=True)
22     contract_approved = models.BooleanField(default=False,verbose_name="合同已经审核")  #谁审核
23     contract_approved_date = models.DateTimeField('合同审核时间',blank=True, null=True)
24 
25     # ————————53PerfectCRM实现CRM客户报名流程缴费————————
26     # Pay_cost = models.BooleanField(default=False, verbose_name="缴费")  # 缴费状态#是不是交定金
27     # ————————53PerfectCRM实现CRM客户报名流程缴费————————
28 
29     class Meta:
30         verbose_name = '学员报名表'
31         verbose_name_plural = '学员报名表'
32         unique_together = ('customer','class_grade')
33 
34     def __str__(self):
35         return self.customer.name
36 
37 class PaymentRecord(models.Model):
38     """存储学员缴费记录"""
39     enrollment = models.ForeignKey('StudentEnrollment',on_delete=models.CASCADE)
40     payment_type_choice = (
41         (0,'报名费'),
42         (1,'学费'),
43         (2,'退费'),
44     )
45     payment_type = models.SmallIntegerField(choices=payment_type_choice,default=0)
46     amount = models.IntegerField('费用',default=500)
47     consultant = models.ForeignKey('UserProFile', on_delete=models.CASCADE)  #费用缴给谁
48     date = models.DateTimeField(auto_now_add=True)
49 
50     class Meta:
51         verbose_name = '学员缴费记录'
52         verbose_name_plural = '学员缴费记录'
53 
54     def __str__(self):
55         return self.enrollment
新增3张表:学员注册表,合同表(和班级关联),缴费记录表

一:销售为想报名的学员提供链接

 1 @login_required
 2 def stu_enrollment(request):
 3     # 通过前端传来的id默认选中
 4     customer_id = int(request.GET.get('stu_id'))
 5 
 6     customer_objs = models.CustomerInfo.objects.all()
 7     class_objs = models.ClassList.objects.all()
 8     if request.method == "POST":
 9         customer_id = int(request.POST.get('customer_id'))
10         class_grade_id = int(request.POST.get('class_grade_id'))
11         # 顾问为当前用户
12         try:
13             enrollment_obj = models.StudentEnrollment.objects.create(
14                 customer_id = customer_id,
15                 class_grade_id = class_grade_id,
16                 consultant_id = request.user.id,
17             )
18         except IntegrityError as e:   # 已经生成过报名表
19             enrollment_obj = models.StudentEnrollment.objects.get(customer_id = customer_id,class_grade_id=class_grade_id)
20             if enrollment_obj.contract_agreed:
21                 return redirect('/crm/stu_enrollment/%s/contract_audit/' % enrollment_obj.id)
22         enrollment_link = "http://localhost:8888/crm/enrollment/%s/" % enrollment_obj.id
23     return render(request, 'crm/stu_enrollment.html',locals())
stu_enrollment学员注册链接获取

二:学员获取链接,进行填写信息,查阅合同,同意并上传证件信息

 1 def enrollment(request,enrollment_id):
 2     """学员在线报名表"""
 3     enrollment_obj = models.StudentEnrollment.objects.get(id=enrollment_id)
 4 
 5     if enrollment_obj.contract_agreed:
 6         return HttpResponse('正在审核中,请耐心等待!')
 7 
 8     if enrollment_obj.contract_approved:
 9         return HttpResponse('审核已通过,请进行缴费操作')
10 
11     if request.method == "POST":
12         print('POST',request.POST)
13         # 为什么加instance=enrollment_obj.customer???
14         customer_form = forms.CustomerInfoForm(instance=enrollment_obj.customer,data=request.POST)
15         if customer_form.is_valid():
16             customer_form.save()
17             enrollment_obj.contract_agreed = True
18             enrollment_obj.contract_signed_date = datetime.now()
19             enrollment_obj.save()
20             return HttpResponse('您已成功提交报名信息,请等待审核通过,欢迎加入打死都不退费的组织!')
21         print('err',customer_form.errors)
22     else:
23         customer_form = forms.CustomerInfoForm(instance=enrollment_obj.customer)
24 
25     # 列出已上传文件
26     uploaded_files = []
27     enroolment_upload_dir = os.path.join(conf.settings.CRM_UPLOAD_FILE_DIR, enrollment_id)
28     if os.path.isdir(enroolment_upload_dir):
29         uploaded_files = os.listdir(enroolment_upload_dir)
30 
31 
32     return render(request, 'crm/enrollment.html',locals())
enrollment学员在线报名

==================

  1 {% extends 'index.html' %}
  2 
  3 {% block extra-css %}
  4     <link href="/static/plugins/dropzone/basic.css" rel="stylesheet">
  5     <link href="/static/plugins/dropzone/dropzone.css" rel="stylesheet">
  6 {% endblock %}
  7 
  8 {% block body %}
  9 <div class="container">
 10     <div class="panel panel-success">
 11         <div class="panel-heading">
 12             <h3 class="panel-title">学员报名表</h3>
 13         </div>
 14 
 15         <div class="panel-body">
 16             <div>
 17                <form onsubmit="return BeforeFormSubmit(this);" class="form" method="post" novalidate>{% csrf_token %}
 18                     <!--循环field进行样式定制-->
 19                     {% for field in customer_form %}
 20                         <div class="form-group col-lg-6">
 21                             <label class="col-sm-2 control-label">{{field.label}}</label>
 22                             <div class="col-sm-10">
 23                                 {{field}}
 24                                 <span style="color:red;">{{field.errors.0}}</span>
 25                             </div>
 26                         </div>
 27                     {% endfor %}
 28                     <div class="form-group col-lg-6">
 29                         <label class="col-sm-2 control-label">班级</label>
 30                         <div class="col-sm-10">
 31                             <input type="text" class="form-control" value="{{enrollment_obj.class_grade}}" disabled="true">
 32                         </div>
 33                     </div>
 34                     <div class="form-group col-lg-6">
 35                         <label class="col-sm-2 control-label">学费</label>
 36                         <div class="col-sm-10">
 37                             <input type="text" class="form-control" value="{{enrollment_obj.class_grade.course.price}}" disabled="true">
 38                         </div>
 39                     </div>
 40                     <div class="form-group col-lg-12">
 41                         <div>
 42                             <pre style="height:200px;">{{enrollment_obj.class_grade.contract.content}}</pre>
 43                         </div>
 44                         <div>
 45                             <input type="checkbox" name="contract_agreed">
 46                             我已认真阅读,无条件同意
 47                         </div>
 48                     </div>
 49 
 50                     <div>
 51                         <input type="submit" class="btn btn-success pull-right" value="提交">
 52                     </div>
 53 
 54                 </form>
 55             </div>
 56 
 57         </div>
 58         <div class="panel-body">
 59             <p>已上传文件列表:</p>
 60             <ul id="uploaded_file">
 61                 {% for file in uploaded_files %}
 62                     <li>{{file}}</li>
 63                 {% endfor %}
 64             </ul>
 65 
 66 
 67             <form action="{% url 'enrollment_fileupload' enrollment_obj.id %}" class="dropzone" id="myAwesomeDropzone">
 68               <div class="fallback">
 69                 <input name="file" type="file" multiple />
 70               </div>
 71             </form>
 72         </div>
 73         <div class="panel-footer">Panel footer</div>
 74     </div>
 75 </div>
 76 
 77 <script>
 78     function BeforeFormSubmit(ele) {
 79         $(':disabled').removeAttr('disabled');
 80         if (!$('input[name="contract_agreed"]').prop('checked')){
 81             alert('请仔细阅读合同后再提交');
 82             return false;
 83         }
 84         if(!$('#uploaded_file').children().length){
 85             alert('请上传文件');
 86             return false;
 87         }
 88     }
 89 </script>
 90 
 91 {% endblock %}
 92 
 93 
 94 {% block extra-js %}
 95     <script src="/static/plugins/dropzone/dropzone.js"></script>
 96     <script>
 97         // "myAwesomeDropzone" is the camelized version of the HTML element's ID
 98         Dropzone.options.myAwesomeDropzone = {
 99           paramName: "file", // The name that will be used to transfer the file
100           maxFilesize: 2, // MB
101           maxFiles:2,
102           paralleUploads:1,
103           accept: function(file, done) {
104             if (file.name == "justinbieber.jpg") {
105               done("Naha, you don't.");
106             }
107             else { done(); }
108           }
109         };
110 
111 
112         $(function() {
113           // Now that the DOM is fully loaded, create the dropzone, and setup the
114           // event listeners
115           // Prevent Dropzone from auto discovering this element:
116           Dropzone.options.myAwesomeDropzone = false;
117           var myDropzone = new Dropzone("#myAwesomeDropzone");
118           myDropzone.on("success", function(file,response) {
119             /* Maybe display some more file information on your page */
120             console.log('complete',file,response);
121             var response = JSON.parse(response);
122             if (!response.status) {
123                 alert(response.err_msg);
124             }else{
125                 $('#uploaded_file').append('<li>' + file.name + '</li>');
126             }
127           });
128         })
129 
130     </script>
131 
132 {% endblock %}
erollment.html报名页面,含有Dropzone使用

==================

 1 from django.views.decorators.csrf import csrf_exempt
 2 @csrf_exempt
 3 def enrollment_fileupload(request,enrollment_id):
 4     enroolment_upload_dir = os.path.join(conf.settings.CRM_UPLOAD_FILE_DIR,enrollment_id)
 5 
 6     # 判断是否有目录
 7     if not os.path.isdir(enroolment_upload_dir):
 8         os.mkdir(enroolment_upload_dir)
 9     # 为什么要放if外边????
10     file_obj = request.FILES.get('file')
11     # 最多两张
12     if len(os.listdir(enroolment_upload_dir)) < 2:
13         # 获取文件对象
14         with open(os.path.join(enroolment_upload_dir,file_obj.name),'wb') as f:
15             # 分块写入
16             for chunks in file_obj.chunks():
17                 f.write(chunks)
18     else:
19         return HttpResponse(json.dumps({'status': False,'err_msg':'max upload limit 2'}))
20 
21     return HttpResponse(json.dumps({'status':True}))
enrollment_fileupload处理Dropzone文件传输

三:销售审核学员注册信息,审核通过,为其生成账号(密码需要使用Django模块加密),发送邮件

from django.contrib.auth.hashers import make_password  #用于生成密码
 1 def create_stu_user(enrollment_obj):
 2     # 生成一个随机字符串
 3     account = "%s@qq.com" % enrollment_obj.customer.contact
 4     pwd = ''.join(random.sample(string.ascii_letters + string.digits, 8))
 5     ret_data_list = {'create_user':None,'add_stu':None,'send_email':None}
 6     # 创建一个账号
 7     # user = models.User.objects.create(
 8     #     username=account,
 9     #     password=make_password(pwd),
10     # )
11 
12     try:
13         # 创建一个用户
14         userprofile = models.UserProFile.objects.get_or_create(email=account)[0]
15         userprofile.name = enrollment_obj.customer.name
16         userprofile.password = make_password(pwd)
17         userprofile.last_login = datetime.now()
18         userprofile.save()
19         ret_data_list['create_user'] = "成功创建学员用户"
20         print("创建用户",userprofile)
21     except Exception as e:
22         ret_data_list['create_user'] = "创建学员用户失败:%s" % e
23 
24     # 为用户绑定一个角色
25     userprofile.role.add(models.Role.objects.get(name="学员"))
26     print('绑定角色',userprofile.role.all())
27 
28     try:
29         # 学员表,没有就创建,因为一个学生可以报多个班
30         student_obj = models.Student.objects.get_or_create(customer=enrollment_obj.customer)[0]
31         # 多对多添加数据
32         student_obj.class_grade.add(enrollment_obj.class_grade_id)   # 实现一个学员多个班级
33         student_obj.account = userprofile  # 实现一个学员一个用户
34         student_obj.save()
35         ret_data_list['add_stu'] = "成功添加学员"
36         print('学员表账号',student_obj.account,'对象',student_obj)
37     except Exception as e:
38         ret_data_list['add_stu'] = "添加学员失败:%s" % e
39 
40     # 保存到学员表
41     # models.Student.objects.create(
42     #     customer=enrollment_obj.customer,
43     #     class_grades=enrollment_obj.class_grade,
44     #     account=userprofile,
45     # )
46 
47     send_info = "账号:%s\n密码:%s" % (account, pwd)
48     ret = auto_send_email(send_info,enrollment_obj)
49     if ret['status']:
50         ret_data_list['send_email'] = "邮件发送成功!"
51     else:
52         ret_data_list['add_stu'] = "邮件发送失败:%s" % ret['err_msg']
53 
54     return ret_data_list
55 
56 @login_required
57 def contract_audit(request,enrollment_id):
58     enrollment_obj = models.StudentEnrollment.objects.get(id=enrollment_id)
59 
60     if not enrollment_obj.contract_agreed:
61         return HttpResponse('该学员还未提交报名表......')
62 
63     if enrollment_obj.contract_approved:
64         return HttpResponse("审核已通过,等待学员缴费")
65 
66     if request.method == "POST":
67         enrollment_form = forms.EnrollmentForm(instance=enrollment_obj,data=request.POST)
68         if enrollment_form.is_valid():
69             # 完善学生报名表信息
70             enrollment_form.save()
71             enrollment_obj.contract_approved = True
72             enrollment_obj.contract_approved_date = datetime.now()
73             enrollment_obj.customer.status = 1
74             enrollment_obj.customer.save()
75             enrollment_obj.save()
76 
77             # try:
78             #     stu_obj = enrollment_obj.customer.student
79             # except Exception:
80             #     pass
81             # else:
82             #     return HttpResponse(
83             #         "用户%s已添加--->账号为:%s" % (enrollment_obj.customer.name, enrollment_obj.customer.student.account.name))
84 
85 
86             ret = create_stu_user(enrollment_obj)
87 
88             return HttpResponse("%s 审核通过,等待缴费" % ret)
89 
90 
91 
92             # return redirect('/kingadmin/crm/customerinfo/%s/change/' % enrollment_obj.customer.id)
93 
94     else:
95         enrollment_form = forms.EnrollmentForm(instance=enrollment_obj)
96     customer_form = forms.CustomerInfoForm(instance=enrollment_obj.customer)
97 
98     return render(request, 'crm/contract_audit.html',locals())
contract_audit合同审核后生成账号,发送信息

四、自动发送邮件

  方式一:写Python脚本

 1 import smtplib
 2 from email.mime.text import MIMEText
 3 
 4 def SendEmail(send_info,email_to):
 5     email = email_to   #设置收件地址
 6     mailto_list=[email]
 7     mail_host="smtp.qq.com"  #设置服务器
 8     mail_user="2430190125@qq.com"    #用户名
 9     mail_pass="xsickvzytoqtechd"   #口令
10     msg = send_info     #Email内容
11     msssageg = MIMEText(msg, _subtype='html', _charset='gb2312')    #创建一个实例,这里设置为html格式邮件
12     msssageg['Subject'] = "python123"    #设置主题
13     msssageg['From'] = "lujun<2430190125@qq.com>"   #发件地址
14     msssageg['To'] = ";".join(mailto_list)
15     try:
16         s = smtplib.SMTP()
17         s.connect(mail_host)  #连接smtp服务器
18         s.login(mail_user,mail_pass)  #登陆服务器
19         s.sendmail(mail_user, mailto_list, msssageg.as_string())  #发送邮件
20         s.close()
21         return {'status':True}
22     except Exception as e:
23         print(str(e))
24         return {'status':False,'err_msg':e}
25 
26 if __name__ == "__main__":
27     SendEmail();
SendEmail模块

================

1 # python 脚本
2     from crm import sendEmail
3     email_to = "%s@qq.com" % enrollment_obj.customer.contact
4     # 调用自定义发送函数
5     ret = sendEmail.SendEmail(send_info,email_to)
调用模块中SendEmail函数

  方式二:Django自带邮件发送模块

    (1)settings中配置

 1 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
 2 EMAIL_USE_TLS = True
 3 EMAIL_HOST = 'smtp.qq.com'
 4 EMAIL_PORT = 25
 5 EMAIL_HOST_USER = '2430190125@qq.com'
 6 EMAIL_HOST_PASSWORD = '密码'
 7 DEFAULT_FROM_EMAIL = 'lujun<2430190125@qq.com>'
 8 
 9 EMAIL_TITLE = "欢迎加入"
10 EMAIL_CONTENT = "哈哈哈哈哈"
11 EMAIL_FROM = DEFAULT_FROM_EMAIL
12 EMAIL_TO = "123456789@qq.com"
配置文件

    (2)导入模块发送邮件

 1 # django email 发送邮件,需要在settings中配置参数????
 2     # from django.core.mail import send_mail
 3     # from django.conf import settings
 4     # email_title = settings.EMAIL_TITLE
 5     # email_content = settings.EMAIL_CONTENT
 6     # email_from = settings.EMAIL_FROM
 7     # email_to = settings.EMAIL_TO
 8     # send_status = send_mail(
 9     #     email_title,
10     #     email_content,
11     #     email_from,
12     #     [email_to],
13     #     fail_silently=False
14     # )
views中操作

day7:实现讲师和学员作业发布上传功能(其中由于多使用table浏览数据,可以自定义一个类似于form的基类,去统一实现table显示)

一:讲师功能

(1)可以看出上面多是table显示信息,下面自定义table_form类似于forms

  1 import re
  2 
  3 """CustomBaseForm实现通过表数据显示table"""
  4 class CustomBaseForm(object):
  5     display_list = []
  6     field_tag = {}
  7     attrs = {}
  8     extra_field = []
  9 
 10     def __init__(self,model,querysets):
 11         self.instance = model
 12         self.querysets = querysets
 13         self.th = []
 14         self.tr = []
 15 
 16 
 17     def register(self):
 18         for field in self.display_list:
 19             if field == "self":
 20                 self.th.append(self.instance._meta.verbose_name)
 21                 continue
 22             field_obj = self.instance._meta.get_field(field)
 23             self.th.append(field_obj.verbose_name)
 24 
 25         #自定义额外字段
 26         for item in self.extra_field:
 27             for k,v in item.items():
 28                 if v.get("verbose_name"):
 29                     self.th.append(v.get("verbose_name"))
 30                 else:
 31                     self.th.append(k)
 32 
 33         for query in self.querysets:
 34             tds = []
 35             for th in self.display_list:
 36                 if th == "self":
 37                     field_val = "%s" % query
 38                 elif len(self.instance._meta.get_field(th).choices) > 0:
 39                     field_val = "%s"%getattr(query,"get_%s_display"%th)()
 40                 else:
 41                     field_val = "%s"%getattr(query,th)
 42                 if self.field_tag.get(th):
 43                     # {"self": {"a": {"href": "127.0.0.1:8000","href": "127.0.0.1:8000"},"a": {"href": "127.0.0.1:8000"}}}
 44                     tags = self.field_tag.get(th)
 45                     # {"a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}, "a": {"href": "127.0.0.1:8000"}}    #前面是内层,后面是外层
 46                     for k,v in tags.items():
 47                         # "a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}
 48                         new_attr = []
 49                         for k1,v1 in v.items(): #{"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}
 50                             pat = re.compile("\{(.*?)\}")
 51                             res = pat.search(v1)
 52                             while res:
 53                                 v1 = pat.sub(str(getattr(query,res.group(1))),v1,1)
 54                                 res = pat.search(v1)
 55 
 56                             new_attr.append("%s='%s'"%(k1,v1))  #获取到所有的属性放在列表中
 57                         field_val = "<%s %s>%s</%s>"%(k," ".join(new_attr),field_val,k)
 58                 tds.append(field_val)
 59 
 60             for item in self.extra_field:
 61                 for e_k,e_v in item.items():
 62                     if e_v.get("value"):
 63                         field_val = "%s" % e_v.get("value")
 64                     else:
 65                         if hasattr(e_v.get("function"), "__call__"):  # 执行自定义方法
 66                             field_val = e_v.get("function")\
 67                                         (   # 参数
 68                                             getattr(query,e_v.get("model_attr")),
 69                                             *e_v.get("args",()),
 70                                             **e_v.get("kwargs",{})
 71                                         )
 72                         else:  # 执行内置方法
 73                             field_val = getattr(getattr(query,e_v.get("model_attr")),e_v.get("function"))\
 74                                                                                     (   # 参数
 75                                                                                         *e_v.get("args",()),
 76                                                                                         **e_v.get("kwargs",{})
 77                                                                                     )
 78                     if self.field_tag.get(e_k):
 79                         # {"self": {"a": {"href": "127.0.0.1:8000","href": "127.0.0.1:8000"},"a": {"href": "127.0.0.1:8000"}}}
 80                         tags = self.field_tag.get(e_k)
 81                         # {"a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}, "a": {"href": "127.0.0.1:8000"}}    #前面是内层,后面是外层
 82                         for k,v in tags.items():
 83                             # "a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}
 84                             new_attr = []
 85                             for k1,v1 in v.items(): #{"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}
 86                                 pat = re.compile("\{(.*?)\}")
 87                                 res = pat.search(v1)
 88                                 while res:
 89                                     v1 = pat.sub(str(getattr(query, res.group(1))), v1, 1)
 90                                     res = pat.search(v1)
 91                                 new_attr.append("%s='%s'"%(k1,v1))  #获取到所有的属性放在列表中
 92                             field_val = "<%s %s>%s</%s>"%(k," ".join(new_attr),field_val,k)
 93                     tds.append(field_val)
 94 
 95             self.tr.append(tds)
 96 
 97     def __str__(self):
 98         cls_attr = []
 99         if len(self.attrs):
100             for item in self.attrs.items():
101                 cls_attr.append("%s='%s'"%(item[0],item[1]))
102 
103         tb = "<table %s>"%(" ".join(cls_attr))
104 
105         tr = "<thead><tr>"
106         for th_data in self.th:
107             th = "<th>%s</th>"%th_data
108             tr += th
109         tr += "</tr></thead><tbody>"
110 
111         tb += tr
112 
113         for tr_data in self.tr:
114             tr = "<tr>"
115             for td_data in tr_data:
116                 td = "<td>%s</td>"%td_data
117                 tr += td
118             tr += "</tr>"
119             tb += tr
120         tb += "</tbody></table>"
121         return tb
CustomerBaseForm实现通过表数据显示table

(2)使用方法

 1 from kingadmin.custom_base_form import CustomBaseForm
 2 from teacher.custom_form_func import student_score
 3 
 4 class ClassListForm(CustomBaseForm):
 5     def __init__(self, model, querysets):
 6         super(ClassListForm, self).__init__(model, querysets)
 7 
 8     # self代表直接显示本条数据__str__
 9     display_list = ["self", "branch", "class_type", "start_date", "graduate_date"]
10     # 在前面的标签会显示在内层,在显示的数据外面加上标签
11     field_tag = {"self": {"a": {"href": "/kingadmin/crm/classlist/{id}/change/"}, },
12                  "course_record": {"a": {"href": "/teacher/classlist/{id}/course_record.html"}},
13                  "student": {"a": {"href": "/teacher/classlist/{id}/student_list.html"}}}
14     attrs = {"class": "table table-hover"}   # 为table设置属性
15     extra_field = [{"student": {'verbose_name': "学员数量", "model_attr": "student_set", "function": "count"}},
16                    {"course_record": {"verbose_name": "上课记录", "value": "上课记录"}}]
17     """
18         额外自定义字段,若是有值value,会直接输出,
19         否则会去当前实例集self.querysets的每一个实例中去获取相关的数据,
20         使用函数去执行。字符串是调用自己的内置方法,function是调用自定义方法,
21         同时可以使用"args"传递元组,"kwargs":传递字典作为参数
22     """
ClassListForm

===============

 1 class StudentForm(CustomBaseForm):
 2     def __init__(self, model, querysets):
 3         super(StudentForm, self).__init__(model, querysets)
 4 
 5     display_list = ["self"]  # ,"couser","semester"
 6     field_tag = {"self": {"a": {"href": "/kingadmin/crm/student/{id}/change/"}, }, }  # 在前面的标签会显示在内层
 7     attrs = {"class": "table table-hover"}
 8     extra_field = [{"score": {'verbose_name': "学员成绩", "model_attr": "studyrecord_set",'args':'', "function": student_score}},
 9                    {"study_status": {"verbose_name": "出勤状况", "value": "N/A"}}]
10     """
11         额外自定义字段,若是有值value,会直接输出,
12         否则会去当前实例集self.querysets的每一个实例中去获取相关的数据,
13         使用函数去执行。字符串是调用自己的内置方法,function是调用自定义方法
14     """
StudentForm

=================

1 class CourseRecordForm(CustomBaseForm):
2     def __init__(self,model,querysets):
3         super(CourseRecordForm, self).__init__(model,querysets)
4 
5     display_list = ["self","title","content","has_homework","homework","date"]   #,"couser","semester"
6     field_tag = {"self":{"a":{"href":"/kingadmin/crm/courserecord/{id}/change/"},},}   #在前面的标签会显示在内层
7     attrs = {"class":"table table-hover"}
8     extra_field = []
CourseRecordForm

(3)在views中调用各个tableform

 1 from . import forms
 2 from django.contrib.auth.decorators import login_required
 3 
 4 def dashboard(request):
 5     return render(request, 'teacher/dashboard.html')
 6 
 7 @login_required
 8 def course_list(request):
 9     course_querysets = models.ClassList.objects.filter(
10         teachers=request.user
11     )
12 
13     #自定义form,用于table
14     form = forms.ClassListForm(models.ClassList,course_querysets)
15     form.register()
16 
17     return render(request,"teacher/course_list.html",locals())
18 
19 @login_required
20 def student_list(request,c_id):
21     student_querysets = models.ClassList.objects.filter(
22         teachers = request.user,
23         id = c_id
24     ).get().student_set.all()
25 
26     form = forms.StudentForm(models.Student,student_querysets)
27     form.register()
28 
29     return render(request,"teacher/student_list.html",locals())
30 
31 @login_required
32 def course_record_list(request,c_id):
33     course_querysets = models.ClassList.objects.filter(
34         teachers = request.user,
35         id= c_id
36     ).get().courserecord_set.all()
37 
38     form = forms.CourseRecordForm(models.CourseRecord,course_querysets)
39     form.register()
40 
41     return render(request, "teacher/course_record.html", locals())
所有显示table的函数

(4)前端调用{{ form|safe }},form是定义的tableform变量,实现简单显示页面

 1 {% extends 'index.html' %}
 2 
 3 {% load kingadmin_tag %}
 4 
 5 {% block right-content-container %}
 6 
 7 <div class="panel panel-primary">
 8     <div class="panel-heading">
 9         <h3 class="panel-title">课程列表</h3>
10     </div>
11     <div class="panel-body">
12         {{ form|safe }}
13     </div>
14 </div>
15 {% endblock %}
Course_list.html

=================

 1 {% extends 'index.html' %}
 2 
 3 {% load kingadmin_tag %}
 4 
 5 {% block right-content-container %}
 6 
 7 <div class="panel panel-primary">
 8     <div class="panel-heading">
 9         <h3 class="panel-title">课程记录</h3>
10     </div>
11     <div class="panel-body">
12         {{ form|safe }}
13         <a href="{% url 'course_record_add' c_id %}" class="btn btn-primary">添加记录</a>
14     </div>
15 </div>
16 {% endblock %}
course_record.html

=================

 1 {% extends 'index.html' %}
 2 
 3 {% load kingadmin_tag %}
 4 
 5 {% block right-content-container %}
 6 
 7 <div class="panel panel-primary">
 8     <div class="panel-heading">
 9         <h3 class="panel-title">学生列表</h3>
10     </div>
11     <div class="panel-body">
12         {{ form|safe }}
13     </div>
14 </div>
15 {% endblock %}
student_list.html

(5)添加记录

 1 class CourseRecordForm(ModelForm):
 2     class Meta:
 3         model = models.CourseRecord #将表与元类中的数据关联
 4         fields = "__all__"
 5         checkbox_fields = []
 6         readonly_fields = []
 7         # exclude = ["teacher"]
 8 
 9     # 表单样式定制
10     def __new__(cls, *args, **kwargs):
11         return forms_handle.class_form(cls, cls.Meta, *args, **kwargs)
使用form类,生成控件

================

 1 @login_required
 2 def course_record_add(request, c_id):
 3     print(c_id)
 4     if request.method == "POST":
 5         form = crm_forms.CourseRecordForm(data=request.POST)
 6         if form.is_valid():
 7             form.save()
 8             return redirect('/teacher/classlist/%s/course_record.html/' % c_id)
 9     else:
10         form = crm_forms.CourseRecordForm()
11     print(form.errors)
12     return render(request, 'teacher/course_record_add.html',locals())
views调用course_record_add,进行显示和添加数据

==================

 1 {% extends "index.html" %}
 2 {% load kingadmin_tag %}
 3 
 4 {% block right-content-container %}
 5 
 6 <div class="panel panel-primary">
 7     <div class="panel-heading">
 8         <h3 class="panel-title">课程记录添加</h3>
 9     </div>
10     <div class="panel-body">
11         <form action="" method="post" class="form-group form-horizontal" novalidate>
12         {% csrf_token %}
13             {% for field in form %}
14             <div class="col-sm-6">
15                 <div class="col-lg-6">
16                     <label class="control-label">{{ field.label }}</label>
17                 </div>
18                 <div class="col-lg-12">
19                     {{ field }}
20                     <span style="color:red;">{{field.errors.0}}</span>
21                 </div>
22             </div>
23             {% endfor %}
24             <div>
25                 <input type="submit" class="btn btn-primary" value="添加">
26             </div>
27         </form>
28     </div>
29 </div>
30 {% endblock %}
前端显示course_record_add.html

二:学员功能,实现课程显示,作业提交

(一)根据邮件中的账号密码登录

(二)实现查看班级,查看课程记录,提交作业

 

(1)由于这里也是table多使用,可以继续使用tableform

 1 from kingadmin.custom_base_form import CustomBaseForm
 2 
 3 class CourseListForm(CustomBaseForm):
 4     display_list = ["self","class_type","start_date","graduate_date",]   #,"couser","semester"
 5     field_tag = {   #在前面的标签会显示在内层
 6                     "self":{"a":{"href":"127.0.0.1:8000"},},
 7                      'score':{"a":{"href":"127.0.0.1:8080"},},
 8                     'mng_homework':{"a":{"href":"/student/course.html"}}
 9                  }
10     attrs = {"class":"table table-hover"}
11     extra_field = [
12                       {"score":{"verbose_name":"成绩","value":"成绩排名"}},
13                       {"mng_homework":{'verbose_name':"作业管理","value":"作业管理"}},
14                    ]
15 
16 class CourseRecordForm(CustomBaseForm):
17     def __init__(self,model,querysets):
18         super(CourseRecordForm, self).__init__(model,querysets)
19 
20     display_list = ["self","title","teacher","content","has_homework","homework"]   #,"couser","semester"
21     field_tag = {
22                     "self":{"a":{"href":"127.0.0.1:8000"},},
23                     'fin_homework':{"a":{"href":"/stu/homework/{id}.html"}}
24                  }
25     attrs = {"class":"table table-hover"}
26     extra_field = [
27                       {"date":{"verbose_name":"日期","model_attr":"date","function":"strftime","args":("%Y-%m-%d %H:%M:%S",)}},
28                       {"fin_homework":{'verbose_name':"我的作业","value":"提交作业"}},
29                    ]
table_forms文件

(2)view中调用,显示班级和课程,前端也是{{form|safe}}

 1 @login_required
 2 def all_course(request):
 3     course_list = models.ClassList.objects.filter(
 4         student__customer__name = request.user.name
 5     ).all()
 6     form = table_forms.CourseListForm(models.ClassList, course_list)
 7     form.register()
 8 
 9     return render(request,"stu/course_list.html",locals())
10 
11 @login_required
12 def course_record_list(request):
13     # 必须优化为id,表结构设计时student与userProfile关联
14     course_record = models.CourseRecord.objects.filter(
15         class_grade__student__customer__name=request.user.name
16     ).all()
17 
18     form = table_forms.CourseRecordForm(models.CourseRecord, course_record)
19     form.register()
20 
21     return render(request,"stu/course_record.html",locals())
显示班级和课程记录

(3)使用form表单显示数据,使用Dropzone添加作业

 1 from django.forms import ModelForm,forms
 2 from crm import models
 3 
 4 class CourseRecordForm(ModelForm):
 5     class Meta:
 6         model = models.CourseRecord #将表与元类中的数据关联
 7         fields = "__all__"
 8         exclude = ["has_homework"]
 9 
10     def __new__(cls, *args, **kwargs):
11         #这张表中的所有字段对象
12         for field_name,field_obj in dict(cls.base_fields).items():
13             field_obj.widget.attrs.update({'class':"form-control","disabled":"true"})
14 
15         return ModelForm.__new__(cls)
CourseRecordForm

==================

 1 @login_required
 2 def homework(request, course_record_id):
 3     course_record_models = models.CourseRecord.objects.filter(id=course_record_id, has_homework=True)
 4 
 5     if not course_record_models.exists():
 6         return HttpResponse("无作业")
 7 
 8     course_record_info = course_record_models.get()
 9     course_dir = os.path.join(conf.settings.STUDENT_HOMEWORK_DIR, str(course_record_id), str(request.user.id))
10     if not os.path.isdir(course_dir):
11         os.makedirs(course_dir)
12 
13     file_info = []
14     file_names = os.listdir(course_dir)
15     for filename in file_names:
16         file_info.append(os.stat(os.path.join(course_dir, filename)))
17 
18     if request.method == "GET":
19         form = MyForms.CourseRecordForm(instance=course_record_info)
20     else:
21         status = {
22             'status': True,
23             "message": None
24         }
25         print(request.FILES)  # 需要去接收文件,前端状态才会是true
26         if len(os.listdir(course_dir)) >= 2:
27             status['status'] = False
28             status['message'] = "文件超出上传个数"
29             return HttpResponse(json.dumps(status))
30 
31         file_obj = request.FILES.get("file")
32 
33         with open(os.path.join(course_dir, file_obj.name), "wb") as fp:
34             for chunks in file_obj.chunks():
35                 fp.write(chunks)
36 
37         return HttpResponse(json.dumps(status))
38 
39     return render(request, "stu/homework.html", locals())
homework作业显示和添加

(4)前端代码,使用Dropzone处理数据,以及ajax删除数据

  1 {% extends "index.html" %}
  2 {% load kingadmin_tag %}
  3 
  4 {% block extra-link %}
  5 <link rel="stylesheet" href="/static/plugins/dropzone/dropzone.css">
  6 {% endblock %}
  7 
  8 
  9 {% block right-content-container %}
 10 
 11 <div class="panel panel-primary">
 12     <div class="panel-heading">
 13         <h3 class="panel-title">作业提交</h3>
 14     </div>
 15     <div class="panel-body">
 16         {{ form }}
 17         <div class="col-sm-12">
 18         <table class="table table-hover" id="file_table">
 19           <caption><label class="control-label" style="padding-top: 0px;">已上传的文件目录</label></caption>
 20           <thead>
 21             <tr>
 22               <th>文件名</th>
 23               <th>大小(KB)</th>
 24               <th>上传时间</th>
 25               <th>删除</th>
 26             </tr>
 27           </thead>
 28           <tbody>
 29             {% for file in file_info %}
 30                 <tr>
 31                     <td>{% get_list_value file_names forloop.counter0  %}</td>
 32                     <td>{{ file.st_size }}</td>
 33                     <td>{% get_date_str file %}</td>
 34                     <td><span style="color: red;" class="glyphicon glyphicon-remove" onclick="deleteFile(this);"></span></td>
 35                 </tr>
 36             {% endfor %}
 37           </tbody>
 38         </table>
 39         <form action="{% url 'homework' course_record_info.id %}" id="myAwesomeDropzone" class="dropzone">
 40             {% csrf_token %}
 41           <div class="fallback">
 42             <input name="file" type="file" multiple />
 43           </div>
 44         </form>
 45   </div>
 46     </div>
 47 </div>
 48 {% endblock %}
 49 
 50 {% block extra-js %}
 51 <script src="/static/plugins/dropzone/dropzone.js"></script>
 52 <script>
 53 $(function () {
 54     Dropzone.options.myAwesomeDropzone = {
 55       paramName: "file", // The name that will be used to transfer the file
 56       maxFilesize: 2, // MB
 57       maxFiles:2,
 58       parallelChunkUploads:true,
 59       accept: function(file, done) {
 60         if (file.name == "justinbieber.jpg") {
 61           done("Naha, you don't.");
 62         }
 63         else { done(); }
 64       },
 65       init: function() {
 66         this.on("success", function(file,respone) {
 67             /* Maybe display some more file information on your page */
 68             var rep = JSON.parse(respone)
 69             if(!rep.status){
 70                 alert(rep.message);
 71                 return;
 72             }else{
 73                 var myDate = new Date();
 74                 var str_tm = myDate.toLocaleString();
 75                 str_tm = str_tm.replace(/\//g, "-");
 76                 str_tm = str_tm.replace(/[\u4e00-\u9fa5]+/g, "");
 77                 var tr = "<tr><td>"+file.name+"</td><td>"+file.size+"</td><td>"+str_tm+"</td><td>"+'<span style="color: red;"  onclick="deleteFile(this);" class="glyphicon glyphicon-remove"></span></td></tr>'
 78                 $("#file_table").append(tr);
 79             }
 80         });
 81       }
 82     };
 83 });
 84 
 85 
 86 function deleteFile(ths){
 87     var filename = $($(ths).parents("tr").children()[0]).text()
 88     $.ajax({
 89         url:"/stu/delete_file.html",
 90         data:{'c':'{{ course_record_id }}','f':filename,'csrfmiddlewaretoken':'{{ csrf_token }}'},
 91         dataType:"json",
 92         type:"post",
 93         success:function(data){
 94             if(data.status){
 95                 $(ths).parents("tr").remove()
 96             }else{
 97                 alert(data.message)
 98             }
 99         }
100     })
101 }
102 
103 </script>
104 
105 {% endblock %}
106 
107 homework.html
homework.html

(5)后台处理数据删除

 1 @login_required
 2 def delete_file(request):
 3     if request.method == "POST":
 4         status = {
 5             'status':True,
 6             'message':""
 7         }
 8         c_id = request.POST['c']
 9         filename = request.POST['f']
10         print(c_id,filename)
11         current_path = os.path.join(conf.settings.STUDENT_HOMEWORK_DIR, str(c_id), str(request.user.id))
12         file_path = os.path.join(current_path,filename)
13         print('文件路径',file_path)
14         if not os.path.isfile(file_path):
15             status['status']=False
16             status['message'] = "没有权限"
17             return HttpResponse(json.dumps(status))
18         else:
19             os.remove(file_path)
20 
21         return HttpResponse(json.dumps(status))
delete_files根据ajax上传数据删除文件

 总结:学会偷懒,化繁为简,学会总结业务,再去动态处理,而不是一直对数据库的增删改查,和重复一个业务逻辑

 

 
 
 
 
 
 
 
posted @ 2018-11-14 19:30  陆游憩  阅读(453)  评论(0编辑  收藏  举报