学习笔记:Django开发网上教育平台(参考了慕课网的教学视频)

第一步:进行环境的搭建(用到的IDE:pycharm  ,数据库为mysql、nacicat、编辑语言python3.7、以及自己配置的虚拟环境venvpy37)

Django==2.2

配置好mysql数据库的连接

第二步就是需求分析和app的设计:

大致上分为 四个app(也就是四个模块,Django是按app来开发的)

  • users--用户相关
  • courese--课程相关
  • organization--机构相关
  • operation-用户操作相关

 

 

然后新建完app并调整好结构,静态文件的文件夹static,html页面的文件夹templates,媒体文件文件夹media等

因为系统自带的User表字段没有需要的字段所以自己新建覆盖了一张用户表来存储用户信息

django提供一种方式让我们可以重载它的类

继承AbstractUser

from datetime import datetime

from django.db import models
from django.contrib.auth.models import AbstractUser

GENDER_CHOICES=(
    ("male",'男'),
    ("female",'女')
)


class UserProfile(AbstractUser):
    nick_name=models.CharField(max_length=50,verbose_name="昵称",default="")
    birthday=models.DateField(verbose_name="生日",null=True,blank=True)
    gender=models.CharField(verbose_name="性别",choices=GENDER_CHOICES,max_length=6)
    address=models.CharField(max_length=100,verbose_name="地址",default="")
    mobile=models.CharField(max_length=11,verbose_name="手机号码")
    image=models.ImageField(verbose_name="用户头像",upload_to="head_image/%Y/%m",default="default.jpg")

    class Meta:
        verbose_name="用户信息"
        verbose_name_plural=verbose_name

    def __str__(self):
        if self.nick_name:
            return self.nick_name
        else:
            return self.username

在模型文件里设计好之后,需要在全局文件里设置

AUTH_USER_MODEL="users.UserProfile",让系统知道用的是这张表

运行makemigrations和migrate 生成迁移文件和表 

还有coures相关的表:课程表、章节、视频、课程资源 四个表

organizations:城市表、课程机构表、讲师表

operations:用户咨询表、课程评论表、用户收藏表、用户信息表、用户课程表

代码太多就不详细贴上去了

在设计过程种使用了分层设计 避免循环导入不同app中的model

配置好相关的path 路径

 

 

然后用xadmin来搭建后台管理系统:在github上搜索xadmin下载

1. 下载xadmin源码
2. 在settings的INSTALLED_APPS中添加
    crispy_forms 和 xadmin
3. 安装xadmin的依赖包
django-crispy-forms
django-import-export
django-reversion
django-formtools
future
httplib2
six
xlwt
xlsxwriter
requests这些依赖包都要安装到虚拟环境中(进入虚拟环境后进入安装的文件夹路径 pip install -r ruquirements.txt 读取需要的依赖包并全部安装)
4. 通过migrate生成xadmin需要的表

 

然后配置好urls.py的路径

import xadmin
path('xadmin/', xadmin.site.urls),

使用xadmin 可以自己识别用户自己定义的表

这样的情况下后台照样把我们自己定义的User识别进去了,它会帮我们自动注册进来

然后在之前新建好的每个app里 新建adminx.py文件 配置后台 例如:course app

import xadmin

from apps.courses.models import Course,Lesson,Video,CourseResource


class GlobalSettings(object):
    #配置后台的标题和标尾
    site_title="林鹏项目--后台管理系统"
    site_footer="林鹏项目"
    menu_style="accordion"

class BaseSettings(object):
    #配置后台主题
    enable_themes = True
    use_bootswatch = True



class CourseAdmin(object):

    list_display = ["id", "name","detail","desc","degree","learn_times","students"]
    search_fields = ["name","detail","desc","degree"]
    list_filter = ["name", "desc", "learn_times","teacher__name"]
    list_editable = ["degree", "desc"]


class LessonAdmin(object):
    list_display = ["course", "name", "learn_times"]
    search_fields = ["course", "name"]
    list_filter = ["course__name", "name", "learn_times"]
    list_editable = ["name"]


class VideoAdmin(object):
    list_display = ["lesson", "name", "learn_times", "url"]
    search_fields = ["lesson", "name"]
    list_filter = ["name", "desc", "learn_times"]
    list_editable = ["lesson", "name"]


class CourseResourceAdmin(object):
    list_display = ["course", "name", "file"]
    search_fields = ["course", "name", "file"]
    list_filter = ["course", "name", "file"]
    list_editable = ["course", "name"]



xadmin.site.register(Course,CourseAdmin)
xadmin.site.register(Lesson,LessonAdmin)
xadmin.site.register(Video,VideoAdmin)
xadmin.site.register(CourseResource,CourseResourceAdmin)

#xadmin全局设置
xadmin.site.register(xadmin.views.CommAdminView,GlobalSettings)
xadmin.site.register(xadmin.views.BaseAdminView,BaseSettings)

这里包括了列表、过滤 、搜索、编辑框的功能  list_display 为列表  search-fields 搜索列 list_filter 过滤  list_editable 编辑框

接下来就是开始第一个功能的编写:登录

在做登录之前需要配置好首页和登录页面

然后编写逻辑函数 在views.py文件

 

使用的是CBV的逻辑来开发

CBV-class base view(优点:

class是可以继承的 为了后期便于维护有利于代码重用

因为是登录所以和用户相关是在users这个app里的views里编写

class LoginView(View):
    def get(self,request, *args, **kwargs):
        #判断用户是否登录  是否登录的状态is_authenticated在django2.o之后版本都是属性如果是登录状态则重定向到首页
        if request.user.is_authenticated:
            return HttpResponseRedirect(reverse("index"))

        login_form=DynamicLoginForm()
        return render(request,"login.html",{
            "login_form":login_form
        })

    def post(self,request, *args, **kwargs):
        #表单验证
        login_form=LoginForm(request.POST)

        if login_form.is_valid():
            user_name=login_form.cleaned_data["username"]
            password=login_form.cleaned_data["password"]
            # 用于通过用户名和密码查询用户是否存在
            user = authenticate(username=user_name, password=password)

            if user is not None:
                #查询到用户
                login(request,user)
                #login方法会自动登录,request为上面django传递的参数
                #登录成功之后返回页面
                return  HttpResponseRedirect(reverse("index"))
            else:
                # 未查询到用户
                return render(request, "login.html", {"msg": "用户名或密码错误","login_form":login_form})
        else:
            return render(request,"login.html",{"login_form":login_form})

 

 

 这里的代码逻辑 全部备注好了: 登陆页面有两种请求 一种是提交的post  一种是get

request.user.is_authenticated:  这句话是判断是否登陆,如果有就重定向到首页,login_form牵涉到表单验证的内容,
user_name=login_form.cleaned_data["username"]
password=login_form.cleaned_data["password"]是获取到表单清洗后的值[]里的值是html页面 input标签的name属性的值,这两行就是获取前段页面提交的数据

然后下面就是逻辑判断 ,判断用户是否存在 存在就登陆,用django 内部的login方法

否则就返回错误信息,并返回表单验证的错误信息

<div class="error btns login-form-tips" id="jsLoginTips">{% if login_form.errors %}{% for key,error in login_form.errors.items %}{{ error }}{% endfor %}{% else %}{{ msg }}{% endif %}

这是前端页面的判断逻辑,

{% if login_form.errors %}意思为如果表单中存在错误,
{% for key,error in login_form.errors.items %}{{ error }}:for key 是哪个字段 ,error in login_form.errors.items是错误类型 , {{ error }}是输出错误结果
{% else %}{{ msg }}:否则输出msg

 

这里的判断逻辑是:如果在表单验证中发现用户名或者密码错误:将前端样式的输入框变成红色 

在表单提交的时候出现403错误是因为django 内部的安全验证  防止跨域攻击只需要在表单的</form>
之前插入这句就行了
{% csrf_token %}

登陆开发完了之后:就是开发退出登陆的借口

编写的View函数都是继承django 本身自带的view

编写一个LogoutView(View)

class LogoutView(View):
    def get(self,request, *args, **kwargs):
        logout(request)
        return HttpResponseRedirect(reverse("index"))

然后再urls.py中修改一下配置

path('logout/', LogoutView.as_view(),name="logout"),

然后就是在html页面配置退出的url

<a class="fr" href="{% url 'logout' %}">退出</a>

这里的url 都是这样的书写格式 和前面的path  的name属性 一直   它会自动识别出路径   而且方便后期维护,只需要改动path的url路径名

接下来还学习了一种用手机验证码来登陆的功能以及注册:

首先为了能够发送手机验证码,需要找有发送手机短信资质的运营商 ,这里使用的是云片网 (具体如何注册使用就不说明了) 

在云片网:www.yunpian.com

 

此时准备完之后就需要在pycharm中编写代码了:

在apps文件夹下新建一个目录为utils 专门存放这种工具类文件

查看API文档有步骤说明

我们短信验证只需要用到单条短信发送 进入相关的文档说明里

这是相关的参数,只需选择3个必传参数即可 其他参数看需求

 

apikey 在控制台里

 

apikey 最好在项目的全局设置里配置好 方便后期更改

 

短信发送需要用到request

 

import requests
import json

def send_single_sms(apikey,code,mobile):
    #发送单条短信
    url="https://sms.yunpian.com/v2/sms/single_send.json"
    text="【林鹏先生】您的验证码是{}".format(code)

    res = requests.post(url, data={
        "apikey":apikey,
        "mobile":mobile,
        "text":text
    })
    re_json=json.loads(res.text)
    return re_json

if __name__ == "__main__":
    res=send_single_sms("f3444e2cea8176e2a16aad30ddc4af19","test","13587701643")
    import json
    res_json=json.loads(res.text)
    code=res_json["code"]
    msg=res_json["msg"]
    if code==0:
        print("发送成功")
    else:
        print("发送失败:{}".format(msg))
    print(res.text)

这里的if __name__== "__main__"

用来测试的

 

这里的url 为文档说明中的url, text 为云片网审核通过的模板样式

有了url 和text 之后就是发送数据了:

通过request的post方法 一个参数为url 一个参数为data data为dict形式

dict里面的值为文档里面要传的参数

这样就算完成了,但是还需要用ajax的方式来完成短信验证码的发送用到了js的知识

$.ajax({
    cache: false,
    type: 'post',
    dataType:'json',
    url:"/send_sms/",
    data:{
        mobile:$inpRegMobile.val(),
        "captcha_1":$inpRegCaptcha.val(),
        "captcha_0":$('#id_captcha_0').val(),
    },
class SendSmsView(View):
    def post(self, request, *args, **kwargs):
        send_sms_form = DynamicLoginForm(request.POST)
        re_dict={}
        if send_sms_form.is_valid():
            mobile=send_sms_form.cleaned_data["mobile"]
            #随机生成数字验证码
            code=generate_random(4,0)
            re_json=send_single_sms(yp_apikey,code,mobile=mobile,)#这个是json数据用于交互
            if re_json["code"] == 0:
                re_dict["status"] = "success"
                r=redis.Redis(host=REDIS_HOST,port=REDIS_PORT,db=0,charset="utf8",decode_responses=True)
                r.set(str(mobile),code)#set模式 可以持久化
                r.expire(str(mobile),60*5)#设置验证码5分钟过期
            else:
                re_dict["msg"]= re_json["msg"]
        else:
            for key,value in send_sms_form.errors.items():
                #如果出错 哪个字段出错,错误原因是什么 表单验证之后errors每个key 它的错误信息是list形式
                re_dict[key]=value[0]
        return JsonResponse(re_dict)

这里return不用rander 而是用JsonResponse 因为这里是用ajax 异步发送短信此时,上面提到的403错误不能再用

{% csrf_token %}来解决了

因为原本用form表单提交的方式只需要在form标签 里加入{% csrf_token %}即可 浏览器会帮我们直接表单里的所有值

但是我们使用ajax异步的方式来进行方式 这样浏览器就不会帮我们提交上去

所有使用{% csrf_token %} 并没有用

此时就用到csrf_exempt() 让服务器不验证csrf

 

此时思考一下 如果在js文件里面 添加csrf_token 获取这个值是否可以???但是在html页面点击查看源码可以看出值并没有赋值进来 所以这个方法行不通

 

 

这里还牵涉到了redis来记录发送的数据

 通过云片网发送验证码之后 这个验证码我们得保存起来 进行验证

所以保存数据:第一种 就是保存在数据库 另一种就是保存在内存中

用存在内存的方式 有两个问题:

1. 重启django之后,变量不存在了

2.随着验证码越来越多,,内存占用越来越大,验证码过期处理

用数据库可以实现 但是有特殊情况 发送完验证码之后用户并没有进行后续操作 这样数据就会越来越多

此时用 redis k-v数据库

(redis要运行起来 redis-server.exe   redis-cli.exe)

import redis

 

if re_json["code"] == 0:
    re_dict["status"] = "success"
    r=redis.Redis(host=REDIS_HOST,port=REDIS_PORT,db=0,charset="utf8",decode_responses=True)
    r.set(str(mobile),code)#set模式 可以持久化
    r.expire(str(mobile),60*5)#设置验证码5分钟过期

把redis的对象取出来赋给r,然后r.set 模式来持久化 

同时验证码还需要一个验证逻辑,我们使用表单来验证 这个验证码和发送的验证码是否一致

class DynamicLoginPostForm(forms.Form):
    mobile = forms.CharField(required=True,min_length=11,max_length=11)
    code = forms.CharField(min_length=4,max_length=4,required=True)

    def clean_code(self):
        mobile = self.data.get("mobile")
        code = self.data.get("code")

        # 取出这两个值之后就是涉及到在redis中查询了
        r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0, charset="utf8", decode_responses=True)
        redis_code = r.get(str(mobile))
        # 然后比较两个code值
        if code != redis_code:
            raise forms.ValidationError("验证码不正确")
        # 如果正确直接返回
        return code

把redis对象取出来之后查询它的code值 和 表单提交的code 进行比较  如果不相等抛出异常

这里的抛出异常我们用 raise

如果正确就返回这个code

 

 

:

 

还需要动态验证码:

在github上搜索 django-captcha-simple 里面有安装和配置步骤

然后文档会告诉如何使用,按照步骤我们创建一个表单

 

class DynamicLoginForm(forms.Form):
    #这是动态验证码和短信的表单
    mobile =forms.CharField(required=True,min_length=11,max_length=11)
    captcha = CaptchaField()

有了这个表单之后在views.py里面我们把表单里的传递过去 ,并显示到前段页面

<div class="form-group marb20 blur" id="jsRefreshCode">
    {{ login_form.captcha }}
    {{ dynamic_form.captcha }}
</div>

这里的两个只会显示其中一个不会冲突

这样动态验证码就实现了

 

接下来就是配置前段的html的url 和逻辑判断

所有的配置完了之后不要忘记配置urls.py文件的路径

 

 

动态登陆的逻辑完了之后需要实现动态注册的功能:

class RegisterView(View):
    def get(self, request, *args, **kwargs):
        register_get_form=RegisterGetForm()
        return render(request,"register.html",{
            "register_get_form":register_get_form
        })

    def post(self, request, *args, **kwargs):
        register_post_form = RegisterPostForm(request.POST)
        if register_post_form.is_valid():
            # 如果验证通过说明不存在用户,要新建
            mobile = register_post_form.cleaned_data["mobile"]
            password = register_post_form.cleaned_data["password"]
            #新建用户
            user = UserProfile(username=mobile)
            # 因为数据库里的密码是加密后的 所以不能直接存储明文密码需要通过set_password加密
            user.set_password(password)
            user.mobile = mobile
            user.save()
            login(request, user)
            return HttpResponseRedirect(reverse("index"))
        else:
            register_get_form = RegisterGetForm()
            return render(request, "register.html.html", {
                "register_get_form":register_get_form,
                "register_post_form":register_post_form,
            })

注册页面也有两个请求 1个是get  1个post ,get 请求需要获取到注册页面的动态验证码

post 请求是要把前段提交的数据存储,但是有两个判断逻辑,第一个是用户没有注册,这个比较简单把用户提交的数据存储到数据库里面, 第二个逻辑是用户已经注册过了,此时需要用到表单验证,专门验证mobile这个字段时候存在

class RegisterPostForm(forms.Form):
    mobile = forms.CharField(required=True, min_length=11, max_length=11)
    code = forms.CharField(min_length=4, max_length=4, required=True)
    password = forms.CharField(min_length=4)

    def clean_mobile(self):
        mobile = self.data.get("mobile")
        # 验证手机号码是否注册过
        users=UserProfile.objects.filter(mobile=mobile)
        if users:
            raise forms.ValidationError("该手机号码已注册")
        return  mobile

    def clean_code(self):
        mobile = self.data.get("mobile")
        code = self.data.get("code")

        # 取出这两个值之后就是涉及到在redis中查询了
        r = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=0, charset="utf8", decode_responses=True)
        redis_code = r.get(str(mobile))
        # 然后比较两个code值
        if code != redis_code:
            raise forms.ValidationError("验证码不正确")
        # 如果正确直接返回
        return code

然后注意前段页面的配置:

form表单的方法要是post方法 action属性值为{% url 'register'%}这种形式

input标签的name属性的值要和view函数里一一对应 并且注意403错误 在表单里加入 {%csrf_token%}

 

然后就是错误信息判断完的显示,和之前在做登陆的时候一样

posted @ 2019-12-03 18:20  Lin_peng  阅读(839)  评论(0编辑  收藏  举报