目录

  • 1、项目开发流程
  • 2、bbs表设计
  • 3、数据库表创建及同步
  • 4、注册功能
    • forms组件
    • 注册页面
  • 5、登录功能
    • 实现图片验证码
  • 6、搭建BBS首页导航条
    • 修改密码
    • 退出登录
  • 7、admin后台管理
  • 8、首页文章展示
    • media配置及用户头像展示
  • 9、图片防盗链
  • 10、个人站点页面搭建

1、项目开发流程

  1. 需求分析
    架构师+产品经理+开发组组长,在跟客户沟通交流中引导客户往我们之前想好的方案上靠。形成一个初步的方案
  2. 项目设计
    架构师进行项目设计:
    编程语言的选择、
    框架的选择(flask、Django、Tornado)、
    数据库的选择(主库mysql postgresql、缓存数据库redis MongoDB memcache)、
    功能划分(将项目划分成几个模块)、
    组长开会(分发任务)、
    项目报价
  3. 分组开发
    组长找组员开会,安排各自功能模块,我们就是在架构师设计好的框架中填写自己的代码
  4. 测试
    测试部门对代码进行压力测试
  5. 交付上线
    交给运维人员

2、BBS表设计(https://www.cnblogs.com/)

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

  1. 用户表
    继承AbstractUser
    扩展字段:phoneavatarcreate_time
    外键字段:
    一对一“个人站点表”
  2. 个人站点表
    site_namesite_title|site_theme
  3. 文章标签表
    name
    外键字段:
    一对多“个人站点表”
  4. 文章分类表
    name
    外键字段:
    一对多“个人站点表”
  5. 文章表
    titledesc文章简介content文章内容create_time发布时间
    数据库字段设计优化(虽然点赞数点踩数评论数可以从其他表跨表查询得到,但是频繁跨表效率低)
    up_num点赞数down_num点踩数comment_num评论数(在点赞点踩表评论表增加数据时,给这三个普通字段数值同步更新+1)
    外键字段:
    一对多“个人站点表”
    多对多“文章标签表“
    一对多”文章分类表”
  6. 点赞点踩表(记录哪个用户给哪篇文章点了赞还是点了踩)
    user ForeignKey(to='User')
    article ForeignKey(to='Article')
    is_up BooleanField()
  7. 文章评论表
    user ForeignKey(to='User')
    article ForeignKey(to='Article')
    content CharField()
    comment_time DateField()
    # 自关联
    # parent ForeignKey(to='comment',null=True)
    # ORM专门提供的自关联的写法
    parent ForeignKey(to='self',null=True)
    根评论:直接评论当前发布的内容
    子评论:评论别人的评论
    (根评论与子评论是一对多的关系)

3、数据库表创建及同步

配置MySQL数据库参数

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

init文件中进行代码声明:

import pymysql
pymysql.install_as_MySQLdb()

models.py文件

from django.db import models# Create your models here.
# 创建表:先写普通字段,再写外键字段from django.contrib.auth.models import AbstractUserclass UserInfo(AbstractUser):phone = models.BigIntegerField(verbose_name='手机号',null=True)# 头像# 给avatar字段传文件对象,该文件会自动存储到avatar文件夹下,设置一个默认头像avatar = models.FileField(upload_to='avatar/',default='avatar/default.jpg',verbose_name='用户头像')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站点样式中存储css/js的文件路径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)def __str__(self):return self.nameclass Article(models.Model):title = models.CharField(max_length=64,verbose_name='文章标题')desc = models.CharField(max_length=255,verbose_name='文章简介')# 文章内容一般用TextFieldcontent = 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(verbose_name='是否点赞')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(verbose_name='评论时间',auto_now_add=True)# 自关联parent = models.ForeignKey(to='self',null=True)

到settings文件中添加代码:AUTH_USERMODEL = 'app01.UserInfo'
告诉Django用UserInfo替代auth_user表。

然后执行数据库迁移命令。

4、注册功能

forms组件

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

如果你的项目自始至终只用到一个forms组件,那么建一个py文件即可。但是如果你的项目需要使用很多个forms组件,那么可以创建一个文件夹,在文件夹内根据forms组件功能的不同创建不同的py文件(userform.pyorderform.py).

app01文件夹下创建myforms.py文件:

from django import forms
from app01 import models# 书写针对用户表的forms组件代码
class MyForm(forms.Form):username = forms.CharField(label='用户名', min_length=3, max_length=8,error_messages={'required': '用户名不能为空','min_length': '用户名最少3位数','max_length': '用户名最多8位数',},# 让标签有bootstrap的样式widget=forms.widgets.TextInput(attrs={'class': 'form-control'}))password = forms.CharField(min_length=3, max_length=8, label='密码',error_messages={'required': '密码不能为空','min_length': '密码最少3位','max_length': '密码最多8位'},widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}))confirm_password = forms.CharField(min_length=3, max_length=8, label='确认密码',error_messages={'required': '密码不能为空','min_length': '密码最少3位','max_length': '密码最多8位'},widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}))email = forms.EmailField(label='邮箱',error_messages={'invalid': '邮箱格式不正确','required': '邮箱不能为空'},widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}))# 钩子函数# 局部钩子:校验用户名是否存在def clean_username(self):username = self.cleaned_data.get('username')# 去数据库中校验is_exist = models.UserInfo.objects.filter(username=username)if is_exist:# 提示信息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': 'jack', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'}# 将校验通过的数据字典赋值个一个变量clean_data = form_obj.cleaned_data# 将字典中的confirm_password键值对删除clean_data.pop('confirm_password')# 获取用户头像file_obj = request.FILES.get('avatar')# 针对用户头像一定要判断是否传值,不能直接添加到字典中去if file_obj:clean_data['avatar'] = file_obj# 直接操作数据库保存数据models.UserInfo.objects.create_user(**clean_data)# 注册成功之后要跳转到一个登录界面,所以给这个前后端交互的字典加上一个urlback_dic['url'] = '/login/'else:back_dic['code'] = 2000back_dic['msg'] = form_obj.errorsreturn JsonResponse(back_dic)   # 将这个字典返回给回调函数return render(request,'register.html',locals())

register.html页面

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script src="jQuery.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script><link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script></head>
<body>
<div class="container-fluid"><div class="row"><div class="col-md-8 col-md-offset-2"><h1 class="text-center">注册页面</h1>{# 里我们不用form表单提交数据,只是单纯用一下form标签,#}{# 之后在ajax请求中会用到$('#myform').serializeArray()这个方法,能够获取到form标签内所有用户普通键值对数据 :[{},{},{},{}]#}<form id="myform">{% csrf_token %}{% for form in form_obj %}<div class="form-group">{# form.auto_id找到标签所对应的id值#}<label for="{{ form.auto_id }}">{{ form.label }}:</label>{{ form }}<span style="color: red" class="pull-right"></span></div>{% endfor %}{# 手动渲染获取用户头像的标签#}<div class="form-group"><label for="myfile">头像{% load static %}<img id="myimg" src="{% static 'img/default.jpg' %}" alt="" width="80" style="margin-left: 1px">{# 只要是点击label内的内容,都会跳转到for指定的标签上 #}</label><input type="file" id="myfile" name="avatar" style="display: none"></div>{#            不要写submit,会出发form表单的提交#}<input id="id_commit" type="button" value="注册" class="btn btn-primary pull-right"></form></div></div>
</div><script>// 实时展示用户头像:$('#myfile').change(function () {// 文件阅读器对象// 1、先生成一个文件阅读器对象let myFileReaderObj = new FileReader();// 2、获取用户上传的头像文件let fileobj = $(this)[0].files[0];// 3、将文件对象交给阅读器对象myFileReaderObj.readAsDataURL(fileobj)// 这是一个异步操作 IO操作,所以要等待文件阅读器读取完毕之后再进行文件的展示,否则头像不显示// 需要用到onload功能// 4、利用文件阅读器将文件展示到前端页面// 修改src属性myFileReaderObj.onload = function(){$('#myimg').attr('src',myFileReaderObj.result)}}){#发送ajax请求,我们的数据中既包含普通键值对,也包含文件,所以用到了FormData内置对象#}$('#id_commit').click(function(){let formDataObj = new FormData();// 1、添加普通键值对$.each($('#myform').serializeArray(),function (index,obj) {formDataObj.append(obj.name,obj.value)})// 2、添加文件数据formDataObj.append('avatar',$('#myfile')[0].files[0]);// 3、发送ajax请求$.ajax({url:"",type:'post',data:formDataObj,// 需要指定两个关键性的参数contentType:false,processData:false,success:function(args){if (args.code == 1000){// 跳转到登录页面window.location.href = args.url} else{// 将对应的错误提示展示到对应的input框后面// forms组件渲染的标签的ID值都是:id_字段名$.each(args.msg,function (index,obj) {let targetId = '#id_'+index;$(targetId).next().text(obj[0]).parent().addClass('has-error')})}}})})// 当框变成红色并且有错误提示时,要达到鼠标点击框则红色框错误提示都消失的效果:// 给所有的input框绑定获取焦点事件$('input').focus(function () {// 将input下面的span标签和input外面的div标签修改内容及属性// jQuery的链式操作$(this).next().text('').parent().removeClass('has-error')})
</script>
</body>
</html>

5、登陆功能

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='reg'),url(r'^login/',views.login,name='login'),# 图片验证码相关操作url(r'^get_code/',views.get_code,name='get_code')
]

views.py文件

from django.shortcuts import render,HttpResponse
from app01.myforms import MyRegForm
from app01 import models
from django.http import JsonResponse
from django.contrib import authdef login(request):if request.method == 'POST':# 针对ajax的前后端交互,通常会定义一个字典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').upper() == code.upper(): # 都转成大写来比较(忽略大小写)# 校验用户名和密码是否正确user_obj = auth.authenticate(request,username=username,password=password)if user_obj:# 保存用户状态auth.login(request,user_obj)# 校验成功的话,给字典增加一个url,之后再跳转到home首页back_dic['url'] = '/home/'else:back_dic['code'] = 2000back_dic['msg'] = '用户名或密码错误'else:back_dic['code'] = 3000back_dic['msg'] = '验证码错误'return JsonResponse(back_dic)return render(request,'login.html')# 图片相关的模块
from PIL import Image,ImageDraw,ImageFont
# Image:生成图片
# ImageDraw:在图片上写字
# ImageFont:控制字体样式
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'/Users/dingding/PycharmProjects/day72_BBS/static/img/default.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('x.png','wb') as f:#     img_obj.save(f,'png')# 再将图片对象读取出来# with open('x.png','rb') as f:#     data = f.read()# return HttpResponse(data)# 推导步骤3:# img_obj = Image.new('RGB', (430, 35), get_random())# 生成一个io内存管理器对象,可以看成是文件句柄# io_obj = BytesIO()# img_obj.save(io_obj,'png')   # 要指定图片的格式# 从内存管理器中读取二进制的图片数据返回给前端# return HttpResponse(io_obj.getvalue())# 最终步骤4:写图片验证码img_obj = Image.new('RGB', (430, 35), get_random())# 产生一个画笔对象img_draw = ImageDraw.Draw(img_obj)# 字体样式(.ttf格式的文件)img_font = ImageFont.truetype('static/font/杨任东竹石体-Semibold.ttf',30)  # 要加上字体大小# 接下来产生随机验证码(包含5位数的数字、大写小写字母)code = ''for i in range(5):random_upper = chr(random.randint(65,90))  # chr会将数字通过ASCII表转成数字对应的字母random_lower = chr(random.randint(97,122))random_int = str(random.randint(0,9))  # 转成字符串# 从上面3个随机选择一个tmp = random.choice([random_upper,random_lower,random_int])# 将产生的随机字符串一个个写入到图片上(一个个写可以控制每个字之间的间隙,生成之后再写就没法控制间隙了)img_draw.text((i*60+50,0),tmp,get_random(),img_font)code += tmpprint(code)# 随机验证码在登录的视图函数中需要用到,要进行校验,所以需要存起来,并且其他视图函数也要能拿到,可以存到session中。request.session['code'] = codeio_obj = BytesIO()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><script src="jQuery.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script><link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>{% 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 col-xs-6"><input type="text" name="code" id="id_code" class="form-control"></div><div class="col-md-6 col-xs-6">{# <img src="{% static 'img/default.jpg' %}" alt="" height="35" width="350" >#}{# 动态展示验证码图片,这个页面只要一加载出来就会朝/get_code/这个页面发get请求#}{# 给这个图片验证码绑定一个点击事件,使得点击后图片会刷新#}<img  id="get_code" src="/get_code/" alt="" height="35" width="350"></div></div></div><input  id="id_commit" type="button" class="btn btn-success pull-right" value="登陆"><span style="color: red" id="error"></span></div></div>
</div>
<script>$('#get_code').click(function(){// 1、先获取标签之前的srclet oldVal = $(this).attr('src');$(this).attr('src',oldVal += '?')})// 点击登录按钮发送ajax请求$('#id_commit').click(function(){$.ajax({url:'',type:'post',data:{'username':$('#username').val(),'password':$('#password').val(),'code':$('#id_code').val(),'csrfmiddlewaretoken':'{{ csrf_token }}'},success:function (args) {if (args.code == 1000){// 跳转到首页window.location.href = args.url}else{// 渲染错误信息$('#error').text(args.msg)}}})})
</script>
</body>
</html>

补充:img标签src属性后面可以写的内容:
1、直接写网络图片地址
2、url后缀(自动朝该url发送get请求获取数据)
3、图片二进制数据

6、搭建BBS首页导航条

导航条以及修改密码、退出登录功能

views.py文件

def home(request):return render(request,'home.html')@ login_required
def set_password(request):# 直接判断是不是ajax请求,只处理ajax请求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_new_password = request.POST.get('confirm_new_password')is_right = request.user.check_password(old_password) # 自动转成加密密码并校验if is_right:if new_password == confirm_new_password:request.user.set_password(new_password)request.user.save()back_dic['msg'] = '密码修改成功'else:back_dic['code'] = 1001back_dic['msg'] = '两次密码输入不一致'else:back_dic['code'] = 1002back_dic['msg'] = '原密码错误'return JsonResponse(back_dic)@login_required
def logout(request):auth.logout(request)# 注销后重定向到home页面return redirect('/home/')

home.html页面

<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="#">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"><h1 class="text-center">修改密码</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="text" id="id_old_password" class="form-control"></div><div class="form-group"><label for="">新密码</label><input type="text" id="id_new_password" class="form-control"></div><div class="form-group"><label for="">确认密码</label><input type="text" id="id_confirm_new_password" class="form-control"></div><div class="modal-footer"><button id="id_edit" type="button" class="btn btn-primary">修改</button><button type="button" class="btn btn-default" data-dismiss="modal">取消</button><span id="password_errors" style="color: red"></span></div></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><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_new_password': $('#id_confirm_new_password').val(),'csrfmiddlewaretoken': '{{ csrf_token }}'},success: function (args) {if (args.code == 1000) {window.location.reload()   // 如果修改成功可以刷新一下页面或者跳转到登录页面让用户重新登录} else {$("#password_errors").text(args.msg)}}})})
</script>
</body>

7、admin后台管理

先创建一个超级管理员:createsuperuser。
Django给你提供了一个管理员可视化界面让你方便的对模型表进行增删改查操作。
如果你想要使用admin后台管理操作这些模型表,就需要先注册你的模型表(告诉admin你需要操作哪些模型表)。

注册模型表app01/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)

修改admin后台管理默认显示的表名,models.py文件后面加上class Meta::

from django.db import models
from django.contrib.auth.models import AbstractUserclass UserInfo(AbstractUser):phone = models.BigIntegerField(verbose_name='手机号',null=True,blank=True)# null=True 表示数据库中该字段可以为空# blank=True 表示admin后台管理该字段可以为空# 头像# 给avatar字段传文件对象,该文件会自动存储到avatar文件夹下,设置一个默认头像avatar = models.FileField(upload_to='avatar/',default='avatar/default.jpg',verbose_name='用户头像')create_time = models.DateField(auto_now_add = True)# 一对一关系blog = models.OneToOneField(to='Blog',null=True)class Meta:# verbose_name = '用户表' # admin后台管理默认显示的表名后面会加sverbose_name_plural  = '用户表'  # 修改admin后台管理默认显示的表名def __str__(self):return self.username

admin会给每一个注册了的模型表自动生成增删改查四条url:
以UserInfo表为例:http://127.0.0.1:8000/admin/app01/userinfo/add/http://127.0.0.1:8000/admin/app01/userinfo/1/delete/http://127.0.0.1:8000/admin/app01/userinfo/1/change/
http://127.0.0.1:8000/admin/app01/userinfo/

数据绑定需要注意的是:
先去文章表绑定数据
个人站点
文章分类

用户和个人站点绑定关系标签和文章(不要把别人的文章绑定到其他人的标签)。

admin后台管理​www.bilibili.com

8、首页文章展示

media配置

网站所使用的静态文件默认放在static文件夹下,用户上传的静态文件也应该保存下来。
media配置:该配置可以让用户上传的所有文件都固定存放在某一个指定的文件夹下。
在settings.py文件中配置:

# 配置用户上传的文件存储位置
MEDIA_ROOT = os.path.join(BASE_DIR,'media') # 文件名可修改

等下次用户上传了文件时,系统会自动在根目录下生成文件夹media/avatar/存放文件。我们之前在根目录下手动创建的avatar文件夹就不需要了。

如何开设后端指定文件夹资源

urls.py中增加代码:

from django.conf.urls import url
from django.contrib import admin
from app01 import views
from django.views.static import serve
from day72_BBS import settingsurlpatterns = [url(r'^admin/', admin.site.urls),url(r'^register/',views.register,name='reg'),url(r'^login/',views.login,name='login'),# 图片验证码相关操作url(r'^get_code/',views.get_code,name='get_code'),# 首页url(r'^home/',views.home,name='home'),# 退出登录url(r'^logout/',views.logout,name='logout'),# 修改密码url(r'^set_password/',views.set_password,name='set_password'),# 暴露后端指定文件夹资源(MEDIA_ROOT的路径)# 固定写法,不要自己改动url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})
]

home.html首页文章展示部分代码:

<div class="container-fluid"><div class="col-md-2 col-xs-2"><div class="panel panel-warning"><div class="panel-heading"><h3 class="panel-title">广告1</h3></div><div class="panel-body">内容1</div></div><div class="panel panel-danger"><div class="panel-heading"><h3 class="panel-title">广告2</h3></div><div class="panel-body">内容2</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">广告3</h3></div><div class="panel-body">内容3</div></div></div><div class="col-md-8 col-xs-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="#">{# 图片的路径需要我们手动加上media前缀#}<img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="60"height="60"></a></div><div class="media-body">{{ article_obj.desc }}</div><br><div>{# 通过文章拿到用户对象#}<span><a href="">{{ article_obj.blog.userinfo.username }}</a></span><span>&nbsp;发布于&nbsp;</span><span><a href="">&nbsp;{{ article_obj.create_time|date:"Y-m-d" }}&nbsp;</a></span><span><span class="glyphicon glyphicon-comment"></span>&nbsp;评论({{ article_obj.comment_num }})&nbsp;</span><span><span class="glyphicon glyphicon-thumbs-up"></span>&nbsp;点赞({{ article_obj.up_num }})&nbsp;</span></div></li><hr>{% endfor %}</ul></div><div class="col-md-2 col-xs-2"><div class="panel panel-warning"><div class="panel-heading"><h3 class="panel-title">广告1</h3></div><div class="panel-body">内容1</div></div><div class="panel panel-danger"><div class="panel-heading"><h3 class="panel-title">广告2</h3></div><div class="panel-body">内容2</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">广告3</h3></div><div class="panel-body">内容3</div></div></div>
</div>

9、图片防盗链

如何避免别的网站直接通过本网站的url访问本网站资源:
简单的防盗:可以做到请求来的时候先看看当前请求是从哪个网站过来的,如果是本网站那么可以正常访问,如果是其它网站则直接拒绝。
请求头里有一个专门记录请求来自于哪个网址的参数:Referer: http://127.0.0.1:8000/asfdasf/

绕过别人的防盗方式:
1、修改请求头Referer
2、写爬虫将对方网址的所有资源直接下载到我们自己的服务器上

10、个人站点页面搭建

ps:由于url方法第一个参数是正则表达式,所以当路由特别多的时候,可能会出现被顶替的情况,针对这种情况有两种解决方式:
1、修改正则表达式
2、调整url方法的位置

添加个人站点的路由:url(r'^(?P<username>w+)/$',views.site,name='site')

增加视图函数:

def site(request,username):# 先校验当前用户名对应的个人站点是否存在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)return render(request,'site.html',locals())

个人站点页面site.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script src="jQuery.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script><link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script></head>
<body>
{#   这里要加上导航条的代码 #}
<div class="container-fluid"><div class="row"><div class="col-md-3 col-xs-3"><div class="panel panel-warning"><div class="panel-heading"><h3 class="panel-title">广告1</h3></div><div class="panel-body">内容1</div></div><div class="panel panel-danger"><div class="panel-heading"><h3 class="panel-title">广告2</h3></div><div class="panel-body">内容2</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">广告3</h3></div><div class="panel-body">内容3</div></div></div><div class="col-md-9 col-xs-9"><ul class="media-list">{% for article_obj in article_list %}<li class="media"><h4 class="media-heading"><a href="">{{ article_obj.title }}</a></h4><div class="media-left"><a href="#">{# 图片的路径需要我们手动加上media前缀#}<img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..."width="60"height="60"></a></div><div class="media-body">{{ article_obj.desc }}</div><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 }})&nbsp;&nbsp;</span><span><a href="">编辑</a></span></div></li><hr>{% endfor %}</ul></div></div>
</div></body>
</html>

tornado项目搭建_Day71-73 BBS项目(1)相关推荐

  1. 004-Spring boot 快速入门-项目搭建与启动、SpringBootApplication、启动图标

    一.官方地址 Spring:http://spring.io/ Spring Project:http://spring.io/projects Spring boot:https://project ...

  2. 移动物联网项目搭建(一)——起步

    移动物联网项目搭建(一) 项目背景意义 项目规划 项目目标(初级) 项目所需硬件设备 项目实现准备 项目传送门 前言 本项目是我本人在2019年7月也就是大二暑假所做的一个云端通信新手训练项目,阿里云 ...

  3. springboot项目搭建0000-导航篇

    前言:这篇帖子没有任何涉及技术的内容,如果你想找springboot相关的技术知识,可以不用往下看了. 这篇帖子仅仅是对我的springboot相关博客,开辟一个导航页,方便读者.技术大牛略过 主要参 ...

  4. 美团项目 ---搭建环节1

    ❤ 项目源码 ❤ GitHub地址:https://github.com/Umbrella001/mtapp 文章目录 一.项目搭建依赖版本一览 二.项目搭建 -- Nuxt部分 三.项目搭建 -- ...

  5. 尚硅谷谷粒商城项目学习笔记-基础-P1项目搭建

    基础-P1项目搭建 0.虚拟机 1.项目架构 1.1Docker 1.1Docker命令 1.3Docker容器文件挂载与端口映射 1.2微服务-注册中心.配置中心.网关 1.3工具配置 1.3.1 ...

  6. SSM米米商城项目笔记一(数据库表创建和项目搭建)

    米米商城项目笔记一(数据库表创建和项目搭建) 使用的相关技术[当前最新技术]: IDEA:2022+ JDK:1.8 MySQL:8.0+ Maven:3.8+ Tomact:9.0+ 创建数据库信息 ...

  7. SWT项目搭建、打包(可执行jar)、部署(包装exe)

    简述 SWT与Eclipse插件(RCP)不同.SWT比较原生,后者是基于前者而起的一个框架,它能结合Eclipse提供的功能自动的打成一个exe的可执行文件.而SWT需要做额外的操作. 本文记述了S ...

  8. 【医疗健康项目】传智健康项目(一)

    第1章 项目概述和环境搭建 1. 项目概述 1.1 前言 传智健康管理系统是一款应用于健康管理机构的业务系统,实现健康管理机构工作内容可视化.会员管理专业化.健康评估数字化.健康干预流程化.知识库集成 ...

  9. Django框架项目——BBS项目介绍、表设计、表创建同步、注册、登录功能、登录功能、首页搭建、admin、头像、图片防盗、个人站点、侧边栏筛选、文章的详情页、点赞点踩、评论、后台管理、添加文章、头像

    文章目录 1 BBS项目介绍.表设计 项目开发流程 表设计 2 表创建同步.注册.登录功能 数据库表创建及同步 注册功能 登陆功能 3 登录功能.首页搭建.admin.头像.图片防盗.个人站点.侧边栏 ...

  10. maven 多项目搭建

    参考http://www.cnblogs.com/xdp-gacl/p/3498271.html . 1.什么是maven maven是基于项目对象模型(POM),是跨平台的管理工具,主要服务于jav ...

最新文章

  1. [ASP.NET入门随想七]主角与配角——OO思想的多态、接口与委托
  2. java中 hashmap中小数,java基础知识--HashMap中对 h(length-1)的理解
  3. 设计模式(十)------23种设计模式(3):抽象工厂模式
  4. jzoj3919-志愿者【换根法,线段树,树形dp】
  5. pycharm cant open file_PyCharm
  6. cadence导入dxf文件_Allegro中如何导入DXF文件
  7. python矩阵中的冒号:
  8. dateutils 工具类_五金工具泡壳封边机
  9. 小微型计算机2018北大核心,2018年全国北大中文核心期刊目录
  10. 不是纸上谈兵,VR原来真的可以缓解抑郁症
  11. 人身三流指什么_保险合同的主体是什么,主体的变更是什么?
  12. css 这个特性,你敢信
  13. C语言——函数的综合运用。自定义函数,gotoxy清屏函数与HideCursor隐藏光标,防闪屏,共同制作打飞机游戏。
  14. 8086汇编基础 inc 自加一
  15. springboot整合J2cache简单使用
  16. android开发 手写签名,Android实现手写签名
  17. 机器学习 交叉验证与网格搜索调参
  18. java update是什么_javaupdate是什么_tbupdate
  19. mybatis插入数据到sqlserver报异常The statement must be executed before any results can be obtained.
  20. Python 如何 ping

热门文章

  1. Tomcat在自定义xml文件中配置虚拟目录
  2. Shiro + JWT + Spring Boot Restful 简易教程
  3. SSM三大框架整合Springfox(Swagger2)详细解决方案
  4. layout_width和width,layout_height和height
  5. 利用计算机进行信息加工正确的说法,信息技术习题1(1-50)
  6. POJ-4004:数字组合(用位移方法解组合数问题,Java版)
  7. C语言实例:将人员信息写入磁盘文件并读出显示
  8. [转]查询oracle数据库的数据库名、实例名、ORACLE_SID
  9. 深究AngularJS——ui-router详解
  10. github 公钥 私钥_GitGithub入门教程笔记(2)