BBS仿博客园项目
项目需求分析
项目需求(产品经理,架构师,开发组组长)
项目设计(框架的选择,数据库的选择,主要功能模块) 报价(工期,开发人员工资)
任务分发(开发组长>>>小弟开发)
测试(本地测试+测试人员测试)
交付上线
项目分析
表设计
用户表(UserInfo)
用户电话phone
用户头像avatar
用户创建时间create_time
blog 》》》site 一对一个人站点表
个人站点表(Blog)
站点名称site_name
站点标题site_title
站点样式site_theme
文章标签表(Tag)
标签名称name
blog >>> Blog 一对多个人站点表
文章分类表
分类名称name
blog >>> Blog 一对多个人站点表
文章表
文章标题title
文章简介desc
文章详情content
文章发布时间create_time
# 数据库查询优化(可写可不写,主要是为了节省跨表查询,而与点赞点踩这表表要关联,联级,所以是要用要事物)
文章评论数comment_num
文章点赞数up_num
文章点踩数down_num
blog 》》》 Blog 一对多个人站点表
tags >>> Tag 多对多标签表
category 》》》 Category 一对多分类表
点赞点踩表
用户名字段user 一对多 个人站点/用户
文章字段article 一对多 文章表
点赞点踩is_up 0/1
user article is_up
1 1 1
2 1 0
1 2 0
文章评论表
用户名字段user 一对多 个人站点/用户
文章字段article 一对多 文章表
评论内容content
父评论parent(自己跟自己关联) 一对多自身
user article content parent
1 1 666
1 2 888
2 1 666
1 from django.db import models 2 3 # Create your models here. 4 from django.contrib.auth.models import AbstractUser 5 6 7 8 class UserInfo(AbstractUser): 9 10 phone = models.BigIntegerField(null=True) 11 create_time = models.DateField(auto_now_add=True) 12 #文件存放的路径,avatar,自动给你创这个文件夹,默认头像 13 #如果用户上传了一个头像,就会把这个文件放到avatar这个文件夹下面,如果不上传头像我就用默认的这个 14 avatar = models.FileField(upload_to=‘avatar/‘,default=‘static/img/default.jpg‘) 15 blog = models.OneToOneField(to=‘Blog‘,null=True) #null=True 可以不写,不要样式什么的 16 17 #个人站点 18 class Blog(models.Model): 19 site_name = models.CharField(max_length=32) 20 site_title = models.CharField(max_length=64) 21 #存css样式文件的样式 22 theme = models.CharField(max_length=32) 23 24 class Category(models.Model): 25 name = models.CharField(max_length=32) 26 blog = models.ForeignKey(to=‘Blog‘) 27 28 class Tag(models.Model): 29 name = models.CharField(max_length=32) 30 blog = models.ForeignKey(to=‘Blog‘) 31 32 class Article(models.Model): 33 title = models.CharField(max_length=32) 34 desc = models.CharField(max_length=256) 35 #存大段文本 36 content = models.TextField() 37 create_time = models.DateField(auto_now_add=True) 38 #文章的评论数,点赞数,点踩数 39 comment_num = models.IntegerField(default=0) 40 up_num = models.IntegerField(default=0) 41 down_num = models.IntegerField(default=0) 42 blog = models.ForeignKey(to=‘Blog‘,null=True) 43 category = models.ForeignKey(to=‘Category‘,null=True) 44 tag = models.ManyToManyField(to=‘Tag‘,through=‘Article2Tag‘, through_fields=(‘article‘,‘tag‘)) #文章的标签 45 46 #自创的第三张表 47 class Article2Tag(models.Model): 48 article = models.ForeignKey(to=‘Article‘) 49 tag = models.ForeignKey(to=‘Tag‘) 50 51 class UpAndDown(models.Model): 52 user = models.ForeignKey(to=‘UserInfo‘) 53 article = models.ForeignKey(to=‘Article‘) 54 #存0,1 55 is_up = models.BooleanField() 56 57 58 59 class Comment(models.Model): 60 user = models.ForeignKey(to=‘UserInfo‘) 61 article = models.ForeignKey(to=‘Article‘) 62 content = models.CharField(max_length=128) 63 create_time = models.DateField(auto_now_add=True) 64 parent = models.ForeignKey(to=‘self‘,null=True)
注意(默认值设计):
UserInfo表需要注意的(默认值):
1.phone =models.BigIntegerField(null=True)
2.create_time=models.DateField(auto_now_add=True)
3.blog = models.OneToOneField(to=‘Blog‘,null=True) 可以不要样式
Article表
1.create_time = models.DateField(auto_now_add=True)
2.blog = models.ForeignKey(to=‘Blog‘,null=True)
3.category = models.ForeignKey(to=‘Category‘,null=True)
4.content = models.TextField() #存大段文本
Comment表
1.create_time = models.DateField(auto_now_add=True)
2.parent = models.ForeignKey(to=‘self‘,null=True)
UpAndDown表
1.is_up= models.BooleanField()
用户表用的是auth认证,
1.from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):需要继承
2.settings配置
指定自己的auth用户表
AUTH_USER_MODEL = ‘app01.UserInfo‘
连接数据库
1.navicat创建数据库
2.
DATABASES = {
‘default‘: {
‘ENGINE‘: ‘django.db.backends.mysql‘,
‘NAME‘: ‘bbs‘,
‘HOST‘:‘127.0.0.1‘,
‘PORT‘:3306,
‘USER‘:‘root‘,
‘PASSWORD‘:‘123‘,
}
}
3.到__init__文件中导入
import pymysql
pymysql.install_as_MySQLdb()
配置静态文件
STATICFILES_DIRS= [
os.path.join(BASE_DIR, ‘static‘)
]
数据迁移,执行两行命令
python3 manage.py makemigrations
python3 manage.py migrate
创建自定义from组件
在app01下创建文件myforms.py
1 from django import forms 2 from django.forms import widgets 3 from app01 import models 4 5 6 class RegForm(forms.Form): 7 username = forms.CharField(max_length=8,min_length=3,label=‘用户名‘,error_messages={ 8 ‘max_length‘:‘用户名最长8位‘, 9 ‘min_length‘:‘用户名最少3位‘, 10 ‘required‘:‘用户名不能为空‘, 11 },widget=widgets.TextInput(attrs={‘class‘:‘form-control‘})) 12 password = forms.CharField(max_length=8, min_length=3, label=‘密码‘, error_messages={ 13 ‘max_length‘: ‘密码最长8位‘, 14 ‘min_length‘: ‘密码最少3位‘, 15 ‘required‘: ‘密码不能为空‘, 16 }, widget=widgets.PasswordInput(attrs={‘class‘: ‘form-control‘})) 17 confirm_password = forms.CharField(max_length=8, min_length=3, label=‘确认密码‘, error_messages={ 18 ‘max_length‘: ‘确认密码最长8位‘, 19 ‘min_length‘: ‘确认密码最少3位‘, 20 ‘required‘: ‘确认密码不能为空‘, 21 }, widget=widgets.PasswordInput(attrs={‘class‘: ‘form-control‘})) 22 email = forms.EmailField(label=‘邮箱‘,error_messages={ 23 ‘invalid‘:‘邮箱格式错误‘, 24 ‘required‘:‘邮箱不能为空‘ 25 },widget=forms.EmailInput(attrs={"class":‘form-control‘})) 26 27 # 局部钩子 校验用户名是否已存在 28 def clean_username(self): 29 username = self.cleaned_data.get(‘username‘) 30 user = models.UserInfo.objects.filter(username=username).first() 31 if user: 32 self.add_error(‘username‘,‘用户名已存在‘) 33 else: 34 return username 35 36 # 全局钩子 校验密码是否一致 37 def clean(self): 38 password = self.cleaned_data.get(‘password‘) 39 confirm_password = self.cleaned_data.get(‘confirm_password‘) 40 if not password == confirm_password: 41 self.add_error(‘confirm_password‘,‘两次密码不一致‘) 42 else: 43 return self.cleaned_data
连接路由
1 from django.conf.urls import url 2 from django.contrib import admin 3 4 urlpatterns = [ 5 url(r‘^admin/‘, admin.site.urls), 6 ]
包裹在div中,方便调节样式,这个加大间距
<div class="form-group">
{{ form.label }} {{ form }}
<span class="errors"></span>
</div>
‘注册居中’
<h2 class="text-center">注册</h2>
分割线
<hr>
{#input框对象点auto_id能够直接获取input的id值#}
<p><label for="{{ foo.auto_id }}">{{ foo.label }}</label>
{{ foo }}
<span class="errors pull-right" style="color: red"></span>
</p>

$(‘#id_submit‘).click(function () {
console.log($(‘#myform‘).serializeArray())
})
$(‘#id_submit‘).click(function () {
var formData = new FormData();
$.each($(‘#myform‘).serializeArray(),function(index,obj){
formData.append(obj.name,obj.value)
})
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> 7 <meta name="viewport" content="width=device-width, initial-scale=1"> 8 <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css"> 9 <script src="/static/bootstrap-3.3.7/js/bootstrap.min.js"></script> 10 </head> 11 <body> 12 <div class="container-fluid"> 13 <div class="row"> 14 <div class="col-md-6 col-md-offset-3"> 15 <h2 class="text-center">注册</h2> 16 <hr> 17 <form id="myform"> 18 {% csrf_token %} 19 {% for foo in form_obj %} 20 <div class="form-group"> 21 {#input框对象点auto_id能够直接获取input的id值#} 22 <p><label for="{{ foo.auto_id }}">{{ foo.label }}</label> 23 {{ foo }} 24 <span class="errors pull-right" style="color: red"></span> 25 </p> 26 </div> 27 {% endfor %} 28 <div class="form-group"> 29 <label for="id_myfile">头像 30 <img src="/static/img/default.jpg" alt="" width="80" style="margin-left: 20px" id="id_img"> 31 </label> 32 <input type="file" name="myfile" id="id_myfile" style="display: none;"> 33 </div> 34 <input type="button" class="btn btn-primary pull-right" id="id_button" value="提交"> 35 </form> 36 </div> 37 </div> 38 </div> 39 40 <script> 41 $(‘#id_myfile‘).change(function () { 42 // 获取当前用户上传到的文件对象 43 var myfileObj = $(this)[0].files[0]; 44 // 需要用文件阅读器这个内置对象 45 var fileReader = new FileReader(); 46 // 将文件对象丢给文件阅读器 47 fileReader.readAsDataURL(myfileObj); 48 // 将文件对象放入img标签的src属性中 49 // 当文件对象全部加载完毕再渲染 50 fileReader.onload = function(){ 51 $(‘#id_img‘).attr(‘src‘,fileReader.result); 52 } 53 }); 54 $(‘#id_button‘).click(function () { 55 var formData = new FormData(); 56 {#console.log($(‘#myform‘).serializeArray()); 自动获取form表单中所有input框键值对#} 57 $.each($(‘#myform‘).serializeArray(),function (index,obj) { 58 {#console.log(index,obj) 知识添加了普通的键值对,文件对象需要你手动添加#} 59 formData.append(obj.name,obj.value) 60 }); 61 {#手动添加文件对象#} 62 formData.append(‘myfile‘,$(‘#id_myfile‘)[0].files[0]); 63 $.ajax({ 64 url:‘‘, 65 type:‘post‘, 66 data:formData, 67 // 用formdata传数据的时候需要指定两个参数 68 processData:false, 69 contentType:false, 70 success:function (data) { 71 if(data.code == 100){ 72 location.href = data.url 73 }else{ 74 $.each(data.msg,function (index,obj) { 75 // 手动拼接处forms组件渲染的input的id值 id_字段的特点 76 var targetId = ‘#id_‘ + index; 77 $(targetId).next().html(obj[0]).parent().parent().addClass(‘has-error‘) 78 }) 79 } 80 } 81 }) 82 }); 83 $(‘input‘).focus(function () { 84 $(this).next().html(‘‘).parent().parent().removeClass(‘has-error‘) 85 }) 86 87 </script> 88 </body> 89 </html>
1 from django.shortcuts import render 2 from app01 import myforms 3 from app01 import models 4 from django.http import JsonResponse 5 # Create your views here. 6 def register(request): 7 back_dic = {‘code‘:100,‘msg‘:‘‘} 8 form_obj = myforms.MyForm() 9 if request.method == ‘POST‘: 10 form_obj = myforms.MyForm(request.POST) 11 if form_obj.is_valid(): 12 data = form_obj.cleaned_data 13 # 将confirm_password去掉 14 data.pop(‘confirm_password‘) 15 # 获取用户上传的文件对象 16 file_obj = request.FILES.get(‘myfile‘) 17 # 判断用户是否上传了自己的头像 18 if file_obj: 19 # 往data添加一组键值 20 data[‘avatar‘] = file_obj 21 models.UserInfo.objects.create_user(**data) 22 back_dic[‘msg‘] = ‘注册成功‘ 23 back_dic[‘url‘] = ‘/login/‘ 24 else: 25 back_dic[‘code‘] = 101 26 back_dic[‘msg‘] = form_obj.errors 27 return JsonResponse(back_dic) 28 return render(request,‘register.html‘,locals())
原文:https://www.cnblogs.com/huangxuanya/p/11054938.html