mxonline 总结

 

课程相关

  1. 课程列表
  2. 课程的剪接
  3. 课程详情
  4. 课程章节
  5. 课程关联的授课机构,课程关联的授课教师
  6. 热门课程
  7. 相关课程推荐

课程留言

  1. 需要登录
  2. 若未登录,返回到登录页面
  3. 留言失败反馈信息
  4. 留言成功,异步刷新页面

讲师相关

  1. 教师简介
  2. 讲师所有课程
  3. 热门讲师

授课机构相关

  1. 机构简介
  2. 机构所有课程
  3. 机构所有教师

用户相关

1.收藏

收藏前检查用户登录状态 实现异步功能 课程,教师,机构的收藏

2.注册
验证是否注册
发送注册邮件
用户激活
密码通过django自带的make_password加密保存到数据库

3.登陆
验证用户邮箱与密码

4.密码找回
通过post的邮件发送验证修改的用户名密码,通过用户邮箱点击链接,给予修改权限

后台相关

Xamin:定制后后台样式
修改页头页尾
通过superuser登陆

数据增删改查,查看数据访问次数

搜索相关

Q Icontains
用户名与游戏都可登录

功能实现

登陆功能
自定义auth认证
官方文档 自定义认证

先在settings.py中设置auth允许的函数

AUTHENTICATION_BACKENDS = (
    'users.views.CustomBackend',
)

然后在views中重载authenticate方法

from django.contrib.auth.backends import ModelBackend
from .models import UserProfile
from django.db.models import Q

class CustomBackend(ModelBackend):
    # 继承ModelBackend 重载authenticate方法,使得邮件账户也能登陆
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            # 返回0个和2个  get都会失败
            user = UserProfile.objects.get(Q(username=username)|Q(email=username))
            if user.check_password(password):  # 这里如果密码错误 user为None
                return user
        except Exception as e:
            return None

FBV改成CBV
CBV比FBV会有很多好处,所以改写为CBV方式书写。(菜鸡我一直写FBV……)

在views中

from django.views.generic.base import View

在views中定义的class要继承自这个基类View,然后书写get和post方法,记得传入request

在urls中

path(‘login/’, LoginView.as_view(), name=‘login’)

要调用as_view()方法,这样会返回使用LoginView处理的函数句柄
注册功能

规范养成:在模板中使用{% static ‘’ %}便于后期维护,记得先{% load staticfiles %}

captcha库
使用django-simple-captcha这个库来快速实现验证码功能。python3直接pip install django-simple-captcha
官方使用手册 ,这里简要记录

要把captcha注册进INSTALLED_APPS

然后添加path(‘captcha’, include(‘captcha.urls’))

在生成数据库 migrate一下。

这里注意下,点击图片刷新验证码是用前端js写的,手册上都有实例代码!
发送邮件
之前没有使用过Django发送过邮件,这里可以实验学习下,get!
发送电子邮件的最简单方法是使用 django.core.mail.send_mail()。
的subject,message,from_email和recipient_list参数是必需的。

subject:一个字符串。
message:一个字符串。
from_email:一个字符串。
recipient_list:字符串列表,每个字符串都是电子邮件地址。每个成员都recipient_list将在电子邮件的“收件人:”字段中看到其他收件人。
fail_silently:一个布尔值。如果是的话False,send_mail会提出一个smtplib.SMTPException。有关smtplib可能的例外列表,请参阅文档,所有这些例外都是。的子类 SMTPException。
auth_user:用于向SMTP服务器进行身份验证的可选用户名。如果没有提供,Django将使用该EMAIL_HOST_USER设置的值 。
auth_password:用于验证SMTP服务器的可选密码。如果没有提供,Django将使用该EMAIL_HOST_PASSWORD设置的值 。
connection:用于发送邮件的可选电子邮件后端。如果未指定,将使用默认后端的实例。有关 更多详细信息,请参阅电子邮件后端的文档。
html_message:如果html_message被提供,所得到的电子邮件将是一个 多部分/替代电子邮件message作为 文本/无格式内容类型和html_message作为 text / html的内容类型。

这里选择django自带的send_mail,故下面的步骤是按此而言

settings中配置

发送邮件的setting设置

EMAIL_HOST = “smtp.qq.com
EMAIL_PORT = 25
EMAIL_HOST_USER = “xxx”
EMAIL_HOST_PASSWORD = " "
EMAIL_USE_TLS= True
EMAIL_FROM = “xxx”

这边我吧邮箱配置放到了另一个脚本中,并计入了gitignore

from random import Random
from users.models import EmailVerifyRecord
from django.core.mail import send_mail
from mxonline.settings import EMAIL_FROM

def random_str(random_len=8):
“”"
生成随机字符串
:param random_len: 字符串长度,默认为8
:return: 返回字符串
“”"
rd_str = ‘’
chars = ‘AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789’
length = len(chars) - 1
random = Random()
for i in range(random_len):
rd_str += chars[random.randint(0, length)]
return rd_str

注册发信

def send_mx_email(email, send_type=‘register’):
“”"
发送邮件函数 : 发送前将生成的str拼接成url放入数据库,到时候查询数据库是否存在url
:param email: 邮箱
:param send_type: 发送类型,默认是注册,还有是找回密码
:return:
“”"
email_record = EmailVerifyRecord()
code = random_str(20) # 生成随机字符串,数据库中code最长为20
email_record.code = code
email_record.email = email
email_record.send_type = send_type
email_record.save()

# 定义邮件的内容
email_title = ''
email_body = ''

if send_type == 'register':
    email_title = '我的慕学网站 注册激活链接'
    email_body = '请点击下面的链接激活你的账号:http://127.0.0.1:8000/active/{0}'.format(code)
    # 使用Django内置函数完成邮件发送。四个参数:主题,邮件内容,从哪里发,接受者list
    send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
    if send_status:
        return True
    else:
        return False

暂时没有加入body模板之类的,先用简单的进行测试,后续再进行添加修改完善整个发信函数
对应的在view中添加了一个激活的视图函数ActiveUserView
class ActiveUserView(View):
“”"
激活用户视图函数
“”"
def get(self, request, active_code):
# 取出所有符合验证码的记录 如果查询不到 激活链接肯定是假的 直接返回404
record_list = get_list_or_404(models.EmailVerifyRecord,code= active_code)
msg = ‘’

    for record in record_list:
        email = record.email
        # 根据邮箱 去查用户表中 未激活的用户
        try:
            user = models.UserProfile.objects.get(email=email, is_active=False)
            user.is_active = True
            user.save()
            msg = '用户激活成功,请登录!'
        except models.UserProfile.DoesNotExist:
            msg = '你的账户已是激活状态!'

    return render(request, 'login.html', {'msg': msg})

并在LoginView中需要添加逻辑判断是否用户已经激活
找回密码
首次看视频和代码感觉到的两个问题:(好像后面有review)

重复输入密码应该放在 forms is_valid中,自写clean函数进行检测
根据前端传入的email,明显存在任意密码重置漏洞

还要考虑重置密码链接是否被点击过,过期时间等!!!
设计url
path(‘forget/’, ForgetPwdView.as_view(), name=‘forget_pwd’),

找回密码url

re_path(‘reset/(?P<reset_code>.*)/’, ResetView.as_view(), name=‘reset_pwd’),

后台修改密码操作的url

path(‘modify_pwd/’, ModifyPwdView.as_view(), name=‘modify_pwd’)

其实我觉得应该把这些都放入users这个app的urls内,然后include下,
设计对应form
form我认为只用来做校验比较好,不要直接拿到前端用,因为前端太多样式css了,在form里面写widget会累死的
class ForgetForm(forms.Form):
# 此处email与前端name需保持一致。
email = forms.EmailField(required=True)
# 应用验证码 自定义错误输出key必须与异常一样
captcha = CaptchaField(error_messages={“invalid”: u"验证码错误"})

class ModifyPwdForm(forms.Form):
# 提交新密码的form
password = forms.CharField(required=True, min_length=6, error_messages={
‘min_length’: ‘最少为6个字符’,
‘required’: ‘必须输入密码啊!’
})
# 再次输入密码的字段
re_pwd = forms.CharField(required=True)

def clean_re_pwd(self):
    pwd = self.cleaned_data.get('password')
    re_pwd = self.cleaned_data.get('re_pwd')
    if pwd is None:
        raise ValidationError("请先正确输入密码!!")
    if pwd != re_pwd:
        raise ValidationError("两次密码不一样!!")
    return re_pwd

我在这里利用了它本身的hook机制 重载了re_pwd的clean方法,验证两次密码是否一致。不放在view中去做验证了
完善发信
def send_mx_email(email, send_type=‘register’):
“”"
发送邮件函数 : 发送前将生成的str拼接成url放入数据库,到时候查询数据库是否存在url
:param email: 邮箱
:param send_type: 发送类型,默认是注册,还有是找回密码
:return:
“”"
email_record = EmailVerifyRecord()
code = random_str(20) # 生成随机字符串,数据库中code最长为20
email_record.code = code
email_record.email = email
email_record.send_type = send_type
email_record.save()

# 定义邮件的内容
email_title = ''
email_body = ''

if send_type == 'register':
    email_title = '我的慕学网站 注册激活链接'
    email_body = '请点击下面的链接激活你的账号:http://127.0.0.1:8000/active/{0}'.format(code)
    # 使用Django内置函数完成邮件发送。四个参数:主题,邮件内容,从哪里发,接受者list

elif send_type == 'forget':
    email_title = '我的慕学网站 密码找回链接'
    email_body = '请点击下面的链接重置密码:http://127.0.0.1:8000/reset/{0}'.format(code)

send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
if send_status:
    return True
else:
    return False

视图View
按照视频中的思路写了3个视图类,但是我感觉有点臃肿,后面考虑是否重写

class ForgetPwdView(View):
    """
    忘记密码显示页面
    """
    def get(self, request):
        forget_from = forms.ForgetForm()
        return render(request, "forgetpwd.html", {"forget_from": forget_from})

    def post(self, request):
        forget_form = forms.ForgetForm(request.POST)
        # form验证合法情况下取出email,从clean data中取
        if forget_form.is_valid():
            email = forget_form.cleaned_data.get("email", "")
            # 发送找回密码邮件
            send_mx_email(email, "forget")
            # 发送完毕返回登录页面并显示发送邮件成功。
            return render(request, "login.html", {"msg": "重置密码邮件已发送,请注意查收"})
        # 如果表单验证失败也就是他验证码输错等。
        else:
            return render(request, "forgetpwd.html", {"forget_from": forget_form})


class ResetView(View):
    """
    重置密码显示页面函数
    """
    def get(self, request, reset_code):
        # 查询生成的随机字符串 和对应的email
        record_list = get_object_or_404(models.EmailVerifyRecord, code=reset_code)

        return render(request, 'password_reset.html', {'reset_code': reset_code})


class ModifyPwdView(View):
    """
    修改密码操作视图
    """
    def post(self, request):
        modifypwd_form = forms.ModifyPwdForm(request.POST)
        if modifypwd_form.is_valid():
            # 两次密码是否一致验证已经在form检测中 验证
            password = modifypwd_form.cleaned_data.get("password")
            # 从前端取到resetcode这样定位到是谁在重置密码
            reset_code = request.POST.get("reset_code")
            # 如果重置reset code是伪造的 取不到object 直接给他返回404 干嘛还给他友好显示!!!
            email_obj = get_object_or_404(models.EmailVerifyRecord, code=reset_code)
            # 从记录验证码表中取出对应的email 去寻找user
            user = models.UserProfile.objects.get(email=email_obj.email)
            user.password = make_password(password)
            user.save()
            return render(request, 'login.html',{'msg':'密码重置成功!请登录'})
        else:
            reset_code = request.POST.get("reset_code")
            return render(request, 'password_reset.html', {'reset_code': reset_code, 'modifypwd_form':modifypwd_form})

补充
视频中没有提及的个人认为补充下:

前端回传forms内容时可以不用if,error显示和value显示也好,感觉直接用filter过滤器的default更方便简单些

{{ modifypwd_form.password.errors.0 |default:’’ }} # 错误
{{ register_form.password.value |default:’’}} # 值

password_reset模板中的标签

最好修改成这样,onclick可能会触发失败的
错误可以回显在中,这里教程中好像没有提及

应该还要添加链接是否点击,是否过期等考虑项

机构列表
终于开始采用模板继承的方式。
这里记录下MEDIA的配置方式:

MEDIA_ROOT:在settings中配置,这样上传的文件会相对于这个路径。否则会相对于项目路径的

MEDIA_URL:可以使得在模板中动态使用media,便于后期维护。记得先再setting的template中加入’django.template.context_processors.media’才能用
要在URL中配置让media访问的url,下面的方法仅仅推荐在本地开发环境中,生产环境千万不要这样

from django.views.static import serve
from mxonline.settings import MEDIA_ROOT
from django.urls import path, include, re_path

urlpatterns = [

设置media访问url

re_path(r'^media/(?P<path>.*)', serve, {"document_root": MEDIA_ROOT})

]

分页功能
这里分页功能选择了GitHub上一个Django的第三方包 django-pure-pagination
配合我们的例子使用是这样:
class OrgListView(View):
def get(self, request):
org_list = models.CourseOrg.objects.all()
citys_list = models.CityDict.objects.all()
org_sum = models.CourseOrg.objects.count()

    # 尝试获取前台get请求传递过来的page参数
    # 如果是不合法的配置参数默认返回第一页
    try:
        page = request.GET.get('page', 1)
    except PageNotAnInteger as e:
        page = 1
    p = Paginator(org_list, 5, request=request)
    orgs = p.page(page)
    return render(request, 'org_list.html', {
        'org_list': orgs,
        'citys_list': citys_list,
        'org_sum': org_sum
    }
    )

模板中使用原样式,然后添加一些判断和遍历,不建议直接使用内置的样式,或者可以自己魔改他的模板

  • {% if org_list.has_previous %}
  • 上一页 {% endif %} {% for page in org_list.pages %} {% if page %} {% ifequal page org_list.number %}
  • {{ page }} {% else %}
  • {{ page }} {% endifequal %} {% else %} ... {% endif %} {% endfor %} {% if org_list.has_next %}
  • 下一页 {% endif %}

分类筛选
对模板中的 city_id category sort 参数在view中进行filter筛选,返回结果org_list 然后显示内容
”我要学习“部分
在这里使用modelForm了,就是把form和model结合在一起,form验证完可以直接插入model内,更加方便快捷。

import re
from django import forms

from operation.models import UserAsk


class UserAskForm(forms.ModelForm):
    # 继承model外也能新增其他字段
    class Meta:
        model = UserAsk
        # 指定验证的字段
        fields = ['name', 'mobile', 'course_name']

    def clean_mobile(self):
        mobile = self.cleaned_data['mobile']
        REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
        p = re.compile(REGEX_MOBILE)
        if p.match(mobile):
            return mobile
        else:
            raise forms.ValidationError(u"手机号码非法", code="mobile_invalid")

在这里我直接修改了前端模板中的js代码,


视图里简单这样写了
class AddUserAskView(View):
    """
    我要学习模块
    """
    def post(self, request):
        userask_form = forms.UserAskForm(request.POST)
        if userask_form.is_valid():
            # 如果验证通过 就存入数据库中
            userask_form.save(commit=True)
            return JsonResponse({"status": "success", "msg": "咨询成功,请等待回复"})
        else:
            return JsonResponse({"status": "fail", "msg": '咨询失败,稍后再试'})

机构详情
机构详情分为四个部分:主页、课程、介绍、教师
都是赋值模板 遍历的东西,重复做就行了,不过这边的URL我与视频中设计不同。我觉得他那样有点冗余,所以我分发了一下

from django.urls import path, include, re_path
from organization import views


app_name = 'org'


extra_patterns = [
    path('home/', views.OrgHomeView.as_view(), name='org_home'),
    path('course/', views.OrgCourseView.as_view(), name='org_course'),
    path('desc/', views.OrgDescView.as_view(), name='org_desc'),
    path('teacher/', views.OrgTeacherView.as_view(), name='org_teacher'),
]

urlpatterns = [
    path('list/', views.OrgListView.as_view(), name='org_list'),
    path('add_ask/', views.AddUserAskView.as_view(), name='add_ask'),
    path('show/<int:org_id>/', include(extra_patterns)), # org_id一致 url分发一下 减少冗余
]

公开课
课程列表
模板还是一样的继承,变量赋值循环,不过这里get一个小知识点,CharField中的choice可以用特定方法显示为中文

<div class="head">热门课程推荐</div>
<div class="group_recommend">
    {% for course in hot_courses %}
    <dl>
        <dt>
        <a target="_blank" href="">
        <img width="240" height="220" class="scrollLoading"
        src="{{ MEDIA_URL }}{{ course.image }}"/>
        </a>
        </dt>
        <dd>
        <a target="_blank" href=""><h2> {{ course.name }}</h2></a>
        <span class="fl">难度:<i class="key">{{ course.get_degree_display }}</i></span>
        </dd>                               # 这里get_degree_display
    </dl>
    {% endfor %}
</div>
视图简单这样写
class CourseListView(View):
    """
    课程列表页面
   
        def get(self, request):
            all_course = models.Course.objects.all()
            hot_courses = models.Course.objects.all().order_by("-students")[:3]
            # 对课程进行分页
            # 尝试获取前台get请求传递过来的page参数
            # 如果是不合法的配置参数默认返回第一页
            try:
                page = request.GET.get('page', 1)
            except PageNotAnInteger:
                page = 1
            # sort排序
            sort = request.GET.get('sort', "")
            if sort:
                if sort == "students":
                    all_course = all_course.order_by("-students")
                elif sort == "hot":
                    all_course = all_course.order_by("-click_nums")
    
            p = Paginator(all_course, 6, request=request)
            courses = p.page(page)
            return render(request, 'course-list.html', {
                'all_course': courses,
                'sort': sort,
                "hot_courses": hot_courses
            })

分页功能前端模板直接照搬org_list页面就行了,注意变量名修改下
详情页面
详情页中有些前端模板中需要显示的内容,需要后端对象用自己的外键跨表查询,通常一般我View视图内,但是这次看到教程中直接写在了model内。然后还是一样的render页面和变量。直接在模板中使用该model的方法就行了。感觉不错!
下面以Course为例,model修改为
class Course(models.Model):
“”"
课程model
“”"
DEGREE_CHOICES = (
(‘cj’, ‘初级’),
(‘zj’, ‘中级’),
(‘gj’, ‘高级’)

)
name = models.CharField(max_length=50, verbose_name="课程名")
desc = models.CharField(max_length=300, verbose_name="课程描述")
detail = models.TextField(verbose_name="课程详情")
learn_times = models.IntegerField(default=0, verbose_name='学习时长(分钟)')  # 分钟为单位
course_org = models.ForeignKey(to=CourseOrg, verbose_name='所属机构', on_delete=models.CASCADE, null=True, blank=True)
students = models.IntegerField(default=0, verbose_name='学习人数')
category = models.CharField(verbose_name='课程类别', max_length=20, default='编程开发')
tag = models.CharField(verbose_name='课程标签',default='', max_length=10)
fav_nums = models.IntegerField(default=0, verbose_name='收藏人数')
image = models.ImageField(upload_to='courses/%Y/%m', verbose_name='封面图', max_length=100)
click_nums = models.IntegerField(default=0, verbose_name='点击数')
add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间')
degree = models.CharField(verbose_name='课程难度', choices=DEGREE_CHOICES, max_length=2, default='cj')

class Meta:
    verbose_name = '课程'
    verbose_name_plural = verbose_name

def get_lesson_nums(self):
    """
    获取章节数
    :return: int
    """
    return self.lesson_set.all().count()

def get_learn_user(self):
    """
    获取学习该课程的5个用户
    :return: QurySet
    """
    return self.usercourse_set.all()[:5]

def __str__(self):
    return self.name

在模板中直接这样使用了

{{ course.name }}

{{ course.desc }}
难度:{{ course.get_degree_display }} 学习人数:{{ course.students }}
  • 时     长:{{ course.learn_times }}
  • 章 节 数:{{ course.get_lesson_nums }}
  • 课程类别:{{ course.category }}
  • 学习用户: {% for usercourse in course.get_learn_user %}
    {% endfor %}
    
    
    </li>
    </ul>
    <div class="btns">
    <div class="btn colectgroupbtn"  id="jsLeftBtn">
    收藏
    </div>
    <div class="buy btn"><a style="color: white" href="course-video.html">开始学习</a></div>
    </div>
    

View中只这样写
class CourseDetailView(View):
“”"
详情页
“”"
def get(self, request, course_id):
course_obj = get_object_or_404(models.Course, pk=course_id)
course_obj.click_nums += 1
course_obj.save()
return render(request, ‘course-detail.html’, {
‘course’: course_obj
})

章节信息
课程和章节是一对多关系,章节和视频也是一对多关系。
在视频中新添加url字段。给vedio添加一个学习时长的字段。然后添加模拟数据。

在course中添加获取章节的方法
在lesson中添加获取所有视频的方法
创建课程与讲师直接的外键关联

{% for lesson in course.get_course_lesson %}

{{ lesson.name }}

{% endfor %}

前端模板中for嵌套就形成了层级界面
课程评论
评论页面模板类似继承,然后添加评论也是ajax。
相关课程推荐
这里要通过orm去找 当前学习当前课程的用户还学过的其他课程

选出学了这门课的学生关系

user_courses = UserCourse.objects.filter(course= course)

从关系中取出user_id

user_ids = [user_course.user_id for user_course in user_courses]

这些用户学了的课程,外键会自动有id,取到字段

all_user_courses = UserCourse.objects.filter(user_id__in=user_ids)

取出所有课程id

course_ids = [all_user_course.course_id for all_user_course in all_user_courses]

获取学过该课程用户学过的其他课程

relate_courses = Course.objects.filter(id__in=course_ids).order_by("-click_nums")[:5]

是否收藏课程

return render(request, “course-video.html”, {
“course”: course,
“all_resources”: all_resources,
“relate_courses”:relate_courses,
})

然后还要把这个添加到三个视图中,我觉得这样代码重复太多了,考虑其他方式减少冗余。
使用include_tag
考虑到课程详情章节页面和评论页面,右边部分内容重复,我自己把他单拿出来放到了include_tag里面了。这样减少代码重复度。记录下简要步骤

在APP下建立templatetags包文件然后新建tag文件,这里以get_content.py为例
在tag文件中创建定义函数,
这里定义takes_context=True,可以自动获取上下文变量。就是在模板中的变量可以从context这个字典中取出来

from django import template
from courses import models
from operation.models import UserCourse
from mxonline.settings import MEDIA_URL
register = template.Library()


@register.inclusion_tag('course_aside.html', takes_context=True)
def get_right_side(context):
    # 从上下文变量中取出course
    course = context['course']
    all_resources = models.CourseResource.objects.filter(course=course)
    # 选出学了这门课的学生
    user_courses = UserCourse.objects.filter(course=course)
    # 从关系中取出user_id遍历成列表
    user_ids = [user_course.user_id for user_course in user_courses]
    # 查询出用户课程表中 用户id在上面列表中的 其他课程
    all_user_courses = UserCourse.objects.filter(user_id__in=user_ids)
    # 取出所有课程id
    course_ids = [all_user_course.course_id for all_user_course in all_user_courses]
    # 获取学过该课程用户学过的其他课程
    relate_courses = models.Course.objects.filter(id__in=course_ids).order_by("-click_nums")[:5]
    # 是否收藏课程

    return {
        'all_resources': all_resources,
        'course': course,
        # 我也不知道为啥 这个MEDIA 不传前端就获取不到了 那就显式传递一下吧
        'MEDIA_URL': MEDIA_URL,
        "relate_courses": relate_courses,
    }

这个是get_right_side中使用的模板

    <div class="box mb40">
        <h4>资料下载</h4>
        <ul class="downlist">
            {% for resource in all_resources %}
                <li>
                    <span><i class="aui-iconfont aui-icon-file"></i>&nbsp;&nbsp;{{ resource.name }}</span>
                    <a href="{{ MEDIA_URL }}{{ resource.download }}" class="downcode" target="_blank" download=""
                       data-id="274" title="">下载</a>
                </li>
            {% endfor %}


        </ul>
    </div>
    <div class="box mb40">
        <h4>讲师提示</h4>
        <div class="teacher-info">
            <a href="/u/315464/courses?sort=publish" target="_blank">
                <img src='{{ MEDIA_URL }}{{ course.teacher.image }}' width='80' height='80'/>
            </a>
            <span class="tit">
      <a href="/u/315464/courses?sort=publish" target="_blank">{{ course.teacher.name }}</a>
    </span>
            <span class="job">{{ course.teacher.work_position }}</span>
        </div>
        <div class="course-info-tip">
            <dl class="first">
                <dt>课程须知</dt>
                <dd class="autowrap">{{ course.need_know }}</dd>
            </dl>
            <dl>
                <dt>老师告诉你能学到什么?</dt>
                <dd class="autowrap">{{ course.teacher_tell }}</dd>
            </dl>
        </div>
    </div>


    <div class="cp-other-learned  js-comp-tabs">
        <div class="cp-header clearfix">
            <h2 class="cp-tit l">该课的同学还学过</h2>
        </div>
        <div class="cp-body">
            <div class="cp-tab-pannel js-comp-tab-pannel" data-pannel="course" style="display: block">
                <!-- img 200 x 112 -->
                <ul class="other-list">
                    {% for course in relate_courses %}
                     <li class="curr">
                        <a href="{% url 'course:detail' course.id %}" target="_blank">
                            <img src="{{ MEDIA_URL }}{{ course.image }}"
                                 alt="{{ course.name }}">
                            <span class="name autowrap">{{ course.name }}</span>
                        </a>
                    </li>
                    {% endfor %}
                </ul>
            </div>

        </div>
    </div>

</div>

再到前面模板中使用这个include_tag

{% load get_content %}

{% get_right_side %}

这样就不用在course_comment,course_vedio页面两次使用相同内容的HTML代码,也不用在CourseInfoView,CommentsView视图里用同样的代码获取变量了。
CBV装饰器
一些视图的操作必须登录才能进行,这样就要给某些视图类加上登录的判断。当然也可以用
if request.user.is_authenticated:

但是这样判断后在进行操作总归觉得不是最好的解决方式。
所以这里改用装饰器。FBV模式直接用@login_required就行了。但是CBV模式的需要另外的方法

apps/utils/mixin_utils.py

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator

class LoginRequiredMixin(object):
    @method_decorator(login_required(login_url='/login/'))   
    def dispatch(self,request,*args,**kwargs):
        return super(LoginRequiredMixin, self).dispatch(request,*args,**kwargs)

在django中已Mixin结尾的,就代表最基本的View
然后让CourseInfoView和CommentsView都继承LoginRequiredMixin
主页
重新继承自base,之前开始写的时候很随便,直接用的index.html 现在要考虑整体性,所以教程中又继承自base了。其他操作没有什么特殊,就是替换render而已。这里有个对主页导航栏active判断的方法之前没有想到过,记录下
配置全局导航和全局"active"状态

 

说明:

request.path 可以获取当前访问页面的相对url
比如“http://127.0.0.1:8000/org/teacher_list/”,则request.path 就是“/org/teacher_list/”
比如"http://127.0.0.1:8000/org/teacher/detail/1/",则request.path 就是“/org/teacher/detail/1/”
slice:12 是过滤器,取前七位数
利用这种发发可以达到全局的“active”效果,而不用每个子页面都要去设置“active”了

全局搜索功能
先来看看deco-common.js中的代码
//顶部搜索栏搜索方法
function search_click(){
var type = $(’#jsSelectOption’).attr(‘data-value’),
keywords = $(’#search_keywords’).val(),
request_url = ‘’;
if(keywords == “”){
return
}
if(type == “course”){
request_url = “/course/list?keywords=”+keywords
}else if(type == “teacher”){
request_url = “/org/teacher/list?keywords=”+keywords
}else if(type == “org”){
request_url = “/org/list?keywords=”+keywords
}
window.location.href = request_url
}

在对应的视图中取keywords这个键值,然后使用orm操作sql like方法找到结果,再正常返回页面,这里仅以Course为例

搜索功能

    search_keywords = request.GET.get('keywords', '')
    if search_keywords:
        # Q可以实现多个字段,之间是or的关系
        all_course = all_course.filter(
            Q(name__icontains=search_keywords) |
            Q(desc__icontains=search_keywords) |
            Q(detail__icontains=search_keywords)
        )

Q方法是or的用法
个人信息中心
个人资料的修改这部分是后面逻辑性较强的一块了。
修改头像
使用modelform,然后用meta指定field只为image,上传后直接保存
修改密码
使用modelform,再自写其中的clean方法,对两次输入的密码做相同比对,就是使用的上次的modifyform
class ModifyPwdForm(forms.Form):
# 提交新密码的form
password = forms.CharField(required=True, min_length=6, error_messages={
‘min_length’: ‘最少为6个字符’,
‘required’: ‘必须输入密码啊!’
})
# 再次输入密码的字段
re_pwd = forms.CharField(required=True)

def clean_re_pwd(self):
    pwd = self.cleaned_data.get('password')
    re_pwd = self.cleaned_data.get('re_pwd')
    if pwd is None:
        raise ValidationError("请先正确输入密码!!")
    if pwd != re_pwd:
        raise ValidationError("两次密码不一样!!")
    return re_pwd

view里面这样写
class UpdatePwdView(LoginRequiredMixin, View):
“”"
个人中心修改用户密码
“”"
def post(self, request):
modify_form = ModifyPwdForm(request.POST)
# 我把两次密码是否相同验证放在clean方法内了
if modify_form.is_valid():
password = modify_form.cleaned_data.get(“password”)
user = request.user
user.password = make_password(password)
user.save()

        return HttpResponse('{"status":"success"}', content_type='application/json')
    else:
        # 两次密码不一致会在这里返回的
        return HttpResponse(json.dumps(modify_form.errors), content_type='application/json')

修改邮箱
修改邮箱属于这里面逻辑更多的一部分

判断要修改的邮箱是否已经存在,不存在就发送邮件验证码
判断验证码是否正确(去验证码表中寻找和当前用户比对)

class SendEmailCodeView(LoginRequiredMixin, View):
“”"
发送邮箱验证码视图
“”"
def get(self, request):
email = request.GET.get(‘email’, ‘’)

    if UserProfile.objects.filter(email=email):
        return HttpResponse('{"email":"邮箱已存在"}', content_type='application/json')

    send_mx_email(email, 'update_email')
    return HttpResponse('{"status":"success"}', content_type='application/json')

class UpdateEmailView(LoginRequiredMixin, View):
“”"
修改邮箱视图
“”"
def get(self, request):
email = request.POST.get(“email”, “”)
code = request.POST.get(“code”, “”)

    existed_records = models.EmailVerifyRecord.objects.filter(email=email, code=code, send_type='update_email')
    if existed_records:
        user = request.user
        user.email = email
        user.save()
        return HttpResponse('{"status":"success"}', content_type='application/json')
    else:
        return HttpResponse('{"email":"验证码无效"}', content_type='application/json')

查看收藏和信息
个人中心还可以查看收藏(机构、教师、课程)、消息、和自己学习的公开课程。还是一样的查然后赋值给模板渲染。
class MyCourseView(LoginRequiredMixin, View):
def get(self, request):
user_courses = opmodel.UserCourse.objects.filter(user=request.user)
return render(request, “usercenter-mycourse.html”, {
“user_courses”: user_courses,
})

class MyFavOrgView(LoginRequiredMixin, View):
“”"
我收藏的课程机构
“”"

def get(self, request):
    org_list = []
    fav_orgs = opmodel.UserFavorite.objects.filter(user=request.user, fav_type=2)
    # 上面的fav_orgs只是存放了id。我们还需要通过id找到机构对象
    for fav_org in fav_orgs:
        # 取出fav_id也就是机构的id。
        org_id = fav_org.fav_id
        # 获取这个机构对象
        org = CourseOrg.objects.get(id=org_id)
        org_list.append(org)
    return render(request, "usercenter-fav-org.html", {
        "org_list": org_list,
    })
class MyFavTeacherView(LoginRequiredMixin, View):
    """
    我收藏的授课讲师
    """
    def get(self, request):
        teacher_list = []
        fav_teachers = opmodel.UserFavorite.objects.filter(user=request.user, fav_type=3)
        for fav_teacher in fav_teachers:
            teacher_id = fav_teacher.fav_id
            teacher = Teacher.objects.get(id=teacher_id)
            teacher_list.append(teacher)
        return render(request, "usercenter-fav-teacher.html", {
            "teacher_list": teacher_list,
        })
class MyFavCourseView(LoginRequiredMixin, View):
    """
    我收藏的公开课程
    """
    def get(self, request):
        course_list = []
        fav_courses = opmodel.UserFavorite.objects.filter(user=request.user, fav_type=1)
        for fav_course in fav_courses:
            course_id = fav_course.fav_id
            course = Course.objects.get(id=course_id)
            course_list.append(course)

        return render(request, 'usercenter-fav-course.html', {
            "course_list": course_list,
        })


class MyMessageView(LoginRequiredMixin, View):
    def get(self, request):
        all_message = opmodel.UserMessage.objects.filter(user=request.user.id)

        try:
            page = request.GET.get('page', 1)
        except PageNotAnInteger:
            page = 1
        p = Paginator(all_message, 4, request=request)
        messages = p.page(page)
        return render(request, "usercenter-message.html", {
            "messages": messages,
        })

在页面导航栏都会有消息个数提醒,那如何把消息个数变量放置在每个页面中?

考虑到每个页面中的request.user本来就不用显式赋值给模板调用,所以可以直接在userProfile的model中加入一个获取消息个数的方法!!Mark 之前都未想到过这样使用。
细节收尾
对各种点击数保存在视图中添加相应代码,对收藏操作进行一定判断。对导航栏class是否有active状态进行判断,

 

这里以usercenter-base为例,这样处理判断active的。
全局配置404和500
视频中讲到了配置需要注意的点:
handler
在urls中定义handler404和handler500

全局404页面配置

handler404 = ‘users.views.page_not_found’

全局500页面配置

handler500 = ‘users.views.page_error’

page_not_found和page_error两个函数定义在视图中,404和500时执行对应的操作
定义处理函数

from django.shortcuts import render_to_response
def pag_not_found(request):
    # 全局404处理函数
    response = render_to_response('404.html', {})
    response.status_code = 404
    return response

def page_error(request):
    # 全局500处理函数
    from django.shortcuts import render_to_response
    response = render_to_response('500.html', {})
    response.status_code = 500
    return response

关闭DEBUG
如果不关闭debug模式,不会执行我们指定的404和500函数内的代码,而且仍旧报错,这是由于Django的调试模式决定的
设置static访问
视频中说到,关闭debug模式后,Django不会接管我们的静态文件,需要和media一样设置一个url路由给它访问。
re_path(r’^static/(?P.*)’, serve, {“document_root”: STATIC_ROOT }),

一般静态文件通过第三方http服务器代理转发。nignx 和 Apache都会自动代理这些静态文件
allowed_hosts
settings中的allowed_hosts设置项为允许运行该站点的ip,暂时先设置为*。
富文本编辑器
教程中使用了DjangoUeditor。这里记录一下添加这个组件的过程。
下载
https://github.com/twz915/DjangoUeditor3/
下载后放置随意一个目录下,我放于extra_apps下
配置

settings中添加app

INSTALLED_APPS = [
‘DjangoUeditor’,
]

mxonline/urls.py

富文本编辑器url

path('ueditor/',include('DjangoUeditor.urls' )),

course/models.py中Course修改detail字段

from DjangoUeditor.models import UEditorField

class Course(models.Model):
# detail = models.TextField(“课程详情”)
detail = UEditorField(verbose_name=u’课程详情’, width=600, height=300, imagePath=“courses/ueditor/”,
filePath=“courses/ueditor/”, default=’’)

xadmin/plugs目录下新建ueditor.py文件

import xadmin
from xadmin.views import BaseAdminPlugin, CreateAdminView, ModelFormAdminView, UpdateAdminView
from DjangoUeditor.models import UEditorField
from DjangoUeditor.widgets import UEditorWidget
from django.conf import settings

class XadminUEditorWidget(UEditorWidget):
def init(self, **kwargs):
self.ueditor_options = kwargs
self.Media.js = None
super(XadminUEditorWidget,self).init(kwargs)

class UeditorPlugin(BaseAdminPlugin):

def get_field_style(self, attrs, db_field, style, **kwargs):
    if style == 'ueditor':
        if isinstance(db_field, UEditorField):
            widget = db_field.formfield().widget
            param = {}
            param.update(widget.ueditor_settings)
            param.update(widget.attrs)
            return {'widget':XadminUEditorWidget(**param)}
    return attrs

def block_extrahead(self, context, nodes):
    js  = '<script type="text/javascript" src="%s"></script>' %(settings.STATIC_URL + "ueditor/ueditor.config.js")
    js += '<script type="text/javascript" src="%s"></script>' %(settings.STATIC_URL + "ueditor/ueditor.all.min.js")
    nodes.append(js)

xadmin.site.register_plugin(UeditorPlugin, UpdateAdminView)
xadmin.site.register_plugin(UeditorPlugin, CreateAdminView)

xadmin/plugs/init.py里面添加ueditor插件

PLUGINS = (
‘ueditor’,
)

course/adminx.py中使用

class CourseAdmin(object):
#detail就是要显示为富文本的字段名
style_fields = {“detail”: “ueditor”}

course-detail.html

必须要在需要显示富文本的地方关闭自动转义

{% autoescape off %} {{ course.detail }} {% endautoescape %}

课程中还有xadmin进阶开发和部署上线等步骤在此不展开了,代码开发全部完成。结束啦撒花!!!
下面说说收获吧!
收获

CBV模式的进一步熟悉使用,CBV方式的装饰器
model建立思想,一对多关系等。为防止循环调用,需要构建的有层次。
在model中书写某些orm查询,而后在模板中直接调用该变量的属性来使用。而不直接在view中书写
注册、找回密码、更换邮箱、全局消息等具体实现逻辑思路。
xadmin组件的进阶使用
全局配置处理404和500的方法。
项目部署上线的大体步骤。

 

转载于:https://www.cnblogs.com/donghaoblogs/p/10389684.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值