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

Python入门自学进阶-Web框架——18FormModelForm

Python入门自学进阶-Web框架——2Django初识

Python入门自学进阶-Web框架——3Django的URL配置