(项目)在线教育平台(十一)
十五、首页全局配置
1、首页前端页面配置
将index.html继承base.html页面,修改继承的block地方:
修改base.html页面中导航栏选中状态的代码:
2、全局搜索功能
首页的全局搜索功能可以对课程,机构,教师进行全局搜索,搜索的代码放在deco-common.js文件中:
1 //顶部搜索栏搜索方法 2 function search_click(){ 3 var type = $('#jsSelectOption').attr('data-value'), 4 keywords = $('#search_keywords').val(), 5 request_url = ''; 6 if(keywords == ""){ 7 return 8 } 9 if(type == "course"){ 10 request_url = "/course/list?keywords="+keywords 11 }else if(type == "teacher"){ 12 request_url = "/org/teacher/list?keywords="+keywords 13 }else if(type == "org"){ 14 request_url = "/org/list?keywords="+keywords 15 } 16 window.location.href = request_url 17 }
只需要在课程列表接口、机构列表接口、讲师列表接口中加入搜索的逻辑即可:
1 class CourseListView(View): 2 """课程列表页""" 3 def get(self, request): 4 # 获取所有的课程 5 all_courses = Course.objects.all() 6 7 # 排序(学习人数,点击数) 8 sort = request.GET.get('sort', '') 9 if sort: 10 if sort == 'students': 11 all_courses = all_courses.order_by('-students') 12 elif sort == 'hot': 13 all_courses = all_courses.order_by('-click_nums') 14 15 # 热门课程 16 hot_courses = all_courses.order_by('-click_nums')[:2] 17 18 # 搜索 19 search_keywords = request.GET.get('keywords', '') 20 if search_keywords: 21 all_courses = all_courses.filter(Q(name__icontains=search_keywords)| 22 Q(desc__icontains=search_keywords)| 23 Q(detail__icontains=search_keywords)) 24 25 # 分页 26 try: 27 page = request.GET.get('page', 1) 28 except PageNotAnInteger: 29 page = 1 30 p = Paginator(all_courses, 3, request=request) 31 courses = p.page(page) 32 33 return render(request, 'course-list.html', { 34 'all_courses': courses, 35 'sort': sort, 36 'hot_courses': hot_courses 37 })
1 class OrgView(View): 2 """机构列表""" 3 def get(self, request): 4 # 取出所有的机构 5 all_orgs = CourseOrg.objects.all() 6 7 # 取出所有的城市 8 all_citys = CityDict.objects.all() 9 10 # 搜索 11 search_keywords = request.GET.get('keywords', '') 12 if search_keywords: 13 all_orgs = all_orgs.filter(Q(name__icontains=search_keywords)| 14 Q(desc__icontains=search_keywords)) 15 16 # 排名筛选(根据点击量排名) 17 hot_orgs = all_orgs.order_by('-click_nums')[:3] 18 19 # 学习人数和课程数排名筛选 20 sort = request.GET.get('sort', '') 21 if sort: 22 if sort == 'students': 23 all_orgs = all_orgs.order_by('-students') 24 elif sort == 'courses': 25 all_orgs = all_orgs.order_by('-course_nums') 26 27 # 城市筛选(从request中获取城市的id) 28 city_id = request.GET.get('city', '') 29 if city_id: 30 all_orgs = all_orgs.filter(city_id=int(city_id)) 31 32 # 类别筛选(从request中获取机构类别ct) 33 category = request.GET.get('ct', '') 34 if category: 35 all_orgs = all_orgs.filter(category=category) 36 37 # 筛选完再统计数量 38 org_nums = all_orgs.count() 39 40 # 分页 41 try: 42 page = request.GET.get('page', 1) 43 except PageNotAnInteger: 44 page = 1 45 p = Paginator(all_orgs, 5, request=request) 46 orgs = p.page(page) 47 48 return render(request, 'org-list.html', { 49 'all_orgs': orgs, 50 'all_citys': all_citys, 51 'org_nums': org_nums, 52 'city_id': city_id, 53 'category': category, 54 'hot_orgs': hot_orgs, 55 'sort': sort 56 })
1 class TeacherListView(View): 2 """讲师列表页面""" 3 def get(self, request): 4 # 获取所有的讲师 5 all_teachers = Teacher.objects.all() 6 7 # 搜索 8 search_keywords = request.GET.get('keywords', '') 9 if search_keywords: 10 all_teachers = all_teachers.filter(name__icontains=search_keywords) 11 12 # 统计讲师的总数 13 teacher_nums = all_teachers.count() 14 15 # 排序,按点击数排序 16 sort = request.GET.get('sort', '') 17 if sort: 18 if sort == 'hot': 19 all_teachers = all_teachers.order_by('-click_nums') 20 21 # 讲师排行版 22 teacher_sorted = all_teachers.order_by('-click_nums')[:3] 23 24 # 分页 25 try: 26 page = request.GET.get('page', 1) 27 except PageNotAnInteger: 28 page = 1 29 p = Paginator(all_teachers, 5, request=request) 30 teachers = p.page(page) 31 32 return render(request, 'teachers-list.html', { 33 'all_teachers': teachers, 34 'teacher_nums': teacher_nums, 35 'sort': sort, 36 'teacher_sorted': teacher_sorted 37 })
十六、个人中心
1、个人中心页面
1.1 个人中心前端页面配置
在templates目录下新建usercenter-base.html文件,作为个人中心页面的母版,然后将usercenter-info.html页面拷贝到该目录下,并把其中的内容拷贝到母版中,修改其中需要block的地方:
1 {% load staticfiles %} 2 3 <!DOCTYPE html> 4 <html> 5 6 <head> 7 <meta charset="UTF-8"> 8 <meta name="renderer" content="webkit"> 9 <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" > 10 <title>{% block title %}个人信息- 知能网{% endblock %}</title> 11 <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}"> 12 <link rel="stylesheet" type="text/css" href="{% static 'css/animate.css' %}"> 13 <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}"> 14 <link rel="stylesheet" type="text/css" href="{% static 'js/plugins/queryCity/css/cityLayout.css' %}"> 15 16 <link rel="stylesheet" type="text/css" href="{% static 'css/lq.datetimepick.css' %}"/> 17 {% block custom_css %}{% endblock %} 18 19 20 <script src="{% static 'js/jquery.min.js' %}" type="text/javascript"></script> 21 <script src="{% static 'js/jquery-migrate-1.2.1.min.js' %}" type="text/javascript"></script> 22 23 </head> 24 <body> 25 <section class="headerwrap headerwrap2"> 26 <header> 27 <div class="header2 header"> 28 <div class="top"> 29 <div class="wp"> 30 <div class="fl"><p>服务电话:<b>13993601652</b></p></div> 31 <!--登录后跳转--> 32 33 34 <div class="personal"> 35 <dl class="user fr"> 36 <dd>bobby<img class="down fr" src="{% static 'images/top_down.png' %}"/></dd> 37 <dt><img width="20" height="20" src="{% static 'media/image/2016/12/default_big_14.png' %}"/></dt> 38 </dl> 39 <div class="userdetail"> 40 <dl> 41 <dt><img width="80" height="80" src="{% static 'media/image/2016/12/default_big_14.png' %}"/></dt> 42 <dd> 43 <h2>django</h2> 44 <p>bobby</p> 45 </dd> 46 </dl> 47 <div class="btn"> 48 <a class="personcenter fl" href="usercenter-info.html">进入个人中心</a> 49 <a class="fr" href="/logout/">退出</a> 50 </div> 51 </div> 52 </div> 53 <a href="usercenter-message.html"> 54 <div class="msg-num"><span id="MsgNum">0</span></div> 55 </a> 56 57 58 </div> 59 </div> 60 61 <div class="middle"> 62 <div class="wp"> 63 <a href="index.html"><img class="fl" src="{% static 'images/logo2.png' %}"/></a> 64 <h1>我的知能网</h1> 65 </div> 66 </div> 67 </div> 68 </header> 69 </section> 70 71 72 73 <!--crumbs start--> 74 {% block custom_bread %} 75 76 {% endblock %} 77 78 79 <section> 80 <div class="wp list personal_list"> 81 <div class="left"> 82 <ul> 83 <li class="active2"><a href="usercenter-info.html">个人资料</a></li> 84 <li ><a href="usercenter-mycourse.html">我的课程</a></li> 85 <li ><a href="usercenter-fav-course.html">我的收藏</a></li> 86 <li > 87 <a href="usercenter-message.html" style="position: relative;"> 88 我的消息 89 </a> 90 </li> 91 </ul> 92 </div> 93 94 95 {% block custom_right_content %} 96 97 {% endblock %} 98 99 100 </div> 101 </section> 102 103 <!--sidebar start--> 104 <section> 105 <ul class="sidebar"> 106 <li class="qq"> 107 <a target="_blank" href="http://wpa.qq.com/msgrd?v=3&uin=2023525077&site=qq&menu=yes"></a> 108 </li> 109 <li class="totop"></li> 110 </ul> 111 </section> 112 <!--sidebar end--> 113 <!--header start--> 114 115 <div class="dialog" id="jsDialog"> 116 <div class="successbox dialogbox" id="jsSuccessTips"> 117 <h1>成功提交</h1> 118 <div class="close jsCloseDialog"><img src="../images/dig_close.png"/></div> 119 <div class="cont"> 120 <h2>您的需求提交成功!</h2> 121 <p></p> 122 </div> 123 </div> 124 <!--提示弹出框--> 125 <div class="bidtips dialogbox promptbox" id="jsComfirmDialig"> 126 <h1>确认提交</h1> 127 <div class="close jsCloseDialog"><img src="../images/dig_close.png"/></div> 128 <div class="cont"> 129 <h2>您确认提交吗?</h2> 130 <dd class="autoTxtCount"> 131 <div class="button"> 132 <input type="button" class="fl half-btn" value="确定" id="jsComfirmBtn"/> 133 <span class="fr half-btn jsCloseDialog">取消</span> 134 </div> 135 </dd> 136 </div> 137 </div> 138 <div class="resetpwdbox dialogbox" id="jsResetDialog"> 139 <h1>修改密码</h1> 140 <div class="close jsCloseDialog"><img src="../images/dig_close.png"/></div> 141 <div class="cont"> 142 <form id="jsResetPwdForm" autocomplete="off"> 143 <div class="box"> 144 <span class="word2" >新 密 码</span> 145 <input type="password" id="pwd" name="password1" placeholder="6-20位非中文字符"/> 146 </div> 147 <div class="box"> 148 <span class="word2" >确定密码</span> 149 <input type="password" id="repwd" name="password2" placeholder="6-20位非中文字符"/> 150 </div> 151 <div class="error btns" id="jsResetPwdTips"></div> 152 <div class="button"> 153 <input id="jsResetPwdBtn" type="button" value="提交" /> 154 </div> 155 <input type='hidden' name='csrfmiddlewaretoken' value='DaP7IUKm9FA9nELA9YUlYYWpyIDdCiIP' /> 156 <input type='hidden' name='csrfmiddlewaretoken' value='799Y6iPeEDNSGvrTu3noBrO4MBLv6enY' /> 157 </form> 158 </div> 159 </div> 160 <div class="dialogbox changeemai1 changephone" id="jsChangeEmailDialog"> 161 <h1>修改邮箱</h1> 162 <div class="close jsCloseDialog"><img src="../images/dig_close.png"/></div> 163 <p>请输入新的邮箱地址</p> 164 <form id="jsChangeEmailForm" autocomplete="off"> 165 <div class="box"> 166 <input class="fl change_email" name="email" id="jsChangeEmail" type="text" placeholder="输入重新绑定的邮箱地址"> 167 </div> 168 <div class="box"> 169 <input class="fl email_code" type="text" id="jsChangeEmailCode" name="code" placeholder="输入邮箱验证码"> 170 <input class="getcode getting" type="button" id="jsChangeEmailCodeBtn" value="获取验证码"> 171 </div> 172 <div class="error btns change_email_tips" id="jsChangeEmailTips" >请输入...</div> 173 <div class="button"> 174 <input class="changeemai_btn" id="jsChangeEmailBtn" type="button" value="完成"/> 175 </div> 176 <input type='hidden' name='csrfmiddlewaretoken' value='DaP7IUKm9FA9nELA9YUlYYWpyIDdCiIP' /> 177 <input type='hidden' name='csrfmiddlewaretoken' value='799Y6iPeEDNSGvrTu3noBrO4MBLv6enY' /> 178 </form> 179 </div> 180 181 <div class="noactivebox dialogbox" id="jsUnactiveForm" > 182 <h1>邮件验证提示</h1> 183 <div class="close jsCloseDialog"><img src="../images/dig_close.png"/></div> 184 <div class="center"> 185 <img src="../images/send.png"/> 186 <p>我们已经向您的邮箱<span class="green" id="jsEmailToActive">12@13.com</span>发送了邮件,<br/>为保证您的账号安全,请及时验证邮箱</p> 187 <p class="a"><a class="btn" id="jsGoToEmail" target="_blank" href="http://mail.qq.com">去邮箱验证</a></p> 188 <p class="zy_success upmove"></p> 189 <p style="display: none;" class="sendE2">没收到,您可以查看您的垃圾邮件和被过滤邮件,也可以再次发送验证邮件(<span class="c5c">60s</span>)</p> 190 <p class="sendE">没收到,您可以查看您的垃圾邮件和被过滤邮件,<br/>也可以<span class="c5c green" id="jsSenEmailAgin" style="cursor: pointer;">再次发送验证邮件</span></p> 191 </div> 192 </div> 193 <div class="resetpassbox dialogbox" id="jsSetNewPwd"> 194 <h1>重新设置密码</h1> 195 <div class="close jsCloseDialog"><img src="../images/dig_close.png"/></div> 196 <p class="green">请输入新密码</p> 197 <form id="jsSetNewPwdForm"> 198 <div class="box"> 199 <span class="word2">密 码</span> 200 <input type="password" name="password" id="jsResetPwd" placeholder="请输入新密码"/> 201 </div> 202 <div class="box"> 203 <span class="word2">确 认 密 码</span> 204 <input type="password" name="password2" id="jsResetPwd2" placeholder="请再次输入新密码"/> 205 </div> 206 <div class="box"> 207 <span class="word2">验 证 码</span> 208 <input type="text" name="code" id="jsResetCode" placeholder="请输入手机验证码"/> 209 </div> 210 <div class="error btns" id="jsSetNewPwdTips"></div> 211 <div class="button"> 212 <input type="hidden" name="mobile" id="jsInpResetMobil" /> 213 <input id="jsSetNewPwdBtn" type="button" value="提交" /> 214 </div> 215 <input type='hidden' name='csrfmiddlewaretoken' value='DaP7IUKm9FA9nELA9YUlYYWpyIDdCiIP' /> 216 </form> 217 </div> 218 <div class="forgetbox dialogbox"> 219 <h1>忘记密码</h1> 220 <div class="close jsCloseDialog"><img src="../images/dig_close.png"/></div> 221 <div class="cont"> 222 <form id="jsFindPwdForm" autocomplete="off"> 223 <input type='hidden' name='csrfmiddlewaretoken' value='DaP7IUKm9FA9nELA9YUlYYWpyIDdCiIP' /> 224 <div class="box"> 225 <span class="word2" >账 号</span> 226 <input type="text" id="account" name="account" placeholder="手机/邮箱"/> 227 </div> 228 <div class="box"> 229 <span class="word3">验证码</span> 230 <input autocomplete="off" class="form-control-captcha find-password-captcha" id="find-password-captcha_1" name="captcha_f_1" placeholder="请输入验证码" type="text" /> <input class="form-control-captcha find-password-captcha" id="find-password-captcha_0" name="captcha_f_0" placeholder="请输入验证码" type="hidden" value="5f3c00e47fb1be12d2fd15b9a860711597721b3f" /> <img src="/captcha/image/5f3c00e47fb1be12d2fd15b9a860711597721b3f/" alt="captcha" class="captcha" /> 231 </div> 232 <div class="error btns" id="jsForgetTips"></div><!--忘记密码错误--> 233 <div class="button"> 234 <input type="hidden" name="sms_type" value="1"> 235 <input id="jsFindPwdBtn" type="button" value="提交" /> 236 </div> 237 </form> 238 </div> 239 </div> 240 </div> 241 <div class="bg" id="dialogBg"></div> 242 243 244 <script src="{% static 'js/selectUi.js' %}" type='text/javascript'></script> 245 <script type="text/javascript" src="{% static 'js/plugins/laydate/laydate.js' %}"></script> 246 <script src="{% static 'js/plugins/layer/layer.js' %}"></script> 247 <script src="{% static 'js/plugins/queryCity/js/public.js' %}" type="text/javascript"></script> 248 <script src="{% static 'js/unslider.js' %}" type="text/javascript"></script> 249 <script src="{% static 'js/plugins/jquery.scrollLoading.js' %}" type="text/javascript"></script> 250 <script src="{% static 'js/validateDialog.js' %}" type="text/javascript"></script> 251 <script src="{% static 'js/deco-common.js' %}" type="text/javascript"></script> 252 253 <script src='{% static 'js/plugins/jquery.upload.js' %}' type='text/javascript'></script> 254 <script src="{% static 'js/validate.js' %}" type="text/javascript"></script> 255 <script src="{% static 'js/deco-user.js' %}"></script> 256 257 258 {% block custom_js %} 259 260 {% endblock %} 261 262 <script type="text/javascript"> 263 $('.jsDeleteFav_course').on('click', function(){ 264 var _this = $(this), 265 favid = _this.attr('data-favid'); 266 alert(favid) 267 $.ajax({ 268 cache: false, 269 type: "POST", 270 url: "/org/add_fav/", 271 data: { 272 fav_type: 1, 273 fav_id: favid, 274 csrfmiddlewaretoken: '799Y6iPeEDNSGvrTu3noBrO4MBLv6enY' 275 }, 276 async: true, 277 success: function(data) { 278 Dml.fun.winReload(); 279 } 280 }); 281 }); 282 283 $('.jsDeleteFav_teacher').on('click', function(){ 284 var _this = $(this), 285 favid = _this.attr('data-favid'); 286 $.ajax({ 287 cache: false, 288 type: "POST", 289 url: "/org/add_fav/", 290 data: { 291 fav_type: 3, 292 fav_id: favid, 293 csrfmiddlewaretoken: '799Y6iPeEDNSGvrTu3noBrO4MBLv6enY' 294 }, 295 async: true, 296 success: function(data) { 297 Dml.fun.winReload(); 298 } 299 }); 300 }); 301 302 303 $('.jsDeleteFav_org').on('click', function(){ 304 var _this = $(this), 305 favid = _this.attr('data-favid'); 306 $.ajax({ 307 cache: false, 308 type: "POST", 309 url: "/org/add_fav/", 310 data: { 311 fav_type: 2, 312 fav_id: favid, 313 csrfmiddlewaretoken: '799Y6iPeEDNSGvrTu3noBrO4MBLv6enY' 314 }, 315 async: true, 316 success: function(data) { 317 Dml.fun.winReload(); 318 } 319 }); 320 }); 321 </script> 322 323 324 <script> 325 var shareUrl = '', 326 shareText = '', 327 shareDesc = '', 328 shareComment = ''; 329 $(function () { 330 $(".bdsharebuttonbox a").mouseover(function () { 331 var type = $(this).attr('data-cmd'), 332 $parent = $(this).parent('.bdsharebuttonbox'), 333 fxurl = $parent.attr('data-url'), 334 fxtext = $parent.attr('data-text'), 335 fxdesc = $parent.attr('data-desc'), 336 fxcomment = $parent.attr('data-comment'); 337 switch (type){ 338 case 'tsina': 339 case 'tqq': 340 case 'renren': 341 shareUrl = fxurl; 342 shareText = fxdesc; 343 shareDesc = ''; 344 shareComment = ''; 345 break; 346 default : 347 shareUrl = fxurl; 348 shareText = fxtext; 349 shareDesc = fxdesc; 350 shareComment = fxcomment; 351 break; 352 } 353 }); 354 }); 355 function SetShareUrl(cmd, config) { 356 if (shareUrl) { 357 config.bdUrl = "" + shareUrl; 358 } 359 if(shareText){ 360 config.bdText = shareText; 361 } 362 if(shareDesc){ 363 config.bdDesc = shareDesc; 364 } 365 if(shareComment){ 366 config.bdComment = shareComment; 367 } 368 369 return config; 370 } 371 window._bd_share_config = { 372 "common": { 373 "onBeforeClick":SetShareUrl, 374 "bdPic":"", 375 "bdMini":"2", 376 "searchPic":"1", 377 "bdMiniList":false 378 }, 379 "share": { 380 "bdSize":"16" 381 } 382 }; 383 with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com../api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)]; 384 </script> 385 </body> 386 </html>
然后将usercenter-info.html页面继承母版:
1.2 个人信息接口
在users/views.py中编写个人中心的接口:
1 class UserInfoView(View): 2 """个人中心页面""" 3 def get(self, request): 4 return render(request, 'usercenter-info.html', { 5 6 })
先在users下新建urls.py文件,然后在MxOnline/urls.py中配置个人信息的路由分发:
1 urlpatterns = [ 2 path('users/', include('users.urls', namespace='users')), # 个人信息 3 ]
在users/urls.py中配置个人中心页面的url:
1 from django.urls import path 2 3 from .views import UserInfoView 4 5 6 app_name = 'users' 7 8 urlpatterns = [ 9 path('info/', UserInfoView.as_view(), name='user_info'), # 个人中心 10 ]
修改个人中心页面中显示个人信息的代码:
要完善个人信息接口,首先需要在form.py中加入个人信息的form表单验证:
1 class UserInfoForm(forms.ModelForm): 2 """个人信息表单验证""" 3 class Meta: 4 model = UserProfile 5 fields = ['nick_name', 'gender', 'birthday', 'address', 'mobile']
然后完善个人信息接口中更新个人信息的逻辑:
1 class UserInfoView(View): 2 """个人中心页面""" 3 def get(self, request): 4 return render(request, 'usercenter-info.html') 5 6 def post(self, request): 7 userinfo_form = UserInfoForm(request.POST, instance=request.user) 8 if userinfo_form.is_valid(): 9 userinfo_form.save() 10 return HttpResponse('{"status": "success"}', content_type='application/json') 11 else: 12 return HttpResponse(json.dumps(userinfo_form.errors), content_type='application/json')
然后修改个人中心页面中显示个人信息的代码,加上{% csrf_token %}:
1.3 修改用户头像接口
首先在form.py文件中加入头像的ModelForm表单验证:
1 class UploadImageForm(forms.ModelForm): 2 """修改用户头像""" 3 class Meta: 4 model = UserProfile 5 fields = ['image']
然后完成修改用户头像的接口:
1 class UploadImageView(LoginRequiredMixin, View): 2 """用户头像修改""" 3 def post(self, request): 4 # 上传的文件都在request.FILES里面获取 5 image_form = UploadImageForm(request.POST, request.FILES) 6 if image_form.is_valid(): 7 # 将获取到的图片保存到数据库 8 image = image_form.cleaned_data['image'] 9 request.user.image = image 10 request.user.save() 11 12 return HttpResponse('{"status": "success"}', content_type='application/json') 13 else: 14 return HttpResponse('{"status": "fail"}', content_type='application/json')
配置url:
1 from .views import UploadImageView 2 3 4 urlpatterns = [ 5 path('image/upload', UploadImageView.as_view(), name='image_upload'), # 修改头像 6 ]
修改个人中心页面显示用户头像的代码:
1.4 修改密码接口
1 class UpdatePwdView(LoginRequiredMixin, View): 2 """修改密码""" 3 def post(self, request): 4 modify_form = ModifyPwdForm(request.POST) 5 if modify_form.is_valid(): 6 # 从request中获取密码 7 pwd1 = request.POST.get('password1', '') 8 pwd2 = request.POST.get('password2', '') 9 if pwd1 != pwd2: 10 return HttpResponse('{"status": "fail", "mag": "密码不一致"}', content_type='application/json') 11 12 # 保存密码 13 user = request.user 14 user.password = make_password(pwd2) 15 user.save() 16 17 return HttpResponse('{"status": "success"}', content_type='application/json') 18 else: 19 return HttpResponse(json.dumps(modify_form.errors), content_type='application/json')
配置url:
1 from .views import UpdatePwdView 2 3 urlpatterns = [ 4 path('update/pwd/', UpdatePwdView.as_view(), name='update_pwd'), # 修改密码 5 ]
修改密码的ajax代码在deco-user.js文件中,现在只需要在usercenter-base.html页面中修改密码的form表单中加上{% csrf_token %}:
修改完成之后需要重新登录,现在修改右上角的登录状态以及显示信息,在usercenter-base.html页面中:
同时将base.html页面和org_base.html页面也修改了。
1.5 发送邮箱验证码接口
首先在邮箱验证码的model中添加SEND_CHOICES的选项,并将发送类型字段的长度改成30:
1 class EmailVerifyRecord(models.Model): 2 """邮箱验证码""" 3 SEND_CHOICES = ( 4 ('register', '注册'), 5 ('forget', '找回密码'), 6 ('update_email', '修改邮箱') 7 ) 8 9 code = models.CharField('验证码', max_length=20) 10 email = models.EmailField('邮箱', max_length=50) 11 send_type = models.CharField('发送类型', max_length=30, choices=SEND_CHOICES) 12 send_time = models.DateTimeField('发送时间', default=datetime.now) 13 14 class Meta: 15 verbose_name = '邮箱验证码' 16 verbose_name_plural = verbose_name
迁移数据库。
1 class SendEmailCodeView(LoginRequiredMixin, View): 2 """发送邮箱验证码""" 3 def get(self, request): 4 # 从request中获取email 5 email = request.GET.get('email', '') 6 7 # 判断邮箱是否已经存在 8 if UserProfile.objects.filter(email=email): 9 return HttpResponse('{"email": "邮箱已存在"}', content_type='application/json') 10 11 # 发送邮件 12 send_register_email(email, 'update_email') 13 return HttpResponse('{"status": "success"}', content_type='application/json')
配置url:
from .views import SendEmailCodeView urlpatterns = [ path("sendemail_code/", SendEmailCodeView.as_view(),name='sendemail_code'), # 发送邮箱验证码 ]
发送邮件的ajax代码在dec-user.js中,在utils/email_send.py中增加发送邮件的内容:
1 if send_type == 'update_email': 2 email_title = "知能网邮箱修改验证码" 3 email_body = "你的邮箱验证码为{0}".format(code) 4 5 send_status = send_mail(email_title, email_body, EMAIL_FROM, [email]) 6 if send_status: 7 pass
1.6 修改邮箱接口
1 class UpdateEmailView(LoginRequiredMixin, View): 2 """修改邮箱""" 3 def post(self, request): 4 # 从request中获取email和code 5 email = request.POST.get('email', '') 6 code = request.POST.get('code', '') 7 8 # 在数据库中查找是否已有记录 9 existed_records = EmailVerifyRecord.objects.filter(email=email, code=code, send_type='update_email') 10 if existed_records: 11 # 修改邮箱 12 user = request.user 13 user.email = email 14 user.save() 15 16 return HttpResponse('{"status": "success"}', content_type='application/json') 17 else: 18 return HttpResponse('{"email": "验证码无效"}', content_type='application/json')
配置url:
1 from .views import UpdateEmailView 2 3 urlpatterns = [ 4 path("update_email/", UpdateEmailView.as_view(), name='update_email'), # 修改邮箱 5 ]
修改邮箱的ajax代码在dec-user.js中,然后在usercenter-base.html中显示修改邮箱代码加上{% csrf_token %}:
2、我的课程页面
2.1 前端页面配置
将前端页面usercenter-mycourse.html拷贝到templates下。
然后继承usercenter-base.html页面,重写需要block的地方:
2.2 我的课程接口
1 class MyCourseView(LoginRequiredMixin, View): 2 """我的课程页面""" 3 def get(self, request): 4 # 获取用户的课程 5 user_courses = UserCourse.objects.filter(user=request.user) 6 7 return render(request, 'usercenter-mycourse.html', { 8 'user_courses': user_courses 9 })
配置url:
1 urlpatterns = [ 2 path('mycourse/', MyCourseView.as_view(), name='mycourse'), # 我的课程 3 ]
然后修改usercenter.html页面中跳转到我的课程页面的url:
修改我的课程页面中显示我的课程的代码:
3、我的收藏页面
3.1 课程机构页面
前端页面配置,先将usercenter-fav-org.html页面拷贝到templates下,继承usercenter-base.html页面,重写需要block的地方:
我的收藏-机构接口编写:
1 class MyFavOrgView(LoginRequiredMixin, View): 2 """我的收藏 - 机构""" 3 def get(self, request): 4 # 存放用户收藏的机构对象 5 org_list = [] 6 7 # 从UserFavorite中获取用户收藏机构的id 8 fav_orgs = UserFavorite.objects.filter(user=request.user, fav_type=2) 9 10 # 根据id将机构对象放到org_list中 11 for fav_org in fav_orgs: 12 org_id = fav_org.fav_id 13 org = CourseOrg.objects.get(id=org_id) 14 org_list.append(org) 15 16 return render(request, 'usercenter-fav-org.html', { 17 'org_list': org_list 18 })
配置url:
1 urlpatterns = [ 2 path('myfav/org/', MyFavOrgView.as_view(), name="myfav_org"), # 我的收藏 - 机构 3 ]
修改usercenter-base.html页面中跳转到我的收藏的url:
修改我的收藏页面显示收藏机构的代码:
3.2 授课教师页面
前端页面配置,先将usercenter-fav-teacher.html页面拷贝到templates下,继承usercenter-base.html页面,重写需要block的地方:
我的收藏 - 教师接口编写:
1 class MyFavTeacherView(LoginRequiredMixin, View): 2 """我的收藏 - 教师""" 3 def get(self, request): 4 teacher_list = [] 5 fav_teachers = UserFavorite.objects.filter(user=request.user, fav_type=3) 6 for fav_teacher in fav_teachers: 7 teacher_id = fav_teacher.fav_id 8 teacher = Teacher.objects.get(id=teacher_id) 9 teacher_list.append(teacher) 10 11 return render(request, 'usercenter-fav-teacher.html', { 12 'teacher_list': teacher_list 13 })
配置url:
1 urlpatterns = [ 2 path('myfav/teacher/', MyFavTeacherView.as_view(), name="myfav_teacher"), # 我的收藏 - 教师 3 ]
该页面需要显示教师课程的数量,需要在teacher的model中添加获取课程数量的函数:
1 class Teacher(models.Model): 2 """机构老师""" 3 org = models.ForeignKey(CourseOrg, verbose_name='所属机构', on_delete=models.CASCADE) 4 name = models.CharField('老师名', max_length=50) 5 age = models.IntegerField('年龄', default=25) 6 work_years =models.IntegerField('工作年限', default=0) 7 work_company = models.CharField('就职公司', max_length=50) 8 work_position = models.CharField('工作职位', max_length=50) 9 points = models.CharField('教学特点', max_length=50) 10 click_nums = models.IntegerField('点击数', default=0) 11 fav_nums = models.IntegerField('收藏数', default=0) 12 image = models.ImageField('头像', upload_to='teacher/%Y/%m', max_length=100, default='') 13 add_time = models.DateTimeField('添加时间', default=datetime.now) 14 15 class Meta: 16 verbose_name = '教师' 17 verbose_name_plural = verbose_name 18 19 # 获取教师课程的数量 20 def get_course_nums(self): 21 return self.course_set.all().count() 22 23 def __str__(self): 24 return '[{}]机构的教师:{}'.format(self.org.name, self.name)
在课程机构页面修改跳转到授课教师的url:
修改授课教师页面中显示授课教师的代码:
3.3 公开课程页面
前端页面配置,先将usercenter-fav-course.html页面拷贝到templates下,继承usercenter-base.html页面,重写需要block的地方:
我的收藏 - 课程接口编写:
1 class MyFavCourseView(LoginRequiredMixin, View): 2 """我的收藏 - 课程""" 3 def get(self, request): 4 course_list = [] 5 fav_courses = UserFavorite.objects.filter(user=request.user, fav_type=1) 6 for fav_course in fav_courses: 7 course_id = fav_course.fav_id 8 course = Course.objects.get(id=course_id) 9 course_list.append(course) 10 11 return render(request, 'usercenter-fav-course.html', { 12 'course_list': course_list 13 })
配置url:
1 urlpatterns = [ 2 path('myfav/course/', MyFavCourseView.as_view(), name="myfav_course"), # 我的收藏 - 课程 3 ]
修改公开课程页面显示收藏课程的代码:
最后不要忘记修改各个页面之间跳转的url。
4、我的消息页面
4.1 前端页面配置
将前端页面usercenter-message.html页面拷贝到templates下,继承usercenter-base.html页面,重写需要block的地方:
4.2 我的消息接口
1 class MyMessageView(LoginRequiredMixin, View): 2 """我的消息页面""" 3 def get(self, request): 4 # 获取用户的消息 5 all_message = UserMessage.objects.filter(user=request.user.id) 6 7 # 分页 8 try: 9 page = request.GET.get('page', 1) 10 except PageNotAnInteger: 11 page = 1 12 p = Paginator(all_message, 5, request=request) 13 messages = p.page(page) 14 15 return render(request, 'usercenter-message.html', { 16 'messages': messages 17 })
配置url:
1 urlpatterns = [ 2 path('my_message/', MyMessageView.as_view(), name="my_message"), # 我的消息 3 ]
在usercenter-base.html页面中修改跳转到我的消息页面的url:
修改我的消息页面显示消息的代码:
分页显示:
5、登出功能
在views.py中编写登出的接口:
1 class LogoutView(View): 2 """登出功能""" 3 def get(self, request): 4 # 使用django的logout函数登出 5 logout(request) 6 7 # 将页面重定向到index 8 return HttpResponseRedirect(reverse('index'))
在MxOnline/urls.py中配置url:
1 urlpatterns = [ 2 path('logout/', LogoutView.as_view(), name='logout'), # 登出 3 ]
然后在三个base页面修改登出的url。
6、点击数、收藏数
在用户点击开始学习之后,学习人数需要加1,在课程章节接口CourseLessonView中完善加1操作:
1 class CourseLessonView(LoginRequiredMixin, View): 2 """课程章节""" 3 def get(self, request, course_id): 4 # 根据前端传递的课程id找到对应的课程 5 course = Course.objects.get(id=int(course_id)) 6 7 # 学习人数+1 8 course.students += 1 9 course.save() 10 11 # 课程和用户关联,先判断用户是否已经学习过该课程 12 user_courses = UserCourse.objects.filter(user=request.user, course=course) 13 if not user_courses: 14 # 没有学习过该课程,关联 15 user_course = UserCourse(user=request.user, course=course) 16 user_course.save() 17 18 # 获取所有的资源文件 19 all_resources = CourseResourse.objects.filter(course=course) 20 21 # 相关课程推荐 22 # 找到学习这门课程的所有用户 23 user_courses = UserCourse.objects.filter(course=course) 24 # 找到学习这门课的所有用户的id 25 user_ids = [user_course.user_id for user_course in user_courses] 26 # 通过所有用户的id,找到所有用户学习过的所有课程 27 all_user_courses = UserCourse.objects.filter(user_id__in=user_ids) 28 # 取出所有课程id 29 course_ids = [all_user_course.course_id for all_user_course in all_user_courses] 30 # 通过所有课程id找到所有的课程,按点击量区5个 31 relate_courses = Course.objects.filter(id__in=course_ids).order_by('-click_nums')[:5] 32 33 return render(request, 'course-video.html', { 34 'course': course, 35 'all_resources': all_resources, 36 'relate_courses': relate_courses 37 })
在点击教师进入教师详情页面,教师的点击数加1,在教师详情接口TeacherDetailView中完善加1逻辑:
1 class TeacherDetailView(View): 2 """讲师详情页面""" 3 def get(self, request, teacher_id): 4 # 根据前端的讲师id找到对应的讲师 5 teacher = Teacher.objects.get(id=int(teacher_id)) 6 7 # 老师点击数加1 8 teacher.click_nums += 1 9 teacher.save() 10 11 # 获取该老师所有的课程 12 all_courses = Course.objects.filter(teacher=teacher) 13 14 # 讲师排行 15 teacher_sorted = Teacher.objects.all().order_by('-click_nums')[:3] 16 17 # 讲师收藏、机构收藏 18 has_teahcer_faved = False 19 if UserFavorite.objects.filter(user=request.user, fav_type=3, fav_id=teacher_id): 20 has_teahcer_faved = True 21 has_org_faved = False 22 if UserFavorite.objects.filter(user=request.user, fav_type=2, fav_id=teacher.org.id): 23 has_org_faved = True 24 25 return render(request, 'teacher-detail.html', { 26 'teacher': teacher, 27 'all_courses': all_courses, 28 'teacher_sorted': teacher_sorted, 29 'has_teahcer_faved': has_teahcer_faved, 30 'has_org_faved': has_org_faved 31 })
在点击机构进入机构详情页,机构的点击数加1,在机构详情接口OrgHomeView中完善点击数加1逻辑:
1 class OrgHomeView(View): 2 """机构首页页面""" 3 def get(self, request, org_id): 4 # 根据前端的id找机构 5 org = CourseOrg.objects.get(id=int(org_id)) 6 7 # 点击数加1 8 org.click_nums += 1 9 org.save() 10 11 # 反向查询机构所有的课程和教师 12 all_courses = org.course_set.all() 13 all_teachers =org.teacher_set.all() 14 15 # 标记 16 current_page = 'home' 17 18 # 判断收藏状态 19 has_fav = False 20 if request.user.is_authenticated: 21 if UserFavorite.objects.filter(user=request.user, fav_id=org.id, fav_type=2): 22 has_fav = True 23 24 return render(request, 'org-detail-homepage.html', { 25 'org': org, 26 'all_courses': all_courses, 27 'all_teachers': all_teachers, 28 'current_page': current_page, 29 'has_fav': has_fav 30 })
用户在进行收藏和取消收藏时都需要对收藏数进行加和减的操作,在机构收藏接口OrgFavView中完善收藏数的逻辑:
1 class OrgFavView(View): 2 """机构收藏""" 3 def post(self, request): 4 # 从request中获取收藏的机构id和类型 5 id = request.POST.get('fav_id', 0) 6 type = request.POST.get('fav_type', 0) 7 8 # 未登录返回json数据到前端,由前端进行登录页面的跳转 9 if not request.user.is_authenticated: 10 return HttpResponse('{"status": "fail", "msg": "用户未登录"}', content_type='application/json') 11 12 # 在数据库中查找是否有过收藏记录: 13 exist_record = UserFavorite.objects.filter(user=request.user, fav_id=int(id), fav_type=int(type)) 14 if exist_record: 15 # 记录存在,取消收藏 16 exist_record.delete() 17 18 # 收藏数减1 19 if int(type) == 1: 20 course = Course.objects.get(id=int(id)) 21 course.fav_nums -= 1 22 if course.fav_nums < 0: 23 course.fav_nums = 0 24 course.save() 25 elif int(type) == 2: 26 org = CourseOrg.objects.get(id=int(id)) 27 org.fav_nums -= 1 28 if org.fav_nums < 0: 29 org.fav_nums = 0 30 org.save() 31 elif int(type) == 3: 32 teacher = Teacher.objects.get(id=int(id)) 33 teacher.fav_nums -= 1 34 if teacher.fav_nums < 0: 35 teacher.fav_nums = 0 36 teacher.save() 37 return HttpResponse('{"status": "success", "msg": "收藏"}', content_type='application/json') 38 else: 39 # 记录不存在,收藏 40 user_fav = UserFavorite() 41 if int(id)>0 and int(type)>0: 42 user_fav.user = request.user 43 user_fav.fav_id = int(id) 44 user_fav.fav_type = int(type) 45 user_fav.save() 46 47 # 收藏数 48 if int(type) == 1: 49 course = Course.objects.get(id=int(id)) 50 course.fav_nums += 1 51 course.save() 52 elif int(type) == 2: 53 org = CourseOrg.objects.get(id=int(id)) 54 org.fav_nums += 1 55 org.save() 56 elif int(type) == 3: 57 teacher = Teacher.objects.get(id=int(id)) 58 teacher.fav_nums += 1 59 teacher.save() 60 return HttpResponse('{"status": "success", "msg": "已收藏"}', content_type='application/json') 61 else: 62 return HttpResponse('{"status": "fail", "msg": "收藏出错"}', content_type='application/json')
7、个人中心页面左侧选中状态
现在在usercenter-base.html页面中修改左侧导航栏选中状态的代码:
还有一个问题,就是我的收藏中删除机构收藏,需要修改usercenter-base.html页面中最下面的代码:
下面的教师和课程也是同样的改法。