• 使用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("你的私户人数太多了")
  • 相关阅读:
    网页轮播图案例
    表单
    表格标签的使用
    HTML5标签2
    HTML标签
    外边距
    h5css产品模块设计
    mouseenter 和mouseover的区别
    动画函数封装
    jQuery 插件
  • 原文地址:https://www.cnblogs.com/chenrun/p/9709471.html
Copyright © 2020-2023  润新知