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(自己创建的)
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()
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
第四步:视图函数中使用自定义的form
# views.py
from django.shortcuts import render, redirect from . import forms from django.contrib import auth from django.contrib.auth.decorators import login_required from crm_manage.forms import RegForm from . import models def login(request): msg = '' loginForm = forms.LoginForm() if request.method == "POST": loginForm = forms.LoginForm(request.POST) username = request.POST.get('username') pwd = request.POST.get("pwd") obj = auth.authenticate(request, username=username, password=pwd) if obj: auth.login(request, obj) return redirect("/index/") else: msg = "用户名密码错误" return render(request, "login.html", {"loginForm": loginForm, "msg": msg}) def regForm(request): reg_obj = RegForm() if request.method == "POST": reg_obj = RegForm(request.POST) if reg_obj.is_valid(): # 数据库中写入数据 # 第一种方法 # reg_obj.cleaned_data.pop("groups") # reg_obj.cleaned_data.pop("user_permissions") # reg_obj.cleaned_data.pop("re_password") # models.UserProfile.objects.create_user(**reg_obj.cleaned_data) # 第二种方法(此方法写入数据库中的密码是明文,所以多了一步设置密码的操作) password = reg_obj.cleaned_data.get("password") user = reg_obj.save() user.set_password(password) user.save() return redirect("/login/") return render(request, "reg.html", {"reg_obj": reg_obj}) @login_required def index(request): return render(request, "index.html") def logout(request): auth.logout(request) return redirect("/login/") @login_required def control(request): customers = models.Customer.objects.all() return render(request, "control.html", {"customers": customers})
第五步:
前端中展示
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <title>Bootstrap 101 Template</title> <link rel="stylesheet" href="/static/css/bootstrap.min.css"> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-4 col-md-offset-4"> <form class="form-signin" method="post" novalidate> {% csrf_token %} <h2 class="form-signin-heading">欢迎登陆</h2> <label for="inputEmail" class="sr-only">Email address</label> {{ loginForm.username }} <br> <label for="inputPassword" class="sr-only">{{ loginForm.pwd.label }}</label> {{ loginForm.pwd }} <div class="checkbox"> <label> <input type="checkbox" value="remember-me"> Remember me </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">登陆</button> <br> <a href="/reg/"> <button class="btn btn-lg btn-primary btn-block" type="button">注册</button> </a> </form> </div> </div> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/css/bootstrap.min.css"> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <form action="" class="form-horizontal" novalidate method="post"> {% csrf_token %} <h2 class="form-signin-heading">注册</h2> <div class="form-group {% if reg_obj.username.errors.0 %}has-error{% endif %}"> <label for="{{ reg_obj.username.id_for_label }}" class="col-sm-2 control-label"> {{ reg_obj.username.label }} </label> <div class="col-sm-10"> {{ reg_obj.username }} </div> <span id="helpBlock2" class="help-block">{{ reg_obj.username.errors.0 }}</span> </div> <div class="form-group {% if reg_obj.name.errors.0 %}has-error{% endif %}"> <label for="{{ reg_obj.name.id_for_label }}" class="col-sm-2 control-label"> {{ reg_obj.name.label }} </label> <div class="col-sm-10"> {{ reg_obj.name }} </div> <span id="helpBlock2" class="help-block">{{ reg_obj.name.errors.0 }}</span> </div> <div class="form-group {% if reg_obj.password.errors %}has-error{% endif %}"> <label for="{{ reg_obj.password.id_for_label }}" class="col-sm-2 control-label"> {{ reg_obj.password.label }} </label> <div class="col-sm-10"> {{ reg_obj.password }} </div> <span id="helpBlock2" class="help-block">{{ reg_obj.password.errors.0 }}</span> </div> <div class="form-group {% if reg_obj.re_password.errors %}has-error{% endif %}"> <label for="{{ reg_obj.password.id_for_label }}" class="col-sm-2 control-label"> {{ reg_obj.re_password.label }} </label> <div class="col-sm-10"> {{ reg_obj.re_password }} </div> <span id="helpBlock2" class="help-block">{{ reg_obj.re_password.errors.0 }}</span> </div> <div class="form-group"> <label for="{{ reg_obj.department.id_for_label }}" class="col-sm-2 control-label"> {{ reg_obj.department.label }} </label> <div class="col-sm-10"> {{ reg_obj.department }} </div> </div> <button class="btn btn-lg btn-primary btn-block" type="submit">提交</button> </form> </div> </div> </div> </body> </html>
<!doctype html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1, user-scalable=no"> <title>实名认证</title> <link href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css" title="" rel="stylesheet"> <link title="" href="/static/css/style.css" rel="stylesheet" type="text/css"> <link title="blue" href="/static/css/dermadefault.css" rel="stylesheet" type="text/css"> <link href="/static/css/templatecss.css" rel="stylesheet" title="" type="text/css"> <script src="/static/jquery/jquery-1.10.2.js"></script> <script src="/static/js/jquery.cookie.js" type="text/javascript"></script> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js" type="text/javascript"></script> </head> <body style=""> <nav class="nav navbar-default navbar-mystyle navbar-fixed-top"> <div class="navbar-header"> <button class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand mystyle-brand"><span class="glyphicon glyphicon-home"></span></a></div> <div class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li class="li-border"><a class="mystyle-color" href="#">管理控制台</a></li> </ul> <ul class="nav navbar-nav pull-right"> <li class="li-border"> <a href="#" class="mystyle-color"> <span class="glyphicon glyphicon-bell"></span> <span class="topbar-num">0</span> </a> </li> <li class="li-border dropdown"><a href="#" class="mystyle-color" data-toggle="dropdown"> <span class="glyphicon glyphicon-search"></span> 搜索</a> <div class="dropdown-menu search-dropdown"> <div class="input-group"> <input type="text" class="form-control"> <span class="input-group-btn"> <button type="button" class="btn btn-default">搜索</button> </span> </div> </div> </li> <li class="dropdown li-border"><a href="#" class="dropdown-toggle mystyle-color" data-toggle="dropdown">帮助与文档<span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">帮助与文档</a></li> <li class="divider"></li> <li><a href="#">论坛</a></li> <li class="divider"></li> <li><a href="#">博客</a></li> </ul> </li> {# <li class="dropdown li-border"><a href="#" class="dropdown-toggle mystyle-color" data-toggle="dropdown">605875855@qq.com<span#} {# class="caret"></span></a>#} {# <ul class="dropdown-menu">#} {# <li><a href="#">退出</a></li>#} {# </ul>#} {# </li>#} <li class="li-border"><a href="/login/" class="mystyle-color">登陆</a></li> </ul> </div> </nav> <div class="down-main"> <div class="left-main left-off"> <div class="sidebar-fold"><span class="glyphicon glyphicon-menu-hamburger"></span></div> <div class="subNavBox"> <div class="sBox"> <div class="subNav"><span class="title-icon glyphicon glyphicon-chevron-up"></span><span class="sublist-title">用户中心</span> </div> <ul class="navContent" style="display: block;"> <li> <div class="showtitle" style="width: 100px; display: none;"><img src="/static/img/leftimg.png">账号管理 </div> <a href=""><span class="sublist-icon glyphicon glyphicon-user"></span><span class="sub-title">账号管理</span></a></li> <li> <div class="showtitle" style="width: 100px; display: none;"><img src="/static/img/leftimg.png">消息中心 </div> <a href=""><span class="sublist-icon glyphicon glyphicon-envelope"></span><span class="sub-title">消息中心</span></a></li> <li> <div class="showtitle" style="width:100px;"><img src="/static/img/leftimg.png">短信</div> <a href=""><span class="sublist-icon glyphicon glyphicon-bullhorn"></span><span class="sub-title">短信</span></a></li> <li class="active"> <div class="showtitle" style="width: 100px; display: none;"><img src="/static/img/leftimg.png">实名认证 </div> <a href=""><span class="sublist-icon glyphicon glyphicon-credit-card"></span><span class="sub-title">实名认证</span></a></li> </ul> </div> </div> </div> <div class="right-product view-product right-off"> <div class="table-responsive"> {% block main_info %} <table class="table table-striped"> <thead> <tr> {# <th>序号</th>#} <th>ID</th> <th>姓名</th> <th>qq</th> <th>性别</th> <th>客户来源</th> <th>咨询课程</th> <th>最后一次咨询时间</th> </tr> </thead> <tbody> {% for customer in customers %} {% if customer.private == 0 %} <tr> {# <td>{{ forloop.counter }}</td>#} <td>{{ customer.id }}</td> <td>{{ customer.name }}</td> <td>{{ customer.qq }}</td> <td>{{ customer.sex }}</td> <td>{{ customer.source }}</td> <td>{{ customer.course }}</td> <td>{{ customer.last_consult_date }}</td> </tr> {% endif %} {% endfor %} </tbody> </table> <a href="/logout/"><button class="btn btn-block">注销</button></a> {% endblock %} </div> </div> </div> <script type="text/javascript"> $(function () { /*左侧导航栏显示隐藏功能*/ $(".subNav").click(function () { /*显示*/ if ($(this).find("span:first-child").attr('class') == "title-icon glyphicon glyphicon-chevron-down") { $(this).find("span:first-child").removeClass("glyphicon-chevron-down"); $(this).find("span:first-child").addClass("glyphicon-chevron-up"); $(this).removeClass("sublist-down"); $(this).addClass("sublist-up"); } /*隐藏*/ else { $(this).find("span:first-child").removeClass("glyphicon-chevron-up"); $(this).find("span:first-child").addClass("glyphicon-chevron-down"); $(this).removeClass("sublist-up"); $(this).addClass("sublist-down"); } // 修改数字控制速度, slideUp(500)控制卷起速度 $(this).next(".navContent").slideToggle(300).siblings(".navContent").slideUp(300); }); /*左侧导航栏缩进功能*/ $(".left-main .sidebar-fold").click(function () { if ($(this).parent().attr('class') == "left-main left-full") { $(this).parent().removeClass("left-full"); $(this).parent().addClass("left-off"); $(this).parent().parent().find(".right-product").removeClass("right-full"); $(this).parent().parent().find(".right-product").addClass("right-off"); } else { $(this).parent().removeClass("left-off"); $(this).parent().addClass("left-full"); $(this).parent().parent().find(".right-product").removeClass("right-off"); $(this).parent().parent().find(".right-product").addClass("right-full"); } }); }) </script> </body> </html>
部分页面效果展示:
登陆页面:
注册页面:
第三部分:完成主页面展示信息功能和分页功能
主页面代码:
主模板:
<!DOCTYPE html> <html lang="en"> <head> {% load static %} <meta charset="UTF-8"> <title>Title</title> <link rel="icon" href="{% static "img/luffy-logo.png" %}"> <link rel="stylesheet" href="{% static "bootstrap-3.3.7-dist/css/bootstrap.min.css" %}"> <link rel="stylesheet" href="{% static "css/layout.css" %}"> <link rel="stylesheet" href="{% static "font-awesome-4.7.0/css/font-awesome.min.css" %}"> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#"><i class="fa fa-tripadvisor fa-fw" aria-hidden="true" style="margin-right: 6px;"></i>CRM管理系统</a> </div> <div id="navbar" class="navbar-collapse collapse"> <div class="nav navbar-nav navbar-right"> <img src="{% static "img/default.png" %}" alt="" class="dropdown-toggle img-circle" width="46px" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <img src="" alt=""> <ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> <li><a href="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </div> <ul class="nav navbar-nav navbar-right"> <li> <a href="#">任务<i class="fa fa-bell-o fa-fw" aria-hidden="true"></i> <span class="badge">4</span> </a> </li> <li> <a href="#">通知<i class="fa fa-envelope-o fa-fw" aria-hidden="true"></i> <span class="badge">2</span> </a> </li> <li> <a href="#">消息<i class="fa fa-comment-o fa-fw" aria-hidden="true"></i> <span class="badge">3</span> </a> </li> <li> <a href="#">更多<i class="fa fa-ellipsis-v fa-fw" aria-hidden="true"></i></a> </li> </ul> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="active"><a href="#">信息广场 <span class="sr-only">(current)</span></a></li> <li><a href="#">个人中心</a></li> <li><a href="#">帮助</a></li> <li><a href="#">更多</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> {% block content %} <h1 class="page-header">Dashboard</h1> <h2 class="sub-header">Section title</h2> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>#</th> <th>Header</th> <th>Header</th> <th>Header</th> <th>Header</th> </tr> </thead> <tbody> <tr> <td>1,001</td> <td>Lorem</td> <td>ipsum</td> <td>dolor</td> <td>sit</td> </tr> <tr> <td>1,002</td> <td>amet</td> <td>consectetur</td> <td>adipiscing</td> <td>elit</td> </tr> <tr> <td>1,003</td> <td>Integer</td> <td>nec</td> <td>odio</td> <td>Praesent</td> </tr> </tbody> </table> </div> {% endblock %} </div> </div> </div> <script src="{% static "jquery/jquery-1.10.2.js" %}"></script> <script src="{% static "bootstrap-3.3.7-dist/js/bootstrap.js" %}"></script> </body> </html>
子模板:继承主模板
{% extends "layout.html" %} {% block content %} <h2 class="sub-header" style="display: inline-block">公户信息</h2> <a href="/add/"> <button type="button" class="btn btn-success" style="float: right; margin-top: 30px; margin-right: 50px;">添加 </button> </a> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th style="text-align: center">序号</th> <th style="text-align: center">ID</th> <th style="text-align: center">QQ</th> <th style="text-align: center">QQ昵称</th> <th style="text-align: center">姓名</th> <th style="text-align: center">客户来源</th> <th style="text-align: center">班级类型</th> <th style="text-align: center">销售</th> <th style="text-align: center">状态</th> <th style="text-align: center">日期</th> <th style="text-align: center">咨询日期</th> <th style="text-align: center">已报班级</th> <th style="text-align: center"> 操作 </th> </tr> </thead> <tbody> {% for customer in customers %} {% if customer.private == 0 %} <tr> <td>{{ forloop.counter }}</td> <td>{{ customer.id }}</td> <td>{{ customer.qq }}</td> <td>{{ customer.qq_name|default:"暂无" }}</td> <td>{{ customer.name|default:"暂无" }}</td> <td>{{ customer.get_source_display }}</td> <td>{{ customer.get_class_type_display }}</td> <td>{{ customer.consultant }}</td> <td> {{ customer.show_status }} </td> <td>{{ customer.date }}</td> <td>{{ customer.last_consult_date }}</td> <td>{{ customer.show_class }}</td> <td> <a href="/edit/"> <button type="button" class="btn btn-info">编辑</button> </a> <a href="/remove/"> <button type="button" class="btn btn-danger">删除</button> </a> </td> </tr> {% endif %} {% endfor %} </tbody> </table> </div> {% endblock %}
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("密码不合格")
分页功能的实现代码:
# ! /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()})
前端模板的使用:
{% 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 %}
后端使用类去写:
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)
然后再添加的函数中添加:
同样的编辑也是需要添加筛选条件的
# 接受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 = { }
第六部分:继续完善
问题一:
当两个销售同时将同一个公户转为私户时,理应时先到先得,而现在是后来的可以转换成功。
解决:使用数据库中的锁。
# 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("你的私户人数太多了")