bbs项目

数据库表设计:

models.py

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,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,因为有些评论就是根评论

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/'

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

注册功能

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')
]
urls.py

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
myforms.py

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>

登录功能

验证码推导:

# 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.py
<!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'
settings.py

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),
]
urls.py

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,因为有些评论就是根评论
models.py

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')
views.py

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>
register.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>
login.html

 

posted @ 2020-06-11 00:43  耗油炒白菜  阅读(130)  评论(0编辑  收藏  举报