使用Django完成CRM管理系統


CRM介紹:

  CRM即客戶關系管理,是指企業用CRM技術來管理與客戶之間的關系。在不同場合下,CRM可能是一個管理學術語,可能是一個軟件系統。通常所指的CRM,指用計算機自動化分析銷售、市場營銷、客戶服務以及應用等流程的軟件系統。它的目標是通過提高客戶的價值、滿意度、贏利性和忠實度來縮減銷售周期和銷售成本、增加收入、尋找擴展業務所需的新的市場和渠道。CRM是選擇和管理有價值客戶及其關系的一種商業策略,CRM要求以客戶為中心的企業文化來支持有效的市場營銷、銷售與服務流程。

本次CRM項目的需求以及特點:

  本次項目的特點基於教學系統,在此基礎上進行的開發,不僅有傳統意義CRM的功能,還具備了一些擴展的功能,目的旨在提高效率,解決辦公上的一些痛點問題.

  需求:

    1, 不同的用戶使用該系統, 顯示不同的展示頁面.

    2, 解決銷售在聯系客戶時產生的沖突.

    3, 客戶(學員)可以實時查看自己的有關信息,以及課程進度

    4, 老師可以布置作業,給自己所在的班級的同學評分.

    ...

  掌握的知識:

    1, python

    2, Django框架的使用

    3, bootstrap的使用

    4, jQuery的使用

    5, ajax

    6, HTML/CSS/JS

    ...

花里胡哨的說這么多,下面一步一步來吧!!!

第一部分: 創建crm_system的Django項目, 並完成基礎的配置

第一步:

下載安裝Django

pip install django==1.11.15

第二步:

打開pycharm創建一個項目

第三步:

配置settings.py文件

  1, 手動創建static文件,用於存放靜態文件

  2, settings文件的配置

"""
Django settings for blog_crm project.

Generated by 'django-admin startproject' using Django 1.11.15.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'xx%re+j4h-@mwr_%u8c@46im%m==e877jadvqz@4lszx*fl!33'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'crm_manage.apps.CrmManageConfig',  # 注冊的app
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',  # csrf中間件
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'blog_crm.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'blog_crm.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': "blog",
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': 'localhost',
        'PORT': 3306,

    }
}


# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'  # 靜態文件的別名
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static")
]

AUTH_USER_MODEL = "crm_manage.UserProfile"  # 不是用admin提供的表,自己對auth的表進行擴展后的表名.規則: app.表名

LOGIN_URL = " login"  # 不使用Django中自己跳轉的/account/login,重新配置為login(自己創建的)
settings.py的配置

  3, 使用MySQL,使用pymysql

# settings文件中配置完成后.
# 1, 在settings同級目錄下的__init__文件中導入pymysql模塊
import pymysql
pymysql.install_as_MySQLdb()

  4,創建庫,建表

# Django不能創建庫
# 1, 打開CMD,進入mysql
mysql -uroot -p

# 2, 創建數據庫
create database crm_data;

  5, 在app下的models中創建項目用的表(表結構復雜,不貼出來了)

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 multiselectfield import MultiSelectField
from django.utils.translation import ugettext_lazy as _

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

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

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

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

seek_status_choices = (('A', '近期無報名計划'), ('B', '一個月內包名'),
                       ('C', '2周內報名'), ('D', '1周內報名'),
                       ('E', '定金'), ('F', '到班'), ('G', '全款'), ('H', '無效'))

pay_type_choices = (('deposit', "訂金/報名費"),
                    ("tuition", "學費"),
                    ("transfer", "轉班"),
                    ("dropout", "退學"),
                    ("refund", "退款"),
                    )

attendance_choice = (("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 Customer(models.Model):
    """
    客戶表
    """
    qq = models.CharField("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)
    birthday = models.DateField("出生日期", default=None, help_text="格式yyyy-mm-dd", blank=True, null=True)
    phone = models.BigIntegerField("手機號", 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)
    course = MultiSelectField("咨詢課程", choices=course_choices)
    class_type = models.CharField("班級類型", max_length=64, choices=class_type_choices, default="fulltime")
    customer_note = models.TextField("課程顧問咨詢內容", blank=True, null=True, help_text=True)
    status = models.CharField("狀態", choices=enroll_status_choices, max_length=64, default="unregistered",
                              help_text="選擇客戶此時的狀態")
    network_cosult_note = models.TextField(blank=True, null=True, verbose_name="網絡咨詢師咨詢內容")
    date = models.DateTimeField("咨詢日期", auto_now_add=True)
    last_consult_date = models.DateField("最后跟進日期", blank=True, null=True)
    next_date = models.DateField("預計再次跟進事件", blank=True, null=True)
    private = models.BooleanField(verbose_name="私人客戶", default=True)
    # 關聯對象
    network_consultant = models.ForeignKey("UserProfile", blank=True, null=True, verbose_name="咨詢師")
    consultant = models.ForeignKey("UserProfile", verbose_name="銷售", related_name="consultant")
    class_list = models.ManyToManyField("ClassList", verbose_name="已報班級")

    def __str__(self):
        return "{}+{}".format(self.name, self.qq)


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 ContractTemplate(models.Model):
    """
    合同模板表
    """
    name = models.CharField("合同名稱", max_length=128, unique=True)
    content = models.TextField("合同內容")
    date = models.DateField(auto_now=True)


class ClassList(models.Model):
    course = models.CharField("課程名稱", max_length=64, choices=course_choices)
    semester = models.IntegerField("學期")
    campuses = models.ForeignKey("Campuses", verbose_name="校區")
    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)
    teachers = models.ManyToManyField("UserProfile", verbose_name="講師")
    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 self.course


class ConsultRecord(models.Model):
    """
    跟進記錄
    """
    consultant = models.ForeignKey("Customer", verbose_name="所咨詢客戶")
    note = models.TextField(verbose_name="跟進內容...")
    status = models.CharField("跟進狀態", max_length=8, choices=seek_status_choices, help_text="選擇客戶此時的狀態")
    date = models.DateTimeField("跟進日期", auto_now_add=True)
    delete_status = models.BooleanField(verbose_name="刪除狀態", default=False)


class Enrollment(models.Model):
    """
    報名表
    """
    why_us = models.TextField("為什么選擇我們報名", max_length=1024, default=None, blank=True, null=True)
    your_expectation = models.TextField("學完想達到具體期望", max_length=1024, blank=True, null=True)
    contract_agreed = models.BooleanField("我已經認真閱讀完培訓協議並同意全部協議內容")
    contract_approved = models.BooleanField("審批通過", help_text="在審批完學員的資料無誤后勾選此項,合同即生效")
    enrolled_date = models.DateTimeField(auto_now_add=True, verbose_name="報名日期")
    memo = models.TextField("備注", blank=True, null=True)
    delete_status = models.ForeignKey("Customer", verbose_name="客戶名稱")
    school = models.ForeignKey('Campuses')
    enrolment_class = models.ForeignKey("ClassList", verbose_name="所報班級")


class PaymentRecord(models.Model):
    """
    繳費記錄
    """
    pay_type = models.CharField("費用類型", choices=pay_type_choices, max_length=64, default="deposit")
    paid_fee = models.IntegerField("費用數額", default=0)
    note = models.TextField("備注", blank=True, null=True)
    date = models.DateTimeField("交款日期", auto_now_add=True)
    delete_status = models.BooleanField(verbose_name="刪除狀態", default=False)
    course = models.CharField("課程名", choices=course_choices, max_length=64, blank=True, null=True, default="N/A")
    class_type = models.CharField("班級類型", choices=class_type_choices, max_length=64, blank=True, null=True,
                                  default="N/A")
    enrollment_class = models.ForeignKey("ClassList", verbose_name="所報班級", blank=True, null=True)
    customer = models.ForeignKey("Customer", verbose_name="客戶")
    consultant = models.ForeignKey("UserProfile", verbose_name="銷售")


class CourseRecord(models.Model):
    """
    課程記錄表
    """
    day_num = models.IntegerField("節次", help_text="此處填寫第幾節課或第幾天課程..., 必須為數字")
    date = models.DateField(auto_now_add=True, verbose_name="上課日期")
    course_title = models.CharField("本屆課程鏢旗", max_length=64, blank=True, null=True)
    has_homework = models.BooleanField(default=True,
                                       verbose_name="本節有作業")
    homework_title = models.CharField('本節作業標題', max_length=64,
                                      blank=True, null=True)
    homework_memo = models.TextField('作業描述', max_length=500,
                                     blank=True, null=True)
    scoring_point = models.TextField('得分點', max_length=300,
                                     blank=True, null=True)
    re_class = models.ForeignKey('ClassList', verbose_name="班級")
    teacher = models.ForeignKey('UserProfile', verbose_name="講師")

    class Meta:
        unique_together = ("re_class", "day_num")


class StudyRecord(models.Model):
    """
    上課記錄
    """
    attendance = models.CharField("考勤", choices=attendance_choice, default="checked", max_length=64)
    score = models.IntegerField("本節成績", choices=score_choices, default=-1)
    homework_note = models.CharField(max_length=255, verbose_name="作業批語", blank=True, null=True)
    date = models.DateTimeField(auto_now_add=True)
    note = models.CharField("備注", max_length=255, blank=True, null=True)
    homework = models.FileField(verbose_name='作業文件', blank=True,
                                null=True, default=None)
    course_record = models.ForeignKey('CourseRecord',
                                      verbose_name="某節課程")
    student = models.ForeignKey('Customer', verbose_name="學員")

    class Meta:
        unique_together = ("course_record", "student")


class UserManage(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, username, password, **extra_fields):
        """
        Creates and saves a User with the given username, email and password.
        """
        if not username:
            raise ValueError('The given username must be set')
        username = self.normalize_email(username)
        # username = self.model.normalize_username(username)
        user = self.model(username=username, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, username, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(username, password, **extra_fields)

    def create_superuser(self, username, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(username, password, **extra_fields)


# A few helper functions for common logic between User and AnonymousUser.
def _user_get_all_permissions(user, obj):
    permissions = set()
    for backend in auth.get_backends():
        if hasattr(backend, "get_all_permissions"):
            permissions.update(backend.get_all_permissions(user, obj))
    return permissions


def _user_has_perm(user, perm, obj):
    """
    A backend can raise `PermissionDenied` to short-circuit permission checking.
    """
    for backend in auth.get_backends():
        if not hasattr(backend, 'has_perm'):
            continue
        try:
            if backend.has_perm(user, perm, obj):
                return True
        except PermissionDenied:
            return False
    return False


def _user_has_module_perms(user, app_label):
    """
    A backend can raise `PermissionDenied` to short-circuit permission checking.
    """
    for backend in auth.get_backends():
        if not hasattr(backend, 'has_module_perms'):
            continue
        try:
            if backend.has_module_perms(user, app_label):
                return True
        except PermissionDenied:
            return False
    return False


class Department(models.Model):
    name = models.CharField(max_length=32, verbose_name="部門名稱")
    count = models.IntegerField(verbose_name="人數", default=0)


class UserProfile(AbstractBaseUser, PermissionsMixin):
    username = models.EmailField(
        max_length=255,
        unique=True,
    )
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    name = models.CharField('名字', max_length=32)
    department = models.ForeignKey('Department', default=None,
                                   blank=True, null=True)
    mobile = models.CharField('手機', max_length=32, default=None,
                              blank=True, null=True)

    memo = models.TextField('備注', blank=True, null=True, default=None)
    date_joined = models.DateTimeField(auto_now_add=True)

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['name']

    class Meta:
        verbose_name = '賬戶信息'
        verbose_name_plural = "賬戶信息"

    def get_full_name(self):
        # The user is identified by their email address
        return self.name

    def get_short_name(self):
        # The user is identified by their email address
        return self.username

    def __str__(self):  # __unicode__ on Python 2
        return self.username

    def has_perm(self, perm, obj=None):
        #     "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always

        if self.is_active and self.is_superuser:
            return True
        return _user_has_perm(self, perm, obj)

    def has_perms(self, perm_list, obj=None):
        #     "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        for perm in perm_list:
            if not self.has_perm(perm, obj):
                return False
        return True

    def has_module_perms(self, app_label):
        #     "Does the user have permissions to view the app `app_label`?"
        #     Simplest possible answer: Yes, always
        if self.is_active and self.is_superuser:
            return True

        return _user_has_module_perms(self, app_label)

    objects = UserManage()
models下創建表

  6, 執行數據遷移的命令

# 記錄數據表有哪些改變
python manage.py makemigrations
# 在數據庫中真正寫入數據
python manage.py migrate

 

第二部分:完成登陸注冊功能

第一步:設計url,創建html頁面

 

第二步:

在app中創建myforms.py文件,創建自定義的form表單

第三步:自定義form表單

#  myforms.py

# ! /usr/bin/env python3.6
# -*- coding: utf-8 -*-
# 2018/9/25 20:28


from crm_manage import models
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


def check(value):
    if "alex" in value:
        raise ValidationError("含有敏感字符")


class LoginForm(forms.Form):
    username = forms.CharField(
        label="用戶名",
        min_length=5,
        max_length=20,
        # initial="張三",
        required=True,
        validators=[check, ],
        widget=widgets.TextInput(attrs={"class": "form-control", "placeholder": "Email address"}),
        error_messages={"min_length": "用戶名最少是5位", "max_length": "用戶名最長不能超過20位"}
    )
    pwd = forms.CharField(
        label="密碼",
        min_length=8,
        required=True,
        widget=widgets.PasswordInput(attrs={"class": "form-control", "placeholder": "Password"}),
        error_messages={"min_length": "密碼最少需要8位"}
    )


class RegForm(forms.ModelForm):
    # 還可以添加ModelForm中沒有的字段
    re_password = forms.CharField(label="確認密碼", widget=forms.PasswordInput(attrs={"class": "form-control"}))

    class Meta:
        model = models.UserProfile
        # 使用所有的字段
        fields = "__all__"
        # fields = ["username", "password"]
        # 派出列表中的字段
        exclude = ["is_active"]
        labels = {
            "username": "用戶名",
            "name": "真實姓名",
            "password": "密碼",
            "department": "部門",
        }
        widgets = {
            "username": forms.widgets.TextInput(attrs={"class": "form-control"}),
            "password": forms.widgets.PasswordInput(attrs={"class": "form-control"}),
        }

    # labels = {
    #     "username": "用戶名",
    #     "password": "密碼",
    # }

    # 對每個字段添加屬性, self.fields是一個有序的字典, self.fields.values()獲取出每個字段的values值,即每個對象,
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs.update({"class": "form-control"})

    # 校驗兩次密碼是否一致
    def clean(self):
        if self.cleaned_data.get("password") != self.cleaned_data.get("re_password"):
            self.add_error("re_password", "兩次密碼不一致")
            raise ValidationError("兩次密碼不一致")
        return self.cleaned_data
自定義forms

第四步:視圖函數中使用自定義的form

# views.py

from django.shortcuts import render, redirect
from . import forms
from django.contrib import auth
from django.contrib.auth.decorators import login_required
from crm_manage.forms import RegForm
from . import models


def login(request):
    msg = ''
    loginForm = forms.LoginForm()
    if request.method == "POST":
        loginForm = forms.LoginForm(request.POST)
        username = request.POST.get('username')
        pwd = request.POST.get("pwd")
        obj = auth.authenticate(request, username=username, password=pwd)
        if obj:
            auth.login(request, obj)
            return redirect("/index/")
        else:
            msg = "用戶名密碼錯誤"
    return render(request, "login.html", {"loginForm": loginForm, "msg": msg})


def regForm(request):
    reg_obj = RegForm()
    if request.method == "POST":
        reg_obj = RegForm(request.POST)
        if reg_obj.is_valid():
            # 數據庫中寫入數據
            # 第一種方法
            # reg_obj.cleaned_data.pop("groups")
            # reg_obj.cleaned_data.pop("user_permissions")
            # reg_obj.cleaned_data.pop("re_password")
            # models.UserProfile.objects.create_user(**reg_obj.cleaned_data)

            # 第二種方法(此方法寫入數據庫中的密碼是明文,所以多了一步設置密碼的操作)
            password = reg_obj.cleaned_data.get("password")
            user = reg_obj.save()
            user.set_password(password)
            user.save()

            return redirect("/login/")
    return render(request, "reg.html", {"reg_obj": reg_obj})


@login_required
def index(request):
    return render(request, "index.html")


def logout(request):
    auth.logout(request)
    return redirect("/login/")


@login_required
def control(request):
    customers = models.Customer.objects.all()
    return render(request, "control.html", {"customers": customers})
視圖函數

第五步:

前端中展示

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! -->
    <title>Bootstrap 101 Template</title>
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">

</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-4 col-md-offset-4">
            <form class="form-signin" method="post" novalidate>
                {% csrf_token %}
                <h2 class="form-signin-heading">歡迎登陸</h2>
                <label for="inputEmail" class="sr-only">Email address</label>
                {{ loginForm.username }}
                <br>
                <label for="inputPassword" class="sr-only">{{ loginForm.pwd.label }}</label>
                {{ loginForm.pwd }}
                <div class="checkbox">
                    <label>
                        <input type="checkbox" value="remember-me"> Remember me
                    </label>
                </div>
                <button class="btn btn-lg btn-primary btn-block" type="submit">登陸</button>
                <br>
                <a href="/reg/">
                    <button class="btn btn-lg btn-primary btn-block" type="button">注冊</button>
                </a>
            </form>
        </div>
    </div>
</div>
</body>
</html>
登陸頁面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">

</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" class="form-horizontal" novalidate method="post">
                {% csrf_token %}
                <h2 class="form-signin-heading">注冊</h2>
                <div class="form-group {% if reg_obj.username.errors.0 %}has-error{% endif %}">
                    <label for="{{ reg_obj.username.id_for_label }}" class="col-sm-2 control-label">
                        {{ reg_obj.username.label }}
                    </label>
                    <div class="col-sm-10">
                        {{ reg_obj.username }}
                    </div>
                    <span id="helpBlock2" class="help-block">{{ reg_obj.username.errors.0 }}</span>
                </div>

                <div class="form-group {% if reg_obj.name.errors.0 %}has-error{% endif %}">
                    <label for="{{ reg_obj.name.id_for_label }}" class="col-sm-2 control-label">
                        {{ reg_obj.name.label }}
                    </label>
                    <div class="col-sm-10">
                        {{ reg_obj.name }}
                    </div>
                    <span id="helpBlock2" class="help-block">{{ reg_obj.name.errors.0 }}</span>
                </div>

                <div class="form-group {% if reg_obj.password.errors %}has-error{% endif %}">
                    <label for="{{ reg_obj.password.id_for_label }}" class="col-sm-2 control-label">
                        {{ reg_obj.password.label }}
                    </label>
                    <div class="col-sm-10">
                        {{ reg_obj.password }}
                    </div>
                    <span id="helpBlock2" class="help-block">{{ reg_obj.password.errors.0 }}</span>
                </div>

                <div class="form-group {% if reg_obj.re_password.errors %}has-error{% endif %}">
                    <label for="{{ reg_obj.password.id_for_label }}" class="col-sm-2 control-label">
                        {{ reg_obj.re_password.label }}
                    </label>
                    <div class="col-sm-10">
                        {{ reg_obj.re_password }}
                    </div>
                    <span id="helpBlock2" class="help-block">{{ reg_obj.re_password.errors.0 }}</span>
                </div>

                <div class="form-group">
                    <label for="{{ reg_obj.department.id_for_label }}" class="col-sm-2 control-label">
                        {{ reg_obj.department.label }}
                    </label>
                    <div class="col-sm-10">
                        {{ reg_obj.department }}
                    </div>
                </div>

                <button class="btn btn-lg btn-primary btn-block" type="submit">提交</button>
            </form>
        </div>
    </div>
</div>
</body>
</html>
注冊頁面
<!doctype html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1, user-scalable=no">
    <title>實名認證</title>
    <link href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css" title="" rel="stylesheet">
    <link title="" href="/static/css/style.css" rel="stylesheet" type="text/css">
    <link title="blue" href="/static/css/dermadefault.css" rel="stylesheet" type="text/css">
    <link href="/static/css/templatecss.css" rel="stylesheet" title="" type="text/css">
    <script src="/static/jquery/jquery-1.10.2.js"></script>
    <script src="/static/js/jquery.cookie.js" type="text/javascript"></script>
    <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js" type="text/javascript"></script>
</head>
<body style="">
<nav class="nav navbar-default navbar-mystyle navbar-fixed-top">
    <div class="navbar-header">
        <button class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand mystyle-brand"><span class="glyphicon glyphicon-home"></span></a></div>
    <div class="collapse navbar-collapse">
        <ul class="nav navbar-nav">
            <li class="li-border"><a class="mystyle-color" href="#">管理控制台</a></li>
        </ul>
        <ul class="nav navbar-nav pull-right">
            <li class="li-border">
                <a href="#" class="mystyle-color">
                    <span class="glyphicon glyphicon-bell"></span>
                    <span class="topbar-num">0</span>
                </a>
            </li>
            <li class="li-border dropdown"><a href="#" class="mystyle-color" data-toggle="dropdown">
                <span class="glyphicon glyphicon-search"></span> 搜索</a>
                <div class="dropdown-menu search-dropdown">
                    <div class="input-group">
                        <input type="text" class="form-control">
                        <span class="input-group-btn">
<button type="button" class="btn btn-default">搜索</button>
</span>
                    </div>
                </div>
            </li>
            <li class="dropdown li-border"><a href="#" class="dropdown-toggle mystyle-color" data-toggle="dropdown">幫助與文檔<span
                    class="caret"></span></a>
                <ul class="dropdown-menu">
                    <li><a href="#">幫助與文檔</a></li>
                    <li class="divider"></li>
                    <li><a href="#">論壇</a></li>
                    <li class="divider"></li>
                    <li><a href="#">博客</a></li>
                </ul>
            </li>
            {#            <li class="dropdown li-border"><a href="#" class="dropdown-toggle mystyle-color" data-toggle="dropdown">605875855@qq.com<span#}
            {#                    class="caret"></span></a>#}
            {#                <ul class="dropdown-menu">#}
            {#                    <li><a href="#">退出</a></li>#}
            {#                </ul>#}
            {#            </li>#}
            <li class="li-border"><a href="/login/" class="mystyle-color">登陸</a></li>
        </ul>
    </div>
</nav>
<div class="down-main">
    <div class="left-main left-off">
        <div class="sidebar-fold"><span class="glyphicon glyphicon-menu-hamburger"></span></div>
        <div class="subNavBox">
            <div class="sBox">
                <div class="subNav"><span class="title-icon glyphicon glyphicon-chevron-up"></span><span
                        class="sublist-title">用戶中心</span>
                </div>
                <ul class="navContent" style="display: block;">
                    <li>
                        <div class="showtitle" style="width: 100px; display: none;"><img src="/static/img/leftimg.png">賬號管理
                        </div>
                        <a href=""><span class="sublist-icon glyphicon glyphicon-user"></span><span
                                class="sub-title">賬號管理</span></a></li>
                    <li>
                        <div class="showtitle" style="width: 100px; display: none;"><img src="/static/img/leftimg.png">消息中心
                        </div>
                        <a href=""><span class="sublist-icon glyphicon glyphicon-envelope"></span><span
                                class="sub-title">消息中心</span></a></li>
                    <li>
                        <div class="showtitle" style="width:100px;"><img src="/static/img/leftimg.png">短信</div>
                        <a href=""><span class="sublist-icon glyphicon glyphicon-bullhorn"></span><span
                                class="sub-title">短信</span></a></li>
                    <li class="active">
                        <div class="showtitle" style="width: 100px; display: none;"><img src="/static/img/leftimg.png">實名認證
                        </div>
                        <a href=""><span class="sublist-icon glyphicon glyphicon-credit-card"></span><span
                                class="sub-title">實名認證</span></a></li>
                </ul>
            </div>
        </div>
    </div>
    <div class="right-product view-product right-off">
        <div class="table-responsive">
            {% block main_info %}
                <table class="table table-striped">
                    <thead>
                    <tr>
{#                        <th>序號</th>#}
                        <th>ID</th>
                        <th>姓名</th>
                        <th>qq</th>
                        <th>性別</th>
                        <th>客戶來源</th>
                        <th>咨詢課程</th>
                        <th>最后一次咨詢時間</th>
                    </tr>
                    </thead>
                    <tbody>
                    {% for customer in customers %}
                        {% if customer.private == 0 %}
                            <tr>
{#                                <td>{{ forloop.counter }}</td>#}
                                <td>{{ customer.id }}</td>
                                <td>{{ customer.name }}</td>
                                <td>{{ customer.qq }}</td>
                                <td>{{ customer.sex }}</td>
                                <td>{{ customer.source }}</td>
                                <td>{{ customer.course }}</td>
                                <td>{{ customer.last_consult_date }}</td>
                            </tr>
                        {% endif %}

                    {% endfor %}

                    </tbody>
                </table>
                <a href="/logout/"><button class="btn btn-block">注銷</button></a>
            {% endblock %}

        </div>
    </div>
</div>
<script type="text/javascript">
    $(function () {
        /*左側導航欄顯示隱藏功能*/
        $(".subNav").click(function () {
            /*顯示*/
            if ($(this).find("span:first-child").attr('class') == "title-icon glyphicon glyphicon-chevron-down") {
                $(this).find("span:first-child").removeClass("glyphicon-chevron-down");
                $(this).find("span:first-child").addClass("glyphicon-chevron-up");
                $(this).removeClass("sublist-down");
                $(this).addClass("sublist-up");
            }
            /*隱藏*/
            else {
                $(this).find("span:first-child").removeClass("glyphicon-chevron-up");
                $(this).find("span:first-child").addClass("glyphicon-chevron-down");
                $(this).removeClass("sublist-up");
                $(this).addClass("sublist-down");
            }
            // 修改數字控制速度, slideUp(500)控制卷起速度
            $(this).next(".navContent").slideToggle(300).siblings(".navContent").slideUp(300);
        });
        /*左側導航欄縮進功能*/
        $(".left-main .sidebar-fold").click(function () {

            if ($(this).parent().attr('class') == "left-main left-full") {
                $(this).parent().removeClass("left-full");
                $(this).parent().addClass("left-off");

                $(this).parent().parent().find(".right-product").removeClass("right-full");
                $(this).parent().parent().find(".right-product").addClass("right-off");


            }
            else {
                $(this).parent().removeClass("left-off");
                $(this).parent().addClass("left-full");

                $(this).parent().parent().find(".right-product").removeClass("right-off");
                $(this).parent().parent().find(".right-product").addClass("right-full");


            }
        });
    })
</script>


</body>
</html>
主頁面

部分頁面效果展示:

登陸頁面:

注冊頁面:

 

第三部分:完成主頁面展示信息功能和分頁功能

 主頁面代碼:

主模板:

<!DOCTYPE html>
<html lang="en">
<head>
    {% load static %}
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="icon" href="{% static "img/luffy-logo.png" %}">
    <link rel="stylesheet" href="{% static "bootstrap-3.3.7-dist/css/bootstrap.min.css" %}">
    <link rel="stylesheet" href="{% static "css/layout.css" %}">
    <link rel="stylesheet" href="{% static "font-awesome-4.7.0/css/font-awesome.min.css" %}">


</head>
<body>

<nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container-fluid">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
                    aria-expanded="false" aria-controls="navbar">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>

            <a class="navbar-brand" href="#"><i class="fa fa-tripadvisor fa-fw" aria-hidden="true"
                                                style="margin-right: 6px;"></i>CRM管理系統</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
            <div class="nav navbar-nav navbar-right">
                <img src="{% static "img/default.png" %}" alt="" class="dropdown-toggle img-circle" width="46px" id="dropdownMenu1" data-toggle="dropdown"
                        aria-haspopup="true" aria-expanded="true">
                <img src="" alt="">
                <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
                    <li><a href="#">Action</a></li>
                    <li><a href="#">Another action</a></li>
                    <li><a href="#">Something else here</a></li>
                    <li role="separator" class="divider"></li>
                    <li><a href="#">Separated link</a></li>
                </ul>
            </div>
            <ul class="nav navbar-nav navbar-right">
                <li>
                    <a href="#">任務<i class="fa fa-bell-o fa-fw" aria-hidden="true"></i>
                        <span class="badge">4</span>
                    </a>
                </li>
                <li>
                    <a href="#">通知<i class="fa fa-envelope-o fa-fw" aria-hidden="true"></i>
                        <span class="badge">2</span>
                    </a>
                </li>
                <li>
                    <a href="#">消息<i class="fa fa-comment-o fa-fw" aria-hidden="true"></i>
                        <span class="badge">3</span>
                    </a>
                </li>
                <li>
                    <a href="#">更多<i class="fa fa-ellipsis-v fa-fw" aria-hidden="true"></i></a>
                </li>
            </ul>
        </div>

    </div>
</nav>

<div class="container-fluid">
    <div class="row">
        <div class="col-sm-3 col-md-2 sidebar">
            <ul class="nav nav-sidebar">
                <li class="active"><a href="#">信息廣場 <span class="sr-only">(current)</span></a></li>
                <li><a href="#">個人中心</a></li>
                <li><a href="#">幫助</a></li>
                <li><a href="#">更多</a></li>
            </ul>
        </div>
        <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">

            {% block content %}
                <h1 class="page-header">Dashboard</h1>

                <h2 class="sub-header">Section title</h2>

                <div class="table-responsive">
                    <table class="table table-striped">
                        <thead>
                        <tr>
                            <th>#</th>
                            <th>Header</th>
                            <th>Header</th>
                            <th>Header</th>
                            <th>Header</th>
                        </tr>
                        </thead>
                        <tbody>
                        <tr>
                            <td>1,001</td>
                            <td>Lorem</td>
                            <td>ipsum</td>
                            <td>dolor</td>
                            <td>sit</td>
                        </tr>
                        <tr>
                            <td>1,002</td>
                            <td>amet</td>
                            <td>consectetur</td>
                            <td>adipiscing</td>
                            <td>elit</td>
                        </tr>
                        <tr>
                            <td>1,003</td>
                            <td>Integer</td>
                            <td>nec</td>
                            <td>odio</td>
                            <td>Praesent</td>
                        </tr>
                        </tbody>
                    </table>
                </div>
            {% endblock %}

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

<script src="{% static "jquery/jquery-1.10.2.js" %}"></script>
<script src="{% static "bootstrap-3.3.7-dist/js/bootstrap.js" %}"></script>
</body>
</html>
主頁面

子模板:繼承主模板

{% extends "layout.html" %}
{% block content %}
    <h2 class="sub-header" style="display: inline-block">公戶信息</h2>

    <a href="/add/">
        <button type="button" class="btn btn-success" style="float: right; margin-top: 30px; margin-right: 50px;">添加
        </button>
    </a>
    <div class="table-responsive">
        <table class="table table-striped">
            <thead>
            <tr>
                <th style="text-align: center">序號</th>
                <th style="text-align: center">ID</th>
                <th style="text-align: center">QQ</th>
                <th style="text-align: center">QQ昵稱</th>
                <th style="text-align: center">姓名</th>
                <th style="text-align: center">客戶來源</th>
                <th style="text-align: center">班級類型</th>
                <th style="text-align: center">銷售</th>
                <th style="text-align: center">狀態</th>
                <th style="text-align: center">日期</th>
                <th style="text-align: center">咨詢日期</th>
                <th style="text-align: center">已報班級</th>
                <th style="text-align: center">
                    操作
                </th>
            </tr>
            </thead>
            <tbody>
            {% for customer in customers %}
                {% if customer.private == 0 %}
                    <tr>
                        <td>{{ forloop.counter }}</td>
                        <td>{{ customer.id }}</td>
                        <td>{{ customer.qq }}</td>
                        <td>{{ customer.qq_name|default:"暫無" }}</td>
                        <td>{{ customer.name|default:"暫無"  }}</td>
                        <td>{{ customer.get_source_display }}</td>
                        <td>{{ customer.get_class_type_display }}</td>
                        <td>{{ customer.consultant }}</td>
                        <td>
                            {{ customer.show_status }}
                        </td>
                        <td>{{ customer.date }}</td>
                        <td>{{ customer.last_consult_date }}</td>
                        <td>{{ customer.show_class }}</td>
                        <td>
                            <a href="/edit/">
                                <button type="button" class="btn btn-info">編輯</button>
                            </a>

                            <a href="/remove/">
                                <button type="button" class="btn btn-danger">刪除</button>
                            </a>
                        </td>
                    </tr>
                {% endif %}

            {% endfor %}

            </tbody>
        </table>
    </div>
{% endblock %}
主頁面HTML

form表單的代碼:

# ! /usr/bin/env python3.6
# -*- coding: utf-8 -*-
# 2018/9/25 20:28


from crm_manage import models
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


def check(value):
    if "alex" in value:
        raise ValidationError("含有敏感字符")


def checkio(s):
    fs = "".join(filter(str.isalnum, s))
    return (not fs.isalpha() and not fs.isdigit() and not fs.islower() and not fs.isupper())


class LoginForm(forms.Form):
    username = forms.CharField(
        label="用戶名",
        min_length=5,
        max_length=20,
        # initial="張三",
        required=True,
        validators=[check, ],
        widget=widgets.TextInput(attrs={"class": "form-control", "placeholder": "Email address"}),
        error_messages={"min_length": "用戶名最少是5位", "max_length": "用戶名最長不能超過20位"}
    )
    pwd = forms.CharField(
        label="密碼",
        min_length=8,
        required=True,
        widget=widgets.PasswordInput(attrs={"class": "form-control", "placeholder": "Password"}),
        error_messages={"min_length": "密碼最少需要8位"}
    )


class AddForm(forms.ModelForm):
    class Meta:
        model = models.Customer
        fields = "__all__"

    # 給每個input標簽添加form-control.
    def __init__(self, *args, **kwargs):
        super(AddForm, self).__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs.update({"class": "form-control"})


class RegForm(forms.ModelForm):
    # 還可以添加ModelForm中沒有的字段
    re_password = forms.CharField(label="確認密碼", widget=forms.PasswordInput(attrs={"class": "form-control"}))

    class Meta:
        model = models.UserProfile
        # 使用所有的字段
        # fields = "__all__"
        fields = ["username", "name", "password", "re_password", "department"]
        # 派出列表中的字段
        exclude = ["is_active"]
        labels = {
            "username": "用戶名",
            "name": "真實姓名",
            "password": "密碼",
            "department": "部門",
        }
        widgets = {
            "username": forms.widgets.TextInput(attrs={"class": "form-control"}),
            "password": forms.widgets.PasswordInput(attrs={"class": "form-control"}),
        }

    # 對每個字段添加屬性, self.fields是一個有序的字典, self.fields.values()獲取出每個字段的values值,即每個對象,
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs.update({"class": "form-control"})

    # 校驗兩次密碼是否一致
    def clean(self):
        if self.cleaned_data.get("password") != self.cleaned_data.get("re_password"):
            self.add_error("re_password", "兩次密碼不一致")
            raise ValidationError("兩次密碼不一致")
        return self.cleaned_data

    def clean_password(self):
        password = self.cleaned_data.get("password")
        status = checkio(password)
        if status:
            return password
        else:
            self.add_error("password", "密碼太簡單了")
            raise ValidationError("密碼不合格")
主要是ModelForm的使用

 

分頁功能的實現代碼:

# ! /usr/bin/env python3.6
# -*- coding: utf-8 -*-
# 2018/9/27 21:36

from django.utils.html import mark_safe


class Pagination(object):
    def __init__(self, request, all_count,base_url, per_num=10, max_show=11, ):
        try:
            current_page = int(request.GET.get("page"))
            if current_page <= 0:  # 判斷頁碼是否為負數
                raise Exception()
        except Exception as e:
            current_page = 1
        self.base_url = base_url
        self.current_page = current_page
        self.max_show = max_show
        self.half_show = max_show // 2
        self.all_count = all_count
        self.per_num = per_num  # 每頁顯示的數量
        self.total_page, more = divmod(self.all_count, self.per_num)  # 計算顯示的總頁數
        if more:
            self.total_page += 1

    def start(self):
        return (self.current_page - 1) * self.per_num

    def end(self):
        return self.current_page * self.per_num

    def html_str(self):

        # 總頁碼數小於最大顯示
        if self.total_page < self.max_show:
            page_start = 1
            page_end = self.total_page
        # 總頁碼大於顯示頁碼
        else:
            if self.current_page < self.half_show:  # 當前頁面小於顯示的一半,防止有負數
                page_start = 1
                page_end = self.max_show

            elif self.current_page + self.half_show > self.total_page:  # 限制當前+一半大於總頁數
                page_start = self.total_page - self.max_show + 1
                page_end = self.total_page
            else:
                page_start = self.current_page - self.half_show
                page_end = self.current_page + self.half_show

        html_list = []

        if self.current_page <= 1:
            prev_li = '<li class="disabled"><a>上一頁</a></li>'
        else:
            prev_li = '<li><a href="{1}?page={0}">上一頁</a></li>'.format(self.current_page - 1, self.base_url)
        html_list.append(prev_li)

        for i in range(page_start, page_end + 1):
            if i == self.current_page:
                li_html = '<li class="active"><a href="{1}?page={0}">{0}</a></li>'.format(i, self.base_url)
            else:
                li_html = '<li><a href="{1}?page={0}">{0}</a></li>'.format(i, self.base_url)
            html_list.append(li_html)
        if self.current_page >= self.total_page:
            last_li = '<li class="disabled"><a>下一頁</a></li>'
        else:
            last_li = '<li><a href="{1}?page={0}">下一頁</a></li>'.format(self.current_page + 1, self.base_url)
        html_list.append(last_li)

        html_str = mark_safe("".join(html_list))

        return html_str
將分頁功能封裝為類

使用封裝好的類實現分頁功能

視圖函數的使用:

def user_list(request):
    # 實例化一個對象
    p = Pagination(request, len(users), request.path_info)
    return render(request, "user_list.html", {"user": users[p.start(): p.end()], "html_str": p.html_str()})
views.py視圖函數的使用

前端模板的使用:

{% extends "layout.html" %}
{% block content %}
    <h2 class="sub-header" style="display: inline-block">公戶信息</h2>

    <a href="/add/">
        <button type="button" class="btn btn-success" style="float: right; margin-top: 30px; margin-right: 50px;">添加
        </button>
    </a>
    <div class="table-responsive">
        <table class="table table-striped">
            <thead>
            <tr>
                <th style="text-align: center">用戶名</th>
                <th style="text-align: center">密碼</th>
            </tr>
            </thead>
            <tbody>
            {% for ret in user %}
                <tr>
                    <td>{{ ret.name }}</td>
                    <td>{{ ret.password }}</td>
                </tr>
            {% endfor %}
            </tbody>
        </table>
    <div class="text-center">
        <nav aria-label="Page navigation">
            <ul class="pagination">

                {{ html_str }}

                {#                {% for page in total_page %}#}
                {#                    <li><a href="/user_list/?page={{ page }}">{{ page }}</a></li>#}
                {#                {% endfor %}#}


            </ul>
        </nav>
    </div>

    </div>
{% endblock %}
前端模板的使用

 

第四部分:很多!!!

完成的主要內容:

1, 完成私戶和公戶的區分,以及互相轉換的功能

  基於以上代碼的修改:models.py中的Customer中的consultant字段的related_name="customers", null=True, blank="True"; private注釋掉,因為可以通過銷售來判斷是否為公戶.

url設計:

前端頁面的展示:

{% extends "layout.html" %}
{% block content %}
    {% if request.path_info == "/index/" %}
        <h2 class="sub-header" style="display: inline-block">公戶信息</h2>
    {% else %}
        <h2 class="sub-header" style="display: inline-block">擁有的客戶信息</h2>
    {% endif %}


    <form action="" class="form-inline" method="post">
        {% csrf_token %}
        <div class="table-responsive">
            <div class="container-fluid">
                <div class="row">
                    <div class="col-md-2">
                        <select name="actions" id="" class="form-control">
                            <option value="">請選擇</option>
                            <option value="">刪除</option>
                            {% if request.path_info == "/index/" %}
                                <option value="mutil_apply">轉為私戶</option>
                            {% else %}
                                <option value="mutil_pub">轉為公戶</option>
                            {% endif %}


                        </select>
                        <button type="submit" class="btn btn-info">執行
                        </button>
                    </div>

                    <div class="col-md-1 col-md-offset-9">
                        <a href="/add/">
                            <button type="button" class="btn btn-success">添加
                            </button>
                        </a>
                    </div>
                </div>
            </div>
            <table class="table table-striped">
                <thead>
                <tr>
                    <th style="text-align: center">選擇</th>
                    <th style="text-align: center">序號</th>
                    {#                <th style="text-align: center">ID</th>#}
                    <th style="text-align: center">QQ</th>
                    <th style="text-align: center">QQ昵稱</th>
                    <th style="text-align: center">姓名</th>
                    <th style="text-align: center">客戶來源</th>
                    <th style="text-align: center">班級類型</th>
                    <th style="text-align: center">銷售</th>
                    <th style="text-align: center">狀態</th>
                    <th style="text-align: center">日期</th>
                    <th style="text-align: center">咨詢日期</th>
                    <th style="text-align: center">已報班級</th>
                    <th style="text-align: center">
                        操作
                    </th>
                </tr>
                </thead>
                <tbody>
                {% for customer in customers %}
                    <tr>
                        <td style="text-align: center"><input type="checkbox" value="{{ customer.id }}" name="id"></td>
                        <td style="text-align: center">{{ forloop.counter }}</td>
                        {#                    <td>{{ customer.id }}</td>#}
                        <td style="text-align: center">{{ customer.qq }}</td>
                        <td style="text-align: center">{{ customer.qq_name|default:"暫無" }}</td>
                        <td style="text-align: center">{{ customer.name|default:"暫無" }}</td>
                        <td style="text-align: center">{{ customer.get_source_display }}</td>
                        <td style="text-align: center">{{ customer.get_class_type_display }}</td>
                        <td style="text-align: center">{{ customer.consultant }}</td>
                        <td style="text-align: center">
                            {{ customer.show_status }}
                        </td>
                        <td style="text-align: center">{{ customer.date }}</td>
                        <td style="text-align: center">{{ customer.last_consult_date }}</td>
                        <td style="text-align: center">{{ customer.show_class }}</td>
                        <td style="text-align: center">
                            <a href="/edit/{{ customer.id }}/">
                                <button type="button" class="btn btn-info">編輯</button>
                            </a>

                            <a href="/remove/{{ customer.id }}">
                                <button type="button" class="btn btn-danger">刪除</button>
                            </a>
                        </td>
                    </tr>


                {% endfor %}

                </tbody>
            </table>
        </div>
    </form>
    <div class="container-fluid">
        <div class="row">
            <div class="row">
                <div class="col-md-6 col-md-offset-6">
                    <div class="pull-right">
                        <form action="" class="form-inline">
                            <input type="text" placeholder="請輸入內容" class="form-control" name="query">
                            <button type="submit" class="btn btn-info">搜索<i class="fa fa-search"></i>
                            </button>
                        </form>
                    </div>
                </div>
                <div class="col-md-12">
                    <div class="text-center">
                        <nav aria-label="Page navigation">
                            <ul class="pagination">
                                {{ html_str }}
                            </ul>
                        </nav>
                    </div>
                </div>
            </div>
        </div>
    </div>


{% endblock %}
主頁面的HTML 

 

后端使用類去寫:

class UserInex(View):

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        ret = super(UserInex, self).dispatch(request, *args, **kwargs)
        return ret

    def get(self, request):

        # 獲取所有字段
        field_obj = forms.Customer()
        field_list = [i for i in field_obj.fields]
        print(field_list)
        q = self.get_search(field_list)

        if request.path_info == "/user_index/":
            user_obj = models.Customer.objects.filter(q, consultant=request.user)
        else:
            user_obj = models.Customer.objects.filter(q, consultant__isnull=True)

        query_params = deepcopy(request.GET)
        query_params._mutable = True
        # query_params["page"] = 2
        # # 需要修改配置
        # print(query_params.urlencode())

        # 實例化一個分頁
        pagination = Pagination(request, len(user_obj), request.path_info, query_params, per_num=3, max_show=5)
        html_str = pagination.html_str

        return render(request, "user_index.html",
                      {"customers": user_obj[pagination.start: pagination.end], "html_str": html_str})

    def post(self, request):
        action = request.POST.get("actions")
        if not hasattr(self, action):
            return HttpResponse("非法操作")
        getattr(self, action)()
        return self.get(request)

    def mutil_pub(self):
        obj_ids = self.request.POST.getlist("id")
        self.request.user.customers.remove(*models.Customer.objects.filter(id__in=obj_ids))

    def mutil_apply(self):
        obj_ids = self.request.POST.getlist("id")
        self.request.user.customers.add(*models.Customer.objects.filter(id__in=obj_ids))

    def get_search(self, search_list):
        query = self.request.GET.get("query", "")
        q = Q()
        q.connector = "OR"
        for field in search_list:
            q.children.append(Q(("{}__contains".format(field), query)))
        return q
后端代碼

 

 

2, 完成批量操作

 

3, 完成搜索功能

4, 加入分頁功能 

修改之后的分頁類;

# ! /usr/bin/env python3.6
# -*- coding: utf-8 -*-
# 2018/9/27 21:36

from django.utils.html import mark_safe


class Pagination(object):
    def __init__(self, request, all_count, base_url,query_params, per_num=10, max_show=11, ):
        try:
            current_page = int(request.GET.get("page"))
            if current_page <= 0:  # 判斷頁碼是否為負數
                raise Exception()
        except Exception as e:
            current_page = 1
        self.base_url = base_url
        self.current_page = current_page
        self.max_show = max_show
        self.half_show = max_show // 2
        self.all_count = all_count
        self.per_num = per_num  # 每頁顯示的數量
        self.total_page, more = divmod(self.all_count, self.per_num)  # 計算顯示的總頁數
        self.query_params = query_params
        if more:
            self.total_page += 1

    @property
    def start(self):
        return (self.current_page - 1) * self.per_num

    @property
    def end(self):
        return self.current_page * self.per_num

    @property
    def html_str(self):

        # 總頁碼數小於最大顯示
        if self.total_page < self.max_show:
            page_start = 1
            page_end = self.total_page
        # 總頁碼大於顯示頁碼
        else:
            if self.current_page < self.half_show:  # 當前頁面小於顯示的一半,防止有負數
                page_start = 1
                page_end = self.max_show

            elif self.current_page + self.half_show > self.total_page:  # 限制當前+一半大於總頁數
                page_start = self.total_page - self.max_show + 1
                page_end = self.total_page
            else:
                page_start = self.current_page - self.half_show
                page_end = self.current_page + self.half_show

        html_list = []

        if self.current_page <= 1:
            prev_li = '<li class="disabled"><a>上一頁</a></li>'
        else:
            self.query_params["page"] = self.current_page - 1
            prev_li = '<li><a href="{1}?{0}">上一頁</a></li>'.format(self.query_params.urlencode(), self.base_url)
        html_list.append(prev_li)

        for i in range(page_start, page_end + 1):
            self.query_params["page"] = i
            if i == self.current_page:
                li_html = '<li class="active"><a href="{1}?{0}">{2}</a></li>'.format(self.query_params.urlencode(), self.base_url, i)
            else:
                li_html = '<li><a href="{1}?{2}">{0}</a></li>'.format(i, self.base_url, self.query_params.urlencode())
            html_list.append(li_html)
        if self.current_page >= self.total_page:
            last_li = '<li class="disabled"><a>下一頁</a></li>'
        else:
            self.query_params["page"] = self.current_page + 1
            last_li = '<li><a href="{1}?{0}">下一頁</a></li>'.format(self.query_params.urlencode(), self.base_url)
        html_list.append(last_li)

        html_str = mark_safe("".join(html_list))

        return html_str
分頁功能代碼示例

使用:

 

5, 添加編輯

from . import forms
from . import models
from django.views import View
from django.db.models import Q
from django.contrib import auth
from crm_manage.forms import RegForm
from django.utils.html import mark_safe
from utils.pagination import Pagination
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect, reverse, HttpResponse
from utils.pagination import Pagination
from django.http import QueryDict
from copy import deepcopy


class UserInex(View):

    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        ret = super(UserInex, self).dispatch(request, *args, **kwargs)
        return ret

    def get(self, request):

        # 獲取所有字段
        field_obj = forms.Customer()
        field_list = [i for i in field_obj.fields]
        print(field_list)
        q = self.get_search(field_list)

        if request.path_info == "/user_index/":
            user_obj = models.Customer.objects.filter(q, consultant=request.user)
        else:
            user_obj = models.Customer.objects.filter(q, consultant__isnull=True)

        query_params = deepcopy(request.GET)
        query_params._mutable = True
        # query_params["page"] = 2
        # # 需要修改配置
        # print(query_params.urlencode())

        # 實例化一個分頁
        pagination = Pagination(request, len(user_obj), request.path_info, query_params, per_num=3, max_show=5)
        html_str = pagination.html_str

        return render(request, "user_index.html",
                      {"customers": user_obj[pagination.start: pagination.end], "html_str": html_str})

    def post(self, request):
        action = request.POST.get("actions")
        if not hasattr(self, action):
            return HttpResponse("非法操作")
        getattr(self, action)()
        return self.get(request)

    def mutil_pub(self):
        obj_ids = self.request.POST.getlist("id")
        self.request.user.customers.remove(*models.Customer.objects.filter(id__in=obj_ids))

    def mutil_apply(self):
        obj_ids = self.request.POST.getlist("id")
        self.request.user.customers.add(*models.Customer.objects.filter(id__in=obj_ids))

    def get_search(self, search_list):
        query = self.request.GET.get("query", "")
        q = Q()
        q.connector = "OR"
        for field in search_list:
            q.children.append(Q(("{}__contains".format(field), query)))
        return q


def login(request):
    msg = ''
    loginForm = forms.LoginForm()
    if request.method == "POST":
        loginForm = forms.LoginForm(request.POST)
        username = request.POST.get('username')
        pwd = request.POST.get("pwd")
        obj = auth.authenticate(request, username=username, password=pwd)
        if obj:
            auth.login(request, obj)
            return redirect("/index/")
        else:
            msg = "用戶名密碼錯誤"
    return render(request, "login.html", {"loginForm": loginForm, "msg": msg})


def regForm(request):
    reg_obj = RegForm()
    if request.method == "POST":
        reg_obj = RegForm(request.POST)
        if reg_obj.is_valid():
            # 數據庫中寫入數據
            # 第一種方法
            # reg_obj.cleaned_data.pop("groups")
            # reg_obj.cleaned_data.pop("user_permissions")
            # reg_obj.cleaned_data.pop("re_password")
            # models.UserProfile.objects.create_user(**reg_obj.cleaned_data)

            # 第二種方法(此方法寫入數據庫中的密碼是明文,所以多了一步設置密碼的操作)
            password = reg_obj.cleaned_data.get("password")
            user = reg_obj.save()
            user.set_password(password)
            user.save()

            return redirect("/login/")
    return render(request, "reg.html", {"reg_obj": reg_obj})


# @login_required
# def index(request):
#     customers = models.Customer.objects.filter(consultant__isnull=True)
#     return render(request, "index.html", {"customers": customers})


def logout(request):
    auth.logout(request)
    return redirect("/login/")


# @login_required
# def control(request):
#     customers = models.Customer.objects.all()
#     return render(request, "control.html", {"customers": customers})


# 增加和添加
def add_edit(request, edit_id=None):
    edit_obj = models.Customer.objects.filter(id=edit_id).first()
    form_obj = forms.AddForm(instance=edit_obj)
    if request.method == "POST":
        form_obj = forms.AddForm(request.POST, instance=edit_obj)
        if form_obj.is_valid():
            form_obj.save()
            return redirect("/index/")
    return render(request, "add.html", {"form_obj": form_obj})


def remove(request):
    return render(request, "index.html")


# 分頁功能

users = [{"name": "chenrun{}".format(i), "password": "chenrunasb{}".format(i)} for i in range(1, 302)]

# def user_list(request):
#     """
#     :param current_page:  當前頁碼
#     :param all_count: 總數據條數
#     :param per_num: 每頁顯示數據條數
#     :param max_show: 最多顯示頁碼數
#     :param total_page: 總頁碼數
#     :param start: 數據切片起始索引
#     :param end: 數據切片終止索引
#     :return:
#     """
#     max_show = 11
#     half_show = max_show//2
#
#     all_count = len(users)  # 所有的數據數
#
#     per_num = 10  # 每頁顯示的數量
#
#     total_page, more = divmod(all_count, per_num)  # 計算顯示的總頁數
#
#     # 獲取用戶點擊的那一頁
#     current_page = 1
#     try:
#         current_page = int(request.GET.get("page"))
#         if current_page <= 0:  # 判斷頁碼是否為負數
#             raise Exception()
#     except Exception as e:
#         current_page = 1
#
#     # 分割數據並顯示
#     """
#     1 1     10  0   10
#     2 11    20  10  20
#     """
#     start = (current_page - 1) * 10
#     end = current_page * 10
#
#     # 判斷more時候有值,如果有余數,需要在總頁數上加1
#     if more:
#         total_page += 1
#
#     # 總頁碼數小於最大顯示
#     if total_page < max_show:
#         page_start = 1
#         page_end = total_page
#     # 總頁碼大於顯示頁碼
#     else:
#         if current_page < half_show:  # 當前頁面小於顯示的一半,防止有負數
#             page_start = 1
#             page_end = max_show
#
#         elif current_page + half_show > total_page:  # 限制當前+一半大於總頁數
#             page_start = total_page - max_show + 1
#             page_end = total_page
#         else:
#             page_start = current_page - half_show
#             page_end = current_page + half_show
#
#
#     html_list = []
#
#     if current_page <= 1:
#         prev_li = '<li class="disabled"><a>上一頁</a></li>'
#     else:
#         prev_li = '<li><a href="/user_list/?page={0}">上一頁</a></li>'.format(current_page - 1)
#     html_list.append(prev_li)
#
#     for i in range(page_start, page_end+1):
#         if i == current_page:
#             li_html = '<li class="active"><a href="/user_list/?page={0}">{0}</a></li>'.format(i)
#         else:
#             li_html = '<li><a href="/user_list/?page={0}">{0}</a></li>'.format(i)
#         html_list.append(li_html)
#     if current_page >= total_page:
#         last_li = '<li class="disabled"><a>下一頁</a></li>'
#     else:
#         last_li = '<li><a href="/user_list/?page={0}">下一頁</a></li>'.format(current_page+1)
#     html_list.append(last_li)
#
#     html_str = mark_safe("".join(html_list))
#
#     return render(request, "user_list.html", {
#         "user": users[start:end],
#         "html_str": html_str,
#     })

# return render(request, "user_list.html",
#               {
#                   "user": users[start: end],
#                   "total_page": range(page_start, page_end+1),  # 因為range顧頭不顧尾,所以要加一
#
#               }
#               )

# def user_list(request):
#     # 實例化一個對象
#     p = Pagination(request, len(users), request.path_info)
#     return render(request, "user_list.html", {"user": users[p.start(): p.end()], "html_str": p.html_str()})
目前所有的后端代碼(包含添加)

 

第五部分: 遇到一些問題;並加以解決

問題一: 在個人用戶添加或編輯完成之后跳轉的公戶信息.

解決思路: 在訪問添加或編輯的時候,將url的信息添加到next=...后邊,提交過去,添加或者修改之后,拿到提交的next的url地址返回即.

第一步: 記錄刪一條的搜索地址和查詢條件; 將地址拼接到添加的buttun的按鈕上.

修改之前的添加按鈕:

<a href="/add/">
    <button type="button" class="btn btn-success">添加</button>
</a>

在后端的cbv中定義方法:獲取url的路徑以及查詢條件

    def get_add_btn(self, request):
        """
        生成按鈕的標簽
        :param request:
        :return:
        """
        url = request.path_info
        param = request.GET.copy()  # 得到的是一個querydict對象
        qd = QueryDict()
        qd._mutable = True

        qd["next"] = url
        qd["_query"] = param.urlencode()  # 通過urlencode得到字符串
        query = qd.urlencode()
        add_btn = '<a href="{}?{}"><button type="button" class="btn btn-success">添加</button></a>'.format(reverse('add'), query)
        return mark_safe(add_btn)
生成add_btn按鈕

然后再添加的函數中添加:

 同樣的編輯也是需要添加篩選條件的

# 接受query並傳遞到前端頁面
add_btn, query = self.get_add_btn(request)

前端接收:

 

問題二:

需要添加客戶的跟進記錄和展示客戶的跟進記錄

先定義跟進記錄表:

class BaseForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(BaseForm, self).__init__(*args, **kwargs)
        for filed in self.fields.values():
            filed.widget.attrs.update({"class": "form-control"})

# 定義跟進記錄
class ConsultRecord(BaseForm):
    class Meta:
        model = models.ConsultRecord
        fields = "__all__"

        widgets = {

        }
再forms中定義跟進記錄表

 

 

第六部分:繼續完善

問題一:

當兩個銷售同時將同一個公戶轉為私戶時,理應時先到先得,而現在是后來的可以轉換成功。

解決:使用數據庫中的鎖。

# 1, 開始使用鎖
$ begin;
# 2, 使用鎖
$ select * from table where id = 11 for update;
# 此時另外一個終端去開啟mysql使用同一張表修改這個字段的時候就會夯住。只有釋放掉鎖另一邊才可以修改成功
# 3,結束事物
$ commit;

views.py中如何加鎖

from django.db import transaction

此時應該判斷這兩個用戶是否是私戶, 進而判斷是否銷售能否進行修改

    def mutil_apply(self):
        flag = False
        obj_ids = self.request.POST.getlist("id")
        # self.request.user.customers.add(*models.Customer.objects.filter(id__in=obj_ids))
        with transaction.atomic():
            old = models.Customer.objects.filter(id__in=obj_ids, consultant__isnull=True).select_for_update()
            if len(obj_ids) == len(old):
                models.Customer.objects.filter(id__in=obj_ids).update(consultant=self.request.user)
                flag = True
        if not flag:
            return HttpResponse("下手滿了,已經被別人搶走了")

問題2: 銷售不能無限制將公戶添加到自己的私戶中

解決:第一步:在settings.py中配置最大的私戶限制

MAX_CUSTOMER_NUM = 3

第二步:

倒入settins文件

from django.conf import settings

在views.py中的轉私戶的函數中添加判斷限制最大人數

obj_ids = self.request.POST.getlist("id")
count
= models.Customer.objects.filter(consultant=self.request.user).count() if count + len(obj_ids) > settings.MAX_CUSTOMER_NUM:   return HttpResponse("你的私戶人數太多了")

 


免責聲明!

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



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