python门店会员管理系统_python---CRM用户关系管理

Day1:项目分析

一:需求分析

二:CRM角色功能介绍

三:业务场景分析

销售:1.销售A 从百度推广获取了一个客户,录入了CRM系统,咨询了Python课程,但是没有报名2.销售B 从qq群获取一个客户,成功使他报名Python班,然后给他发送了报名连接,等待用户填写完毕后,将他添加到Python具体的学习班级中3.销售C 打电话给之前的一个客户,说服他报名Python课程,但是没有成功,更新了跟踪记录4.销售D 获取了一个客户,录入信息时,发现此客户已经存在,不允许重复录入,随后通知相应的原负责人跟进5.销售E 从客户库中获取了,超过一个月未跟进的客户,进行再次跟进6.销售主管 查看了部门本月的销售报表,包括来源分析,成单率分析,班级报名数量分析,销售额环比,同比

学员:1.客户A 填写了销售发来的报名连接,上传了个人的证件信息,提交,之后收到邮件,告知报名成功,并为他开通了学员账号,升级为学员A2.学员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.创建账号 ABCD5.创建了销售,讲师,学员角色6.为账号分配到对应的角色,将ABCD分配给销售7.创建相关权限8.为销售角色分配了相关权限

四:表结构设计

数据库关联模型

Django表结构实现

fromdjango.db import modelsfromdjango.contrib.auth.models import User

# Create your models here.classUserProfile(models.Model):'''用户信息表:

含有讲师,销售,管理员这些正式人员''' user =models.OneToOneField(User) #使用的是Django自带的用户验证Username, password and email are required. Other fields are optional.

name= models.CharField(max_length=64,verbose_name="姓名")

role= models.ManyToManyField("Role",blank=True) #,null=Truenull has no effect on ManyToManyField.,null对于manytomanyfield无作用,会报警

def __str__(self):returnself.nameclassRole(models.Model):'''角色表:学员,讲师,销售,管理员''' name = models.CharField(max_length=64,unique=True)

menus= models.ManyToManyField("Menu",blank=True)

def __str__(self):returnself.nameclassMenu(models.Model):'''动态菜单''' name = models.CharField("菜单名",max_length=64)

url_type_choices=(

(0,"absolute"), #绝对路径/Sale/index.html

(1,"dynamic"), #动态url,根据url()方法中的name获取

)

url_type= models.SmallIntegerField(choices=url_type_choices)

url_name= models.CharField("URL",max_length=128)

def __str__(self):returnself.nameclassCustumerInfo(models.Model):'''客户信息表:联系方式,姓名等'''name= models.CharField(max_length=64,null=True,blank=True) #开始咨询的时候允许为空

contact_type_choices= ((0,'qq'),(1,"微信"),(2,'手机'))

contact_type= models.SmallIntegerField(choices=contact_type_choices,default=0)

contact= models.CharField(max_length=64,unique=True)

source_choices=(

(0,'QQ群'),

(1,"51CTO"),

(2,"百度推广"),

(3,"知乎"),

(4,"转介绍"),

(5,"其他")

)

source= models.SmallIntegerField(choices=source_choices)

referral_from= models.ForeignKey("self",blank=True,null=True,verbose_name="转介绍人员")

consult_courses= models.ManyToManyField("Course",verbose_name="咨询课程") #咨询的课程,允许咨询多门

consult_content= models.TextField("咨询内容",blank=True)

status_choices= ((0,"未报名"),(1,"已报名"),(2,"已退学"))

status= models.SmallIntegerField(choices=status_choices)

consultant= models.ForeignKey("UserProfile",verbose_name="课程顾问")

date= models.DateField(auto_now_add=True)

def __str__(self):returnself.nameclassCustumerFollowUp(models.Model):'''客户跟踪记录表:跟踪进度''' customer = models.ForeignKey("CustumerInfo")

content= models.TextField(verbose_name="跟进内容")

user= models.ForeignKey("UserProfile",verbose_name="跟进人员")

status_choices=(

(0,"近期无报名计划"),

(1,"一个月内报名"),

(2,"2周内报名"),

(3,"已报名"),

)

status= models.SmallIntegerField(choices=status_choices)

date= models.DateField(auto_now_add=True)

def __str__(self):returnself.contentclassStudent(models.Model):'''学员信息表:(未报名的客户在客户表中),报名成功的在学员表''' customer = models.ForeignKey("CustumerInfo")

class_grades= models.ManyToManyField("ClassList") #学员可以报多门课程

def __str__(self):returnself.customer.nameclassCourse(models.Model):'''课程表''' name = models.CharField(max_length=64,verbose_name="课程名称",unique=True)

price=models.PositiveSmallIntegerField() #必须为正

period= models.PositiveSmallIntegerField(verbose_name="课程周期(月)",default=5)

outline= models.TextField(verbose_name="大纲")

def __str__(self):returnself.nameclassClassList(models.Model):'''班级列表''' branch = models.ForeignKey("Branch") #校区关联

couser= models.ForeignKey("Course")

class_type_choices=(

(0,"脱产"),

(1,"周末"),

(2,"网络班")

)

class_type= models.SmallIntegerField(choices=class_type_choices,default=0)

semester= models.SmallIntegerField(verbose_name="学期")

teachers= models.ManyToManyField("UserProfile",verbose_name="讲师")

start_date= models.DateField("开班日期")

graduate_date= models.DateField("毕业日期",blank=True,null=True)

def __str__(self):return "%s (%s)期"%(self.couser,self.semester)classMeta:

unique_together= ('branch','class_type',"couser","semester") #联合唯一classCourseRecord(models.Model):'''上课记录:该节课程内容等''' class_grade = models.ForeignKey("ClassList",verbose_name="上课班级")

day_num= models.PositiveSmallIntegerField(verbose_name="课程节次")

teacher= models.ForeignKey("UserProfile")

title= models.CharField("本节主题",max_length=64)

content= models.TextField("本节内容")

has_homework= models.BooleanField("本节是否有作业",default=True)

homework= models.TextField("作业需求",blank=True,null=True)

date= models.DateTimeField(auto_now_add=True,verbose_name="上课时间")

def __str__(self):return "%s第(%s)节"%(self.class_grade,self.day_num )classMeta:

unique_together= ("class_grade","day_num")classStudyRecord(models.Model):'''学习记录表:学员考勤,作业,成绩,备注''' course_record = models.ForeignKey("CourseRecord")

student= models.ForeignKey("Student")

score_choices=(

(100,"A+"),

(90,"A"),

(85,"B+"),

(80,"B"),

(75,"B-"),

(70,"C+"),

(60,"C"),

(40,"C-"),

(0,"N/A"), #不可得not avaliable

(-50, "D"), #未交作业

(-100,"COPY") #抄袭

)

score= models.SmallIntegerField(choices=score_choices)

show_choices=(

(0,"缺勤"),

(1,"已签到"),

(2,"迟到"),

(3,"早退"),

)

show_status= models.SmallIntegerField(choices=show_choices)

note= models.CharField("情况备注",max_length=128,blank=True,null=True)

date= models.DateTimeField(auto_now_add=True)

def __str__(self):return "%s %s %s"%(self.course_record,self.student,self.score)classBranch(models.Model):'''校区''' name = models.CharField(max_length=64,unique=True)

addr= models.CharField(max_length=128,blank=True,null=True)

def __str__(self):return self.name

表结构创建

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

kingadmin目录

销售目录

学员目录

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

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

fromkingadmin.sites import site #虽然说,每个APP:sale,student都去导入了一次site,但是在python项目中对于同一个模块只会导入一次,所以这本身就是单例模式(使用的是内存中存在的那个)fromkingadmin.admin_base import BaseKingAdminfromrepository import models

print("Sale.kingadmin")classCustomerAdmin(BaseKingAdmin):

list_display= ['name','contact_type','contact','source','consult_content','consultant','status','date']

list_filter= ['source','consultant','status','date']

search_fields= ['contact','consultant__name']

site.register(models.CustumerInfo,CustomerAdmin)

site.register(models.Role)

site.register(models.Menu)

site.register(models.UserProfile)

Sale模块中kingadmin

fromkingadmin.sites import sitefromkingadmin.admin_base import BaseKingAdminfromStudent import modelsclassTestAdmin(BaseKingAdmin):

list_display= ['name']

site.register(models.TestAdmin,TestAdmin)----------------------------------------------------------student模块中自定义一个表classTestAdmin(models.Model):

name= models.CharField("姓名",max_length=64)

def __str__(self):return self.name

Student模块中kingadmin

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

class BaseKingAdmin(object):

pass

admin_base.py中BaseKingAdmin基类

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

fromkingadmin.admin_base import BaseKingAdminclass AdminSite(object):

def __init__(self):

self.enabled_admins={}

def register(self,model_class,admin_class=None):'''注册admin表

:param model_class:

:param admin_class:

:return:''' app_name =model_class._meta.app_label #app_label是当前应用的名字 一个应用可以注册多个表

model_name=model_class._meta.model_name #model_name是表名 和app_lable连接就是数据表全名ifnot admin_class:

admin_class=BaseKingAdmin()else:

admin_class=admin_class()if app_name not inself.enabled_admins:

self.enabled_admins[app_name]={}

admin_class.model=model_class

self.enabled_admins[app_name][model_name]=admin_class

site= AdminSite()

sites.py中的site方法

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

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

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

所以说:python模块本身就是单例模式

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

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

fromdjango.shortcuts import render,redirectfromdjango.contrib.auth import authenticate,login,logout #快捷操作fromkingadmin import app_setup

app_setup.kingadmin_auto_discover() #用来导入所有含Kingadmin的模块,模块中会去调用相应的Kingadmin文件去注册事件fromkingadmin.sites import site #发现只导入模块一次,site对象只有一个---------------------下面实现的是将数据分发给前端-------------------------------------def get_filter_result(request,querysets):

filter_conditions={}for k,v inrequest.GET.items():ifv:

filter_conditions[k]=vreturn querysets.filter(**filter_conditions),filter_conditions

def table_obj_list(request,app_name,model_name):'''取出指定的数据返给前端'''admin_class=site.enabled_admins[app_name][model_name]

model_class=admin_class.model

querysets=model_class.objects.all()

filter_data,filter_conditions=get_filter_result(request,querysets)

print(filter_conditions)

admin_class.filter_conditions=filter_conditions #也可以传值给前端,但是这样也不错return render(request,"kingadmin/table_obj_list.html",{"queryset":filter_data,'admin_class':admin_class})

views.py进入后,顺序执行,首先去调用app_setup.kingadmin_auto_discover()方法采集信息

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

INSTALLED_APPS =['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','repository.apps.RepositoryConfig','kingadmin','Student','Sale',

]

settings文件INSTALLED_APPS

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

fromdjango import conf  #实现动态获取配置文件,而不是以目录形式

import importlib

def kingadmin_auto_discover():for module inconf.settings.INSTALLED_APPS:try:

# md= importlib.import_module('.kingadmin',module)  #这个也可以

md= __import__('%s.kingadmin'%module) #导入Kingadmin,然后回去执行该文件中的数据,去注册事件(模块导入后,会自动使用site.register方法注册事件)

except ImportErrorase:

pass

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

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

{% for app_name,app_tables in site.enabled_admins.items %}

{{ app_name }}

{% for model_name in app_tables %}

{{ model_name }}

ADDChange{% endfor %}{% endfor %}

前端主要代码

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

def get_filter_result(request,querysets):

filter_conditions={}for k,v inrequest.GET.items():ifv:

filter_conditions[k]=vreturn querysets.filter(**filter_conditions),filter_conditions

def table_obj_list(request,app_name,model_name):'''取出指定的数据返给前端'''admin_class=site.enabled_admins[app_name][model_name]

model_class=admin_class.model

querysets=model_class.objects.all()

filter_data,filter_conditions=get_filter_result(request,querysets)

print(filter_conditions)

admin_class.filter_conditions=filter_conditions #也可以传值给前端,但是这样也不错return render(request,"kingadmin/table_obj_list.html",{"queryset":filter_data,'admin_class':admin_class})

table_obj_list方法根据模块和表名去获取site对象中的数据

{% extends "kingadmin/index.html" %}

{% load my_func %}

{% block right-content-container %}

{% for field in admin_class.list_filter %}

{% build_filter_row field admin_class %}

{% endfor %}提交

{% for field in admin_class.list_display %}{{ field }}{% endfor %}

{% for item in queryset %}{% build_table_row item admin_class %}{% endfor %}
{% endblock %}

前端

# coding:utf8

# __author: Administrator

# date:2018/5/26 0026#/usr/bin/env pythonfromdjango import templatefromdjango.utils.safestring import mark_safefromdatetime import datetime,timedelta

register=template.Library()

@register.simple_tag

def build_filter_row(field,admin_class):

model=admin_class.model

field_obj=model._meta.get_field(field)

filter_conditions=admin_class.filter_conditionstry:select = ""%field

data_list=field_obj.get_choices(field) #可以获取choices选项和外键

except AttributeError:if field_obj.get_internal_type() in ("DateField","DateTimeField"):

field= "%s__gte"%fieldselect = ""%field

time_now=datetime.now()

time_list=[

["", "---------"],

[time_now,"today"],

[time_now- timedelta(7), "七天内"],

[time_now.replace(day=1), "本月"],

[time_now- timedelta(90), "三个月内"],

[time_now.replace(month=1, day=1), "今年内"],

['', "ALL"]

]

def turn_date(date_list):

date_obj, date_str=date_listif type(date_obj) isdatetime:

date_obj= date_obj.strftime("%Y-%m-%d")return(date_obj,date_str)

data_list=map(turn_date,time_list)else:select = ""%field

data_list= list(model.objects.values_list("id",field))for item indata_list:if str(item[0]) == filter_conditions.get(field,None):

option= "" % str(item[0])else:

option= ""%str(item[0])

option+= item[1]

option+= ""

select +=optionselect += ""

return mark_safe(select)

@register.simple_tag

def build_table_row(obj,admin_class):'''生成一条HTML中tr元素'''tr= ""

for field inadmin_class.list_display:

# column_obj=admin_class.model._meta.get_field(field) #model是获取对应的模型对象

#ifcolumn_obj.choices:

# column_data= getattr(obj,"get_%s_display"%field)() #使用方法,要加上()

#else:

# column_data=getattr(obj,field) #使用属性不需要()

func= "get_"+field+"_display"

ifhasattr(obj,func):

column_data=getattr(obj,func)()else:

column_data=getattr(obj,field)

td= "

%s"%column_data

tr+=tdreturn mark_safe(tr)

my_func.py设置自定义函数

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

fromdjango.core.paginator import PaginatorclassCustomPagimator(Paginator):

def __init__(self,current_page,max_page_num,*args,**kwargs):

self.current_page= int(current_page) #当前页

self.max_page_num=max_page_num #可以显示多少页

super(CustomPagimator,self).__init__(*args,**kwargs)

def page_num_range(self):

# self.num_pages 总页数

part_num= int(self.max_page_num/2)if self.num_pages <=self.max_page_num:return range(1, self.num_pages + 1)if self.current_page <=part_num:return range(1,self.max_page_num+1)

elif self.current_page+part_num>=self.num_pages:return range(self.num_pages-self.max_page_num,self.num_pages+1)else:return range(self.current_page - part_num, self.current_page + part_num + 1)

分页类代码

current_page = request.GET.get('_p',1)

paginator= CustomPagimator.CustomPagimator(current_page=current_page, max_page_num=3,object_list=querysets,per_page=2) # 传入总数据和每页显示的数据try:

filter_data=paginator.page(current_page)

except PageNotAnInteger:

filter_data= paginator.page(1)

except EmptyPage:

filter_data=paginator.page(paginator.num_pages) # num_pages数总页数,最后一页

page_html= paginator.page_num_range()

分页类的使用

{% build_page_row queryset page_html admin_class %}

使用模板函数对分页数据进行url组合

@register.simple_tag

def build_page_row(queryset,page_html,admin_class):

#先生成条件过滤数据

filter_conditions= ''

for k,v inadmin_class.filter_conditions.items():

filter_conditions+= "&"+k+'='+v;

#再生成排序条件ifadmin_class.sort_conditions:

filter_conditions+= "&o="+list(admin_class.sort_conditions.values())[0]

#在生成搜索条件ifadmin_class.search_conditions:

filter_conditions+= "&_q="+admin_class.search_conditions

page_str= '

  • '

ifqueryset.has_previous():

page_str+= '

«'%(queryset.previous_page_number(),filter_conditions)for i inpage_html:if i ==queryset.number:

page_str+= '

%d'%(i,filter_conditions,i)else:

page_str+= '

%d'%(i,filter_conditions,i)ifqueryset.has_next():

page_str+= '

»'%(queryset.next_page_number(),filter_conditions)return mark_safe(page_str)

build_page_row模板函数

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

1:前端显示

{% for field in admin_class.list_filter %}

{% build_filter_row field admin_class %}

{% endfor %}

{% build_order_filter admin_class %}提交

build_filter_row模板函数去获取数据生成标签

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

@register.simple_tag

def build_filter_row(field,admin_class):

model=admin_class.model

field_obj=model._meta.get_field(field)

filter_conditions=admin_class.filter_conditions

label= """%s"""%fieldtry:select = "

%s:"%(label,field)

data_list=field_obj.get_choices(field) #可以获取choices选项和外键

except AttributeError:if field_obj.get_internal_type() in ("DateField","DateTimeField"):

field= "%s__gte"%fieldselect = "

%s:"%(label,field)

time_now=datetime.now()

time_list=[

["", "---------"],

[time_now,"today"],

[time_now- timedelta(7), "七天内"],

[time_now.replace(day=1), "本月"],

[time_now- timedelta(90), "三个月内"],

[time_now.replace(month=1, day=1), "今年内"],

['', "ALL"]

]

def turn_date(date_list):

date_obj, date_str=date_listif type(date_obj) isdatetime:

date_obj= date_obj.strftime("%Y-%m-%d")return(date_obj,date_str)

data_list=map(turn_date,time_list)else:select = "

%s:"%(label,field)

data_list= list(model.objects.values_list("id",field))for item indata_list:if str(item[0]) == filter_conditions.get(field,None):

option= "" % str(item[0])else:

option= ""%str(item[0])

option+= item[1]

option+= ""

select +=optionselect += "

"

return mark_safe(select)

build_filter_row模板函数对日期筛选进行自定义,外键或者choices字段使用字段对象获取数据,对于其他的字段使用model获取所有的值,组成select框进行筛选

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

@login_required

def table_obj_list(request,app_name,model_name):'''取出指定的数据返给前端'''admin_class=site.enabled_admins[app_name][model_name]

model_class=admin_class.model

querysets=model_class.objects.all() #所有数据

#搜索后的数据

querysets,search_conditions=get_search_result(request,querysets,admin_class)

admin_class.search_conditions=search_conditions

querysets,filter_conditions=get_filter_result(request,querysets) #过滤条件后的数据

admin_class.filter_conditions=filter_conditions #也可以传值给前端,但是这样也不错

querysets, sort_conditions=get_order_result(request,querysets,admin_class)

admin_class.sort_conditions=sort_conditions

current_page= request.GET.get('_p',1)

paginator= CustomPagimator.CustomPagimator(current_page=current_page, max_page_num=3,object_list=querysets,per_page=2) # 传入总数据和每页显示的数据try:

filter_data=paginator.page(current_page)

except PageNotAnInteger:

filter_data= paginator.page(1)

except EmptyPage:

filter_data=paginator.page(paginator.num_pages) # num_pages数总页数,最后一页

page_html=paginator.page_num_range()return render(request,"kingadmin/table_obj_list.html",{"queryset":filter_data,'admin_class':admin_class,"page_html":page_html})

注意:我在views中将各个url条件放在admin_class中,方便查询对比(也可以放在变量中分发出来)

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

def get_filter_result(request,querysets):

filter_conditions={}for k,v inrequest.GET.items():if k in ("_p","o","_q"):continue

ifv:

filter_conditions[k]=vreturn querysets.filter(**filter_conditions),filter_conditions

get_filter_result过滤条件,获取querysets数据

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

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

{% build_title_row admin_class %}

使用模板函数处理

2.模板函数build_title_row 去生成标签

@register.simple_tag

def build_title_row(admin_class):

#先生成过滤条件

filter_conditions= ''

for k, v inadmin_class.filter_conditions.items():

filter_conditions+= "&" + k + '=' +v;

#再生成搜索条件ifadmin_class.search_conditions:

filter_conditions+= "&_q="+admin_class.search_conditions

icon= """"""title= ''th= "

%s%s"

try:

sort_cond= list(admin_class.sort_conditions.keys())[0]

sort_val= list(admin_class.sort_conditions.values())[0]

except IndexError:

sort_cond=None

sort_val=Nonefor counter,field inenumerate(admin_class.list_display):if field ==sort_cond:if sort_val.startswith("-"):

title+= th%(sort_val.strip("-"),filter_conditions,field,icon%"top")else:

title+= th%("-"+sort_val,filter_conditions,field,icon%"bottom")else:

title+= th%(counter,filter_conditions,field,"")return mark_safe(title)

build_title_row中先将过滤和搜索条件组合,再生成排序url(符号倒序,数字代表在admin_class中list_display字段中的索引顺序)

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

def get_order_result(request,querysets,admin_class):

order_index= request.GET.get("o")

sort_conditions={}iforder_index:

index= abs(int(order_index))

order_by=admin_class.list_display[index]if order_index.startswith("-"):

querysets= querysets.order_by("-"+order_by)else:

querysets=querysets.order_by(order_by)

sort_conditions[order_by]=order_indexreturn querysets,sort_conditions

get_order_result获取排序结果

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

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

{% build_search_filter admin_class %}

前端数据form

2.使用模板函数生成标签

@register.simple_tag

def build_search_filter(admin_class):'''向搜索框中添加入过滤条件和排序条件'''# 先生成过滤条件

inp= ""

for k, v inadmin_class.filter_conditions.items():

inp+= """"""%(k,v)

# 再生成排序条件ifadmin_class.sort_conditions:

inp+= """""" % ("o", list(admin_class.sort_conditions.values())[0])return mark_safe(inp)

build_search_filter模板函数,生成input标签(含有各个条件)

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

def get_search_result(request,querysets,admin_class):

search_val= request.GET.get("_q")ifsearch_val:

q=Q()

q.connector= "OR"

for field inadmin_class.search_fields:

q.children.append(("%s__contains"%field,search_val))

querysets=querysets.filter(q)return querysets,search_val

views处理搜索字段,注意使用OR,需要用到Q方法

Day4:动态生成任意表的CURD

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

fromdjango.forms import ModelFormfromrepository import modelsclassCustomerForm(ModelForm):classMeta:

model=models.CustumerInfo #将表与元类中的数据关联

fields= "__all__"def __new__(cls,*args, **kwargs):

print(cls.base_fields)

#OrderedDict([('name', ), ('contact_type', ), ('contact', ), ('source', ), ('referral_from', ), ('consult_courses', ), ('consult_content', ), ('status', ), ('consultant', )])

#这张表中的所有字段对象for field_name,field_obj indict(cls.base_fields).items():

field_obj.widget.attrs.update({'class':"form-control"})return ModelForm.__new__(cls)

实验:使用固定的数据模型去生成对应的form验证类,可以用来在前端之间生成控件

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

fromdjango.forms import ModelFormfromrepository import models

def create_dynamic_model_form(admin_class,form_add=False):'''动态生成modelform,form_add表示是添加数据生成form类。添加和编辑有所区别'''

classMeta:

model=admin_class.model # 将表与元类中的数据关联

fields= "__all__"

ifnot form_add:

exclude=admin_class.readonly_fields

admin_class.add_flag=Falseelse:

exclude=[]

admin_class.add_flag=True

def __new__(cls,*args, **kwargs):

#OrderedDict([('name', ), ('contact_type', ), ('contact', ), ('source', ), ('referral_from', ), ('consult_courses', ), ('consult_content', ), ('status', ), ('consultant', )])

#这张表中的所有字段对象for field_name,field_obj indict(cls.base_fields).items():

field_obj.widget.attrs.update({'class':"form-control"})

#if field_name inadmin_class.readonly_fields:

# field_obj.widget.attrs.update({'disabled':'true'})returnModelForm.__new__(cls)

dynamic_form= type("DynamicModelForm",(ModelForm,),{'Meta':Meta,"__new__":__new__})return dynamic_form

form_handle.py中去创建方法,动态创建类

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

@login_required

def table_obj_change(request,app_name,model_name,obj_id):'''kingadmin数据修改页面'''admin_class=site.enabled_admins[app_name][model_name]

#动态生成form表单

model_form=form_handle.create_dynamic_model_form(admin_class)

obj= admin_class.model.objects.get(id=obj_id)if request.method == "GET":

form_obj= model_form(instance=obj)

elif request.method== "POST":

form_obj= model_form(instance=obj,data=request.POST)ifform_obj.is_valid():

form_obj.save()return redirect("/kingadmin/%s/%s"%(app_name,model_name))return render(request,"kingadmin/table_obj_change.html",locals())

table_obj_change方法去创建form类,传递到前端

url(r"^(\w+)/(\w+)/(\d+)/change/$", views.table_obj_change, name="table_obj_change"),

url中对于修改的匹配

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

@login_required

def table_obj_add(request,app_name,model_name):

admin_class=site.enabled_admins[app_name][model_name]

model_form= form_handle.create_dynamic_model_form(admin_class,form_add =True)if request.method == "GET":

form_obj=model_form()

elif request.method== "POST":

form_obj= model_form(data=request.POST)ifform_obj.is_valid:

form_obj.save()return redirect("/kingadmin/%s/%s" %(app_name, model_name))return render(request,"kingadmin/table_obj_add.html",locals())

table_obj_add方法

url(r"^(\w+)/(\w+)/add/$", views.table_obj_add, name="table_obj_add")

url.py中对于添加的匹配

添加的url

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

{% extends "kingadmin/index.html" %}

{% load my_func %}

{% block right-content-container %}

add

{% include "kingadmin/table_obj_change_component.html" %}

{% endblock %}

table_obj_add.html

{% extends "kingadmin/index.html" %}

{% load my_func %}

{% block right-content-container %}

change

{% include "kingadmin/table_obj_change_component.html" %}

{% endblock %}

table_obj_change.html

{% load my_func %}

{% csrf_token %}

{% for field in form_obj %}

{{ field.label }}{% if field.name in admin_class.filter_horizontal %}

{% get_rel_m2m_val field.name admin_class as rel_querysets %}

{% for rel_obj in rel_querysets %}

{% get_rel_m2m_sel form_obj field.name rel_obj as sel_flag%}

{% if not sel_flag %}{{ rel_obj }}{% endif %}

{% endfor %}

{% get_rel_m2m_val field.name admin_class as rel_querysets %}

{% for rel_obj in rel_querysets %}

{% get_rel_m2m_sel form_obj field.name rel_obj as sel_flag%}

{% if sel_flag %}{{ rel_obj }}{% endif %}

{% endfor %}

{% else %}
{{ field }} {{ field.errors.0}}
{% endif %}
{% endfor %}

{% if not admin_class.add_flag %}

{% for field_name in admin_class.readonly_fields %}

{{ field_name }}

{% get_field_value_p field_name form_obj %}

{% endfor %}

{% endif %}

{% block extra-js %}{% endblock %}

table_obj_change_component.html公共部分

6.处理在add和change中对于readonly_fileds字段的不同

{% if not admin_class.add_flag %}

{% for field_name in admin_class.readonly_fields %}

{{ field_name }}

{% get_field_value_p field_name form_obj %}

{% endfor %}

{% endif %}

在动态生成ModelForm修改,并且向admin_class.add_flag加入标识,前端进行判别,决定是否去显示只读字段

7.对于filter_horizontal字段我们在模板函数中进行获取所有的值,并且判断是否显示在哪一个select标签中

{% get_rel_m2m_val field.name admin_class as rel_querysets %}

{% for rel_obj in rel_querysets %}

{% get_rel_m2m_sel form_obj field.name rel_obj as sel_flag%}

{% if not sel_flag %}{{ rel_obj }}{% endif %}

{% endfor %}

{% get_rel_m2m_val field.name admin_class as rel_querysets %}

{% for rel_obj in rel_querysets %}

{% get_rel_m2m_sel form_obj field.name rel_obj as sel_flag%}

{% if sel_flag %}{{ rel_obj }}{% endif %}

{% endfor %}

前端对filter_horizontal进行判别,针对两个select都进行判别,一个放置选中一个放置未选中

@register.simple_tag

def get_rel_m2m_val(field_name,admin_class):

field_obj=admin_class.model._meta.get_field(field_name)

rel_model=field_obj.related_model

querysets=rel_model.objects.all()return querysets

get_rel_m2m_val模板函数获取关联对象得所有值,用到字段对象的related_model属性获取关联对象

@register.simple_tag

def get_rel_m2m_sel(form_obj,field_name,rel_obj):try:

querysets=getattr(form_obj.instance, field_name).all()if rel_obj inquerysets:returnTruereturnFalse

except TypeError:return False

get_rel_m2m_sel方法判断是否数据被选中,返回True选中,放在第二个select标签,放在未选中,放在第一个select标签

8.实现js双击option,在两个select之间跳转

{{ rel_obj }}

为两个select标签绑定同一个MoveEleToOpp方法

function MoveEleToOpp(ths,field_name) {if($(ths).parent().prop("id") == "id_"+field_name+"_from"){var new_target = "id_"+field_name+"_to";

}else{var new_target = "id_"+field_name+"_from";

}

$("#"+new_target).append(ths);

}

MoveEleToOpp方法实现:通过判断父标签select的id,将当前option转移append到对方的select中

9.实现在点击保存时,form表单自动将右侧select中的数据全部选中。注意:加上name为select标签,name="字段名"

为form表单绑定方法ChangeSelStatus

function ChangeSelStatus(ths){

$("select[tag] option").prop("selected",true);

}

ChangeSelStatus实现

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

ChooseAll

RemoveAll

前端HTML

function ChooseAll(ths,field_name) {var sel_id = $(ths).parent().prev().prop("id")if(sel_id == "id_"+field_name+"_from"){var new_target = "id_"+field_name+"_to";

}else{var new_target = "id_"+field_name+"_from";

}

$("#"+sel_id).find("option").each(function(){

$("#"+new_target).append(this);

})

}

ChooseAll函数js代码

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

1.删除功能开发

{% extends "kingadmin/index.html" %}

{% load my_func %}

{% block right-content-container %}

{% get_del_obj obj app_name model_name as res_del %}

{{ res_del|safe }}

{% csrf_token %}

返回

{% endblock %}

前端HTML代码

@register.simple_tag

def get_del_obj(model_obj, app_name, model_name):

all_rel=model_obj._meta.related_objects

ul= "

  • "ul+= "
  • %s:%s"%(model_obj._meta.label.rsplit('.',maxsplit=1)[1],app_name,model_name,model_obj.id,model_obj)for rel_field inall_rel:

sub_querysets= getattr(model_obj,rel_field.name+"_set").all()ifnot sub_querysets: #若是关联但是没有数据,则不显示continue

if rel_field.get_internal_type() == "ManyToManyField":

ul+= "

  • "ul+= "
  • %s"%rel_field.namefor i insub_querysets:

ul+= "

  • %s
" %i

ul+= "

"

else:for sub_item insub_querysets:

sub_res=get_del_obj(sub_item,sub_item._meta.app_label,sub_item._meta.model_name)

ul+= "

%s"%(sub_res)

ul+= "

"

return ul

模板函数,去递归生成标签

@login_required

def table_obj_delete(request,app_name,model_name,obj_id):

admin_class=site.enabled_admins[app_name][model_name]

obj= admin_class.model.objects.get(id=obj_id)if request.method == "POST":

obj.delete()return redirect("/kingadmin/{app_name}/{model_name}".format(app_name=app_name,model_name=model_name))return render(request, "kingadmin/table_obj_delete.html", locals())

views后台删除代码

2.action字段功能完善

classCustomerAdmin(BaseKingAdmin):

list_display= ['id','name','contact_type','contact','source','consult_content','consultant','status','date']

list_filter= ['source','consultant','status','date']

search_fields= ['contact','consultant__name','name']

readonly_fields= ['status','contact']

filter_horizontal= ['consult_courses',]

action= ['change_status',]

def change_status(self,request,querysets):

querysets.update(status=1)

kingadmin.py中放置action字段,包含有自定义方法

fromdjango.shortcuts import renderclass BaseKingAdmin(object):

list_display=[]

list_filter=[]

search_fields=[]

readonly_fields=[]

filter_horizontal=[]

action=[]

def_action= ['delete_selected_objs']

def delete_selected_objs(self,request,querysets):return render(request,'kingadmin/table_obj_delete.html')

def __init__(self):

self.action.extend(self.def_action)

admin_base.py中需要去设置action默认数据

(1)设置form表单布局

{% csrf_token %}Action:

---------{% for action in admin_class.action %}{{ action }}{% endfor %}

form表单

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

{% build_title_row admin_class %}

{% for item in queryset %}{% build_table_row item admin_class %}{% endfor %}

HTML代码

function CheckAll(ths) {if($(ths).prop("checked")){

$("input[name=check_row]").prop("checked",true)

}else{

$("input[name=check_row]").prop("checked",false)

}

}

CheckAll方法js完成全选

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

function Raw_input_action(ths) {if($("#action").val() == ""){

alert("请选择正确的action");return false;

}var select_ids =[];

$("input[name=check_row]").filter(":checked").each(function(){

select_ids.push($(this).val());

})if(select_ids.length == 0){

alert("请选择正确的项目");return false;

}

new_ele= "";

$(ths).append(new_ele);return true;

}

Raw_input_action方法生成一个人input标签

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

@login_required

def table_obj_list(request,app_name,model_name):'''取出指定的数据返给前端'''admin_class=site.enabled_admins[app_name][model_name]

model_class=admin_class.model

querysets=model_class.objects.all() #所有数据if request.method == "POST":

selected_action= request.POST.get("action")

selected_ids= request.POST.get("select_ids")

getattr(admin_class,admin_class.action[int(selected_action)])(request,querysets.filter(id__in=json.loads(selected_ids)))

在table_obj_list方法添加上post方法即可

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

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

def delete_selected_objs(self,request,querysets):return render(request,'kingadmin/table_obj_delete.html',{"admin_class":self,'obj':querysets})

delete_selected_objs的action方法

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

if request.method == "POST":if request.POST.get("delete_ids"):'''如果是删除做post传递过来的话另外处理,否则就是action操作'''del_id= json.loads(request.POST.get("delete_ids"))

admin_class.model.objects.filter(id__in=del_id).delete()return redirect("/kingadmin/%s/%s"%(app_name,model_name))else:

selected_action= request.POST.get("action")

selected_ids= request.POST.get("select_ids")

res= getattr(admin_class,admin_class.action[int(selected_action)])(request,querysets.filter(id__in=json.loads(selected_ids)))

#如果返回值,代表是返回render指向delete页面ifres:return res

table_obj_list中对于post的处理

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

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

@register.simple_tag

def get_del_obj(model_objs, app_name, model_name):

ul= "

  • "

try:

iter(model_objs)

except TypeError:

model_objs=[model_objs,]for model_obj inmodel_objs:

all_rel=model_obj._meta.related_objects

ul+= "

%s: %s"%(model_obj._meta.label.rsplit('.',maxsplit=1)[1],app_name,model_name,model_obj.id,model_obj)for rel_field inall_rel:

sub_querysets= getattr(model_obj,rel_field.name+"_set").all()ifnot sub_querysets: #若是关联但是没有数据,则不显示continue

if rel_field.get_internal_type() == "ManyToManyField":

ul+= "

  • "ul+= "
  • %s"%rel_field.namefor i insub_querysets:

ul+= "

  • %s
" %i

ul+= "

"

else:for sub_item insub_querysets:

sub_res=get_del_obj(sub_item,sub_item._meta.app_label,sub_item._meta.model_name)

ul+= "

%s"%(sub_res)

ul+= "

"

return ul

简单改变模板函数get_del_obj,将原来单个对象也改写为可迭代

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

在显示的table_obj_delete.html页面加入隐藏标签,收集所有id集合

@register.simple_tag

def get_del_objs_id(objs):

obj_ser=[]for obj inobjs:

obj_ser.append(obj.id)return json.dumps(obj_ser)

get_del_objs_id模板函数收集所有的数据对象的id,json序列化返回给前端

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

if request.method == "POST":if request.POST.get("delete_ids"):'''如果是删除做post传递过来的话另外处理,否则就是action操作'''del_id= json.loads(request.POST.get("delete_ids"))

admin_class.model.objects.filter(id__in=del_id).delete()return redirect("/kingadmin/%s/%s"%(app_name,model_name))

获取前端input表单名delete_ids,判断是否有数据,来决定是否删除

4.实现面包屑导航

add页面导航

list页面导航

change页面导航

@register.simple_tag

def get_nva_active(admin_class):return admin_class.model._meta.verbose_name

get_nva_active模板函数获取对象的中文名

delete页面导航

@register.simple_tag

def get_nav_del(model_objs):

obj_names=[]try:

iter(model_objs)

except TypeError:

model_objs=[model_objs,]for model inmodel_objs:

obj_names.append("%s"%model)return '|'.join(obj_names)

get_nav_del模板函数组合对象名

5.左侧菜单状态

{% for menu in role.menus.select_related %}

{% if menu.url_type == 0 %}

{% if menu.url_name == request.path %}

{{ menu.name }}{% else %} {{ menu.name }}{% endif %}

{% else %}

{% url menu.url_name as url_name %}

{% if url_name == request.path %}

{{ menu.name }}{% else %} {{ menu.name }}{% endif %}

{% endif %}

{% endfor %}

index页面在生成url时,对其进行判断。要分辨动态和绝对

Day6:学员报名流程开发

classContractTemplate(models.Model):'''合同模板表'''name= models.CharField(max_length=64)

content=models.TextField()

date= models.DateField(auto_now_add=True)

def __str__(self):returnself.nameclassStudentEnrollment(models.Model):'''学员报名表:这里还没有变成学员,适合客户表相关联'''customer= models.ForeignKey("CustumerInfo")

class_grade= models.ForeignKey("ClassList")

consultant= models.ForeignKey("UserProfile") #对应的销售

contract_agreed= models.BooleanField(default=False) #是否同意合同

contract_signed_date= models.DateTimeField(blank=True,null=True) #同意合同未到时间

contract_approved= models.BooleanField(default=False) #审核是否完毕

contract_approved_date= models.DateTimeField(blank=True,null=True)classMeta:

unique_together= ("customer","class_grade")

def __str__(self):return "%s"%self.customerclassPaymentRecord(models.Model):'''存储学员缴费记录'''enrollment= models.ForeignKey("StudentEnrollment")

payment_type_choice=(

(0,"报名费"),

(1,"学费"),

(2,"退费"),

)

payment_type= models.SmallIntegerField(choices=payment_type_choice)

amount= models.IntegerField("费用",default=500)

consultant= models.ForeignKey("UserProfile") #费用缴给谁

date= models.DateTimeField(auto_now_add=True)

def __str__(self):return "%s"%self.enrollment

新增3张表:学员注册表,合同表(和班级关联),缴费记录表

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

@login_required

def Student_encroll(request):

ClassList=models.ClassList.objects.all()

StuEncroll= models.CustumerInfo.objects.filter(consultant__user=request.user).all()if request.method == "POST":

student= request.POST.get("student")

classlist= request.POST.get("classlist")try:

StuEncObj=models.StudentEnrollment.objects.create(

customer_id=student,

class_grade_id=classlist,

consultant=request.user.userprofile

)

except IntegrityError:

StuEncObj= models.StudentEnrollment.objects.get(

customer_id=student,

class_grade_id=classlist,

consultant=request.user.userprofile

)ifStuEncObj.contract_agreed:return redirect("encrollment/%s/contract_audit.html"%StuEncObj.id)else:return HttpResponse("等待学员身份验证")

link= "http://127.0.0.1:8000/sale/encrollment/%s.html"%StuEncObj.idreturn render(request,"sale/stu_encroll.html",locals())

Student_encroll学员注册链接获取

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

def enrollment(request,id):'''学员在线报名'''enrollment_obj= models.StudentEnrollment.objects.get(id=id)ifenrollment_obj.contract_agreed and not enrollment_obj.contract_approved:return HttpResponse("信息正在审核当中")ifenrollment_obj.contract_approved:return HttpResponse("审核通过,去进行缴费操作")if request.method == "GET":

forms= CustomerForm(instance=enrollment_obj.customer)

elif request.method== "POST":if not request.POST.get("contract_agreed"):return HttpResponse("信息提交失败,请先阅读合同")

cus_dir=os.path.join(conf.settings.SALE_FILE_UPLOAD_DIR, id)if len(os.listdir(cus_dir)) == 0:return HttpResponse("信息提交失败,请先上传证件信息")

forms= CustomerForm(instance=enrollment_obj.customer,data=request.POST)ifforms.is_valid():

forms.save()

enrollment_obj.contract_agreed=True

enrollment_obj.contract_signed_date=datetime.datetime.now()

enrollment_obj.save()return HttpResponse("信息提交成功")

file_dir=os.path.join(conf.settings.SALE_FILE_UPLOAD_DIR,id)ifos.path.isdir(file_dir):

file_info=os.listdir(file_dir)return render(request,"sale/enrollment.html",locals())

enrollment学员在线报名

{% extends "index.html" %}

{% block extra-link %}{% endblock %}

{% block body %}

学员在线报名

{% csrf_token %}

{% for field in forms %}

{{ field.label }}

{{ field }} {{ field.errors.0 }}
{% endfor %}

报名班级

{{ enrollment_obj.class_grade }}

学费

{{ enrollment_obj.class_grade.couser.price }}

{{ enrollment_obj.class_grade.contract_template.content }}

是否同意以上合同

已上传的文件目录{% for file in file_info %}

{{ file }}{% endfor %}
{% endblock %}

{% block extra-js %}

Dropzone.options.myAwesomeDropzone={

paramName:"file", //The name that will be used to transfer the file

maxFilesize: 2, //MB

maxFiles:2,

parallelChunkUploads:true,

accept: function(file, done) {if (file.name == "justinbieber.jpg") {

done("Naha, you don't.");

}else{ done(); }

},

init: function() {this.on("success", function(file,respone) {/*Maybe display some more file information on your page*/

var rep =JSON.parse(respone)if(!rep.status){

alert(rep.message);return;

}else{var li = "

"+file.name+"";

$("#file_ul").append(li);

}

});

}

};

});

function PrevSubmit(ths){if(!$($(ths).find(":checkbox[name=contract_agreed]")[0]).prop("checked")){

alert("请先阅读合同");return false;

}if($("#file_ul").find("li").length==0){

alert("请先上传证件信息");return false;

}

$(ths).find(":disabled").removeAttr("disabled")return true}{% endblock %}

erollment.html报名页面,含有Dropzone使用

@csrf_exempt

def enrollment_fileupload(request,encrollment_obj_id):

cus_dir=os.path.join(conf.settings.SALE_FILE_UPLOAD_DIR,encrollment_obj_id)ifnot os.path.isdir(cus_dir):

os.makedirs(cus_dir)

status={'status':True,"message":None

}

print(request.FILES) #需要去接收文件,前端状态才会是trueif len(os.listdir(cus_dir)) >= 2:

status['status'] =False

status['message'] = "文件超出上传个数"

returnHttpResponse(json.dumps(status))

file_obj= request.FILES.get("file")

with open(os.path.join(cus_dir,file_obj.name),"wb") asfp:for chunks infile_obj.chunks():

fp.write(chunks)return HttpResponse(json.dumps(status))

enrollment_fileupload处理Dropzone文件传输

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

from django.contrib.auth.hashers import make_password  #用于生成密码

@login_required

def contract_audit(request,id):'''合同审查'''enrollment_obj= models.StudentEnrollment.objects.get(id=id)ifnot enrollment_obj.contract_agreed:return redirect("/sale/EncrollLink.html")ifenrollment_obj.contract_approved:return HttpResponse("审核通过,等待缴费")if request.method == "GET":

forms= CustomerForm(instance=enrollment_obj.customer)

contract_forms= ContractForm(instance=enrollment_obj)

elif request.method== "POST":

forms= CustomerForm(instance=enrollment_obj.customer,data=request.POST)

contract_forms= ContractForm(instance=enrollment_obj,data=request.POST)ifforms.is_valid() and contract_forms.is_valid():

forms.save()

contract_forms.save()

enrollment_obj.contract_approved_date=datetime.datetime.now()

enrollment_obj.save()try:

stu_obj=enrollment_obj.customer.student

except Exception:

passelse:returnHttpResponse("用户%s已添加--->账号为:%s" %(enrollment_obj.customer.name, enrollment_obj.customer.student.account.name))

#生成一个随机字符串

account= ''.join(random.sample(string.digits,8))

pwd= ''.join(random.sample(string.ascii_letters+string.digits,8))

#创建一个账号

user=models.User.objects.create(

username=account,

password=make_password(pwd),

)

#创建一个用户

userprofile=models.UserProfile.objects.create(

user=user,

name=enrollment_obj.customer.name,

)

#为用户绑定一个角色

userprofile.role.add(models.Role.objects.get(name="Student"))

#保存到学员表

models.Student.objects.create(

customer=enrollment_obj.customer,

class_grades=enrollment_obj.class_grade,

account=userprofile,

)

send_info= "账号:%s\n密码:%s"%(account,pwd)

send_mail('成功成为正式学员',

send_info,'18904190363@sina.cn',

["%s@qq.com"%enrollment_obj.customer.contact,],

fail_silently=False,

)return HttpResponse("审核通过,等待缴费")

print(forms.errors,contract_forms.errors)return render(request,"sale/contract_audit.html",locals())

contract_audit合同审核后生成账号,发送信息

Django自带邮件发送模块

(1)settings中配置

(2)导入模块发送邮件

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

一:讲师功能

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

import reclass BaseForm(object):

display_list=[]

field_tag={}

attrs={}

extra_field=[]

def __init__(self,model,querysets):

self.instance=model

self.querysets=querysets

self.th=[]

self.tr=[]

def register(self):for field inself.display_list:if field == "self":

self.th.append(self.instance._meta.verbose_name)continuefield_obj=self.instance._meta.get_field(field)

self.th.append(field_obj.verbose_name)

#自定义额外字段for item inself.extra_field:for k,v initem.items():if v.get("verbose_name"):

self.th.append(v.get("verbose_name"))else:

self.th.append(k)for query inself.querysets:

tds=[]for th inself.display_list:if th == "self":

field_val= "%s" %query

elif len(self.instance._meta.get_field(th).choices)> 0:

field_val= "%s"%getattr(query,"get_%s_display"%th)()else:

field_val= "%s"%getattr(query,th)if self.field_tag.get(th):

# {"self": {"a": {"href": "127.0.0.1:8000","href": "127.0.0.1:8000"},"a": {"href": "127.0.0.1:8000"}}}

tags= self.field_tag.get(th)

# {"a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}, "a": {"href": "127.0.0.1:8000"}} #前面是内层,后面是外层for k,v intags.items():

#"a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}

new_attr=[]for k1,v1 in v.items(): #{"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}

pat= re.compile("\{(.*?)\}")

res=pat.search(v1)whileres:

v1= pat.sub(str(getattr(query,res.group(1))),v1,1)

res=pat.search(v1)

new_attr.append("%s='%s'"%(k1,v1)) #获取到所有的属性放在列表中

field_val= "%s%s>"%(k," ".join(new_attr),field_val,k)

tds.append(field_val)for item inself.extra_field:for e_k,e_v initem.items():if e_v.get("value"):

field_val= "%s" % e_v.get("value")else:if hasattr(e_v.get("function"),"__call__"):

field_val= e_v.get("function")(getattr(query,e_v.get("model_attr")),*e_v.get("args",()),**e_v.get("kwargs",{}))else:

field_val= getattr(getattr(query,e_v.get("model_attr")),e_v.get("function"))(*e_v.get("args",()),**e_v.get("kwargs",{}))if self.field_tag.get(e_k):

# {"self": {"a": {"href": "127.0.0.1:8000","href": "127.0.0.1:8000"},"a": {"href": "127.0.0.1:8000"}}}

tags= self.field_tag.get(e_k)

# {"a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}, "a": {"href": "127.0.0.1:8000"}} #前面是内层,后面是外层for k,v intags.items():

#"a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}

new_attr=[]for k1,v1 in v.items(): #{"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}

pat= re.compile("\{(.*?)\}")

res=pat.search(v1)whileres:

v1= pat.sub(str(getattr(query, res.group(1))), v1, 1)

res=pat.search(v1)

new_attr.append("%s='%s'"%(k1,v1)) #获取到所有的属性放在列表中

field_val= "%s%s>"%(k," ".join(new_attr),field_val,k)

tds.append(field_val)

self.tr.append(tds)

def __str__(self):

cls_attr=[]iflen(self.attrs):for item inself.attrs.items():

cls_attr.append("%s='%s'"%(item[0],item[1]))

tb= "

tr= ""

for th_data inself.th:

th= "

%s"%th_data

tr+=th

tr+= "

"tb+=trfor tr_data inself.tr:

tr= "

"

for td_data intr_data:

td= "

%s"%td_data

tr+=td

tr+= "

"tb+=tr

tb+= "

"

return tb

BaseForm实现通过表数据显示table

(2)使用方法

fromTeacher.table_form import BaseFormclassClassListForm(BaseForm):

display_list= ["self","branch","class_type","start_date","graduate_date"] #self代表直接显示本条数据__str__field_tag= {"self":{"a":{"href":"127.0.0.1:8000"},},"study_record":{"a":{"href":"/teacher/classlist/{id}/class_list.html"}},"student":{"a":{"href":"/teacher/classlist/{id}/student_list.html"}}} #在前面的标签会显示在内层,在显示的数据外面加上标签

attrs= {"class":"table table-hover"}  #为table设置属性

extra_field= [{"student":{'verbose_name':"学员数量","model_attr":"student_set","function":"count"}},{"study_record":{"verbose_name":"上课记录","value":"上课记录"}}]

#额外自定义字段,若是有值value,会直接输出,否则会去当前实例集self.querysets的每一个实例中去获取相关的数据,使用函数去执行。字符串是调用自己的内置方法,function是调用自定义方法,同时可以使用"args"传递元组,"kwargs":传递字典作为参数

def __init__(self,model,querysets):

super(ClassListForm, self).__init__(model,querysets)

classStudentForm(BaseForm):

display_list= ["self"] #,"couser","semester"field_tag= {"self":{"a":{"href":"127.0.0.1:8000"},},} #在前面的标签会显示在内层

attrs= {"class":"table table-hover"}

extra_field= [{"student_grade":{'verbose_name':"学员成绩","value":"N/A"}},{"study_status":{"verbose_name":"出勤状况","value":"N/A"}}]

#额外自定义字段,若是有值value,会直接输出,否则会去当前实例集self.querysets的每一个实例中去获取相关的数据,使用函数去执行。字符串是调用自己的内置方法,function是调用自定义方法

def __init__(self,model,querysets):

super(StudentForm, self).__init__(model,querysets)

StudentForm

classCourseForm(BaseForm):

display_list= ["self","title","content","has_homework","homework","date"] #,"couser","semester"field_tag= {"self":{"a":{"href":"127.0.0.1:8000"},},} #在前面的标签会显示在内层

attrs= {"class":"table table-hover"}

extra_field=[]

#额外自定义字段,若是有值value,会直接输出,否则会去当前实例集self.querysets的每一个实例中去获取相关的数据,使用函数去执行。字符串是调用自己的内置方法,function是调用自定义方法

def __init__(self,model,querysets):

super(CourseForm, self).__init__(model,querysets)

CourseForm

(3)在views中调用各个tableform

@login_required

def course_list(request):

course_querysets=models.ClassList.objects.filter(

teachers=request.user.userprofile

)

#自定义form,用于table

forms=ClassListForm(models.ClassList,course_querysets)

forms.register()return render(request,"teacher/class_list.html",locals())

@login_required

def student_list(request,c_id):

student_querysets=models.ClassList.objects.filter(

teachers=request.user.userprofile,

id=c_id

).get().student_set.all()

forms=StudentForm(models.Student,student_querysets)

forms.register()return render(request,"teacher/student_list.html",locals())

@login_required

def classRec_list(request,c_id):

course_querysets=models.ClassList.objects.filter(

teachers=request.user.userprofile,

id=c_id

).get().courserecord_set.all()

forms=CourseForm(models.CourseRecord,course_querysets)

forms.register()return render(request, "teacher/course_list.html", locals())

所有显示table的函数

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

{% extends "index.html" %}

{% load my_func %}

{% block right-content-container %}

课程列表

{{ forms|safe }}
{% endblock %}

class_list.html

{% extends "index.html" %}

{% load my_func %}

{% block right-content-container %}

课程记录

{{ forms|safe }} 添加记录
{% endblock %}

course_list.html

{% extends "index.html" %}

{% load my_func %}

{% block right-content-container %}

学员列表

{{ forms|safe }}
{% endblock %}

student_list.html

(5)添加记录

fromdjango.forms import ModelForm,formsfromrepository import modelsclassCustomerForm(ModelForm):classMeta:

model=models.CustumerInfo #将表与元类中的数据关联

fields= "__all__"exclude= ["consult_content","status","consult_courses"]

readonly_fields= ['contact_type',"contact","consultant",'referral_from','source']

def __new__(cls,*args, **kwargs):

#OrderedDict([('name', ), ('contact_type', ), ('contact', ), ('source', ), ('referral_from', ), ('consult_courses', ), ('consult_content', ), ('status', ), ('consultant', )])

#这张表中的所有字段对象for field_name,field_obj indict(cls.base_fields).items():

field_obj.widget.attrs.update({'class':"form-control"})if field_name incls.Meta.readonly_fields:

field_obj.widget.attrs.update({'disabled': "true"})returnModelForm.__new__(cls)

def clean(self):ifself.errors:

raise forms.ValidationError("Please fix errors before re-submit")if self.instance.id isnot None: #这是一个修改的表单,而不是添加for field_name inself.Meta.readonly_fields:

new_val=self.cleaned_data[field_name]

old_val=getattr(self.instance, field_name)if new_val !=old_val:

self.add_error(field_name,"ReadOnly fileds error: you cannot change %s"%old_val)classCourseRecordForm(ModelForm):classMeta:

model=models.CourseRecord #将表与元类中的数据关联

fields= "__all__"# exclude= ["teacher"]

def __new__(cls,*args, **kwargs):

#OrderedDict([('name', ), ('contact_type', ), ('contact', ), ('source', ), ('referral_from', ), ('consult_courses', ), ('consult_content', ), ('status', ), ('consultant', )])

#这张表中的所有字段对象for field_name,field_obj indict(cls.base_fields).items():

field_obj.widget.attrs.update({'class':"form-control"})return ModelForm.__new__(cls)

使用form类,生成控件

@login_required

def classRec_add(request,c_id):if request.method == "GET":

CRfm=forms.CourseRecordForm()else:

CRfm= forms.CourseRecordForm(data=request.POST)

CRfm.save()return redirect("/teacher/classlist/%s/course_list.html"%c_id)return render(request,"teacher/course_add.html",locals())

views调用classRec_add,进行显示和添加数据

{% extends "index.html" %}

{% load my_func %}

{% block right-content-container %}

课程记录添加

{% csrf_token %}

{% for field in CRfm %}

{{ field.label }}

{{ field }}
{% endfor %}
{% endblock %}

前端显示course_add.html

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

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

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

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

fromTeacher.table_form import BaseFormclassClassListForm(BaseForm):

display_list= ["self","class_type","start_date","graduate_date",] #,"couser","semester"field_tag= {"self":{"a":{"href":"127.0.0.1:8000"},},'score':{"a":{"href":"127.0.0.1:8080"},},'mng_homework':{"a":{"href":"/student/course.html"}}} #在前面的标签会显示在内层

attrs= {"class":"table table-hover"}

extra_field= [{"score":{"verbose_name":"成绩","value":"成绩排名"}},{"mng_homework":{'verbose_name':"作业管理","value":"作业管理"}},]classClassRecordForm(BaseForm):

display_list= ["self","title","teacher","content","has_homework","homework"] #,"couser","semester"field_tag= {"self":{"a":{"href":"127.0.0.1:8000"},},'fin_homework':{"a":{"href":"/student/homework/{id}.html"}}} #在前面的标签会显示在内层

attrs= {"class":"table table-hover"}

extra_field= [{"date":{"verbose_name":"日期","model_attr":"date","function":"strftime","args":("%Y-%m-%d %H:%M:%S",)}},{"fin_homework":{'verbose_name':"我的作业","value":"提交作业"}},]

#额外自定义字段,若是有值value,会直接输出,否则会去当前实例集self.querysets的每一个实例中去获取相关的数据,使用函数去执行。字符串是调用自己的内置方法,function是调用自定义方法

def __init__(self,model,querysets):

super(ClassRecordForm, self).__init__(model,querysets)

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

# Create your views here.

@login_required

def couse_list(request):

course_list=models.CourseRecord.objects.filter(

class_grade__student=request.user.userprofile.student

).all()

forms=ClassRecordForm(models.CourseRecord,course_list)

forms.register()return render(request,"student/class_list.html",locals())

@login_required

def AllClass(request):

class_list=models.ClassList.objects.filter(

student=request.user.userprofile.student

).all()

forms=ClassListForm(models.ClassList,class_list)

forms.register()return render(request,"student/class.html",locals())

显示班级和课程

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

fromdjango.forms import ModelForm,formsfromrepository import modelsclassCourseRecordForm(ModelForm):classMeta:

model=models.CourseRecord #将表与元类中的数据关联

fields= "__all__"exclude= ["has_homework"]

def __new__(cls,*args, **kwargs):

#OrderedDict([('name', ), ('contact_type', ), ('contact', ), ('source', ), ('referral_from', ), ('consult_courses', ), ('consult_content', ), ('status', ), ('consultant', )])

#这张表中的所有字段对象for field_name,field_obj indict(cls.base_fields).items():

field_obj.widget.attrs.update({'class':"form-control","disabled":"true"})return ModelForm.__new__(cls)

CourseRecordForm

@login_required

def homework(request,id):

CRModel= models.CourseRecord.objects.filter(id=id,has_homework=True)ifnot CRModel.exists():return HttpResponse("无作业")

CRInfo= CRModel.get()

cus_dir=os.path.join(conf.settings.STUDENT_HOMEWORK_DIR, str(id), str(request.user.userprofile.student.id))ifnot os.path.isdir(cus_dir):

os.makedirs(cus_dir)

file_info=[]

file_names=os.listdir(cus_dir)for filename infile_names:

file_info.append(os.stat(os.path.join(cus_dir,filename)))if request.method == "GET":

form= myforms.CourseRecordForm(instance=CRInfo)else:

status={'status': True,"message": None

}

print(request.FILES) # 需要去接收文件,前端状态才会是trueif len(os.listdir(cus_dir)) >= 2:

status['status'] =False

status['message'] = "文件超出上传个数"

returnHttpResponse(json.dumps(status))

file_obj= request.FILES.get("file")

with open(os.path.join(cus_dir, file_obj.name),"wb") asfp:for chunks infile_obj.chunks():

fp.write(chunks)returnHttpResponse(json.dumps(status))return render(request,"student/homework.html",locals())

homework作业显示和添加

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

{% extends "index.html" %}

{% load my_func %}

{% block extra-link %}{% endblock %}

{% block right-content-container %}

作业提交

{{ form }}

文件名大小(KB)上传时间删除

{% for file in file_info %}{% get_list_value file_names forloop.counter0 %}{{ file.st_size }}{% get_date_str file.st_atime %} {% endfor %}{% csrf_token %}
{% endblock %}

{% block extra-js %}

Dropzone.options.myAwesomeDropzone={

paramName:"file", //The name that will be used to transfer the file

maxFilesize: 2, //MB

maxFiles:2,

parallelChunkUploads:true,

accept: function(file, done) {if (file.name == "justinbieber.jpg") {

done("Naha, you don't.");

}else{ done(); }

},

init: function() {this.on("success", function(file,respone) {/*Maybe display some more file information on your page*/

var rep =JSON.parse(respone)if(!rep.status){

alert(rep.message);return;

}else{var myDate = newDate();var str_tm =myDate.toLocaleString();

str_tm= str_tm.replace(/\//g, "-");

str_tm = str_tm.replace(/[\u4e00-\u9fa5]+/g, "");var tr = "

"+file.name+""+file.size+""+str_tm+""+' '$("#file_table").append(tr);

}

});

}

};

});

function deleteFile(ths){var filename = $($(ths).parents("tr").children()[0]).text()

$.ajax({

url:"/student/delete_file.html",

data:{'c':'{{ id }}','f':filename,'csrfmiddlewaretoken':'{{ csrf_token }}'},

dataType:"json",

type:"post",

success:function(data){if(data.status){

$(ths).parents("tr").remove()

}else{

alert(data.message)

}

}

})

}{% endblock %}

homework.html

(5)后台处理数据删除

@login_required

def delete_files(request):if request.method == "POST":

status={'status':True,'message':""}

c_id= request.POST['c']

filename= request.POST['f']

current_path=os.path.join(conf.settings.STUDENT_HOMEWORK_DIR, str(c_id), str(request.user.userprofile.student.id))

file_path=os.path.join(current_path,filename)

print(file_path)ifnot os.path.isfile(file_path):

status['status']=False

status['message'] = "没有权限"

returnHttpResponse(json.dumps(status))else:

os.remove(file_path)return HttpResponse(json.dumps(status))

delete_files根据ajax上传数据删除文件

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值