数据库设计的时候要注意一定,用户表是直接继承auth模块的,所以需要以下的操作
#在models文件中
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
pass
#然后再setitings中配置
#AUTH_USER_MODEL = "app名.models里面对应的模型表名"
AUTH_USER_MODEL = 'app01.Userinfo'
数据库中的是mysql 所以需要将数据库设置为mysql
#在settings中修改DATABASES中的配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',#设置为mysql数据库
'NAME': 'bbs',#数据库的名字
'HOST':'127.0.0.1',#主机号
"PORT":3306,#端口号
'USER':'root',#用户名
'PASSWORD':'123456'#密码
}
}
#然后再__init__.py中配置如下
import pymysql
pymysql.install_as_MySQLdb()
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class UserInfo(AbstractUser):
phone=models.BigIntegerField(null=True,blank=True)
create_time=models.DateField(auto_now_add=True)
avatar=models.FileField(upload_to='avatar/',default='avatar/1.jpg')
#用户表和个人站点 一对一
blog=models.OneToOneField(to='Blog',null=True)
class Meta:
verbose_name_plural='用户表'
#个人站点 博客
class Blog(models.Model):
site_name=models.CharField(max_length=32)
site_title=models.CharField(max_length=32)
#存css样式文件的路径
theme=models.CharField(max_length=32)
def __str__(self):
return self.site_name
#分类
class Category(models.Model):
name=models.CharField(max_length=32)
#分类和个人博客是一对多的关系
blog=models.ForeignKey(to='Blog')
def __str__(self):
return self.name
#标签
class Tag(models.Model):
name=models.CharField(max_length=32)
#标签和个人博客也是一对多的关系
blog=models.ForeignKey(to='Blog')
def __str__(self):
return self.name
#文章表
class Article(models.Model):
title=models.CharField(max_length=32)
desc=models.CharField(max_length=256)
#存大段文本
content=models.TextField()
create_time=models.DateField(auto_now_add=True)
#文章的评论数 点赞书 点踩数
comment_num=models.IntegerField(default=0)
up_num=models.IntegerField(default=0)
down_num=models.IntegerField(default=0)
#文章和个人博客是一对多
#和分类是一对多
#和标签是多对多
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'))
def __str__(self):
return self.title
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()
class Commet(models.Model):
user=models.ForeignKey(to='UserInfo')
article=models.ForeignKey(to='Article')
content=models.CharField(max_length=120)
parent=models.ForeignKey(to='self',null=True)
create_time=models.DateTimeField(auto_now_add=True,null=True)
#在这里存放的都是系统要用到的文件 比如前端页面的图片 css样式等
#先在总文件下创建一个static文件夹
#然后再settings中进行配置
STATIC_URL = '/static/'
# 静态文件配置
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static')
]
#这个文件夹主要放的是用户上传的资源 这也就意味着开放了这个文件夹的接口,可以在网上直接访问到这个文件夹,所有开放需要谨慎
#首先 需要再settings中进行配置
#指定用户上传的静态文件储存位置
MEDIA_ROOT=os.path.join(BASE_DIR,'media')
#然后再urls.py中
from django.views.static import serve
from BBS import settings#这个BBS是项目的名字
url(r'^media/(?P.*)' , serve, {'document_root': settings.MEDIA_ROOT}),
##这种暴露给用户服务器资源的方法适用于所有的后端文件 所有在配置的时候慎重!!
#为了方便在admin后台添加数据,需要将想在后台操作的表放在admin.py中
#可以将用户表名现实为自己定义的 也可以将显示的对象改为显示名字,这些都需要在models中配置,具体看上面的models
from django.contrib import admin
from app01 import models
# Register your models here.
admin.site.register(models.UserInfo)
admin.site.register(models.Article)
admin.site.register(models.Article2Tag)
admin.site.register(models.Tag)
admin.site.register(models.Blog)
admin.site.register(models.Commet)
admin.site.register(models.UpAndDown)
admin.site.register(models.Category)
注册主要用到了forms组件 方便信息的校验 以及头像上传
注册页面
更换头像
#在应用下创建一个py文件 例(myforms.py)
#这里要注意的是email有一个invalid,用来判断邮箱的格式
from app01 import models
from django import forms
class MyForms(forms.Form):
username=forms.CharField(max_length=8,min_length=3,label='用户名',error_messages={
'max_length':'用户名最长8位',
'min_length':'用户名最短3位',
'required':'用户名不能为空'
},widget=forms.TextInput(attrs={'class':'form-control'}))
password=forms.CharField(max_length=8,min_length=3,label='密码',error_messages={
'max_length':'密码最长8位',
'min_length':'密码最短3位',
'required':'密码不能为空'
},widget=forms.PasswordInput(attrs={'class':'form-control'}))
c_password = forms.CharField(max_length=8, min_length=3,label='确认密码', error_messages={
'max_length': '确认密码最长8位',
'min_length': '确认密码最短3位',
'required': '确认密码不能为空'
}, widget=forms.PasswordInput(attrs={'class':'form-control'}))
email=forms.EmailField(label='邮箱',error_messages={
'required':'邮箱不能为空',
'invalid':'邮箱的格式不对'
},widget=forms.EmailInput(attrs={'class': 'form-control'}))
forms组件的error还不是太完善 比如不能判断用户名是否存在,两次密码是否相同,所以需要使用forms组件的钩子函数
#钩子函数分为局部钩子和全局钩子
# 局部钩子判断用户名是否重复
def clean_username(self):
username=self.cleaned_data.get('username')
user=models.UserInfo.objects.filter(username=username)
if user:
self.add_error('username','用户已存在')
else:
return username
#全局钩子判断2次密码是否一样
def clean(self):
password=self.cleaned_data.get('password')
c_password=self.cleaned_data.get('c_password')
if password==c_password:
return self.cleaned_data
else:
self.add_error('c_passward','两次密码不一样')
<form id="id_form" class="form-group">
{% csrf_token %}
{% for foo in Myforms %}
<label for="{{ foo.auto_id }}">{{ foo.label }}label>
{{ foo }}
<span id="id_span" class="error pull-right" style="color:red;">span>
{% endfor %}
form>
<div class="form-group">
<label for="id_myfile">头像
<img src="/static/img/1.jpg" alt="" width="80" name="img" id="id_img">
label>
<input type="file" name="myfile" id="id_myfile" class="hidden">
div>
<input type="button" value="提交" name="button" id="id_button" class="btn btn-primary">
//这里要注意的是 文件上传的速度慢于代码执行的速度 所以说要等文件上传完再执行下面的代码 所以用onload
$('#id_myfile').change(function () {
var myfile = $(this)[0].files[0];
//文件阅读器
var fileread = new FileReader();
fileread.readAsDataURL(myfile);
//读取完毕
fileread.onload = function () {
$('#id_img').attr('src', fileread.result)
}
});
//传输数据
$('#id_button').click(function () {
var formData = new FormData();
$.each($('#id_form').serializeArray(), function (index, obj) {
formData.append(obj.name, obj.value);
});
//把图片存进去
formData.append('myfile',$('#id_myfile')[0].files[0])
//ajax 传文件
$.ajax({
url:'',
type:'post',
data:formData,
processData: false,
contentType:false,
//回调
success:function (data) {
if(data.code==100){
location.href='/login'
}
else {
$.each(data.msg,function (index,obj) {
var tag='#id_'+index;
//这个是为了错误提示
$(tag).next().html(obj[0]).parent().parent().addClass('has-error')
})
}
}
})
})
def register(request):
response_msg = {'code': 100, 'msg': ''}
Myforms = myforms.MyForms()
if request.method == 'POST':
Myforms = myforms.MyForms(request.POST)
#form组件校验通过
if Myforms.is_valid():
cleaned_data = Myforms.cleaned_data
#删除确认密码,因为数据库没有这个字段
cleaned_data.pop('c_password')
avatar = request.FILES.get('myfile')
#如果上传了头像,将头像加入到字典中
if avatar:
cleaned_data['avatar'] = avatar
models.UserInfo.objects.create_user(**cleaned_data)
response_msg['msg'] = '登录成功'
else:
response_msg['code'] = 101
response_msg['msg'] = Myforms.errors
return JsonResponse(response_msg)
return render(request, 'register.html', locals())
登录页面
修改密码
修改头像
注销
<form>
<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="id_yzm">验证码label>
<input type="text" name="yzm" id="id_yzm">
<img src="/yzm/" alt="" style="width: 280px;height: 35px" name="img" id="id_img">
div>
<div>
<input type="button" value="提交" name="button" id="id_button">
<span id="id_span" style="color:red;">span>
div>
form>
//点击图片的时候,让图片后面加上一个?,就可以重新加载一次图片
$('#id_img').click(function () {
var a = $(this).attr('src');
$(this).attr('src', a += '?')
})
$('#id_button').click(function () {
$.ajax({
url: '',
type: 'post',
data: {
'username': $('#id_username').val(),
'password': $('#id_password').val(),
'code': $('#id_yzm').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success:function (data) {
if (data.code==100){
location.href=data.url
}
else {
$('#id_span').html(data.msg)
}
}
})
})
import random
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
#获取数据数(255,255,255)
def get_random():
return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
# 获取验证码
def yzm(request):
img=Image.new('RGB',(280,35),get_random())
img_draw=ImageDraw.Draw(img)#生成一个画笔,路由在img图片上为所欲为
img_font=ImageFont.truetype('static/font/xxoo.ttf',40)#定义字体样式
#写验证码
code=''
for i in range(5):
random_num=str(random.randint(0,9))
random_upper=str(chr(random.randint(65,90)))
random_lower=str(chr(random.randint(97,122)))
#随机一个字符写入图片
random_code=random.choice([random_num,random_upper,random_lower])
img_draw.text((45+i*45,-10),random_code,get_random(),img_font)
code+=random_code
print(code)
io_obj=BytesIO()
#将验证码记录下来以后以便后续比对 存入session以便后续比对
img.save(io_obj,'png')
request.session['code'] = code
return HttpResponse(io_obj.getvalue())
def login(request):
response_msg = {'code': 100, 'msg': ''}
if request.method == "POST":
username = request.POST.get('username')
password = request.POST.get('password')
code = request.POST.get("code")
#将验证码和自己输入的验证码全部大写比较
if code.upper() == request.session.get('code').upper():
#auth认证用户登录
user = auth.authenticate(username=username, password=password)
if user:
#把用户信息存到session中
auth.login(request, user)
response_msg['msg'] = '登录成功'
response_msg['url'] = '/index/'
else:
response_msg['code'] = 101
response_msg['msg'] = '用户名密码错误'
else:
response_msg['code'] = 102
response_msg['msg'] = '验证码错误'
return JsonResponse(response_msg)
return render(request, 'login.html', locals())
def zhuxiao(request):
#相当于request.session.flush()
auth.logout(request)
return redirect('/index/')
def set_password(request):
response_msg = {'code': 100, 'msg': ''}
if request.method == "POST":
password = request.POST.get('password')
xpassword = request.POST.get('xpassword')
cxpassword = request.POST.get('cxpassword')
# 检查原密码
if request.user.check_password(password):
# 修改密码
if xpassword == cxpassword:
request.user.set_password(xpassword)
zhuxiao(request)
return render(request, 'login.html', locals())
else:
response_msg['msg'] = '两次密码不一样'
return render(request, 'set_password.html', locals())
else:
response_msg['msg'] = '原密码错误'
return render(request, 'set_password.html', locals())
return render(request, 'set_password.html', locals())
from django.views.decorators.csrf import csrf_exempt
#装饰器 不用csrf校验
@csrf_exempt
#换头像一定要先取出对象,然后更新,然后save()
def set_avatar(request):
if request.method == "POST":
myfile = request.FILES.get('myfile')
user = request.user
user.avatar = myfile
user.save()
return redirect('/index/')
return render(request, 'set_avatar.html', locals())
主页界面
{% for article in article_list %}
<div class="media">
<h4 class="media-heading">{{ article.title }}h4>
<div class="media-left media-middle">
<a href="#">
<img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" alt="..." width="60">
a>
div>
<div class="media-body">
{{ article.desc }}
div>
<span><a href="">{{ article.blog.userinfo.username }}a>span>
<span>发布时间 {{ article.create_time|date:'Y-m-d ' }}span>
<span>评论数({{ article.comment_num }})span>
<span>评论数({{ article.up_num }})span>
div>
{% endfor %}
def index(request):
article_list = models.Article.objects.all()
return render(request, 'index.html', locals())
个人站点主界面
分类归档页
标签归档页
时间归档页
#在应用下创建一个templatetags文件夹
#然后创建一个py文件(zidingyi.py)
#必写的两句
from django import template
register = template.Library()
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth
@register.inclusion_tag('left.html')
def left_html(username):
user = models.UserInfo.objects.filter(username=username).first()
blog = models.Blog.objects.filter(userinfo__username=username).first()
article = models.Article.objects.filter(blog=blog).all()
# 分类
category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c',
'pk')
# 标签
tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c', 'pk')
# 日期
time_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
'month').annotate(c=Count('id')).values_list('month', 'c')
return {'username':username,'category_list':category_list,'tag_list':tag_list,'time_list':time_list}
category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c',
'pk')
# 标签
tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c', 'pk')
from django.db.models import Count
from django.db.models.functions import TruncMonth
time_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
'month').annotate(c=Count('id')).values_list('month', 'c')
<h3 class="panel-title">分类归档h3>
{% for category in category_list %}
<p>
<a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }}a>({{ category.1 }})
p>
{% endfor %}
<h3 class="panel-title">标签归档h3>
{% for tag in tag_list %}
<p>
<a href="/{{ username }}/tag/{{ tag.2 }}"> {{ tag.0 }}a>({{ tag.1 }})
p>
{% endfor %}
<h3 class="panel-title">日期归档h3>
{% for time in time_list %}
<p>
<a href="/{{ username }}/time/{{ time.0|date:'Y-m' }}">{{ time.0|date:'Y-m' }}a>({{ time.1 }})
p>
{% endfor %}
自定义inclusion_tag-使用
{% load zidingyi %}
{% left_html username %}
# 个人站点
# url(r'^(?P.*)/(?Pcategory|tag|time)/(?P.*)/$',views.site)
# url(r'^(?P\w+)/$', views.site)
#对一个url分类后的视图层路径,第二个是主页的视图层路径
def site(request, username, *args, **kwargs):
# print(username)
user = models.UserInfo.objects.filter(username=username).first()
blog = models.Blog.objects.filter(userinfo__username=username).first()
article = models.Article.objects.filter(blog=blog).all()
if kwargs:
# print(kwargs)
conditon = kwargs.get('condition')
# print(conditon)
definite = kwargs.get('definite')
if conditon == 'category':
article = article.filter(category_id=definite)
elif conditon == 'tag':
article = article.filter(article2tag__tag_id=definite)
elif conditon == 'time':
year, month = definite.split('-')
article = article.filter(create_time__year=year, create_time__month=month)
return render(request, 'site.html', locals())
文章详情
点赞点踩
评论
<h3 class="text-center">{{ article.title }}h3>
<hr>
{{ article.content|safe }}
<div class="clearfix">
<div id="div_digg">
<div class="diggit action">
<span class="diggnum" id="digg_count">{{ article.up_num }}span>
div>
<div class="buryit action">
<span class="burynum" id="bury_count">{{ article.down_num }}span>
div>
<div class="clear">div>
<div class="diggword" id="digg_tips">
<span class="error" style="color: red">span>
div>
div>
div>
<div class="list-group">
评论列表
<hr>
{% for commet in commet_list %}
<span><a>{{ forloop.counter }}楼a>span>
<span>{{ commet.create_time|date:'Y-m-d h-i-s' }}span>
<span>{{ commet.user.username }}span>
<a class="reply pull-right" comment_pk="{{ commet.user.pk }}" username="{{ commet.user.username }}">回复a>
<div>
{% if commet.parent %}
<p><a>@a>{{ commet.parent.user.username }}p>
{% endif %}
{{ commet.content }}
div>
<hr>
{% endfor %}
div>
<div>
发表评论
<p>
<p>
昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"
value="{{ request.user.username }}">
p>
p>
<p>评论内容:p>
<textarea name="content" id="id_content" cols="60" rows="10">textarea>
<p><input type="button" value="提交评论" name="button" id="id_button">p>
div>
$('.action').click(function () {
var is_up = $(this).hasClass('diggit');
{#alert(is_up)#}
$.ajax({
url: '',
type: 'post',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'is_up': is_up,
'article_id':{{ article.pk }}
},
success: function (data) {
if (data.code == 100) {
$('#digg_count').text(Number({{ article.up_num }}+1))
$(".error").text(data.msg)
} else {
$(".error").text(data.msg)
}
}
})
})
//点击回复
parentid = '',
$('.reply').click(function () {
var username = $(this).attr('username');
$('#id_content').val('@' + username + '\n').focus()
parentID = $(this).attr('id_content')
})
//评论
$('#id_button').click(function () {
var username = '{{ request.user.username }}'
var content = $('#id_content').val()
var user_id = {{ request.user.pk }}
$.ajax({
url: '/commet/',
type: 'post',
data: {
'username': username,
'content': content,
'user_id': user_id,
'article_id':{{ article.pk }},
'parentid': parentid,
},
success: function (data) {
//模板替换
temp_str = `
${username}
${content}
`;
$('.list-group').append(temp_str);
$('#id_content').val('')
parentid=''
}
})
})
#添加评论
@csrf_exempt
def commet(request):
response_msg={'code':100,'msg':''}
print(request.POST)
if request.method=='POST':
username=request.POST.get('username')
content=request.POST.get('content')
user_id=request.POST.get('user_id')
article_id=request.POST.get('article_id')
parent_id=request.POST.get('parent_id')
with transaction.atomic():
models.Commet.objects.create(content=content,article_id=article_id,parent_id=parent_id,user_id=user_id)
models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num')+1)
return HttpResponse(123)
#点赞点踩
#url(r'^(?P\w+)/article/(?P\w+)', views.article),
#视图层路径
from django.db import transaction
def article(request,username,article_id):
response_msg={'code':100,"msg":''}
user = models.UserInfo.objects.filter(username=username).first()
blog = models.Blog.objects.filter(userinfo__username=username).first()
article = models.Article.objects.filter(pk=article_id).first()
commet_list=models.Commet.objects.filter(article_id=article_id).all()
# print(commet_list)
if request.method=='POST':
#判断用户是否登录
if request.user.is_authenticated():
up_down=json.loads(request.POST.get('is_up'))
name=request.POST.get('username')
#判断是否是当前用户
if name==username:
response_msg['code']=102
response_msg['msg']='不要给自己点赞啊 大兄弟'
# return JsonResponse(response_msg)
else:
obj=models.UpAndDown.objects.filter(article_id=article_id,user=request.user)
#判断是否点过赞了
if obj:
response_msg['code']=103
response_msg['msg']='你已经点过赞了'
# return JsonResponse(response_msg)
else:
# 开启事务
with transaction.atomic():
#判断是不是点赞
if up_down:
models.UpAndDown.objects.create(is_up=up_down,article_id=article_id,user=request.user)
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num')+1)
response_msg['msg']='点赞成功'
#点赞
else:
models.UpAndDown.objects.create(is_up=up_down,article_id=article_id,user=request.user)
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num')+1)
response_msg['msg']='点踩成功'
# return JsonResponse(response_msg)
#没有登录 清先登录
else:
response_msg['code']=101
response_msg['msg']="请先登录"
return JsonResponse(response_msg)
后台界面
添加文章界面
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>添加文章p>
<p>标题p>
<input type="text" class="form-control" name="title">
<p>摘要p>
<input type="text" class="form-control" name="desc">
<p>内容(kindeditor编辑器编辑器,支持拖放/粘贴上传图片)p>
<p><textarea name="content" id="id_content" cols="80" rows="10">textarea>p>
<p><input type="submit" value="发布" class="btn btn-primary">p>
form>
<script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#id_content',{
width:'100%' ,
height:'400px',
themeType:0,
//上传文件路径
uploadJson:'/upload_img/',
//携带额外参数
extraFileUploadParams:{
'csrfmiddlewaretoken':"{{ csrf_token }}"
}
});
});
</script>
def houtai(request):
article=models.Article.objects.filter(blog__userinfo=request.user)
return render(request,'houtai/houtai.html',locals())
#利用bs4放置xss攻击
from bs4 import BeautifulSoup
def add(request):
username=request.user.username
blog = models.Blog.objects.filter(userinfo__username=username).first()
if request.method=="POST":
# print(request.POST)
content=request.POST.get('content')
desc=request.POST.get('desc')
title=request.POST.get('title')
soup=BeautifulSoup(content,'html.parser')#实例化一个对象
tags=soup.find_all()#内容中所有的标签
for tag in tags:
#找到script标签
if tag.name=='script':
#去掉script标签 防止xss攻击
tag.decompose()
models.Article.objects.create(title=title, content=str(soup), desc=desc, blog=blog)
return redirect('/houtai/')
return render(request,'houtai/add.html',locals())
def upload_img(request):
#这是官方文档固定格式
# // 成功时
# {
# "error": 0,
# "url": "http://www.example.com/path/to/file.ext"
# }
# // 失败时
# {
# "error": 1,
# "message": "错误信息"
# }
response_msg={'error':'','message':''}
if request.method=='POST':
file_obj=request.FILES.get('imgFile')
if file_obj:
path=os.path.join(settings.BASE_DIR,'media','article_img')
if not os.path.exists(path):
os.mkdir(path)
file_path=os.path.join(path,file_obj.name)
with open(file_path,'wb') as f:
for line in file_obj:
f.write(line)
response_msg['error'] = 0
response_msg['url'] = '/media/article_img/%s' % file_obj.name
else:
response_msg['error'] = 1
response_msg['message'] = '文件不存在'
return JsonResponse(response_msg)
return HttpResponse(123)