Django-CRM后台管理系統


crm整體流程

表結構

from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.contrib import auth
from django.core.exceptions import PermissionDenied
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager, User
from django.utils.translation import ugettext_lazy as _
from multiselectfield import MultiSelectField
#安裝:pip install django-multiselectfield,針對choices多選用的

from django.utils.safestring import mark_safe

course_choices = (('LinuxL', 'Linux中高級'),
                  ('PythonFullStack', 'Python高級全棧開發'),)

class_type_choices = (('fulltime', '脫產班',),
                      ('online', '網絡班'),
                      ('weekend', '周末班',),)

source_type = (('qq', "qq群"),
               ('referral', "內部轉介紹"),
               ('website', "官方網站"),
               ('baidu_ads', "百度推廣"),
               ('office_direct', "直接上門"),
               ('WoM', "口碑"),
               ('public_class', "公開課"),
               ('website_luffy', "路飛官網"),
               ('others', "其它"),)

enroll_status_choices = (('signed', "已報名"),
                         ('unregistered', "未報名"),
                         ('studying', '學習中'),
                         ('paid_in_full', "學費已交齊"))

seek_status_choices = (('A', '近期無報名計划'), ('B', '1個月內報名'), ('C', '2周內報名'), ('D', '1周內報名'),
                       ('E', '定金'), ('F', '到班'), ('G', '全款'), ('H', '無效'),)
pay_type_choices = (('deposit', "訂金/報名費"),
                    ('tuition', "學費"),
                    ('transfer', "轉班"),
                    ('dropout', "退學"),
                    ('refund', "退款"),)

attendance_choices = (('checked', "已簽到"),
                      ('vacate', "請假"),
                      ('late', "遲到"),
                      ('absence', "缺勤"),
                      ('leave_early', "早退"),)

score_choices = ((100, 'A+'),
                 (90, 'A'),
                 (85, 'B+'),
                 (80, 'B'),
                 (70, 'B-'),
                 (60, 'C+'),
                 (50, 'C'),
                 (40, 'C-'),
                 (0, ' D'),
                 (-1, 'N/A'),
                 (-100, 'COPY'),
                 (-1000, 'FAIL'),)
# 用戶表
class UserInfo(AbstractUser):
    #銷售,班主任,講師,三哥
    telephone = models.CharField(max_length=32,null=True)
    roles = models.ManyToManyField('Role')
    def __str__(self):
        return self.username
# 角色表
class Role(models.Model):
    title = models.CharField(max_length=32)
    permissions = models.ManyToManyField('Permission')
    def __str__(self):
        return self.title
# 系統所有的url
class Permission(models.Model):
    title = models.CharField(max_length=32)
    url = models.CharField(max_length=128)
    def __str__(self):
        return self.title


class Customer(models.Model):
    """
    客戶表(最開始的時候大家都是客戶,銷售就不停的撩你,你還沒交錢就是個客戶)
    """
    qq = models.CharField(verbose_name='QQ', max_length=64, unique=True, help_text='QQ號必須唯一')
    qq_name = models.CharField('QQ昵稱', max_length=64, blank=True, null=True)
    name = models.CharField('姓名', max_length=32, blank=True, null=True, help_text='學員報名后,請改為真實姓名')
    sex_type = (('male', ''), ('female', ''))
    sex = models.CharField("性別", choices=sex_type, max_length=16, default='male', blank=True, null=True) #存的是male或者female,字符串
    birthday = models.DateField('出生日期', default=None, help_text="格式yyyy-mm-dd", blank=True, null=True)

    phone = models.CharField('手機號', blank=True, null=True,max_length=32)
    # phone = models.CharField('手機號', blank=True, null=True)
    source = models.CharField('客戶來源', max_length=64, choices=source_type, default='qq')

    introduce_from = models.ForeignKey('self', verbose_name="轉介紹自學員", blank=True, null=True)  #self指的就是自己這個表,和下面寫法是一樣的效果


    # introduce_from = models.ForeignKey('Customer', verbose_name="轉介紹自學員", blank=True, null=True,on_delete=models.CASCADE)
    course = MultiSelectField("咨詢課程", choices=course_choices) #多選,並且存成一個列表的格式
    # course = models.CharField("咨詢課程", choices=course_choices) #如果你不想用上面的多選功能,可以使用Charfield來存
    class_type = models.CharField("班級類型", max_length=64, choices=class_type_choices, default='fulltime')
    customer_note = models.TextField("客戶備注", blank=True, null=True, )
    status = models.CharField("狀態", choices=enroll_status_choices, max_length=64, default="unregistered",help_text="選擇客戶此時的狀態") #help_text這種參數基本都是針對admin應用里面用的

    date = models.DateTimeField("咨詢日期", auto_now_add=True)

    last_consult_date = models.DateField("最后跟進日期", auto_now_add=True) #考核銷售的跟進情況,如果多天沒有跟進,會影響銷售的績效等
    next_date = models.DateField("預計再次跟進時間", blank=True, null=True) #銷售自己大概記錄一下自己下一次會什么時候跟進,也沒啥用

    #用戶表中存放的是自己公司的所有員工。
    consultant = models.ForeignKey('UserInfo', verbose_name="銷售", blank=True, null=True)

    #一個客戶可以報多個班,報個脫產班,再報個周末班等,所以是多對多。
    class_list = models.ManyToManyField('ClassList', verbose_name="已報班級", )

    def __str__(self):
        return "%s:%s"%(self.name,self.qq)  #主要__str__最好是個字符串昂,不然你會遇到很多的坑,還有我們返回的這兩個字段填寫數據的時候必須寫上數據,必然相加會報錯,null類型和str類型不能相加等錯誤信息。

    def get_classlist(self):  #當我們通過self.get_classlist的時候,就拿到了所有的班級信息,前端顯示的時候用

        l=[]
        for cls in self.class_list.all():
            l.append(str(cls))
        # return mark_safe(",".join(l)) #純文本,不用mark_safe也可以昂
        return ",".join(l) #純文本,不用mark_safe也可以昂

class Campuses(models.Model):
    """
    校區表
    """
    name = models.CharField(verbose_name='校區', max_length=64)
    address = models.CharField(verbose_name='詳細地址', max_length=512, blank=True, null=True)

    def __str__(self):
        return self.name

class ClassList(models.Model):
    """
    班級表
    """
    course = models.CharField("課程名稱", max_length=64, choices=course_choices)
    semester = models.IntegerField("學期") #python20期等
    campuses = models.ForeignKey('Campuses', verbose_name="校區",on_delete=models.CASCADE)
    price = models.IntegerField("學費", default=10000)
    memo = models.CharField('說明', blank=True, null=True, max_length=100)
    start_date = models.DateField("開班日期")
    graduate_date = models.DateField("結業日期", blank=True, null=True) #不一定什么時候結業,哈哈,所以可為空

    #contract = models.ForeignKey('ContractTemplate', verbose_name="選擇合同模版", blank=True, null=True,on_delete=models.CASCADE)
    teachers = models.ManyToManyField('UserInfo', verbose_name="老師") #對了,還有一點,如果你用的django2版本的,那么外鍵字段都需要自行寫上on_delete=models.CASCADE

    class_type = models.CharField(choices=class_type_choices, max_length=64, verbose_name='班額及類型', blank=True,null=True)

    class Meta:
        unique_together = ("course", "semester", 'campuses')

    def __str__(self):
        return "{}{}({})".format(self.get_course_display(), self.semester, self.campuses)

class ConsultRecord(models.Model):
    """
    跟進記錄表
    """
    customer = models.ForeignKey('Customer', verbose_name="所咨詢客戶")

    note = models.TextField(verbose_name="跟進內容...")

    status = models.CharField("跟進狀態", max_length=8, choices=seek_status_choices, help_text="選擇客戶此時的狀態")

    consultant = models.ForeignKey("UserInfo", verbose_name="跟進人", related_name='records')

    date = models.DateTimeField("跟進日期", auto_now_add=True)

    delete_status = models.BooleanField(verbose_name='刪除狀態', default=False)
    # def __str__(self):
    #     return self.consultant
model

1、繼承django內置的user表並擴展字段

django內置的user表中給我們提供了username,password

from django.contrib.auth.models import AbstractUser
# 用戶表
class UserInfo(AbstractUser):
    telephone = models.CharField(max_length=32,null=True)  # 該字段不可以為空

2、基於form組件實現注冊

定義form組件的驗證規則並生成標簽

from django import forms # form組件
from app01 import models
from django.core.exceptions import ValidationError # 錯誤信息
from django.core.validators import RegexValidator # django內置的正則

# 用戶注冊form
class Myform(forms.Form):
    username=forms.CharField(
        max_length=32,
        min_length=6,
        label="用戶名",
        error_messages={
            "required": "用戶名不能為空",
            "min_length": "用戶名不能少於6位",
            "max_length":"用戶名不能超過32位",
        },
        widget=forms.widgets.TextInput(attrs={"placeholder": "請輸入用戶名"})
    )
    password=forms.CharField(
        max_length=10,
        min_length=6,
        label="密碼",
        error_messages={
            "required": "密碼不能為空",
            "min_length": "密碼不能少於6位",
            "max_length": "密碼不能超過32位",
        },
        widget=forms.widgets.PasswordInput(attrs={"placeholder":"請輸入密碼"})
    )
    r_password = forms.CharField(
        max_length=10,
        min_length=6,
        label="確認密碼",
        error_messages={
            "required": "確認密碼不能為空",
            "min_length": "確認密碼不能少於6位",
            "max_length": "確認密碼不能超過32位",
        },
        widget=forms.widgets.PasswordInput(attrs={"placeholder":"請確認密碼"})
    )
    # 給手機號定義校驗規則
    telephone = forms.CharField(
        max_length=11,
        label="電話",
        error_messages={
            "required": "電話不能為空",
            "max_length": "電話不能超過11位",
        },
        widget=forms.widgets.TextInput(attrs={"placeholder": "請輸入11位的手機號"}),
        validators=[RegexValidator(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$', '電話必須以13/15/17/18/14/開頭,且必須滿足11位')],
    )
    # 批量添加樣式
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        for field in self.fields:
            # 取每個字段,給每個字段添加樣式
            self.fields[field].widget.attrs.update({"class": "form-control"})

    # 局部鈎子判斷用戶名是否存在
    def clean_username(self):
        username=self.cleaned_data.get("username")
        # 判斷用戶名是否存在
        if models.UserInfo.objects.filter(username=username).exists():
            raise ValidationError("用戶名已經存在")
        else:
            # 將驗證通過的信息返回
            return username

    # 局部鈎子判斷手機號是否已經注冊
    def clean_phone(self):
        phone=self.cleaned_data.get("phone")
        # 判斷手機號是否已經注冊
        if models.UserInfo.objects.filter(phone=phone).exists():
            raise ValidationError("該手機號已經注冊!!!")
        else:
            # 將驗證通過的信息返回
            return phone

    # 全局鈎子判斷兩次輸入的密碼是否一致
    def clean(self):
        password_value = self.cleaned_data.get('password')
        r_password_value = self.cleaned_data.get('r_password')
        if password_value == r_password_value:
            return self.cleaned_data  # 全局鈎子要返回所有的數據
        else:
            self.add_error('r_password', '兩次密碼不一致')
View Code

實例化Myform對象傳給前端進行渲染

# 注冊
def register(request):
    # 實例化Myform對象
    form_obj = Myform()
    if request.method=="GET":
        return render(request,"register.html",{"form_obj":form_obj})
    else:
        form_obj=Myform(data=request.POST)
        if form_obj.is_valid():
            # 所有信息都驗證完,通過的認證信息都在cleaned_data里面
            dic = form_obj.cleaned_data
            # 將不需要寫入數據庫的信息刪除
            dic.pop("r_password")
            # 用戶注冊信息寫入數據庫,(這種創建是密文密碼)
            models.UserInfo.objects.create_user(**dic)
            return redirect("login")
        else:
            return render(request,"register.html",{"form_obj":form_obj})
View Code

前端頁面

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static "bootstrap-3.3.7-dist/css/bootstrap.min.css" %}">
</head>
<body>
<div class="container">
    <div class="row">

        <div class="col-md-6 col-md-offset-3" style="margin-top: 150px">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">用戶注冊</h3>
                </div>
                <div class="panel-body">
                    <div>
                        <form action="{% url "register" %}" method="post" novalidate>
                            {% csrf_token %}
                            {% for foo in form_obj %}
                                <div class="form-group {% if foo.errors.0 %}
                                has-error
                                {% endif %}">
                                    <label for="{{ foo.id_for_label }}">{{ foo.label }}</label>
                                    {{ foo }}{{ foo.errors.0 }}
                                </div>

                            {% endfor %}
                            <input type="submit" value="注冊" class="btn btn-success pull-right">
                        </form>

                    </div>
                </div>
            </div>

        </div>

    </div>

</div>
</body>
</html>
View Code

3、登錄認證基於ajax請求發送|附加驗證碼

驗證碼:生成的驗證碼保存在session中,方便校驗

from io import BytesIO  # 在內存中開辟空間
from PIL import Image,ImageDraw,ImageFont # 驗證碼模塊
# 生成驗證碼
def code(request):
    f = BytesIO()
    # 創建圖片
    img = Image.new(mode="RGB", size=(120, 30),
                    color=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
    # 創建畫筆
    draw = ImageDraw.Draw(img, mode='RGB')
    draw.arc((0, 0, 30, 30), 0, 360, fill="red")

    # 隨機生成4位驗證碼
    str_code = ''
    for i in range(4):
        a1 = chr(random.randint(65, 90))
        a2 = chr(random.randint(97, 122))
        a3 = random.randint(0, 9)
        str_num = random.choice([a1, a2, str(a3)])
        # 每次生成的隨機數保存在列表中
        str_code+=str_num
        # 選擇字體
        font = ImageFont.truetype("Monaco.ttf", 28)
        # 寫字
        draw.text([i * 35, 0], str_num, (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)),
                  font=font)
    print(str_code)
    # 將驗證碼存在session中,方便后面取出來與用戶輸入的進行驗證
    request.session["str_code"] = str_code
    # 將圖片保存在內存中
    img.save(f, format="png")
    # 讀取內存中的圖片
    data = f.getvalue()
    return HttpResponse(data)
View Code

登錄認證

# 登錄認證
def login(request):
    if request.method=="GET":
        return render(request, "login.html")
    else:
        username = request.POST.get("name")
        password = request.POST.get("pwd")
        str_code = request.POST.get('code')
        # 從session中取出事先我們保存好的驗證碼
        code = request.session['str_code']
        # 驗證碼錯誤
        if str_code.upper()!=code.upper():
            dic = {"status":"code"}
            # 驗證碼錯誤直接給ajax回復一個json格式的字符串
            return JsonResponse(dic)
        else:
            # 判斷用戶輸入的用戶名和密碼與數據庫中的是否匹配
            user_obj = auth.authenticate(username=username, password=password)
            if user_obj and str_code.upper()==code.upper():
                # 登錄成功設置session
                auth.login(request, user_obj)
                # 獲取該用戶的所有權限並保存在session中,並去重
                permissions = models.Permission.objects.filter(role__userinfo__username=user_obj.username).distinct()
                # 將該用戶可以訪問的所有url都添加到session中
                permisson_list = [i.url for i in permissions]
                request.session["permisson_list"] = permisson_list
                # print(permisson_list)
                # ['/home/', '/customers/', '/my_customers/', '/add_customers/', '/edit_customers/', '/delete_customers/(\\d+)/', '/show_file/', '/update_file/(\\d+)/', '/update_add/(\\d+)/', '/update_edit/(\\d+)/(\\d+)/']
                dic = {"status":"correct","url":reverse("home")}
                return JsonResponse(dic)
            else:
                # 賬號密碼錯誤
                dic = {"status":"userinfo"}
                return JsonResponse(dic)
View Code

ajax請求的頁面

驗證碼顯示是一個img標簽,我們讓它單獨去請求一個視圖函數,並將圖片返回

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static "css/css.css" %}">
    <link rel="stylesheet" href="{% static "bootstrap-3.3.7-dist/css/bootstrap.min.css" %}">
</head>
<body>
<div class="change">
    <div class="container ">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">

                <div class="panel panel-primary" style="margin-top: 150px">

                    <div class="panel-heading change_color">客戶關系管理系統</div>
                  <div class="panel-body ">
                    <form class="form-horizontal">

                    {% csrf_token %}
                    <div class="form-group">
                        <h3 id="h3" style="color: red"></h3>
                        <label for="name" class="col-sm-3 control-label change_color">賬號</label>

                        <div class="col-sm-8">
                            <input type="text" class="form-control" id="name" placeholder="賬號" name="username">
                            <span id="in_span" style="color: red"></span>

                        </div>
                        <span class="col-sm-3 s1"></span>
                    </div>
                    <div class="form-group">
                        <label for="pwd" class="col-sm-3 control-label change_color">密碼</label>

                        <div class="col-sm-8">
                            <input type="password" class="form-control" id="pwd" placeholder="密碼" name="password">
                        </div>

                    </div>

                    <div class="form-group">
                        <label for="yzm" class="col-sm-3 control-label change_color">驗證碼</label>

                        <div class="col-sm-5">
                            <input type="text" class="form-control" id="yzm" placeholder="驗證碼" name="code">
                            <span id="in_yzm" style="color: red"></span>
                        </div>
                        <div >
                            <img src="{% url "code" %}"
                                 id="auth_code" alt="">
                        </div>
                    </div>


                    <div class="form-group">
                        <div class="col-sm-offset-3 col-sm-9">
                            <div class="checkbox">
                                <label class="change_color">
                                    <input type="checkbox"> 是否記住密碼
                                </label>
                            </div>
                        </div>
                    </div>
                    <div class="form-group">
                        <div class="col-md-8 col-sm-offset-3">
                            <button type="button" class="btn btn-success change_color" id="login_in">登錄</button>
                            <a href="{% url "register" %}" class="btn btn-danger pull-right change_color"
                               >注冊</a>
                        </div>
                    </div>

                </form>

                  </div>
                </div>

        </div>

    </div>

</div>
</div>

</body>
<script src="{% static "jquery-3.4.1.js" %}"></script>
<script src="{% static "bootstrap-3.3.7-dist/js/bootstrap.min.js" %}"></script>
<script>
    // 判斷用戶名密碼是否為空
    $("#login_in").click(function () {
        var name = $("#name").val();
        var pwd = $("#pwd").val();
        var code = $("#yzm").val();
        var csrf_data=$("[name=csrfmiddlewaretoken]").val();
        if (!name.trim()){
            $("#in_span").text("用戶名不能位空!");
            return false;
        }

        $.ajax({
            url:"{% url "login" %}",
            type:"post",
            data:{
                name:name,
                pwd:pwd,
                code:code,
                csrfmiddlewaretoken:csrf_data,
            },
            success:function (response) {
                if (response["status"]==="code"){
                    $("#in_yzm").text("驗證碼有誤!");
                    return false
                }else {
                    if(response['status']==="correct"){
                        var href = location.search.slice(6);
                        if (href){
                            location.href=href
                        }else {
                            location.href={% url "home" %}
                        }

                    }else {
                        $("#h3").text("賬號或密碼有誤!")
                    }
                }
            }
        })
    });

    // 點擊刷新驗證碼
    $("#auth_code").click(function () {
        $("#auth_code")[0].src+='?'
    })

</script>
</html>
View Code

 

登錄成功后顯示登錄用戶的信息:

從request.user中取出先要展示的信息
{{ request.user.username }}    

4、退出和修改密碼

退出登錄:

我們為了保存登錄狀態,所以我們在登陸的時候通過django內置的anth模塊設置了session

所以我們點擊退出按鈕就需要清除用戶的登錄狀態

from django.contrib import auth  # django認證
# 退出
def logout(request):
    # 清空session
    auth.logout(request)
    return redirect("login")

修改密碼:

1、判斷舊密碼是否正確;

2、判斷新密碼與確認密碼是否一致;

3、手機號碼我們在form組件時已經做了認證,前兩項都判斷正確就需要我們將數據庫中的舊密碼替換成我們的修改密碼;

request中的user對象給我們提供了校驗舊密碼和修改密碼以及保存密碼的方法

def set_password(request):
    if request.method == "GET":
        return render(request, "set_password.html")
    else:
        dic = request.POST.dict()
        dic.pop("csrfmiddlewaretoken")
        old_password = dic["old_password"]
        new_password = dic['new_password']
        r_new_password = dic['r_new_password']
        # 判斷用戶輸入的舊密碼是否與數據庫中的一致
        if request.user.check_password(old_password):
            if new_password != r_new_password:
                return render(request,"set_password.html",{"new_password":"兩次輸入的密碼不一致!"})
            else:
                # 修改密碼
                request.user.set_password(new_password)
                request.user.save()
                return redirect("login")
        else:
            return render(request,"set_password.html",{"old_password":"舊密碼不正確!"})
View Code

5、自定義分頁器

可以保存搜索條件

#自定義分頁
#官方推薦,頁碼數為奇數
class PageNation:
    def __init__(self,base_url,current_page_num,total_counts,request,per_page_counts=10,page_number=5,):
        '''
        :param base_url:   分頁展示信息的基礎路徑
        :param current_page_num:  當前頁頁碼
        :param total_counts:  總的數據量
        :param per_page_counts:  每頁展示的數據量
        :param page_number:  顯示頁碼數
        '''
        self.base_url = base_url
        self.current_page_num = current_page_num
        self.total_counts = total_counts
        self.per_page_counts = per_page_counts
        self.page_number = page_number
        self.request = request
        try:
            self.current_page_num = int(self.current_page_num)

        except Exception:
            self.current_page_num = 1
        if self.current_page_num < 1:
            self.current_page_num = 1

        half_page_range = self.page_number // 2
        # 計算總頁數
        self.page_number_count, a = divmod(self.total_counts, self.per_page_counts)


        if a:
            self.page_number_count += 1


        if self.current_page_num > self.page_number_count:
            self.current_page_num = self.page_number_count

        if self.page_number_count <= self.page_number:
            self.page_start = 1
            self.page_end = self.page_number_count
        else:
            if self.current_page_num <= half_page_range:  #2
                self.page_start = 1
                self.page_end = page_number  #5
            elif self.current_page_num + half_page_range >= self.page_number_count:
                self.page_start = self.page_number_count - self.page_number + 1
                self.page_end = self.page_number_count
            else:
                self.page_start = self.current_page_num - half_page_range
                self.page_end = self.current_page_num + half_page_range


        import copy
        from django.http.request import QueryDict

        self.params = copy.deepcopy(request.GET)

        # ?condition = qq & wd = 1 & page = 3
        # params['page'] = current_page_num
        # query_str = params.urlencode()
    #數據切片依據,起始位置
    @property
    def start_num(self):
        start_num = (self.current_page_num - 1) * self.per_page_counts
        return start_num

    #數據切片依據,終止位置
    @property
    def end_num(self):
        end_num = self.current_page_num * self.per_page_counts
        return end_num

    # 拼接HTMl標簽
    def page_html(self):
        tab_html = ''
        tab_html += '<nav aria-label="Page navigation" class="pull-right"><ul class="pagination">'
        #首頁
        self.params['page'] = 1
        showye = '<li><a href="{0}?{1}" aria-label="Previous" ><span aria-hidden="true">首頁</span></a></li>'.format(self.base_url,self.params.urlencode())
        tab_html += showye
        # 上一頁
        if self.current_page_num == 1:
            previous_page = '<li disabled><a href="#" aria-label="Previous" ><span aria-hidden="true">&laquo;</span></a></li>'
        else:
            self.params['page'] = self.current_page_num - 1
            previous_page = '<li><a href="{0}?{1}" aria-label="Previous" ><span aria-hidden="true">&laquo;</span></a></li>'.format(
                self.base_url,self.params.urlencode())
        tab_html += previous_page

        #循環生成頁碼標簽
        for i in range(self.page_start, self.page_end + 1):
            # request.GET  {condition: qq, wd: 1,'page':1} request.GET.urlencode() condition=qq&wd=1&page=4

            self.params['page'] = i # {condition: qq, wd: 1,'page':1} urlencode() -- condition=qq&wd=1&page=4

            if self.current_page_num == i:

                one_tag = '<li class="active"><a href="{0}?{2}">{1}</a></li>'.format(self.base_url, i,self.params.urlencode()) #?condition=qq&wd=1&page=3
            else:
                one_tag = '<li><a href="{0}?{2}">{1}</a></li>'.format(self.base_url, i,self.params.urlencode())
            tab_html += one_tag

        # 下一頁
        if self.current_page_num == self.page_number_count:
            next_page = '<li disabled><a href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>'
        else:
            self.params['page'] = self.current_page_num + 1
            next_page = '<li><a href="{0}?{1}" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>'.format(self.base_url, self.params.urlencode())
        tab_html += next_page

        # 尾頁
        self.params['page'] = self.page_number_count
        weiye = '<li><a href="{0}?{1}" aria-label="Previous" ><span aria-hidden="true">尾頁</span></a></li>'.format(
            self.base_url, self.params.urlencode())

        tab_html += weiye
        tab_html += '</ul></nav>'

        return tab_html
page

6、公有|私有客戶信息展示|搜索|批量處理

 樣式:

數據展示分公有和私有客戶的信息展示:

公有客戶銷售字段為空;

私有客戶銷售字段為當前登錄用戶;

因為數據展示和搜索都走get請求,所以我們用同一個視圖函數即可;

搜索:

1、獲取用戶選擇的搜索條件及用戶輸入的搜索內容
2、去數據庫中查找匹配項,返回給前端頁面

select提交時我們通過choice來獲取用戶選擇的內容:

搜索時記得做成模糊匹配__contains

choice = request.GET.get("choice",'')  # 用戶選擇的搜索條件
wd = request.GET.get("wd",'')       # 用戶輸入的搜索內容
print(choice,wd)    # qq 362    通過choice獲取用戶選擇的搜索條件

這里注意一個Q查詢的高級用法:

from django.db.models import F,Q,Max,Min,Avg
# 實例化一個Q對象
q = Q()
# 必須是元組,因為只接收一個參數
q.children.append((choice,wd)) # choice是選擇條件,wd是搜索內容

customers_obj = models.Customer.objects.filter(consultant__isnull=True).customers_obj.filter(q)

批量處理:

1、提取用戶發來的動作和批量選擇的id
2、提前定義好這些動作的函數,利用反射執行對應的操作

批量操作的js代碼:

$("#choice").click(function () {
    // 獲取當前選中的狀態true或false
    var state = $(this).prop("checked");
    $("[name=selected_id]").prop("checked",state)
    })

整體代碼:

# 公有和私有客戶信息展示
class CustomerView(View):
    # get請求
    def get(self,request):
        # 獲取用戶選擇的和用戶輸入的搜索內容,默認為空,不選擇和不輸入get的返回結果為None
        choice = request.GET.get("choice",'')
        wd = request.GET.get("wd",'')
        print(choice,wd)   # qq 362
        # 在查詢時設置成包含的情況contains--->關鍵字
        choice = choice + '__contains'
        # 默認get第一次請求第一頁
        current_page_num = request.GET.get('page', 1)
        # 判斷請求的路徑是哪個
        if request.path == reverse('customers'):
            # 公有客戶 銷售為空
            customers_obj = models.Customer.objects.filter(consultant__isnull=True)
        else:
            # 私有客戶 銷售是當前登錄的用戶,字段名=對象--->字段_id=具體id
            customers_obj = models.Customer.objects.filter(consultant=request.user)
        # 判斷用戶是否輸入了搜索條件
        if wd:
            # 實例化一個Q對象
            q = Q()
            # 指定連接條件,默認是and
            # q.connector = "or"
            # 必須是元組,因為只接收一個參數
            q.children.append((choice,wd))
            # q.children.append(('name__contains', '小'))
            # 名字中包含wd字段的所有信息  qq__contains 66   name__contains 小
            # models.Customer.objects.filter(name__contains=wd)
            # if request.path != reverse('customers'):
            #     customers_obj = models.Customer.objects.filter(consultant=request.user)
            customers_obj = customers_obj.filter(q)
            # customers_obj = models.Customer.objects.filter(consultant__isnull=True).filter(q)
        else:
            # 所有銷售為空的,就是公共客戶
            # customers_obj = models.Customer.objects.filter(consultant__isnull=True)
            customers_obj = customers_obj
        # 每頁展示的數據量
        page_counts = 5
        # 頁碼數
        page_number = 7
        # 總數據
        total_count = customers_obj.count()
        # 判斷查詢數據是否為空
        if total_count:
            # 實列化分頁的類
            page_obj = page.PageNation(request.path, current_page_num, total_count, request,page_counts, page_number)
            # 切片:從總數據中每次切出多少條數據展示在頁面上
            customers_obj = customers_obj.order_by('-pk')[page_obj.start_num:page_obj.end_num]
            ret_html = page_obj.page_html()
            # 根據請求的路徑返回對應的頁面
            path = request.path
            if path == reverse("customers"):
                return render(request,"customers.html",{"customers_obj":customers_obj,"ret_html":ret_html,'path':path})
            else:
                return render(request,'my_customers.html',{"my_customers_obj":customers_obj,"ret_html":ret_html,'path':path})

        else:
            return render(request,"404.html")
    def post(self,request):
        # 提取用戶發來的動作和批量選擇的id
        action = request.POST.get("action")
        self.data = request.POST.getlist("selected_id") # 用getlist獲取用戶選擇的多條數據
        print(action,self.data)   # batch_delete ['107', '103']
        # 判斷這個動作是否在這個對象中
        if hasattr(self,action):
            func = getattr(self,action)
            if callable(func):
                func(request)
                # 判斷當前頁面是公戶還是私護
                if request.path == reverse("customers"):
                    return redirect("customers")
                else:
                    return redirect("my_customers")
            else:
                return render(request, "404.html")
        else:
            return render(request, "404.html")
    # 批量刪除
    def batch_delete(self,request):
        # self.data值是一個范圍,pk值在這個范圍內的都刪除
        models.Customer.objects.filter(pk__in=self.data).delete()  
    # 批量更新
    def batch_update(self,request):
        models.Customer.objects.filter(pk__in=self.data).update(status="已報名")
    # 批量公戶轉私護
    def batch_reverse_gs(self,request):
        batch_customer = models.Customer.objects.filter(pk__in=self.data)
        ret = []
        for i in batch_customer:
            if i.consultant:
                ret.append(i)
            else:
                # 通過model對象更新數據
                i.consultant=request.user
                # 調用save才會保存
                i.save()
        batch_customer.update(consultant=request.user)
    # 批量私護轉公戶
    def batch_reverse_sg(self,request):
        models.Customer.objects.filter(pk__in=self.data).update(consultant=None)
View Code

7、基於modelform客戶信息的增刪改

增:

1、獲取用戶輸入的信息
2、將用戶輸入的信息request.POST交給modelform做校驗
3、校驗成功通過save方法直接保存到數據庫

生成標簽

# modelform生成添加頁面
class MyModelForm(forms.ModelForm):
    class Meta:
        model = models.Customer    # 指定哪張表
        fields="__all__"
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        from multiselectfield.forms.fields import MultiSelectFormField
        for field in self.fields.values():
            # 不給哪個字段添加樣式
            if not isinstance(field, MultiSelectFormField):
                field.widget.attrs.update({
                    'class': 'form-control',
                })

視圖函數

# 添加客戶信息
def add_customers(request):
    model_obj = MyModelForm()
    if request.method=="GET":
        return render(request,"add_customers.html",{"model_obj":model_obj})
    else:
        # 將用戶輸入的信息交給Mymodel對象做校驗
        model_obj = MyModelForm(request.POST)
        # 如果校驗成功
        if model_obj.is_valid():
            # 將用戶輸入的所有信息保存到數據庫,數據會一一對應保存
            model_obj.save()
            return redirect("customers")
        else:
            return render(request, "add_customers.html", {"model_obj": model_obj})

前端頁面

{% extends "template.html" %}
{% load static %}
{% block countent %}
<div class="container">
    <div class="row">

        <div class="col-md-6 col-md-offset-3">
                <div class="panel-heading">
                    <h3 class="panel-title">添加信息</h3>
                </div>
                <div class="panel-body">
                    <form action="{% url "add_customers" %}" method="post" novalidate>
                    {% csrf_token %}
                        {% for filed in model_obj %}
                            <label for="{{ filed.id_for_label }}">{{ filed.label }}</label>
                            {{ filed }}{{ filed.errors.0 }}
                        {% endfor %}
                        
                        <input type="submit" value="提交" class="btn btn-primary pull-right">
                    </form>
                </div>
        </div>
    </div>
</div>
{% endblock %}
View Code

刪:

1、前端發送post請求時在請求的url后面攜帶要刪除數據的id
2、對應的視圖函數到數據庫中查到指定的id刪除即可
<a href="{% url "delete_customers" customers.pk %}"class="btn btn-danger btn-sm">刪除</a>
# 刪除客戶信息
def delete_customers(request,pk):
    models.Customer.objects.filter(pk=pk).delete()
    return redirect("customers")

改:

1、前端發送post請求時在請求的url后面攜帶要刪除數據的id
2、對應的視圖函數查找該id的對像,返回給前端頁面,
3、在前端修改完數據后走的post請求,直接將用戶輸入的傳給modelform並指定instance=要編輯的對象,如果不寫instance代表添加,
4、modelform校驗完數據通過save保存到數據庫
<a href="{% url "edit_customers" customers.pk %}"class="btn btn-primary btn-sm">編輯</a>
# 編輯客戶信息
def edit_customers(request,pk):
    if request.method=="GET":
        edit_obj = models.Customer.objects.filter(pk=pk).first()
        model_obj = MyModelForm(instance=edit_obj) # 指定編輯哪個信息
        return render(request,"edit_customers.html",{"model_obj":model_obj})
    else:
        edit_obj = models.Customer.objects.filter(pk=pk).first()
        # instance指定該字段是更新而不是添加,並將數據進行驗證
        model_obj = MyModelForm(request.POST,instance=edit_obj)
        if model_obj.is_valid():
            # 更新數據
            model_obj.save()
            return redirect("customers")
        else:
            # 檢驗不通過
            return render(request,"edit_customers.html",{"model_obj":model_obj})

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM