Python入门自学进阶-Web框架——31开发客户报名流程
Posted kaoa000
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门自学进阶-Web框架——31开发客户报名流程相关的知识,希望对你有一定的参考价值。
完成客户报名的流程
流程大体如下:在已有收集的客户信息基础上——>销售填写报名表(报什么班、课程顾问)——>自动生成一个链接,让学员填写——>学员填写个人信息,并上传身份照片,同意合同协议——>销售审核信息和合同——>学员缴费,生成缴费——>报名完成,状态改为已报名。
销售点击相关客户的报名链接:
销售录入报名信息:
生成链接给学员填报:
学员按照给定的链接,填报信息:
销售审核信息:
审核通过,生成缴费信息:
缴费成功,修改状态:
代码实现:
一、销售填写报名信息并生成报名信息链接
添加路由项:path('customer/<int:id_num>/enrollment/',views.enrollment,name='enrollment'),当点击报名链接时,此链接地址:<a href='/plcrm/customer/%s/enrollment/'>%s</a>,就跳转到views.enrollment函数:
@login_required
def enrollment(req,id_num):
customer_obj = models.Customer.objects.get(id=id_num)
# 因为生成的modelform中没有customer,这里通过id查询到customer,然后返回给前端
msgs =
if req.method == "POST":
enroll_form = forms.EnrollmentForm(req.POST)
if enroll_form.is_valid():
msg = '''请将下面链接发送给客户进行填写:
http://127.0.0.1:8000/plcrm/customer/registration/enroll_obj_id/random_str/'''
try:
print("cleandata:",enroll_form.cleaned_data)
enroll_form.cleaned_data["customer"] = customer_obj
# 通过前面查询到的customer,这里手工添加到form的cleaned_data中,主要是前端表单中没有传递这一项
enroll_obj = models.Enrollment.objects.create(**enroll_form.cleaned_data)
random_str = "".join(random.sample(string.ascii_lowercase + string.digits, 8))
cache.set(enroll_obj.id,random_str,3000)
msgs['msg'] = msg.format(enroll_obj_id=enroll_obj.id,random_str=random_str )
except IntegrityError as e:
# 前面通过手动添加customer到modelform的cleaned_data中,没有经过验证,所以同样的记录会被保存到数据库,会引发联合唯一错误
# 这里通过捕获错误,给modelform的errors增加错误项,进行信息提示和流程阻止
enroll_obj = models.Enrollment.objects.get(customer_id=customer_obj.id,
enrolled_class_id=enroll_form.cleaned_data['enrolled_class'].id)
if enroll_obj.contract_agreed: # 学生已经同意
return redirect('/plcrm/contract_review/%s/' %enroll_obj.id)
enroll_form.add_error("__all__", "该用户此条报名信息已存在,不能重复")
random_str = "".join(random.sample(string.ascii_lowercase+string.digits,8))
cache.set(enroll_obj.id, random_str, 3000)
msgs['msg'] = msg.format(enroll_obj_id=enroll_obj.id,random_str=random_str)
else:
enroll_form = forms.EnrollmentForm()
return render(req,"sales/enrollment.html",'enroll_form':enroll_form,"customer_obj":customer_obj,"msgs":msgs)
点击报名时,是GET方式进入函数views.enrollment,此时执行else语句体,就是生成一个空的eroll_form,然后返回给前端页面enrollment.html。在这个页面填写信息后点击下一步,提交信息,再次进入此函数,是以POST方式进入,执行POST方法语句体,判断form是否有错误,即先是form级验证,验证通过,保存数据库,即model的create方法,因为有可能保存数据库失败,即唯一性错误,这里进行异常捕获,出错了,要进行提示,销售进行修改。一切都正常后,要给出一条学员信息填写的链接,这里这个链接为了防止被爆破,增加了一个随机串,并将其保存到cache中,设置有效时间,这样定时更改学员信息填报链接。
用到的EnrollmentForm:
class EnrollmentForm(ModelForm):
def __new__(cls, *args, **kwargs):
for field_name,field_obj in cls.base_fields.items():
field_obj.widget.attrs['class'] = 'form-control'
return ModelForm.__new__(cls)
class Meta:
model = models.Enrollment
fields = ['enrolled_class','consultant']
前端页面enrollment.html部分代码
<div class="container">
<div class="row"><hr/></div>
<div class="row">学生报名信息录入</div>
<div class="row"><hr/></div>
<form class="form-group" role="form"method="post">% csrf_token %
<span style="color: red;"> enroll_form.errors </span>
<div class="row">
<div class="col-2"><h4>客户:</h4></div>
<div class="col-4"><h4>qq: customer_obj.qq name: customer_obj.qq_name <h4/></div>
</div>
% for field in enroll_form %
<div class="row">
<div class="col-2"><label> field.label </label></div>
<div class="col-4"> field </div>
</div>
% endfor %
<div class="row">
<input type="submit" class="btn btn-info pull-right" value="下一步">
</div>
</form>
<div class="row">
<div>
% for k,v in msgs.items %
<li> k :<br> v </li>
% endfor %
</div>
</div>
</div>
二、学员报名信息填写实现
按照给学员的链接,在路由表中添加路由项:path('customer/registration/<int:id_num>/<str:random_str>/',views.stu_registration,name='stu_registration'),
编写学员报名信息填报处理函数views.stu_registration:
def stu_registration(req,id_num,random_str):
# 学生报名相关信息,主要是一个form
if cache.get(id_num) == random_str:
enroll_obj = models.Enrollment.objects.get(id=id_num)
if req.method == "POST":
if req.is_ajax(): # 这里处理dropzone使用ajax上传图片的过程,将图片保存
print("ajax post:",req.FILES)
enroll_data_dir = "%s/%s" %(settings.ENROLL_DATA,id_num)
if not os.path.exists(enroll_data_dir):
os.makedirs(enroll_data_dir,exist_ok=True)
for k,file_obj in req.FILES.items():
with open("%s/%s" %(enroll_data_dir,file_obj.name),"wb") as f:
for chunk in file_obj.chunks():
f.write(chunk)
return HttpResponse("success")
customer_form = forms.CustomerForm(req.POST,instance=enroll_obj.customer)
if customer_form.is_valid():
customer_form.save() # 用户Form表单验证通过,保存学员填报的信息
enroll_obj.contract_agreed = True
enroll_obj.save() # 数据库报名表中将合同协议同意字段改为True,即学员同意合同
return render(req,"sales/stu_registration.html","enrolled_obj":enroll_obj,"status":1) # 保存成功返回一个报名成功提示页面
else: # get方法进入时,要判断学员是否同意合同项,如果该项为True,说明已经提交过,设置状态字status为1,否则为0
if enroll_obj.contract_agreed == True:
status = 1
else:
status = 0
customer_form = forms.CustomerForm(instance=enroll_obj.customer)
return render(req,"sales/stu_registration.html","customer_form":customer_form,"enrolled_obj":enroll_obj,'status':status)
else:
return HttpResponse("不要乱试了")
用到的CustomerForm:
class CustomerForm(ModelForm):
def __new__(cls, *args, **kwargs):
for field_name,field_obj in cls.base_fields.items():
field_obj.widget.attrs['class'] = 'form-control'
if field_name in cls.Meta.readonly_fields:
field_obj.widget.attrs['disabled'] = 'disabled'
return ModelForm.__new__(cls)
def clean_qq(self):
print('--------1',self.cleaned_data['qq']) # 这一步打印是有数据的
if self.instance.qq != self.cleaned_data['qq']:
self.add_error("qq","不允许修改QQ号")
print('--------2', self.cleaned_data['qq']) # 这一步打印,就出错,KeyError:‘qq’,说明cleaned_data中清除了qq字段
# return self.cleaned_data['qq'] # 一开始返回的是这个,感觉都一样,但是此时cleaned_data中没有qq字段了,会出错
return self.instance.qq
def clean_consultant(self):
if self.instance.consultant != self.cleaned_data['consultant']:
self.add_error("consultant","不允许修改consultant号")
return self.instance.consultant
def clean_source(self):
if self.instance.source != self.cleaned_data['source']:
self.add_error("source","不允许修改source号")
return self.instance.source
class Meta:
model = models.Customer
fields = "__all__"
exclude = ['tags','content','memo','status','referral_form','consult_course']
readonly_fields = ['qq','consultant','source',]
前端页面stu_registration.html
% extends "base.html" %
% load plcrm_tags %
% block mybody %
<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">我的客户管理系统</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search" -->
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="#"> request.user.name </a>
</li>
</ul>
</nav>
<div class="container pt-3">
<div class="card">
<div class="card-header bg-info text-light">
Xxx教育学院 | 报名入学
</div>
<div class="card-body ml-3">
customer_form.errors
% if status != 1 %
<blockquote class="blockquote mb-0">
客户信息:
</blockquote>
<form method="post" onsubmit="return RegisterFormCheck();">% csrf_token %
% for field in customer_form %
<div class="form-group row m-sm-0">
<label for="staticEmail" class="col-sm-2 col-form-label p-0"> field.label </label>
<div class="col-sm-6 p-0">
field
</div>
</div>
% endfor %
<hr/>
<blockquote class="blockquote mb-0">
所报班级信息:
</blockquote>
<div class="form-group row m-sm-0">
<label for="staticEmail" class="col-sm-2 col-form-label">班级信息</label>
<div class="col-sm-6">
enrolled_obj.enrolled_class
</div>
</div>
<div class="form-group row m-sm-0">
<label for="staticEmail" class="col-sm-2 col-form-label">课程费用</label>
<div class="col-sm-6">
enrolled_obj.enrolled_class.course.price
</div>
</div>
<div class="form-group row m-sm-0">
<label for="staticEmail" class="col-sm-2 col-form-label">开课日期</label>
<div class="col-sm-6">
enrolled_obj.enrolled_class.start_date
</div>
</div>
<hr/>
<blockquote class="blockquote mb-0">
合同信息:
</blockquote>
<div class="form-group row m-sm-0">
<div class="col-sm-12">
<pre style="height: 200px;overflow: auto;">% render_enroll_contract enrolled_obj %</pre>
</div>
</div>
<div class="form-group row m-sm-0">
<div class="col-sm-12">
<input type="checkbox" name="contract_agreed"> 已认真阅读完协议并接受所有条款
</div>
</div>
<div class="text-center">
<input class="btn btn-info text-center" type="submit" value="提交">
</div>
</form>
<hr/>
# <div id="mydropz" class="dropzone svelte-12uhhij dz-clickable">#
# </div>#
<form id="mydropz" action= request.path class="dropzone">
<div class="fallback">
<input name="file" type="file" multiple />
</div>
</form>
% else %
<blockquote class="blockquote mb-0">
enrolled_obj.customer ,你已提交成功
</blockquote>
% endif %
</div>
</div>
</div>
<script>
#$("form#mydropz").dropzone(dictDefaultMessage:'拖拽上传文件到此',method:"post",#
# headers: 'X-CSRFToken':'csrf_token')#
# 注意这里的headers设置,是将csrf_token带上,否则将出现Forbidden (CSRF token missing or incorrect.)错误 #
#使用上面的写法会出现Uncaught Error: Dropzone already attached.错误,说明$("form#mydropz").dropzone是再次初始化,使用下面的写法 #
#Dropzone.options.mydropz = #
# headers:'X-CSRFToken':'csrf_token',#
# dictDefaultMessage:"kkkkkkkkkkkkkk",#
# init:function () #
# this.on("success",function (file,data) #
# console.log(file);#
# console.log(data);#
##
# )#
# #
##
if ($("form#mydropz").length !=0)
Dropzone.options.mydropz = false;
var myDropzone = new Dropzone("form#mydropz",
headers:'X-CSRFToken':'csrf_token',
dictDefaultMessage:"图片拖拽到此",
);
#$(function () #
# Dropzone.options.mydropz = #
# maxFiles:2,#
# addRemoveLinks:true,#
# uploadMultiple:true,#
# headers: 'X-CSRFToken':'csrf_token',#
# accept:function (file,done) #
# if (file.name == "justinbieber.jpg") #
# done("Naha,don't");#
# #
# else done();#
# #
# #
# );#
function RegisterFormCheck()
if (myDropzone.files.length<2)
alert("至少上传两张图片");
return false;
if ($("form input:checkbox").prop('checked'))
$('form').find("[disabled]").removeAttr("disabled")
return true;
else
alert("必须同意条款");
return false;
</script>
</body>
<script>
</script>
% endblock %
这个过程中,主要在于dropzone这个组件的使用。
三、销售人员审核:
接第一步,当学员信息提报以后,销售再次点击下一步按钮时,因为contract_agreed为True,进入合同审核过程,即执行:
if enroll_obj.contract_agreed: # 学生已经同意
return redirect('/plcrm/contract_review/%s/' %enroll_obj.id)
在路由表中增加路由项:path('contract_review/<int:enroll_id>/',views.contract_review,name='contract_review'),
编写函数views.contract_review:
def contract_review(req,enroll_id):
enroll_obj = models.Enrollment.objects.get(id=enroll_id) # 获取报名信息对象
enroll_form = forms.EnrollmentForm(instance=enroll_obj) # 报名信息的Form,由于展示
customer_form = forms.CustomerForm(instance=enroll_obj.customer) # 学员信息Form
return render(req,'sales/contract_review.html','enroll_obj':enroll_obj,
'enroll_form':enroll_form,
'customer_form':customer_form)
前端页面contract_review.html:
<div class="container pt-3">
<div class="card">
<div class="card-header bg-info text-light">
<h3>学员信息审核</h3>
</div>
<hr/>
<div class="card-body ml-3">
<blockquote class="blockquote mb-0">
学员信息:
</blockquote>
<form method="post" >% csrf_token %
% for field in customer_form %
<div class="form-group row m-sm-0">
<label for="staticEmail" class="col-sm-2 col-form-label p-0"> field.label </label>
<div class="col-sm-6 p-0">
field
</div>
</div>
% endfor %
<hr/>
<blockquote class="blockquote mb-0">
报名信息:
</blockquote>
% for field in enroll_form %
<div class="form-group row m-sm-0">
<label for="staticEmail" class="col-sm-2 col-form-label p-0"> field.label </label>
<div class="col-sm-6 p-0">
field
</div>
</div>
% endfor %
<hr/>
<div class="clearfix col-sm-8">
<a href="% url 'enrollment_rejection' enroll_obj.id %" class="btn btn-danger float-left" >审核驳回</a>
<a href="% url 'payment' enroll_obj.id %" class="btn btn-info float-right" >审核通过</a>
</div>
</form>
</div>
</div>
</div>
功能就是显示学员基本信息和报名信息,这里提供审核驳回和审核通过两项,审核驳回,就是将报名信息的contract_agreed置为False,就是学员同意协议置为False,这样,学员又可以重新填写相关信息。
审核通过,跳转到缴费页面
四、学员缴费
审核通过后跳转到payment路由项,增加此项
path('payment/<int:enroll_id>/',views.payment,name='payment'),
函数views.payment:
def payment(req,enroll_id):
enroll_obj = models.Enrollment.objects.get(id=enroll_id)
errors = []
if req.method == "POST":
payment_amount = req.POST.get('amount')
if payment_amount and int(payment_amount)>500:
payment_obj = models.Payment.objects.create(
customer=enroll_obj.customer,
course= enroll_obj.enrolled_class.course,
amount=int(payment_amount),
consultant=enroll_obj.consultant,
)
enroll_obj.contract_approved = True
enroll_obj.save()
enroll_obj.customer.status = 0
enroll_obj.customer.save()
return redirect("/mytestapp/plcrm/customer/")
else:
errors.append("缴费金额不能低于500")
return render(req,'sales/payment.html','enroll_obj':enroll_obj,
'errors':errors)
主要功能是对缴费金额进行确认保存,费用不能低于500,保存缴费金额后,需要同时修改报名表中的contract_approved为True,表明报名生效,同时修改客户表中的状态status为0,表明客户已报名。
前端页面payment.html:
<div class="container pt-3">
<div class="card">
<div class="card-header bg-info text-light">
<h3>学员信缴费</h3>
</div>
<hr/>
<div class="card-body ml-3">
<blockquote class="blockquote mb-0">
学员报名基本信息:
</blockquote>
<ul style="color: red;">
% for error in errors %
<li> error </li>
% endfor %
</ul>
<form method="post" action="">% csrf_token %
<div class="form-group row m-sm-0">
<label for="staticEmail" class="col-sm-2 col-form-label p-0">客户:</label>
<div class="col-sm-6 p-0"> enroll_obj.customer </div>
</div>
<div class="form-group row m-sm-0">
<label for="staticEmail" class="col-sm-2 col-form-label p-0">所报课程:</label>
<div class="col-sm-6 p-0"> enroll_obj.enrolled_class </div>
</div>
<div class="form-group row m-sm-0">
<label for="staticEmail" class="col-sm-2 col-form-label p-0">课程缴费:</label>
<div class="col-sm-6 p-0">
<input type="text" name="amount" placeholder="缴费金额不能小于500">
</div>
</div>
<div class="form-group row m-sm-0">
<label for="staticEmail" class="col-sm-2 col-form-label p-0">课程顾问:</label>
<div class="col-sm-6 p-0"> enroll_obj.consultant </div>
</div>
<hr/>
<div class="clearfix col-sm-8 offset-sm-3">
<input class="btn btn-info" type="submit" value="缴费提交,开启学习">
</div>
</form>
</div>
</div>
</div>
至此,整个报名过程结束。
以上是关于Python入门自学进阶-Web框架——31开发客户报名流程的主要内容,如果未能解决你的问题,请参考以下文章
Python入门自学进阶-Web框架——20Django其他相关知识2
Python入门自学进阶-Web框架——20Django其他相关知识2
Python入门自学进阶-Web框架——18FormModelForm