Form组件、auth认证组件、自定义图片验证码登录、 自定义分页

12.7 Form组件

Django form组件实现的功能:生成页面可用的HTML标签、对用户提交的数据进行校验、保留上次输入内容

12.71 使用form组件实现注册功能

views.py:

# 按照Django form组件的要求写一个类
from django import forms
from django.forms import fields
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
# 自定义验证规则
def passward_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value):
        raise ValidationError('手机号码格式错误')
        
class RegForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三"        # 设置默认值,初始值,input框里面的初始值
        error_messages={      #重写错误信息。
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    )
    pwd = forms.CharField(
        min_length=6, 
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
                                        #给input标签添加class='c1',刷新页面保留原input框中的值
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],                       #自定义正则校验器,如果不符合正则匹配则显示'请输入数字'
        validators=[passward_validate, ],#使用自定义验证函数
    )                                   
    
    gender = forms.fields.ChoiceField(
        choices=((1, ""), (2, ""), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()  #单radio值为字符串
    )
    hobby = forms.fields.ChoiceField(       #单选Select
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )
    hobby = forms.fields.MultipleChoiceField(   #多选Select
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )
    keep = forms.fields.ChoiceField(            #单选checkbox
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )
    hobby = forms.fields.MultipleChoiceField(   #多选checkbox
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )
  def __init__(self, *args, **kwargs):          #动态choices选项
        super(RegForm,self).__init__(*args, **kwargs)
        # self.fields['hobby'].choices = ((1, '篮球'), (2, '足球'),)
        self.fields['hobby'].choices = models.Hobby.objects.all().values_list('id','name')
​
# 使用form组件实现注册方式
def register2(request):
    form_obj = RegForm()
    if request.method == "POST":
        form_obj = RegForm(request.POST)    # 实例化form对象的时候,把post提交过来的数据直接传进去   
        if form_obj.is_valid():             # 调用form_obj校验数据的方法
            #print(form_obj.cleaned_data)    # 获取所有经过验证的数据
            models.User.objects.create(**form_obj.cleaned_data)
            return HttpResponse("注册成功")
    return render(request, "register.html", {"form_obj": form_obj})

register.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册2</title>
</head>
<body>
    <form action="/reg2/" method="post" novalidate autocomplete="off">
        {% csrf_token %}
        <div>
            <label for="{{ form_obj.username.id_for_label }}">
            {{ form_obj.username.label }}
            </label>
            {{ form_obj.username }} 
            <span class="error">{{ form_obj.username.errors.0 }}</span>
        </div>
        <div>
            <label for="{{ form_obj.pwd.id_for_label }}">
            {{ form_obj.pwd.label }}
            </label>
            {{ form_obj.pwd }} 
            <span class="error">{{ form_obj.pwd.errors.0 }}</span>
        </div>
        <div>
            <label for="">{{ form_obj.gender.label }}</label>
            {{ form_obj.gender }}
            <span class="error">{{ form_obj.gender.errors.0 }}</span>
        </div>
        <div>
            <label for="">{{ form_obj.hobby.label }}</label>
            {{ form_obj.hobby }}
            <span class="error">{{ form_obj.hobby.errors.0 }}</span>
        </div>
        <div>
            <label for="">{{ form_obj.keep.label }}</label>
            {{ form_obj.keep }}
            <span class="error">{{ form_obj.keep.errors.0 }}</span>
        </div>
        <div>
            <input type="submit" class="btn btn-success" value="注册">
        </div>
    </form>
</body>
</html>

12.72 Hook方法

除了上面两种方式,还可以在Form类中定义钩子函数,来实现自定义的验证功能

局部钩子

在Form类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        },
        widget=forms.widgets.TextInput(attrs={"class": "form-control"})
    )
    ...
    # 定义局部钩子,用来校验username字段
    def clean_username(self):
        value = self.cleaned_data.get("username")
        if "666" in value:
            raise ValidationError("光喊666是不行的")
        else:
            return value

全局钩子:

在Form类中定义 clean() 方法,就能够实现对字段进行全局校验

class LoginForm(forms.Form):
    ...
    password = forms.CharField(
        min_length=6,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )
    re_password = forms.CharField(
        min_length=6,
        label="确认密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )
    ...
    # 定义全局的钩子,用来校验密码和确认密码字段是否相同
    def clean(self):
        password_value = self.cleaned_data.get('password')
        re_password_value = self.cleaned_data.get('re_password')
        if password_value == re_password_value:
            return self.cleaned_data
        else:
            self.add_error('re_password', '两次密码不一致')
            raise ValidationError('两次密码不一致')

12.8 auth认证组件

urls.py:

from django.conf.urls import url
from django.contrib import admin
from app01 import views
​
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.login),
    url(r'^register/', views.register),
    url(r'^home/', views.home),
    url(r'^logout/', views.logout),
    url(r'^set_password/', views.set_password),
]

views.py:

from django.shortcuts import render, redirect, HttpResponse
from django.contrib import auth
from django.contrib.auth.models import User
# from app01.models import UserInfo
from django.contrib.auth.decorators import login_required
​
def login(request):
    if request.method == "POST":
        username = request.POST.get("username")
        pwd = request.POST.get("pwd")
        # authenticate():校验用户名、密码,认证成功返回user对象
        user_obj = auth.authenticate(username=username, password=pwd)
        if user_obj:
            # 内置的login方法
            auth.login(request, user_obj)
            # 生成Session数据,存一下user_id 然后把sessionid写入Cookie
            # 后续每一次请求来的时候,AuthenticationMiddleware中的process_request方法中
            # 会取到user_id,进而取到user对象,然后添加到request.user属性中 --> request.user = user_obj
            # 后续可以通过request.user拿到当前的登陆用户对象,否则得到一个匿名用户对象
            return redirect("/home/")
    return render(request, "login.html")
​
def register(request):
    if request.method == "POST":
        username = request.POST.get("username")
        pwd = request.POST.get("pwd")
        # 调用内置的创建普通用户的专用方法,创建一个新的普通用户
        User.objects.create_user(username=username, password=pwd)
        #调用内置的创建超级用户的专用方法,创建一个新的超级用户
        #User.objects.create_superuser(username='用户名',password='密码')
    return render(request, "register.html")
​
@login_required     
#auth提供的装饰器工具,用来快捷的给某个视图添加登录校验。若用户没有登录,则会跳转到django默认的登录URL '/accounts/login/ ' 并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。如果需要自定义登录的URL,则需要在settings.py文件中通过LOGIN_URL进行修改:LOGIN_URL = '/login/' 
def home(request):
    return render(request, "home.html")
​
# 调用auth内置的注销方法
#当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错
def logout(request):
    auth.logout(request)
    return redirect("/login/")
​
@login_required
def set_password(request):
    if request.method == "POST":
        old_pwd = request.POST.get("old_pwd")
        pwd = request.POST.get("pwd")
        re_pwd = request.POST.get("re_pwd")
        user_obj = request.user
        if user_obj.check_password(old_pwd):# 先校验原密码是否正确,正确返回True,否则返回False
            if pwd == re_pwd:
                user_obj.set_password(pwd)   # 去数据库修改密码
                user_obj.save()             # 修改密码一定要保存
                return redirect("/login/")
            elsereturn HttpResponse("两次输入不一致")
        else:
            return HttpResponse("原密码错误")
    return render(request, "set_password.html")

login.html:

<body>
<h1>欢迎登陆</h1>
<form action="" method="post">
    {% csrf_token %}
    <input type="text" name="username">
    <input type="password" name="pwd">
    <input type="submit" value="登录">
</form>
</body>

register.html:

<body>
<h1>欢迎注册</h1>
<form action="/reg/" method="post">
    {% csrf_token %}
    <input type="text" name="username">
    <input type="password" name="pwd">
    <input type="submit" value="注册">
</form>
</body>

home.html:

<body>
<h1>Hello,{{ request.user.username }}</h1>  #request.user获取当前用户对象
<a href="/logout/">注销</a>
<a href="/set_password/">修改密码</a>
</body>

set_password.html:

<body>
<form action="" method="post">
    {% csrf_token %}
    <p>原密码:<input type="password" name="old_pwd"></p>
    <p>新密码:<input type="password" name="pwd"></p>
    <p>重复密码:<input type="password" name="re_pwd"></p>
    <p><input type="submit" value="修改"></p>
</form>
</body>

12.81 扩展默认的auth_user表

User类内没有实现方法,只是继承内置的 AbstractUser 类,通过继承内置的 AbstractUser 类,来自定义一个Model类,这样既能根据项目需求灵活的设计用户表,又能使用Django强大的认证系统了

models:

from django.db import models
from django.contrib.auth.models import AbstractUser
​
class UserInfo(AbstractUser):
    phone = models.CharField(max_length=11)

注意:按上面的方式扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,现在使用新定义的UserInfo表来做用户认证

# 引用Django自带的User表,继承使用时需要设置
AUTH_USER_MODEL = "app01.UserInfo"

自定义认证系统默认使用的数据表之后,就可以像使用默认的auth_user表那样使用自定义的UserInfo表了,比如:

创建普通用户:

UserInfo.objects.create_user(username='用户名', password='密码')

创建超级用户:

UserInfo.objects.create_superuser(username='用户名', password='密码')

注意:一旦指定了新的认证系统所使用的表,就需要重新在数据库中创建该表,而不能继续使用原来默认的auth_user表

12.9 自定义图片验证码登录

login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎登陆</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/mystyle.css">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-4 col-md-offset-4" id="login-form">
            <form autocomplete="off" novalidate>        {# 去掉浏览器验证和提示 #}
                <div class="form-group">
                    <label for="{{ form_obj.username.id_for_label }}">
                    {{ form_obj.username.label }}</label>
                    {{ form_obj.username }}
                    {# <span class="error">{{ form_obj.username.errors.0 }}</span>    #}
                    {#ajax提交数据,此处不刷新#}
                </div><div class="form-group">
                    <label for="{{ form_obj.password.id_for_label }}">
                    {{ form_obj.password.label }}</label>
                    {{ form_obj.password }}
                </div><div class="form-group" id="v-code-wrapper">
                    <label for="{{ form_obj.password.id_for_label }}">验证码</label>
                    <input type="text" class="form-control" id="v-code-input">
                    <img src="/v_code/" alt="" id="v-code" >    //#此处请求URL
                </div><button type="button" class="btn btn-success" id="login-button">登录</button>
                <span class="error" id="login-error"></span>
            </form>
        </div>
    </div>
</div>
<script src="/static/jquery-3.3.1.min.js"></script>
<script src="/static/setupAjax.js"></script>        // #ajax请求,csrf_token验证(跨站请求伪造)
<script>
    $(document).ready(function () {                 // #文档加载完之后自动执行的
        $("#login-button").click(function () {       // #登录按钮点击之后要做的事儿           
            $.ajax({
                url: "/login/",
                type: "POST",
                data: {
                    username: $("#id_username").val(),
                    password: $("#id_password").val(),
                    v_code: $("#v-code-input").val()
                },
                success: function (data) {
                    if (!data.code) {
                        location.href = data.data;
                    } else {
                        // #有错误
                        $("#login-error").text(data.data);
                    }
                },
                error: function (err) {
                    console.log(err)
                }
            })
        });
        // #当form中的input标签获取光标之后,就清空之前的错误信息
        $("form input").focus(function () {
            $("#login-error").text("");
        });
​
        // #点击图片刷新验证码(如果是问号结尾就去掉问号,如果不是问号结尾就加个问号)
        $("#v-code").click(function () {
            var oUrl = this.src;  // #得到原始的url
            if (/[?]$/.test(oUrl)){
                this.src = "/v_code/";
            }else {
                this.src += "?";
            }
        });
    })
</script>
</body>
</html>

urls.py:

from django.conf.urls import url, include
from django.contrib import admin
from blog import views
from django.conf import settings
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.login),
    url(r'^index/', views.index),
    url(r'^v_code/', views.v_code),
    url(r'^logout/', views.logout), 
]

views.py: (v_code:生成随机图片验证码函数)

from django.views.decorators.cache import never_cache
import random
​
@never_cache  # 指定该视图不使用缓存
def v_code(request):
    #导入pillow模块
    from PIL import Image, ImageDraw, ImageFont
   
    # 定义一个生成随机颜色代码的内部函数
    def get_color():
        return random.randint(0,255), random.randint(0,255), random.randint(0,255)
​
    # 生成一个图片对象
    img_obj = Image.new(
        "RGB",
        (250, 35),
        color=get_color()
    )
​
    # 在图片中加文字,生成一个画笔对象
    draw_obj = ImageDraw.Draw(img_obj)
    # 加载字体文件
    font_obj = ImageFont.truetype("static/font/kumo.ttf", size=28)
    # 写字
    # draw_obj.text(
    #     (0, 0),  # 位置
    #     "A",  # 内容
    #     (0,0,0),  # 颜色
    #     font=font_obj
    # )
# for循环5次,每次写一个随机的字符
    tmp_list = []
    for i in range(5):
        n = str(random.randint(0, 9))     # 随机生成一个数字
        l = chr(random.randint(97, 122))  # 随机生成一个小写的字母
        u = chr(random.randint(65, 90))   # 随机生成一个大写的字母
        r = random.choice([n, l, u])      # 从上面三个随机选一个
        tmp_list.append(r)
        draw_obj.text(
            (i*48+20, 0),  # 位置
            r,            # 内容
            get_color(),   # 随机颜色
            font=font_obj
        )
    # 得到生成的随机验证码
    v_code_str = "".join(tmp_list)
    request.session["v_code"] = v_code_str.upper() #使用session保存验证码
    # global VCODE          不能使用全局变量保存验证码,会被覆盖,每一个请求应该对应自己的验证码
    # VCODE = v_code_str
# 加干扰线
        # width = 250  # 图片宽度(防止越界)
        # height = 35
        # for i in range(2):
        #     x1 = random.randint(0, width)
        #     x2 = random.randint(0, width)
        #     y1 = random.randint(0, height)
        #     y2 = random.randint(0, height)
        #     draw_obj.line((x1, y1, x2, y2), fill=get_color())
        #
        # 加干扰点
        # for i in range(2):
        #     draw_obj.point([random.randint(0, width), random.randint(0, height)], fill=get_color())
        #     x = random.randint(0, width)
        #     y = random.randint(0, height)
        #     draw_obj.arc((x, y, x+4, y+4), 0, 90, fill=get_color())
​
​
    # 第一版: 将生成的图片保存到文件中
    # with open("xx.png", "wb") as f:
    #     img_obj.save(f, "png")
    # print("图片已经生成!")
    # with open("xx.png", "rb") as f:
    #     return HttpResponse(data, content_type="image/png")
# 第二版:直接将图片在内存中保存
    from io import BytesIO
    tmp = BytesIO()  # 生成一个io对象
    img_obj.save(tmp, "png")
    data = tmp.getvalue()
    return HttpResponse(data, content_type="image/png")

view.py:(login.py)

#前端显示错误信息的两种方式:
#form表单提交数据,使用render刷新页面,return render(request, "login.html", {"form_obj": form_obj}),
#前端页面相应添加 <span class="error">{{ form_obj.username.errors.0 }}</span>
#ajax提交数据局部刷新,使用js显示错误信息
from django.contrib import auth
def login(request):
    form_obj = forms.LoginForm()
    if request.method == "POST":
        ret = {"code": 0}
        username = request.POST.get("username")
        password = request.POST.get("password")
        v_code = request.POST.get("v_code", "")
​
        if v_code.upper() == request.session.get("v_code", ""):
            # 验证码正确
            user = auth.authenticate(username=username, password=password)
            if user:
                # 用户名密码正确
                auth.login(request, user)  # 生成session数据, session数据里保存user_id
                ret["data"] = "/index/"
            else:
                # 用户名或密码错误
                ret["code"] = 1
                ret["data"] = "用户名或密码错误"
        else:
            # 验证码错误
            ret["code"] = 1
            ret["data"] = "验证码错误"
        return JsonResponse(ret)
    return render(request, "login.html", {"form_obj": form_obj})

12.91 极验科技滑动验证码

login2.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎登陆</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/mystyle.css">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-4 col-md-offset-4" id="login-form">
            <form autocomplete="off" novalidate>
                <div class="form-group">
                    <label for="{{ form_obj.username.id_for_label }}">
                    {{ form_obj.username.label }}</label>
                    {{ form_obj.username }}
                </div>
                <div class="form-group">
                    <label for="{{ form_obj.password.id_for_label }}">
                    {{ form_obj.password.label }}</label>
                    {{ form_obj.password }}
                </div>
                <button type="button" class="btn btn-success" id="login-button">登录</button>
                <span class="error" id="login-error"></span>
                <div id="popup-captcha"></div>
            </form>
        </div>
    </div>
</div>
<script src="/static/jquery-3.3.1.min.js"></script>
<script src="/static/setupAjax.js"></script>                        #提供ajax的csrf_token验证
<!-- 引入封装了failback的接口--initGeetest -->
<script src="http://static.geetest.com/static/tools/gt.js"></script>//  #导入js文件
<script>
    var handlerPopup = function (captchaObj) {
        // #成功的回调
        captchaObj.onSuccess(function () {
            var validate = captchaObj.getValidate();
            $.ajax({
                url: "/login2/", // #进行二次验证
                type: "post",
                data: {
                    username: $("#id_username").val(),
                    password: $("#id_password").val(),
                    geetest_challenge: validate.geetest_challenge,
                    geetest_validate: validate.geetest_validate,
                    geetest_seccode: validate.geetest_seccode
                },
                success: function (data) {
                    if (!data.code) {
                        location.href = data.data;
                    } else {
                        // #有错误
                        $("#login-error").text(data.data);
                    }
                },
                error: function (err) {
                    console.log(err)
                }
            });
        });
         $("#login-button").click(function () {
            captchaObj.show();
        });
        // 将验证码加到id为captcha的元素里
        captchaObj.appendTo("#popup-captcha");
        // 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html
    };
​
    // #验证开始需要向网站主后台获取id,challenge,success(是否启用failback)
    $.ajax({
        url: "/pcgetcaptcha?t=" + (new Date()).getTime(), // 加随机数防止缓存
        type: "get",
        dataType: "json",
        success: function (data) {
            // #使用initGeetest接口
            // #参数1:配置参数
            // #参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件
            initGeetest({
                gt: data.gt,
                challenge: data.challenge,
                product: "popup", // #产品形式,包括:float,embed,popup。注意只对PC版验证码有效
                offline: !data.success // #表示用户后台检测极验服务器是否宕机,一般不需要关注
                // 更多配置参数请参见:http://www.geetest.com/install/sections/idx-client-sdk.html#config
            }, handlerPopup);
        }
    });
​
    $(document).ready(function () {          // #文档加载完之后自动执行的                         
        $("form input").focus(function () {  // #当form中的input标签获取光标之后,就清空之前的错误信息
            $("#login-error").text("");
        });
       
        $("#v-code").click(function () {      // #点击图片刷新验证码
            this.src += "?";
        });
    })
</script>
</body>
</html>

urls.py:

from django.conf.urls import url, include
from django.contrib import admin
from blog import views
from django.conf import settings
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index),
    url(r'^login2/', views.login2),
    # 极验科技 获取验证码的url
    url(r'^pcgetcaptcha/', views.pcgetcaptcha)
    url(r'^logout/', views.logout),
    # 以上都匹配不上,就由我来
    url(r'^$', views.index)
]

views.py:

# 极验科技依赖
from geetest import GeetestLib
pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"
pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"# 滑动验证码加载需要的视图函数
def pcgetcaptcha(request):
    user_id = 'test'
    gt = GeetestLib(pc_geetest_id, pc_geetest_key)
    status = gt.pre_process(user_id)
    request.session[gt.GT_STATUS_SESSION_KEY] = status
    request.session["user_id"] = user_id
    response_str = gt.get_response_str()
    return HttpResponse(response_str)
def login2(request):
    form_obj = forms.LoginForm()
    if request.method == "POST":
        gt = GeetestLib(pc_geetest_id, pc_geetest_key)
        challenge = request.POST.get(gt.FN_CHALLENGE, '')
        validate = request.POST.get(gt.FN_VALIDATE, '')
        seccode = request.POST.get(gt.FN_SECCODE, '')
        status = request.session[gt.GT_STATUS_SESSION_KEY]
        user_id = request.session["user_id"]
        if status:
            result = gt.success_validate(challenge, validate, seccode, user_id)
        else:
            result = gt.failback_validate(challenge, validate, seccode)
        # 如果验证码正确
        if result:
            ret = {"code": 0}
            username = request.POST.get("username")
            password = request.POST.get("password")
            user = auth.authenticate(username=username, password=password)
            if user:
                # 用户名密码正确
                ret["data"] = "/index/"
            else:
                # 用户名或密码错误
                ret["code"] = 1
                ret["data"] = "用户名或密码错误"
            return JsonResponse(ret)
    return render(request, "login2.html", {"form_obj": form_obj})

12.92 媒体文件media、 kindeditor、beautifulsoup模块

urls.py:

from django.conf.urls import url, include
from django.contrib import admin
from django.views.static import serve
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # media路由配置
    url(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}),
]

settings.py:

# 用户上传的文件配置项
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")

views.py:

# kindeditor 接收上传文件的视图
from django.conf import settings
def upload_img(request):
    #print(request.POST)
    #print(request.FILES)
    # 把上传的文件保存在服务端
    file_obj = request.FILES.get('imgFile')
    with open(os.path.join(settings.MEDIA_ROOT, "article_img", file_obj.name), "wb") as f:
        # 从file_obj一点一点读,往f里面一点一点写
        for chunk in file_obj.chunks():
            f.write(chunk)
​
    # 返回响应的格式要有要求
    ret = {"error": 0, "url": "/media/article_img/{}".format(file_obj.name)} #"error": 0表示响应成功
    return JsonResponse(ret)
​
# 添加新文章
def add_article(request):
    if request.method == "POST":
        print(request.POST)
        title = request.POST.get("title")
        content = request.POST.get("content")
        # 入库之前要对数据做校验
        from bs4 import BeautifulSoup       # 导入beautifulsoup模块,用于解析HTML标签
        soup = BeautifulSoup(content, 'html.parser')
        for tag in soup.find_all("script"): 
            tag.decompose()                 # 找到每一个script标签,并删除掉
        # 销毁后的数据
        # 去数据库创建新文章,操作两张表:文章表和文章详情表
        with transaction.atomic():
            article_obj = models.Article.objects.create(
                title=title,
                desc=soup.text[0:150],  # 针对文档内容做截取,soup.text:去掉标签后的文本
                user=request.user
            )
            models.ArticleDetail.objects.create(
                content=soup.prettify(),  # 经过处理后的文章内容,soup.prettify:格式化后的html
                article=article_obj
            )
        return redirect("/backend/")
    return render(request, "add_article.html")

add_article.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>添加新文章</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-12">
            <form action="/add_article/" method="post">
                {% csrf_token %}
                <div class="form-group">
                    <label for="article-title">标题</label>
                    <input type="text" name="title" class="form-control" id="article-title" placeholder="标题">
                </div>
                <div class="form-group">
                    <label for="article-content">内容</label>
                    <textarea id="article-content" name="content" class="form-control" cols="30" rows="10"></textarea>
                </div>
                <button type="submit" class="btn btn-success">发布</button>
            </form>
        </div>
    </div>
</div>
<script src="/static/jquery-3.3.1.min.js"></script> # 先导入jquery.js
<script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
<script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
<script>
    KindEditor.ready(function (K) {
        window.editor = K.create('#article-content', {  // #设置textarea文本域
            width: '800px',             // #宽
            height: '500px',            // #高
            uploadJson: '/upload_img/',  // #上传文件的URL
            extraFileUploadParams: {csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val()}  // 给上传文件添加csrftoken
        });
    });
</script>
</body>
</html>

12.10 自定义分页

utils/mypage.py:(封装)

class Page(object):
    """
    一个自定义分页类,可以实现Django ORM数据的分页展示
    使用说明:
        from utils import mypage
        page_obj = mypage.Page(total_num, current_page, 'publisher_list')
        publisher_list = data[page_obj.data_start:page_obj.data_end]
        page_html = page_obj.page_html()
​
        为了显示效果,show_page_num最好使用奇数
    """
    def __init__(self, total_num, current_page, url_prefix, per_page=10, show_page_num=11):
        """
        :param total_num: 数据的总条数
        :param current_page: 当前访问的页码
        :param url_prefix: 分页代码里a标签的前缀
        :param per_page: 每一页显示多少条数据
        :param show_page_num: 页面上最多显示多少个页码
        """
        self.total_num = total_num
        self.url_prefix = url_prefix
        self.per_page = per_page
        self.show_page_num = show_page_num
        # 通过初始化传入的值计算得到的值
        self.half_show_page_num = self.show_page_num // 2
        # 当前数据总共需要多少页码
        total_page, more = divmod(self.total_num, self.per_page)
        # 如果有余数,就把页码数+1
        if more:
            total_page += 1
        self.total_page = total_page
        # 对传进来的当前页码数做有效性校验
        try:
            current_page = int(current_page)
        except Exception as e:
            current_page = 1
        # 如果当前页码数大于总页码数,默认展示最后一页的数据
        # current_page = total_page if current_page > total_page else current_page
        if current_page > self.total_page:
            current_page = self.total_page
        # 如果当前页码数小于1,默认展示第一页的数据
        if current_page < 1:
            current_page = 1
​
        self.current_page = current_page
        # 求 页面上 需要展示的页码范围
        if self.current_page - self.half_show_page_num <= 1:
            page_start = 1
            page_end = show_page_num
        elif self.current_page + self.half_show_page_num >= self.total_page:
            page_end = self.total_page
            page_start = self.total_page - self.show_page_num + 1
        else:
            page_start = self.current_page - self.half_show_page_num
            page_end = self.current_page + self.half_show_page_num
​
        self.page_start = page_start
        self.page_end = page_end  # 我上面一通计算得到的页面显示的页码结束
        # 如果你一通计算的得到的页码数比我总共的页码数还多,我就把页码结束指定成我总共有的页码数
        if self.page_end > self.total_page:
            self.page_end = self.total_page
​
    @property
    def data_start(self):
        # 返回当前页应该从哪儿开始切数据
        return (self.current_page-1)*self.per_page
    @property
    def data_end(self):
        # 返回当前页应该切到哪里为止
        return self.current_page*self.per_page
​
    def page_html(self):
        li_list = []
        # 添加前面的nav和ul标签
        li_list.append("""
            <nav aria-label="Page navigation">
            <ul class="pagination">
        """)
        # 添加首页
        li_list.append('<li><a href="/{}/?page=1">首页</a></li>'.format(self.url_prefix))
        # 添加上一页
        if self.current_page <= 1:  # 没有上一页
            prev_html = '<li class="disabled"><a aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>'
        else:
            prev_html = '<li><a href="/{}/?page={}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>'.format(self.url_prefix,
                self.current_page - 1)
        li_list.append(prev_html)
        for i in range(self.page_start, self.page_end + 1):
            if i == self.current_page:
                tmp = '<li class="active"><a href="/{0}/?page={1}">{1}</a></li>'.format(self.url_prefix, i)
            else:
                tmp = '<li><a href="/{0}/?page={1}">{1}</a></li>'.format(self.url_prefix, i)
            li_list.append(tmp)
        # 添加下一页
        if self.current_page >= self.total_page:  # 表示没有下一页
            next_html = '<li class="disabled"><a aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>'
        else:
            next_html = '<li><a href="/{}/?page={}" aria-label="Previous"><span aria-hidden="true">&raquo;</span></a></li>'.format(
                self.url_prefix, self.current_page + 1)
        li_list.append(next_html)
        # 添加尾页
        li_list.append('<li><a href="/{}/?page={}">尾页</a></li>'.format(self.url_prefix, self.total_page))
​
        # 添加nav和ul的结尾
        li_list.append("""
            </ul>
        </nav>
        """)
        # 将生成的li标签 拼接成一个大的字符串
        page_html = "".join(li_list)
        return page_html

urls.py:

from django.conf.urls import url
from blog import views
urlpatterns = [
    url(r'(.*)/(category|tag|archive)/(.*)/$', views.home),  # home(request, username, tag, "跆拳道")
    url(r'(.*)/$', views.home),  # home(request, username)
]

views.py:(使用)

def home(request, username, *args):
    print(username, args)
    print("=" * 120)
    """
    :param request: 请求对象
    :param username: 用户名
    :return:
    """
    user_obj = models.UserInfo.objects.filter(username=username).first()
    # 去数据库中根据用户名找所有的文章
    if not user_obj:
        return HttpResponse("404")
    else:
        blog = user_obj.blog
​
    # # 查询左侧分类那些面板中用到的数据
    # # 1. 当前站点下的文章分类
    # # category_list = models.Category.objects.filter(blog=blog)
    # from django.db.models import Count
    # category_list = models.Category.objects.filter(blog=blog).annotate(num=Count("article")).values("title", "num")
    # # 2. 文章标签分类
    # tag_list = models.Tag.objects.filter(blog=blog)
    # # 3. 日期归档
    # archive_list = models.Article.objects.filter(user=user_obj).extra(
    #     select={"ym": "DATE_FORMAT(create_time, '%%Y-%%m')"}
    # ).values("ym").annotate(num=Count("nid")).values("ym", "num")
    # print(archive_list)
​
​
    if not args: # 查当前用户的所有文章
        data = models.Article.objects.filter(user__username=username)
    else:
        if args[0] == "category":  # 按照文章分类查询
            data = models.Article.objects.filter(user=user_obj).filter(category__title=args[1])
        elif args[0] == "tag":      # 按照标签查询
            data = models.Article.objects.filter(user=user_obj).filter(tags__title=args[1])
        else:                      # 按照日期归档查询
            year, month = args[1].split("-")
            data = models.Article.objects.filter(user=user_obj).filter(create_time__year=year, create_time__month=month)
    total_num = data.count()
    current_page = request.GET.get("page")
    url_prefix = request.path_info.strip("/")
​
    from utils import mypage
    page_obj = mypage.Page(total_num, current_page, url_prefix, per_page=1)
    data = data[page_obj.data_start:page_obj.data_end]
    page_html = page_obj.page_html()
​
    return render(request, "home.html", {
        "username": username,
        "blog": blog,
        "article_list": data,
        "page_html": page_html
    })

home.html:

{% extends 'base.html' %}
​
{% block page-main %}
    <div class="article-list">
        {% for article in article_list %}
            <div class="article">
                <div class="article-top">
                    <h2><a href="/blog/{{ username }}/article/{{ article.nid }}/">{{ article.title }}</a></h2>
                </div>
                <div class="article-middle">
                    <div class="media">
                        <div class="media-left media-middle">
                            <a href="#">
                                <img class="media-object article-avatar" src="/media/{{ article.user.avatar }}"
                                     alt="...">
                            </a>
                        </div>
                        <div class="media-body">
                            {{ article.desc }}
                        </div>
                    </div>
                </div>
                <div class="article-footer">
                    <span class="article-user"><a
                            href="/blog/{{ article.user.username }}/">{{ article.user.username }}</a></span>发布于
                    <span>{{ article.create_time }}</span>
                    <span class="glyphicon glyphicon-thumbs-up">点赞({{ article.up_count }})</span>
                    <span class="glyphicon glyphicon-comment">评论({{ article.comment_count }})</span>
                </div>
            </div>
            <hr>
        {% endfor %}
        {{ page_html|safe }} #page_html:所有的分页tag
    </div>
{% endblock %}
posted @ 2020-10-15 15:09  small_white-  阅读(404)  评论(0编辑  收藏  举报