Bootstrap

django 实战(11): 自定义Lecture模型(与Teacher模型多对一关系)

1 创建应用lecture

    (base) xxx@xxx-virtual-machine:~/AuthDemo$ django-admin startapp lecture

    (base) xxx@xxx-virtual-machine:~/AuthDemo$

1.1 在应用lecture的目录下创建文件和文件夹

    新建模板目录templates、templatetags,
    新建文件forms.py、urls.py
    在模板目录templates下,创建与应用名同名的子目录

1.2 在settings.py中注册应用

    INSTALLED_APPS = [
        "django.contrib.admin",
        "django.contrib.auth",
        "django.contrib.contenttypes",
        "django.contrib.sessions",
        "django.contrib.messages",
        "django.contrib.staticfiles",
        "basedata",
        "users",

        "student",

        "teacher",

        "lecture",

    ]

2 在lecture/models.py中定义讲座模型Lecture

Teacher模型与Lecture模型一对多关系,外键定义在Lecture模型内

from django.db import models

# Create your models here.

from teacher.models import Teacher

# 讲座模型  与Teacher是多对一的关系

class Lecture(models.Model):

  # 如果删除了老师,关联的讲座也会被删除

  lectureTeacher = models.ForeignKey(Teacher,on_delete=models.CASCADE)

  name = models.CharField(max_length=256, null=False)  # 讲座名称

  address = models.CharField(max_length=256)   # 讲座地点

  start_time = models.DateTimeField(null=True) # 讲座开始时间

  status = models.BooleanField(default=True)   # 讲座状态

  limits = models.IntegerField(default=1000)   # 讲座限制人数

  # 覆盖对象对外的字符串表现形式

  def __str__(self):

    return self.name

2.1  数据迁移

    (base) xxx@xxx-virtual-machine:~/AuthDemo$ python manage.py makemigrations

    Migrations for 'teacher':

      teacher/migrations/0001_initial.py

        - Create model Teacher

    (base) xxx@xxx-virtual-machine:~/AuthDemo$ python manage.py migrate lecture

    Operations to perform:

      Apply all migrations: teacher

    Running migrations:

      Applying teacher.0001_initial... OK

    (base) xxx@xxx-virtual-machine:~/AuthDemo$
 

 3 在lecture/forms.py中,定义表单

LectureAddForm、LectureUpdateForm两张表单一摸一样,只是字段名称不一样。这是因为这两张表单要用在同一个页面中。所以每个输入框的id不能一样。LectureUpdateForm在每个字段名称后面加上了U。

from lecture.models import Lecture

class LectureAddForm(forms.Form):

  name = forms.fields.CharField(       #要填入Lecture模型中的字段

    label = '讲座名称',

    required =True,

    min_length = 2,

    max_length = 256,

    error_messages={

      "required":"讲座名称不可以为空!",

      "min_length":"讲座名称不能低于2位!",

      "max_length":"讲座名称不能超过256位!"

    }

  )

  address = forms.fields.CharField(

    label = '讲座地点',

    required =True,

    min_length = 4,

    max_length = 256,

    error_messages={

      "required":"讲座地点不可以为空!",

      "min_length":"讲座地点不能低于4位!",

      "max_length":"讲座地点不能超过256位!"

    }

  )

  start_time = forms.fields.CharField(

    label = '开始时间',

    required =True,

    min_length = 18,

    max_length = 19,

    error_messages={

      "required":"开始时间不可以为空!",

      "min_length":"开始时间格式为yyyy-mm-dd hh:mm:ss不能低于18位!",

      "max_length":"开始时间格式为yyyy-mm-dd hh:mm:ss不能高于19位!"

    }

  )

  status = forms.fields.BooleanField(

    label = '讲座状态',

    required =False,

  )

  limits = forms.fields.IntegerField(

    label = '限制人数',

    required =True,

    initial=1000,

    error_messages={

      "required":"限制人数不可以为空!",

    }

  )

  lectureTeacher = forms.fields.ChoiceField(

    label = '讲座老师',

    required =True,

    error_messages={

      "required":"必须选择一个讲座老师!",

    }

  )

class LectureUpdateForm(forms.Form):

  nameU = forms.fields.CharField(       #要填入Lecture模型中的字段

    label = '讲座名称',

    required =True,

    min_length = 2,

    max_length = 256,

    error_messages={

      "required":"讲座名称不可以为空!",

      "min_length":"讲座名称不能低于2位!",

      "max_length":"讲座名称不能超过256位!"

    }

  )

  addressU = forms.fields.CharField(

    label = '讲座地点',

    required =True,

    min_length = 4,

    max_length = 256,

    error_messages={

      "required":"讲座地点不可以为空!",

      "min_length":"讲座地点不能低于4位!",

      "max_length":"讲座地点不能超过256位!"

    }

  )

  start_timeU = forms.fields.CharField(

    label = '开始时间',

    required =True,

    min_length = 18,

    max_length = 19,

    error_messages={

      "required":"开始时间不可以为空!",

      "min_length":"开始时间格式为yyyy-mm-dd hh:mm:ss不能低于18位!",

      "max_length":"开始时间格式为yyyy-mm-dd hh:mm:ss不能高于19位!"

    }

  )

  statusU = forms.fields.BooleanField(

    label = '讲座状态',

    required =False,

  )

  limitsU = forms.fields.IntegerField(

    label = '限制人数',

    required =True,

    initial=1000,

    error_messages={

      "required":"限制人数不可以为空!",

    }

  )

  lectureTeacherU = forms.fields.ChoiceField(

    label = '讲座老师',

    required =True,

    error_messages={

      "required":"必须选择一个讲座老师!",

    }

  )

4 在lecture/views.py中定义视图函数query、add、update、delete

add()、update()函数存在添加,修改失败的问题;需要将信息传递到上述两个函数重定向到的query()函数中。详细说明在代码中有:

重定向传值是通过session来进行的。

详细代码如下:

from django.shortcuts import render,redirect

# Create your views here.

from lecture.models import Lecture

from teacher.models import Teacher

from lecture.forms import LectureAddForm,LectureUpdateForm

from django.core.paginator import Paginator

# 导入获取自定义User对象的方法

from django.contrib.auth import get_user_model

User = get_user_model()     # 获取自定义User模型

##########################################

# Lecture Teacher的操作 (多对一)            #

##########################################

# 分页查询所有指向数据库操作的主页面teacher.html

def query(request,per_page='2',current_page='1'):

  ''''

   能:分页函数。

        若是查询所有,进行分页。若是模糊查询,不分页。

   : per_page:  每页记录数, 默认值是'2'.

         current_page: 当前页, 默认值是'1', 即首页

         request.POST[name]: 模糊查询的条件

  返回值: 一个字典。字典中的key含义如下:

         'count':         查询结果总记录数。      int

         'count_all':     数据表中总记录数。   int

            count == count_all,就是全部查询

         'per_page':      每页记录数。      int

         'num_pages':     页面总数。      int

         'current_page':  当前页面数。  int

         'page_bar_range':分页栏显示的数字范围。list

         'result':        若不是模糊查询, 当前页面数对应的结果。querySet

                          若是模糊查询,不分页,模糊查询的所有结果。querySet

         'form':          Lecture的新增表单

         'formU':         Lecture的修改表单

         'search_name':   查询条件。供页面刷新时<input>显示最近一次输入值用

         'app_name':      应用名称。

         'id':            页面选择状态

         'teacherList':   <select>元素的<option>选项表

         'code':          add()update()函数的状态码

         'message':       add()update()函数的信息

         'errorInfo':     add()update()函数的错误原因

  '''

 

  # 初始化返回值为空的字典

  context = {}

  # 读取add()update()函数设置的session,若没有,则为None

  context['code'] = request.session.get('code')

  context['message'] = request.session.get('message')

  context['errorINFO'] = request.session.get('errorINFO')

  #删除由add()update()函数设置的session

  if request.session.get('code') != None:

    request.session.pop('code')

    request.session.pop('message')

    request.session.pop('errorINFO')

  # 读取update()函数设置的页面,若没有,则为None

  id ='0' #应为路由不传id值,所以设置一个

  if request.session.get('per_page') != None:

    # 读取session

    per_page     = int(request.session['per_page'])

    current_page = int(request.session['current_page'])

    id           = int(request.session['id'])

    #删除session

    request.session.pop('per_page')

    request.session.pop('current_page')

    request.session.pop('id')

  else: #没有session,则是路由传值,或初始值

    #因为路由传值、初始值都是字符串,所以需要转换为整数

    per_page     = int(per_page)

    current_page = int(current_page)

    id           = int(id)

  # 获取数据,按id排序

  varList = ''

  if request.POST : # 模糊查询

    varList = Lecture.objects.filter(name__contains = request.POST['search_name']).order_by('start_time')

    # 保存查询条件,供index.html页面要设置搜索栏表单中的相应输入框value属性用

    context['search_name'] = request.POST['search_name']

  else:  # 查询所有

    context['search_name'] = ''

    varList = Lecture.objects.all().order_by('start_time')

   

  # 创建分页器实例

  paginator = Paginator(varList,per_page)

  page = paginator.get_page(current_page)

   

  # 设置在前端分页器栏为Bootstrap分页器内固定显示7页的参数

  page_bar_range = range(1,8)

  if paginator.num_pages > 7:

    if current_page-3 < 1:

        #当页面输入前三页时,使其分页器只显示前7页不变

      page_bar_range = range(1,8)

    elif current_page+3 > paginator.num_pages:

      #当页面处于最后三页时,使分页器只显示最后固定7

      page_bar_range = range(paginator.num_pages-6,paginator.num_pages+1)

    else:

      page_bar_range = range(current_page-3,current_page+4)

  else:

    page_bar_range = paginator.page_range

  context['page_bar_range'] = page_bar_range

  context['count']          = paginator.count

  context['count_all']      = Lecture.objects.all().count();

  context['per_page']       = per_page

  context['current_page']   = current_page

  context['num_pages']      = paginator.num_pages

  context['id']             = id

 

  # 如果不是模糊查询,则分页;如果是,则不分页

  if context['count'] == context['count_all']:

    context['result'] = page

  else:

    context['result'] = varList

  '''

  将所有有关修改、新增所需要的基础数据列表传入前端

  供前端<select><option>选项使用

  '''

  context['teacherList'] = querySetToList(Teacher.objects.all().order_by('id').values('id','name'))

  context['form']       = LectureAddForm()  #新增讲座用到的空表格

  context['formU']      = LectureUpdateForm()  #修改讲座用到的空表格

  context['app_name']   = 'lecture'

  return render(request, "index.html", context)

def querySetToList(querySet):

  '''querySet对象,转化为列表对象'''

  varList =[]

  for var in querySet:

     varList.append( var)

  return varList

def add(request):

  # 1 处理POST请求

  name=request.POST['name']

  address=request.POST['address']

  start_time = request.POST['start_time']

  '''

    模型中定义的statusBoolean类型

    在定义表单相应的form时,该输入变成了type='checkbox'<input>框。

    status在前端反映的是输入类型checkbox的状态,勾选为true

          不勾选则在request.POST中不存在这个键,所以不能直接用

            request.POST['status']

          而应该改用:

            request.POST.get('status')

    但传到后端来,变成了,勾选是on

    不符合模型定义,尽管通过了表单清洗,还需要再清洗

  '''

  status = ''

  if request.POST.get('status') == 'on':

    status = True

  else:

    status = False

  limits = request.POST['limits']

  lectureTeacher_id = request.POST['lectureTeacher']

  # 2 判断讲座名是否已经存在?

  name_exists = Lecture.objects.filter(name= name).exists()

  if name_exists: #如果讲座名称已经存在

    # # 设置session, 操作状态

    request.session['code'] = 400

    request.session['message'] = '讲座添加失败'

    request.session['errorINFO'] =  "您输入的讲座名称已存在!"

    print("您输入的讲座名称已存在!")

    return redirect("/lecture/query")

  # 3 保存到Lecture数据库

  Lecture(

    name=name,

    address=address,

    start_time = start_time,

    status = status,

    limits = limits,

    lectureTeacher_id = lectureTeacher_id,

    ).save()

  # 4 设置session

  request.session['code']      = 200

  request.session['message'] = '讲座添加成功'

  request.session['errorINFO'] = ''

  return redirect("/lecture/query/")

def update(request):

  # 1 处理POST请求

  id = request.POST['id']

  current_page = request.POST["current_page"]

  per_page     = request.POST["per_page"]

  name=request.POST['nameU']

  address=request.POST['addressU']

  start_time = request.POST['start_timeU']

  status = ''

  if request.POST.get('statusU') == 'on':

    status = True

  else:

    status = False

  limits = request.POST['limitsU']

  lectureTeacher_id = request.POST['lectureTeacherU']

  # 2 设置页面状态session

  request.session['current_page'] = current_page

  request.session['per_page']     = per_page

  request.session['id']           = id

  # 3 根据id 查询 修改前的User信息

  lectureBeforUpdate = Lecture.objects.get(id = id)

  # 4 判定讲座名称是否已经存在

  name_exists = Lecture.objects.filter(name= name).exists()

  if (name_exists) and (name !=lectureBeforUpdate.name ):#如果讲座名称已经存在,并且与修改前的不一致

    # 设置session, 操作状态

    request.session['code'] = 400

    request.session['message'] = '讲座修改失败'

    request.session['errorINFO'] =  "您输入的讲座名称已存在!"

    print("您输入的讲座名称已存在!")

    return redirect("/lecture/query")

  # 5 保存到Lecture数据库

  Lecture.objects.filter(id= id).update(

    name=name,

    address=address,

    start_time = start_time,

    status = status,

    limits = limits,

    lectureTeacher_id = lectureTeacher_id,

    )

  # 6 设置session

  # 操作状态

  request.session['code']    = 200

  request.session['message'] = '讲座修改成功'

  request.session['errorINFO'] = ''

  # 页面状态

  request.session['current_page'] = current_page

  request.session['per_page']     = per_page

  request.session['id']           = id

  return redirect("/lecture/query/")

def delete(request):

  # 1 将前端传过来的idList字符串分割为数组

  idList=request.POST['idList'].split(' ')

  # 2 逐一删除表中数据

  for var in idList: # 删除从表

    Lecture.objects.filter(id=int(var)).delete()

  return redirect("/lecture/query")

 5  模板文件

除了lecture/module_query.html之外,其他的都可以用第9节的模板文件。

<!-- module_query.html  在应用lecture目录下的模板目录templates中的lecture子目录中

  内  容:以<table>展示查询和模糊查询的结果。

         在<table>第一列设置复选框,用于同时选择多条记录,共批量删除用。

         在<table>最后一列为操作列。设置两个默认禁用图标按钮用于当前行的修改、删除。

  涉及的变量:

    {{result}}: 含有多条查询结果。

      每一行的key和数据表中的字段名相同。若{{var}}是{{result}}的一行数据,

      那么各列数据分别为:{{var.id}}、{{var.name}}、{{var.address}}、

        {{var.start_time}}、{{var.status}}、{{var.limits}}、

        {{var.lectureTeacher}}

-->

<table id='table' class="table table-striped text-info">

  <thead>

    <tr>

      <td><input id="allChecked" type="checkbox"></td>

      <th><h4>ID</h4></th>

      <th><h4>讲座名称</h4></th>

      <th><h4>讲座地点</h4></th>

      <th><h4>开始时间</h4></th>

      <th><h4>状态</h4></th>

      <th><h4>限制人数</h4></th>

      <th><h4>主讲老师</h4></th>

      <th><h4>操作</h4></th>

    </tr>

  </thead>

  <tbody>

    {% for var in result %}

      <tr>

        <td><input class="checked" type="checkbox"></td>

        <td class="id">       <h5>{{var.id}}</h5></td>

        <td>                  <h5>{{var.name}}</h5></td>

        <td>                  <h5>{{var.address}}</h5></td>

        <!-- Lecture中的start_time是日期时间字段,若不加任何处理

          在页面显示的英文格式,所以要加过滤器date,格式为"Y-m-d H:i",

          大写Y表示,年份是4位数,小写y表示年份是2位数,数据库存储需要年份为4位数

        -->

        <td class="start_time">  <h5>{{var.start_time|date:"Y-m-d H:i"}}</h5></td>

        <td class="status">  <h5>{{var.status}}</h5></td>

        <td class="limits">    <h5>{{var.limits}}</h5></td>

        <td class="lectureTeacher">    <h5>{{var.lectureTeacher}}</h5></td>

        <td class="edit">

          <!--修改按钮 启用id为module_updateModal的弹窗-->

          <button type="button" class="update" data-toggle="modal" data-target="#module_updateModal" disabled="disabled">

            <i class="fa-regular fa-pen-to-square"></i>

          </button>

          <!--删除按钮 启用id为module_deleteModal的弹窗-->

          <button type="button" class="delete" data-toggle="modal" data-target="#module_deleteModal" disabled="disabled">

            <i class="fa-solid fa-trash-can"></i>

          </button>

        </td>

      </tr>

    {% endfor %}

  </tbody>

</table>

 6 在lecture/urls.py中定义子路由

    from django.urls import path,re_path

    from . import views as view

    # 子路由列表

    urlpatterns = [

      path('query/', view.query,  name='query-url'),  # 分页查询

      re_path(r'^query/(?P<per_page>\d+)/(?P<current_page>\d+)/$',view.query),  #传值路由

      path('add/',   view.add,    name='add-url'),     # 新增

      path('update/',view.update, name='update-url'),  # 修改

      path('delete/',view.delete, name='delete-url'),  # 删除

    ]

7 在AuthDemo/urls.py中管理子路由

    from django.contrib import admin
    from django.urls import path,include

    urlpatterns = [
      path("admin/", admin.site.urls),
      path('basedata/',include(('basedata.urls','basedata'))),
      path('student/', include(('student.urls', 'student'))),

      path('teacher/', include(('teacher.urls', 'teacher'))),

      path('lecture/', include(('lecture.urls', 'lecture'))),

    ]

8 访问http://127.0.0.1:8000/lecture/query/

可以得到与第9节类似的页面
 

;