from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
phone = models.BigIntegerField(verbose_name='手机号',null=True,blank=True)
"""
null=True 数据库该字段可以为空
blank=True admin后台管理该字段可以为空
"""
# 头像
avatar = models.FileField(upload_to='avatar/',default='avatar/default.png',verbose_name='用户头像')
"""
给avatar字段传文件对象 该文件会自动存储到avatar文件下 然后avatar字段只保存文件路径avatar/default.png
"""
create_time = models.DateField(auto_now_add=True)
blog = models.OneToOneField(to='Blog',null=True)
class Meta:
verbose_name_plural = '用户表' # 修改admin后台管理默认的表名
# verbose_name = '用户表' # 末尾还是会自动加s
def __str__(self):
return self.username
comment_time = models.DateTimeField(verbose_name='评论时间',auto_now_add=True)
# 自关联
parent = models.ForeignKey(to='self',null=True) # 有些评论就是根评论
from django.views.static import serve
url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT}),
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# 'NAME': 'bbs14',
# 'USER':'root',
# 'PASSWORD':"admin123",
# 'HOST':'127.0.0.1',
# 'PORT':3306,
# 'CHARSET':'utf8'
}
}
LANGUAGE_CODE = 'en-us'
# TIME_ZONE = 'Asia/Shanghai'
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
AUTH_USER_MODEL = 'app01.UserInfo'
LOGIN_URL = '/login/'
# 配置用户上传的文件存储位置
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 文件名 随你 自己
url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)/',views.site),
# 文章详情页
url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/',views.article_detail),
admin.site.register(models.UserInfo)
class Pagination(object): def __init__(self, current_page, all_count, per_page_num=10, pager_count=11): """ 封装分页相关数据 :param current_page: 当前页 :param all_count: 数据库中的数据总条数 :param per_page_num: 每页显示的数据条数 :param pager_count: 最多显示的页码个数 """ try: current_page = int(current_page) except Exception as e: current_page = 1 if current_page < 1: current_page = 1 self.current_page = current_page self.all_count = all_count self.per_page_num = per_page_num # 总页码 all_pager, tmp = divmod(all_count, per_page_num) if tmp: all_pager += 1 self.all_pager = all_pager self.pager_count = pager_count self.pager_count_half = int((pager_count - 1) / 2) @property def start(self): return (self.current_page - 1) * self.per_page_num @property def end(self): return self.current_page * self.per_page_num def page_html(self): # 如果总页码 < 11个: if self.all_pager <= self.pager_count: pager_start = 1 pager_end = self.all_pager + 1 # 总页码 > 11 else: # 当前页如果<=页面上最多显示11/2个页码 if self.current_page <= self.pager_count_half: pager_start = 1 pager_end = self.pager_count + 1 # 当前页大于5 else: # 页码翻到最后 if (self.current_page + self.pager_count_half) > self.all_pager: pager_end = self.all_pager + 1 pager_start = self.all_pager - self.pager_count + 1 else: pager_start = self.current_page - self.pager_count_half pager_end = self.current_page + self.pager_count_half + 1 page_html_list = [] # 添加前面的nav和ul标签 page_html_list.append(''' <nav aria-label='Page navigation>' <ul class='pagination'> ''') first_page = '<li><a href="?page=%s">首页</a></li>' % (1) page_html_list.append(first_page) if self.current_page <= 1: prev_page = '<li class="disabled"><a href="#">上一页</a></li>' else: prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,) page_html_list.append(prev_page) for i in range(pager_start, pager_end): if i == self.current_page: temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,) else: temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,) page_html_list.append(temp) if self.current_page >= self.all_pager: next_page = '<li class="disabled"><a href="#">下一页</a></li>' else: next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,) page_html_list.append(next_page) last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,) page_html_list.append(last_page) # 尾部添加标签 page_html_list.append(''' </nav> </ul> ''') return ''.join(page_html_list)
class MyRegForm(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'})
)
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
from django import template from app01 import models from django.db.models import Count from django.db.models.functions import TruncMonth register = template.Library() # 自定义inclusion_tag @register.inclusion_tag('left_menu.html') def left_menu(username): # 构造侧边栏需要的数据 user_obj = models.UserInfo.objects.filter(username=username).first() blog = user_obj.blog # 1 查询当前用户所有的分类及分类下的文章数 category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list( 'name', 'count_num', 'pk') # print(category_list) # <QuerySet [('jason的分类一', 2), ('jason的分类二', 1), ('jason的分类三', 1)]> # 2 查询当前用户所有的标签及标签下的文章数 tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name', 'count_num', 'pk') # print(tag_list) # <QuerySet [('tank的标签一', 1), ('tank的标签二', 1), ('tank的标签三', 2)]> # 3 按照年月统计所有的文章 date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values( 'month').annotate(count_num=Count('pk')).values_list('month', 'count_num') # print(date_list) return locals()
{% load mytag %} {% left_menu username %}
values().anoonate() 按照values分组
{% for category in category_list %}
<p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }}({{ category.1 }})</a></p>
{% endfor %}
<div class="panel-body">
{% for date in date_list %}
<p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}">{{ date.0|date:'Y年m月' }}({{ date.1 }})</a></p>
{% endfor %}
</div>
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="/media/css/{{ blog.site_theme }}/">
{% for form in form_obj %}
<div class="form-group">
<label for="{{ form.auto_id }}">{{ form.label }}</label>
{{ form }}
<span style="color: red" class="pull-right"></span>
</div>
{% endfor %}
<label for="myfile">头像
{% load static %}
<img src="{% static 'img/default.png' %}" id='myimg' alt="" width="100" style="margin-left: 10px">
</label>
<input type="file" id="myfile" name="avatar" style="display: none" >
</div>
$("#myfile").change(function () {
// 文件阅读器对象
// 1 先生成一个文件阅读器对象
let myFileReaderObj = new FileReader();
// 2 获取用户上传的头像文件
let fileObj = $(this)[0].files[0];
// 3 将文件对象交给阅读器对象读取
myFileReaderObj.readAsDataURL(fileObj) // 异步操作 IO操作
// 4 利用文件阅读器将文件展示到前端页面 修改src属性
// 等待文件阅读器加载完毕之后再执行
myFileReaderObj.onload = function(){
$('#myimg').attr('src',myFileReaderObj.result)
}
})
$('#id_commit').click(function () {
// 发送ajax请求 我们发送的数据中即包含普通的键值也包含文件
let formDataObj = new FormData();
// 1.添加普通的键值对
{#console.log($('#myform').serializeArray()) // [{},{},{},{},{}] 只包含普通键值对#}
$.each($('#myform').serializeArray(),function (index,obj) {
{#console.log(index,obj)#} // 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) {
{#console.log(index,obj) // username ["用户名不能为空"]#}
let targetId = '#id_' + index;
$(targetId).next().text(obj[0]).parent().addClass('has-error')
})
}
}
})
})
// 给所有的input框绑定获取焦点事件
$('input').focus(function () {
// 将input下面的span标签和input外面的div标签修改内容及属性
$(this).next().text('').parent().removeClass('has-error')
})
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': 'jason', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'}
clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量
# 将字典里面的confirm_password键值对删除
clean_data.pop('confirm_password') # {'username': 'jason', 'password': '123', 'email': '123@qq.com'}
# 用户头像
file_obj = request.FILES.get('avatar')
"""针对用户头像一定要判断是否传值 不能直接添加到字典里面去"""
if file_obj:
clean_data['avatar'] = file_obj
# 直接操作数据库保存数据
models.UserInfo.objects.create_user(**clean_data)
back_dic['url'] = '/login/'
else:
back_dic['code'] = 2000
back_dic['msg'] = form_obj.errors
return JsonResponse(back_dic)
return render(request,'register.html',locals())
def login(request): if request.method == 'POST': back_dic = {'code':1000,'msg':''} username = request.POST.get('username') password = request.POST.get('password') code = request.POST.get('code') # 1 先校验验证码是否正确 自己决定是否忽略 统一转大写或者小写再比较 if request.session.get('code').upper() == code.upper(): # 2 校验用户名和密码是否正确 user_obj = auth.authenticate(request,username=username,password=password) if user_obj: # 保存用户状态 auth.login(request,user_obj) back_dic['url'] = '/home/' else: back_dic['code'] = 2000 back_dic['msg'] = '用户名或密码错误' else: back_dic['code'] = 3000 back_dic['msg'] = '验证码错误' return JsonResponse(back_dic) return render(request,'login.html') """ 图片相关的模块 pip3 install pillow """ 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'static/img/111.jpg','rb') as f: # data = f.read() # return HttpResponse(data) # 推导步骤2:利用pillow模块动态产生图片 # img_obj = Image.new('RGB',(430,35),'green') # img_obj = Image.new('RGB',(430,35),get_random()) # # 先将图片对象保存起来 # with open('xxx.png','wb') as f: # img_obj.save(f,'png') # # 再将图片对象读取出来 # with open('xxx.png','rb') as f: # data = f.read() # return HttpResponse(data) # 推导步骤3:文件存储繁琐IO操作效率低 借助于内存管理器模块 # img_obj = Image.new('RGB', (430, 35), get_random()) # 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) # 产生一个画笔对象 img_font = ImageFont.truetype('static/font/222.ttf',30) # 字体样式 大小 # 随机验证码 五位数的随机验证码 数字 小写字母 大写字母 code = '' for i in range(5): random_upper = chr(random.randint(65,90)) random_lower = chr(random.randint(97,122)) random_int = str(random.randint(0,9)) # 从上面三个里面随机选择一个 tmp = random.choice([random_lower,random_upper,random_int]) # 将产生的随机字符串写入到图片上 """ 为什么一个个写而不是生成好了之后再写 因为一个个写能够控制每个字体的间隙 而生成好之后再写的话 间隙就没法控制了 """ img_draw.text((i*60+60,-2),tmp,get_random(),img_font) # 拼接随机字符串 code += tmp print(code) # 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到 request.session['code'] = code io_obj = BytesIO() img_obj.save(io_obj,'png') return HttpResponse(io_obj.getvalue()) $("#id_img").click(function () { // 1 先获取标签之前的src let 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) } } })
{{ article_obj.blog.userinfo.username }} 发布于 {{ article_obj.create_time|date:'Y-m-d' }}
{% if request.user.is_authenticated %}
{% else %}
<li><a href="{% url 'reg' %}">注册</a></li>
<li><a href="{% url 'login' %}">登陆</a></li>
{% endif %}
def home(request):
# 查询本网站所有的文章数据展示的前端页面 这里可以使用分页器做分页 但是我不做了 你们自己课下加
article_queryset = models.Article.objects.all()
return render(request,'home.html',locals())
@login_required def set_password(request): 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_password = request.POST.get('confirm_password') is_right = request.user.check_password(old_password) if is_right: if new_password == confirm_password: request.user.set_password(new_password) request.user.save() back_dic['msg'] = '修改成功' else: back_dic['code'] = 1001 back_dic['msg'] = '两次密码不一致' else: back_dic['code'] = 1002 back_dic['msg'] = '原密码错误' return JsonResponse(back_dic) @login_required def logout(request): auth.logout(request) return redirect('/home/') $('#id_edit').click(function () { $.ajax({ url:'/set_password/', type:'post', data:{ 'old_password':$('#id_old_password').val(), 'new_password':$('#id_new_password').val(), 'confirm_password':$('#id_confirm_password').val(), 'csrfmiddlewaretoken':'{{ csrf_token }}' }, success:function (args) { if (args.code == 1000){ window.location.reload() }else{ $("#password_error").text(args.msg) } } }) })
<li><a href="{% url 'logout' %}">退出登陆</a></li>
def site(request,username,**kwargs): """ :param request: :param username: :param kwargs: 如果该参数有值 也就意味着需要对article_list做额外的筛选操作 :return: """ # 先校验当前用户名对应的个人站点是否存在 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) # queryset对象 侧边栏的筛选其实就是对article_list再进一步筛选 if kwargs: # print(kwargs) # {'condition': 'tag', 'param': '1'} condition = kwargs.get('condition') param = kwargs.get('param') # 判断用户到底想按照哪个条件筛选数据 if condition == 'category': article_list = article_list.filter(category_id=param) elif condition == 'tag': article_list = article_list.filter(tags__id=param) else: year,month = param.split('-') # 2020-11 [2020,11] article_list = article_list.filter(create_time__year=year,create_time__month=month) # # 1 查询当前用户所有的分类及分类下的文章数 # category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk') # # print(category_list) # <QuerySet [('jason的分类一', 2), ('jason的分类二', 1), ('jason的分类三', 1)]> # # # 2 查询当前用户所有的标签及标签下的文章数 # tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk') # # print(tag_list) # <QuerySet [('tank的标签一', 1), ('tank的标签二', 1), ('tank的标签三', 2)]> # # # 3 按照年月统计所有的文章 # date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(count_num=Count('pk')).values_list('month','count_num') # # print(date_list) return render(request,'site.html',locals())
def article_detail(request,username,article_id): """ 应该需要校验username和article_id是否存在,但是我们这里先只完成正确的情况 默认不会瞎搞 :param request: :param username: :param article_id: :return: """ user_obj = models.UserInfo.objects.filter(username=username).first() blog = user_obj.blog # 先获取文章对象 article_obj = models.Article.objects.filter(pk=article_id,blog__userinfo__username=username).first() if not article_obj: return render(request,'errors.html') # 获取当前 文章所有的评论内容 comment_list = models.Comment.objects.filter(article=article_obj) return render(request,'article_detail.html',locals())
<div class="article_content">
{{ article_obj.content|safe }}
<style>
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 128px;
text-align: center;
margin-top: 10px;
}
.diggit {
float: left;
width: 46px;
height: 52px;
background: url('/static/img/upup.gif') no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.buryit {
float: right;
margin-left: 20px;
width: 46px;
height: 52px;
background: url('/static/img/downdown.gif') no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.clear {
clear: both;
}
</style>
<div class="clearfix">
<div id="div_digg">
<div class="diggit action" >
<span class="diggnum " id="digg_count">{{ article_obj.up_num }}</span>
</div>
<div class="buryit action">
<span class="burynum " id="bury_count">{{ article_obj.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red">
</div>
</div>
</div>
{# 点赞点踩样式结束#}
<ul class="list-group">
{% for comment in comment_list %}
<li class="list-group-item">
<span>#{{ forloop.counter }}楼</span>
<span>{{ comment.comment_time|date:'Y-m-d h:i:s' }}</span>
<span>{{ comment.user.username }}</span>
<span><a class="pull-right reply" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span>
<div>
{# 判断当前评论是否是子评论 如果是需要渲染对应的评论人名#}
{% if comment.parent_id %}
<p>@{{ comment.parent.user.username }}</p>
{% endif %}
{{ comment.content }}
</div>
</li>
{% endfor %}
</ul>
{% if request.user.is_authenticated %}
<div>
<p><span class="glyphicon glyphicon-comment"></span>发表评论</p>
<div>
<textarea name="comment" id="id_comment" cols="60" rows="10" ></textarea>
</div>
<button class="btn btn-primary" id="id_submit">提交评论</button>
<span style="color: red" id="errors"></span>
</div>
{% else %}
<li><a href="{% url 'reg' %}">注册</a></li>
<li><a href="{% url 'login' %}">登陆</a></li>
{% endif %}
<script>
// 给所有的action类绑定事件
$('.action').click(function () {
{#alert($(this).hasClass('diggit'))#}
let isUp = $(this).hasClass('diggit');
let $div = $(this);
// 朝后端发送ajax请求
$.ajax({
url:'/up_or_down/',
type:'post',
data:{
'article_id':'{{ article_obj.pk }}',
'is_up':isUp,
'csrfmiddlewaretoken':'{{ csrf_token }}'
},
success:function (args) {
if(args.code == 1000){
$('#digg_tips').text(args.msg)
// 将前端的数字加一
// 先获取到之前的数字
let oldNum = $div.children().text(); // 文本 是字符类型
// 易错点
$div.children().text(Number(oldNum) + 1) // 字符串拼接了 1+1 = 11 11 + 1 = 111
}else{
$('#digg_tips').html(args.msg)
}
}
})
})
// 设置一个全局的parentID字段
let parentId = null;
// 用户点击评论按钮朝后端发送ajax请求
$('#id_submit').click(function () {
// 获取用户评论的内容
let conTent = $('#id_comment').val();
// 判断当前评论是否是子评论 如果是 需要将我们之前手动渲染的@username去除
if(parentId){
// 找到\n对应的索引 然后利用切片 但是前片顾头不顾尾 所以索引+1
let indexNum = conTent.indexOf('\n') + 1;
conTent = conTent.slice(indexNum) // 将indexNum之前的所有数据切除 只保留后面的部分
}
$.ajax({
url:'/comment/',
type:'post',
data:{
'article_id':'{{ article_obj.pk }}',
'content':conTent,
// 如果parantId没有值 那么就是null 后端存储null没有任何关系
'parent_id':parentId,
'csrfmiddlewaretoken':'{{ csrf_token }}'
},
success:function (args) {
if(args.code ==1000){
$('#error').text(args.msg)
// 将评论框里面的内容清空
$('#id_comment').val('');
// 临时渲染评论楼
let userName = '{{ request.user.username }}';
let temp = `
<li class="list-group-item">
<span>${userName}</span>
<span><a href="#" class="pull-right">回复</a></span>
<div>
${conTent}
</div>
</li>
`
// 将生成好的标签添加到ul标签内
$('.list-group').append(temp);
// 清空全局的parentId
parentId = null;
}
}
})
})
// 给回复按钮绑定点击事件
$('.reply').click(function () {
// 需要评论对应的评论人姓名 还需要评论的主键值
// 获取用户名
let commentUserName = $(this).attr('username');
// 获取主键值 直接修改全局
parentId = $(this).attr('comment_id');
// 拼接信息塞给评论框
$('#id_comment').val('@' + commentUserName + '\n').focus()
})
</script>
import json
from django.db.models import F
def up_or_down(request):
"""
1.校验用户是否登陆
2.判断当前文章是否是当前用户自己写的(自己不能点自己的文章)
3.当前用户是否已经给当前文章点过了
4.操作数据库了
:param request:
:return:
"""
if request.is_ajax():
back_dic = {'code':1000,'msg':''}
# 1 先判断当前用户是否登陆
if request.user.is_authenticated():
article_id = request.POST.get('article_id')
is_up = request.POST.get('is_up')
# print(is_up,type(is_up)) # true <class 'str'>
is_up = json.loads(is_up) # 记得转换
# print(is_up, type(is_up)) # True <class 'bool'>
# 2 判断当前文章是否是当前用户自己写的 根据文章id查询文章对象 根据文章对象查作者 根request.user比对
article_obj = models.Article.objects.filter(pk=article_id).first()
if not article_obj.blog.userinfo == request.user:
# 3 校验当前用户是否已经点了 哪个地方记录了用户到底点没点
is_click = models.UpAndDown.objects.filter(user=request.user,article=article_obj)
if not is_click:
# 4 操作数据库 记录数据 要同步操作普通字段
# 判断当前用户点了赞还是踩 从而决定给哪个字段加一
if is_up:
# 给点赞数加一
models.Article.objects.filter(pk=article_id).update(up_num = F('up_num') + 1)
back_dic['msg'] = '点赞成功'
else:
# 给点踩数加一
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
back_dic['msg'] = '点踩成功'
# 操作点赞点踩表
models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up)
else:
back_dic['code'] = 1001
back_dic['msg'] = '你已经点过了,不能再点了' # 这里你可以做的更加的详细 提示用户到底点了赞还是点了踩
else:
back_dic['code'] = 1002
back_dic['msg'] = '你个臭不要脸的!'
else:
back_dic['code'] = 1003
back_dic['msg'] = '请先<a href="/login/">登陆</a>'
return JsonResponse(back_dic)
from django.db import transaction def comment(request): # 自己也可以给自己的文章评论内容 if request.is_ajax(): back_dic = {'code': 1000, 'msg': ""} if request.method == 'POST': if request.user.is_authenticated(): article_id = request.POST.get('article_id') content = request.POST.get("content") parent_id = request.POST.get('parent_id') # 直接操作评论表 存储数据 两张表 with transaction.atomic(): models.Article.objects.filter(pk=article_id).update(comment_num = F('comment_num') + 1) models.Comment.objects.create(user=request.user,article_id=article_id,content=content,parent_id=parent_id) back_dic['msg'] = '评论成功' else: back_dic['code'] = 1001 back_dic['msg'] = '用户未登陆' return JsonResponse(back_dic)
{% for article in page_queryset %}
<tr>
<td><a href="/{{ article.blog.userinfo.username }}/article/{{ article.pk }}/">{{ article.title }}</a></td>
<td>{{ article.up_num }}</td>
<td>{{ article.comment_num }}</td>
<td><a href="">编辑</a></td>
<td><a href="">删除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pull-right">
{{ page_obj.page_html|safe }}
from app01.utils.mypage import Pagination
@login_required
def backend(request):
# 获取当前用户对象所有的文章展示到页面
article_list = models.Article.objects.filter(blog=request.user.blog)
page_obj = Pagination(current_page=request.GET.get('page',1),all_count=article_list.count())
page_queryset = article_list[page_obj.start:page_obj.end]
return render(request,'backend/backend.html',locals())
from bs4 import BeautifulSoup @login_required def add_article(request): if request.method == 'POST': title = request.POST.get('title') content = request.POST.get('content') category_id = request.POST.get("category") tag_id_list = request.POST.getlist('tag') # 模块使用 soup = BeautifulSoup(content,'html.parser') tags = soup.find_all() # 获取所有的标签 for tag in tags: # print(tag.name) # 获取页面所有的标签 # 针对script标签 直接删除 if tag.name == 'script': # 删除标签 tag.decompose() # 文章简介 # 1 先简单暴力的直接切去content 150个字符 # desc = content[0:150] # 2 截取文本150个 desc = soup.text[0:150] article_obj = models.Article.objects.create( title=title, content=str(soup), desc=desc, category_id=category_id, blog=request.user.blog ) # 文章和标签的关系表 是我们自己创建的 没法使用add set remove clear方法 # 自己去操作关系表 一次性可能需要创建多条数据 批量插入bulk_create() article_obj_list = [] for i in tag_id_list: tag_article_obj = models.Article2Tag(article=article_obj,tag_id=i) article_obj_list.append(tag_article_obj) # 批量插入数据 models.Article2Tag.objects.bulk_create(article_obj_list) # 跳转到后台管理文章展示页 return redirect('/backend/') category_list = models.Category.objects.filter(blog=request.user.blog) tag_list = models.Tag.objects.filter(blog=request.user.blog) return render(request,'backend/add_article.html',locals())
<textarea name="content" id="id_content" cols="30" rows="10"></textarea>
{% load static %}
<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: '600px',
resizeType:1,
uploadJson : '/upload_image/', // 上传图片的后端提交路径
extraFileUploadParams : {
'csrfmiddlewaretoken':'{{ csrf_token }}'
}
});
});
</script>
def upload_image(request): """ //成功时 { "error" : 0, "url" : "http://www.example.com/path/to/file.ext" } //失败时 { "error" : 1, "message" : "错误信息" } :param request: :return: """ back_dic = {'error': 0, } # 先提前定义返回给编辑器的数据格式 # 用户写文章上传的图片 也算静态资源 也应该防盗media文件夹下 if request.method == "POST": # 获取用户上传的图片对象 # print(request.FILES) # 打印看到了健固定叫imgFile file_obj = request.FILES.get('imgFile') # 手动拼接存储文件的路径 file_dir = os.path.join(settings.BASE_DIR,'media','article_img') # 优化操作 先判断当前文件夹是否存在 不存在 自动创建 if not os.path.isdir(file_dir): os.mkdir(file_dir) # 创建一层目录结构 article_img # 拼接图片的完整路径 file_path = os.path.join(file_dir,file_obj.name) with open(file_path,'wb') as f: for line in file_obj: f.write(line) back_dic['url'] = '/media/article_img/%s'%file_obj.name return JsonResponse(back_dic)
def set_avatar(request):
if request.method == 'POST':
file_obj = request.FILES.get('avatar')
# models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=file_obj) # 不会再自动加avatar前缀
# 1.自己手动加前缀
# 2.换一种更新方式
user_obj = request.user
user_obj.avatar = file_obj
user_obj.save()
return redirect('/home/')
blog = request.user.blog
username = request.user.username
return render(request,'set_avatar.html',locals()) 修改头像
浙公网安备 33010602011771号