CRM項目講解和django知識點回顧

  今天想把以前寫的CRM項目梳理下,順便回顧一下djiango的部分重要知識.html

1.登陸頁面(包含簡單驗證碼)前端

首先來看下CRM的登陸頁面,樣式啥的不重要,你們能夠去jquery ui的網站上或者其餘地方找前端頁面,這裏主要說一下django的後臺實現jquery

登陸的視圖函數回顧,首先這裏登錄我用的是ajax的請求作的,圖中有代碼註釋,主要是提交數據並展現登陸錯誤信息git

//登陸提交數據
$('#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實現的github

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組件ajax

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組件sql

頁面效果以下:這裏主要用了form組件作了約束,當前也能夠用modelform,並且會更簡單些,其實我都作了,等會都會貼出來看下數據庫

註冊的視圖函數(先看下基於form組件實現的):django

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組件的內容:json

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

相關文章
相關標籤/搜索