python---CRM用戶關系管理


 

Day1:項目分析

一:需求分析

二:CRM角色功能介紹

三:業務場景分析

銷售:
    1.銷售A 從百度推廣獲取了一個客戶,錄入了CRM系統,咨詢了Python課程,但是沒有報名
    2.銷售B 從qq群獲取一個客戶,成功使他報名Python班,然后給他發送了報名連接,等待用戶填寫完畢后,將他添加到Python具體的學習班級中
    3.銷售C 打電話給之前的一個客戶,說服他報名Python課程,但是沒有成功,更新了跟蹤記錄
    4.銷售D 獲取了一個客戶,錄入信息時,發現此客戶已經存在,不允許重復錄入,隨后通知相應的原負責人跟進
    5.銷售E 從客戶庫中獲取了,超過一個月未跟進的客戶,進行再次跟進
    6.銷售主管 查看了部門本月的銷售報表,包括來源分析,成單率分析,班級報名數量分析,銷售額環比,同比
學員:
    1.客戶A 填寫了銷售發來的報名連接,上傳了個人的證件信息,提交,之后收到郵件,告知報名成功,並為他開通了學員賬號,升級為學員A
    2.學員A 登錄學員系統,看到自己的合同,報名的班級,課程大綱
    3.學員A 提交了Python課程當時課時作業
    4.學員A 查看自己的Python課程成績,排名
    5.學員A 搜索問題,未找到答案,錄入一條問題
    6.學員A 轉介紹學員,錄入其信息
講師:
    1.講師A 登錄CRM系統,查看自己管理的班級列表
    2.講師A 進入Python 5期課程,創建第3節的上課記錄,填寫了本節課內容,作業要求
    3.講師A 在課程中點名,對點名情況進行錄入,標記相關狀態
    4.講師A 批量下載所有學員的課時作業,給每個學員在線批注了成績+狀態
    
管理員:
    1.創建課程 C++,Python..
    2.創建校區 上海,北京..
    3.創建班級 C++35期,Python27期
    4.創建賬號 ABCD
    5.創建了銷售,講師,學員角色
    6.為賬號分配到對應的角色,將ABCD分配給銷售
    7.創建相關權限
    8.為銷售角色分配了相關權限

四:表結構設計

數據庫關聯模型

 

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=64,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=64,unique=True)
    menus = models.ManyToManyField("Menu",blank=True)
    def __str__(self):
        return self.name

class Menu(models.Model):
    '''
    動態菜單
    '''
    name = models.CharField("菜單名",max_length=64)
    url_type_choices = (
        (0,"absolute"), #絕對路徑/Sale/index.html
        (1,"dynamic"),  #動態url,根據url()方法中的name獲取
    )

    url_type = models.SmallIntegerField(choices=url_type_choices)
    url_name = models.CharField("URL",max_length=128)

    def __str__(self):
        return self.name

class CustumerInfo(models.Model):
    '''
    客戶信息表:聯系方式,姓名等
    '''

    name = models.CharField(max_length=64,null=True,blank=True) #開始咨詢的時候允許為空
    contact_type_choices = ((0,'qq'),(1,"微信"),(2,'手機'))
    contact_type = models.SmallIntegerField(choices=contact_type_choices,default=0)
    contact = models.CharField(max_length=64,unique=True)
    source_choices = (
        (0,'QQ群'),
        (1,"51CTO"),
        (2,"百度推廣"),
        (3,"知乎"),
        (4,"轉介紹"),
        (5,"其他")
    )
    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 = ((0,"未報名"),(1,"已報名"),(2,"已退學"))
    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 = (
        (0,"近期無報名計划"),
        (1,"一個月內報名"),
        (2,"2周內報名"),
        (3,"已報名"),
    )

    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=64,verbose_name="課程名稱",unique=True)
    price = models.PositiveSmallIntegerField()  #必須為正
    period = models.PositiveSmallIntegerField(verbose_name="課程周期(月)",default=5)
    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 = (
        (0,"脫產"),
        (1,"周末"),
        (2,"網絡班")
    )
    class_type = models.SmallIntegerField(choices=class_type_choices,default=0)

    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=64)
    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 = (
        (100,"A+"),
        (90,"A"),
        (85,"B+"),
        (80,"B"),
        (75,"B-"),
        (70,"C+"),
        (60,"C"),
        (40,"C-"),
        (0,"N/A"),  #不可得not avaliable
        (-50, "D"), #未交作業
        (-100,"COPY")   #抄襲
    )
    score = models.SmallIntegerField(choices=score_choices)

    show_choices = (
        (0,"缺勤"),
        (1,"已簽到"),
        (2,"遲到"),
        (3,"早退"),
    )
    show_status = models.SmallIntegerField(choices=show_choices)

    note = models.CharField("情況備注",max_length=128,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=64,unique=True)
    addr = models.CharField(max_length=128,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=64)

    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:      2018/5/26 0026
# /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(7), "七天內"],
                [time_now.replace(day=1), "本月"],
                [time_now - timedelta(90), "三個月內"],
                [time_now.replace(month=1, day=1), "今年內"],
                ['', "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[0]) == filter_conditions.get(field,None):
            option = "<option value='%s' selected>" % str(item[0])
        else:
            option = "<option value='%s'>"%str(item[0])
        option += item[1]
        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/2)
        if self.num_pages <= self.max_page_num:
            return range(1, self.num_pages + 1)
        if self.current_page <= part_num:
            return range(1,self.max_page_num+1)
        elif self.current_page+part_num>= self.num_pages:
            return range(self.num_pages-self.max_page_num,self.num_pages+1)
        else:
            return range(self.current_page - part_num, self.current_page + part_num + 1)
分頁類代碼
    current_page = request.GET.get('_p',1)
    paginator = CustomPagimator.CustomPagimator(current_page=current_page, max_page_num=3,object_list=querysets,per_page=2)  # 傳入總數據和每頁顯示的數據
    try:
        filter_data = paginator.page(current_page)
    except PageNotAnInteger:
        filter_data = paginator.page(1)
    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())[0]

    #在生成搜索條件
    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">&laquo;</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">&raquo;</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(7), "七天內"],
                [time_now.replace(day=1), "本月"],
                [time_now - timedelta(90), "三個月內"],
                [time_now.replace(month=1, day=1), "今年內"],
                ['', "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[0]) == filter_conditions.get(field,None):
            option = "<option value='%s' selected>" % str(item[0])
        else:
            option = "<option value='%s'>"%str(item[0])
        option += item[1]
        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',1)
    paginator = CustomPagimator.CustomPagimator(current_page=current_page, max_page_num=3,object_list=querysets,per_page=2)  # 傳入總數據和每頁顯示的數據
    try:
        filter_data = paginator.page(current_page)
    except PageNotAnInteger:
        filter_data = paginator.page(1)
    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())[0]
        sort_val = list(admin_class.sort_conditions.values())[0]
    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())[0])

    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方法

 推文:python---django中orm的使用(2)

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方法去動態生成類

推文:python---基礎知識回顧(三)(面向對象)

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.0 }}
                        </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=1)[1],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=1)
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 == 0){
            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=1)[1],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 == 0 %}
                       {% 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=64)
    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 = (
        (0,"報名費"),
        (1,"學費"),
        (2,"退費"),
    )
    payment_type = models.SmallIntegerField(choices=payment_type_choice)
    amount = models.IntegerField("費用",default=500)
    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)) == 0:
            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.0 }}</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="1" 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: 2, // MB
      maxFiles:2,
      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]")[0]).prop("checked")){
        alert("請先閱讀合同");
        return false;
    }

    if($("#file_ul").find("li").length==0){
        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)) >= 2:
        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,8))
            pwd = ''.join(random.sample(string.ascii_letters+string.digits,8))

            #創建一個賬號
            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) > 0:
                    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(1))),v1,1)
                                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(1))), v1, 1)
                                    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[0],item[1]))

        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)) >= 2:
            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: 2, // MB
      maxFiles:2,
      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()[0]).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上傳數據刪除文件

 總結:學會偷懶,化繁為簡,學會總結業務,再去動態處理,而不是一直對數據庫的增刪改查,和重復一個業務邏輯


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM