项目开发流程

# 1.需求分析
    架构师+产品经理+开发者组长
      在跟客户谈需求之前,会大致先了解客户的需求,然后自己先设计一套比较好写方案
      在跟客户沟通交流中引导客户往我们之前想好的方案上面靠
      形成一个初步的方案

# 2.项目设计
    架构师干的活
      编程语言选择
    框架选择
    数据库选择
        主库:MySQL,postgreSQL,...
      缓存数据库:redis、mongodb、memcache...
    功能划分
        将整个项目划分成几个功能模块
    找组长开会
        给每个组分发任务
       项目报价
        技术这块需要多少人,多少天(一个程序员一天15002000计算(大致))
      产品经理公司层面 再加点钱
          公司财务签字确认
        公司老板签字确认
      产品经理去跟客户沟通

      后续需要加功能 继续加钱

# 3.分组开发
    组长找组员开会,安排各自功能模块
  我们其实就是在架构师设计好的框架里面填写代码而已(码畜)

  我们在写代码的时候 写完需要自己先测试是否有bug
  如果是一些显而易见的bug,你没有避免而是直接交给了测试部门测出来
  那你可能就需要被扣绩效了(一定要跟测试小姐姐搞好关系)
      薪资组成    15K(合理合规合法的避税)
      底薪    10K
      绩效    3K
      岗位津贴 1K
      生活补贴 1K

# 4.测试
    测试部门测试你的代码
      压力测试
    ...
# 5.交付上线
    1.交给对方的运维人员
    2.直接上线到我们的服务器上 收取维护费用
    3.其他...

BBS项目表设计

分析部分

"""
一个项目中最最最重要的不是业务逻辑的书写
而是前期的表设计,只要将表设计好了,后续的功能书写才会一帆风顺

bbs表设计
    1.用户表
        继承AbstractUser
        扩展
            phone 电话号码
            avatar  用户头像
            create_time  创建时间

        外键字段
            一对一个人站点表

    2.个人站点表
        site_name   站点名称
        site_title  站点标题
        site_theme  站点样式

    3.文章标签表
        name        标签名

        外键字段
            一对多个人站点

    4.文章分类表
        name        分类名

        外键字段
            一对多个人站点

    5.文章表
        title    文章标题
        desc    文章简介
        content    文章内容
        create_time 发布时间

        数据库字段设计优化(******)
            (虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率)
        up_num         点赞数
        down_num       点踩数
        comment_num    评论数

        外键字段
            一对多个人站点
            多对多文章标签
            一对多文章分类



    6.点赞点踩表
        记录哪个用户给哪篇文章点了赞还是点了踩
        user                        ForeignKey(to="User")                
        article                    ForeignKey(to="Article")    
        is_up                        BooleanField()

        1                1                1
        1                2                1
        1                3                0
        2                1                1


    7.文章评论表
        记录哪个用户给哪篇文章写了哪些评论内容
        user                        ForeignKey(to="User")                
        article                    ForeignKey(to="Article")
        content                    CharField()
        comment_time        DateField()
        # 自关联(例:根子评论),注意null=True,可以为空        # 写法一:
        parent                    ForeignKey(to="Comment",null=True)        
        # 写法二:ORM专门提供了自关联写法    
        parent                    ForeignKey(to="self",null=True)

    id(根评论主键值)    user_id            article_id                parent_id
    1                   1                        1                                        
    2                   2                        1                   1       # id=1的根评论的子评论

根评论子评论的概念
    根评论就是直接评论当前发布的内容的

    子评论是评论别人的评论
        1.PHP是世界上最牛逼的语言
            1.1 python才是最牛逼的
                1.2 java才是

    根评论与子评论是一对多的关系
"""

注意自关联方法

BBS表关系图解

创建部分

  • models.py
from django.db import models

# Create your models here.
from django.contrib.auth.models import AbstractUser


class UserInfo(AbstractUser):
    phone = models.BigIntegerField(verbose_name='手机号', null=True)
    # 头像
    avatar = models.FileField(upload_to='avatar/', default='avatar/default.png', verbose_name='用户头像')
    """
    给avatar字段传文件对象,改文件会自动存储到avatar文件下,然后avatar字段只保存文件路径avatar/default.png
    """
    create_time = models.DateField(auto_now_add=True)
    blog = models.OneToOneField(to='Blog', null=True)


class Blog(models.Model):
    site_name = models.CharField(max_length=32, verbose_name='站点')
    site_title = models.CharField(max_length=32, verbose_name='站点标题')
    # 简单模拟 认识样式内部原理操作
    site_theme = models.CharField(max_length=64, verbose_name='站点样式')


class Category(models.Model):
    name = models.CharField(max_length=32, verbose_name='文章分类')
    blog = models.ForeignKey(to='Blog', null=True)


class Tag(models.Model):
    name = models.CharField(max_length=32, verbose_name='文章标签')
    blog = models.ForeignKey(to='Blog', null=True)


class Article(models.Model):
    title = models.CharField(max_length=64, verbose_name='文章标题')
    desc = models.CharField(max_length=255, verbose_name='文章简介')
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateField(auto_now_add=True)

    # 数据库字段设计优化
    up_num = models.BigIntegerField(default=0, verbose_name='点赞数')
    down_num = models.BigIntegerField(default=0, verbose_name='点踩数')
    comment_num = models.BigIntegerField(default=0, verbose_name='评论数')

    # 外键字段
    blog = models.ForeignKey(to='Blog', null=True)
    category = models.ForeignKey(to='Category', null=True)
    tags = models.ManyToManyField(to='Tag',
                                  through='Article2Tag',
                                  through_fields=('article', 'tag')
                                  )


class Article2Tag(models.Model):
    article = models.ForeignKey(to='Article')
    tag = models.ForeignKey(to='Tag')


class UpAndDown(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    is_up = models.BooleanField()  # 传布尔值存0/1


class Comment(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    content = models.CharField(max_length=255, verbose_name='评论内容')
    comment_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
    # 自关联
    parent = models.ForeignKey(to='self', null=True)
  • settings.py
"""
Django settings for BBS project.

Generated by 'django-admin startproject' using Django 1.11.11.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'n=#fyz31x#is!izkxbvtgwjduq@)9+zc(yv%4c!0z)%hs!e_$k'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'BBS.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR, 'templates']
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'BBS.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'BBS',
        'USER': 'root',
        'PASSWORD': '*****',
        'HOST': '47.116.41.***',
        'PORT': 3307,
        'CHARSET': 'utf8'
    }
}


# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static')
]

# 告诉django你要用UserInfo替代auth_user(******)
AUTH_USER_MODEL = 'app01.UserInfo'

注册功能

  • 拓展
"""
我们之前是直接在views.py中书写的forms组件代码
但是为了接耦合 应该将所有的forms组件代码单独写到一个地方

如果你的项目至始至终只用到一个forms组件那么你可以直接建一个py文件书写即可
    myforms.py
但是如果你的项目需要使用多个forms组件,那么你可以创建一个文件夹在文件夹内根据
forms组件功能的不同创建不同的py文件
    myforms文件夹
        regform.py
        loginform.py
        userform.py
        orderform.py
        ...
"""

一般情况下我们在存储用户文件的时候为了避免文件名冲突的情况
会自己给文件名加一个前缀    
    uuid
    随机字符串
    ...
  • urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
from django.views.static import serve
from BBS import settings

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 注册功能
    url(r'^register/', views.register, name='register'),
    # 暴露后端指定文件夹资源
    url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
]

# settings.py
# 配置文件上传的文件存储位置
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')  # 文件名 自己定义
  • myforms.py
from django import forms  # forms组件所需模块
from app01 import models


class MyRegForm(forms.Form):
    username = forms.CharField(label="用户名", max_length=8, min_length=3,
                               error_messages={
                                   'required': '用户名不能为空',
                                   'max_length': '用户名最多8位',
                                   'min_length': '用户名最少3位',
                               },
                               widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
                               )

    password = forms.CharField(label="密码", max_length=8, min_length=3,
                               error_messages={
                                   'required': '密码不能为空',
                                   'max_length': '密码最多8位',
                                   'min_length': '密码最少3位',
                               },
                               widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                               )

    confirm_password = forms.CharField(label="确认密码", max_length=8, min_length=3,
                                       error_messages={
                                           'required': '确认密码不能为空',
                                           'max_length': '确认密码最多8位',
                                           'min_length': '确认密码最少3位',
                                       },
                                       widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                                       )

    email = forms.EmailField(label='邮箱',
                             error_messages={
                                 'required': '邮箱不能为空',
                                 'invalid': '邮箱格式不正确',
                             },
                             widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})
                             )

    # 钩子函数
    def clean_username(self):
        username = self.cleaned_data.get('username')
        # 数据库中校验
        is_exists = models.UserInfo.objects.filter(username=username)
        if is_exists:
            # 提示信息
            self.add_error('username', '用户名已存在')

        return username

    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')

        if not password == confirm_password:
            self.add_error('confirm_password', '两次密码不一致')
        return self.cleaned_data
  • views.py
from django.shortcuts import render, HttpResponse
from app01.myforms import MyRegForm
from app01 import models
from django.http import JsonResponse


# Create your views here.
def register(request):
    form_obj = MyRegForm()
    if request.method == "POST":
        back_dic = {"code": 1000, "msg": ""}
        # 校验数据是否合法
        form_obj = MyRegForm(request.POST)
        if form_obj.is_valid():
            # print(form_obj.cleaned_data)     # {'username': 'alias', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'}
            clean_data = form_obj.cleaned_data  # 将校验通过的数据字典赋值给一个变量
            clean_data.pop("confirm_password")  # {'username': 'alias', 'password': '123', 'email': '123@qq.com'}
            # 用户头像
            file_obj = request.FILES.get("avatar")
            """针对用户头像一定要判断是否传值 不能直接添加到字典中"""
            if file_obj:
                clean_data['avatar'] = file_obj
            # 操作数据库保存数据
            models.UserInfo.objects.create_user(**clean_data)
            back_dic["url"] = "/login/"
        else:
            back_dic["code"] = 2000
            back_dic["msg"] = form_obj.errors
        return JsonResponse(back_dic)

    return render(request, "register.html", locals())
  • register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- Bootstrap3 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- Bootstrap3 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <!-- font-awesome.min.css图标库4.7版本 -->
    <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h1 class="text-center">注册</h1>
            <form id="myform">    <!--我们不用form表单提交数据 只是单纯用一下form标签而已-->
                {% for form in form_obj %}
                    <div class="from-group">
                        <!--form.auto_id获取forms渲染的input框的id-->
                        <label for="{{ form.auto_id }}">{{ form.label }}</label>
                        {{ form }}
                        <span style="color: red" class="pull-right"></span>
                    </div>
                {% endfor %}
                <div class="form-group" style="margin-top: 10px">
                    <label for="myfile">头像
                        {% load static %}
                        <img src="{% static "img/default.png" %}" alt="" id="myimg" width="100px" style="margin-left: 10px">
                    </label>
                    <input type="file" id="myfile" name="avatar" style="display: none">
                </div>

                <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit">
            </form>
        </div>
    </div>
</div>
<script src="{% static "js/mysteup.js" %}/"></script>
<script>
    // 选择头像实时显示
    $("#myfile").change(function () {
        // 文件阅读器对象
        // 1、先生成一个文件阅读器对象
        let myFileReaderObj = new FileReader();
        // 2、获取用户上传的头像文件
        let fileObj = $(this)[0].files[0];
        // 3、将文件对象交给阅读器对象读取
        myFileReaderObj.readAsDataURL(fileObj);     // 这步操作是异步
        // 4、利用文件阅读器将文件展示到前端页面   修改img的src属性
        // 等待myFileReaderObj文件阅读器加载完毕之后再执行
        myFileReaderObj.onload = function(){
            $("#myimg").attr("src",myFileReaderObj.result)
        }
    });
    $("#id_commit").click(function () {
        // 发送ajax请求  我们发送的数据中即包含普通键值也包含文件
        let formDataObj = new FormData();
        // 添加普通键值对
        // console.log($("#myform").serializeArray())  // [{},{},{},...] 只包含普通键值对
        $.each($("#myform").serializeArray(),function (index,obj) {
            // console.log(index,obj)   // obj = {}
            formDataObj.append(obj.name,obj.value)
        });
        // 添加文件
        formDataObj.append('avatar',$("#myfile")[0].files[0]);
        // 发送ajax请求
        $.ajax({
            url: '',
            type: 'post',
            dataType: 'json',
            data: formDataObj,

            contentType: false,
            processData: false,
            success: function (args) {
                if (args.code == 1000){
                    // 跳转到登录页面
                    window.location.href = args.url
                }else{
                    // 如何将对应的错误信息提示展示到对应的input框下面,因为用的ajax所以无法像之前在span标签里面写form.errors.0
                    // forms组件渲染的标签的id值都是 id_字段名
                    $.each(args.msg,function (index,obj) {
                        {#console.log(index,obj)  //  username        ["用户名不能为空"]#}
                        let targetId = '#id_' + index;      // input标签
                        {#$(targetId).next().text(obj[0]).parent().addClass('has-error')#}
                        $(targetId).next().text(obj[0]).prev().css("border","solid red")
                    })
                }
            }
        })
    })
    // 给所有的input框绑定获取焦点事件
    $('input').focus(function () {
        // 将input下面的span标签和input外面的div标签修改内容及属性
        $(this).next().text('').prev().removeAttr("style","");
    })
</script>
</body>
</html>

自定义上传文件的文件名

一 需求

Django实现自定义文件名存储文件
    使文件名看起来统一
    避免收到中文文件导致传输、存储等问题
    相同的文件也需要使用不同的文件名

二 实现思路

思路:
    生成14位随机字母加数字、后10位采用时间戳。从而实现相同文件不同文件名

1.view版:
    在view接收到文件名之后进行重命名,不修改Django默认的文件存储逻辑。从而实现需求
2.upload_to版
    参考链接:https://docs.djangoproject.com/en/3.1/ref/models/fields/#django.db.models.FileField.upload_to
    官方原文:
    upload_to may also be a callable, such as a function. This will be called to obtain the upload path, including the filename. 
    upload_to也可以是可调用的,例如函数。这将被调用以获得上载路径,包括文件名。
3.自定义存储系统:
    暂时没那个必要:只是单纯的修改个文件名而已
4.forms版(20210206补充):
    使用clean方法对文件格式验证并修改文件名

三 具体实现

1.views版:

涉及views.py 、utils.py(自定义)

  • views.py
class UploadIdentImageView(APIView):
    def get(self, request, *args, **kwargs):
        return Response({"msg":"ident", "status_code": True})

    def post(self, request, *args, **kwargs):
        # from somewhere import handle_uploaded_file
        file_name = request.FILES.get("file", None)
        if not file_name:
            return Response({"msg": "ident", "status_code": False})

        from test_app.utils import custom_file_name
        file_name.name = custom_file_name(file_name)

        models_object = models.IdCardImage(image_path=file_name)
        models_object.save()
        print(models_object)
        return Response({"msg":"ident", "status_code": True})
  • test_app\utils.py
def random_str():
    import random
    import time

    num_set = [chr(i) for i in range(48, 58)]
    char_set = [chr(i) for i in range(97, 123)]
    total_set = num_set + char_set
    bits = 14
    value_set = "".join(random.sample(total_set, bits))
    return value_set + str(int(time.time()))

def custom_file_name(file_name):
    file_type = str(file_name).split(".")[-1]
    new_file_name = random_str().upper()

    return ".".join([new_file_name,file_type])

2.upload_to版:

涉及models.py。test_app\utils.py沿用上面内容

  • models.py
def user_directory_path(upload_to):
    def wrapper(instance, filename):
        import datetime
        import os
        from mini_programe.utils import custom_file_name

        filename = custom_file_name(filename)

        dirname = datetime.datetime.now().strftime(upload_to)
        new_upload_to = os.path.join(dirname,filename)
        return new_upload_to
    return wrapper


class IdCardImage(models.Model):
    f_id = models.ForeignKey(Users, on_delete=models.SET_NULL,null=True,verbose_name="用户",related_name="user_id_card_img")
    # image_path = models.FileField(upload_to='media/id_card_img/%Y/%m/')
    image_path = models.FileField(upload_to=user_directory_path('media/id_card_img/%Y/%m/'))

4.forms版

  • forms.py
class IdCardImageForm(forms.ModelForm):
    class Meta:
        model = models.IdCardImage
        fields = "__all__"

    def clean_image_path(self):   #clean_字段名
        file = self.cleaned_data["image_path"]
        ext = file.name.split(".")[-1].lower()

        if ext != "png":
            raise forms.ValidationError("仅允许上传png文件")
        new_file_name = random.randint(111111111,9999999999)
        file.name = "%d.png"%new_file_name

        return file
  • models.py
class IdCardImage(models.Model):
    f_id = models.ForeignKey(Users, on_delete=models.SET_NULL,null=True,verbose_name="用户",related_name="user_id_card_img")
    image_path = models.FileField(upload_to='media/id_card_img/%Y/%m/')
  • views.py
class UploadIdentImageView(TemplateView):
    def get(self,....)
        ....
    def post(self,request,*args,**kwargs):
        check_form = forms.IdCardImageForm(request.POST,request.FILES)
        if not check_form.is_valid():
            return JsonResponse({'message': check_form.errors, "code": 400})
        instance = check_form.save()
        return JsonResponse({'message': instance.name + ",保存成功","code":200})
  • upload.html
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

function upload_pdf() {
    const csrftoken = getCookie('csrftoken');

    var form = new FormData();
    form.append("f_id", $("#f_id").val());
    form.append("image_path", $("#image_path")[0].files[0]);
    $.ajax({
        headers: {'X-CSRFToken': csrftoken},
        url: "{% url 'add_data' %}",
        type: "POST",
        data: form,

        processData: false,
        contentType: false,
        cache: false,

        success: function (result) {
            if (result.code === 200) {
                alert_message(JSON.stringify(result.message));
                    } else {
                        console.log("success", result);
                        alert_message(JSON.stringify(result.message));
                    }
                },
                fail: function (result) {
                    console.log("fail",);
                    alert_message(result.info);
                },
                error: function (result) {
                    alert_message(result.status + ": " + result.statusText);
                }
            });

        }

四 最终效果

五 效果对比

个人更倾向于方案二或方案四,理由是:views.py文件中的处理函数的处理逻辑应该看起来简单一些。例如采用方案二整理后的view.py看起来像这样:

class UploadIdentImageView(APIView):
    def get(self, request, *args, **kwargs):
        return Response({"msg":"ident", "status_code": True})

    def post(self, request, *args, **kwargs):
        file_name = request.FILES.get("file", None)
        if not file_name:
            return Response({"msg": "ident", "status_code": False})

        models_object = models.IdCardImage(image_path=file_name)
        models_object.save()
        return Response({"msg":"ident", "status_code": True})

登录功能

  • 知识储备
"""
img标签的src属性
    1.图片路径
    2.url
    3.图片的二进制数据

我们的计算机上面致所有能够输出各式各样的字体样式
内部其实对应的是一个个.ttf结尾的文件

http://www.zhaozi.cn/ai/2019/fontlist.php?ph=1&classid=32&softsq=%E5%85%8D%E8%B4%B9%E5%95%86%E7%94%A8
"""
  • 验证码推导
# 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'^register/', views.register, name='register'),
    # 登录功能
    url(r'^login/', views.login, name='login'),
    # 验证码
    url(r'^get_code/', views.get_code, name='gc'),
]

# views.py
from django.shortcuts import render, HttpResponse
from app01.myforms import MyRegForm
from app01 import models
from django.http import JsonResponse


# Create your views here.
def register(request):
    form_obj = MyRegForm()
    if request.method == "POST":
        back_dic = {"code": 1000, "msg": ""}
        # 校验数据是否合法
        form_obj = MyRegForm(request.POST)
        if form_obj.is_valid():
            # print(form_obj.cleaned_data)     # {'username': 'alias', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'}
            clean_data = form_obj.cleaned_data  # 将校验通过的数据字典赋值给一个变量
            clean_data.pop("confirm_password")  # {'username': 'alias', 'password': '123', 'email': '123@qq.com'}
            # 用户头像
            file_obj = request.FILES.get("avatar")
            """针对用户头像一定要判断是否传值 不能直接添加到字典中"""
            if file_obj:
                clean_data['avatar'] = file_obj
            # 操作数据库保存数据
            models.UserInfo.objects.create_user(**clean_data)
            back_dic["url"] = "/login/"
        else:
            back_dic["code"] = 2000
            back_dic["msg"] = form_obj.errors
        return JsonResponse(back_dic)

    return render(request, "register.html", locals())


def login(request):
    return render(request, 'login.html')

"""
图片相关的模块
    # pip3 install pillow
"""
from PIL import Image,ImageDraw,ImageFont,ImageFilter
"""
Image:生成图片
ImageDraw:能够在图片上乱涂乱画
ImageFont:控制字体样式
ImageFilter:控制图片模糊度
"""
from io import BytesIO,StringIO
"""
内存管理器模块
BytesIO:临时的帮你存储数据 返回的时候数据就是二进制格式
StringIO:临时的帮你存储数据 返回的时候数据就是字符串格式
"""

import random

def get_random():
    return random.randint(0,255),random.randint(0,255),random.randint(0,255)

def get_code(request):
    # 推导步骤1:直接获取后端现成的图片二进制数据发送给前端
    # with open(r"static/img/111.jpg", "rb") as f:
    #     data = f.read()
    # return HttpResponse(data)
    #
    # 推导步骤2:利用pillow模块动态产生图片(文件存储繁琐IO操作效率低)
    # img_obj = Image.new('RGB', (430, 35), "red")   # 第二个参数图片尺寸(要和前端划定的尺寸一致),第三个参数还可以放三基色参数
    # img_obj = Image.new('RGB', (430, 35), get_random())
    # 先将图片对象保存起来
    # with open("xxx.png",'wb') as f:
    #     img_obj.save(f,"png")   # 文件句柄,图片格式
    # # 再将图片对象读取出来
    # with open('xxx.png','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 推导步骤3:借助于内存管理模块
    # img_obj = Image.new('RGB', (430, 35), get_random())
    # io_obj = BytesIO()  # 生成一个内存管理器对象,可以看成是文件句柄
    # img_obj.save(io_obj,'png')     # 要指定图片的格式
    # return HttpResponse(io_obj.getvalue())    # io_obj.getvalue()读取出文件,返回的是二进制格式

    # 最终步骤:绘制图片验证码
    img_obj = Image.new('RGB', (430, 35), get_random())
    img_draw = ImageDraw.Draw(img_obj)   # 产生一个画笔对象
    img_font = ImageFont.truetype("static/font/222.TTF",30)    # 字体样式,以及字体大小

    # 随机验证码  五位数的随机验证码 数字 小写字母 大写字母
    code = ''
    for i in range(5):
        random_upper = chr(random.randint(65,90))
        random_lower = chr(random.randint(97,122))
        random_int = str(random.randint(0,9))
        # 从上面三个里面随机选一个
        tmp = random.choice([random_upper,random_lower,random_int])
        # 将产生的随机字符串写入图片
        """
        为什么一个个写而不是生成好了之后再写
        因为一个个写能够控制每个字体的间隙 而生成好之后再写的话
        间隙就没法控制了
        """
        img_draw.text((i*45+100,-3),tmp,get_random(),img_font)
        # 拼接随机字符串
        code += tmp
    # print(code)
    # 随机验证码在登录的视图函数中需要用到 要比对,所以要找地方存起来且其他视图函数也能拿到(可以放在auth_session表中)
    request.session['code'] = code
    io_obj = BytesIO()
    # img_obj = img_obj.filter(ImageFilter.GaussianBlur)
    img_obj.save(io_obj,"png")

    return HttpResponse(io_obj.getvalue())
  • login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- Bootstrap3 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- Bootstrap3 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <!-- font-awesome.min.css图标库4.7版本 -->
    <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
    {% load static %}

</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h1 class="text-center">登录</h1>
            <div class="form-group">
                <label for="username">用户名</label>
                <input type="text" name="username" id="username" class="form-control">
            </div>
            <div class="form-group">
                <label for="password">密码</label>
                <input type="password" name="password" id="password" class="form-control">
            </div>
            <div class="form-group">
                <label for="">验证码</label>
                <div class="row">
                    <div class="col-md-6">
                        <input type="text" name="code" id="id_code" class="form-control">
                    </div>
                    <div class="col-md-6">
                        <!--这个页面只要一加载出来,就会朝这个/get_code/路径发get请求-->
                        <img src="/get_code/" alt="" width="430px" height="35px" id="id_img">
                    </div>
                </div>
            </div>
            <input type="button" class="btn btn-success btn-block" value="登陆">
        </div>
    </div>
</div>
<script>
    $("#id_img").click(function () {
        // 1 先获取标签之前的src
        let oldVal = $(this).attr('src');
        $(this).attr('src',oldVal += '?')
    })
</script>
</body>
</html>

登录注册完整代码

目录

  • settings.py
"""
Django settings for BBS14 project.

Generated by 'django-admin startproject' using Django 1.11.11.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '7vht@lf-*t@weir@mcdg^-r+lmyg4qboyy1i_xf^#mwieg!(&x'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'BBS14.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR, 'templates']
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'BBS14.wsgi.application'

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#     }
# }

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'bbs14',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'CHARSET': 'utf8'
    }
}

# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),
]

# 告诉django你要用UserInfo替代auth_user(******)
AUTH_USER_MODEL = 'app01.UserInfo'
  • 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'^register/', views.register, name='register'),
    # 登录功能
    url(r'^login/', views.login, name='login'),
    # 验证码
    url(r'^get_code/', views.get_code, name='gc'),
    # 首页
    url(r'^home/', views.home),
]
  • common.py验证码
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter


def get_chars_str():
    '''
    :return:验证码字符集合
    '''
    _letter_cases = "abcdefghjkmnpqrstuvwxy"  # 小写字母,去除可能干扰的i,l,o,z
    _upper_cases = _letter_cases.upper()  # 大写字母
    _numbers = ''.join(map(str, range(3, 10)))  # 数字
    init_chars = ''.join((_letter_cases, _upper_cases, _numbers))
    return init_chars


def create_validate_code(size=(430, 35),
                         chars=get_chars_str(),
                         img_type="GIF",
                         mode="RGB",
                         bg_color=(255, 255, 255),
                         fg_color=(0, 0, 255),
                         font_size=20,
                         font_type="static/font/222.TTF",
                         length=4,
                         draw_lines=True,
                         n_line=(1, 2),
                         draw_points=True,
                         point_chance=2):
    """
    @todo: 生成验证码图片
    @param size: 图片的大小,格式(宽,高),默认为(120, 30)
    @param chars: 允许的字符集合,格式字符串
    @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG
    @param mode: 图片模式,默认为RGB
    @param bg_color: 背景颜色,默认为白色
    @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF
    @param font_size: 验证码字体大小
    @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf
    @param length: 验证码字符个数
    @param draw_lines: 是否划干扰线
    @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效
    @param draw_points: 是否画干扰点
    @param point_chance: 干扰点出现的概率,大小范围[0, 100]
    @return: [0]: PIL Image实例
    @return: [1]: 验证码图片中的字符串
    """

    width, height = size  # 宽高
    # 创建图形
    img = Image.new(mode, size, bg_color)
    draw = ImageDraw.Draw(img)  # 创建画笔

    def get_chars():
        """生成给定长度的字符串,返回列表格式"""
        return random.sample(chars, length)

    def create_lines():
        """绘制干扰线"""
        line_num = random.randint(*n_line)  # 干扰线条数

        for i in range(line_num):
            # 起始点
            begin = (random.randint(0, size[0]), random.randint(0, size[1]))
            # 结束点
            end = (random.randint(0, size[0]), random.randint(0, size[1]))
            draw.line([begin, end], fill=(0, 0, 0))

    def create_points():
        """绘制干扰点"""
        chance = min(100, max(0, int(point_chance)))  # 大小限制在[0, 100]

        for w in range(width):
            for h in range(height):
                tmp = random.randint(0, 100)
                if tmp > 100 - chance:
                    draw.point((w, h), fill=(0, 0, 0))

    def create_strs():
        """绘制验证码字符"""
        c_chars = get_chars()
        strs = ' %s ' % ' '.join(c_chars)  # 每个字符前后以空格隔开

        font = ImageFont.truetype(font_type, font_size)
        font_width, font_height = font.getsize(strs)

        draw.text(((width - font_width) / 3, -1),
                  strs, font=font, fill=fg_color)

        return ''.join(c_chars)

    if draw_lines:
        create_lines()
    if draw_points:
        create_points()
    strs = create_strs()

    # 图形扭曲参数
    params = [1 - float(random.randint(1, 2)) / 100,
              0,
              0,
              0,
              1 - float(random.randint(1, 10)) / 100,
              float(random.randint(1, 2)) / 500,
              0.001,
              float(random.randint(1, 2)) / 500
              ]
    img = img.transform(size, Image.PERSPECTIVE, params)  # 创建扭曲

    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)  # 滤镜,边界加强(阈值更大)

    return img, strs
  • models.py
from django.db import models
from django.contrib.auth.models import AbstractUser


# Create your models here.
class UserInfo(AbstractUser):
    phone = models.BigIntegerField(null=True, verbose_name="手机号")
    # 头像
    avatar = models.FileField(upload_to="avatar/", default='avatar/default.png')
    """
    给avatar字段传文件对象 该文件会自动存储到avatar文件夹 然后avatar字段只保存文件路径avatar/default.png
    """
    create_time = models.DateTimeField(auto_now_add=True)

    blog = models.OneToOneField(to='Blog', null=True)


class Blog(models.Model):
    site_name = models.CharField(max_length=32, verbose_name='站点名称')
    site_title = models.CharField(max_length=32, verbose_name='站点标题')
    # 简单模拟 认识样式内部原理的操作
    site_theme = models.CharField(max_length=64, verbose_name='站点样式')  # 存css/js的文件路径


class Category(models.Model):
    name = models.CharField(max_length=32, verbose_name='文章分类')
    blog = models.ForeignKey(to='Blog', null=True)


class Tag(models.Model):
    name = models.CharField(max_length=32, verbose_name='文章标签')
    blog = models.ForeignKey(to='Blog', null=True)
    tags = models.ManyToManyField(to='Article',
                                  through='Article2Tag',
                                  through_fields=('tag', 'article')
                                  )


class Article(models.Model):
    title = models.CharField(max_length=64, verbose_name='文章标题')
    desc = models.CharField(max_length=255, verbose_name='文章简介')
    # 文章内容有很多 一般情况下都是使用TextField
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateTimeField(auto_now_add=True)

    # 数据库字段设计优化
    up_num = models.BigIntegerField(default=0, verbose_name='点赞数')
    down_num = models.BigIntegerField(default=0, verbose_name='点踩数')
    comment_num = models.BigIntegerField(default=0, verbose_name='评论数')

    # 外键字段
    blog = models.ForeignKey(to='Blog', null=True)
    category = models.ForeignKey(to='Category', null=True)
    tags = models.ManyToManyField(to='Tag',
                                  through='Article2Tag',
                                  through_fields=('article', 'tag')
                                  )


class Article2Tag(models.Model):
    article = models.ForeignKey(to='Article')
    tag = models.ForeignKey(to='Tag')


class UpAndDown(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    is_up = models.BooleanField()  # 传布尔值存0/1


class Comment(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    content = models.CharField(max_length=255, verbose_name="评论内容")
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
    # 自关联
    parent = models.ForeignKey(to='self', null=True)  # 一定要加null=True,因为有些评论就是根评论
  • views.py
from django.shortcuts import render, HttpResponse
from app01.myforms import MyRegForm
from app01 import models
from django.http import JsonResponse
from app01.lib.common import create_validate_code
from io import BytesIO
from django.contrib import auth


# Create your views here.

def register(request):
    form_obj = MyRegForm()
    if request.method == "POST":
        back_dic = {"code": 1000, "msg": ""}
        # 校验数据是否合法
        form_obj = MyRegForm(request.POST)
        if form_obj.is_valid():
            clean_data = form_obj.cleaned_data  # 将校验通过的数据字典赋值给一个变量
            clean_data.pop("confirm_password")  # {'username': 'alias', 'password': '123', 'email': '123@qq.com'}
            # 用户头像
            file_obj = request.FILES.get("avatar")
            """针对用户头像一定要判断是否传值 不能直接添加到字典中"""
            if file_obj:
                clean_data['avatar'] = file_obj
            # 操作数据库保存数据
            models.UserInfo.objects.create_user(**clean_data)
            back_dic["url"] = "/login/"
        else:
            back_dic["code"] = 2000
            back_dic["msg"] = form_obj.errors
            print(form_obj.errors)
        return JsonResponse(back_dic)

    return render(request, "register.html", locals())


def login(request):
    if request.is_ajax():
        if request.method == "POST":
            back_dic = {"code": 1000, "msg": ""}
            username = request.POST.get("username")
            password = request.POST.get("password")
            code = request.POST.get("code")
            # 校验验证码是否正确    自己决定是否忽略验证码大小写    统一转大写或小写即可
            if request.session.get("code").lower() == code.lower():
                # 校验用户名和密码
                user_obj = auth.authenticate(request, username=username, password=password)
                if user_obj:
                    # 保存用户状态
                    auth.login(request, user_obj)
                    back_dic["url"] = '/home/'
                    print(request.user, type(request.user))
                else:
                    back_dic["code"] = 2000
                    back_dic["msg"] = "用户名或密码错误"
            else:
                back_dic["code"] = 3000
                back_dic["msg"] = "验证码错误"

            return JsonResponse(back_dic)
    return render(request, 'login.html')


def get_code(request):
    f = BytesIO()  # 创建一个内存地址存放图片
    img, code = create_validate_code()  # 调用方法生成图片对象和验证码
    request.session['code'] = code  # 设置session
    print(code)
    img.save(f, 'PNG')  # 保存图片
    return HttpResponse(f.getvalue())  # 返回图片


def home(request):
    return HttpResponse('home')
  • register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- Bootstrap3 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- Bootstrap3 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <!-- font-awesome.min.css图标库4.7版本 -->
    <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h1 class="text-center">注册</h1>
            <form id="myform">    <!--我们不用form表单提交数据 只是单纯用一下form标签而已-->
                {% for form in form_obj %}
                    <div class="from-group">
                        <!--form.auto_id获取forms渲染的input框的id-->
                        <label for="{{ form.auto_id }}">{{ form.label }}</label>
                        {{ form }}
                        <span style="color: red" class="pull-right"></span>
                    </div>
                {% endfor %}
                <div class="form-group" style="margin-top: 10px">
                    <label for="myfile">头像
                        {% load static %}
                        <img src="{% static "img/default.png" %}" alt="" id="myimg" width="100px"
                             style="margin-left: 10px">
                    </label>
                    <input type="file" id="myfile" name="avatar" style="display: none">
                </div>

                <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit">
            </form>
        </div>
    </div>
</div>
<script src="{% static "js/mysteup.js" %}/"></script>
<script>
    // 选择头像实时显示
    $("#myfile").change(function () {
        // 文件阅读器对象
        // 1、先生成一个文件阅读器对象
        let myFileReaderObj = new FileReader();
        // 2、获取用户上传的头像文件
        let fileObj = $(this)[0].files[0];
        // 3、将文件对象交给阅读器对象读取
        myFileReaderObj.readAsDataURL(fileObj);     // 这步操作是异步
        // 4、利用文件阅读器将文件展示到前端页面   修改img的src属性
        // 等待myFileReaderObj文件阅读器加载完毕之后再执行
        myFileReaderObj.onload = function () {
            $("#myimg").attr("src", myFileReaderObj.result)
        }
    });
    $("#id_commit").click(function () {
        // 发送ajax请求  我们发送的数据中即包含普通键值也包含文件
        let formDataObj = new FormData();
        // 添加普通键值对
        // console.log($("#myform").serializeArray())  // [{},{},{},...] 只包含普通键值对
        $.each($("#myform").serializeArray(), function (index, obj) {
            // console.log(index,obj)   // obj = {}
            formDataObj.append(obj.name, obj.value)
        });
        // 添加文件
        formDataObj.append('avatar', $("#myfile")[0].files[0]);
        // 发送ajax请求
        $.ajax({
            url: '',
            type: 'post',
            dataType: 'json',
            data: formDataObj,

            contentType: false,
            processData: false,
            success: function (args) {
                if (args.code == 1000) {
                    // 跳转到登录页面
                    window.location.href = args.url
                } else {
                    // 如何将对应的错误信息提示展示到对应的input框下面,因为用的ajax所以无法像之前在span标签里面写form.errors.0
                    // forms组件渲染的标签的id值都是 id_字段名
                    $.each(args.msg, function (index, obj) {
                        {#console.log(index,obj)  //  username        ["用户名不能为空"]#}
                        let targetId = '#id_' + index;      // input标签
                        {#$(targetId).next().text(obj[0]).parent().addClass('has-error')#}
                        $(targetId).next().text(obj[0]).prev().css("border", "solid red")
                    })
                }
            }
        })
    })
    // 给所有的input框绑定获取焦点事件
    $('input').focus(function () {
        // 将input下面的span标签和input外面的div标签修改内容及属性
        $(this).next().text('').prev().removeAttr("style", "");
    })
</script>
</body>
</html>
  • login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- Bootstrap3 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- Bootstrap3 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <!-- font-awesome.min.css图标库4.7版本 -->
    <link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
    {% load static %}

</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h1 class="text-center">登录</h1>
            <div class="form-group">
                <label for="id_username">用户名</label>
                <input type="text" name="username" id="id_username" class="form-control">
            </div>
            <div class="form-group">
                <label for="id_password">密码</label>
                <input type="password" name="password" id="id_password" class="form-control">
            </div>
            <div class="form-group">
                <label for="">验证码</label>
                <div class="row">
                    <div class="col-md-6">
                        <input type="text" name="code" id="id_code" class="form-control">
                    </div>
                    <div class="col-md-6">
                        <!--这个页面只要一加载出来,就会朝这个/get_code/路径发get请求-->
                        <img src="/get_code/" alt="" width="430px" height="35px" id="id_img">
                    </div>
                </div>
            </div>
            <input type="button" class="btn btn-success" value="登陆" id="id_commit">
            <span style="color: red;font: bold 16px 微软雅黑" id="error"></span>
        </div>
    </div>
</div>
<script>
    $("#id_img").click(function () {
        // 1 先获取标签之前的src
        let oldVal = $(this).attr('src');
        $(this).attr('src', oldVal += '?')
    })
    // 点击登录按钮发送ajax请求
    $("#id_commit").click(function () {
        $.ajax({
            url: '',
            type: 'post',
            data: {
                'username': $("#id_username").val(),
                'password': $("#id_password").val(),
                'code': $("#id_code").val(),
                'csrfmiddlewaretoken': '{{ csrf_token }}',
            },
            dataType: 'json',
            success: function (args) {
                if (args.code == 1000) {
                    // 跳转到首页
                    window.location.href = args.url
                } else {
                    //渲染错误信息
                    $("#error").text(args.msg)
                }
            }
        })
    })
</script>
</body>
</html>

首页导航

  • urls.py
    # 首页
    url(r'^home/', views.home, name='home'),
  • views.py
def home(request):
    # 查询本网站所有的文章数据展示前端页面,(可以使用分页器)
    article_queryset = models.Article.objects.all()
    return render(request, 'home.html', locals())
  • home.html
<nav class="navbar navbar-inverse">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/home/">BBS系统</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
                <li><a href="#">文章</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">更多 <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">Action</a></li>
                        <li><a href="#">Another action</a></li>
                        <li><a href="#">Something else here</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">Separated link</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">One more separated link</a></li>
                    </ul>
                </li>
            </ul>
            <form class="navbar-form navbar-left">
                <div class="form-group">
                    <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
            </form>
            <ul class="nav navbar-nav navbar-right">
                {% if request.user.is_authenticated %}
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">更多操作 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
                            <li><a href="#">修改头像</a></li>
                            <li><a href="#">后台管理</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="{% url 'logout' %}">退出登录</a></li>
                        </ul>
                        <!-- Large modal -->
                        <div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog"
                             aria-labelledby="myLargeModalLabel">
                            <div class="modal-dialog modal-lg" role="document">
                                <div class="modal-content">
                                    <br>
                                    <br>
                                    <h1 class="text-center" style="color: green">修改密码</h1>
                                    <div class="row">
                                        <div class="col-md-8 col-md-offset-2">
                                            <div class="form-group">
                                                <label for="">用户名</label>
                                                <input type="text" disabled value="{{ request.user.username }}"
                                                       class="form-control">
                                            </div>
                                            <div class="form-group">
                                                <label for="">原密码</label>
                                                <input type="password" id="id_old_password" class="form-control">
                                            </div>
                                            <div class="form-group">
                                                <label for="">新密码</label>
                                                <input type="password" id="id_new_password" class="form-control">
                                            </div>
                                            <div class="form-group">
                                                <label for="">确认密码</label>
                                                <input type="password" id="id_confirm_password" class="form-control">
                                            </div>
                                            <div class="modal-footer">
                                                <button type="button" class="btn btn-default" data-dismiss="modal">取消
                                                </button>
                                                <button class="btn btn-primary" id="id_edit">修改</button>
                                                <span style="color: red" id="password_error"></span>
                                            </div>
                                            <br>
                                            <br>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </li>
                {% else %}
                    <li><a href="{% url 'reg' %}">注册</a></li>
                    <li><a href="{% url 'login' %}">登录</a></li>
                {% endif %}
        </ul>
    </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

导航条修改密码及突出登录

导航条修改密码(模态框)

  • urls.py
    # 修改密码
    url(r'^set_password/', views.set_password, name='set_pwd'),
  • views.py
from django.contrib.auth.decorators import login_required

@login_required
def set_password(request):
    if request.is_ajax():
        back_dic = {'code': 1000, 'msg': ''}
        if request.method == 'POST':
            old_password = request.POST.get('old_password')
            new_password = request.POST.get('new_password')
            confirm_password = request.POST.get('confirm_password')
            is_right = request.user.check_password(old_password)
            if is_right:
                if new_password == confirm_password:
                    request.user.set_password(new_password)
                    request.user.save()
                    back_dic['msg'] = '修改成功'
                else:
                    back_dic['code'] = 1001
                    back_dic['msg'] = '两次密码不一致'
            else:
                back_dic['code'] = 1002
                back_dic['msg'] = '原密码错误'
        return JsonResponse(back_dic)
  • home.html
<nav class="navbar navbar-inverse">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/home/">BBS系统</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
                <li><a href="#">文章</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">更多 <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">Action</a></li>
                        <li><a href="#">Another action</a></li>
                        <li><a href="#">Something else here</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">Separated link</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">One more separated link</a></li>
                    </ul>
                </li>
            </ul>
            <form class="navbar-form navbar-left">
                <div class="form-group">
                    <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
            </form>
            <ul class="nav navbar-nav navbar-right">
                {% if request.user.is_authenticated %}
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">更多操作 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
                            <li><a href="#">修改头像</a></li>
                            <li><a href="#">后台管理</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="{% url 'logout' %}">退出登录</a></li>
                        </ul>
                        <!-- Large modal -->
                        <div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog"
                             aria-labelledby="myLargeModalLabel">
                            <div class="modal-dialog modal-lg" role="document">
                                <div class="modal-content">
                                    <br>
                                    <br>
                                    <h1 class="text-center" style="color: green">修改密码</h1>
                                    <div class="row">
                                        <div class="col-md-8 col-md-offset-2">
                                            <div class="form-group">
                                                <label for="">用户名</label>
                                                <input type="text" disabled value="{{ request.user.username }}"
                                                       class="form-control">
                                            </div>
                                            <div class="form-group">
                                                <label for="">原密码</label>
                                                <input type="password" id="id_old_password" class="form-control">
                                            </div>
                                            <div class="form-group">
                                                <label for="">新密码</label>
                                                <input type="password" id="id_new_password" class="form-control">
                                            </div>
                                            <div class="form-group">
                                                <label for="">确认密码</label>
                                                <input type="password" id="id_confirm_password" class="form-control">
                                            </div>
                                            <div class="modal-footer">
                                                <button type="button" class="btn btn-default" data-dismiss="modal">取消
                                                </button>
                                                <button class="btn btn-primary" id="id_edit">修改</button>
                                                <span style="color: red" id="password_error"></span>
                                            </div>
                                            <br>
                                            <br>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </li>
                {% else %}
                    <li><a href="{% url 'reg' %}">注册</a></li>
                    <li><a href="{% url 'login' %}">登录</a></li>
                {% endif %}
        </ul>
    </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

退出登录

  • urls.py
    # 退出登录
    url(r'^logout/', views.logout, name='logout'),
  • views.py
from django.contrib.auth.decorators import login_required

@login_required
def logout(request):
    auth.logout(request)
    return redirect('home')

admin后台管理

"""
django给你提供了一个可视化的界面用来让你方便的对你的模型表
进行数据的增删改查操作

如果你先想要使用amdin后台管理操作模型表
你需要先注册你的模型表告诉admin你需要操作哪些表

去你的应用下的admin.py中注册你的模型表
  • admin.py 注册需要管理操作的模型表
from django.contrib import admin
from app01 import models

# Register your models here.
admin.site.register(models.UserInfo)    #注册需要管理操作的模型表
admin.site.register(models.Blog)
admin.site.register(models.Category)
admin.site.register(models.Tag)
admin.site.register(models.Article)
admin.site.register(models.Article2Tag)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)

表名默认会加s后缀,可以在models.py中更改,如下:

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


class UserInfo(AbstractUser):
    phone = models.BigIntegerField(null=True,verbose_name="手机号")
    # 头像
    avatar = models.FileField(upload_to="avatar/",default='avatar/default.png')
    """
    给avatar字段传文件对象 该文件会自动存储到avatar文件夹 然后avatar字段只保存文件路径avatar/default.png
    """
    create_time = models.DateTimeField(auto_now_add=True)

    blog = models.OneToOneField(to='Blog',null=True)
    class Meta:
        verbose_name_plural = "用户表"   # 修改admin后台管理默认的表名
        # verbose_name = "用户表"     # 使用verbose_name更改还是会加s ==> 用户表s
  • 注意1:
# admin会给每一个注册了的模型表自动生成增删改查四条url
http://127.0.0.1:8000/admin/app01/userinfo/ 查
http://127.0.0.1:8000/admin/app01/userinfo/add/ 增
http://127.0.0.1:8000/admin/app01/userinfo/1/change/ 改
http://127.0.0.1:8000/admin/app01/userinfo/1/delete/ 删


http://127.0.0.1:8000/admin/app01/blog/ 查
http://127.0.0.1:8000/admin/app01/blog/add/ 增
http://127.0.0.1:8000/admin/app01/blog/1/change/ 改
http://127.0.0.1:8000/admin/app01/blog/1/delete/ 删

"""
关键点就在于urls.py中的第一条自带的url

前期我们需要自己手动苦逼的录入数据,自己克服一下
"""
  • 注意2:虽然我们在models.py中的class模型类中设置了字段可以为空,但是admin后台管理不允许为空,如果要admin后台管理也可以为空需要给字段添加blank=True
from django.db import models
from django.contrib import auth
from django.contrib.auth.models import AbstractUser

# Create your models here.

class UserInfo(AbstractUser):
    phone = models.BigIntegerField(null=True,blank=True,verbose_name="手机号")
    """
    null=True   数据库该字段可以为空
    blank=True  admin后台管理该字段可以为空
    """

    # 头像
    avatar = models.FileField(upload_to="avatar/",default='avatar/default.png')
    """
    给avatar字段传文件对象 该文件会自动存储到avatar文件夹 然后avatar字段只保存文件路径avatar/default.png
    """
    create_time = models.DateTimeField(auto_now_add=True)

    blog = models.OneToOneField(to='Blog',null=True)
    class Meta:
        verbose_name_plural = "用户表"
        # verbose_name = "用户表"     # 使用verbose_name更改还是会加s ==> 用户表s

    def __str__(self):
        return self.username

用户头像展示

"""
1 网址所使用的静态文件默认放在static文件夹下
2 用户上传的静态文件也应该单独放在某个文件夹下

media配置
    该配置可以让用户上传的所有文件都固定存放在某一个指定的文件夹下
    # 配置用户上传的文件存储位置
    MEDIA_ROOT = os.path.join(BASE_DIR,'media')  # 文件名 随你 自己
    会自动创建多级目录

如何开设后端指定文件夹资源
    首先你需要自己去urls.py书写固定的代码
    from django.views.static import serve
    from BBS14 import settings

    # 暴露后端指定文件夹资源
  url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})

"""

步骤:

  • settings.py中添加一行
MEDIA_ROOT = os.path.join(BASE_DIR, 'files')    #上传路径,文件名随意
MEDIA_URL = '/files/'  # 访问路径
  • urls.py中添加
from django.conf.urls import url
from django.contrib import admin
from app01 import views
from django.views.static import serve    # 暴露接口使用
#from BBS import settings     # 暴露接口
from django.conf import settings  # 以后都用这个导配置文件,暴露接口


urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 注册
    url(r'^register/', views.register,name='register'),
    # 登录
    url(r'^login/', views.login, name='login'),
    # 退出登陆
    url(r'^logout/', views.logout, name='logout'),
    # 图片验证码
    url(r'^get_code/', views.get_code, name='get_code'),
    # 首页
    url(r'^home/', views.home, name='home'),
    # 修改密码
    url(r'^set_password/', views.set_password, name='set_password'),

    # 暴露后端指定文件夹资源,这样就会把files暴露给外界
    url(r'^files/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
]
  • home.html

展示头像:

<img class="media-object" src="/files/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80px">

完整代码:

  • home.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    {% load static %}
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}">
</head>
<body>
<nav class="navbar navbar-inverse">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Brand</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
                <li><a href="#">Link</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">Dropdown <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">Action</a></li>
                        <li><a href="#">Another action</a></li>
                        <li><a href="#">Something else here</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">Separated link</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">One more separated link</a></li>
                    </ul>
                </li>
            </ul>
            <form class="navbar-form navbar-left">
                <div class="form-group">
                    <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
            </form>
            <ul class="nav navbar-nav navbar-right">
                {% if request.user.is_authenticated %}
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">更多操作 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
                            <li><a href="#">修改头像</a></li>
                            <li><a href="#">后台管理</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="{% url 'logout' %}">退出登录</a></li>
                        </ul>
                        <!-- Large modal -->
                        <div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog"
                             aria-labelledby="myLargeModalLabel">
                            <div class="modal-dialog modal-lg" role="document">
                                <div class="modal-content">
                                    <h1 class="text-center">修改密码</h1>
                                    <div class="row">
                                        <div class="col-md-8 col-md-offset-2">
                                            <div class="formgroup">
                                                <label for="">用户名</label>
                                                <input type="text" value="{{ request.user.username }}"
                                                       class="form-control" disabled>
                                            </div>
                                            <div class="formgroup">
                                                <label for="id_old_password">旧密码</label>
                                                <input type="password" id="id_old_password" class="form-control">
                                            </div>
                                            <div class="formgroup">
                                                <label for="id_new_password">新密码</label>
                                                <input type="password" id="id_new_password" class="form-control">
                                            </div>
                                            <div class="formgroup">
                                                <label for="">确认密码</label>
                                                <input type="password" id="id_confirm_password" class="form-control">
                                            </div>
                                            <div class="modal-footer">
                                                <button type="button" class="btn btn-default" data-dismiss="modal"
                                                        id="id_edit_close">取消
                                                </button>
                                                <button class="btn btn-primary" id="id_edit">修改</button>
                                                <span style="color: red;font: bold 16px 微软雅黑"
                                                      id="password_error"></span>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </li>
                {% else %}
                    <li><a href="{% url 'register' %}">注册</a></li>
                    <li><a href="{% url 'login' %}">登录</a></li>
                {% endif %}
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-2">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">重金求子</h3>
                </div>
                <div class="panel-body">
                    事成之后,上海别墅一套
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">千万大奖</h3>
                </div>
                <div class="panel-body">
                    事成之后,上海别墅一套
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">线上赌场</h3>
                </div>
                <div class="panel-body">
                    性感荷官
                </div>
            </div>
        </div>
        <div class="col-md-8">
            <ul class="media-list">
                {% for article_obj in article_queryset %}
                    <li class="media">
                        <h4 class="media-heading"><a href="#">{{ article_obj.title }}</a></h4>
                        <div class="media-left">
                            <a href="#">
                                <img class="media-object" src="/files/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80px">
                            </a>
                        </div>
                        <div class="media-body">
                            {{ article_obj.desc }}
                        </div>
                        <br>
                        <div>
                            <span><a href="#">{{ article_obj.blog.userinfo.username }}  </a></span>
                            <span>发布于  </span>
                            <span>{{ article_obj.create_time|date:'Y-m-d' }}  </span>
                            <span><i class="fa fa-commenting" aria-hidden="true"></i>评论({{ article_obj.comment_num }})  </span>
                            <span><i class="fa fa-thumbs-up" aria-hidden="true"></i>点赞{{ article_obj.up_num }}</span>
                        </div>
                    </li>
                    <hr>
                {% endfor %}
            </ul>
        </div>
        <div class="col-md-2">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">重金求子</h3>
                </div>
                <div class="panel-body">
                    事成之后,上海别墅一套
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">千万大奖</h3>
                </div>
                <div class="panel-body">
                    事成之后,上海别墅一套
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">线上赌场</h3>
                </div>
                <div class="panel-body">
                    性感荷官
                </div>
            </div>
        </div>
    </div>
</div>
<script src="{% static "js/mysteup.js" %}"></script>
<script>
    $('#id_edit').click(function () {
        $.ajax({
            url: '/set_password/',
            type: 'post',
            data: {
                'old_password': $('#id_old_password').val(),
                'new_password': $('#id_new_password').val(),
                'confirm_password': $('#id_confirm_password').val(),
                'csrfmiddlewaretoken': '{{ csrf_token }}'
            },
            success: function (args) {
                if (args.code == 1000) {
                    window.location.reload()
                } else {
                    $("#password_error").text(args.msg)
                }
            }
        })
    })
</script>
</body>
</html>
  • views.py
from django.shortcuts import render, HttpResponse, redirect
from app01.myforms import MyRegForm
from app01 import models
from django.http import JsonResponse
from app01.lib.common import create_validate_code
from io import BytesIO
from django.contrib import auth
from django.contrib.auth.decorators import login_required


# Create your views here.

def register(request):
    form_obj = MyRegForm()
    if request.method == "POST":
        back_dic = {"code": 1000, "msg": ""}
        # 校验数据是否合法
        form_obj = MyRegForm(request.POST)
        if form_obj.is_valid():
            clean_data = form_obj.cleaned_data  # 将校验通过的数据字典赋值给一个变量
            clean_data.pop("confirm_password")  # {'username': 'alias', 'password': '123', 'email': '123@qq.com'}
            # 用户头像
            file_obj = request.FILES.get("avatar")
            """针对用户头像一定要判断是否传值 不能直接添加到字典中"""
            if file_obj:
                clean_data['avatar'] = file_obj
            # 操作数据库保存数据
            models.UserInfo.objects.create_user(**clean_data)
            back_dic["url"] = "/login/"
        else:
            back_dic["code"] = 2000
            back_dic["msg"] = form_obj.errors
            print(form_obj.errors)
        return JsonResponse(back_dic)

    return render(request, "register.html", locals())


def login(request):
    if request.is_ajax():
        if request.method == "POST":
            back_dic = {"code": 1000, "msg": ""}
            username = request.POST.get("username")
            password = request.POST.get("password")
            code = request.POST.get("code")
            # 校验验证码是否正确    自己决定是否忽略验证码大小写    统一转大写或小写即可
            if request.session.get("code").lower() == code.lower():
                # 校验用户名和密码
                user_obj = auth.authenticate(request, username=username, password=password)
                if user_obj:
                    # 保存用户状态
                    auth.login(request, user_obj)
                    back_dic["url"] = '/home/'
                else:
                    back_dic["code"] = 2000
                    back_dic["msg"] = "用户名或密码错误"
            else:
                back_dic["code"] = 3000
                back_dic["msg"] = "验证码错误"

            return JsonResponse(back_dic)
    return render(request, 'login.html')


def get_code(request):
    f = BytesIO()  # 创建一个内存地址存放图片
    img, code = create_validate_code()  # 调用方法生成图片对象和验证码
    request.session['code'] = code  # 设置session
    print(code)
    img.save(f, 'PNG')  # 保存图片
    return HttpResponse(f.getvalue())  # 返回图片

@login_required
def logout(request):
    auth.logout(request)
    return redirect("/home/")


def home(request):
    # 查询本网站所有的文章数据展示到前端
    article_queryset = models.Article.objects.all()

    return render(request, 'home.html', locals())


@login_required
def set_password(request):
    if request.is_ajax():
        if request.method == "POST":
            back_dic = {"code": 1000, "msg": ""}
            old_password = request.POST.get("old_password")
            new_password = request.POST.get("new_password")
            confirm_password = request.POST.get("confirm_password")
            is_right = request.user.check_password(old_password)
            if is_right:
                if new_password == confirm_password:
                    request.user.set_password(new_password)
                    request.user.save()
                    back_dic["msg"] = "修改成功"
                else:
                    back_dic["code"] = 2000
                    back_dic["msg"] = "两次密码不一致"
            else:
                back_dic["code"] = 3000
                back_dic["msg"] = "原密码错误"
            return JsonResponse(back_dic)
  • models.py
- from django.db import models
from django.contrib.auth.models import AbstractUser


# Create your models here.
class UserInfo(AbstractUser):
    phone = models.BigIntegerField(null=True, blank=True, verbose_name="手机号")
    # 头像
    avatar = models.FileField(upload_to="avatar/", default='avatar/default.png')
    """
    给avatar字段传文件对象 该文件会自动存储到avatar文件夹 然后avatar字段只保存文件路径avatar/default.png
    """
    create_time = models.DateTimeField(auto_now_add=True)

    blog = models.OneToOneField(to='Blog', null=True)

    class Meta:
        verbose_name_plural = "用户表"  # 修改admin后台管理默认的表名

    def __str__(self):
        return self.username


class Blog(models.Model):
    site_name = models.CharField(max_length=32, verbose_name='站点名称')
    site_title = models.CharField(max_length=32, verbose_name='站点标题')
    # 简单模拟 认识样式内部原理的操作
    site_theme = models.CharField(max_length=64, verbose_name='站点样式')  # 存css/js的文件路径

    class Meta:
        verbose_name_plural = "个人站点"

    def __str__(self):
        return self.site_name


class Category(models.Model):
    name = models.CharField(max_length=32, verbose_name='文章分类')
    blog = models.ForeignKey(to='Blog', null=True)

    class Meta:
        verbose_name_plural = "文章分类"

    def __str__(self):
        return self.name


class Tag(models.Model):
    name = models.CharField(max_length=32, verbose_name='文章标签')
    blog = models.ForeignKey(to='Blog', null=True)
    tags = models.ManyToManyField(to='Article',
                                  through='Article2Tag',
                                  through_fields=('tag', 'article')
                                  )

    class Meta:
        verbose_name_plural = "文章标签"

    def __str__(self):
        return self.name


class Article(models.Model):
    title = models.CharField(max_length=64, verbose_name='文章标题')
    desc = models.CharField(max_length=255, verbose_name='文章简介')
    # 文章内容有很多 一般情况下都是使用TextField
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateTimeField(auto_now_add=True)

    # 数据库字段设计优化
    up_num = models.BigIntegerField(default=0, verbose_name='点赞数')
    down_num = models.BigIntegerField(default=0, verbose_name='点踩数')
    comment_num = models.BigIntegerField(default=0, verbose_name='评论数')

    # 外键字段
    blog = models.ForeignKey(to='Blog', null=True)
    category = models.ForeignKey(to='Category', null=True)
    tags = models.ManyToManyField(to='Tag',
                                  through='Article2Tag',
                                  through_fields=('article', 'tag')
                                  )

    class Meta:
        verbose_name_plural = "文章"

    def __str__(self):
        return self.title


class Article2Tag(models.Model):
    article = models.ForeignKey(to='Article')
    tag = models.ForeignKey(to='Tag')


class UpAndDown(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    is_up = models.BooleanField()  # 传布尔值存0/1


class Comment(models.Model):
    user = models.ForeignKey(to='UserInfo')
    article = models.ForeignKey(to='Article')
    content = models.CharField(max_length=255, verbose_name="评论内容")
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
    # 自关联
    parent = models.ForeignKey(to='self', null=True)  # 一定要加null=True,因为有些评论就是根评论

注:此时再注册用户时,会自动新建files文件夹并新建头像的avatar目录

访问资源例如

http://127.0.0.1:8000/files/avatar/default.png

图片防盗链

# 如何避免别的网站直接通过本网站的url访问本网站资源

# 简单的防盗
    我可以做到请求来的时候先看看当前请求是从哪个网站过来的
  如果是本网站那么正常访问
  如果是其他网站直接拒绝
      请求头里面有一个专门记录请求来自于哪个网址的参数
        Referer: http://127.0.0.1:8000/xxx/

# 如何绕过防盗链技术
  1.要么修改请求头referer
  2.直接写爬虫把对方网址的所有资源直接下载到我们自己的服务器上

个人文章展示及侧边栏inclusion_tag制作

  • urls.py
    # 个人站点页面搭建
    url(r'^(?P<username>\w+)/$', views.site, name='site'),
    # 侧边栏筛选功能
    # url(r'^(?P<username>\w+)/category/(\d+)/',views.site),
    # url(r'^(?P<username>\w+)/tag/(\d+)/',views.site),
    # url(r'^(?P<username>\w+)/archive/(\w+)/',views.site),
    # 上面的三条url其实可以合并成一条
    url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)/', views.site),
    # 文章详情页
    url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/', views.article_detail),
  • views.py
def site(request, username, **kwargs):
    # 先校验当前用户对应的个人站点是否存在
    user_obj = models.UserInfo.objects.filter(username=username).first()
    # 用户如果不存在应该返回一个404页面
    if not user_obj:
        return render(request, 'errors.html')
    blog = user_obj.blog
    # 查询当前个人站点下所有文章
    article_list = models.Article.objects.filter(blog=blog)
    if kwargs:
        # print(kwargs)  # {'condition': 'tag', 'param': '1'}
        condition = kwargs.get('condition')
        param = kwargs.get('param')
        # 判断用户到底想按照哪个条件筛选数据
        if condition == 'category':
            article_list = article_list.filter(category_id=param)
        elif condition == 'tag':
            article_list = article_list.filter(tags__id=param)
        else:
            year, month = param.split('-')  # 2020-11  [2020,11]
            article_list = article_list.filter(create_time__year=year, create_time__month=month)

    return render(request, 'site.html', locals())


def article_detail(request, username, article_id):
    """
    应该需要校验username和article_id是否存在,但是我们这里先只完成正确的情况
    :param request:
    :param username:
    :param article_id:
    :return:
    """
    user_obj = models.UserInfo.objects.filter(username=username).first()
    blog = user_obj.blog
    # 先获取文章对象
    article_obj = models.Article.objects.filter(pk=article_id, blog__userinfo__username=username).first()
    if not article_obj:
        return render(request, 'errors.html')
    return render(request, 'article_detail.html', locals())
  • mytag.py
from django import template
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth

register = template.Library()


# 自定义inclusion_tag
@register.inclusion_tag('left_menu.html')
def left_menu(username):
    # 构造侧边栏需要的数据
    user_obj = models.UserInfo.objects.filter(username=username).first()
    blog = user_obj.blog

    # 1 查询当前用户所有分类及分类下的文章数
    category_list = models.Category.objects.filter(blog=blog).annotate(
        count_num=Count('article__pk')).values_list('name', 'count_num', 'pk')

    # 2 查询当前用户所有的标签及标签下的文章数
    tag_list = models.Tag.objects.filter(blog=blog).annotate(
        count_num=Count('article__pk')).values_list('name', 'count_num', 'pk')

    # 3 安照年月统计所有文章
    date_list = models.Article.objects.filter(blog=blog).annotate(
        month=TruncMonth('create_time')).values('month').annotate(
        count_num=Count('pk')).values_list('month', 'count_num')
    return locals()
  • base.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    {% load static %}
    <!-- Bootstrap3 核心 CSS 文件,本地导入 -->
    <link href="{% static "bootstrap-3.4.1-dist/css/bootstrap.min.css" %}" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="{% static "js/jquery-3.6.0.min.js" %}"></script>
    <!-- Bootstrap3 核心 JavaScript 文件 -->
    <script src="{% static "bootstrap-3.4.1-dist/js/bootstrap.min.js" %}"></script>
    <link rel="stylesheet" href="/media/css/{{ blog.site_theme }}">
    {% block css %}

    {% endblock %}
</head>
<body>
.....
省略导航条代码
.....
<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            {% load mytag %}
            {% left_menu username %}
        </div>
        <div class="col-md-9">
            {% block content %}

            {% endblock %}
        </div>

    </div>
</div>
</body>
</html>
  • home.py
这部分内容无变化
  • site.html
{% extends 'base.html' %}

{% block content %}
<ul class="media-list">
              {% for article_obj in article_list %}
                  <li class="media">
                    <h4 class="media-heading"><a href="/{{ username }}/article/{{ article_obj.pk }}">{{ article_obj.title }}</a></h4>
                    <div class="media-left">
                      <a href="#">
                        <img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80">
                      </a>
                    </div>
                    <div class="media-body">

                      {{ article_obj.desc }}
                    </div>
                  </li>

                  <div class="pull-right">
                    <span>posted&nbsp;&nbsp;</span>
                    <span>@&nbsp;&nbsp;</span>
                    <span>{{ article_obj.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span>
                    <span>{{ article_obj.blog.userinfo.username }}&nbsp;&nbsp;</span>
                    <span><span class="glyphicon glyphicon-comment"></span>评论({{ article_obj.comment_num }})&nbsp;&nbsp;</span>
                    <span><span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article_obj.up_num }})</span>
                    <span><a href="#">编辑</a></span>
                </div>
                  <br>
                  <hr>
              {% endfor %}

            </ul>
{% endblock %}
  • left_menu.html
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">文章分类</h3>
</div>
<div class="panel-body">
{% for category in category_list %}
    <li><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }}({{ category.1 }})</a></li>
{% endfor %}

</div>
</div>
<div class="panel panel-danger">
  <div class="panel-heading">
    <h3 class="panel-title">文章标签</h3>
  </div>
  <div class="panel-body">
    {% for tag in tag_list %}
          <li><a href="/{{ username }}/tag/{{ tag.2 }}">{{ tag.0 }}({{ tag.1 }})</a></li>
     {% endfor %}
  </div>
</div>
<div class="panel panel-info">
  <div class="panel-heading">
    <h3 class="panel-title">日期归档</h3>
  </div>
  <div class="panel-body">
      {% for date in date_list %}
            <li><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}">{{ date.0|date:'Y年m月' }}({{ date.1 }})</a></li>
      {% endfor %}

  </div>
</div>
  • article_detail.py
{% extends 'base.html' %}

{% block css %}

{% endblock %}


{% block content %}
    <h1>{{ article_obj.title }}</h1>
    <div class="article_content">
        {{ article_obj.content|safe }}
    </div>

{% endblock %}

BBS代码

https://gitee.com/sunrisenan/bbs

文档更新时间: 2022-03-27 21:24   作者:李延召