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("你的私戶人數太多了")
