Frank的学习之路

Day_22_总结_BBS设计

1.需求分析

         1).登录,图片验证码(ajax

         https://docs.geetest.com/(极验验证码)

         2).注册:ajax提交,错误信息的展示,头像的预览上传(基于forms

         3).首页文章的展示

         4).个人站点(不同人,不同的样式),标签、分类、归档

         5).文章详情页(母版的继承,文章样式)

         6).点赞点踩

         7).评论的展示和提交

                   -子评论

         8).后台管理(文章展示)

         9).文章发布(富文本编辑器的使用),通过H2自动汇总目录

        

        

2.设计程序的架构,数据库

 

         -基于django   

         -设计数据库(一对多的关系一旦确立,关联字段写在多的一方,一对一的关系,关联字段写在哪里都可以,多对多关系,需要创建第三张表)    

                   -用户表:userInfo---auth(继承)

                   https://www.cnblogs.com/liuqingzheng/articles/9628105.html

                  

                   -个人站点表:blog,userInfo一对一           

                   -文章:分类跟文章一对多,标签跟文章是多对多               

                   -分类表:跟用户表是一对多,userid         

                   -标签表(文章关键字):跟用户的表是一对多,userid            

                   -点赞,点踩表                

                   -评论表:

                            -子评论

        

3.分任务开发程序

         1.登录

         2.注册

                   -register页面渲染(form组件渲染)

                   -头像预览(labelfor属性)

                            -文件阅读器 var filereader=new FileReader();

                            -onload等它读完再进行处理

                   -错误信息渲染

                            -each循环,把错误信息,拼到span

                            -定时器

                   -form组件(数据校验功能用的比较多)

                            -form.clean_data:校验通过的正确的数据

                            -form.error:所有错误的数据

                            -form.is_valid();判断是否校验通过

                            -局部钩子函数:def clean_字段名():

                            -全局钩子函数:def clean():

                            -错误信息的中文显示error_messages={'max_length':'最大长度为18',

                                         'min_length': '最小长度为3',

                                         'required':'这个必填'}

                            -可以控制前端页面显示的样式attrs={'class':'form-control'}

                            -页面渲染:for 循环form对象 {% for foo in form %} {% endfor %}

                   -ajax提交数据

                            -$("#form").serializeArray()form表单的数据序列化(里面需要带着csrf_token

                            -上传文件:需要借助FormData这个类

                            -contentType:false  //不要给我编码

                            -processData:false  //不要预处理数据

                   -FileField:

                            -把文件对象直接给它,它会自动保存,并且把文件地址,放到该字段下(文件不存在,会自动创建文件夹)

4.测试

 

5.上线运行

 

举例:

 

(1).-BBS_new-settings.py

 

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        #ORM不能创建数据库
       
'NAME': 'BBS', #数据库的名字
       
'USER':'root',
        'PASSWORD':'123456',
        'HOST':'192.168.10.5',
        'PORT':3306,
    }
}

 

STATIC_URL = '/static/' #静态文件路径 STATICFILES_DIRS=[

    os.path.join(BASE_DIR,'static')

]

 

 (2). -blog-urls.py

 

from django.conf.urls import url from django.contrib import admin from blog import views



urlpatterns = [

    url(r'^admin/', admin.site.urls),

    url(r'^register/', views.register),

    url(r'^login/', views.login),

    url(r'^get_code/', views.get_code),

]

 

(3).-blog-__init__.py

 

import pymysql

pymysql.install_as_MySQLdb()

 

(4).-blog-models.py

 

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



#auth组件,需要继承AbstractBaseUser class UserInfo(AbstractUser):

    '''

    用户信息     '''

    nid=models.AutoField(primary_key=True)

    # telephone=models.CharField(max_length=11,null=False,unique=True)

    #存用户头像(FileField用来存文件,存了一个文件的地址,对到数据库,是一个varchar类型)     #直接给它一个文件对象,它会自动的去保存,保存到avatars文件夹下,没有自动创建文件夹     #default 如果没有传,字段存成avatars/default.png

    avatar=models.FileField(upload_to='avatars/',default="avatars/default.png")

    #创建用户时间,时间类型DateTimeField,年月日,时分秒 verbose_name admin中使用,admin中录入数据使用到     #auto_now_add :创建数据记录的时候会把当前的时间添加到数据库     #auto_add:每次更新数据记录的时候会更新改字段     create_time=models.DateTimeField(verbose_name='创建时间',auto_now_add=True)

    #null=True表示可以为空,on_delete=models.CASCADE级联删除     blog=models.OneToOneField(to='Blog',to_field='nid',null=True,on_delete=models.CASCADE)

    #重写了 __str,打印的时候,会显示 username

    def __str__(self):

        return self.username class Blog(models.Model):

    '''

    博客信息     '''

    nid=models.AutoField(primary_key=True)

    title=models.CharField(verbose_name='个人博客标题',max_length=64)

    site_name=models.CharField(verbose_name='站点名称',max_length=64)

    theme=models.CharField(verbose_name='博客主题',max_length=32)



    def __str__(self):

        return self.title class Category(models.Model):

    '''

    博主个人文章分类表     '''

    nid=models.AutoField(primary_key=True)

    title=models.CharField(verbose_name='分类标题',max_length=32)

    #blog一对多     blog=models.ForeignKey(verbose_name='所属博客',to='Blog',to_field='nid',on_delete=models.CASCADE)



    def __str__(self):

        return self.title class Tag(models.Model):

    nid=models.AutoField(primary_key=True)

    title=models.CharField(verbose_name='标签名称',max_length=32)

    blog=models.ForeignKey(verbose_name='所属博客',to='Blog',to_field='nid',on_delete=models.CASCADE)



    def __str__(self):

        return self.title class Article(models.Model):

    nid=models.AutoField(primary_key=True)

    title=models.CharField(verbose_name='文章标题',max_length=50)

    desc=models.CharField(verbose_name='文章描述',max_length=255)

    create_time=models.DateTimeField(verbose_name='创建时间',auto_now_add=True)

    #TextField 大文本     content=models.TextField()



    # comment_count=models.IntegerField(default=0)

    # up_count=models.IntegerField(default=0)

    # down_count=models.IntegerField(default=0)



    #文章和作者是一对多关系     user=models.ForeignKey(verbose_name='作者',to=UserInfo,to_field='nid',on_delete=models.CASCADE)

    category=models.ForeignKey(verbose_name='分类标签',to=Category,to_field='nid',on_delete=models.CASCADE)

    #文章跟标签,多对多,手动指定第三张表     tags=models.ManyToManyField(

        to='Tag',

        through='Article2Tag',

        through_fields=('article','tag'),

    )



    def __str__(self):

        return self.title class Article2Tag(models.Model):

    nid=models.AutoField(primary_key=True)

    article=models.ForeignKey(verbose_name='文章',to='Article',to_field='nid',on_delete=models.CASCADE)

    tag=models.ForeignKey(verbose_name='标签',to='Tag',to_field='nid',on_delete=models.CASCADE)



    # class Meta:

    #联合唯一     #     unique_together=[

    #         ('article','tag'),

    #     ]

    def __str__(self):

        v=self.article.title + "---" + self.tag.title

        return v class ArticleUpDown(models.Model):

    '''

    点赞表     '''

    nid=models.AutoField(primary_key=True)

    user=models.ForeignKey('UserInfo',null=True,on_delete=models.CASCADE)

    article=models.ForeignKey('Article',null=True,on_delete=models.CASCADE)

    #default=True 默认为真     is_up=models.BooleanField(default=True)



    class Meta:

        unique_together=[

            ('article','user')

        ] class Comment(models.Model):

    '''

    评论表     '''

    nid=models.AutoField(primary_key=True)

    article=models.ForeignKey(verbose_name='评论文章',to='Article',to_field='nid',on_delete=models.CASCADE)

    user=models.ForeignKey(verbose_name='评论者',to='UserInfo',to_field='nid',on_delete=models.CASCADE)

    content=models.CharField(verbose_name='评论内容',max_length=255)

    create_time=models.DateTimeField(verbose_name='创建时间',auto_now_add=True)

    #自关联,为了方便查询和约束     parent_comment=models.ForeignKey('self',null=True,on_delete=models.CASCADE)



    def __str__(self):

        return self.content

        # 1    lqz    111文章   写的很好           null

        # 2    egon   111文章   写的就是很好       null

        # 3    sls    111文章   你说的对           2

 

-blog-myForms.py
from django import forms from django.forms import  widgets from blog import models from django.core.exceptions import ValidationError class RegForm(forms.Form):

    name=forms.CharField(max_length=18,min_length=3,label='用户名',

                         widget=widgets.TextInput(attrs={'class':'form-control'}),

                         error_messages={'max_length':'最大长度为18',

                                         'min_length': '最小长度为3',

                                         'required':'这个必填'})

    pwd=forms.CharField(max_length=18,min_length=3,label='密码',

                        widget=widgets.PasswordInput(attrs={'class':'form-control'}),

                         error_messages={'max_length':'最大长度为18',

                                         'min_length': '最小长度为3',

                                         'required':'这个必填'})

    re_pwd=forms.CharField(max_length=18,min_length=3,label='确认密码',

                           widget=widgets.PasswordInput(attrs={'class':'form-control'}),

                         error_messages={'max_length':'最大长度为18',

                                         'min_length': '最小长度为3',

                                         'required':'这个必填'})

    email=forms.EmailField(label='邮箱',widget=widgets.TextInput(attrs={'class':'form-control'}),

                         error_messages={'invalid':'格式不合法',

                                         'required':'这个必填'})



    #局部钩子函数     #检验name是不是在数据库存在     # 函数名字必须:clean_字段名     def clean_name(self):

        #cleaned_data通过校验的数据         name=self.cleaned_data.get('name')

        #如果能查出结果,说明数据库已经存在这个名字         user=models.UserInfo.objects.filter(username=name).first()

        # if name.startswith('sb'):

        #     raise ValidationError('不能以sb开头')

        # else:

        #     return name

        if user:

            #如果校验失败,抛出ValidationError异常             raise ValidationError('用户名已经存在')

        else:

            #如果校验成功,返回name

            return name

    #全局钩子函数     def clean(self):

        pwd=self.cleaned_data.get('pwd')

        re_pwd=self.cleaned_data.get('re_pwd')

        if pwd==re_pwd:

            #通过了             return self.cleaned_data

        else:

            raise ValidationError('两次密码不一致')

 

(5).-blog-views.py

 

from django.shortcuts import render,HttpResponse # Create your views here. from blog.myForms import RegForm from blog import  models from django.contrib import  auth from django.http import JsonResponse # 生成图片包 from PIL import Image   #安装Pillow资源包 # 生成随机数 import random # 内存管理器 from io import BytesIO #在图片上写文字 from PIL import ImageDraw #生成字体对象 from PIL import ImageFont #django提供验证 from django.contrib import auth def register(request):

    if request.method == 'GET':

        form = RegForm()

        return render(request, 'register.html', {"form": form})

    if request.method=='POST':

        #生成form对象,把要校验的数据传递过去         form = RegForm(request.POST)

        response={'status':100,'msg':'注册成功'}

        #form.is_valid() 如果是true,说明校验通过,就可以保存数据,如果是false,校验不通过,不能保存         if form.is_valid():

            #取出数据,创建用户             name=request.POST.get('name')

            pwd=request.POST.get('pwd')

            re_pwd=request.POST.get('re_pwd')

            email=request.POST.get('email')

            #拿到前台传过来的文件对象             myfile=request.FILES.get('myfile')

            #如果myfileNone就不能给avatar赋值             if myfile:

                models.UserInfo.objects.create_user(username=name, password=pwd, email=email, avatar=myfile)

            else:

                models.UserInfo.objects.create_user(username=name,password=pwd,email=email)

            return JsonResponse(response)

        else:

            print(form.errors)

            # from django.forms.utils import  ErrorDict

            # print(type(form.errors))

            response['msg']=form.errors

            response['status']=101

            return JsonResponse(response) # 生成随机颜色函数 def get_random_color():

    return (random.randint(0,255),random.randint(0,255),random.randint(0,255)) def get_code(request):

    # 方案一:直接放入图片     # with open('egon.jpg','rb') as f:

    #     data=f.read()



    # # 方案二:生成一张图片Pillow,虚拟可以环境可以使用不同版本python

    # #生成一张新图片(三个参数:第一个,模式:RGB,第二个:长高,第三个:图片颜色)

    # new_img=Image.new('RGB',(400,40),color=get_random_color())

    # #打开一个空文件,把它保存进去     # with open('test.png','wb') as f:

    #     #第二个参数是图片类型     #     new_img.save(f,'png')

    #     with open('test.png','rb') as f:

    #         data=f.read()



    # # 方案三:生成图片放入内存     # new_img = Image.new('RGB', (400, 40), color=get_random_color())

    # # 打开一个内存管理器,保存进去     # img=BytesIO()

    # new_img.save(img,'png')

    # #从内存管理器中取出img

    # data=img.getvalue()



    # 方案四:生成图片放入内存     new_img = Image.new('RGB', (400, 40), color=get_random_color())

    #把图片放到ImageDraw     draw=ImageDraw.Draw(new_img)

    #构造字体对象,第一个参数是字体文件(ttf),第二个是字体大小     font=ImageFont.truetype('static/font/kumo.ttf',30)

    # draw.text((0,5),'python',get_random_color(),font=font)

    # ASCII对照表:https: // baike.baidu.com / pic / ASCII / 309296 / 0 / c2fdfc039245d688c56332adacc27d1ed21b2451?fr = lemma & ct = single  # aid=0&pic=c2fdfc039245d688c56332adacc27d1ed21b2451

    # 保存验证码     valid_code=''

    for i in range(5):

        num_str=str(random.randint(0,9))

        upper_str=chr(random.randint(65,90))

        low_str=chr(random.randint(97,122))

        random_str=random.choice([num_str,upper_str,low_str])

        draw.text((i*70+70, 5),random_str, get_random_color(), font=font)

        valid_code+=random_str

    print(valid_code)

    #把验证码存储到session,设置全局变量进行提交验证     request.session['valid_code']=valid_code

    # 打开一个内存管理器,保存进去     img=BytesIO()

    new_img.save(img,'png')

    #从内存管理器中取出img

    data=img.getvalue()

    return HttpResponse(data) def login(request):



    if request.method=='GET':

        return render(request,'login.html')

    if request.method=='POST':

        response={'status':100,'msg':'登录成功'}

        name=request.POST.get('name')

        pwd=request.POST.get('pwd')

        code=request.POST.get('code')

        #忽略大小写         if code.upper()==request.session.get('valid_code').upper():

            #如果校验通过,user就是这个对象,如果校验不通过,user就是None

            user=auth.authenticate(request,username=name,password=pwd)

            if user:

                #这样之后,所有的request对象中都能拿出request.user                 auth.login(request,user)

            else:

                response['status']=101

                response['msg']='用户名或密码错误'

        else:

            response['status'] = 102

            response['msg'] = '验证码错误'

    return JsonResponse(response)

 

-static-bs

-static -font

-static -img

-static-jquery

 

(6).-templates-login.html

 

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <script src="/static/jquery-3.2.1.min.js"></script>     <link rel="stylesheet" href="/static/bs/css/bootstrap.css">     <title>登录</title>     <style>         .error{

            color: red;

            margin-left: 255px;

        }

    </style> </head> <body> <div class="container-fluid">     <div class="row">         <div class="col-md-6 col-md-offset-3">             <h2>登录</h2>             {% csrf_token %}

            <div class="form-group">                 <label for="">用户名</label>                 <input type="text" id="id_name" class="form-control">             </div>             <div class="form-group">                 <label for="">密码</label>                 <input type="text" id="id_pwd" class="form-control">             </div>             <div class="form-group">                 <label for="">验证码</label>                 <div class="row">                     <div class="col-md-6">                         <input type="text" id="id_code" class="form-control">                     </div>                     <img src="/get_code/" alt="" width="255" height="35" id="id_img">                 </div>             </div>             <button class="btn btn-danger " id="submit">登录</button><span class="error"></span>         </div>     </div> </div> </body> <script> $("#id_img").click(function () {

    $("#id_img")[0].src = $("#id_img")[0].src + '?' }) $("#submit").click(function () {

    $.ajax({

        url:'/login/',

        type:'post',

        data:{'name':$("#id_name").val(),

            'pwd':$("#id_pwd").val(),

            'code':$("#id_code").val(),

            'csrfmiddlewaretoken':'{{ csrf_token }}'

        },



        success:function (data) {

            console.log(data)

            if (data.status!=100){

                $(".error").text(data.msg)

            }else {

                location.href='/index/'

            }

        }

    })

}) </script> </html>

 

(7).-templates-register.html

 

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <script src="/static/jquery-3.2.1.min.js"></script>     <link rel="stylesheet" href="/static/bs/css/bootstrap.css">     <title>注册</title>     <style>         #id_myfile{

            display: none;

        }

        .errors{

            color: red;

        }

    </style> </head> <body> <div class="container-fluid">     <div class="row">         <div class="col-md-6 col-md-offset-3">         <h2>注册</h2>             <form id="form">             {% csrf_token %}

            {% for foo in form %}

                <div class="form-group">                     <label for="{{ foo.auto_id}}">{{ foo.label}}</label>                     {{ foo }} <span class="pull-right errors"></span>                 </div>             {% endfor %}

            <div class="form-group">                 <label for="id_myfile">头像                     <img src="/static/img/default.png" id="img_file" height="80" width="80" style="margin-left: 10px">                 </label>                 <input type="file" name="myhead" id="id_myfile">             </div>             <input type="button" class="btn btn-danger " value="注册" id="id_submit">             </form>         </div>     </div> </div> </body> <script>     //id_myfile控件发生变化的时候,取出头像,显示在img标签上,实现预览功能 $("#id_myfile").change(function () {

    //  alert('11')

    //$("#id_myfile")[0]        拿到原生的dom

    //$("#id_myfile")[0].files  拿到当前控件的所有文件     //$("#id_myfile")[0].files[0] 拿到当前控件的所有文件的第一个文件     var obj=$("#id_myfile")[0].files[0];

    //借助文件阅读器,生产一个文件阅读的对象     var filereader=new FileReader();

    //把文件读到文件阅读器中     filereader.readAsDataURL(obj); {#    $("#img_file").attr('src',filereader.result);#}

    filereader.onload=function () {

        $("#img_file").attr('src',filereader.result);

    }

}) $("#id_submit").click(function () {

    //上传文件,需要生成一个formdata

    var formdata=new FormData()

    //$('#id_name').val()取到name对应的值     //formdata.append('name',$('#id_name').val())



    var submit_data=$("#form").serializeArray()  //取出form的值     //console.log(submit_data)

    $.each(submit_data,function (index,value) {

        //console.log(index)

        //console.log(value)

        //把数据拼到formdata         //console.log(value.name,value.value)

        formdata.append(value.name,value.value)

    })

    //把文件对象放入formdata     formdata.append('myfile',$("#id_myfile")[0].files[0])



    $.ajax({

        url:'/register/',

        type:'post',

        //如果要上传文件,必须指定下面两个参数为false

        contentType:false,//不要给我编码,告诉jQuery不要去处理发送的数据         processData:false,//不要预处理数据,告诉jQuery不要去设置Content-Type请求头         data:formdata,



        success: function (data) {

            //console.log(data)

            if (data.status==100){

                location.href='/login/'

            }else {

                //错误信息渲染                 //先找到input这个标签                 //console.log(data.msg)

                $.each(data.msg,function (index,value) {

                    console.log(index)

                    console.log(value)

                    //-----------第一种方法-------------------------------------------

                    ////$("#id_"+index) 拿到是email 对应的那个input标签,下一个标签是span

                    //$("#id_"+index).next().text(value)

                    ////拿到input标签的父空间,也就是div,给它添加上has-error样式                     //$("#id_"+index).parent().addClass('has-error')

                    //-----------第二种方法-------------------------------------------

                    $("#id_"+index).next().text(value).parent().addClass('has-error')

                    //两次密码不一致的情况                     if (index=='__all__'){

                        $("#id_re_pwd").next().text(value).parent().addClass('has-error')

                    }

                    //定时器,两个参数,第一个是匿名函数,第二个是时间毫秒                     setTimeout(function () {

                        //清理div上的has-error

                        $(".form-group").removeClass('has-error')

                        $(".errors").text("")

                    },3000)

                })

            }

        }



    })



}) </script> </html>

 

返回顶部