首页 > 其他 > 详细

CRM项目讲解和django知识点回顾

时间:2019-07-11 23:26:43      阅读:186      评论:0      收藏:0      [点我收藏+]

  今天想把之前写的CRM项目梳理下,顺便回顾一下djiango的部分重要知识.

1.登录页面(包含简单验证码)

首先来看下CRM的登录页面,样式啥的不重要,大家可以去jquery ui的网站上或者其他地方找前端页面,这里主要说一下django的后台实现

技术分享图片

登录的视图函数回顾,首先这里登陆我用的是ajax的请求做的,图中有代码注释,主要是提交数据并展示登录错误信息

//登录提交数据
$(‘#login_in‘).on(‘click‘,function () {
    // 点击图片后刷新,通过+?的形式实现
    $(‘.validcode-img‘)[0].src += "?";
    $.ajax({
        url: "",
        type: ‘post‘,
        headers: {
            // 从cookies里面获取csrftoken,这里要引入jquery.cookie.js文件才能用$.cookie
            ‘X-CSRFToken‘: $.cookie(‘csrftoken‘)
        },
        data:{
            // 获取并提交登录数据,默认urlencoded格式
            username:$(‘#username‘).val(),
            password:$(‘#password‘).val(),
            validcode:$(‘#validcode‘).val()
        },
        success:function (response) {
            code = response.code;
            $("#login_error").html("");
            if (code==1000){
                // 成功后跳转页面,这里next_url指的是登陆前请求的页面
                location.href = response.next_url
            }else{
                error_msg = response.error_msg;
                $("#login_error").addClass(‘login-error‘).html(error_msg);
            }
        }
    })
});

对了,这里也说一下这个简单验证码的实现,虽然比较low,但是还是说明下,这里把噪点和线我注释了(因为我怕自己看不清楚验证码),这里主要用了PIL下的一些方法Image, ImageDraw, ImageFont实现的

def get_vaildcode_img(request):
    """生成验证码"""
    img = Image.new(RGB, (180, 38), myfunction.get_random_color())   # 生成随机底色
    draw = ImageDraw.Draw(img)  # 以img进行画画
    font = ImageFont.truetype("static/font/gordon.ttf", 35)     # 设置字体
    check_code = ""
    # 获取四个随机字符
    for i in range(4):
        random_num = str(random.randint(0, 9))
        random_lowstr = chr(random.randint(ord(a), ord(z)))
        random_upperstr = chr(random.randint(ord(A), ord(Z)))
        random_char = random.choice([random_num, random_lowstr, random_upperstr])
        draw.text((20+i*30+10, 0), random_char, myfunction.get_random_color(), font=font)     # 在img上写字
        check_code += random_char
    print(check_code)
    # 将用户个人的验证码保存到session中
    request.session[check_code] = check_code
    # 加噪点和线
    # width = 180
    # height = 38
    # # 加10条线
    # for i in range(10):
    #     x1 = random.randint(0, width)
    #     x2 = random.randint(0, width)
    #     y1 = random.randint(0, height)
    #     y2 = random.randint(0, height)
    #     draw.line((x1,y1,x2,y2), fill=myfunction.get_random_color())
    #
    # # 加10个点
    # for i in range(10):
    #     draw.point([random.randint(0, width), random.randint(0, height)], fill=myfunction.get_random_color())
    #     x = random.randint(0, width)
    #     y = random.randint(0, height)
    #     draw.arc((x, y ,x+4, y+4), 0 , 90, fill=myfunction.get_random_color())

    # 将图片保存到内存
    f = BytesIO()
    img.save(f, "png")
    data = f.getvalue()     # 从内存中读取
    return HttpResponse(data)

下面是登陆的源码,登录主要用到了django的auth组件

class LoginView(View):
    """登录"""
    def get(self, request):
        return render(request, login.html)

    def post(self, request):
        next_url = request.GET.get(next, /index/)
        res = {code: ‘‘, user: ‘‘, error_msg: ‘‘, next_url: next_url}
        username = request.POST.get(username)
        password = request.POST.get(password)
        valid_code = request.POST.get(validcode)
        check_code = request.session.get(check_code)
        if valid_code.upper() == check_code.upper():
            # 验证码输入正确再去判断用户名和密码,运用了django提供的auth组件
            user_obj = auth.authenticate(username=username, password=password)
            if user_obj:
                res[code] = 1000
                res[user_info] = username
                # 保存登录状态,实际上就是保存了session_id,源码主要代码request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
                auth.login(request, user_obj)
                # 获取rbac中的user对象,这里是因为嵌入了rbac,所以CRM用户表和rbac用户表做了1对1关联,因为权限认证要用rbac的用户表
                n_user = user_obj.user
                # 初始化用户,也就是读取用户的权限
                initial_session(n_user, request)
            else:
                res[code] = 1001
                res[error_msg] = 用户名或密码错误!
        else:
            res[code] = 1002
            res[error_msg] = 验证码错误!
        # 以json格式返回,实际上就是响应头说明返回是json数据,和将字典序列化了(dumps)
        return JsonResponse(res)

 

2.注册页面,主要回顾form组件

页面效果如下:这里主要用了form组件做了约束,当前也可以用modelform,而且会更简单些,其实我都做了,等会都会贴出来看下

技术分享图片

注册的视图函数(先看下基于form组件实现的):

def register(request):
    """基于form组件的注册页面"""
    if request.method == "POST":
        res = {code:‘‘,error_msg:‘‘}
        username = request.POST.get(username)
        password = request.POST.get(password)
        email = request.POST.get(email)
        telphone = request.POST.get(telphone)
        user_form = UserReg(request.POST)
        if user_form.is_valid():
            res[code] = 2000
            # 注册时在权限用户表和crm用户表都创建相同用户,初始化给与访客的权限
            user = User.objects.create(name=username,pwd=password)
            user.roles.add(4)
            UserInfo.objects.create_user(username=username,password=password,email=email,telphone=telphone, user=user)
        else:
            res[code] = 2001
            res[error_msg] = user_form.errors     # 把不符合的错误全部返回
        return JsonResponse(res)
    user_form = UserReg()
    return render(request,register.html,{user_form:user_form})

看下form组件的内容:

from django.forms import (
    ModelForm, fields as fid, widgets as wid
)


class UserReg(forms.Form):
    """注册form表单验证"""
    username=forms.CharField(error_messages={required:用户名不能为空},
                             widget=wid.TextInput(attrs={placeholder:用户名}))
    password=forms.CharField(error_messages={required:密码不能为空},
                             widget=wid.PasswordInput(attrs={placeholder: 密码}))
    repeat_password=forms.CharField(error_messages={required:确认密码不能为空},
                             widget=wid.PasswordInput(attrs={placeholder: 确认密码}))
    email=forms.EmailField(error_messages={required:邮箱不能为空,invalid:邮箱格式有误},
                             widget=wid.EmailInput(attrs={placeholder: 邮箱}))
    telphone=forms.CharField(required=False,widget=wid.TextInput(attrs={placeholder: 电话号码}))

    def clean_username(self):
        """用户名验证"""
        clean_user = self.cleaned_data.get(username)
        re_user = re.search(^[a-zA-Z][a-zA-Z0-9_]{4,15}$, clean_user)     # 看用户名是否满足5-16位
        if re_user:
            sql_user = UserInfo.objects.filter(username=clean_user).first()     # 看数据库是否有该用户
            if not sql_user:
                return clean_user
            raise ValidationError("该用户名已被注册")
        raise ValidationError("字母开头,5-16位,只允许字母数字下划线")

    def clean_password(self):
        """密码验证"""
        clean_pwd= self.cleaned_data.get(password)
        re_password = re.search(r^.*(?=.{8,16})(?=.*[0-9a-zA-Z])(?=.*[_!@#$%^&*?\(\)]).*$, clean_pwd)     # 密码的规则
        if re_password:
            return clean_pwd
        raise ValidationError("密码8-16位,必须包含字母、数字和特殊字符的组合")

    def clean_repeat_password(self):
        """确认密码验证"""
        clean_rep_pwd= self.cleaned_data.get(repeat_password)
        re_rep_password = re.search(r^.*(?=.{8,16})(?=.*[0-9a-zA-Z])(?=.*[_!@#$%^&*?\(\)]).*$, clean_rep_pwd)     # 确认密码的规则
        if re_rep_password:
            return clean_rep_pwd
        raise ValidationError("密码8-16位,必须包含字母、数字和特殊字符的组合")

    def clean_email(self):
        """邮箱验证"""
        clean_emails = self.cleaned_data.get(email)
        re_emails = re.search(r^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$, clean_emails)     # 邮箱的规则
        if re_emails:
            return clean_emails
        raise ValidationError("邮箱格式有误")

    def clean_telphone(self):
        """电话验证"""
        clean_tel = self.cleaned_data.get(telphone)
        # 用户输入了电话号码才校验,没输入不校验,因为该字段可选
        if clean_tel:
            re_tel = re.search(r^(13[0-9]|14[5|7]|15[0-3|5-9]|18[0-3|5-9])\d{8}$, clean_tel)     # 电话的规则
            if re_tel:
                return clean_tel
            raise ValidationError("电话号码格式有误")
        return clean_tel

    def clean(self):
        """验证两次密码输入是否一致"""
        pwd = self.cleaned_data.get(password)
        rep_pwd = self.cleaned_data.get(repeat_password)
        if pwd and rep_pwd and pwd!=rep_pwd:
            self.add_error(repeat_password, ValidationError("两次输入的密码不一致"))        # 给错误名称并加入到errors中
        return self.cleaned_data
    UserInfo.objects.values()

上面clean_xxx的都是局部钩子,clean则是全局钩子.来校验两次密码一致性,接下来再看下modelform写的简单代码,重复的钩子我可能就不展示了

下面是注册的modelform的视图函数:

def reg_modelform(request):
    """modelform构建的注册页面"""
    if request.method == "POST":
        user_modelform = UserRegModelForm(request.POST)
        if user_modelform.is_valid():
            # modelform提供save方法可直接保存ok的数据
            user_modelform.save()
            return redirect(reverse(login))
        return render(request, reg_modelform.html, {user_modelform: user_modelform})
    user_modelform = UserRegModelForm()
    return render(request, reg_modelform.html, {user_modelform: user_modelform})

还有就是modelform

class UserRegModelForm(ModelForm):
    """构建注册的modelform"""
    rep_password = fid.CharField(widget=wid.PasswordInput(attrs={placeholder: 确认密码}),
                                 error_messages={required:确认密码不能为空})
    class Meta:
        model = UserInfo
        fields = [username, password, rep_password, email, telphone]
        widgets = {
            username: wid.TextInput(attrs={placeholder: 用户名}),
            password: wid.PasswordInput(attrs={placeholder: 密码}),
            email: wid.EmailInput(attrs={placeholder: 邮箱}),
            telphone: wid.TextInput(attrs={placeholder: 电话号码}),
        }
        error_messages = {
            username: {required:用户名不能为空},
            password: {required:密码不能为空},
            email: {required:邮箱不能为空,invalid:邮箱格式有误},
        }

    def __init__(self, *args , **kwargs):
        """统一处理多个字段"""
        super(UserRegModelForm, self).__init__(*args , **kwargs)
        self.fields[telphone].required = False
        self.fields[email].required = True
        # for filed in self.fields.values():
        #     filed.error_messages={‘required‘: ‘该字段不能为空‘}

 

3.客户管理相关页面

效果图如下:

技术分享图片

 

页面主要有批量操作,搜索功能,分页实现,跟进详情跳转,剩下的就是增删改查等,下面是视图类的实现

class CustomersList(View):
    """客户列表"""
    def get(self, request):
        # 通过反向解析获取的路径对比当前请求路径,返回查询不同的数据
        if reverse(allcustomers_list) == request.path:
            customer_list = Customer.objects.all()
        elif reverse(customers_list) == request.path:
            customer_list = Customer.objects.filter(consultant__isnull=True)
        else:
            customer_list = Customer.objects.filter(consultant=request.user)

        # 搜索的字段和内容
        search_field = request.GET.get(field_select)
        search_content = request.GET.get(table_search)
        if search_content:
            # Q的扩展使用
            q = Q()
            if search_field == consultant:
                q.children.append((search_field + "__username__icontains", search_content))
            else:
                q.children.append((search_field + "__icontains", search_content))
            customer_list = customer_list.filter(q)

        # 分页的使用
        current_page_num = request.GET.get(page)
        pagination = MyPagination(current_page_num, customer_list.count(), request)
        customer_list = customer_list[pagination.start:pagination.end]

        # 返回当前path,记录当前的path,新增,编辑后返回原来页面
        path = request.path
        next = "?next=%s" % path

        return render(request, "crm/customer_manager/customer_list.html",
                      {next: next, customer_list: customer_list, pagination: pagination,
                       search_field: search_field, search_content: search_content})

    def post(self, request):
        select_action = request.POST.get(select_action)
        selected_pk_list = request.POST.getlist(selected_pk_list)
        if hasattr(self, select_action):
            # 通过反射实现
            func = getattr(self, select_action)
            queryset = Customer.objects.filter(pk__in=selected_pk_list)
            func(request, queryset)
        return self.get(request)

    def delete_selected(self, request, queryset):
        """删除选中的数据"""
        queryset.delete()

    def public_to_private(self, request, queryset):
        """公户转私户"""
        if queryset.filter(consultant__isnull=True):
            queryset.update(consultant=request.user)

    def private_to_public(self, request, queryset):
        """私户转公户"""
        queryset.update(consultant=None)

上面只是客户列表的部分功能,还有新增,编辑,删除,下面是实现的代码:

class CustomerOperate(View):
    """客户信息操作(新增和编辑)"""
    def get(self, request, edit_id=None):
        customer_obj = Customer.objects.filter(pk=edit_id).first()
        # 注意,虽然新增没有edit_id,但是编辑有,注意modelform有instance=customer_obj
        customer_form = CustomerModelForm(instance=customer_obj)
        return render(request, "crm/customer_manager/customer_operate.html", {customer_form:customer_form, customer_obj:customer_obj})

    def post(self, request, edit_id=None):
        customer_obj = Customer.objects.filter(pk=edit_id).first()
        # 如果不写instance=customer_obj,那么又是新增一条记录了
        customer_form = CustomerModelForm(request.POST, instance=customer_obj)
        if customer_form.is_valid():
            customer_form.save()
            # 跳转回编辑或添加前的页面
            return redirect(request.GET.get(next))
        else:
            return render(request, "crm/customer_manager/customer_operate.html", {customer_form:customer_form, customer_obj:customer_obj})


class CustomerDelete(View):
    """客户删除"""
    def get(self, request, delete_id):
        Customer.objects.filter(pk=delete_id).delete()
        # 跳转回删除前的页面
        return redirect(request.GET.get(next))

 

4.批量录入班级学习记录,主要用到了modelformset

 

技术分享图片

视图函数相关代码:

class RecordScoreView(View):
    """为班级批量录入成绩"""
    def get(self, request, id):
        # 根据表模型和表约束创建modelformset类(批量操作使用modelformset)
        model_formset_cls = modelformset_factory(model=StudentStudyRecord, form=StudentStudyRecordModelFormSet, extra=0)
        # 根据班级记录的id找出所有这个班级的学生记录
        queryset = StudentStudyRecord.objects.filter(classstudyrecord=id)
        # 将数据给modelformset,实例化,前端循环formset就可以取出对应的数据
        formset = model_formset_cls(queryset=queryset)
        class_study_record_list = ClassStudyRecord.objects.filter(pk=id).first()
        # 获取当前班级的所有学生记录
        student_study_record_list = class_study_record_list.studentstudyrecord_set.all()
        return render(request, "crm/class_manager/record_score.html", locals())

    def post(self, request, id):
        model_formset_cls = modelformset_factory(model=StudentStudyRecord, form=StudentStudyRecordModelFormSet, extra=0)
        formset = model_formset_cls(request.POST)
        if formset.is_valid():
            formset.save()
        return self.get(request, id)

前端页面相关代码:注意:form表单内必须要有{{ formset.management_form }},每一行都要有{{ form.id }},使用{{ form.instance.student }}的话就是保留原始字段,不能被编辑修改

{% extends ‘layout.html‘ %}
{% block content-header %}
    <h3 style="margin-left: 10px">录入{{ class_study_record_list }}成绩</h3>
{% endblock %}

{% block content %}

    <div class="box-body">
    <a href="{% url ‘class_study_record_list‘ %}" class="btn btn-danger pull-right">返回</a>
        <form action="" method="post">
            {% csrf_token %}
            {{ formset.management_form }}
            <input type="submit" value="保存" class="btn btn-success pull-right" style="margin-right: 20px;margin-bottom: 20px;">
            <table id="" class="table table-bordered table-hover">
                <thead>
                <tr>
                    <th>序号</th>
                    <th>姓名</th>
                    <th>考勤</th>
                    <th>成绩</th>
                    <th>批语</th>

                </tr>
                </thead>
                <tbody>
                {% for form in formset %}
                    <tr>
                        {{ form.id }}
                        <td>{{ forloop.counter }}</td>
                        <td>{{ form.instance.student }}</td>
                        <td>{{ form.instance.get_record_display }}</td>
                        <td>{{ form.score }}</td>
                        <td>{{ form.homework_note }}{{ form.errors.0 }}</td>
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </form>
    </div>

{% endblock %}

 

5.最后说一下统计相关的,比如客户成交量的统计

效果图如下:用到了highchart,分别统计了今天,昨天,最近一周和最近一个月的数据,在页面上进行展示

技术分享图片

视图代码如下:

class CustomerQuantity(View):
    """客户成交量统计"""
    def get(self, request):
        # 获取前端需要展示的天数,默认是今天
        date = request.GET.get(date, today)
        # 以年月日表示今天
        now = datetime.datetime.now().date()
        # 时延,分别是1天,一周,和一个月
        delta1 = datetime.timedelta(days=1)
        delta2 = datetime.timedelta(weeks=1)
        delta3 = datetime.timedelta(days=30)
        # 通过字典嵌套列表再包含字典的形式保存数据
        condition = {today: [{deal_date: now}, {customers__deal_date: now}],
                     yesterday: [{deal_date: now-delta1}, {customers__deal_date: now-delta1}],
                     week: [{deal_date__gte: now - delta2, deal_date__lte: now},
                              {customers__deal_date__gte: now - delta2, customers__deal_date__lte: now}],
                     month: [{deal_date__gte: now - delta3, deal_date__lte: now},
                              {customers__deal_date__gte: now - delta3, customers__deal_date__lte: now}],
                     }
        # 根据条件查询出所有的客户
        customer_list = Customer.objects.filter(**(condition[date][0]))
        # 每个销售的成交量(根据时间不同进行筛选)
        customer_count = UserInfo.objects.filter(depart__name=销售部门).filter(**(condition[date][1])).annotate(
            c=Count(customers)).values_list(username, c)
        # 由于highchart接收的数据是[[]]这种格式,所以将querysey变成列表,发现[()]也可以
        customer_count = list(customer_count)
        return render(request, "crm/count_manager/customer_quantity.html", {customer_count: customer_count,customer_list: customer_list})

前端页面代码:

{% extends ‘layout.html‘ %}

{% block content %}
    <section class="content">
        <div class="row">
            <div class="col-xs-12">
                <div class="box-header">
                    <a href="?date=today">今天</a>
                    <a href="?date=yesterday">昨天</a>
                    <a href="?date=week">最近一周</a>
                    <a href="?date=month">最近一个月</a>
                </div>
                <!-- /.box-header -->
                <div class="box-body">
                    <table id="all_customers" class="table table-bordered table-hover">
                        <thead>
                        <tr>
                            <th>序号</th>
                            <th>客户姓名</th>
                            <th>性别</th>
                            <th>客户来源</th>
                            <th>销售</th>
                            <th>已报班级</th>
                        </tr>
                        </thead>
                        <tbody>
                        {% for customer in customer_list %}
                            <tr>
                                <td>{{ forloop.counter }}</td>
                                <td>{{ customer.name |default:‘暂无‘ }}</td>
                                <td>{{ customer.get_sex_display }}</td>
                                <td>{{ customer.get_source_display }}</td>
                                <td>{{ customer.consultant|default:‘暂无‘ }}</td>
                                <td>{{ customer.get_classlist|default:‘暂无‘ }}</td>
                            </tr>
                        {% endfor %}
                        </tbody>
                    </table>
                </div>
                <!-- /.box-body -->
            </div>
        </div>
    </section>
    <div id="container" style="width:600px;height:400px"></div>
{% endblock %}
{% block js %}
    <script>
        var chart = Highcharts.chart(container, {
            chart: {
                type: column
            },
            title: {
                text: 客户成交量
            },
            subtitle: {
                text: 数据截止 2019-03
            },
            xAxis: {
                type: category,
                labels: {
                    rotation: 0  // 设置轴标签旋转角度
                }
            },
            yAxis: {
                min: 0,
                title: {
                    text: 成交数量(个)
                }
            },
            legend: {
                enabled: false
            },
            tooltip: {
                pointFormat: 成交数量: <b>{point.y:f}个</b>
            },
            series: [{
                name: 各个销售,
                data: {{ customer_count | safe }},
                dataLabels: {
                    enabled: true,
                    rotation: 0,
                    color: #FFFFFF,
                    align: center,
                    format: {point.y:.f}, // :.1f 为保留 1 位小数
                    y: 0
                }
            }]
        });
    </script>
{% endblock %}

至此就大致说完了,详细代码在git上,地址如下:

https://github.com/leixiaobai/CRM/tree/master/LkCRM

CRM项目讲解和django知识点回顾

原文:https://www.cnblogs.com/leixiaobai/p/11173418.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!