西游之路——python全栈——CRM用户关系管理
Day1:项目分析
一:需求分析
![](https://img2018.cnblogs.com/blog/1407931/201811/1407931-20181114152917885-1400483260.png)
二: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表结构实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
===================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
从中发现需要用到一个基类BaseKingAdmin来自于kingadmin模块:是为了防止注册事件时出现为空的现象,而且在基类中添加功能更加方便
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 })
还需要from kingadmin.sites import site,使用到site方法(类似于admin.site.register(模型,自定义模型显示类)):功能是将各个模块中的数据模型统一添加在一个数据结构中,方便调用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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() # 只实例化一次,后面的导入为调用对象
将数据统一放入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()方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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))
看如何采集各个模块信息:从配置文件中settings的INSTALLED_APPS中获取所有模块信息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 ]
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})
=====================================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)实现点击表名,查看数据的功能
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 })
=====================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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则默认为值-----,只有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 %}
前端使用了自定义模板函数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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">«</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">»</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自带分页组件下进行扩展)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
===========
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
===========
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <div> 2 {% render_paginator querysets admin_class sorted_column %} 3 </div>
===========
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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">«</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">»</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)
二:对各个字段筛选(对kingadmin中list_filter字段进行筛选)
1:前端显示
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
2.模板函数去定制标签,在form表单中加入隐藏标签(表示排序和搜索条件)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
3.在views中将url中的各个条件,放置到admin_class中,方便模板标签的使用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 })
4.在views中的url数据获取时将其他_q搜索,o排序,_p分页数据过滤,获取所有数据
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
推文:python---Django中模型类中Meta元对象了解,可以知道数据模型中的字段对象或者其他所需要的内容
三:对各个字段进行排序(list_display)
1.前端传递排序数据,对于table中的th加上url
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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值
========第一种方式================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
========第二种方式================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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字段中的索引顺序)
3.views中对我们获取的所有数据,根据前端传递的排序方法进行排序处理
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
四:对字段进行搜索(search_fields)
1.前端生成标签时,form表单中需要一起传递其他条件的input隐藏框
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
2.后端处理搜索条件,生成querysets数据
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
Day4:动态生成任意表的CURD
1.如何在前端动态生成标签?使用form验证可以针对model生成所有的字段控件
推文:python---django中form组件(2)自定制属性以及表单的各种验证,以及数据源的实时更新,以及和数据库关联使用ModelForm和元类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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验证类,可以用来在前端之间生成控件
2.如何针对每张表动态生成一个Form类?需要用到type方法去动态生成类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
3.在修改页面中动态创建Form类(需要传递原来数据)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 })
=================================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 re_path('^(\w+)/(\w+)/(\d+)/change/$', views.table_obj_change,name='table_obj_change'),
4.在添加页面动态创建Form类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 })
==================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 re_path('^(\w+)/(\w+)/add', views.table_obj_add,name='table_obj_add'),
添加的url
5.修改和添加的HTML和公共部分
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 %}
===================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 %}
========================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
6.处理在add和change中对于readonly_fileds字段的不同(使用其他方式处理readonly_fields可跳过)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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加入标识,前端进行判别,决定是否去显示只读字段
7.对于filter_horizontal字段我们在模板函数中进行获取选中值与未选中值,分别显示在不同的选择框
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
============================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
===========================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
==========================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <form class="form-horizontal" method="post" onsubmit="VerificationBeforeFormSubmit()" novalidate>{% csrf_token %}
==================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // form表单提交前的检测 2 function VerificationBeforeFormSubmit() { 3 // 移除所有‘disabled’属性 4 $(':disabled').removeAttr('disabled'); 5 6 // 选中有tag属性的select下所有option标签 7 $("select[tag] option").prop('selected',true); 8 }
10.为filter_horizontal完善功能,添加全选,全部移除,模糊匹配
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
===============
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // 一键全选 2 function MoveAllOption(from_id,to_id) { 3 $('#' + from_id).children().each(function() { 4 MoveSelectedOption(this,to_id); 5 }); 6 }
11.为filter_horizontal完善功能之模糊匹配
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!--模糊匹配--> 2 <input type="search" class="form-control" oninput="FuzzSearch(this);">
================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
Day5:删除功能开发和action方法实现
1.删除功能开发
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 %}
============
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
============
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 })
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
==============================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 })
(1)设置form表单布局
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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则默认为值-----,只有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>
(2)设置复选框完成全选功能
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <th><input onclick="SelectAllObjs(this);" type="checkbox"></th> 2 3 4 <td><input row-select='true' type="checkbox" value="{{obj.id}}"></td>
===========
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
(3)提交表单前先生成隐藏表单去获取数据集
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
(4)传递到后端进行处理
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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提交
3.处理action中的默认行为delete批量删除
(1)提交的url不是上面的table_obj_delete,而是本页面和change_status一起作为action传递入当前url
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 })
(2)获取delete_selected_objs在table_obj_list方法中返回
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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提交
若是执行完action方法后没有返回值则是正常执行,如果有返回值,则是代表我们接下来是执行删除操作。需要返回
(3)我们还是调用的上面的table_obj_delete.html页面,但是其中的模板标签函数,是针对一个数据对象,而现在是一个数据集,我们需要再次处理
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
(4)我们提交数据,也不再是table_obj_delete方法,而是table_obj_list方法,所以我们需要传递一个数据代表要删除的数据id集合,同时一个一个标识
======= 方式一 =======
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!--隐藏input标签,用于区别是否为批量删除,传递数据--> 2 <input type="hidden" name="selected_ids" value="{{queryset_ids}}">
======= 方式二 ========
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <input type="hidden" name="delete_ids" value="{% get_del_objs_id obj %}">
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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序列化返回给前端
(5)views页面根据post传递过来的隐藏标签的name,判断是不是执行删除数据操作
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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提交
4.实现面包屑导航
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
============
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
===========
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
===========
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 """显示中文""" 2 @register.simple_tag 3 def get_model_verbose_name(admin_class): 4 return admin_class.model._meta.verbose_name
===========
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
==========
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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.左侧菜单状态
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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>
Day6:学员报名流程开发
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
一:销售为想报名的学员提供链接
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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())
二:学员获取链接,进行填写信息,查阅合同,同意并上传证件信息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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())
==================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 %}
==================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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}))
三:销售审核学员注册信息,审核通过,为其生成账号(密码需要使用Django模块加密),发送邮件
from django.contrib.auth.hashers import make_password #用于生成密码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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())
四、自动发送邮件
方式一:写Python脚本
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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();
================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
方式二:Django自带邮件发送模块
(1)settings中配置
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)导入模块发送邮件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 # )
day7:实现讲师和学员作业发布上传功能(其中由于多使用table浏览数据,可以自定义一个类似于form的基类,去统一实现table显示)
一:讲师功能
(1)可以看出上面多是table显示信息,下面自定义table_form类似于forms
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
(2)使用方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 """
===============
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 """
=================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 = []
(3)在views中调用各个tableform
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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())
(4)前端调用{{ form|safe }},form是定义的tableform变量,实现简单显示页面
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 %}
=================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 %}
=================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 %}
(5)添加记录
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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())
==================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 %}
二:学员功能,实现课程显示,作业提交
(一)根据邮件中的账号密码登录
(二)实现查看班级,查看课程记录,提交作业
(1)由于这里也是table多使用,可以继续使用tableform
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 ]
(2)view中调用,显示班级和课程,前端也是{{form|safe}}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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添加作业
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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)
==================
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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())
(4)前端代码,使用Dropzone处理数据,以及ajax删除数据
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
(5)后台处理数据删除
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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))
总结:学会偷懒,化繁为简,学会总结业务,再去动态处理,而不是一直对数据库的增删改查,和重复一个业务逻辑