一、课程机构列表页
(一)、利用 django templates 的继承机制配置课程机构列表页 org-list.html
- 观察页面,提取公共部分,公共 header、footer
- 分析,可以用 django 的 {% include "test.html" %} 语法来完成,但是有问题,include 进来的页面是全部写死的,比如“登录”按钮,如果用户登录成功是要换成用户名的,但 include 过来的页面是没法自动完成替换的,所以,include 不适合导入动态变化的页面,只适用于静态页面部分。
- 为了解决上面的这个问题,就要用到 django 提供的另一中模板机制--继承,{% extends "base.html" %}。类似于 python 中的“类”,可以先定义一个整体的框架,然后把页面中动态的部分定义为一个一个的 {% block %},子类继承 "base.html" 的时候只需要重写 {% block %} 部分。
-
页面中的公共部分
001(1).png
-
step1:在 templates/ 目录下 新建 base.html 文件,将 org-list.html 页面页放到此目录下。
002.png -
step2:复制 org-list.html 所有内容到 base.html,替换静态文件,定义 {% block %},在 org-list.html 继承 base.html
# templates/base.html
# base.html 中需要定义的 block
1.每个页面的 title 是动态切换的
<title>{% block title %}慕学在线网{% endblock title %}</title>
2.静态文件,静态文件是有共用部分的,很多 css、js 等静态文件都是共用的,有些页面是有自己的新加的 静态文件的
<head>
{% block custom_css %}{% endblock custom_css %}
{% block custom_js %}{% endblock custom_js %}
</head>
3.面包屑导航部分
{% block custom_bread %}{% endblock custom_bread %}
4.内容区部分
{% block content %}{% endblock content %}
# templates/org-list.html
{% extends "base.html" %}
{% block title %}课程机构列表 - 慕学在线网{% endblock %}
下图为 {% block custom_bread %} 定义的面包屑导航部分
003.png
- step3:定义 课程机构列表页类视图 OrgView,配置 url
# apps/organization/views.py
from django.shortcuts import render
from django.views.generic import View
class OrgView(View):
"""
课程机构列表页类视图
"""
def get(self, request):
return render(request, "org-list.html", context={})
# mxonline/urls.py
from organization.views import OrgView
urlpatterns = [
...
# 课程机构首页
path("org_list/", OrgView.as_view(), name="org_list"),
]
- step4:访问 http://127.0.0.1:8000/org_list/ 查看页面,完善 org-list.html
# templates/org-list.html
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}课程机构列表 - 慕学在线网{% endblock %}
{% block custom_bread %}
<section>
<div class="wp">
<ul class="crumbs">
<li><a href="index.html">首页</a>></li>
<li>课程机构</li>
</ul>
</div>
</section>
{% endblock custom_bread %}
{% block content %}
<section>
...
</section>
{% endblock content %}
(二)、编写课程机构列表页后台逻辑
- 分析页面的数据,不会变动的数据可以直接写成静态数据,经常要变动的数据要从后台获取,写成动态数据
- 启动项目,进入 admin 后台管理系统
- step1:随意添加几个城市
- step2:增加课程机构,因为课程机构有上传封面图的按钮,所以需要先在 steeings.py 中配置上传文件路径
- 回顾 CourseOrg 课程机构的 model,里面有一个 image 字段是用来上传封面图的字段,其中 upload_to 指明图片上传路径,这个路径是一个相对路径,是相对于项目上传文件的根目录,而这个根目录是需要我们自己手动配置的
class CourseOrg(models.Model):
"""
课程机构表
"""
...
image = models.ImageField(max_length=100, upload_to="organization/%Y/%m", verbose_name="封面图")
- 在项目根目录下创建 media/ 目录,作为用户上传文件的根目录,存放上传文件
- 需要在 settings.py 中配置文件上传的根目录
# mxonline/settings.py
# 指明上传文件的路径
# media 只能配置一个, 不能像 static 那样配置多个
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
- 现在可以上传课程机构的封面图了,图片将存放于 /medai/organization/%Y/%m/ 目录下
- 为了便于测试筛选功能,和稍后需要做的分页功能,需要多添加几条数据
- 因为机构列表页有一个按照“机构类别”来筛选课程机构的功能,而数据库之前没有相应字段,所以为 CourseOrg model 类添加 category 字段
# apps/organization/models.py
class CourseOrg(models.Model):
"""
课程机构表
"""
pxjg = "pxjg"
gr = "gr"
gx = "gx"
category_choices = (
(pxjg, "培训机构"),
(gr, "个人"),
(gx, "高校"),
)
name = models.CharField(max_length=50, verbose_name="机构名称")
describe = models.TextField(verbose_name="机构描述")
category = models.CharField(max_length=20, choices=category_choices, default=pxjg, verbose_name="所属机构")
favorite_nums = models.IntegerField(default=0, verbose_name="收藏数")
click_nums = models.IntegerField(default=0, verbose_name="点击数")
image = models.ImageField(max_length=100, upload_to="organization/%Y/%m", verbose_name="封面图")
address = models.CharField(max_length=100, verbose_name="机构地址")
city = models.ForeignKey(CityDict, on_delete=models.SET_NULL, null=True, verbose_name="所在城市")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "课程机构"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
- 运行 makemigrations organization,migrate organization,重启项目
- 完善 OrgView 类视图
# apps/organization/views.py
from django.shortcuts import render
from django.views.generic import View
from .models import CourseOrg, CityDict
class OrgView(View):
"""
课程机构列表页类视图
"""
def get(self, request):
# 课程机构
all_orgs = CourseOrg.objects.all()
# 课程机构所在城市
all_citys = CityDict.objects.all()
# 课程机构总数量
org_nums = CourseOrg.objects.count()
context = {
"all_orgs": all_orgs,
"all_citys": all_citys,
"org_nums": org_nums,
}
return render(request, "org-list.html", context)
- 将 课程机构 和 课程机构所在城市 传递到前端页面,完成动态获取数据
- 先替换 org-list.html 中 课程机构所在城市 部分代码的 <a> 标签
# templates/org-list.html
<h2>所在地区</h2>
<div class="more">更多</div>
<div class="cont">
<a href="?ct="><span class="active2">全部</span></a>
<a href="?city=1&ct="><span class="">北京市</span></a>
<a href="?city=2&ct="><span class="">上海市</span></a>
<a href="?city=3&ct="><span class="">广州市</span></a>
<a href="?city=4&ct="><span class="">深圳市</span></a>
<a href="?city=5&ct="><span class="">天津市</span></a>
</div>
- 替换结果如下
# templates/org-list.html
<h2>所在地区</h2>
<div class="more">更多</div>
<div class="cont">
<a href="?ct="><span class="active2">全部</span></a>
{% for city in all_citys %}
<a href="?city=1&ct="><span class="">{{ city }}</span></a>
{% endfor %}
</div>
- 刷新课程机构列表页,所在地区已经由静态变成动态获取数据库中的数据了
- 观察 课程机构 部分代码都是由 <dl> 标签构成的
# templates/org-list.html
<dl class="des difdes">
<dt>
<a href="org-detail-homepage.html">
<img width="200" height="120" class="scrollLoading" data-url="{% static "media/org/2016/11/imooc.png" %}"/>
</a>
</dt>
<dd>
<div class="clearfix">
<a href="org-detail-homepage.html">
<h1>慕课网</h1>
<div class="pic fl">
<img src="{% static "images/authentication.png" %}"/>
<img src="{% static "images/gold.png" %}"/>
</div>
</a>
</div>
<ul class="cont">
<li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>
<li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>
<li class="pic10" style="padding-left:18px;">经典课程:
<a href="/diary/19/">c语言基础入门</a>
<a href="/diary/16/">数据库基础</a>
</li>
</ul>
</dd>
<div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>
</dl>
...
- 替换结果如下
# templates/org-list.html
{% for course_org in all_orgs %}
<dl class="des difdes">
<dt>
<a href="org-detail-homepage.html">
<img width="200" height="120" class="scrollLoading" data-url="{{ MEDIA_URL }}{{ course_org.image }}"/>
</a>
</dt>
<dd>
<div class="clearfix">
<a href="org-detail-homepage.html">
<h1>{{ course_org.name }}</h1>
<div class="pic fl">
<img src="{% static "images/authentication.png" %}"/>
<img src="{% static "images/gold.png" %}"/>
</div>
</a>
</div>
<ul class="cont">
<li class="first"><p class="pic9">课程数:<span>1</span></p><p class="c7">学习人数:<span>1000</span></p></li>
<li class="c8" style="padding-left:18px;">北京市海淀区中关村北大街</li>
<li class="pic10" style="padding-left:18px;">经典课程:
<a href="/diary/19/">c语言基础入门</a>
<a href="/diary/16/">数据库基础</a>
</li>
</ul>
</dd>
<div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>
</dl>
{% endfor %}
- 其中 课程机构的 logo(封面图) 是 通过 <img> 标签的 data-url 属性获得的
<img width="200" height="120" class="scrollLoading" data-url="{% static "media/org/2016/11/imooc.png" %}"/>
- 因为 data-url 需要的是一个 url 地址,所以需要把 image (model 中字段类型为 ImageField)字段转换成一个 url 地址,先看一下数据库是如何保存的
- 实际上 image 字段在数据库中存储的就是一个字符串,这个字符串是一个相对路径地址
# 如果直接这样写,实际上是取得相对路径地址,不能完成图片存取
<img data-url="{{ course_org.image }}"/>
# 可以这样写成完整路径地址,这样是可以完成图片存取的
<img data-url="/media/{{ course_org.image }}"/>
# 可以使用可灵活配置的更优雅的方式
<img data-url="{{ MEDIA_URL }}{{ course_org.image }}"/>
-
完成 <img data-url="{{ MEDIA_URL }}{{ course_org.image }}"/> 配置实际上前端页面还是获取不到图片的
-
在 settings.py 中的 TEMPLATES找到 context_processors 变量,并在内部添加 media 文件的配置 "django.template.context_processors.media", 只有配置了这个类,django 才会自动将 {{ MEDIA_URL }} 注册到 html 代码中,不然是不会显示在 html 代码中的
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
...
"django.template.context_processors.media",
],
},
},
]
- 虽然 代码中是下面这样写的
<img width="200" height="120" class="scrollLoading" data-url="{{ MEDIA_URL }}{{ course_org.image }}"/>
- 如果不在 settings.py 中配置 "django.template.context_processors.media", 前端显示结果
- 配置后 前端显示结果
- 现在 <img> 标签的 url 是对了,但图片还是没有显示出来,还需要在 urls.py 中配置对应的 url,用来告诉 django 如果 url 路径是在 media/ 目录之下应该去哪里取相应文件
- 所以要配置一个 ulr 专门来处理 media 的信息
# mxonline/urls.py
from django.views.static import serve
from mxonline.settings import MEDIA_ROOT
urlpatterns = [
...
# 配置上传文件的访问处理函数
# 这里的配置一定要这样写, 先取出 media/ 之下的路径, 放到 path 变量下,
# media/ 之下的文件是静态文件, 为了处理这些静态文件,
# 需要用到 django 用来处理静态文件的内置函数 static 的 serve方法,
# serve 方法需要传递一个参数, 参数的名称叫 document_root,
# 意思是处理 path 路径之下的访问, 需要去 MEDIA_ROOT 路径之下去寻找
re_path(r"^media/(?P<path>.*)$", serve, {"document_root": MEDIA_ROOT}),
]
- 刷新页面,图片已经传递到前端了
- 完成分页功能
-
step1:分页功能可以用第三方开源库 django-pure-pagination 来完成,可以在 github 上面搜索到(github 地址:https://github.com/jamespacileo/django-pure-pagination)
-
1.进入项目虚拟环境,pip install django-pure-pagination 进行安装
(mxonline) pythonic@pythonic-machine:~$ pip install django-pure-pagination
Collecting django-pure-pagination
Downloading django-pure-pagination-0.3.0.tar.gz
Building wheels for collected packages: django-pure-pagination
Running setup.py bdist_wheel for django-pure-pagination ... done
Stored in directory: /home/pythonic/.cache/pip/wheels/6a/ba/38/21d6880456355583455f47c35e0c14659c66bdcfb8214aad1a
Successfully built django-pure-pagination
Installing collected packages: django-pure-pagination
Successfully installed django-pure-pagination-0.3.0
- 2.将 pure_pagination 添加到 settings.py 中的 INSTALLED_APPS 当中
# mxonline/settings.py
INSTALLED_APPS = [
...
"pure_pagination",
]
- 3.将分页逻辑添加到 OrgView 中
# apps/organization/views.py
from pure_pagination import Paginator, EmptyPage, PageNotAnInteger
class OrgView(View):
"""
课程机构列表页类视图
"""
def get(self, request):
# 课程机构
all_orgs = CourseOrg.objects.all()
# 课程机构总数量
org_nums = CourseOrg.objects.count()
# 课程机构所在城市
all_citys = CityDict.objects.all()
# 对课程机构进行分页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# Paginator 接收 3 个参数, 需要分页的对象列表, 每页显示的对象数量, 以及 request
p = Paginator(all_orgs, 1, request=request)
orgs = p.page(page)
context = {
"all_orgs": orgs,
"org_nums": org_nums,
"all_citys": all_citys,
}
return render(request, "org-list.html", context)
- 4.模板中处理分页需要显示的逻辑
# templates/org-list.html
显示课程机构列表部分,只需要修改 for 循环遍历的 all_orgs 对象,其他部分完全不用修改
{% for course_org in all_orgs %}
...
{% endfor %}
将上面代码改成如下代码,现在后端传递过来的 all_orgs 已经不是所有课程机构列表了,而是课程机构列表的一个 page 对象,这个对象的 object_list 方法即可取出当前页面全部的课程机构
{% for course_org in all_orgs.object_list %}
...
{% endfor %}
# templates/org-list.html
# 页码部分逻辑
<div class="pageturn pagination">
<ul>
{% if all_orgs.has_previous %}
<li class="long"><a href="?{{ all_orgs.previous_page_number.querystring }}">上一页</a></li>
{% endif %}
{% for page in all_orgs.pages %}
{% if page %}
{% ifequal page all_orgs.number %}
<li class="page active"><a href="?{{ page.querystring }}">{{ page }}</a></li>
{% else %}
<li><a href="?{{ page.querystring }}" class="page">{{ page }}</a></li>
{% endifequal %}
{% else %}
<li class="none"><a href="">...</a></li>
{% endif %}
{% endfor %}
{% if all_orgs.has_next %}
<li class="long"><a href="?{{ all_orgs.next_page_number.querystring }}">下一页</a></li>
{% endif %}
</ul>
</div>
- 5.访问页面查看结果
- 6.实现按照机构类别和所在地区筛选课程机构
# apps/organization/views.py
class OrgView(View):
"""
课程机构列表页类视图
"""
def get(self, request):
# 课程机构
all_orgs = CourseOrg.objects.all()
# 课程机构所在城市
all_citys = CityDict.objects.all()
# 取出筛选城市
city_id = request.GET.get("city", "")
# 如果有 city_id, 对 all_orgs 结果集进行进一步筛选
if city_id:
all_orgs = all_orgs.filter(city_id=city_id)
# 取出筛选机构类别
category = request.GET.get("category", "")
# 如果有 category, 对 all_orgs 结果集进行进一步筛选
if category:
all_orgs = all_orgs.filter(category=category)
# 课程机构总数量
org_nums = all_orgs.count()
# 对课程机构进行分页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# Paginator 接收 3 个参数, 需要分页的对象列表, 每页显示的对象数量, 以及 request
p = Paginator(all_orgs, 1, request=request)
orgs = p.page(page)
context = {
"all_orgs": orgs,
"org_nums": org_nums,
"all_citys": all_citys,
"city_id": city_id,
"category": category,
}
return render(request, "org-list.html", context)
# templates/org-list.html
<div class="listoptions">
<ul>
<li>
<h2>机构类别</h2>
<div class="cont">
<a href="?city={{ city_id }}"><span {% ifequal category "" %}class="active2"{% endifequal %}>全部</span></a>
<a href="?category=pxjg&city={{ city_id }}"><span {% ifequal category "pxjg" %}class="active2"{% endifequal %}>培训机构</span></a>
<a href="?category=gx&city={{ city_id }}"><span {% ifequal category "gx" %}class="active2"{% endifequal %}>高校</span></a>
<a href="?category=gr&city={{ city_id }}"><span {% ifequal category "gr" %}class="active2"{% endifequal %}>个人</span></a>
</div>
</li>
<li>
<h2>所在地区</h2>
<div class="more">更多</div>
<div class="cont">
<a href="?category={{ category }}"><span {% ifequal city_id "" %}class="active2"{% endifequal %}>全部</span></a>
{% for city in all_citys %}
<a href="?city={{ city.id }}&category={{ category }}"><span {% ifequal city_id city.id|stringformat:"i" %}class="active2"{% endifequal %}>{{ city }}</span></a>
{% endfor %}
</div>
</li>
</ul>
</div>
- 对以上模板中部分代码做讲解:
{% ifequal city_id city.id|stringformat:"i" %}class="active2"{% endifequal %}
其中:
1. ifequal 是 django 在模板中判定两个变量是否相等的语法,
ifequal var1 var2 ==等价于==> if var1 == var2;
2. city.id|stringformat:"i" 是通过过滤器将 int 类型的 city.id 转换成 str 类型,因为 city_id 是字符串类型,想比较两者是否相等就要转换成相同的类型比较
- 7.实现效果
- 通过观察 org-list.html 中的代码和 url 地址栏,可以发现 django-pure-pagination 强大的地方就在于,其实在写分页部分逻辑的时候,<a> 标签 href 属性我们没有自己写,而是直接采用了这个库提供的 href="?{{ page.querystring }}" ,它能够自动将 URL 地址中的 city=1&category=pxjg 这两个参数记住,这样就能够实现 既可以通过机构类别和所在地区筛选课程机构,同时分页功能还可以适用筛选结果页
- 完成页面右侧“授课机构排名”,根据点击量进行排序
# apps/organization/views.py
class OrgView(View):
"""
课程机构列表页类视图
"""
def get(self, request):
# 课程机构
all_orgs = CourseOrg.objects.all()
# 根据点击量对课程机构进行排序
hot_orgs = all_orgs.order_by("-click_nums")[:3]
...
context = {
...
"hot_orgs": hot_orgs,
}
return render(request, "org-list.html", context)
# templates/org-list.html
<div class="head">授课机构排名</div>
{% for hot_org in hot_orgs %}
<dl class="des">
<dt class="num fl">{{ forloop.counter }}</dt>
<dd>
<a href="/company/2/"><h1>{{ hot_org.name }}</h1></a>
<p>{{ hot_org.address }}</p>
</dd>
</dl>
{% endfor %}
</div>
021.png
- 实现点击页面“学习人数”和“课程数”能够使课程机构排序
- 因为课程机构模型类 CourseOrg 中没有这两个字段,所以先加上这两个字段
# apps/organization/models.py
class CourseOrg(models.Model):
"""
课程机构表
"""
...
students = models.IntegerField(default=0, verbose_name="学习人数")
course_nums = models.IntegerField(default=0, verbose_name="课程数")
- 依次运行 makemigrations organization,migrate organization 完成数据库迁移
# apps/organization/views.py
class OrgView(View):
"""
课程机构列表页类视图
"""
def get(self, request):
# 课程机构
all_orgs = CourseOrg.objects.all()
...
# 根据学习人数或课程数对 all_orgs 进行排序
sort = request.GET.get("sort", "")
if sort:
if sort == "students":
all_orgs = all_orgs.order_by("-students")
elif sort == "courses":
all_orgs = all_orgs.order_by("-course_nums")
...
context = {
...
# 这里要把 sort 传递到前端模板中, 目的是为了在前端模板中
# 可以通过 sort 来判断当前排序规则的文字应该处于激活状态
"sort": sort,
}
return render(request, "org-list.html", context)
# templates/org-list.html
<div class="head">
<ul class="tab_header">
<li class="{% if sort == "" %}active{% endif %}"><a href="?category={{ category }}&city={{ city_id }}">全部</a> </li>
<li class="{% if sort == "students" %}active{% endif %}"><a href="?sort=students&category={{ category }}&city={{ city_id }}">学习人数 ↓</a></li>
<li class="{% if sort == "courses" %}active{% endif %}"><a href="?sort=courses&category={{ category }}&city={{ city_id }}">课程数 ↓</a></li>
</ul>
</div>
023.png
- 整理下 url,在 organization/ 目录下新建 urls.py 文件
# mxonline/urls.py
from django.urls import path, re_path, include
urlpatterns = [
...
# 课程机构首页
path("org/", include("organization.urls")),
]
# apps/organization/urls.py
from django.urls import path, re_path
from .views import OrgView
app_name = "org"
urlpatterns = [
path("", OrgView.as_view(), name="org_list"),
]
- 通过 ModelForm 完成 我要学习功能
# apps/operation/models.py
class UserAsk(models.Model):
"""
用户咨询表
"""
name = models.CharField(max_length=20, verbose_name="用户名")
mobile = models.CharField(max_length=11, verbose_name="手机")
course_name = models.CharField(max_length=50, verbose_name="课程名称")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
- 分析发现,前端页面用户所要提交的我要学习表单和后台定义的 UserAsk 模型类是相对应的,所以可以采用 django 的 ModelForm 来处理表单的提交验证
# apps/organization/forms.py
from django import forms
from operation.models import UserAsk
# class UserAskForm(forms):
# name = forms.CharField(required=True, min_length=2, max_length=20)
# mobile = forms.CharField(required=True, min_length=11, max_length=11)
# course_name = forms.CharField(required=True, min_length=5, max_length=50)
# 上面是通常需要定义的验证表单的类, 但是这个 UserAskForm
# 有一个与之对应的 model, 所以完全可以用下面的写法来替代上面的写法
# 因为前端用户咨询所要提交的表单刚好有一个模型类与之对应,
# 所以这里可以直接定义一个 UserAskForm 表单类, 继承自
# forms.ModelForm, 这样就不需要自己再定义需要验证的字段,
# 只需要在 Meta 中指定 表单所对应的数据库模型类, 和所需要
# 验证的字段就可以了
class UserAskForm(forms.ModelForm):
"""
用户咨询表单
"""
class Meta:
model = UserAsk
fields = ["name", "mobile", "course_name"]
# apps/organization/views.py
class UserAskView(View):
def post(self, request):
userask_form = UserAskForm(request.POST)
userask_form.save(commit=True)
-
观察分析我要学习功能,有两个特点:1.这个功能是页面的一个小的独立区域,适合用 ajax 异步,而不是点击点击立即咨询按钮就刷新整个页面;2.如果提交的表单信息有错误,前端页面是要返回错误信息的
-
定义 AddUserAskView 用来处理用户点击"我要咨询"按钮,提交表单信息
# apps/organization/views.py
from django.http import JsonResponse
class AddUserAskView(View):
"""
用户添加咨询类视图
"""
def post(self, request):
userask_form = UserAskForm(request.POST)
if userask_form.is_valid():
# userask_form 的 save 方法有个参数 commit, 默认就是为 True,
# 是直接将数据保存到数据库中, 如果 commit 为 False, 则这里只是
# 将结果提交到数据库, 但并没有真正保存到数据库
user_ask = userask_form.save(commit=True)
# 这里是进行 ajax 操作, 所以不能用 render 来返回整个页面,
# 用 render 返回页面是要刷新整个页面的, 而进行 ajax 异步操作,
# 这里应该返回 json, 通过 JsonResponse 返回 json 字符串,
# JsonResponse 是 HttpResponse 的子类
return JsonResponse(data='{"status": "success"}', safe=False)
else:
# 注意: 这里有个坑, data 是要返回到前端的 json, 标准 json 是用的
# 双引号, 所以这里一定要在 json 中用 双引号, 花括号外面用单引号
return JsonResponse(data='{"status": "fail", "error_msg": "填写错误!"}', safe=False)
- 定义相应的 url
# apps/organization/urls.py
from django.urls import path, re_path
from .views import OrgView, AddUserAskView
app_name = "org"
urlpatterns = [
...
path("add_ask/", AddUserAskView.as_view(), name="add_ask"),
]
- 利用 jQuery 编写课程机构列表页 我要学习表单的 ajax 部分代码
# templates/org-list.html
{% block custom_js %}
<script>
$(function(){
$('#jsStayBtn').on('click', function(){
$.ajax({
cache: false,
type: "POST",
url: "{% url "org:add_ask" %}",
data: $('#jsStayForm').serialize(),
dataType: "json",
async: true,
success: function(data) {
console.log(typeof(data));
console.log(data);
var data = JSON.parse(data);
console.log(typeof(data));
console.log(data.status);
if(data.status == 'success'){
$('#jsStayForm')[0].reset();
alert("提交成功")
}else if(data.status == 'fail'){
$('#jsCompanyTips').html(data.error_msg)
alert("失败");
}else {
alert("见鬼了");
}
},
});
});
})
</script>
{% endblock custom_js %}
- 访问 http://127.0.0.1:8000/org/ 点击"立即咨询"按钮, 可以测试下结果
- 修改用户咨询表单验证视图 UserAskForm, 利用正则来自定义手机号的验证
# apps/organization/forms.py
import re
from django import forms
from operation.models import UserAsk
class UserAskForm(forms.ModelForm):
"""
用户咨询表单
"""
class Meta:
model = UserAsk
fields = ["name", "mobile", "course_name"]
# 因为 model 中只是对 "mobile" 字段进行了长度验证,
# 这里需要对手机号进行更加严格的匹配, 采用正则来验证手机号是否合法,
# django 的 ModelForm 为我们提供了一个方便自定义字段验证规则的方法,
# 使用这个方法的要求是必须以 clean_ 开头, 下划线后面可以根据需求自定义,
# 这样, django 在这个 UserAskForm 表单类被实例化的时候, 会自动在初始化
# 过程中调用下面所定义的 clean_mobile 方法, 来完成 "mobile" 的验证
def clean_mobile(self):
"""
验证手机号码是否合法
"""
# 先获取要自定义验证规则的字段, 字段会以字典的形式存储在 cleaned_data 中,
# 可以通过 cleaned_data["字段名"] 来得到前端提交的相应数据
mobile = self.cleaned_data["mobile"]
re_mobile = r"^1[34578]\d{9}$"
p = re.compile(re_mobile)
if p.match(mobile):
# 验证成功, 返回 mobile
return mobile
else:
# 验证失败, 抛出自定义错误信息
# 这里用 raise 来抛出异常
raise forms.ValidationError("手机号码不合法", code="mobile_invalid")
- 访问 http://127.0.0.1:8000/org/ 输入不合法的手机号, 点击"立即咨询"按钮, 可以测试下结果
二、课程机构详情页
- 课程机构详情页主要包括 机构课程、机构介绍、机构讲师
- 可以通过 admin 后台增加几条数据
- 增加数据过程发现 "机构课程" 与 "机构" 之间是 多 对 1 的关系,所以要在 课程 的 model 中新增一个 机构 的 外键
# apps/courses/models.py
from organization.models import CourseOrg
class Course(models.Model):
"""
课程表
"""
cj = "cj"
zj = "zj"
gj = "gj"
degree_choices = (
(cj, "初级"),
(zj, "中级"),
(gj, "高级"),
)
course_org = models.ForeignKey(CourseOrg, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="课程机构")
name = models.CharField(max_length=50, verbose_name="课程名称")
describe = models.CharField(max_length=200, verbose_name="课程描述")
detail = models.TextField(verbose_name="课程详情")
degree = models.CharField(max_length=2, choices=degree_choices, verbose_name="课程难度")
learn_times = models.IntegerField(default=0, verbose_name="学习时长(分钟数)")
students = models.IntegerField(default=0, verbose_name="学习人数")
favorite_nums = models.IntegerField(default=0, verbose_name="收藏人数")
image = models.ImageField(max_length=100, upload_to="courses/%Y/%m", verbose_name="封面图")
click_nums = models.IntegerField(default=0, verbose_name="点击数")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "课程"
verbose_name_plural = verbose_name
- 运行 makemigrations courses,migrate courses,重启项目
- 再次进入后台添加课程的时候,就会多出来一个 "课程机构" 的下拉框,可以选择课程所属的 课程机构
- 将页面复制到 templates/ 目录下
- 定义 OrgHomeView 类视图
# apps/organization/views.py
class OrgHomeView(View):
"""
机构首页类视图
"""
def get(self, request, org_id):
# 通过 url 地址中传递进来的 org_id, 取出对应的 课程机构
course_org = CourseOrg.objects.get(id=org_id)
# 知道了课程机构 course_org, 就可以通过这个 课程机构
# 反向取出所有该课程机构下面的 课程 courses,
# 在 django 的 ORM 中 可以直接通过 course_set 来得到
courses = course_org.course_set.all()[:3]
teachers = course_org.teacher_set.all()[:1]
context = {
"courses": courses,
"teachers": teachers,
}
return render(request, "org-detail-homepage.html", context)
- 配置 url
# apps/organization/urls.py
from django.urls import path, re_path
from .views import OrgHomeView
app_name = "org"
urlpatterns = [
...
re_path(r"^home/(?P<org_id>\d+)/$", OrgHomeView.as_view(), name="org_home"),
]
- 定义 "org_base.html" 页面, 用做 机构详情页的基本页面, 其他 4 个页面可以继承此页面
- 先来完善 "org-detail-homepage.html" 页面, 替换模板中的静态文件, 将后台传递过来的数据显示出来
- 访问 http://127.0.0.1:8000/org/home/2/ 查看效果
- 因为 4 个页面左侧导航菜单部分都是共用的, 所以要处理下这 4 个导航菜单, 当前页面处于哪个页面, 就让对应的导航菜单处于激活状态
- 处理办法:
- (1)先在 类视图中 定义一个 current_name 变量传递到前端页面
# apps/organization/views.py
class OrgHomeView(View):
"""
机构首页类视图
"""
def get(self, request, org_id):
# 因为前端页面中机构相关的四个页面是共用的, 所以这里定义
# 一个 current_name 变量传递到前端页面, 用来判断当前
# 导航菜单是否为激活状态
current_name = "home"
# 通过 url 地址中传递进来的 org_id, 取出对应的 课程机构
course_org = CourseOrg.objects.get(id=org_id)
# 知道了课程机构 course_org, 就可以通过这个 课程机构
# 反向取出所有该课程机构下面的 课程 courses,
# 在 django 的 ORM 中 可以直接通过 course_set 来得到
courses = course_org.course_set.all()[:3]
teachers = course_org.teacher_set.all()[:1]
context = {
"course_org": course_org,
"courses": courses,
"teachers": teachers,
"current_name": current_name,
}
return render(request, "org-detail-homepage.html", context)
- (2)在前端页面判断后台传递过来的变量 current_name 是否等于当前页面, 如果等于当前页面, 添加 active2
# templates/org_base.html
<div class="left">
<ul>
<li class="{% ifequal current_name "home" %}active2{% endifequal %}"><a href="{% url "org:org_home" course_org.id %}">机构首页</a></li>
<li class="{% ifequal current_name "course" %}active2{% endifequal %}"><a href="{% url "org:org_course" course_org.id %}">机构课程</a></li>
<li class="{% ifequal current_name "description" %}active2{% endifequal %}"><a href="org-detail-desc.html">机构介绍</a></li>
<li class="{% ifequal current_name "teacher" %}active2{% endifequal %}"><a href="org-detail-teachers.html">机构讲师</a></li>
</ul>
</div>
- 其他 3 个页面同机构首页类似
- 利用 Ajax 完成课程机构的收藏功能
- (1) 定义 AddFavoriteView 类视图,用来处理用户收藏
# apps/organization/views.py
class AddFavoriteView(View):
"""
用户收藏、取消收藏类视图
"""
def post(self, request):
# 这里 favorite_id 和 favorite_type 默认值都设定为 "0",
# 而不是 "", 是因为下面查询 exist_records 的时候需要将这两个
# 变量转换成 int 类型, 如果默认值设定为 "", 转换 int 是会报错的
favorite_id = request.POST.get("favorite_id", "0")
favorite_type = request.POST.get("favorite_type", "0")
# 既然是用户收藏, 这里首先要判断用户是否登录
# 即使用戶未登录状态, request 中也是有 user 的, 只需要调用
# user 的 is_authenticated 方法, 就可以验证用户是否登录
# 如果用户未登录, 执行以下逻辑
if not request.user.is_authenticated:
# 如果用户没有登录, 返回错误信息, 告诉 Ajax 用户未登录,
# 不能进行收藏, 通过传递到 ajax 的数据, ajax 判断如果
# 用户在点击收藏按钮时, 处于未登录状态, 则跳转到登录页面
return JsonResponse(data='{"status": "fail", "msg": "用户未登录"}', safe=False)
# 如果用户已经登录, 执行以下逻辑
exist_records = UserFavorite.objects.filter(user=request.user, favorite_id=int(favorite_id), favorite_type=int(favorite_type))
if exist_records:
# 如果记录已经存在, 则表示用户要进行取消收藏的操作
# 调用 delete 方法删除记录
exist_records.delete()
return JsonResponse(data='{"status": "success", "msg": "收藏"}', safe=False)
else:
# 如果记录不存在, 则表示用户要进行添加收藏的操作
user_favorite = UserFavorite()
if int(favorite_id) > 0 and int(favorite_type) > 0:
user_favorite.user = request.user
user_favorite.favorite_id = int(favorite_id)
user_favorite.favorite_type = int(favorite_type)
user_favorite.save()
return JsonResponse(data='{"status": "success", "msg": "已收藏"}', safe=False)
else:
return JsonResponse(data='{"status": "fail", "msg": "收藏出错"}', safe=False)
- (2) 配置 url
# apps/organization/urls.py
from django.urls import path, re_path
from .views import AddFavoriteView
app_name = "org"
urlpatterns = [
...
# 添加收藏
re_path(r"^add_favorite/$", AddFavoriteView.as_view(), name="add_favorite"),
]
- (3) 前端 ajax 部分代码
<script type="text/javascript">
//收藏分享
function add_favorite(current_elem, favorite_id, favorite_type){
$.ajax({
cache: false,
type: "POST",
url:"{% url "org:add_favorite" %}",
data:{'favorite_id':favorite_id, 'favorite_type':favorite_type},
async: true,
// 因为用户点击"收藏"按钮后,向后台提交 POST 请求时,没有通过 form,
// 不能在 form 中定义 {% csrf_token %}, 所以只能在这里把 {{ csrf_token }} 传递进来
beforeSend:function(xhr, settings){
xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
},
success: function(data) {
var data = JSON.parse(data);
if(data.status == "fail"){
if(data.msg == "用户未登录"){
window.location.href="{% url "login" %}";
}else{
alert(data.msg);
}
}else if(data.status == 'success'){
current_elem.text(data.msg);
}
},
});
}
// 监听事件, 用来监听收藏按钮的点击操作
$('.collectionbtn').on('click', function(){
add_favorite($(this), {{ course_org.id }}, 2);
});
</script>
- (4) 另外, 需要在页面加载的时候判断,用户是否已经收藏了该课程机构,所以要在 OrgHomeView、OrgCourseView、OrgDescriptionView、OrgTeacherView 类视图中都同时加上判断
# apps/organization/views.py
class OrgHomeView(View):
"""
机构首页类视图
"""
def get(self, request, org_id):
...
# 判断用户是否收藏该机构, 定义 has_favorite 变量, 默认为 False
has_favorite = False
# 首先要判断用户是否登录, 用户为登录状态才能判断用户是否收藏该机构
if request.user.is_authenticated:
if UserFavorite.objects.filter(user=request.user, favorite_id=course_org.id, favorite_type=2):
has_favorite = True
...
context = {
...
"has_favorite": has_favorite,
}
return render(request, "org-detail-homepage.html", context)
前端模板中判断应该显示"收藏", 还是"已收藏"
# templates/org_base.html
<div class="btn fr collectionbtn notlogin">{% if has_favorite %}已收藏{% else %}收藏{% endif %}</div>
网友评论