BBS项目练习
此项目是对博客园软件的简单模仿,旨在整合django的知识点。
七张信息表
- 用户表(与站点一对一)
- 站点表
- 文章表(与站点一对多)
- 文章分类表
- 文章标签表
- 点赞点踩表
- 评论表
建立以上七张表的模型表:
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class UserInfo(AbstractUser):
"""用户信息表:继承了auth类"""
tel = models.CharField(verbose_name='电话号码', max_length=5) # 5位号码模拟
avatar = models.FileField(verbose_name='用户头像', upload_to='avatar/', default='avatar/默认头像.png', )
register_time = models.DateField(verbose_name='注册时间', auto_now_add=True)
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
class Site(models.Model):
"""个人站点表"""
name = models.CharField(verbose_name='站点名称', max_length=16)
title = models.CharField(verbose_name='站点标题', max_length=16)
theme = models.TextField(verbose_name='站点样式') # 这里简单模拟一下存储样式
class Article(models.Model):
"""文章表"""
title = models.CharField(verbose_name='文章标题', max_length=32)
brief = models.CharField(verbose_name='文章简介', max_length=100)
content = models.TextField(verbose_name='文章内容')
load_time = models.DateTimeField(verbose_name='发布时间', auto_now_add=True)
# 文章字段扩展(需要代码层面的自动更新)
comments_num = models.IntegerField(verbose_name='评论数', default=0)
like_num = models.IntegerField(verbose_name='点赞数', default=0)
unlike_num = models.IntegerField(verbose_name='点踩数', default=0)
read_num = models.IntegerField(verbose_name='阅读数', default=0)
# 文章外键字段
site = models.ForeignKey(to='Site', on_delete=models.CASCADE)
article_class = models.ForeignKey(to='ArticleClass', on_delete=models.CASCADE)
tag = models.ManyToManyField(to='ArticleTag')
class ArticleClass(models.Model):
"""文章分类表"""
# 文章分类一对多文章
name = models.CharField(verbose_name='分类名称', max_length=10)
# 也是从属于站点的
site = models.ForeignKey(to='Site', on_delete=models.CASCADE)
class ArticleTag(models.Model):
"""文章标签表"""
# 文章标签多对多文章
tag = models.CharField(verbose_name='文章标签', max_length=8)
# 也是从属于站点的
site = models.ForeignKey(to='Site', on_delete=models.CASCADE)
class PointLike(models.Model):
"""点赞点踩表"""
# 外键字段
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE)
article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
# 某用户对某文章的推荐态度
up_or_down = models.BooleanField(verbose_name='点赞或点踩', null=True)
class Comment(models.Model):
"""评论表"""
# 外键字段
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE)
article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
# 某用户对某文章的评论信息
context = models.CharField(verbose_name='评论内容', max_length=100)
comment_time = models.DateTimeField(verbose_name='评论时间', auto_now_add=True)
parent = models.ForeignKey(to='self', verbose_name='根评论', on_delete=models.CASCADE) # 自关联
注册功能
此次尝试通过ajax请求来完成提交表单,注意视图层和模板层的变化
视图层
视图层在get请求过来时会返回一个纯净的表单界面,而在post请求过来时会进行一系列的校验,并返回一个可能有含错误信息的表单界面。
def register(request):
register_obj = myforms.RegisterForm() # 将表单类直接单独存放到另外的py文件
if request.method == 'POST':
# 用信息的表单类
register_obj = myforms.RegisterForm(request.POST)
# ajax请求通常以json格式的自定义对象(字典)通信
back_dict = {}
if register_obj.is_valid():
# 如果表单数据符合要求,处理数据,并完成注册
back_dict['code'] = 10000 # 自定义状态码
cleaned_data = register_obj.cleaned_data
cleaned_data.pop('confirm_password')
# 如果没有那就不给这个生成这个键,创建数据时按默认头像。
if request.FILES.get('avatar'):
cleaned_data['avatar'] = request.FILES.get('avatar')
models.UserInfo.objects.create_user(**cleaned_data)
back_dict['url'] = reverse('login')
back_dict['msg'] = '注册成功'
else:
# 否则返回错误信息
back_dict['code'] = 10001
back_dict['msg'] = register_obj.errors
return JsonResponse(back_dict)
return render(request, 'registerPage.html', locals())
模板层
主要实现了以下内容:
- 渲染标签
- 提交表单的ajax请求,并处理后端返回过来的信息
- 如果是注册成功的状态码,跳转到指定界面
- 如果是其他状态码,说明错误,将错误信息渲染到界面上
- 当用户上传头像图片时,实时将其渲染到界面上(纯前端)
- 当用户聚焦到输入框时,将错误信息及样式移除(纯前端)
<div class="container">
<div class="col-md-6 col-md-offset-3">
<!--表单标签-->
<form id="regi_form" action="" method="post">
<!--csrf校验-->
{% csrf_token %}
<!--循环生成form组件的input标签-->
{% for regi in register_obj %}
<div class="form-group">
<label for="{{ regi.auto_id }}">{{ regi.label }}</label>
{{ regi }}
<span style="color: red" class="pull-right"></span>
</div>
{% endfor %}
<!--头像提交是单独的-->
<div class="form-group">
<label for="avatar">头像
<img id="myimg" src="{% static '默认头像.png' %}"
style="width:120px; box-shadow: 5px 5px 5px gray; border-radius: 50%"
alt="">
</label>
<input type="file" id="avatar" name="avatar" value="{% static '默认头像.png' %}" style="display: none">
<!--提交按钮-->
</div>
<div class="form-group">
<input type="button" id="regi_submit" value="提交" class="btn btn-success form-control">
</div>
</form>
</div>
</div>
<!--js代码-->
<script>
// 注册按钮发送ajax
$('#regi_submit').click(function () {
let newFormObj = new FormData()
let regiObj = $('#regi_form').serializeArray()
console.log(regiObj)
$.each(regiObj, function (index, regiObj) {
newFormObj.append(regiObj.name, regiObj.value)
})
newFormObj.append('avatar', $('#avatar')[0].files[0])
$.ajax({
url: '',
type: 'post',
data: newFormObj,
contentType: false,
processData: false,
success: function (args) {
if (args.code === 10000){
window.location.href = args.url
}else{
let eleErrors = args.msg
$.each(eleErrors,function (key, errorsList) {
$('#id_' + key).next().text(errorsList[0]).parent().addClass('has-error')
}) // each
} //else
} // success
}) // ajax
}) // click
// 上传图片文件时实时展示到页面上
$('#avatar').change(function () {
let fileReaderObj = new FileReader()
fileReaderObj.readAsDataURL(this.files[0])
fileReaderObj.onload = function () {
$('#myimg').attr('src', fileReaderObj.result)
}
})
// 聚焦时,移除错误信息
$('input').focus(function () {
$(this).next().text('').parent().removeClass('has-error')
})
</script>
表单类
表单类中除了帮助我们快速生成标签,也可以单独的添加widget参数来修改属性,包括class属性,所以可以利用这一点修改样式。
class RegisterForm(forms.Form):
username = forms.CharField(min_length=3, max_length=8, label='用户名',
widget=forms.widgets.TextInput(
attrs={
'class': 'form-control'
}
)
)
password = forms.CharField(min_length=3, max_length=8, label='密码',
widget=forms.widgets.PasswordInput(
attrs={
'class': 'form-control'
}
)
)
confirm_password = forms.CharField(min_length=3, max_length=8, label='确认密码',
widget=forms.widgets.PasswordInput(
attrs={
'class': 'form-control'
}
)
)
email = forms.EmailField(label='邮箱', widget=forms.widgets.EmailInput(
attrs={
'class': 'form-control'
}
))
tel = forms.CharField(label='电话', required=False,
validators=[
RegexValidator('^1[0-9]{4}$', '号码必须是1开头的5位数字'),
],
widget=forms.widgets.TextInput(
attrs={
'class': 'form-control'
}
)
)
# 钩子函数
def clean_username(self):
username = self.cleaned_data.get('username')
user_obj = models.UserInfo.objects.filter(username=username)
if user_obj:
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
标签:verbose,项目,models,练习,length,CharField,BBS,class,name
From: https://www.cnblogs.com/Leethon-lizhilog/p/17020574.html