使用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=" 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=" 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="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=" 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("你的私户人数太多了")
原文地址:https://www.cnblogs.com/chenrun/p/9709471.html