文章目录

  • Django项目:LOL学院学员管理系统
    • 表结构设计
    • 登录注册页面
    • 登录处理视图逻辑和URL
    • 批量插入LOL英雄信息
    • 英雄信息主页
    • 英雄信息分页
    • 封装分页功能
    • 添加和编辑英雄信息
    • 预留钩子动态生成页面标题
    • 控制不同状态显示不同颜色
    • 动态生成验证码
    • 按条件搜索和保存搜索条件
    • 公私分户
    • 公私互转
    • 自定义中间件利用session登陆认证和登出逻辑
    • 跳转回源路径和url编码
    • 跟进记录以及其分页和搜索
    • 编辑和添加跟进记录
    • 编辑和添加报名记录
    • 编辑和添加课程记录
    • 公转私bug修改
    • modelformset批量生成和修改
    • 用户权限分配
    • 根据用户权限动态生成菜单
    • 动态二级菜单
    • 菜单排序
    • 点击二级菜单的子菜单后二级菜单仍然处于选中状态
    • 二级菜单路径导航和面包屑
    • 权限精确到按钮级别
    • 完整项目地址

Django项目:LOL学院学员管理系统

表结构设计

这里只涉及客户信息表及其相关的表,完整项目见文末链接。

# models.py
from django.db import models
# Create your models here.class UserInfo(models.Model):"""用户信息表"""username = models.CharField(max_length=16, verbose_name='姓名')password = models.CharField(max_length=32, verbose_name='密码')email = models.EmailField()telephone = models.CharField(max_length=16)is_active = models.BooleanField(default=True)class Meta:verbose_name_plural = '用户信息表'def __str__(self):  # __unicode__return self.usernameclass CustomerInfo(models.Model):"""客户信息"""name = models.CharField(max_length=32, default=None)contact_type_choices = (('0', 'qq'),('1', '微信'),('2', '手机'),)contact_type = models.CharField(choices=contact_type_choices, default='0', max_length=16)contact = models.CharField(max_length=64, unique=True)source_choices = (('0', 'QQ群'),('1', '51CTO'),('2', '百度推广'),('3', '知乎'),('4', '转介绍'),('5', '其他'),)source = models.CharField(choices=source_choices, max_length=16)referral_from = models.ForeignKey('self', null=True, blank=True, verbose_name='转介绍', on_delete=models.CASCADE)consult_courses = models.ManyToManyField('Course', verbose_name='咨询课程')consult_content = models.TextField(verbose_name='咨询内容')status_choices = (('0', '未报名'),('1', '已报名'),('2', '已退学'),)status = models.CharField(choices=status_choices, max_length=16)consultant = models.ForeignKey('UserInfo', verbose_name='课程顾问', on_delete=models.CASCADE)date = models.DateField(auto_now_add=True)class Meta:verbose_name_plural = '客户信息表'def __str__(self):return self.nameclass Course(models.Model):"""课程表"""name = models.CharField(verbose_name='课程名称', max_length=64, unique=True)price = models.PositiveSmallIntegerField()  # 必须为正period = models.PositiveSmallIntegerField(verbose_name='课程周期(月)', default=5)outline = models.TextField(verbose_name='大纲')class Meta:verbose_name_plural = '课程表'def __str__(self):return self.name

登录注册页面

自己可以去模板之家等网站扒一个自己喜欢的登录注册校验模板

login.html

{% load static %}
<!DOCTYPE html>
<!-- saved from url=(0051)https://www.jq22.com/demo/jquery-Sharelink20151012/ -->
<html lang="zh-CN">
<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 rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<div class="login-container"><h1>SuperCRM 登录页面</h1><div class="connect"><p>欢迎来到LOL学院</p></div><form action="" method="post" id="loginForm"novalidate="novalidate">{% csrf_token %}<div><input type="username" name="username" class="username" placeholder="用户名" oncontextmenu="return false"onpaste="return false"></div><div><input type="password" name="password" class="password" placeholder="密码" oncontextmenu="return false"onpaste="return false"></div><button id="submit" type="submit">登 陆</button><span style="color: red">{{ error }}</span></form>
</div>
<a href="{% url 'register' %}"><button type="button" class="register-tis">还有没有账号?</button>
</a>
</div><script src="{% static 'js/jquery.min.js' %}"></script>
{#<script src="./登陆丨Sharelink_files/jquery.min.js(1).下载"></script>#}
<script src="{% static 'js/common.js' %}"></script><script src="{% static 'js/supersized.3.2.7.min.js' %}"></script>
<script src="{% static 'js/supersized-init.js' %}"></script><script src="{% static 'js/jquery.validate.min.js' %}"></script></body>
</html>

注册页面

{% load static %}
<!DOCTYPE html>
<!-- saved from url=(0064)https://www.jq22.com/demo/jquery-Sharelink20151012/register.html -->
<html lang="zh-CN">
<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 rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<div class="register-container"><h1>ShareLink</h1><div class="connect"><p style="left: 0%;">Link the world. Share to world.</p></div><form action="" method="post" id="registerForm"novalidate="novalidate">{% csrf_token %}{% for field in register_form_obj %}<div>
{#                <label for="{{ field.id_for_label }}">{{ field.label }}</label>#}{{ field }}<span style="color: red;">{{ field.errors.0 }}</span></div>{% endfor %}<button id="submit" type="submit">注 册</button></form><a href="{% url 'login' %}"><button type="button" class="register-tis">已经有账号?</button></a>
</div><script src="{% static 'js/jquery.min.js' %}"></script>
{#<script src="./登陆丨Sharelink_files/jquery.min.js(1).下载"></script>#}
<script src="{% static 'js/common.js' %}"></script><script src="{% static 'js/supersized.3.2.7.min.js' %}"></script>
<script src="{% static 'js/supersized-init.js' %}"></script><script src="{% static 'js/jquery.validate.min.js' %}"></script>
</body>
</html>

登录处理视图逻辑和URL

# views.py
import refrom django.shortcuts import (render, HttpResponse, redirect
)
from django.core.exceptions import ValidationError
from django.views import View
from django import formsfrom app01 import models
from app01.utils.hashlib_func import set_md5
from app01.utils.page_html import MyPagination
# Create your views here.# 自定义验证规则
def mobile_validate(value):mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')if not mobile_re.match(value):raise ValidationError('手机号码格式错误')  # 自定义验证规则的时候,如果不符合你的规则,需要自己发起错误class RegisterForm(forms.Form):username = forms.CharField(max_length=16,min_length=6,label='用户名',widget=forms.widgets.TextInput(attrs={'class': 'username', 'autocomplete': 'off', 'placeholder': '用户名', }),error_messages={'required': '用户名不能为空!','max_length': '用户名不能大于16位!','min_length': '用户名不能小于6位!',})password = forms.CharField(max_length=32,min_length=6,label='密码',widget=forms.widgets.PasswordInput(attrs={'class': 'password', 'placeholder': '密码', 'oncontextmenu': 'return false', 'onpaste': 'return false', }),error_messages={'required': '密码不能为空!','max_length': '密码不能大于16位!','min_length': '密码不能小于6位!',})r_password = forms.CharField(label='确认密码',widget=forms.widgets.PasswordInput(attrs={'class': 'password', 'placeholder': '请再次输入密码', }),error_messages={'required': '密码不能为空!',})# 全局钩子def clean(self):values = self.cleaned_datar_password = values.get('r_password')password = values.get('password')if password == r_password:return valueselse:self.add_error('r_password', '两次输入的密码不一致!')email = forms.EmailField(label='邮箱',error_messages={'invalid': '邮箱格式不对!','required': '邮箱不能为空!',},widget=forms.widgets.EmailInput(attrs={'class': 'email', 'placeholder': '输入邮箱地址', 'type': 'email'}),# validators=[],)telephone = forms.CharField(label='手机号',error_messages={'required': '手机号不能为空!',},widget=forms.widgets.TextInput(attrs={'class': 'phone_number', 'placeholder': '请输入手机号', }),validators=[mobile_validate, ],)class LoginView(View):def get(self, request):return render(request, 'login.html')def post(self, request):username = request.POST.get('username')password = request.POST.get('password')user_obj = models.UserInfo.objects.filter(username=username, password=set_md5(password)).first()if user_obj:# return HttpResponse('ok')return redirect('home')else:# return redirect('login')return render(request, 'login.html', {'error': '用户名或密码错误!'})class RegisterView(View):def get(self, request):register_form_obj = RegisterForm()return render(request, 'register.html', {'register_form_obj': register_form_obj})def post(self, request):register_form_obj = RegisterForm(request.POST)if register_form_obj.is_valid():print(register_form_obj.cleaned_data)register_form_obj.cleaned_data.pop('r_password')password = register_form_obj.cleaned_data.pop('password')password = set_md5(password)register_form_obj.cleaned_data.update({'password': password})models.UserInfo.objects.create(**register_form_obj.cleaned_data)return redirect('login')else:return render(request, 'register.html', {'register_form_obj': register_form_obj})
# urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [url(r'^admin/', admin.site.urls),url(r'^login/', views.LoginView.as_view(), name='login'),url(r'^register/', views.RegisterView.as_view(), name='register'),url(r'^home/', views.HomeView.as_view(), name='home'),url(r'^customer/', views.CustomerView.as_view(), name='customer'),
]

批量插入LOL英雄信息

插入数据

  1. 进入admin后台添加
  2. 在views.py中手动添加
  3. MySQL数据库添加
  4. navicat添加
  5. django项目下直接创建py文件批量添加

方法5:create_table_bats.py

import os
import randomif __name__ == '__main__':os.environ.setdefault("DJANGO_SETTINGS_MODULE", "modelform.settings")import djangodjango.setup()from app01 import modelsnames = []with open('statics/plugins/lol_name', 'r', encoding='utf-8') as f:for line in f:names.append(line.strip())print(len(names))customer_obj = []for i in range(100, 180):obj = models.CustomerInfo(name=names[i-100],contact_type=random.randint(0, 2),contact=f'21321378{i}',source=random.randint(0, 5),referral_from_id=models.CustomerInfo.objects.get(id=1).id,consult_courses_id=models.Course.objects.get(id=1).id,consult_content='什么是'+names[i-100]+'?',status=random.randint(0, 2),consultant_id=models.UserInfo.objects.get(id=1).id)customer_obj.append(obj)models.CustomerInfo.objects.bulk_create(customer_obj)

英雄信息主页

自己去GitHub上下载一个AdminLTE后台管理系统,然后修改加进来。

starter.html

# 修改AdminLTE中starter,html
# 并copy相应用到的js、css文件到项目静态文件夹下,配置静态路径
# 把相关js、css文件引入html文件

home.html

{% extends 'starter.html' %}{% block content %}<table class="table table-hover table-bordered"><thead><tr><td>序号</td><td>姓名</td><td>联系方式</td><td>号码</td><td>来源</td><td>转介绍人员</td><td>状态</td><td>课程顾问</td><td>咨询日期</td><td>咨询内容</td></tr></thead><tbody>{% for customer in customer_obj %}<tr><td>{{ forloop.counter }}</td><td>{{ customer.name }}</td><td>{{ customer.get_contact_type_display }}</td><td>{{ customer.contact }}</td><td>{{ customer.get_source_display }}</td><td>{{ customer.referral_from.name }}</td><td>{{ customer.get_status_display }}</td><td>{{ customer.consultant.username }}</td><td>{{ customer.date }}</td><td>{{ customer.consult_content }}</td></tr>{% endfor %}</tbody></table>{% endblock content %}

英雄信息分页

views.py中

class CustomerView(View):def get(self, request):page_id = request.GET.get('page') # 获取get请求中的page数据num = models.CustomerInfo.objects.all().count()page_num = 15a, b = divmod(num, page_num)page = a if b else apage_count = 9mid = page_count // 2if page_id is None:page_id = 1else:page_id = int(page_id)if page_id < mid:page_num_list = range(1,page_count+1)else:page_num_list = range(page_id - mid, page_id + mid + 1)  customer_obj = models.CustomerInfo.objects.all()[(page_id - 1) * page_num:page_id * page_num]# print(page_id)return render(request, 'customer.html', {'customer_obj': customer_obj, 'page_num_list': page_num_list, })

customer.html

{% extends 'starter.html' %}
{% load static %}
{% block content %}<table class="table table-hover table-bordered"><thead><tr><td>序号</td><td>姓名</td><td>联系方式</td><td>号码</td><td>来源</td><td>转介绍人员</td><td>状态</td><td>课程顾问</td><td>咨询日期</td><td>咨询内容</td></tr></thead><tbody>{% for customer in customer_obj %}<tr>{#        <td>{{ forloop.counter }}</td>#}<td>{{ customer.id }}</td><td>{{ customer.name }}</td><td>{{ customer.get_contact_type_display }}</td><td>{{ customer.contact }}</td><td>{{ customer.get_source_display }}</td><td>{{ customer.referral_from.name }}</td><td>{{ customer.get_status_display }}</td><td>{{ customer.consultant.username }}</td><td>{{ customer.date }}</td><td>{{ customer.consult_content }}</td></tr>{% endfor %}</tbody></table><div class="container"><div class="row"><div class="row-cols-8 offset-2"><nav aria-label="Page navigation"><ul class="pagination"><li><a href="#" aria-label="Previous"><span style="font-size: 25px" aria-hidden="true">&laquo;</span></a></li>{% for i in page_num_list %}<li style="font-size: 25px"><a href="/customer/?page={{ i }}"> {{ i }} &nbsp;</a></li>{% endfor %}<li><a href="#" aria-label="Next"><span style="font-size: 25px" aria-hidden="true">&raquo;</span></a></li></ul></nav></div></div></div>
{% endblock content %}

封装分页功能

app01/utils/page_html

from django.utils.safestring import mark_safeclass MyPagination:def __init__(self, page_id, num, base_url, page_count=9, record=15):a, b = divmod(num, record)page = a + 1 if b else a  # 这些记录可以分多少页mid = page_count // 2if page_id is None:page_id = 1else:page_id = int(page_id)if page_id <= mid:page_num_list = range(1, page_count + 1)elif page_id > page - mid:page_num_list = range(page - page_count + 1, page + 1)else:page_num_list = range(page_id - mid, page_id + mid + 1)self.page_id = page_idself.num = numself.page = pageself.page_num_list = page_num_listself.record = recordself.base_url = base_url@propertydef get_record(self):return self.record@propertydef get_page_id(self):return self.page_iddef html_page(self):page_html = '<div class="container"><div class="row"><div class="row-cols-8 offset-2"><nav aria-label="Page navigation"><ul class="pagination">'if self.page_id <= 1:page_pre = f'<li class="disabled"><a href="javascript:void(0)" aria-label="Previous"><span style="font-size: 25px" aria-hidden="true">&laquo;</span></a></li>'else:page_pre = f'<li><a href="{self.base_url}?page={self.page_id - 1}" aria-label="Previous"><span style="font-size: 25px" aria-hidden="true">&laquo;</span></a></li>'page_html += page_prefor i in self.page_num_list:if i == self.page_id:page_html += f'<li class="active" style="font-size: 25px"><a href="{self.base_url}?page={i}"> {i} </a></li>'else:page_html += f'<li style="font-size: 25px"><a href="{self.base_url}?page={i}"> {i} </a></li>'if self.page_id >= self.page:page_last = f'<li class="disabled"><a href="javascript:void(0)" aria-label="Next"><span style="font-size: 25px" aria-hidden="true">&raquo;</span></a></li>'else:page_last = f'<li><a href="{self.base_url}?page={self.page_id + 1}" aria-label="Next"><span style="font-size: 25px" aria-hidden="true">&raquo;</span></a></li>'page_html += page_lastpage_html += '</ul></nav></div></div></div>'# mark_safe()后端包裹后,前端就不要safe过滤了,自动识别成标签return mark_safe(page_html)

views.py

from modelform import settingsclass CustomerView(View):def get(self, request):page_id = request.GET.get('page') # 获取get请求中的page数据num = models.CustomerInfo.objects.all().count()  # 总共记录数base_url = request.path  # 请求路径# 以后直接在settings配置文件中修改即可page_count = settings.PAGE_COUNT  # 页数栏显示多少个数record = settings.RECORD  # 每页显示多少条记录# print(base_url)html_obj = MyPagination(page_id=page_id, num=num, base_url=base_url, page_count=page_count, record=record)customer_obj = models.CustomerInfo.objects.all()[(html_obj.page_id - 1) * html_obj.record:html_obj.page_id * html_obj.record]# print(page_id)return render(request, 'customer.html', {'customer_obj': customer_obj, 'page_html': html_obj.html_page(), })

settings.py

PAGE_COUNT = 9  # 页数栏显示多少个数
RECORD = 15  # 每页显示多少条记录

customer.html

{% extends 'starter.html' %}
{% load static %}
{% block content %}<table class="table table-hover table-bordered"><thead><tr><td>序号</td><td>姓名</td><td>联系方式</td><td>号码</td><td>来源</td><td>转介绍人员</td><td>状态</td><td>课程顾问</td><td>咨询日期</td><td>咨询内容</td></tr></thead><tbody>{% for customer in customer_obj %}<tr>{#        <td>{{ forloop.counter }}</td>#}<td>{{ customer.id }}</td><td>{{ customer.name }}</td><td>{{ customer.get_contact_type_display }}</td><td>{{ customer.contact }}</td><td>{{ customer.get_source_display }}</td><td>{{ customer.referral_from.name }}</td><td>{{ customer.get_status_display }}</td><td>{{ customer.consultant.username }}</td><td>{{ customer.date }}</td><td>{{ customer.consult_content }}</td></tr>{% endfor %}</tbody></table>
{#    自己封装的分页组件#}
{#    {{ page_html|safe }}#}
{#     # mark_safe()后端包裹后,前端就不要safe过滤了,自动识别成标签#}{{ page_html }}
{% endblock content %}

添加和编辑英雄信息

# 两个表完全可以合一
# 编辑和添加用户
class AddEditCustomer(View):def get(self, request, cid=None):label = '编辑客户' if cid else '添加客户'customer_obj= models.CustomerInfo.objects.filter(pk=cid).first()  # filter返回的时一个QuerrySet集合,取出里边的model对象customer_form = CustomerForm(instance=customer_obj)return render(request, 'add_customer.html', {'customer_form':customer_form, 'label':label})def post(self, request, cid=None):label = '编辑客户' if cid else '添加客户'customer_obj = models.CustomerInfo.objects.filter(pk=cid).first()customer_form = CustomerForm(request.POST, instance=customer_obj)if customer_form.is_valid():customer_form.save()return redirect('customer')else:return render(request, 'add_customer.html', {'customer_form': customer_form, 'label':label})
# urlurl(r'^add_customer/', views.AddEditCustomer.as_view(), name='add_customer'),url(r'^edit_customer/(\d+)/', views.AddEditCustomer.as_view(), name='edit_customer'),

预留钩子动态生成页面标题

starter.html

<div class="col-sm-6"><h1 class="m-0 text-dark">{% block son_content %}LOL学员信息{% endblock %}</h1>
</div><!-- /.col -->

add_customer.html,编辑页面也使用此页面,label在views中动态传输

{% block son_content %}{{ label }}
{% endblock %}
# views
label = '编辑客户' if cid else '添加客户'

控制不同状态显示不同颜色

客户信息表中增加此方法:

def show_status(self):# 设置每个状态的颜色status_color = {'0': 'blue','1': 'green','2': 'red',}return mark_safe(f"<span style='color:{status_color[self.status]}'>{self.get_status_display()}</span>")

在显示页面customer.html文件中调用

<td>{{ customer.show_status }}</td>
# 不同报名状态显示不同颜色

动态生成验证码

使用的是pillow模块动态画图,然后随机画数字,最后随机加噪点。

建议想加入验证码的直接在登陆注册验证时去模板之家扒一个带滑块验证的即可。

按条件搜索和保存搜索条件

对原先的CustomerView进行升级

class CustomerView(View):def get(self, request):# print(request.GET.urlencode())  # 会直接拿到get请求根路径后边的url# request.GET 拿到的是一个QuerrySet不允许修改# 先不进行urlecode,因为后边会多出一个pageget_data = request.GET.copy()  # 直接调用这个类自己的copy方法或者deepcopy方法或者自己import copy 都可以实现内容允许修改print(get_data)page_id = request.GET.get('page')  # 获取get请求中的page数据search_field = request.GET.get('search_field')  # 获取get请求中的搜索字段search = request.GET.get('search')  # 获取get请求中的搜索数据contact_type_choices = {'qq':'0','微信':'1','手机':'2',}if search_field == 'contact_type__contains':search = contact_type_choices[search]# print(search_field, search)if search:#1. Q查询实现多条件查询,或者关系# customer_obj_list = models.CustomerInfo.objects.filter(Q(name__contains=search) | Q(contact__contains=search))# 2.**打散,and关系# customer_obj_list = models.CustomerInfo.objects.filter(**{search_field:search})# 3.Q的另一种方式, q_obj.connector = 'or',  不加or就是and关系q_obj = Q()# q_obj.connector = 'or'q_obj.children.append((search_field, search))# q_obj.children.append((search_field2, search2)) 同时用连个条件查询customer_obj_list = models.CustomerInfo.objects.filter(q_obj)else:customer_obj_list = models.CustomerInfo.objects.all()num = customer_obj_list.count()  # 总共记录数print(num)base_url = request.path  # 请求路径# 以后直接在settings配置文件中修改即可page_count = settings.PAGE_COUNT  # 页数栏显示多少个数record = settings.RECORD  # 每页显示多少条记录# print(base_url)html_obj = MyPagination(page_id=page_id, num=num, base_url=base_url, get_data=get_data, page_count=page_count, record=record)customer_obj = customer_obj_list[(html_obj.page_id - 1) * html_obj.record:html_obj.page_id * html_obj.record]# print(page_id)return render(request, 'customer.html', {'customer_obj': customer_obj, 'page_html': html_obj.html_page(), })

动态生成携带搜索条件的get请求分页链接

# page_html.py
# 动态生成page
for i in self.page_num_list:self.get_data['page'] = i  # 把发来的page动态生成if i == self.page_id:page_html += f'<li class="active" style="font-size: 25px"><a href="{self.base_url}?{self.get_data.urlencode()}"> {i} </a></li>'else:page_html += f'<li style="font-size: 25px"><a href="{self.base_url}?{self.get_data.urlencode()}"> {i} </a></li>'

公私分户

公私户走同一个视图函数处理

url(r'^customer/', views.CustomerView.as_view(), name='customer'),
url(r'^my_customer/', views.CustomerView.as_view(), name='my_customer'),

在loginview中添加当前登录用户的id并存入session中

class LoginView(View):def get(self, request):return render(request, 'login.html')def post(self, request):username = request.POST.get('username')password = request.POST.get('password')user_obj = models.UserInfo.objects.filter(username=username, password=set_md5(password)).first()if user_obj:# return HttpResponse('ok')# 把当前用户id添加到session中request.session['user_id'] = user_obj.idreturn redirect('home')else:# return redirect('login')return render(request, 'login.html', {'error': '用户名或密码错误!'})

customerview中只需加上请求路径公私户判别,和获取session的user_id后进行结果筛选即可,其余不变。

class CustomerView(View):def get(self, request):# print(request.GET.urlencode())  # 会直接拿到get请求根路径后边的url# request.GET 拿到的是一个QuerrySet不允许修改# 先不进行urlecode,因为后边会多出一个pageget_data = request.GET.copy()  # 直接调用这个类自己的copy方法或者deepcopy方法或者自己import copy 都可以实现内容允许修改user_id = request.session.get('user_id')cur_user_name = models.UserInfo.objects.get(id=user_id)# print(user_id)page_id = request.GET.get('page')  # 获取get请求中的page数据search_field = request.GET.get('search_field')  # 获取get请求中的搜索字段search = request.GET.get('search')  # 获取get请求中的搜索数据contact_type_choices = {'qq':'0','微信':'1','手机':'2',}if search_field == 'contact_type__contains':if search in ['qq', '微信', '手机']:search = contact_type_choices[search]# print(search_field, search)cur_request_path = request.pathif cur_request_path == reverse('my_customer'):cur_user_customer = models.CustomerInfo.objects.filter(consultant_id=user_id)else:cur_user_customer = models.CustomerInfo.objects.filter(consultant_id__isnull=True)if search:#1. Q查询实现多条件查询,或者关系# customer_obj_list = models.CustomerInfo.objects.filter(Q(name__contains=search) | Q(contact__contains=search))# 2.**打散,and关系# customer_obj_list = models.CustomerInfo.objects.filter(**{search_field:search})# 3.Q的另一种方式, q_obj.connector = 'or',  不加or就是and关系q_obj = Q()# q_obj.connector = 'or'q_obj.children.append((search_field, search))# q_obj.children.append((search_field2, search2)) 同时用连个条件查询customer_obj_list = cur_user_customer.filter(q_obj)else:customer_obj_list = cur_user_customer.all()num = customer_obj_list.count()  # 总共记录数print(num)base_url = request.path  # 请求路径# 以后直接在settings配置文件中修改即可page_count = settings.PAGE_COUNT  # 页数栏显示多少个数record = settings.RECORD  # 每页显示多少条记录# print(base_url)html_obj = MyPagination(page_id=page_id, num=num, base_url=base_url, get_data=get_data, page_count=page_count, record=record)customer_obj = customer_obj_list[(html_obj.page_id - 1) * html_obj.record:html_obj.page_id * html_obj.record]# print(page_id)return render(request, 'customer.html', {'customer_obj': customer_obj, 'page_html': html_obj.html_page(), 'cur_user_name': cur_user_name})

公私互转

在get中发了一个tag标签,处于公户情况下只能公转私,否则私转公

customer.html

<form class="form-inline pull-left" method="post">
{% csrf_token %}<select name="gs_sg" class="form-control">{% if tag == 'gs' %}<option value="reverse_gs">公户转私户</option>{% else %}<option value="reverse_sg">私户转公户</option>{% endif %}</select><button class="btn btn-warning">go</button><table class="table table-hover table-bordered"><thead><tr><td><input type="checkbox">选择</td><td>序号</td><td>姓名</td><td>联系方式</td><td>号码</td><td>来源</td><td>转介绍人员</td><td>状态</td><td>课程顾问</td><td>咨询日期</td><td>咨询内容</td><td>操作</td></tr></thead><tbody>{% for customer in customer_obj %}<tr><td><input type="checkbox" value="{{ customer.id }}" name="customer_ids"></td><td>{{ forloop.counter }}</td>{#                <td>{{ customer.id }}</td>#}<td>{{ customer.name }}</td><td>{{ customer.get_contact_type_display }}</td><td>{{ customer.contact }}</td><td>{{ customer.get_source_display }}</td><td>{{ customer.referral_from.name|default:'无' }}</td><td>{{ customer.show_status }}</td><td>{{ customer.consultant.username|default:'无' }}</td><td>{{ customer.date }}</td><td>{{ customer.consult_content }}</td><td><a href="{% url 'edit_customer' customer.id %}"><i class="fa fa-edit"></i></a></td></tr>{% endfor %}</tbody></table>{#    自己封装的分页组件#}{#    {{ page_html|safe }}#}{#     # mark_safe()后端包裹后,前端就不要safe过滤了,自动识别成标签#}{{ page_html }}
</form>

CustomerView中增加post方法来处理这个请求

def post(self, request):print(request.POST)gs_sg = request.POST.get('gs_sg')customer_ids = request.POST.getlist('customer_ids')if hasattr(self, gs_sg):res_obj = models.CustomerInfo.objects.filter(pk__in=customer_ids)getattr(self, gs_sg)(request, res_obj)return redirect(request.path)

自定义中间件利用session登陆认证和登出逻辑

loginmiddleware.py

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect
from django.urls import reverse
from app01 import modelsclass MyLoginAuth(MiddlewareMixin):def process_request(self, request):# 白名单white_list = [reverse('login'), reverse('register'), ]if request.path in white_list:returnuser_id = request.session.get('user_id')# print(user_id)if user_id:# 将当前登录用户对象封装到request类中,成为它的一个属性,这样就可以在后边的request中直接使用request.user_obj = models.UserInfo.objects.get(id=user_id)returnelse:return redirect('login')

settings里配置你的中间件

MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',# 自定义中间件'app01.utils.loginmiddleware.MyLoginAuth',
]

现在就可以进行登录认证了,后边加一个登出逻辑,就是点击登出,清楚所有cookie和session

# 登出视图逻辑
class LoginOutView(View):def get(self, request):request.session.flush()  # 清楚所有的cookie和sessionreturn redirect('login')# url路由
url(r'^home/', views.HomeView.as_view(), name='home'),

跳转回源路径和url编码

点击完编辑后会直接跳转到/customer/路径 不会回到你编辑是那个?page=3,所以要解决这个问题。但是,用get_full_path获取路径时,带有搜索条件的&kw=111&name_contains=11这些会根据&断开取关键字,这是由url编码格式决定的。为了解决这个问题,引入QueryDict类,使用它的urlencode()方法。它会在编码时把特殊字符按照ASCII码对应的十六进制字符替换掉,这样在url编码时就不出现之前的关键字断开了。

此时,我们需要在请求时的url上添加提交后要去的路径。原来的url反向解析已经不能满足我们的需求,借助自定义标签来拼接href路径。

my_tags.py

from django.urls import reverse
from django import template
from django.http.request import QueryDictregister = template.Library()@register.simple_tag
def resolve_url(request, url_name, pk):next_url = request.get_full_path()# print(next_url)  # /customer/destination = reverse(url_name, args=(pk,))qd =QueryDict(mutable=True)  # mutable 可不可变,默认是Falseqd['next'] = next_urlfull_url = destination + '?' + qd.urlencode()  # /edit_customer/723/?next=%2Fcustomer%2F%3Fpage%3D2 按照ascii码十六进制替换/ ? &# /edit_customer/703/?next=/customer/?search_field=contact__contains&search=8&page=4# /edit_customer/703/?next=%2Fcustomer%2F%3Fsearch_field%3Dcontact__contains%26search%3D8%26page%3D4# 不用urlencode会导致在带有搜索条件的查询时,在后端request.get不到完整的?后变的信息# full_url = destination + '?next=' + next_url  # /edit_customer/714/?next=/customer/?page=2print(full_url)return full_url

customer.html中的编辑标签的href属性值中使用自定义的标签,

<td><a href="{% resolve_url request 'edit_customer' customer.id %}"><i class="fa fa-edit"></i></a></td>

会返回携带next_url的一个完整路径:

/edit_customer/703/?next=%2Fcustomer%2F%3Fsearch_field%3Dcontact__contains%26search%3D8%26page%3D4

,这是经过urlencode之后的,之前的路径就是:

/edit_customer/703/?next=/customer/?search_field=contact__contains&search=8&page=4

携带下一个返回时的url进入编辑页面时,在edit页面发起post请求后在editview中处理post请求信息后重定向到此时的request对象中的next对应的路径值即可。

就是:

%2Fcustomer%2F%3Fsearch_field%3Dcontact__contains%26search%3D8%26page%3D4

源路径

/customer/?search_field=contact__contains&search=8&page=4

跟进记录以及其分页和搜索

很简单了,就根据客户的逻辑进行修改。

# 跟进记录
class FollowCustomerView(View):def get(self, request):get_data = request.GET.copy()cur_user_name = request.user_obj.usernamecid = request.GET.get('cid')page_id = request.GET.get('page')  # 获取get请求中的page数据search_field = request.GET.get('search_field')  # 获取get请求中的搜索字段search = request.GET.get('search')  # 获取get请求中的搜索数据# print(search)# print(cid)if cid:cur_follow_customer = models.CustomerFollowUp.objects.filter(user=request.user_obj, delete_status=0, customer_id=cid)else:cur_follow_customer = models.CustomerFollowUp.objects.filter(user=request.user_obj, delete_status=0)if search:q_obj = Q()q_obj.children.append((search_field, search))customer_obj_list = cur_follow_customer.filter(q_obj)else:customer_obj_list = cur_follow_customer.all()num = customer_obj_list.count()  # 总共记录数# print(num)base_url = request.path  # 请求路径page_count = settings.PAGE_COUNT  # 页数栏显示多少个数record = settings.RECORD  # 每页显示多少条记录html_obj = MyPagination(page_id=page_id, num=num, base_url=base_url, get_data=get_data, page_count=page_count, record=record)follow_customer = customer_obj_list[(html_obj.page_id - 1) * html_obj.record:html_obj.page_id * html_obj.record]return render(request, 'follow_customer.html', {'follow_customer': follow_customer, 'page_html': html_obj.html_page(), 'cur_user_name': cur_user_name,})def post(self, request):print(request.POST)customer_ids = request.POST.getlist('customer_ids')models.CustomerFollowUp.objects.filter(customer_id__in=customer_ids).delete()return redirect(request.path)# urls
# 跟进记录
url(r'^follow_customer/', MyCustomer.FollowCustomerView.as_view(), name='follow_customer'),

follow_customer.html

{% extends 'starter.html' %}
{% load static %}{% block username %}{{ request.user_obj.username }}
{% endblock %}{% block content %}<h3><a href="{% url 'add_follow_customer' %}">添加跟进客户</a></h3>{% load my_tags %}<form method="get" class="form-inline pull-right"><select name="search_field" class="form-control"><option value="customer__name__contains">客户名</option><option value="customer__name__contains">跟进人</option></select><div class="form-group"><input type="text" class="form-control" name="search" placeholder="请输入搜索的内容"></div><button type="submit" class="btn btn-default">搜索</button></form><form class="form-inline pull-left" method="post">{% csrf_token %}<select name="delete_follow_customer" class="form-control">
<option value="delete_all">删除</option></select><button class="btn btn-warning">go</button><table class="table table-hover table-bordered"><thead><tr><td><input type="checkbox">选择</td><td>序号</td><td>姓名</td><td>跟进内容</td><td>跟进人</td><td>状态</td><td>跟进日期</td><td>操作</td></tr></thead><tbody>{% for customer in follow_customer %}<tr><td><input type="checkbox" value="{{ customer.customer.id }}" name="customer_ids"></td><td>{{ forloop.counter }}</td>{#                <td>{{ customer.id }}</td>#}<td>{{ customer.customer }}</td><td>{{ customer.content }}</td><td>{{ customer.user.username }}</td><td>{{ customer.get_status_display }}</td><td>{{ customer.date }}</td>
{#                    <td><a href="{% url 'edit_customer' customer.id %}"><i class="fa fa-edit"></i></a></td>#}
{#                    <td><a href="{% resolve_url request 'add_follow_customer' customer.id %}"><i class="fa fa-edit"></i></a></td>#}<td><a href="{% resolve_url request 'edit_follow_customer' customer.customer.id %}"><i class="fa fa-edit"></i></a></td></tr>{% endfor %}</tbody></table>{#    自己封装的分页组件#}{#    {{ page_html|safe }}#}{#     # mark_safe()后端包裹后,前端就不要safe过滤了,自动识别成标签#}{{ page_html }}</form>
{% endblock content %}

编辑和添加跟进记录

# 跟进客户modelform
class FollowCustomerForm(forms.ModelForm):class Meta:model = models.CustomerFollowUpfields = '__all__'exclude = ['delete_status', ]def __init__(self, request, *args, **kwargs):super().__init__(*args, **kwargs)for field_name, field in self.fields.items():field.widget.attrs.update({'class': 'form-control'})# modelform默认会生成这个外键关联的表的所有信息,就是models.CustomerInfo.objects.all()# 我们只想显示当前用户下的客户信息if field_name == 'customer':field.queryset = models.CustomerInfo.objects.filter(consultant=request.user_obj)elif field_name == 'user':field.choices = ((request.user_obj.id, request.user_obj.username),)# 添加和编辑跟进客户信息
class AddEditFollowCustomerView(View):def get(self, request, cid=None):label = '编辑跟进客户' if cid else '添加跟进客户'follow_customer_obj= models.CustomerFollowUp.objects.filter(user=request.user_obj, delete_status=0, customer_id=cid).first()  # filter返回的时一个QuerrySet集合,取出里边的model对象# print(follow_customer_obj, cid)follow_customer_form = FollowCustomerForm(request, instance=follow_customer_obj)return render(request, 'add_follow_customer.html', {'follow_customer_form':follow_customer_form, 'label':label})def post(self, request, cid=None):label = '编辑跟进客户' if cid else '添加跟进客户'follow_customer_obj = models.CustomerFollowUp.objects.filter(user=request.user_obj, delete_status=0, customer_id=cid).first()follow_customer_form = FollowCustomerForm(request.POST, instance=follow_customer_obj)next_url = request.GET.get('next')# print(next_url)if follow_customer_form.is_valid():follow_customer_form.save()if next_url:return redirect(next_url)else:return redirect('follow_customer')else:return render(request, 'add_customer.html', {'follow_customer_form': follow_customer_form, 'label':label})

add_follow_customer.html

{% extends 'starter.html' %}{% block son_content %}{{ label }}
{% endblock %}{% block content %}<form class="form-horizontal" method="post" novalidate>{% csrf_token %}{% for field in follow_customer_form %}<div class="form-group"><label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label><div class="col-sm-6">{{ field }}</div><div class="col-sm-4"><span style="color: red">{{ field.errors.0 }}</span></div></div>{% endfor %}<div class="col-sm-6 offset-8"><button class="btn btn-success">提交</button></div></form>{% endblock content %}

urls

# 编辑跟进记录
url(r'^edit_follow_customer/(\d+)/', MyCustomer.AddEditFollowCustomerView.as_view(), name='edit_follow_customer'),
# 添加跟进记录
url(r'^add_follow_customer/', MyCustomer.AddEditFollowCustomerView.as_view(), name='add_follow_customer'),

编辑和添加报名记录

如上重复的操作,只需修改变量名和一些其他的小点即可。

编辑和添加课程记录

如上重复的操作,只需修改变量名和一些其他的小点即可。

公转私bug修改

当两个用户都进行转私户的操作时,之前的代码会出现最后一个转的人把客户都转到它的账户下。这是因为最后一个用户把cid提交后进行更新操作。我们借助事务来处理这个问题。

# 事务
from django.db import transactiondef reverse_gs(self, request, customer_ids):# 公转私bug修改with transaction.atomic():res_obj = models.CustomerInfo.objects.filter(pk__in=customer_ids, consultant_id__isnull=True).select_for_update()print(res_obj)if res_obj.count() != len(customer_ids):tag2 = 'gs_bug'tag1 = 'gs'return render(request, 'customer.html', {'customer_obj': res_obj,'tag1': tag1, 'tag2': tag2})# return HttpResponse('出错!')res_obj.update(consultant_id=request.session.get('user_id'))

customer.html里修改

<h3>{% load my_tags %}{% if tag2 == 'gs_bug'%}由于你操作过慢,部分客户已经被其他用户转走,在你锁选择的客户中,目前可以转为你的私户的客户信息如下:{% else %}{% reverse_url request %}{% endif %}
</h3>

modelformset批量生成和修改

from django.forms.models import modelformset_factoryclass StudyRecordModelForm(forms.ModelForm):class Meta:model = models.StudyRecordfields = '__all__'## def __init__(self, *args, **kwargs):#     super().__init__(*args, **kwargs)#     for field_name, field in self.fields.items():#         field.widget.attrs.update({'class': 'form-control'})class StudyRecordView(View):def get(self, request):# study_record = models.StudyRecord.objects.filter(course_record_id=1).first()# print(study_record)# study_record_form = StudyRecordModelForm(instance=study_record)formset = modelformset_factory(model=models.StudyRecord, form=StudyRecordModelForm)return render(request, 'study_records.html', {'formset': formset, }){% for study in formset %}<tr><td># 就如csrf_token似的,提交修改必须加{{ study.id }}<input type="checkbox" value="" name="customer_ids"></td><td>{{ forloop.counter }}</td>{#                <td>{{ customer.id }}</td>#}<td>{{ study.instance.student}}</td><td>{{ study.course_record.class_grade}}</td><td>{{ study.score}}</td><td>{{ study.show_status}}</td><td>{{ study.note}}</td>
{#                    <td>{{ study.date}}</td>#}
{#                    <td><a href="{% url 'edit_customer' customer.id %}"><i class="fa fa-edit"></i></a></td>#}
{#                    <td><a href="{% resolve_url request 'add_follow_customer' customer.id %}"><i class="fa fa-edit"></i></a></td>#}
{#                    <td><a href="{% resolve_url request 'edit_course_record' course_record.name.id %}"><i class="fa fa-edit"></i></a></td>#}</tr>{% endfor %}

用户权限分配

权限表结构RBAC,在权限菜单表中添加url和名,在role给角色配分不同权限,在UserInfo中给每个用户分配角色

class UserInfo(models.Model):"""用户信息表"""username = models.CharField(max_length=16, verbose_name='姓名')password = models.CharField(max_length=32, verbose_name='密码')email = models.EmailField()telephone = models.CharField(max_length=16)is_active = models.BooleanField(default=True)roles = models.ManyToManyField(to='Role')class Meta:verbose_name_plural = '用户信息表'def __str__(self):  # __unicode__return self.usernameclass Role(models.Model):"""角色表"""name = models.CharField(max_length=64, unique=True)menus = models.ManyToManyField('Menus', verbose_name='菜单', blank=True)class Meta:verbose_name_plural = '角色表'def __str__(self):return self.nameclass Menus(models.Model):"""动态菜单"""name = models.CharField('菜单名', max_length=32)url_type_choices = ((0, 'absolute'),(1, 'dynamic'),)url_type = models.SmallIntegerField(choices=url_type_choices, default=0)url_name = models.CharField('连接', max_length=128)class Meta:verbose_name_plural = '菜单'unique_together = ('name', 'url_name')def __str__(self):return self.name

用户登录成功后就把权限认证放进session中

# 登录
class LoginView(View):def get(self, request):return render(request, 'login.html')def post(self, request):username = request.POST.get('username')password = request.POST.get('password')user_obj = models.UserInfo.objects.filter(username=username, password=set_md5(password)).first()if user_obj:# return HttpResponse('ok')# 把当前用户id添加到session中request.session['user_id'] = user_obj.id# 把权限认证封装到session中permission = models.UserInfo.objects.filter(username=username).values('roles__menus__url_name').distinct()  # 身兼多职的话就去重一下permission_list = list(permission)request.session['permission_list'] = permission_list# print(permission_list)return redirect('home')else:# return redirect('login')return render(request, 'login.html', {'error': '用户名或密码错误!'})

在中间件中完成权限认证

class MyLoginAuth(MiddlewareMixin):def process_request(self, request):user_id = request.session.get('user_id')permission = request.session.get('permission_list')path = request.path# print(path)# /lol/follow_customer/# 登录认证# 白名单white_list = [reverse('login'), reverse('register'), reverse('login_out')]if path in white_list:returnif not user_id:return redirect('login')# 登录成功# 将当前登录用户对象封装到request类中,成为它的一个属性,这样就可以在后边的request中直接使用request.user_obj = models.UserInfo.objects.get(id=user_id)# 权限认证# 权限认证白名单permission_white_list = [reverse('home')]if path in permission_white_list:returnfor url in permission:res = re.match(url['roles__menus__url_name'], path)# print(res)# print(url['roles__menus__url_name'])if res:returnelse:return HttpResponse('您不配!')

根据用户权限动态生成菜单

把是菜单的路径写个标志登陆时一起存在session中

permission = models.UserInfo.objects.filter(username=username).values('roles__menus__url_name', 'roles__menus__url_type', 'roles__menus__name').distinct()  # 身兼多职的话就

在菜单生成出引入自己的自定义inclusion

<ul class="nav nav-treeview">{% load my_tags %}{% menu request %}</ul>

my_tags

@register.inclusion_tag('menu.html')
def menu(request):menu_list = []path = request.pathpermission = request.session.get('permission_list')for url in permission:if url['roles__menus__url_type']:menu_list.append(url)if path == url['roles__menus__url_name']:url['active'] = 'active'# print(menu_list)return {'menu_list': menu_list}

menu.html

{% for menu in menu_list %}<li class="nav-item"><a href="{{ menu.roles__menus__url_name }}" class="nav-link {{menu.active}}"><i class="far fa-circle nav-icon"></i><p>{{ menu.roles__menus__name }}</p></a></li>
{% endfor %}

动态二级菜单

设计注入到session中的数据结构,其中把一级菜单做个标记,然后找到它对应的二级菜单,并加入他所在的键值对的chirldren中。最后再session中获取,在渲染时,设置二级菜单标签css样式,绑定js点击事件,一点击就取消它的hiden类属性。

菜单排序

在菜单表中给每一个菜单加一个权重,然后再处理时按权重排完序后再循进行环模板渲染。

model中增加一个权重字段,然后给他们赋值不同的权重

class Menus(models.Model):"""动态菜单"""name = models.CharField('菜单名', max_length=32)url_type_choices = ((0, 'absolute'),(1, 'dynamic'),)url_type = models.SmallIntegerField(choices=url_type_choices, default=0)url_name = models.CharField('连接', max_length=128)weight = models.IntegerField(default=0)class Meta:verbose_name_plural = '菜单'unique_together = ('name', 'url_name')def __str__(self):return self.name

inclusion_tag中对之前处理的menu_list根据weight进行排序再取逆序使得最大权重排在第一位

@register.inclusion_tag('menu.html')
def menu(request):menu_list = []path = request.pathpermission = request.session.get('permission_list')# print('permission:', permission)for url in permission:if url['roles__menus__url_type']:menu_list.append(url)if path == url['roles__menus__url_name']:url['active'] = 'active'# print('menu_list:', menu_list)# 根据weight排序menu_list = sorted(menu_list, key=lambda dic: dic['roles__menus__weight'], reverse=True)# print(menu_list)return {'menu_list': menu_list}

点击二级菜单的子菜单后二级菜单仍然处于选中状态

在menu表中添加一个parent_id字段,它自关联到主键,在登录时把他注入到session中:

parent_id = models.ForeignKey('self', null=True, blank=True)

在中间件中给request封装一个属性request.current_id = pid

# 标签当前的id
request.current_id = Noneif res:pid = url['roles__menus__parent_id']if pid:request.current_id = pidelse:request.current_id = url['roles__menus__pk']

在自定义menu标签中,本来通过判断当前路径给菜单加active类,现在根据当前路径的pk值与封装到的current_id比较即可

for url in permission:if url['roles__menus__url_type']:menu_list.append(url)# if path == url['roles__menus__url_name']:if request.current_id == url['roles__menus__pk']:url['active'] = 'active'

二级菜单路径导航和面包屑

# 中间件中加面包屑# 面包屑路径导航request.bread_crumbs = [{'url': reverse('home'), 'title': '首页'},]for url in permission:# pattern = '^' + url['roles__menus__url_name'] + '$'res = re.match(url['roles__menus__url_name'], path)# print(url['roles__menus__url_type'])# print(url['roles__menus__url_name'])if res:pid = url['roles__menus__parent_id']if pid:request.current_id = pidfor data in permission:# 三级菜单面包屑if data['roles__menus__pk'] == pid:# 有一层循环可能耗费事件,可以提前把注入session的数据处理一下,弄成字典request.bread_crumbs.append({'url': data['roles__menus__url_name'], 'title': data['roles__menus__name']})break# 二级菜单面包屑request.bread_crumbs.append({'url': None, 'title': url['roles__menus__name']})else:request.current_id = url['roles__menus__pk']# print(url['roles__menus__parent_id'])# 二级菜单面包屑request.bread_crumbs.append({'url': None, 'title': url['roles__menus__name']})return

导航栏动态生成

<ol class="breadcrumb m-lg-3">{#            <li><a href="{% url 'home' %}">主页</a></li>#}{% for bread in request.bread_crumbs %}{% if bread.url %}<li><a href="{{ bread.url }}">{{ bread.title }}</a></li>{% else %}<li class="active">{{ bread.title }}</li>{% endif %}{% endfor %}
</ol>

权限精确到按钮级别

在model表中增加一个url别名字段,然后自定义一个过滤器判断别名在不在session中,最后动态渲染。

菜单表增加别名字段url_other_name

class Menus(models.Model):"""动态菜单"""name = models.CharField('菜单名', max_length=32)url_type_choices = ((0, 'absolute'),(1, 'dynamic'),)url_type = models.SmallIntegerField(choices=url_type_choices, default=0)url_name = models.CharField('连接', max_length=128)url_other_name = models.CharField(max_length=128, null=True, blank=True)weight = models.IntegerField(default=0)class Meta:verbose_name_plural = '菜单'unique_together = ('name', 'url_name')def __str__(self):return self.name

自定义过滤器处理别名

@register.filter
def url_button(request, name):permission = request.session.get('permission_list')url_name = []for url in permission:if url['roles__menus__url_other_name']:url_name.append(url['roles__menus__url_other_name'])# print(url_name)if name in url_name:return Trueelse:return False

在生成按钮的地方使用过滤器

{% if request|url_button:'add_customer' %}
<h3><a href="{% url 'add_customer' %}">添加客户</a></h3>
{% endif %}

完整项目地址

我的码云:

https://gitee.com/libo-sober/super-crm/

Django项目:LOL学院学员管理系统相关推荐

  1. 【学员管理系统】0x01 班级信息管理功能

    [学员管理系统]0x01 班级信息管理功能 写在前面 项目详细需求参见:Django项目之[学员管理系统] 视图函数: 我们把所有的处理请求相关的函数从 urls.py中拿出来,统一放在一个叫view ...

  2. python3 django开发_python3开发进阶-Django框架学习前的小项目(一个简单的学员管理系统)...

    ''' 自己独立写一个学员管理系统 表结构: 班级表: -id -grade_name 学生表: -id -student_name -grade 关联外键班级表 老师表: -id -teacher_ ...

  3. [Django项目案例]搭建用户与商品后台管理系统

    [Django项目案例]搭建用户与商品后台管理系统 1 搭建流程 1.1 创建Django项目 1.2 配置数据库 1.3 创建app并载入Settings 1.4 配置时区与静态文件夹 1.5 定义 ...

  4. 【Go语言】实现一个简单的纯后端学员管理系统,入门级别练手项目,练习结构体,接口,构造函数

    GO语言实现一个简单的纯后端学员管理系统 项目总览: 一.项目开发日志 二.前情知识补充 1.构造函数:用来在创建对象时初始化对象 2.方法和接收者 三.主函数中的内容 四.逐个自定义函数拆解分析 1 ...

  5. ORM版学员管理系统

    ORM版学员管理系统 班级表 表结构 class Class(models.Model):id = models.AutoField(primary_key=True) # 主键cname = mod ...

  6. 用python 开发合同管理系统_python3.6+django2.0 一小时学会开发一套学员管理系统demo...

    1.在pycharm中新建project demo1 添加app01 点击create按钮完成新建 2.在demo项目目录下新建目录static,并在settings.py中追加代码: STATICF ...

  7. 计算机毕业设计springboot驾校学员管理系统w42sj源码+系统+程序+lw文档+部署

    计算机毕业设计springboot驾校学员管理系统w42sj源码+系统+程序+lw文档+部署 计算机毕业设计springboot驾校学员管理系统w42sj源码+系统+程序+lw文档+部署 本源码技术栈 ...

  8. python实训项目-黑马程序员上海校区Python21期Django项目实训

    黑马程序员上海中心 月薪一万只是起点 关注 晚上十点,一名名Python学生正在酣畅淋漓地撸代码,手指不间断地敲击着键盘,发出机械而清脆的声音. 各个小组在经过为期4天的django项目小组开发,终于 ...

  9. Django 1.6 最佳实践: 如何设置django项目的设置(settings.py)和部署文件(requirements.txt)...

    Django 1.6 最佳实践: 如何设置django项目的设置(settings.py)和部署文件(requirements.txt) 作者: Desmond Chen, 发布日期: 2014-05 ...

最新文章

  1. python小游戏系列记忆宫殿,儿时的回忆
  2. Javascript学习--------详解window窗口对象
  3. 项目: 写一个开头的界面 【c++ / c】
  4. 计算机数学基础 课程定位图形,本科《计算机数学基础》(上)课程教学设计方案.doc...
  5. GDCM:gdcm::DefinedTerms的测试程序
  6. c语言1 2 3 10000,在网上看到一个求2的10000次方的方法,有个地方看不懂,求大佬...
  7. 《PHP和MySQL Web开发从新手到高手(第5版)》一一第1章 安装
  8. bmob php支付,支付服务 - 支付服务RESTful 开发文档 - 《Bmob 文档中心》 - 书栈网 · BookStack...
  9. php debug build no,php – 尝试安装xdebug:找不到配置文件
  10. XPath与lxml类库
  11. wps怎么把ppt里的字体一起保存_PPT基础教程!看懂少走弯路!
  12. 解决默写浏览器中点击input输入框时,placeholder的值不消失的方法
  13. MySQL保存计算结果_在数据库中保存计算结果如何对应到相应的工程文件
  14. python实训day7
  15. 3dmax制作alpha通道贴图
  16. c语言rewind函数作用,C语言中的setlinebuf()、utmpname()、rewind函数使用
  17. Mac下使用Parallel Desktop进行虚拟机的网络配置
  18. 串ababaaababaa的next和串ababaabab的nextval
  19. 视频如何转换成mp3
  20. 一维表转二维表(mysql)

热门文章

  1. sql怎么发音mysql_[原创]SQL发音考证(搜寻SQL-86标准)
  2. Arch linux使用yay安装软件时有效性检查错误
  3. 根据微信公众号关注/取消关注事件,获取用户信息
  4. 《代码整洁之道》读后感及总结
  5. 移动端VUE实现一周课程表
  6. 赌运挖洞之Apache目录浏览
  7. 【Python发展】pandas和koalas
  8. 情商高手与小白的言辞,差别究竟在哪里?
  9. div 添加失焦事件
  10. 清橙OJ A1095 回溯之教室排课