python---CRM用户关系管理
Day1:项目分析
一:需求分析
二:CRM角色功能介绍
三:业务场景分析
销售:
.销售A 从百度推广获取了一个客户,录入了CRM系统,咨询了Python课程,但是没有报名
.销售B 从qq群获取一个客户,成功使他报名Python班,然后给他发送了报名连接,等待用户填写完毕后,将他添加到Python具体的学习班级中
.销售C 打电话给之前的一个客户,说服他报名Python课程,但是没有成功,更新了跟踪记录
.销售D 获取了一个客户,录入信息时,发现此客户已经存在,不允许重复录入,随后通知相应的原负责人跟进
.销售E 从客户库中获取了,超过一个月未跟进的客户,进行再次跟进
.销售主管 查看了部门本月的销售报表,包括来源分析,成单率分析,班级报名数量分析,销售额环比,同比
学员:
.客户A 填写了销售发来的报名连接,上传了个人的证件信息,提交,之后收到邮件,告知报名成功,并为他开通了学员账号,升级为学员A
.学员A 登录学员系统,看到自己的合同,报名的班级,课程大纲
.学员A 提交了Python课程当时课时作业
.学员A 查看自己的Python课程成绩,排名
.学员A 搜索问题,未找到答案,录入一条问题
.学员A 转介绍学员,录入其信息
讲师:
.讲师A 登录CRM系统,查看自己管理的班级列表
.讲师A 进入Python 5期课程,创建第3节的上课记录,填写了本节课内容,作业要求
.讲师A 在课程中点名,对点名情况进行录入,标记相关状态
.讲师A 批量下载所有学员的课时作业,给每个学员在线批注了成绩+状态
管理员:
.创建课程 C++,Python..
.创建校区 上海,北京..
.创建班级 C++35期,Python27期
.创建账号 ABCD
.创建了销售,讲师,学员角色
.为账号分配到对应的角色,将ABCD分配给销售
.创建相关权限
.为销售角色分配了相关权限
四:表结构设计
数据库关联模型
Django表结构实现
from django.db import models
from django.contrib.auth.models import User
# Create your models here. class UserProfile(models.Model):
'''
用户信息表:
含有讲师,销售,管理员这些正式人员
'''
user = models.OneToOneField(User) #使用的是Django自带的用户验证Username, password and email are required. Other fields are optional. name = models.CharField(max_length=,verbose_name="姓名")
role = models.ManyToManyField("Role",blank=True) #,null=Truenull has no effect on ManyToManyField.,null对于manytomanyfield无作用,会报警 def __str__(self):
return self.name class Role(models.Model):
'''
角色表:学员,讲师,销售,管理员
'''
name = models.CharField(max_length=,unique=True)
menus = models.ManyToManyField("Menu",blank=True)
def __str__(self):
return self.name class Menu(models.Model):
'''
动态菜单
'''
name = models.CharField("菜单名",max_length=)
url_type_choices = (
(,"absolute"), #绝对路径/Sale/index.html
(,"dynamic"), #动态url,根据url()方法中的name获取
) url_type = models.SmallIntegerField(choices=url_type_choices)
url_name = models.CharField("URL",max_length=) def __str__(self):
return self.name class CustumerInfo(models.Model):
'''
客户信息表:联系方式,姓名等
''' name = models.CharField(max_length=,null=True,blank=True) #开始咨询的时候允许为空
contact_type_choices = ((,'qq'),(,"微信"),(,'手机'))
contact_type = models.SmallIntegerField(choices=contact_type_choices,default=)
contact = models.CharField(max_length=,unique=True)
source_choices = (
(,'QQ群'),
(,"51CTO"),
(,"百度推广"),
(,"知乎"),
(,"转介绍"),
(,"其他")
)
source = models.SmallIntegerField(choices=source_choices)
referral_from = models.ForeignKey("self",blank=True,null=True,verbose_name="转介绍人员") consult_courses = models.ManyToManyField("Course",verbose_name="咨询课程") #咨询的课程,允许咨询多门
consult_content = models.TextField("咨询内容",blank=True)
status_choices = ((,"未报名"),(,"已报名"),(,"已退学"))
status = models.SmallIntegerField(choices=status_choices) consultant = models.ForeignKey("UserProfile",verbose_name="课程顾问") date = models.DateField(auto_now_add=True) def __str__(self):
return self.name class CustumerFollowUp(models.Model):
'''
客户跟踪记录表:跟踪进度
'''
customer = models.ForeignKey("CustumerInfo")
content = models.TextField(verbose_name="跟进内容")
user = models.ForeignKey("UserProfile",verbose_name="跟进人员")
status_choices = (
(,"近期无报名计划"),
(,"一个月内报名"),
(,"2周内报名"),
(,"已报名"),
) status = models.SmallIntegerField(choices=status_choices)
date = models.DateField(auto_now_add=True) def __str__(self):
return self.content class Student(models.Model):
'''
学员信息表:(未报名的客户在客户表中),报名成功的在学员表
'''
customer = models.ForeignKey("CustumerInfo")
class_grades = models.ManyToManyField("ClassList") #学员可以报多门课程 def __str__(self):
return self.customer.name class Course(models.Model):
'''
课程表
'''
name = models.CharField(max_length=,verbose_name="课程名称",unique=True)
price = models.PositiveSmallIntegerField() #必须为正
period = models.PositiveSmallIntegerField(verbose_name="课程周期(月)",default=)
outline = models.TextField(verbose_name="大纲") def __str__(self):
return self.name class ClassList(models.Model):
'''
班级列表
'''
branch = models.ForeignKey("Branch") #校区关联
couser = models.ForeignKey("Course") class_type_choices = (
(,"脱产"),
(,"周末"),
(,"网络班")
)
class_type = models.SmallIntegerField(choices=class_type_choices,default=) semester = models.SmallIntegerField(verbose_name="学期")
teachers = models.ManyToManyField("UserProfile",verbose_name="讲师")
start_date = models.DateField("开班日期")
graduate_date = models.DateField("毕业日期",blank=True,null=True) def __str__(self):
return "%s (%s)期"%(self.couser,self.semester) class Meta:
unique_together = ('branch','class_type',"couser","semester") #联合唯一 class CourseRecord(models.Model):
'''
上课记录:该节课程内容等
'''
class_grade = models.ForeignKey("ClassList",verbose_name="上课班级")
day_num = models.PositiveSmallIntegerField(verbose_name="课程节次")
teacher = models.ForeignKey("UserProfile")
title = models.CharField("本节主题",max_length=)
content = models.TextField("本节内容")
has_homework = models.BooleanField("本节是否有作业",default=True)
homework = models.TextField("作业需求",blank=True,null=True)
date = models.DateTimeField(auto_now_add=True,verbose_name="上课时间")
def __str__(self):
return "%s第(%s)节"%(self.class_grade,self.day_num ) class Meta:
unique_together = ("class_grade","day_num") class StudyRecord(models.Model):
'''
学习记录表:学员考勤,作业,成绩,备注
'''
course_record = models.ForeignKey("CourseRecord")
student = models.ForeignKey("Student") score_choices = (
(,"A+"),
(,"A"),
(,"B+"),
(,"B"),
(,"B-"),
(,"C+"),
(,"C"),
(,"C-"),
(,"N/A"), #不可得not avaliable
(-, "D"), #未交作业
(-,"COPY") #抄袭
)
score = models.SmallIntegerField(choices=score_choices) show_choices = (
(,"缺勤"),
(,"已签到"),
(,"迟到"),
(,"早退"),
)
show_status = models.SmallIntegerField(choices=show_choices) note = models.CharField("情况备注",max_length=,blank=True,null=True) date = models.DateTimeField(auto_now_add=True) def __str__(self):
return "%s %s %s"%(self.course_record,self.student,self.score) class Branch(models.Model):
'''
校区
'''
name = models.CharField(max_length=,unique=True)
addr = models.CharField(max_length=,blank=True,null=True)
def __str__(self):
return self.name
表结构创建
Day2:主要实现功能kingadmin为各个应用实现一个类似于Django自带的数据库管理功能
kingadmin目录
销售目录
学员目录
1.首先我们需要在项目启动后(进入Kingadmin模块中view视图后,能够自动采集所有的应用中需要我们采集的数据库信息)
(1)先设置采集方法:在每个需要我们采集的应用模块中添加上kingadmin.py文件(类似于后台admin会在应用模块的admin.py中采集信息一样)。如上面目录结构,在其中添加了kingadmin.py
from kingadmin.sites import site #虽然说,每个APP:sale,student都去导入了一次site,但是在python项目中对于同一个模块只会导入一次,所以这本身就是单例模式(使用的是内存中存在的那个)
from kingadmin.admin_base import BaseKingAdmin
from repository import models print("Sale.kingadmin") class CustomerAdmin(BaseKingAdmin):
list_display = ['name','contact_type','contact','source','consult_content','consultant','status','date']
list_filter = ['source','consultant','status','date']
search_fields = ['contact','consultant__name'] site.register(models.CustumerInfo,CustomerAdmin)
site.register(models.Role)
site.register(models.Menu)
site.register(models.UserProfile)
Sale模块中kingadmin
from kingadmin.sites import site
from kingadmin.admin_base import BaseKingAdmin
from Student import models class TestAdmin(BaseKingAdmin):
list_display = ['name'] site.register(models.TestAdmin,TestAdmin) ----------------------------------------------------------
student模块中自定义一个表
class TestAdmin(models.Model):
name = models.CharField("姓名",max_length=) def __str__(self):
return self.name
Student模块中kingadmin
从中发现需要用到一个基类BaseKingAdmin来自于kingadmin模块:是为了防止注册事件时出现为空的现象,而且在基类中添加功能更加方便
class BaseKingAdmin(object):
pass
admin_base.py中BaseKingAdmin基类
还需要from kingadmin.sites import site,使用到site方法(类似于admin.site.register(模型,自定义模型显示类)):功能是将各个模块中的数据模型统一添加在一个数据结构中,方便调用
from kingadmin.admin_base import BaseKingAdmin class AdminSite(object):
def __init__(self):
self.enabled_admins = {} def register(self,model_class,admin_class=None):
'''
注册admin表
:param model_class:
:param admin_class:
:return:
'''
app_name = model_class._meta.app_label #app_label是当前应用的名字 一个应用可以注册多个表
model_name = model_class._meta.model_name #model_name是表名 和app_lable连接就是数据表全名 if not admin_class:
admin_class = BaseKingAdmin()
else:
admin_class = admin_class() if app_name not in self.enabled_admins:
self.enabled_admins[app_name] = {} admin_class.model = model_class self.enabled_admins[app_name][model_name] = admin_class site = AdminSite()
sites.py中的site方法
将数据统一放入self.enabled_admins{}中,形式为self.enabled_admins[模块][表名] = 自定义模型显示类(默认BaseKingAdmin)
注意:虽然在每个模块中都导入了一次sites模块,使用一次site对象,实际上使用的是同一个site对象
可以使用id(site)查看内存,因为python机制中将一个模块导入后,会将其保存在内存中,下次导入数据的时候,会直接从内存中获取数据(所以大家使用的是一个site对象)
所以说:python模块本身就是单例模式
(2)从settings.py中获取各个模块。创建app_setup.py文件,在项目进入view时去调用该文件,并执行,获取到所有模块的信息
进入views.py自动调用app_setup.kingadmin_auto_discover()方法
from django.shortcuts import render,redirect
from django.contrib.auth import authenticate,login,logout #快捷操作
from kingadmin import app_setup app_setup.kingadmin_auto_discover() #用来导入所有含Kingadmin的模块,模块中会去调用相应的Kingadmin文件去注册事件 from kingadmin.sites import site #发现只导入模块一次,site对象只有一个 ---------------------下面实现的是将数据分发给前端-------------------------------------
def get_filter_result(request,querysets):
filter_conditions = {}
for k,v in request.GET.items():
if v:
filter_conditions[k] = v
return querysets.filter(**filter_conditions),filter_conditions def table_obj_list(request,app_name,model_name):
'''取出指定的数据返给前端''' admin_class = site.enabled_admins[app_name][model_name] model_class = admin_class.model
querysets = model_class.objects.all() filter_data,filter_conditions = get_filter_result(request,querysets)
print(filter_conditions)
admin_class.filter_conditions = filter_conditions #也可以传值给前端,但是这样也不错 return render(request,"kingadmin/table_obj_list.html",{"queryset":filter_data,'admin_class':admin_class})
views.py进入后,顺序执行,首先去调用app_setup.kingadmin_auto_discover()方法采集信息
看如何采集各个模块信息:从配置文件中settings的INSTALLED_APPS中获取所有模块信息
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'repository.apps.RepositoryConfig',
'kingadmin',
'Student',
'Sale',
]
settings文件INSTALLED_APPS
views调用了app_setup中的kingadmin_auto_discover()方法自动采集信息,下面看看app_setup文件:实现方法。反向查找
from django import conf #实现动态获取配置文件,而不是以目录形式
import importlib def kingadmin_auto_discover():
for module in conf.settings.INSTALLED_APPS:
try:
# md = importlib.import_module('.kingadmin',module) #这个也可以
md = __import__('%s.kingadmin'%module) #导入Kingadmin,然后回去执行该文件中的数据,去注册事件(模块导入后,会自动使用site.register方法注册事件)
except ImportError as e:
pass
(3)上面将数据采集完毕,方法内存中site对象中,使用app_index视图方法,可以实现后台管理admin首页功能
def app_index(request):return render(request,"kingadmin/app_index.html",{'site':site})
<div>
{% for app_name,app_tables in site.enabled_admins.items %}
<table class="table table-striped">
<thead>
<tr>
<th>{{ app_name }}</th>
</tr>
</thead>
<tbody>
{% for model_name in app_tables %}
<tr>
<td>
<a href="{% url 'table_obj_list' app_name model_name %}">
{{ model_name }}
</a>
</td>
<td>ADD</td>
<td>Change</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
</div>
前端主要代码
(4)实现点击表名,查看数据的功能
def get_filter_result(request,querysets):
filter_conditions = {}
for k,v in request.GET.items():
if v:
filter_conditions[k] = v
return querysets.filter(**filter_conditions),filter_conditions def table_obj_list(request,app_name,model_name):
'''取出指定的数据返给前端''' admin_class = site.enabled_admins[app_name][model_name] model_class = admin_class.model
querysets = model_class.objects.all() filter_data,filter_conditions = get_filter_result(request,querysets)
print(filter_conditions)
admin_class.filter_conditions = filter_conditions #也可以传值给前端,但是这样也不错 return render(request,"kingadmin/table_obj_list.html",{"queryset":filter_data,'admin_class':admin_class})
table_obj_list方法根据模块和表名去获取site对象中的数据
{% extends "kingadmin/index.html" %}
{% load my_func %} {% block right-content-container %}
<h1 class="page-header">APP</h1>
<form method="get">
{% for field in admin_class.list_filter %}
{% build_filter_row field admin_class %}
{% endfor %}
<button type="submit" class="btn btn-primary">提交</button>
</form>
<div>
<table class="table table-striped">
<thead>
<tr>
{% for field in admin_class.list_display %}
<th>{{ field }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for item in queryset %}
<tr>
{% build_table_row item admin_class %}
</tr>
{% endfor %}
</tbody>
</table>
</div> {% endblock %}
前端
前端使用了自定义模板函数
# coding:utf8
# __author: Administrator
# date: //
# /usr/bin/env python
from django import template
from django.utils.safestring import mark_safe
from datetime import datetime,timedelta register = template.Library() @register.simple_tag
def build_filter_row(field,admin_class):
model = admin_class.model
field_obj = model._meta.get_field(field)
filter_conditions = admin_class.filter_conditions
try:
select = "<select name='%s'>"%field
data_list = field_obj.get_choices(field) #可以获取choices选项和外键
except AttributeError:
if field_obj.get_internal_type() in ("DateField","DateTimeField"):
field = "%s__gte"%field
select = "<select name='%s'>"%field
time_now = datetime.now()
time_list = [
["", "---------"],
[time_now, "today"],
[time_now - timedelta(), "七天内"],
[time_now.replace(day=), "本月"],
[time_now - timedelta(), "三个月内"],
[time_now.replace(month=, day=), "今年内"],
['', "ALL"]
] def turn_date(date_list):
date_obj, date_str = date_list
if type(date_obj) is datetime:
date_obj = date_obj.strftime("%Y-%m-%d")
return (date_obj,date_str) data_list = map(turn_date,time_list)
else:
select = "<select name='%s'>"%field
data_list = list(model.objects.values_list("id",field)) for item in data_list:
if str(item[]) == filter_conditions.get(field,None):
option = "<option value='%s' selected>" % str(item[])
else:
option = "<option value='%s'>"%str(item[])
option += item[]
option += "</option>"
select += option select += "</select>" return mark_safe(select) @register.simple_tag
def build_table_row(obj,admin_class):
'''生成一条HTML中tr元素'''
tr = ""
for field in admin_class.list_display:
# column_obj = admin_class.model._meta.get_field(field) #model是获取对应的模型对象
# if column_obj.choices:
# column_data = getattr(obj,"get_%s_display"%field)() #使用方法,要加上()
# else:
# column_data = getattr(obj,field) #使用属性不需要()
func = "get_"+field+"_display"
if hasattr(obj,func):
column_data = getattr(obj,func)()
else:
column_data = getattr(obj,field) td = "<td>%s</td>"%column_data
tr += td return mark_safe(tr)
my_func.py设置自定义函数
Day3:对上面的功能添加分页,筛选,排序,搜索功能(功能之间的url需要重组)
一:分页实现(在Django自带分页组件下进行扩展)
from django.core.paginator import Paginator class CustomPagimator(Paginator):
def __init__(self,current_page,max_page_num,*args,**kwargs):
self.current_page = int(current_page) #当前页
self.max_page_num = max_page_num #可以显示多少页
super(CustomPagimator,self).__init__(*args,**kwargs) def page_num_range(self):
# self.num_pages 总页数
part_num = int(self.max_page_num/)
if self.num_pages <= self.max_page_num:
return range(, self.num_pages + )
if self.current_page <= part_num:
return range(,self.max_page_num+)
elif self.current_page+part_num>= self.num_pages:
return range(self.num_pages-self.max_page_num,self.num_pages+)
else:
return range(self.current_page - part_num, self.current_page + part_num + )
分页类代码
current_page = request.GET.get('_p',)
paginator = CustomPagimator.CustomPagimator(current_page=current_page, max_page_num=,object_list=querysets,per_page=) # 传入总数据和每页显示的数据
try:
filter_data = paginator.page(current_page)
except PageNotAnInteger:
filter_data = paginator.page()
except EmptyPage:
filter_data = paginator.page(paginator.num_pages) # num_pages数总页数,最后一页 page_html = paginator.page_num_range()
分页类的使用
<div>
{% build_page_row queryset page_html admin_class %}
</div>
使用模板函数对分页数据进行url组合
@register.simple_tag
def build_page_row(queryset,page_html,admin_class):
#先生成条件过滤数据
filter_conditions = ''
for k,v in admin_class.filter_conditions.items():
filter_conditions += "&"+k+'='+v; #再生成排序条件
if admin_class.sort_conditions:
filter_conditions += "&o="+list(admin_class.sort_conditions.values())[] #在生成搜索条件
if admin_class.search_conditions:
filter_conditions += "&_q="+admin_class.search_conditions page_str = '<ul class="pagination">'
if queryset.has_previous():
page_str += '<li><a href="?_p=%d%s">«</a></li>'%(queryset.previous_page_number(),filter_conditions)
for i in page_html:
if i == queryset.number:
page_str += '<li class="active"><a href="?_p=%d%s">%d</a></li>'%(i,filter_conditions,i)
else:
page_str += '<li><a href="?_p=%d%s">%d</a></li>'%(i,filter_conditions,i)
if queryset.has_next():
page_str += '<li><a href="?_p=%d%s">»</a></li>'%(queryset.next_page_number(),filter_conditions) return mark_safe(page_str)
build_page_row模板函数
二:对各个字段筛选(对kingadmin中list_filter字段进行筛选)
1:前端显示
<div class="form-group">
<form method="get" class="form-inline">
{% for field in admin_class.list_filter %}
{% build_filter_row field admin_class %}
{% endfor %}
{% build_order_filter admin_class %}
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
build_filter_row模板函数去获取数据生成标签
2.模板函数去定制标签,在form表单中加入隐藏标签(表示排序和搜索条件)
@register.simple_tag
def build_filter_row(field,admin_class):
model = admin_class.model
field_obj = model._meta.get_field(field)
filter_conditions = admin_class.filter_conditions
label = """<label class="control-label">%s</label>"""%field
try:
select = "<div class='col-md-3'>%s:<select class='form-control' name='%s'>"%(label,field)
data_list = field_obj.get_choices(field) #可以获取choices选项和外键
except AttributeError:
if field_obj.get_internal_type() in ("DateField","DateTimeField"):
field = "%s__gte"%field
select = "<div class='col-md-2'>%s:<select class='form-control' name='%s'>"%(label,field)
time_now = datetime.now()
time_list = [
["", "---------"],
[time_now, "today"],
[time_now - timedelta(), "七天内"],
[time_now.replace(day=), "本月"],
[time_now - timedelta(), "三个月内"],
[time_now.replace(month=, day=), "今年内"],
['', "ALL"]
] def turn_date(date_list):
date_obj, date_str = date_list
if type(date_obj) is datetime:
date_obj = date_obj.strftime("%Y-%m-%d")
return (date_obj,date_str) data_list = map(turn_date,time_list)
else:
select = "<div class='col-md-3'>%s:<select class='form-control' name='%s'>"%(label,field)
data_list = list(model.objects.values_list("id",field)) for item in data_list:
if str(item[]) == filter_conditions.get(field,None):
option = "<option value='%s' selected>" % str(item[])
else:
option = "<option value='%s'>"%str(item[])
option += item[]
option += "</option>"
select += option select += "</select></div>" return mark_safe(select)
build_filter_row模板函数对日期筛选进行自定义,外键或者choices字段使用字段对象获取数据,对于其他的字段使用model获取所有的值,组成select框进行筛选
3.在views中将url中的各个条件,放置到admin_class中,方便模板标签的使用
@login_required
def table_obj_list(request,app_name,model_name):
'''取出指定的数据返给前端''' admin_class = site.enabled_admins[app_name][model_name] model_class = admin_class.model
querysets = model_class.objects.all() #所有数据 #搜索后的数据
querysets,search_conditions = get_search_result(request,querysets,admin_class)
admin_class.search_conditions = search_conditions querysets,filter_conditions = get_filter_result(request,querysets) #过滤条件后的数据
admin_class.filter_conditions = filter_conditions #也可以传值给前端,但是这样也不错 querysets, sort_conditions = get_order_result(request,querysets,admin_class)
admin_class.sort_conditions = sort_conditions current_page = request.GET.get('_p',)
paginator = CustomPagimator.CustomPagimator(current_page=current_page, max_page_num=,object_list=querysets,per_page=) # 传入总数据和每页显示的数据
try:
filter_data = paginator.page(current_page)
except PageNotAnInteger:
filter_data = paginator.page()
except EmptyPage:
filter_data = paginator.page(paginator.num_pages) # num_pages数总页数,最后一页 page_html = paginator.page_num_range() return render(request,"kingadmin/table_obj_list.html",{"queryset":filter_data,'admin_class':admin_class,"page_html":page_html})
注意:我在views中将各个url条件放在admin_class中,方便查询对比(也可以放在变量中分发出来)
4.在views中的url数据获取时将其他_q搜索,o排序,_p分页数据过滤,获取所有数据
def get_filter_result(request,querysets):
filter_conditions = {}
for k,v in request.GET.items():
if k in ("_p","o","_q"):continue
if v:
filter_conditions[k] = v
return querysets.filter(**filter_conditions),filter_conditions
get_filter_result过滤条件,获取querysets数据
推文:python---Django中模型类中Meta元对象了解,可以知道数据模型中的字段对象或者其他所需要的内容
三:对各个字段进行排序(list_display)
1.前端传递排序数据,对于table中的th加上url
<thead>
<tr>
{% build_title_row admin_class %}
</tr>
</thead>
使用模板函数处理
2.模板函数build_title_row 去生成标签
@register.simple_tag
def build_title_row(admin_class):
#先生成过滤条件
filter_conditions = ''
for k, v in admin_class.filter_conditions.items():
filter_conditions += "&" + k + '=' + v; #再生成搜索条件
if admin_class.search_conditions:
filter_conditions += "&_q="+admin_class.search_conditions icon = """<span class="glyphicon glyphicon-triangle-%s" aria-hidden="true"></span>"""
title = ''
th = "<th><a href='?o=%s%s'>%s%s</a></th>" try:
sort_cond = list(admin_class.sort_conditions.keys())[]
sort_val = list(admin_class.sort_conditions.values())[]
except IndexError:
sort_cond = None
sort_val = None
for counter,field in enumerate(admin_class.list_display):
if field == sort_cond:
if sort_val.startswith("-"):
title += th%(sort_val.strip("-"),filter_conditions,field,icon%"top")
else:
title += th%("-"+sort_val,filter_conditions,field,icon%"bottom")
else:
title += th%(counter,filter_conditions,field,"") return mark_safe(title)
build_title_row中先将过滤和搜索条件组合,再生成排序url(符号倒序,数字代表在admin_class中list_display字段中的索引顺序)
3.views中对我们获取的所有数据,根据前端传递的排序方法进行排序处理
def get_order_result(request,querysets,admin_class):
order_index = request.GET.get("o")
sort_conditions = {}
if order_index:
index = abs(int(order_index))
order_by = admin_class.list_display[index]
if order_index.startswith("-"):
querysets = querysets.order_by("-"+order_by)
else:
querysets = querysets.order_by(order_by)
sort_conditions[order_by] = order_index return querysets,sort_conditions
get_order_result获取排序结果
四:对字段进行搜索(search_fields)
1.前端生成标签时,form表单中需要一起传递其他条件的input隐藏框
<form class="form-horizontal" method="get" role="form">
<div class="form-group">
<div class="col-sm-8">
<input class="form-control" id="focusedInput" name="_q" placeholder="输入数据开始搜索...." value="{{ admin_class.search_conditions }}" type="text">
{% build_search_filter admin_class %}
</div>
<div class="col-sm-2">
<input type="submit" class="btn btn-primary" value="Search">
</div>
</div>
</form>
前端数据form
2.使用模板函数生成标签
@register.simple_tag
def build_search_filter(admin_class):
'''向搜索框中添加入过滤条件和排序条件'''
# 先生成过滤条件
inp = ""
for k, v in admin_class.filter_conditions.items():
inp += """<input type="hidden" name="%s" value="%s"/>"""%(k,v) # 再生成排序条件
if admin_class.sort_conditions:
inp += """<input type="hidden" name="%s" value="%s"/>""" % ("o", list(admin_class.sort_conditions.values())[]) return mark_safe(inp)
build_search_filter模板函数,生成input标签(含有各个条件)
3.后端处理搜索条件,生成querysets数据
def get_search_result(request,querysets,admin_class):
search_val = request.GET.get("_q")
if search_val:
q = Q()
q.connector = "OR"
for field in admin_class.search_fields:
q.children.append(("%s__contains"%field,search_val))
querysets = querysets.filter(q)
return querysets,search_val
views处理搜索字段,注意使用OR,需要用到Q方法
Day4:动态生成任意表的CURD
1.如何在前端动态生成标签?使用form验证可以针对model生成所有的字段控件
推文:python---django中form组件(2)自定制属性以及表单的各种验证,以及数据源的实时更新,以及和数据库关联使用ModelForm和元类
from django.forms import ModelForm
from repository import models class CustomerForm(ModelForm):
class Meta:
model = models.CustumerInfo #将表与元类中的数据关联
fields = "__all__" def __new__(cls, *args, **kwargs):
print(cls.base_fields)
#OrderedDict([('name', <django.forms.fields.CharField object at 0x00000000047FE9E8>), ('contact_type', <django.forms.fields.TypedChoiceField object at 0x00000000047FEBA8>), ('contact', <django.forms.fields.CharField object at 0x00000000047FECC0>), ('source', <django.forms.fields.TypedChoiceField object at 0x00000000047FEE10>), ('referral_from', <django.forms.models.ModelChoiceField object at 0x00000000047FEEF0>), ('consult_courses', <django.forms.models.ModelMultipleChoiceField object at 0x000000000480B048>), ('consult_content', <django.forms.fields.CharField object at 0x000000000480B0B8>), ('status', <django.forms.fields.TypedChoiceField object at 0x000000000480B208>), ('consultant', <django.forms.models.ModelChoiceField object at 0x000000000480B2E8>)])
#这张表中的所有字段对象
for field_name,field_obj in dict(cls.base_fields).items():
field_obj.widget.attrs.update({'class':"form-control"}) return ModelForm.__new__(cls)
实验:使用固定的数据模型去生成对应的form验证类,可以用来在前端之间生成控件
2.如何针对每张表动态生成一个Form类?需要用到type方法去动态生成类
from django.forms import ModelForm
from repository import models def create_dynamic_model_form(admin_class,form_add = False):
'''动态生成modelform,form_add表示是添加数据生成form类。添加和编辑有所区别'''
class Meta:
model = admin_class.model # 将表与元类中的数据关联
fields = "__all__"
if not form_add:
exclude = admin_class.readonly_fields
admin_class.add_flag = False
else:
exclude = []
admin_class.add_flag = True def __new__(cls, *args, **kwargs):
#OrderedDict([('name', <django.forms.fields.CharField object at 0x00000000047FE9E8>), ('contact_type', <django.forms.fields.TypedChoiceField object at 0x00000000047FEBA8>), ('contact', <django.forms.fields.CharField object at 0x00000000047FECC0>), ('source', <django.forms.fields.TypedChoiceField object at 0x00000000047FEE10>), ('referral_from', <django.forms.models.ModelChoiceField object at 0x00000000047FEEF0>), ('consult_courses', <django.forms.models.ModelMultipleChoiceField object at 0x000000000480B048>), ('consult_content', <django.forms.fields.CharField object at 0x000000000480B0B8>), ('status', <django.forms.fields.TypedChoiceField object at 0x000000000480B208>), ('consultant', <django.forms.models.ModelChoiceField object at 0x000000000480B2E8>)])
#这张表中的所有字段对象
for field_name,field_obj in dict(cls.base_fields).items():
field_obj.widget.attrs.update({'class':"form-control"})
# if field_name in admin_class.readonly_fields:
# field_obj.widget.attrs.update({'disabled':'true'})
return ModelForm.__new__(cls) dynamic_form = type("DynamicModelForm",(ModelForm,),{'Meta':Meta,"__new__":__new__}) return dynamic_form
form_handle.py中去创建方法,动态创建类
3.在修改页面中动态创建Form类(需要传递原来数据)
@login_required
def table_obj_change(request,app_name,model_name,obj_id):
'''kingadmin数据修改页面'''
admin_class = site.enabled_admins[app_name][model_name] #动态生成form表单
model_form = form_handle.create_dynamic_model_form(admin_class)
obj = admin_class.model.objects.get(id=obj_id) if request.method == "GET":
form_obj = model_form(instance=obj)
elif request.method == "POST":
form_obj = model_form(instance=obj,data=request.POST)
if form_obj.is_valid():
form_obj.save()
return redirect("/kingadmin/%s/%s"%(app_name,model_name)) return render(request,"kingadmin/table_obj_change.html",locals())
table_obj_change方法去创建form类,传递到前端
url(r"^(\w+)/(\w+)/(\d+)/change/$", views.table_obj_change, name="table_obj_change"),
url中对于修改的匹配
4.在添加页面动态创建Form类
@login_required
def table_obj_add(request,app_name,model_name):
admin_class = site.enabled_admins[app_name][model_name] model_form = form_handle.create_dynamic_model_form(admin_class,form_add = True) if request.method == "GET":
form_obj = model_form()
elif request.method == "POST":
form_obj = model_form(data=request.POST)
if form_obj.is_valid:
form_obj.save()
return redirect("/kingadmin/%s/%s" % (app_name, model_name)) return render(request,"kingadmin/table_obj_add.html",locals())
table_obj_add方法
url(r"^(\w+)/(\w+)/add/$", views.table_obj_add, name="table_obj_add")
url.py中对于添加的匹配
添加的url
5.修改和添加的HTML和公共部分
{% extends "kingadmin/index.html" %}
{% load my_func %} {% block right-content-container %}
<h2 class="page-header">{% get_model_name admin_class %}</h2>
<h3 class="page-header">添加{% get_model_name admin_class %}</h3>
<div>
add
{% include "kingadmin/table_obj_change_component.html" %}
</div>
{% endblock %}
table_obj_add.html
{% extends "kingadmin/index.html" %}
{% load my_func %} {% block right-content-container %}
<h2 class="page-header">{% get_model_name admin_class %}</h2>
<h3 class="page-header">修改{{ form_obj.instance }}</h3>
<div>
change
{% include "kingadmin/table_obj_change_component.html" %}
</div>
{% endblock %}
table_obj_change.html
{% load my_func %} <form class="form-horizontal" onsubmit="ChangeSelStatus(this);" method="post" role="form">
{% csrf_token %}
{% for field in form_obj %}
<div class="form-group">
<label class="col-sm-2 control-label">{{ field.label }}</label>
{% if field.name in admin_class.filter_horizontal %}
<div class="col-sm-5">
<select class="form-control" name="" id="id_{{ field.name }}_from" multiple>
{% get_rel_m2m_val field.name admin_class as rel_querysets %}
{% for rel_obj in rel_querysets %}
{% get_rel_m2m_sel form_obj field.name rel_obj as sel_flag%}
{% if not sel_flag %}
<option ondblclick="MoveEleToOpp(this,'{{ field.name }}');" value="{{ rel_obj.id }}">{{ rel_obj }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="col-sm-5">
<select tag="submit" class="form-control" name="{{ field.name }}" id="id_{{ field.name }}_to" multiple>
{% get_rel_m2m_val field.name admin_class as rel_querysets %}
{% for rel_obj in rel_querysets %}
{% get_rel_m2m_sel form_obj field.name rel_obj as sel_flag%}
{% if sel_flag %}
<option ondblclick="MoveEleToOpp(this,'{{ field.name }}');" value="{{ rel_obj.id }}">{{ rel_obj }}</option>
{% endif %}
{% endfor %}
</select>
</div>
{% else %}
<div class="col-sm-10">
{{ field }}
<span style="color: red;">
{{ field.errors. }}
</span>
</div>
{% endif %}
</div>
{% endfor %}
{% if not admin_class.add_flag %}
{% for field_name in admin_class.readonly_fields %}
<div class="form-group">
<label class="col-sm-2 control-label">{{ field_name }}</label>
<div class="col-sm-10">
<p class="text-left">{% get_field_value_p field_name form_obj %}</p>
</div>
</div>
{% endfor %}
{% endif %}
<div class="col-lg-offset-11 col-sm-1">
<input type="submit" class="btn btn-success" value="Save">
</div>
</form> {% block extra-js %}
<script>
function MoveEleToOpp(ths,field_name) {
if($(ths).parent().prop("id") == "id_"+field_name+"_from"){
var new_target = "id_"+field_name+"_to";
}else{
var new_target = "id_"+field_name+"_from";
}
$("#"+new_target).append(ths);
} function ChangeSelStatus(ths){
$("select[tag] option").prop("selected",true);
}
</script>
{% endblock %}
table_obj_change_component.html公共部分
6.处理在add和change中对于readonly_fileds字段的不同
{% if not admin_class.add_flag %}
{% for field_name in admin_class.readonly_fields %}
<div class="form-group">
<label class="col-sm-2 control-label">{{ field_name }}</label>
<div class="col-sm-10">
<p class="text-left">{% get_field_value_p field_name form_obj %}</p>
</div>
</div>
{% endfor %}
{% endif %}
在动态生成ModelForm修改,并且向admin_class.add_flag加入标识,前端进行判别,决定是否去显示只读字段
7.对于filter_horizontal字段我们在模板函数中进行获取所有的值,并且判断是否显示在哪一个select标签中
<div class="col-sm-5">
<select class="form-control" name="" id="id_{{ field.name }}_from" multiple>
{% get_rel_m2m_val field.name admin_class as rel_querysets %}
{% for rel_obj in rel_querysets %}
{% get_rel_m2m_sel form_obj field.name rel_obj as sel_flag%}
{% if not sel_flag %}
<option ondblclick="MoveEleToOpp(this,'{{ field.name }}');" value="{{ rel_obj.id }}">{{ rel_obj }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="col-sm-5">
<select tag="submit" class="form-control" name="{{ field.name }}" id="id_{{ field.name }}_to" multiple>
{% get_rel_m2m_val field.name admin_class as rel_querysets %}
{% for rel_obj in rel_querysets %}
{% get_rel_m2m_sel form_obj field.name rel_obj as sel_flag%}
{% if sel_flag %}
<option ondblclick="MoveEleToOpp(this,'{{ field.name }}');" value="{{ rel_obj.id }}">{{ rel_obj }}</option>
{% endif %}
{% endfor %}
</select>
</div>
前端对filter_horizontal进行判别,针对两个select都进行判别,一个放置选中一个放置未选中
@register.simple_tag
def get_rel_m2m_val(field_name,admin_class):
field_obj = admin_class.model._meta.get_field(field_name)
rel_model = field_obj.related_model
querysets = rel_model.objects.all()
return querysets
get_rel_m2m_val模板函数获取关联对象得所有值,用到字段对象的related_model属性获取关联对象
@register.simple_tag
def get_rel_m2m_sel(form_obj,field_name,rel_obj):
try:
querysets = getattr(form_obj.instance, field_name).all()
if rel_obj in querysets:
return True
return False
except TypeError:
return False
get_rel_m2m_sel方法判断是否数据被选中,返回True选中,放在第二个select标签,放在未选中,放在第一个select标签
8.实现js双击option,在两个select之间跳转
<option ondblclick="MoveEleToOpp(this,'{{ field.name }}');" value="{{ rel_obj.id }}">{{ rel_obj }}</option>
为两个select标签绑定同一个MoveEleToOpp方法
function MoveEleToOpp(ths,field_name) {
if($(ths).parent().prop("id") == "id_"+field_name+"_from"){
var new_target = "id_"+field_name+"_to";
}else{
var new_target = "id_"+field_name+"_from";
}
$("#"+new_target).append(ths);
}
MoveEleToOpp方法实现:通过判断父标签select的id,将当前option转移append到对方的select中
9.实现在点击保存时,form表单自动将右侧select中的数据全部选中。注意:加上name为select标签,name="字段名"
<form class="form-horizontal" onsubmit="ChangeSelStatus(this);" method="post" role="form">
为form表单绑定方法ChangeSelStatus
function ChangeSelStatus(ths){
$("select[tag] option").prop("selected",true);
}
ChangeSelStatus实现
10.为filter_horizontal完善功能,添加全选,全部移除
<p><a onclick="ChooseAll(this,'{{ field.name }}')">ChooseAll</a></p>
<p><a onclick="ChooseAll(this,'{{ field.name }}')">RemoveAll</a></p>
前端HTML
function ChooseAll(ths,field_name) {
var sel_id = $(ths).parent().prev().prop("id")
if(sel_id == "id_"+field_name+"_from"){
var new_target = "id_"+field_name+"_to";
}else{
var new_target = "id_"+field_name+"_from";
} $("#"+sel_id).find("option").each(function(){
$("#"+new_target).append(this);
})
}
ChooseAll函数js代码
Day5:删除功能开发和action方法实现
1.删除功能开发
{% extends "kingadmin/index.html" %}
{% load my_func %} {% block right-content-container %}
<h2 class="page-header">{% get_model_name admin_class %}</h2>
<h3 class="page-header alert-danger">注意:以下与{{ obj }}想关联的数据都将被删除</h3>
<div>
{% get_del_obj obj app_name model_name as res_del %}
{{ res_del|safe }}
</div> <form method="post">
{% csrf_token %}
<input type="submit" class="btn btn-danger" value="确认删除">
<a href="/kingadmin/{{ app_name }}/{{ model_name }}/{{ obj.id }}/change" class="btn btn-primary">返回</a>
</form>
{% endblock %}
前端HTML代码
@register.simple_tag
def get_del_obj(model_obj, app_name, model_name):
all_rel = model_obj._meta.related_objects
ul = "<ul>"
ul += "<li>%s:<a href='/kingadmin/%s/%s/%s/change'>%s</a></li>"%(model_obj._meta.label.rsplit('.',maxsplit=)[],app_name,model_name,model_obj.id,model_obj)
for rel_field in all_rel:
sub_querysets = getattr(model_obj,rel_field.name+"_set").all()
if not sub_querysets: #若是关联但是没有数据,则不显示
continue
if rel_field.get_internal_type() == "ManyToManyField":
ul += "<li><ul>"
ul += "<li>%s</li>"%rel_field.name
for i in sub_querysets:
ul += "<li><ul><li>%s</li></ul></li>" % i
ul += "</ul></li>"
else:
for sub_item in sub_querysets:
sub_res = get_del_obj(sub_item,sub_item._meta.app_label,sub_item._meta.model_name)
ul += "<li>%s</li>"%(sub_res) ul += "</ul>" return ul
模板函数,去递归生成标签
@login_required
def table_obj_delete(request,app_name,model_name,obj_id):
admin_class = site.enabled_admins[app_name][model_name]
obj = admin_class.model.objects.get(id=obj_id) if request.method == "POST":
obj.delete()
return redirect("/kingadmin/{app_name}/{model_name}".format(app_name=app_name,model_name=model_name)) return render(request, "kingadmin/table_obj_delete.html", locals())
views后台删除代码
2.action字段功能完善
class CustomerAdmin(BaseKingAdmin):
list_display = ['id','name','contact_type','contact','source','consult_content','consultant','status','date']
list_filter = ['source','consultant','status','date']
search_fields = ['contact','consultant__name','name']
readonly_fields = ['status','contact']
filter_horizontal = ['consult_courses',]
action = ['change_status',]
def change_status(self,request,querysets):
querysets.update(status=)
kingadmin.py中放置action字段,包含有自定义方法
from django.shortcuts import render class BaseKingAdmin(object):
list_display = []
list_filter = []
search_fields = []
readonly_fields = []
filter_horizontal = []
action = []
def_action = ['delete_selected_objs'] def delete_selected_objs(self,request,querysets): return render(request,'kingadmin/table_obj_delete.html') def __init__(self):
self.action.extend(self.def_action)
admin_base.py中需要去设置action默认数据
(1)设置form表单布局
<div>
<form action="" method="post" class="form-inline" onsubmit="return Raw_input_action(this);">
<div class="col-lg-3">
{% csrf_token %}
<label class="control-label">Action:</label>
<select name="action" id="action" class="form-control">
<option value="">---------</option>
{% for action in admin_class.action %}
<option value="{{ forloop.counter0 }}">{{ action }}</option>
{% endfor %}
</select>
</div>
<div class="col-sm-2">
<input type="submit" value="Go" class="btn btn-primary">
</div>
</form>
</div>
form表单
(2)设置复选框完成全选功能
<div>
<table class="table table-striped">
<thead>
<tr>
<th><input type="checkbox" id="check_All" onclick="CheckAll(this)"/></th>
{% build_title_row admin_class %}
</tr>
</thead>
<tbody>
{% for item in queryset %}
<tr>
<td><input type="checkbox" name="check_row" value="{{ item.id }}"></td>
{% build_table_row item admin_class %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
HTML代码
function CheckAll(ths) {
if($(ths).prop("checked")){
$("input[name=check_row]").prop("checked",true)
}else{
$("input[name=check_row]").prop("checked",false)
}
}
CheckAll方法js完成全选
(3)提交表单前先生成隐藏表单去获取数据集
function Raw_input_action(ths) {
if($("#action").val() == ""){
alert("请选择正确的action");
return false;
}
var select_ids = [];
$("input[name=check_row]").filter(":checked").each(function(){
select_ids.push($(this).val());
})
if(select_ids.length == ){
alert("请选择正确的项目");
return false;
}
new_ele = "<input type='hidden' name='select_ids' value='"+JSON.stringify(select_ids)+"'/>";
$(ths).append(new_ele);
return true;
}
Raw_input_action方法生成一个人input标签
(4)传递到后端进行处理
@login_required
def table_obj_list(request,app_name,model_name):
'''取出指定的数据返给前端''' admin_class = site.enabled_admins[app_name][model_name] model_class = admin_class.model
querysets = model_class.objects.all() #所有数据 if request.method == "POST":
selected_action = request.POST.get("action")
selected_ids = request.POST.get("select_ids")
getattr(admin_class,admin_class.action[int(selected_action)])(request,querysets.filter(id__in=json.loads(selected_ids)))
在table_obj_list方法添加上post方法即可
3.处理action中的默认行为delete批量删除
(1)提交的url不是上面的table_obj_delete,而是本页面和change_status一起作为action传递入当前url
def delete_selected_objs(self,request,querysets):
return render(request,'kingadmin/table_obj_delete.html',{"admin_class":self,'obj':querysets})
delete_selected_objs的action方法
(2)获取delete_selected_objs在table_obj_list方法中返回
if request.method == "POST":
if request.POST.get("delete_ids"):
'''如果是删除做post传递过来的话另外处理,否则就是action操作'''
del_id = json.loads(request.POST.get("delete_ids"))
admin_class.model.objects.filter(id__in=del_id).delete()
return redirect("/kingadmin/%s/%s"%(app_name,model_name))
else:
selected_action = request.POST.get("action")
selected_ids = request.POST.get("select_ids")
res = getattr(admin_class,admin_class.action[int(selected_action)])(request,querysets.filter(id__in=json.loads(selected_ids)))
#如果返回值,代表是返回render指向delete页面
if res:
return res
table_obj_list中对于post的处理
若是执行完action方法后没有返回值则是正常执行,如果有返回值,则是代表我们接下来是执行删除操作。需要返回
(3)我们还是调用的上面的table_obj_delete.html页面,但是其中的模板标签函数,是针对一个数据对象,而现在是一个数据集,我们需要再次处理
@register.simple_tag
def get_del_obj(model_objs, app_name, model_name):
ul = "<ul>" try:
iter(model_objs)
except TypeError:
model_objs = [model_objs,] for model_obj in model_objs:
all_rel = model_obj._meta.related_objects
ul += "<li>%s:<a href='/kingadmin/%s/%s/%s/change'>%s</a></li>"%(model_obj._meta.label.rsplit('.',maxsplit=)[],app_name,model_name,model_obj.id,model_obj)
for rel_field in all_rel:
sub_querysets = getattr(model_obj,rel_field.name+"_set").all()
if not sub_querysets: #若是关联但是没有数据,则不显示
continue
if rel_field.get_internal_type() == "ManyToManyField":
ul += "<li><ul>"
ul += "<li>%s</li>"%rel_field.name
for i in sub_querysets:
ul += "<li><ul><li>%s</li></ul></li>" % i
ul += "</ul></li>"
else:
for sub_item in sub_querysets:
sub_res = get_del_obj(sub_item,sub_item._meta.app_label,sub_item._meta.model_name)
ul += "<li>%s</li>"%(sub_res) ul += "</ul>" return ul
简单改变模板函数get_del_obj,将原来单个对象也改写为可迭代
(4)我们提交数据,也不再是table_obj_delete方法,而是table_obj_list方法,所以我们需要传递一个数据代表要删除的数据id集合,同时一个一个标识
<input type="hidden" name="delete_ids" value="{% get_del_objs_id obj %}">
在显示的table_obj_delete.html页面加入隐藏标签,收集所有id集合
@register.simple_tag
def get_del_objs_id(objs):
obj_ser = []
for obj in objs:
obj_ser.append(obj.id)
return json.dumps(obj_ser)
get_del_objs_id模板函数收集所有的数据对象的id,json序列化返回给前端
(5)views页面根据post传递过来的隐藏标签的name,判断是不是执行删除数据操作
if request.method == "POST":
if request.POST.get("delete_ids"):
'''如果是删除做post传递过来的话另外处理,否则就是action操作'''
del_id = json.loads(request.POST.get("delete_ids"))
admin_class.model.objects.filter(id__in=del_id).delete()
return redirect("/kingadmin/%s/%s"%(app_name,model_name))
获取前端input表单名delete_ids,判断是否有数据,来决定是否删除
4.实现面包屑导航
<ol class="breadcrumb">
<li><a href="/kingadmin">CRM</a></li>
<li><a href="/kingadmin/{{ app_name }}">{{ app_name }}</a></li>
<li><a href="/kingadmin/{{ app_name }}/{{ model_name }}">{{ model_name }}</a></li>
<li class="active">{% get_nva_active admin_class %}</li>
</ol>
add页面导航
<ol class="breadcrumb">
<li><a href="/kingadmin">CRM</a></li>
<li><a href="/kingadmin/{{ app_name }}">{{ app_name }}</a></li>
<li><a href="/kingadmin/{{ app_name }}/{{ model_name }}">{{ model_name }}</a></li>
<li class="active">{% get_nva_active admin_class %}</li>
</ol>
list页面导航
<ol class="breadcrumb">
<li><a href="/kingadmin">CRM</a></li>
<li><a href="/kingadmin/{{ app_name }}">{{ app_name }}</a></li>
<li><a href="/kingadmin/{{ app_name }}/{{ model_name }}">{{ model_name }}</a></li>
<li class="active">{{ form_obj.instance }}</li>
</ol>
change页面导航
@register.simple_tag
def get_nva_active(admin_class):
return admin_class.model._meta.verbose_name
get_nva_active模板函数获取对象的中文名
<ol class="breadcrumb">
<li><a href="/kingadmin">CRM</a></li>
<li><a href="/kingadmin/{{ app_name }}">{{ app_name }}</a></li>
<li><a href="/kingadmin/{{ app_name }}/{{ model_name }}">{{ model_name }}</a></li>
<li class="active">{% get_nav_del obj %}</li>
</ol>
delete页面导航
@register.simple_tag
def get_nav_del(model_objs):
obj_names = []
try:
iter(model_objs)
except TypeError:
model_objs = [model_objs,]
for model in model_objs:
obj_names.append("%s"%model)
return '|'.join(obj_names)
get_nav_del模板函数组合对象名
5.左侧菜单状态
{% for menu in role.menus.select_related %}
{% if menu.url_type == %}
{% if menu.url_name == request.path %}
<li class="active"><a href="{{ menu.url_name }}">{{ menu.name }}</a></li>
{% else %}
<li><a href="{{ menu.url_name }}">{{ menu.name }}</a></li>
{% endif %}
{% else %}
{% url menu.url_name as url_name %}
{% if url_name == request.path %}
<li class="active"><a href="{{ url_name }}">{{ menu.name }}</a></li>
{% else %}
<li><a href="{{ url_name }}">{{ menu.name }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
index页面在生成url时,对其进行判断。要分辨动态和绝对
Day6:学员报名流程开发
class ContractTemplate(models.Model):
'''合同模板表'''
name = models.CharField(max_length=)
content = models.TextField()
date = models.DateField(auto_now_add=True) def __str__(self):
return self.name class StudentEnrollment(models.Model):
'''学员报名表:这里还没有变成学员,适合客户表相关联'''
customer = models.ForeignKey("CustumerInfo")
class_grade = models.ForeignKey("ClassList")
consultant = models.ForeignKey("UserProfile") #对应的销售
contract_agreed = models.BooleanField(default=False) #是否同意合同
contract_signed_date = models.DateTimeField(blank=True,null=True) #同意合同未到时间
contract_approved = models.BooleanField(default=False) #审核是否完毕
contract_approved_date = models.DateTimeField(blank=True,null=True) class Meta:
unique_together = ("customer","class_grade") def __str__(self):
return "%s"%self.customer class PaymentRecord(models.Model):
'''存储学员缴费记录'''
enrollment = models.ForeignKey("StudentEnrollment")
payment_type_choice = (
(,"报名费"),
(,"学费"),
(,"退费"),
)
payment_type = models.SmallIntegerField(choices=payment_type_choice)
amount = models.IntegerField("费用",default=)
consultant = models.ForeignKey("UserProfile") #费用缴给谁
date = models.DateTimeField(auto_now_add=True) def __str__(self):
return "%s"%self.enrollment
新增3张表:学员注册表,合同表(和班级关联),缴费记录表
一:销售为想报名的学员提供链接
@login_required
def Student_encroll(request):
ClassList = models.ClassList.objects.all()
StuEncroll = models.CustumerInfo.objects.filter(consultant__user=request.user).all() if request.method == "POST":
student = request.POST.get("student")
classlist = request.POST.get("classlist") try:
StuEncObj = models.StudentEnrollment.objects.create(
customer_id=student,
class_grade_id=classlist,
consultant=request.user.userprofile
)
except IntegrityError:
StuEncObj = models.StudentEnrollment.objects.get(
customer_id=student,
class_grade_id=classlist,
consultant=request.user.userprofile
)
if StuEncObj.contract_agreed:
return redirect("encrollment/%s/contract_audit.html"%StuEncObj.id)
else:
return HttpResponse("等待学员身份验证") link = "http://127.0.0.1:8000/sale/encrollment/%s.html"%StuEncObj.id
return render(request,"sale/stu_encroll.html",locals())
Student_encroll学员注册链接获取
二:学员获取链接,进行填写信息,查阅合同,同意并上传证件信息
def enrollment(request,id):
'''学员在线报名'''
enrollment_obj = models.StudentEnrollment.objects.get(id=id) if enrollment_obj.contract_agreed and not enrollment_obj.contract_approved:
return HttpResponse("信息正在审核当中") if enrollment_obj.contract_approved:
return HttpResponse("审核通过,去进行缴费操作") if request.method == "GET":
forms = CustomerForm(instance=enrollment_obj.customer)
elif request.method == "POST":
if not request.POST.get("contract_agreed"):
return HttpResponse("信息提交失败,请先阅读合同")
cus_dir = os.path.join(conf.settings.SALE_FILE_UPLOAD_DIR, id)
if len(os.listdir(cus_dir)) == :
return HttpResponse("信息提交失败,请先上传证件信息") forms = CustomerForm(instance=enrollment_obj.customer,data=request.POST)
if forms.is_valid():
forms.save()
enrollment_obj.contract_agreed = True
enrollment_obj.contract_signed_date = datetime.datetime.now()
enrollment_obj.save()
return HttpResponse("信息提交成功")
file_dir = os.path.join(conf.settings.SALE_FILE_UPLOAD_DIR,id)
if os.path.isdir(file_dir):
file_info = os.listdir(file_dir) return render(request,"sale/enrollment.html",locals())
enrollment学员在线报名
{% extends "index.html" %} {% block extra-link %}
<link rel="stylesheet" href="/static/plugins/dropzone/dropzone.css">
{% endblock %} {% block body %}
<h1 class="page-header">学员报名</h1> <div class="col-lg-offset-1 col-lg-10">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">学员在线报名</h3>
</div>
<div class="panel-body">
<form class="form-horizontal" onsubmit="return PrevSubmit(this);" method="post" role="form">
{% csrf_token %}
{% for field in forms %}
<div class="form-group col-sm-6">
<label class="col-sm-4 control-label">{{ field.label }}</label>
<div class="col-sm-8">
{{ field }}
<span style="color: red;">{{ field.errors. }}</span>
</div>
</div>
{% endfor %}
<div class="form-group col-sm-6">
<label class="col-sm-4 control-label">报名班级</label>
<div class="col-sm-8">
<p class="form-control-static">{{ enrollment_obj.class_grade }}</p>
</div>
</div>
<div class="form-group col-sm-6">
<label class="col-sm-4 control-label">学费</label>
<div class="col-sm-8">
<p class="form-control-static">{{ enrollment_obj.class_grade.couser.price }}</p>
</div>
</div>
<div class="col-sm-12">
<pre style="height: 400px">
{{ enrollment_obj.class_grade.contract_template.content }}
</pre>
<div>
<div class="form-inline">
<label class="col-sm-6 control-label" style="padding-top: 0px">是否同意以上合同</label>
<input type="checkbox" value="" name="contract_agreed">
</div>
</div>
</div>
<div class="form-group col-sm-6">
<div class="col-sm-8">
<input type="submit" class="btn btn-success" value="提交">
</div>
</div>
</form>
<div class="col-sm-12">
<ul id="file_ul" style="list-style: none">
<label class="control-label" style="padding-top: 0px;">已上传的文件目录</label>
{% for file in file_info %}
<li>{{ file }}</li>
{% endfor %}
</ul>
<form action="{% url 'enrollment_fileupload' enrollment_obj.id %}" id="myAwesomeDropzone" class="dropzone">
<div class="fallback">
<input name="file" type="file" multiple />
</div>
</form>
</div>
</div> </div>
</div> {% endblock %} {% block extra-js %}
<script src="/static/plugins/dropzone/dropzone.js"></script>
<script>
$(function () {
Dropzone.options.myAwesomeDropzone = {
paramName: "file", // The name that will be used to transfer the file
maxFilesize: , // MB
maxFiles:,
parallelChunkUploads:true,
accept: function(file, done) {
if (file.name == "justinbieber.jpg") {
done("Naha, you don't.");
}
else { done(); }
},
init: function() {
this.on("success", function(file,respone) {
/* Maybe display some more file information on your page */
var rep = JSON.parse(respone)
if(!rep.status){
alert(rep.message);
return;
}else{
var li = "<li>"+file.name+"</li>";
$("#file_ul").append(li);
}
});
}
};
}); function PrevSubmit(ths){
if(!$($(ths).find(":checkbox[name=contract_agreed]")[]).prop("checked")){
alert("请先阅读合同");
return false;
} if($("#file_ul").find("li").length==){
alert("请先上传证件信息");
return false;
} $(ths).find(":disabled").removeAttr("disabled")
return true
}
</script> {% endblock %}
erollment.html报名页面,含有Dropzone使用
@csrf_exempt
def enrollment_fileupload(request,encrollment_obj_id):
cus_dir = os.path.join(conf.settings.SALE_FILE_UPLOAD_DIR,encrollment_obj_id)
if not os.path.isdir(cus_dir):
os.makedirs(cus_dir) status = {
'status':True,
"message":None
}
print(request.FILES) #需要去接收文件,前端状态才会是true
if len(os.listdir(cus_dir)) >= :
status['status'] = False
status['message'] = "文件超出上传个数"
return HttpResponse(json.dumps(status)) file_obj = request.FILES.get("file") with open(os.path.join(cus_dir,file_obj.name),"wb") as fp:
for chunks in file_obj.chunks():
fp.write(chunks) return HttpResponse(json.dumps(status))
enrollment_fileupload处理Dropzone文件传输
三:销售审核学员注册信息,审核通过,为其生成账号(密码需要使用Django模块加密),发送邮件
from django.contrib.auth.hashers import make_password #用于生成密码
@login_required
def contract_audit(request,id):
'''合同审查'''
enrollment_obj = models.StudentEnrollment.objects.get(id=id) if not enrollment_obj.contract_agreed:
return redirect("/sale/EncrollLink.html") if enrollment_obj.contract_approved:
return HttpResponse("审核通过,等待缴费") if request.method == "GET":
forms = CustomerForm(instance=enrollment_obj.customer)
contract_forms = ContractForm(instance=enrollment_obj)
elif request.method == "POST":
forms = CustomerForm(instance=enrollment_obj.customer,data=request.POST)
contract_forms = ContractForm(instance=enrollment_obj,data=request.POST)
if forms.is_valid() and contract_forms.is_valid():
forms.save()
contract_forms.save()
enrollment_obj.contract_approved_date = datetime.datetime.now()
enrollment_obj.save() try:
stu_obj = enrollment_obj.customer.student
except Exception:
pass
else:
return HttpResponse(
"用户%s已添加--->账号为:%s" % (enrollment_obj.customer.name, enrollment_obj.customer.student.account.name)) #生成一个随机字符串
account = ''.join(random.sample(string.digits,))
pwd = ''.join(random.sample(string.ascii_letters+string.digits,)) #创建一个账号
user = models.User.objects.create(
username=account,
password=make_password(pwd),
) #创建一个用户
userprofile = models.UserProfile.objects.create(
user=user,
name=enrollment_obj.customer.name,
) #为用户绑定一个角色
userprofile.role.add(models.Role.objects.get(name="Student")) #保存到学员表
models.Student.objects.create(
customer=enrollment_obj.customer,
class_grades=enrollment_obj.class_grade,
account=userprofile,
) send_info = "账号:%s\n密码:%s"%(account,pwd) send_mail(
'成功成为正式学员',
send_info,
'18904190363@sina.cn',
["%s@qq.com"%enrollment_obj.customer.contact,],
fail_silently=False,
) return HttpResponse("审核通过,等待缴费")
print(forms.errors,contract_forms.errors) return render(request,"sale/contract_audit.html",locals())
contract_audit合同审核后生成账号,发送信息
Django自带邮件发送模块
(1)settings中配置
(2)导入模块发送邮件
day7:实现讲师和学员作业发布上传功能(其中由于多使用table浏览数据,可以自定义一个类似于form的基类,去统一实现table显示)
一:讲师功能
(1)可以看出上面多是table显示信息,下面自定义table_form类似于forms
import re class BaseForm(object):
display_list = []
field_tag = {}
attrs = {}
extra_field = [] def __init__(self,model,querysets):
self.instance = model
self.querysets = querysets
self.th = []
self.tr = [] def register(self):
for field in self.display_list:
if field == "self":
self.th.append(self.instance._meta.verbose_name)
continue
field_obj = self.instance._meta.get_field(field)
self.th.append(field_obj.verbose_name) #自定义额外字段
for item in self.extra_field:
for k,v in item.items():
if v.get("verbose_name"):
self.th.append(v.get("verbose_name"))
else:
self.th.append(k) for query in self.querysets:
tds = []
for th in self.display_list:
if th == "self":
field_val = "%s" % query
elif len(self.instance._meta.get_field(th).choices) > :
field_val = "%s"%getattr(query,"get_%s_display"%th)()
else:
field_val = "%s"%getattr(query,th)
if self.field_tag.get(th):
# {"self": {"a": {"href": "127.0.0.1:8000","href": "127.0.0.1:8000"},"a": {"href": "127.0.0.1:8000"}}}
tags = self.field_tag.get(th)
# {"a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}, "a": {"href": "127.0.0.1:8000"}} #前面是内层,后面是外层
for k,v in tags.items():
# "a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}
new_attr = []
for k1,v1 in v.items(): #{"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}
pat = re.compile("\{(.*?)\}")
res = pat.search(v1)
while res:
v1 = pat.sub(str(getattr(query,res.group())),v1,)
res = pat.search(v1) new_attr.append("%s='%s'"%(k1,v1)) #获取到所有的属性放在列表中
field_val = "<%s %s>%s</%s>"%(k," ".join(new_attr),field_val,k)
tds.append(field_val) for item in self.extra_field:
for e_k,e_v in item.items():
if e_v.get("value"):
field_val = "%s" % e_v.get("value")
else:
if hasattr(e_v.get("function"),"__call__"):
field_val = e_v.get("function")(getattr(query,e_v.get("model_attr")),*e_v.get("args",()),**e_v.get("kwargs",{}))
else:
field_val = getattr(getattr(query,e_v.get("model_attr")),e_v.get("function"))(*e_v.get("args",()),**e_v.get("kwargs",{}))
if self.field_tag.get(e_k):
# {"self": {"a": {"href": "127.0.0.1:8000","href": "127.0.0.1:8000"},"a": {"href": "127.0.0.1:8000"}}}
tags = self.field_tag.get(e_k)
# {"a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}, "a": {"href": "127.0.0.1:8000"}} #前面是内层,后面是外层
for k,v in tags.items():
# "a": {"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}
new_attr = []
for k1,v1 in v.items(): #{"href": "127.0.0.1:8000", "href": "127.0.0.1:8000"}
pat = re.compile("\{(.*?)\}")
res = pat.search(v1)
while res:
v1 = pat.sub(str(getattr(query, res.group())), v1, )
res = pat.search(v1)
new_attr.append("%s='%s'"%(k1,v1)) #获取到所有的属性放在列表中
field_val = "<%s %s>%s</%s>"%(k," ".join(new_attr),field_val,k)
tds.append(field_val) self.tr.append(tds) def __str__(self):
cls_attr = []
if len(self.attrs):
for item in self.attrs.items():
cls_attr.append("%s='%s'"%(item[],item[])) tb = "<table %s>"%(" ".join(cls_attr)) tr = "<thead><tr>"
for th_data in self.th:
th = "<th>%s</th>"%th_data
tr += th
tr += "</tr></thead><tbody>" tb += tr for tr_data in self.tr:
tr = "<tr>"
for td_data in tr_data:
td = "<td>%s</td>"%td_data
tr += td
tr += "</tr>"
tb += tr
tb += "</tbody></table>"
return tb
BaseForm实现通过表数据显示table
(2)使用方法
from Teacher.table_form import BaseForm class ClassListForm(BaseForm):
display_list = ["self","branch","class_type","start_date","graduate_date"] #self代表直接显示本条数据__str__
field_tag = {"self":{"a":{"href":"127.0.0.1:8000"},},"study_record":{"a":{"href":"/teacher/classlist/{id}/class_list.html"}},"student":{"a":{"href":"/teacher/classlist/{id}/student_list.html"}}} #在前面的标签会显示在内层,在显示的数据外面加上标签
attrs = {"class":"table table-hover"} #为table设置属性
extra_field = [{"student":{'verbose_name':"学员数量","model_attr":"student_set","function":"count"}},{"study_record":{"verbose_name":"上课记录","value":"上课记录"}}]
#额外自定义字段,若是有值value,会直接输出,否则会去当前实例集self.querysets的每一个实例中去获取相关的数据,使用函数去执行。字符串是调用自己的内置方法,function是调用自定义方法,同时可以使用"args"传递元组,"kwargs":传递字典作为参数 def __init__(self,model,querysets):
super(ClassListForm, self).__init__(model,querysets)
class StudentForm(BaseForm):
display_list = ["self"] #,"couser","semester"
field_tag = {"self":{"a":{"href":"127.0.0.1:8000"},},} #在前面的标签会显示在内层
attrs = {"class":"table table-hover"}
extra_field = [{"student_grade":{'verbose_name':"学员成绩","value":"N/A"}},{"study_status":{"verbose_name":"出勤状况","value":"N/A"}}]
#额外自定义字段,若是有值value,会直接输出,否则会去当前实例集self.querysets的每一个实例中去获取相关的数据,使用函数去执行。字符串是调用自己的内置方法,function是调用自定义方法 def __init__(self,model,querysets):
super(StudentForm, self).__init__(model,querysets)
StudentForm
class CourseForm(BaseForm):
display_list = ["self","title","content","has_homework","homework","date"] #,"couser","semester"
field_tag = {"self":{"a":{"href":"127.0.0.1:8000"},},} #在前面的标签会显示在内层
attrs = {"class":"table table-hover"}
extra_field = []
#额外自定义字段,若是有值value,会直接输出,否则会去当前实例集self.querysets的每一个实例中去获取相关的数据,使用函数去执行。字符串是调用自己的内置方法,function是调用自定义方法 def __init__(self,model,querysets):
super(CourseForm, self).__init__(model,querysets)
CourseForm
(3)在views中调用各个tableform
@login_required
def course_list(request):
course_querysets = models.ClassList.objects.filter(
teachers=request.user.userprofile
) #自定义form,用于table
forms = ClassListForm(models.ClassList,course_querysets)
forms.register() return render(request,"teacher/class_list.html",locals()) @login_required
def student_list(request,c_id):
student_querysets = models.ClassList.objects.filter(
teachers = request.user.userprofile,
id = c_id
).get().student_set.all() forms = StudentForm(models.Student,student_querysets)
forms.register() return render(request,"teacher/student_list.html",locals()) @login_required
def classRec_list(request,c_id):
course_querysets = models.ClassList.objects.filter(
teachers = request.user.userprofile,
id = c_id
).get().courserecord_set.all() forms = CourseForm(models.CourseRecord,course_querysets)
forms.register() return render(request, "teacher/course_list.html", locals())
所有显示table的函数
(4)前端调用{{ forms|safe }},form是定义的tableform变量,实现简单显示页面
{% extends "index.html" %}
{% load my_func %} {% block right-content-container %} <div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">课程列表</h3>
</div>
<div class="panel-body">
{{ forms|safe }}
</div>
</div>
{% endblock %}
class_list.html
{% extends "index.html" %}
{% load my_func %} {% block right-content-container %} <div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">课程记录</h3>
</div>
<div class="panel-body">
{{ forms|safe }}
<a href="{% url 'classRec_add' c_id %}" class="btn btn-primary">添加记录</a>
</div>
</div>
{% endblock %}
course_list.html
{% extends "index.html" %}
{% load my_func %} {% block right-content-container %} <div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">学员列表</h3>
</div>
<div class="panel-body">
{{ forms|safe }}
</div>
</div>
{% endblock %}
student_list.html
(5)添加记录
from django.forms import ModelForm,forms
from repository import models class CustomerForm(ModelForm):
class Meta:
model = models.CustumerInfo #将表与元类中的数据关联
fields = "__all__"
exclude = ["consult_content","status","consult_courses"]
readonly_fields = ['contact_type',"contact","consultant",'referral_from','source'] def __new__(cls, *args, **kwargs):
#OrderedDict([('name', <django.forms.fields.CharField object at 0x00000000047FE9E8>), ('contact_type', <django.forms.fields.TypedChoiceField object at 0x00000000047FEBA8>), ('contact', <django.forms.fields.CharField object at 0x00000000047FECC0>), ('source', <django.forms.fields.TypedChoiceField object at 0x00000000047FEE10>), ('referral_from', <django.forms.models.ModelChoiceField object at 0x00000000047FEEF0>), ('consult_courses', <django.forms.models.ModelMultipleChoiceField object at 0x000000000480B048>), ('consult_content', <django.forms.fields.CharField object at 0x000000000480B0B8>), ('status', <django.forms.fields.TypedChoiceField object at 0x000000000480B208>), ('consultant', <django.forms.models.ModelChoiceField object at 0x000000000480B2E8>)])
#这张表中的所有字段对象
for field_name,field_obj in dict(cls.base_fields).items():
field_obj.widget.attrs.update({'class':"form-control"}) if field_name in cls.Meta.readonly_fields:
field_obj.widget.attrs.update({'disabled': "true"}) return ModelForm.__new__(cls) def clean(self):
if self.errors:
raise forms.ValidationError("Please fix errors before re-submit") if self.instance.id is not None: #这是一个修改的表单,而不是添加
for field_name in self.Meta.readonly_fields:
new_val = self.cleaned_data[field_name]
old_val = getattr(self.instance, field_name)
if new_val != old_val:
self.add_error(field_name, "ReadOnly fileds error: you cannot change %s"%old_val) class CourseRecordForm(ModelForm):
class Meta:
model = models.CourseRecord #将表与元类中的数据关联
fields = "__all__"
# exclude = ["teacher"] def __new__(cls, *args, **kwargs):
#OrderedDict([('name', <django.forms.fields.CharField object at 0x00000000047FE9E8>), ('contact_type', <django.forms.fields.TypedChoiceField object at 0x00000000047FEBA8>), ('contact', <django.forms.fields.CharField object at 0x00000000047FECC0>), ('source', <django.forms.fields.TypedChoiceField object at 0x00000000047FEE10>), ('referral_from', <django.forms.models.ModelChoiceField object at 0x00000000047FEEF0>), ('consult_courses', <django.forms.models.ModelMultipleChoiceField object at 0x000000000480B048>), ('consult_content', <django.forms.fields.CharField object at 0x000000000480B0B8>), ('status', <django.forms.fields.TypedChoiceField object at 0x000000000480B208>), ('consultant', <django.forms.models.ModelChoiceField object at 0x000000000480B2E8>)])
#这张表中的所有字段对象
for field_name,field_obj in dict(cls.base_fields).items():
field_obj.widget.attrs.update({'class':"form-control"}) return ModelForm.__new__(cls)
使用form类,生成控件
@login_required
def classRec_add(request,c_id):
if request.method == "GET":
CRfm = forms.CourseRecordForm()
else:
CRfm = forms.CourseRecordForm(data=request.POST)
CRfm.save()
return redirect("/teacher/classlist/%s/course_list.html"%c_id) return render(request,"teacher/course_add.html",locals())
views调用classRec_add,进行显示和添加数据
{% extends "index.html" %}
{% load my_func %} {% block right-content-container %} <div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">课程记录添加</h3>
</div>
<div class="panel-body">
<form action="" method="post" class="form-group form-horizontal">
{% csrf_token %}
{% for field in CRfm %}
<div class="col-sm-6">
<div class="col-lg-4">
<label for="" class="control-label">{{ field.label }}</label>
</div>
<div class="col-lg-8">
{{ field }}
</div>
</div>
{% endfor %}
<div>
<input type="submit" class="btn btn-primary" value="添加">
</div>
</form>
</div>
</div>
{% endblock %}
前端显示course_add.html
二:学员功能,实现课程显示,作业提交
(一)根据邮件中的账号密码登录
(二)实现查看班级,查看课程记录,提交作业
(1)由于这里也是table多使用,可以继续使用tableform
from Teacher.table_form import BaseForm class ClassListForm(BaseForm):
display_list = ["self","class_type","start_date","graduate_date",] #,"couser","semester"
field_tag = {"self":{"a":{"href":"127.0.0.1:8000"},},'score':{"a":{"href":"127.0.0.1:8080"},},'mng_homework':{"a":{"href":"/student/course.html"}}} #在前面的标签会显示在内层
attrs = {"class":"table table-hover"}
extra_field = [{"score":{"verbose_name":"成绩","value":"成绩排名"}},{"mng_homework":{'verbose_name':"作业管理","value":"作业管理"}},] class ClassRecordForm(BaseForm):
display_list = ["self","title","teacher","content","has_homework","homework"] #,"couser","semester"
field_tag = {"self":{"a":{"href":"127.0.0.1:8000"},},'fin_homework':{"a":{"href":"/student/homework/{id}.html"}}} #在前面的标签会显示在内层
attrs = {"class":"table table-hover"}
extra_field = [{"date":{"verbose_name":"日期","model_attr":"date","function":"strftime","args":("%Y-%m-%d %H:%M:%S",)}},{"fin_homework":{'verbose_name':"我的作业","value":"提交作业"}},]
#额外自定义字段,若是有值value,会直接输出,否则会去当前实例集self.querysets的每一个实例中去获取相关的数据,使用函数去执行。字符串是调用自己的内置方法,function是调用自定义方法 def __init__(self,model,querysets):
super(ClassRecordForm, self).__init__(model,querysets)
(2)view中调用,显示班级和课程,前端也是{{forms|safe}}
# Create your views here.
@login_required
def couse_list(request):
course_list = models.CourseRecord.objects.filter(
class_grade__student=request.user.userprofile.student
).all() forms = ClassRecordForm(models.CourseRecord,course_list)
forms.register() return render(request,"student/class_list.html",locals()) @login_required
def AllClass(request):
class_list = models.ClassList.objects.filter(
student = request.user.userprofile.student
).all() forms = ClassListForm(models.ClassList,class_list)
forms.register() return render(request,"student/class.html",locals())
显示班级和课程
(3)使用forms表单显示数据,使用Dropzone添加作业
from django.forms import ModelForm,forms
from repository import models class CourseRecordForm(ModelForm):
class Meta:
model = models.CourseRecord #将表与元类中的数据关联
fields = "__all__"
exclude = ["has_homework"] def __new__(cls, *args, **kwargs):
#OrderedDict([('name', <django.forms.fields.CharField object at 0x00000000047FE9E8>), ('contact_type', <django.forms.fields.TypedChoiceField object at 0x00000000047FEBA8>), ('contact', <django.forms.fields.CharField object at 0x00000000047FECC0>), ('source', <django.forms.fields.TypedChoiceField object at 0x00000000047FEE10>), ('referral_from', <django.forms.models.ModelChoiceField object at 0x00000000047FEEF0>), ('consult_courses', <django.forms.models.ModelMultipleChoiceField object at 0x000000000480B048>), ('consult_content', <django.forms.fields.CharField object at 0x000000000480B0B8>), ('status', <django.forms.fields.TypedChoiceField object at 0x000000000480B208>), ('consultant', <django.forms.models.ModelChoiceField object at 0x000000000480B2E8>)])
#这张表中的所有字段对象
for field_name,field_obj in dict(cls.base_fields).items():
field_obj.widget.attrs.update({'class':"form-control","disabled":"true"}) return ModelForm.__new__(cls)
CourseRecordForm
@login_required
def homework(request,id):
CRModel = models.CourseRecord.objects.filter(id=id,has_homework=True) if not CRModel.exists():
return HttpResponse("无作业") CRInfo = CRModel.get()
cus_dir = os.path.join(conf.settings.STUDENT_HOMEWORK_DIR, str(id), str(request.user.userprofile.student.id))
if not os.path.isdir(cus_dir):
os.makedirs(cus_dir) file_info = []
file_names = os.listdir(cus_dir)
for filename in file_names:
file_info.append(os.stat(os.path.join(cus_dir,filename))) if request.method == "GET":
form = myforms.CourseRecordForm(instance=CRInfo)
else:
status = {
'status': True,
"message": None
}
print(request.FILES) # 需要去接收文件,前端状态才会是true
if len(os.listdir(cus_dir)) >= :
status['status'] = False
status['message'] = "文件超出上传个数"
return HttpResponse(json.dumps(status)) file_obj = request.FILES.get("file") with open(os.path.join(cus_dir, file_obj.name), "wb") as fp:
for chunks in file_obj.chunks():
fp.write(chunks) return HttpResponse(json.dumps(status)) return render(request,"student/homework.html",locals())
homework作业显示和添加
(4)前端代码,使用Dropzone处理数据,以及ajax删除数据
{% extends "index.html" %}
{% load my_func %} {% block extra-link %}
<link rel="stylesheet" href="/static/plugins/dropzone/dropzone.css">
{% endblock %} {% block right-content-container %} <div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">作业提交</h3>
</div>
<div class="panel-body">
{{ form }}
<div class="col-sm-12">
<table class="table table-hover" id="file_table">
<caption><label class="control-label" style="padding-top: 0px;">已上传的文件目录</label></caption>
<thead>
<tr>
<th>文件名</th>
<th>大小(KB)</th>
<th>上传时间</th>
<th>删除</th>
</tr>
</thead>
<tbody>
{% for file in file_info %}
<tr>
<td>{% get_list_value file_names forloop.counter0 %}</td>
<td>{{ file.st_size }}</td>
<td>{% get_date_str file.st_atime %}</td>
<td><span style="color: red;" class="glyphicon glyphicon-remove" onclick="deleteFile(this);"></span></td>
</tr>
{% endfor %}
</tbody>
</table>
<form action="{% url 'homework' CRInfo.id %}" id="myAwesomeDropzone" class="dropzone">
{% csrf_token %}
<div class="fallback">
<input name="file" type="file" multiple />
</div>
</form>
</div>
</div>
</div>
{% endblock %} {% block extra-js %}
<script src="/static/plugins/dropzone/dropzone.js"></script>
<script>
$(function () {
Dropzone.options.myAwesomeDropzone = {
paramName: "file", // The name that will be used to transfer the file
maxFilesize: , // MB
maxFiles:,
parallelChunkUploads:true,
accept: function(file, done) {
if (file.name == "justinbieber.jpg") {
done("Naha, you don't.");
}
else { done(); }
},
init: function() {
this.on("success", function(file,respone) {
/* Maybe display some more file information on your page */
var rep = JSON.parse(respone)
if(!rep.status){
alert(rep.message);
return;
}else{
var myDate = new Date();
var str_tm = myDate.toLocaleString();
str_tm = str_tm.replace(/\//g, "-");
str_tm = str_tm.replace(/[\u4e00-\u9fa5]+/g, ""); var tr = "<tr><td>"+file.name+"</td><td>"+file.size+"</td><td>"+str_tm+"</td><td>"+'<span style="color: red;" onclick="deleteFile(this);" class="glyphicon glyphicon-remove"></span></td></tr>'
$("#file_table").append(tr);
}
});
}
};
}); function deleteFile(ths){
var filename = $($(ths).parents("tr").children()[]).text()
$.ajax({
url:"/student/delete_file.html",
data:{'c':'{{ id }}','f':filename,'csrfmiddlewaretoken':'{{ csrf_token }}'},
dataType:"json",
type:"post",
success:function(data){
if(data.status){
$(ths).parents("tr").remove()
}else{
alert(data.message)
}
}
})
} </script> {% endblock %}
homework.html
(5)后台处理数据删除
@login_required
def delete_files(request):
if request.method == "POST":
status = {
'status':True,
'message':""
}
c_id = request.POST['c']
filename = request.POST['f'] current_path = os.path.join(conf.settings.STUDENT_HOMEWORK_DIR, str(c_id), str(request.user.userprofile.student.id))
file_path = os.path.join(current_path,filename)
print(file_path)
if not os.path.isfile(file_path):
status['status']=False
status['message'] = "没有权限"
return HttpResponse(json.dumps(status))
else:
os.remove(file_path) return HttpResponse(json.dumps(status))
delete_files根据ajax上传数据删除文件
总结:学会偷懒,化繁为简,学会总结业务,再去动态处理,而不是一直对数据库的增删改查,和重复一个业务逻辑
python---CRM用户关系管理的更多相关文章
- 智能化CRM客户关系管理系统介绍一
智能化CRM客户关系管理系统介绍一 CRM客户关系管理的定义是:企业为提高核心竞争力,利用相应的信息技术以及互联网技术来协调企业与顾客间在销售.营销和服务上的交互,从而提升其管理方式,向客户提供创新式 ...
- 如何选择合适的CRM客户关系管理软件?
面对日益激烈的市场竞争,很多企业管理者不断通过各种途径和方式,试图寻找一个合适并行之有效的解决方案,以帮助他们解决企业管理难题,不断提高企业的业绩,获得持续的成功. 企业管理软件的出现填补了企业管理领 ...
- day24 Restful api 设计和CRM 客户关系管理
博客: Restful: http://www.cnblogs.com/alex3714/articles/6808013.html http://www.cnblogs.com/alex3714/a ...
- AEAI CRM客户关系管理升级说明
本次发版的AEAI CRM_v1.5.1版本为AEAI CRM_v1.5.0版本的升级版本,该产品现已开源并上传至开源社区http://www.oschina.net/p/aeaicrm. 1 升级说 ...
- 医院管理者必须知道的医院客户关系管理(CRM)
客户关系管理(customer relationship management,CRM)是在二战之后首先由美国IBM.道氏.通用等大型企业提出并运用的一种以有效销售为目的的市场营销思想,其理论基础就是 ...
- 练习:python 操作Mysql 实现登录验证 用户权限管理
python 操作Mysql 实现登录验证 用户权限管理
- CRM(客户关系管理)
CRM最初是由Gartner Group提出的. CRM定义:"客户关系管理(CRM),是代表增进赢利.收入和客户满意度而设计的,企业范围的商业战略." 我们可以看出,Gartne ...
- Python 学习 第十篇 CMDB用户权限管理
Python 学习 第十篇 CMDB用户权限管理 2016-10-10 16:29:17 标签: python 版权声明:原创作品,谢绝转载!否则将追究法律责任. 不管是什么系统,用户权限都是至关重要 ...
- Gradle用户指南(章8:依赖关系管理基础)
章8:依赖关系管理基础 本章将介绍一些gradle依赖关系管理的基础 什么是依赖关系管理? 简略的说,依赖管理是由两部分组成的.首先,gradle需要知道你要构建或者运行的项目,以便找到它们.我们将这 ...
随机推荐
- 《Spring1之第八次站立会议》
<第八次站立会议> 昨天:我查找了关于实现视频功能的相关代码. 今天:对用C#写的视频功能进行了相关的了解. 遇到的问题:由于对C#不是很了解,所以其中的有些代码还是看不懂.
- Codeforces Round #258 (Div. 2) 容斥+Lucas
题目链接: http://codeforces.com/problemset/problem/451/E E. Devu and Flowers time limit per test4 second ...
- Beta 冲刺 (3/7)
队名:日不落战队 安琪(队长) 过去两天完成了那些任务 上传个人信息. 接下来的任务 建立和上传收藏夹. 还剩下的任务 完善手写涂鸦. 社交模块. 遇到的困难 暂无. 有哪些收获和疑问 收获:okht ...
- Week2:阅读笔记与思考
<构建之法>这本书的内容通俗易懂,每一个知识点都有许多事例佐证,阅读起来不像其他教科书那样枯燥无聊.但阅读过第一.二.十六章之后还是产生了几个疑问,以及更深层次的思考. 第一章 问题1: ...
- 读《构建之法》一、二、十六章随笔a
第一章 概论 “软件团队要从需求分析开始,把合适的需求梳理出来,然后逐步开展后续工作”:——p3 问题:好的用户体验要从软件分析开始,那么软件分析仅仅是从用户的需求出发吗? 我的看法:需求分析是 ...
- beta——5
一.提供当天站立式会议照片一张: 二. 每个人的工作 (有work item 的ID) (1) 昨天已完成的工作: 对用户功能的添加. (2) 今天计划完成的工作: web发布 (3) 工作中遇到的困 ...
- Mysql Group Replication 简介及单主模式组复制配置【转】
一 Mysql Group Replication简介 Mysql Group Replication(MGR)是一个全新的高可用和高扩张的MySQL集群服务. 高一致性,基于原生复制及p ...
- php父级目录文件包函问题
问题: php子目录不能包函父目录中的文件. 环境: 网站根目录:/var/www/html/ PHP版本: 5.3.3 Apache版本:2.2 好了,创建三个文件: //文件路径:/var/www ...
- Mysql innodb 间隙锁 (转)
MySQL InnoDB支持三种行锁定方式: 行锁(Record Lock):锁直接加在索引记录上面. 间隙锁(Gap Lock):锁加在不存在的空闲空间,可以是两个索引记录之间,也可能是第一个索引记 ...
- linux下安装java jdk
第一步:查看java对应版本 yum search java 我自己装的是1.8版本的java包 第二步:装java包 yum install java-1.8.0-ope ...