课程机构列表页数据显示
step1: 在organization app中的views.py文件中创建OrgView类
from django.shortcuts import render
from django.views.generic.base import View
from .models import CourseOrg, CityDict
# Create your views here.
class OrgView(View):
"""
课程机构列表功能
"""
def get(self, request):
# 获取所有机构信息
all_orgs = CourseOrg.objects.all()
# 获取所有城市信息
all_citys = CityDict.objects.all()
return render(request, 'org-list.html', {'all_orgs': all_orgs, 'all_citys': all_citys})
step2:urls.py
from django.urls import path, re_path, include
from django.views.generic import TemplateView # 使用静态模板 不需要在app下面的views.py文件中配置render
from users import views as users_views
from organization import views as org_views
from django.views.static import serve # 处理静态文件
from MxOnline.settings import MEDIA_ROOT
import xadmin
urlpatterns = [
...
re_path('^org_list/$', org_views.OrgView.as_view(), name='org_list'),
# 配置上传文件的访问处理函数
re_path('^media/(?P<path>.*)$', serve, {'document_root': MEDIA_ROOT}),
]
step3:在MxOnline目录下新建一个media文件夹,用于存储机构logo图片
step4:然后再setting.py中配置此路径,添加如下代码:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
step5:配置前端显示页面org-list.html
{% for 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 }}{{ org.image }}"/>
</a>
</dt>
<div class="buy start_groupbuy jsShowPerfect2" data-id="22"><br/>联系<br/>服务</div>
</dl>
{% endfor %}
step6:上面html文件中引用了setting.py里的MEDIA_URL参数,需要把根目录下的media文件路径配置到html页面中。即在setting.py文件中添加如下配置
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', # 把根目录下的media文件路径配置到html页面中
],
},
},
]
step7:此时发现前端页面中的图片还是无法正常显示,因为需要在url中配置django处理静态文件
from django.views.static import serve # 处理静态文件
from MxOnline.settings import MEDIA_ROOT
import xadmin
urlpatterns = [
...
# 配置上传文件的访问处理函数
re_path('^media/(?P<path>.*)$', serve, {'document_root': MEDIA_ROOT}),
]
此时显示如下:
列表分页功能 -- Paginator
Django 是自带 分页功能(Paginator)的,但是功能不太丰富。pure pagination是在django自带的Paginator基础上进一步的封装。
分页理由:比如上面的OrgView类中 all_orgs = CourseOrg.objects.all()。假设数据库中有1000条数据,这时候不能直接把这1000条数据直接返回给all_orgs的,因为这样会在前端页面上直接显示这1000条数据。所以需要先分页。
GitHub链接 :https://github.com/jamespacileo/django-pure-pagination
step1:安装pure pagination
pip install django-pure-pagination
step2:Add pure_pagination to INSTALLED_APPS
INSTALLED_APPS = (
...
'pure_pagination',
)
step3:修改OrgView类
from pure_pagination import Paginator, EmptyPage, PageNotAnInteger
class OrgView(View):
"""
课程机构列表功能
"""
def get(self, request):
# 获取所有机构信息
all_orgs = CourseOrg.objects.all()
# 对课程机构进行分类
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# Provide Paginator with the request object for complete querystring generation
p = Paginator(all_orgs, 5, request=request) # 每页5条记录
orgs = p.page(page)
org_nums = all_orgs.count()
# 获取所有城市信息
all_citys = CityDict.objects.all()
return render(request, 'org-list.html', {
'all_orgs': orgs,
'all_citys': all_citys,
'org_nums': org_nums,
})
step4:前端页面配置
<div class="pageturn">
<ul class="pagelist">
{% 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="active"><a href="?{{ page.querystring }}">{{ page }}</a></li>
{% else %}
<li><a href="?{{ page.querystring }}" class="page">{{ page }}</a></li>
{% endifequal %}
{% endif %}
{% endfor %}
{% if all_orgs.has_next %}
<li class="long"><a href="?{{ all_orgs.next_page_number.querystring }}">下一页</a></li>
{% endif %}
</ul>
</div>
效果:
列表筛选功能
当用户点击前端页面上一个city名字的时候,我们通过get的方式,把该city的city.id传到后天app中的 视图 view中。
html中代码片段:
{% for city in all_citys %}
<a href="?city={{ city.id }}"><span>{{ city }}</span></a> <!--把city.id以get方式传到后台-->
{% endfor %}
然后再OrgView类中拿到该city_id
from django.shortcuts import render
from django.views.generic.base import View
from .models import CourseOrg, CityDict
from pure_pagination import Paginator, EmptyPage, PageNotAnInteger
class OrgView(View):
"""
课程机构列表功能
"""
def get(self, request):
# 获取所有机构信息
all_orgs = CourseOrg.objects.all()
# 取出筛选 (用户点击的) 城市
city_id = request.GET.get('city', '') # 如果GET字典中没有city这个参数的话,默认返回空串
if city_id: # 如果有此参数,说明用户点击了某个city。然后就进行进一步过滤:即筛选出特定城市的org记录
all_orgs = all_orgs.filter(city_id=int(city_id))
# 对课程机构进行分类
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# Provide Paginator with the request object for complete querystring generation
p = Paginator(all_orgs, 1, request=request) # 每页5条记录
orgs = p.page(page)
org_nums = all_orgs.count()
# 获取所有城市信息
all_citys = CityDict.objects.all()
return render(request, 'org-list.html', {
'all_orgs': orgs,
'all_citys': all_citys,
'org_nums': org_nums,
})
效果如下:点击了北京市之后 -- 只有一条记录满足要求。
然后设置 所点击的城市 处于active激活状态。各个城市名 和 '全部' 只能有一个处于激活状态。处理方式如下:
<div class="cont"> <!-- 当city名没有被点击时,也就没有get方式的参数传递,此时city_id为空串,'全部'按钮被激活 -->
<a href="?ct="><span class="{% ifequal '' city_id %}active2{% endifequal %}">全部</span></a>
{% for city in all_citys %} <!-- 由于city.id是int类型,需要转化为str类型。 下面的stringformat过滤器可以转化为str类型-->
<a href="?city={{ city.id }}">
<span class="{% ifequal city_id city.id|stringformat:'i' %} active2 {% endifequal %}">{{ city }}</span>
</a>
{% endfor %}
</div>
效果:
机构类别筛选
html
<div class="cont">
<a href="?city="><span class="active2">全部</span></a>
<a href="?ct=pxjg"><span class="">培训机构</span></a>
<a href="?ct=gx"><span class="">高校</span></a>
<a href="?ct=gr"><span class="">个人</span></a>
</div>
view
class OrgView(View):
"""
课程机构列表功能
"""
def get(self, request):
# 获取所有机构信息
all_orgs = CourseOrg.objects.all()
# 取出筛选 (用户点击的) 城市
city_id = request.GET.get('city', '') # 如果GET字典中没有city这个参数的话,默认返回空串
if city_id: # 如果有此参数,说明用户点击了某个city。然后就进行进一步过滤:即筛选出特定城市的org记录
all_orgs = all_orgs.filter(city_id=int(city_id))
# 类别筛选
category = request.GET.get('ct', '') # 如果GET字典中没有ct这个参数的话,默认返回空串
if category:
all_orgs = all_orgs.filter(category=category)
# 对课程机构进行分类
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# Provide Paginator with the request object for complete querystring generation
p = Paginator(all_orgs, 5, request=request) # 每页5条记录
orgs = p.page(page)
org_nums = all_orgs.count()
# 获取所有城市信息
all_citys = CityDict.objects.all()
return render(request, 'org-list.html', {
'all_orgs': orgs,
'all_citys': all_citys,
'org_nums': org_nums,
'city_id': city_id, # 将city_id传到前端与 每一条记录的city.id比对。相同的话将此city按钮置于激活状态。
'category': category,
})
效果:
为了让ct和city同时传到后台,修改html代码如下:
<li>
<h2>机构类别</h2>
<div class="cont">
<a href="?city={{ city_id }}"><span class="{% ifequal '' category %}active2{% endifequal %}">全部</span></a>
<a href="?ct=pxjg&city={{ city_id }}"><span class="{% ifequal 'pxjg' category %}active2{% endifequal %}">培训机构</span></a>
<a href="?ct=gx&city={{ city_id }}"><span class="{% ifequal 'gx' category %}active2{% endifequal %}">高校</span></a>
<a href="?ct=gr&city={{ city_id }}"><span class="{% ifequal 'gr' category %}active2{% endifequal %}">个人</span></a>
</div>
</li>
<li>
<h2>所在地区</h2>
<div class="more">更多</div>
<div class="cont"> <!-- 当city名没有被点击时,也就没有get方式的参数传递,此时city_id为空串,'全部'按钮被激活 -->
<a href="?ct={{ category }}"><span class="{% ifequal '' city_id %}active2{% endifequal %}">全部</span></a>
{% for city in all_citys %} <!-- 由于city.id是int类型,需要转化为str类型。 下面的stringformat过滤器可以转化为str类型-->
<a href="?city={{ city.id }}&ct={{ category }}">
<span class="{% ifequal city_id city.id|stringformat:'i' %} active2 {% endifequal %}">{{ city }}</span>
</a>
{% endfor %}
</div>
</li>
效果
授课机构排名 -- 排序功能
view -- 调用了对象集合的排序函数 order_by()
class OrgView(View):
"""
课程机构列表功能
"""
def get(self, request):
# 获取所有机构信息
all_orgs = CourseOrg.objects.all()
hot_orgs = all_orgs.order_by('click_nums')[:5] # 筛选出点击量最多的5条机构记录
...
return render(request, 'org-list.html', {
...
'hot_orgs': hot_orgs,
})
html -- 前端页面 for 循环中用于计数的参数 {{ forloop.counter }}
<div class="right companyrank layout">
<div class="head">授课机构排名</div>
{% for org in hot_orgs %}
<dl class="des">
<dt class="num fl">{{ forloop.counter }}</dt> <!--此参数是for循环中用于计数的-->
<dd>
<a href="/company/2/"><h1>{{ org.name }}</h1></a>
<p>{{ org.address }}</p>
</dd>
</dl>
{% endfor %}
</div>
效果:
按 学习人数 和 课程数 排序
需要在CourseOrg 的Model类中添加上述两个字段
students = models.IntegerField(default=0, verbose_name='学习人数')
course_nums = models.IntegerField(default=0, verbose_name='课程数')
迁移 和 生成表
html -- 需要 机构类别 、 所在地区 选定以后, 再选 (学习人数 或者 课程数 或者 全部)之一的话,之前选的的 机构类别、所在地区 不会变化。
但是 (学习人数 或者 课程数 或者 全部)之一 选定的话,再选机构类别 或者 所在地区,则不管前面所选择的是什么,统一跳到'全部'按钮。
<div class="listoptions">
<ul>
<li>
<h2>机构类别</h2>
<div class="cont">
<a href="?city={{ city_id }}"><span class="{% ifequal '' category %}active2{% endifequal %}">全部</span></a>
<a href="?ct=pxjg&city={{ city_id }}"><span class="{% ifequal 'pxjg' category %}active2{% endifequal %}">培训机构</span></a>
<a href="?ct=gx&city={{ city_id }}"><span class="{% ifequal 'gx' category %}active2{% endifequal %}">高校</span></a>
<a href="?ct=gr&city={{ city_id }}"><span class="{% ifequal 'gr' category %}active2{% endifequal %}">个人</span></a>
</div>
</li>
<li>
<h2>所在地区</h2>
<div class="more">更多</div>
<div class="cont"> <!-- 当city名没有被点击时,也就没有get方式的参数传递,此时city_id为空串,'全部'按钮被激活 -->
<a href="?ct={{ category }}"><span class="{% ifequal '' city_id %}active2{% endifequal %}">全部</span></a>
{% for city in all_citys %} <!-- 由于city.id是int类型,需要转化为str类型。 下面的stringformat过滤器可以转化为str类型-->
<a href="?city={{ city.id }}&ct={{ category }}">
<span class="{% ifequal city_id city.id|stringformat:'i' %} active2 {% endifequal %}">{{ city }}</span>
</a>
{% endfor %}
</div>
</li>
</ul>
</div>
<div class="all">共<span class="key">{{ org_nums }}</span>家</div>
<div class="head">
<ul class="tab_header">
<li class="{% ifequal '' sort %}active{% endifequal %}"><a href="?ct={{ category }}&city={{ city_id }}">全部</a> </li>
<li class="{% ifequal 'students' sort %}active{% endifequal %}"><a href="?sort=students&ct={{ category }}&city={{ city_id }}">学习人数 ↓</a></li>
<li class="{% ifequal 'courses' sort %}active{% endifequal %}"><a href="?sort=courses&ct={{ category }}&city={{ city_id }}">课程数 ↓</a></li>
</ul>
</div>
view
class OrgView(View):
"""
课程机构列表功能
"""
def get(self, request):
# 获取所有机构信息
all_orgs = CourseOrg.objects.all()
...
# 按 学习人数 和 课程数排序
sort = request.GET.get('sort', '')
# 并且 sort 传回前端页面 控制 学习人数 和 课程数 按钮的激活状态
if sort == 'students': # 按学习人数降序
all_orgs = all_orgs.order_by('-students')
elif sort == 'courses': # 按课程数降序
all_orgs = all_orgs.order_by('-course_nums')
# 对课程机构进行分页
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
# Provide Paginator with the request object for complete querystring generation
p = Paginator(all_orgs, 5, request=request) # 每页5条记录
orgs = p.page(page)
org_nums = all_orgs.count()
# 获取所有城市信息
all_citys = CityDict.objects.all()
return render(request, 'org-list.html', {
...
'sort': sort,
})
效果:
强大的ModelForm -- 提交我要学习
前面提到过:用户输入的信息,如用户名密码等 在存入数据库之前需要预判断一下是否满足要求,比如密码长度大于6位,手机号码必须为11位等等。 而这种预判断 我们之前是在各个app下面新建的forms.py文件中自定义Form类实现的。
除了自定义之外,django.forms下面有一个强大的类 -- ModelForm。 继承此类的话 就不需要自定义Form类了。
自定义Form类 如下:
from django import forms
class UserAskForm(forms.Form):
"""
自定义Form类。
"""
name = forms.CharField(required=True, min_length=2)
mobile = forms.CharField(required=True, min_length=11, max_length=11)
course_name = forms.CharField(required=True, min_length=2, max_length=50)
继承ModelForm类 -- 并且 除了可以选择性的引用UserAsk中的字段,也可以按需添加新的字段。
from django import forms
from operation.models import UserAsk
class DjangoUserAskForm(forms.ModelForm): # 继承MedelForm类
class Meta:
model = UserAsk # 传入Model类 -- UserAsk
fields = ['name', 'mobile', 'course_name'] # 直接引用UserAsk类中的字段
配置organization app单独的urls文件
MxOnline urls.py ---- 添加namespace命名空间
# 配置 organization app的urls文件
re_path('^org/', include(('organization.urls', 'organization'), namespace='org')),
organization app 下面的 urls.py
from django.urls import path, re_path, include
from organization import views
urlpatterns = [
path('list/', views.OrgView.as_view(), name='list'),
]
html中的url填写如下:(包含命名空间)
<li class="active" ><a href="{% url 'org:list' %}">授课机构</a></li>
注意:‘我要学习’ 表单的提交 是一种异步操作,不会刷新页面;并且如果某个field字段不满足要求时,应该会有错误提示。
实际上是一种Ajax操作,render函数返回的应该是json数据,而非某个html页面。(因为返回html页面会跳转到改页面。)
此处需要用到 django.http 下面的 HttpResponse 类
注意:这里有个深坑:在Django2中,HttpResponse 中返回json的格式如果按下面这样写,在前端页面无法被正确解析。这样会在ajax中一直执行 error函数,无法进入到 success函数。
return HttpResponse("{'status': 'fail', 'msg': '提交出错'}", content_type='application/json')
要写成下面的格式
return HttpResponse('{"status": "fail", "msg": "添加出错!"}', content_type='application/json')
class AddUserAskView(View):
"""
用户添加咨询 -- 即填写'我要学习'表单
"""
def post(self, request):
userask_form = UserAskForm(data=request.POST) # 实例化
if userask_form.is_valid():
# 不用逐个去除Form里面的field,而可以直接将POST表单数据保存到数据库中。
user_ask = userask_form.save(commit=True)
# 返回一个字符串,并指明格式为 json. (第二个参数为固定写法)
return HttpResponse('{"status": "success"}', content_type='application/json') # 注意此格式的写法,否则前端无法正确解析
else:
# 失败则返回对应的errors
# return HttpResponse("{'status': 'fail', 'msg': {0}}".format(userask_form.errors))
return HttpResponse('{"status": "fail", "msg": "添加出错!"}', content_type='application/json')
html中ajax写法:
<script>
$(function(){
$('#jsStayBtn').on('click', function(){
{#alert($('#jsStayForm').serialize());#}
$.ajax({
cache: false,
type: "POST",
url:"{% url 'org:add_ask' %}",
data:$('#jsStayForm').serialize(),
async: true,
success: function(data) {
alert(data);
if(data.status == 'success'){
$('#jsStayForm')[0].reset();
alert("提交成功")
}else if(data.status == 'fail'){
$('#jsCompanyTips').html(data.msg)
}
},
error: function(data) { //后端传来的json数据无法正确解析的话会一直执行error函数
alert('异常!');
alert(data.status);
},
});
});
})
</script>
配置url
re_path('^add_ask/$', views.AddUserAskView.as_view(), name='add_ask'),
为了自动验证手机号是否合法,可以在view类中判断。但更方便合理的是在Form类中判断。在forms.py 对应的UserAskForm类中添加 clean_mobile方法(固定写法 'clean_' 加上 字段名 )
import re
from django import forms
from operation.models import UserAsk
class UserAskForm(forms.ModelForm): # 继承MedelForm类
class Meta:
model = UserAsk # 传入Model类 -- UserAsk
fields = ['name', 'mobile', 'course_name'] # 直接引用UserAsk类中的字段
def clean_mobile(self):
"""
自动验证手机号是否合法的判断函数
"""
mobile = self.cleaned_data['mobile'] # 固定用法,获取mobile字段
# 验证手机号的正则表达式
REGEX_MOBILE = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$"
p = re.compile(REGEX_MOBILE)
if p.match(mobile): # 验证成功
return mobile
else:
raise forms.ValidationError('手机号码非法', code='mobile_invalid')
效果:
机构详情页 -- 机构首页
view类 -- 需要传进 org_id参数
class OrgHomeView(View):
"""
机构首页
"""
def get(self, request, org_id):
course_org = CourseOrg.objects.get(id=int(org_id))
# 获取某机构所有课程的方法,这里只取前三个
all_courses = course_org.course_set.all()[:3]
all_teachers = course_org.teacher_set.all()[:3]
return render(request, 'org-detail-homepage.html', {
'all_courses': all_courses,
'all_teachers': all_teachers,
'course_org': course_org, # 获取对应id的机构,机构内有logo、机构描述desc等字段。
})
url
re_path('^home/(?P<org_id>\d+)/$', views.OrgHomeView.as_view(), name='org_home'),
org-list.html -- 注意 额外参数传进来的用法。
<dt>
<a href="{% url 'org:org_home' org.id %}"> <!--此处需要一个额外的参数-->
<img width="200" height="120" class="scrollLoading" data-url="{{ MEDIA_URL }}{{ org.image }}"/>
</a>
</dt>
org-detail-homepage.html
顶部机构名字及logo
<div class="middle companyheader">
<div class="wp">
<img class="fl" style="width: 112px;height: 103px" src="{{ MEDIA_URL }}{{ course_org.image }}"/>
<div class="head fl">
<h1>
{{ course_org.name }}
<img src="/static/images/authentication.png"/>
<img src="/static/images/gold.png"/>
</h1>
<p class="fl">
<span class="fl" style="margin-top:8px;color:#848484;">推荐指数: </span>
<span class="precision company-credit" data-star-scope="5.0"></span>
<span class="key">5.0</span>
</p>
</div>
<div class="btn fr collectionbtn notlogin
"data-favid="22" data-fav-type="1">
收藏
</div>
</div>
</div>
全部课程
<div class="brief group_list">
{% for course in all_courses %}
<div class="module1_5 box">
<a href="course-detail.html"><img width="214" src="{{ MEDIA_URL }}{{ course.image }}"/></a>
<div class="des">
<a href="course-detail.html"><h2>{{ course.name }}</h2></a>
<span class="fl">课时:<i class="key">{{ course.learn_times }}</i></span>
<span class="fr">参加人数:{{ course.students }}</span>
</div>
<div class="bottom">
<span class="fl">{{ course.course_org }}</span>
<span class="star fr notlogin
" data-favid="13" data-fav-type="4">
{{ course.fav_nums }} <!--收藏人数-->
</span>
</div>
</div>
{% endfor %}
</div>
机构教师
{% for teacher in all_teachers %}
<div class="diarys">
<div class="module5 share company-diary-box" style="padding:10px 0;">
<div class="left">
<img class="pic" src="{{ MEDIA_URL }}{{ teacher.image }}"/>
<p>昵称:{{ teacher.name }}</p>
</div>
<div class="right">
<div class="top">
<div class="fl">
<a href=""><h1>Django实战教程</h1></a>
<span>发表于:2018-11-29</span>
</div>
</div>
<div class="middle" style="border-bottom:0;">课程介绍</div>
</div>
</div>
</div>
{% endfor %}
机构介绍
<div class="right companycenter layout" >
<div class="head">
<h1>机构介绍</h1>
<a class="green fr more" href="org-detail-desc.html">查看更多 > </a>
</div> <!--desc是机构描述字段-->
<div class="cont"> {{ course_org.desc }}<a href="/company/desc/22/"><span class="green">[查看更多]</span></a></div>
</div>
机构课程页、机构介绍页、机构讲师页
class OrgCourseView(View): # 机构课程页
"""
机构课程页
"""
def get(self, request, org_id):
current_page = 'course'
course_org = CourseOrg.objects.get(id=int(org_id))
# 获取某机构所有课程的方法
all_courses = course_org.course_set.all()
return render(request, 'org-detail-course.html', {
'all_courses': all_courses,
'course_org': course_org, # 获取对应id的机构,机构内有logo、机构描述desc等字段。
'current_page': current_page,
})
class OrgDescView(View): # 机构介绍页
"""
机构介绍页
"""
def get(self, request, org_id):
current_page = 'desc'
course_org = CourseOrg.objects.get(id=int(org_id))
return render(request, 'org-detail-desc.html', {
'course_org': course_org, # 获取对应id的机构,机构内有logo、机构描述desc等字段。
'current_page': current_page,
})
class OrgTeacherView(View): # 机构讲师页
"""
机构讲师页
"""
def get(self, request, org_id):
current_page = 'teacher'
course_org = CourseOrg.objects.get(id=int(org_id))
# 获取某机构所有讲师的方法
all_teachers = course_org.teacher_set.all()
return render(request, 'org-detail-teachers.html', {
'all_teachers': all_teachers,
'course_org': course_org, # 获取对应id的机构,机构内有logo、机构描述desc等字段。
'current_page': current_page,
})
url
re_path('^course/(?P<org_id>\d+)/$', views.OrgCourseView.as_view(), name='org_course'),
re_path('^desc/(?P<org_id>\d+)/$', views.OrgDescView.as_view(), name='org_desc'),
re_path('^teacher/(?P<org_id>\d+)/$', views.OrgTeacherView.as_view(), name='org_teacher'),
课程机构收藏功能 -- 也是一个异步方法,由ajax来完成