需求
项目:开发一个简单的BBS论坛
需求:
1、整体参考“抽屉新热榜” + “虎嗅网”
2、实现不同论坛版块
3、帖子列表展示
4、帖子评论数、点赞数展示
5、在线用户展示
6、允许登录用户发贴、评论、点赞
7、允许上传文件
8、帖子可被置顶
9、可进行多级评论
code
1 #! /usr/bin/env python3 2 # -*- coding:utf-8 -*- 3 4 from django.template import Library 5 from django.utils.safestring import mark_safe 6 7 register = Library() 8 9 10 @register.simple_tag 11 def searchCondition(arg_dict, k): 12 if k == 'article_type': 13 if arg_dict['article_type'] == 0: 14 res = '<a class="active_condition" href="/backend/article-0-%s.html">全部</a>' % arg_dict['category_id'] 15 else: 16 res = '<a href="/backend/article-0-%s.html">全部</a>' % arg_dict.category_id 17 return mark_safe(res) 18 else: 19 if arg_dict['category_id'] == 0: 20 res = '<a class="active_condition" href="/backend/article-%s-0.html">全部</a>' % arg_dict['article_type'] 21 else: 22 res = '<a href="/backend/article-%s-0.html">全部</a>' % arg_dict['article_type'] 23 return mark_safe(res) 24 25 26 @register.simple_tag 27 def searchCondition2(arg_dict,category_list): 28 res_list = [] 29 for c in category_list: 30 if arg_dict['category_id'] == c.nid: 31 res_list.append('<a class="active_condition" href="/backend/article-%s-%s.html">%s</a>' % ( 32 arg_dict['article_type'], c.nid, c.title)) 33 else: 34 res_list.append('<a href="/backend/article-%s-%s.html">%s</a>' % (arg_dict['article_type'], c.nid, c.title)) 35 36 return mark_safe(' '.join(res_list)) 37 38 39 @register.simple_tag 40 def searchCondition3(arg_dict,article_type_list): 41 res_list = [] 42 for t in article_type_list: 43 if arg_dict['article_type'] == t[0]: 44 temp = '<a class ="active_condition" href="/backend/article-%s-%s.html">%s</a>'%(t[0],arg_dict['category_id'],t[1]) 45 else: 46 temp = '<a href="/backend/article-%s-%s.html">%s</a>'%(t[0],arg_dict['category_id'],t[1]) 47 res_list.append(temp) 48 49 return mark_safe(' '.join(res_list))
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 from django.shortcuts import render,redirect,HttpResponse 4 from django.db import transaction 5 from repository import models 6 from backend import forms 7 from utils.custom_json_encoder import CustomJsonEncoder 8 import json,os,time 9 10 11 def auth(func): 12 ''' 13 用户验证装饰器 | 或写成2个函数,由对应的view启用对应的装饰器或许更好? 14 :param func: 15 :return: 16 ''' 17 def wrapper(request,*args,**kwargs): 18 # print(request.path_info) 19 if request.path_info == '/backend/base-info.html': 20 if request.session.get('username',None): 21 return func(request,*args,**kwargs) 22 else: 23 return redirect('/login.html') 24 else: 25 if request.session.get('username',None) and request.session.get('blog_nid',None): 26 return func(request,*args,**kwargs) 27 elif request.session.get('username',None) and not request.session.get('blog_nid',None): 28 return render(request,'backend_return_to_base_info.html') 29 else: 30 return redirect('/login.html') 31 32 return wrapper 33 34 35 @auth 36 def base_info(request): 37 """ 38 博主个人信息 39 :param request: 40 :return: 41 """ 42 if request.method == 'GET': 43 user = models.UserInfo.objects.filter(username=request.session['username']).select_related('blog').first() 44 # cur_user = request.session['cur_user'] 45 return render(request, 'backend_base_info.html',{'user':user}) 46 47 elif request.method == 'POST': 48 # print(request.POST) 49 data = {} 50 for k,v in request.POST.items(): 51 data[k] = v.strip() 52 53 data['username'] = request.session['username'] 54 55 bf = forms.BlogForm(data) 56 res = {'status':True,'error':None,'data':None} 57 if bf.is_valid(): 58 userS = models.UserInfo.objects.filter(username=request.session['username']) 59 if bf.cleaned_data['nickname']: 60 userS.update(nickname=bf.cleaned_data['nickname']) 61 62 blog_obj,is_created = models.Blog.objects.update_or_create( 63 user=userS.first(), 64 defaults={ 65 'site':request.POST['site'], 66 'theme':request.POST['theme'], 67 'title':request.POST['title'], 68 } 69 ) 70 # 对于新注册的博客,将blog的id保存入session 71 if is_created: 72 request.session['blog_nid'] = blog_obj.nid 73 else: 74 res['status'] = False 75 res['error'] = bf.errors.as_data() 76 return HttpResponse(json.dumps(res,cls=CustomJsonEncoder)) 77 78 else: 79 return redirect('/') 80 81 82 @auth 83 def uploadAvatar(request): 84 ''' 85 上传头像 86 :param request: 87 :return: 88 ''' 89 if request.method == 'POST': 90 file = request.FILES.get('avatar',None) 91 res = {'status':True,'data':None} 92 file_path = os.path.join('static/imgs/avatar',file.name) 93 with open( file_path,'wb' ) as f: 94 for line in file: 95 f.write(line) 96 97 res['data'] = '/' + file_path 98 models.UserInfo.objects.filter(username = request.session['username']).update(avatar=res['data']) 99 100 return HttpResponse(json.dumps(res)) 101 else: 102 return redirect('/') 103 104 105 @auth 106 def tag(request): 107 """ 108 博主个人标签管理 109 :param request: 110 :return: 111 """ 112 # cur_blog = models.Blog.objects.filter(user__username=request.session['username']).first() 113 cur_blog = models.Blog.objects.filter(nid=request.session['blog_nid']).first() 114 if request.method == 'GET': 115 tags = models.Tag.objects.filter(blog=cur_blog) 116 return render(request,'backend_tag.html',{'tags':tags}) 117 elif request.method == 'POST': 118 res = {'status':True,'error':None,'data':None} 119 data = {} 120 for k,v in request.POST.items(): 121 data[k]=v 122 data['blog_nid'] = cur_blog.nid #;print(data) 123 tf = forms.TagForm(data) 124 if tf.is_valid(): 125 models.Tag.objects.create(title=tf.cleaned_data['title'], 126 blog=cur_blog) 127 else: 128 res['status'] = False 129 res['error'] = tf.errors.as_data() 130 return HttpResponse(json.dumps(res,cls=CustomJsonEncoder)) 131 else: 132 return redirect('/') 133 134 135 @auth 136 def del_tag(request): 137 ''' 138 删除标签 139 :param request: 140 :return: 141 ''' 142 if request.method == 'POST': 143 res = {'status':True,'error':None,'data':None} 144 try: 145 nid = request.POST.get('nid',None) 146 models.Tag.objects.filter(nid=nid).delete() 147 except Exception as e: 148 res['status'] = False 149 res['error'] = str(e) 150 return HttpResponse(json.dumps(res)) 151 else: 152 return redirect('/') 153 154 @auth 155 def edit_tag(request): 156 ''' 157 编辑标签名称 158 :param request: 159 :return: 160 ''' 161 if request.method == 'POST': 162 res = {'status':True,'error':None,'data':None} 163 tf = forms.TagForm2(request.POST) 164 if tf.is_valid(): 165 models.Tag.objects.filter(nid=tf.cleaned_data['nid']).update(title=tf.cleaned_data['title']) 166 else: 167 res['status'] = False 168 res['error'] = tf.errors.as_data() 169 return HttpResponse(json.dumps(res,cls=CustomJsonEncoder)) 170 else: 171 return redirect('/') 172 173 174 @auth 175 def category(request): 176 """ 177 博主个人分类管理 178 :param request: 179 :return: 180 """ 181 # cur_blog = models.Blog.objects.filter(user__username=request.session['username']).first() 182 cur_blog = models.Blog.objects.filter(nid=request.session['blog_nid']).first() 183 if request.method == 'GET': 184 185 categories = models.Category.objects.filter(blog=cur_blog) 186 # request.session['blog_title'] = cur_blog.title 187 # cur_categories = request.session['cur_categories'] 188 return render(request, 'backend_category.html',{'cs':categories}) 189 elif request.method == 'POST': 190 res = {'status':True,'error':None,'data':None} 191 # 添加验证项 192 data = {} 193 for k,v in request.POST.items(): 194 data[k] = v 195 data['blog_nid'] = cur_blog.nid # ;print(blog.nid) 196 # 验证 197 cf = forms.CategoryForm(data) 198 if cf.is_valid(): 199 models.Category.objects.create(title=cf.cleaned_data['title'],blog=cur_blog) 200 else: 201 res['status'] = False 202 res['error'] = cf.errors.as_data() 203 return HttpResponse(json.dumps(res,cls=CustomJsonEncoder)) 204 else: 205 return redirect('/') 206 207 208 @auth 209 def del_ctgy(request): 210 ''' 211 删除文章分类 212 :param request: 213 :return: 214 ''' 215 if request.method == 'POST': 216 res = {'status':True,'error':None,'data':None} 217 try: 218 nid = request.POST.get('nid',None) 219 models.Category.objects.filter(nid=nid).delete() 220 except Exception as e: 221 res['status'] = False 222 res['error'] = str(e) 223 return HttpResponse(json.dumps(res)) 224 else: 225 return redirect('/') 226 227 228 @auth 229 def edit_ctgy(request): 230 ''' 231 编辑文章分类 232 :param request: 233 :return: 234 ''' 235 if request.method == 'POST': 236 res = {'status':True,'error':None,'data':None} 237 cf = forms.CategoryForm2(request.POST) 238 if cf.is_valid(): 239 nid = request.POST.get('nid',None) # ;print(nid) 240 title = request.POST.get('title',None) 241 models.Category.objects.filter(nid=nid).update(title=title) 242 else: 243 res['status'] = False 244 res['error'] = cf.errors.as_data() 245 return HttpResponse(json.dumps(res,cls=CustomJsonEncoder)) 246 else: 247 return redirect('/') 248 249 250 @auth 251 def article(request,*args,**kwargs): 252 """ 253 博主个人文章管理 254 :param request: 255 :return: 256 """ 257 cur_blog = models.Blog.objects.filter(nid=request.session['blog_nid']).first() # ;print(cur_blog.nid) 258 if request.method == 'GET': 259 condition = {} 260 for k,v in kwargs.items(): 261 kwargs[k] = int(v) 262 if v != '0': 263 condition[k] = v 264 265 articles = models.Article.objects.filter(blog_id=cur_blog.nid,**condition) # ;print(articles);print(condition) 266 category_list = models.Category.objects.all() 267 article_type_list = models.Article.type_choices 268 return render(request, 'backend_article.html',{'articles':articles,'category_list':category_list,'arg_dict':kwargs,'article_type_list':article_type_list}) 269 elif request.method == 'POST': 270 pass 271 else: 272 return redirect('/') 273 274 275 @auth 276 def add_article(request): 277 """ 278 添加文章 279 :param request: 280 :return: 281 """ 282 cur_blog = models.Blog.objects.filter(nid=request.session['blog_nid']).first() 283 if request.method == 'GET': 284 categories = models.Category.objects.filter(blog=cur_blog) 285 tags = models.Tag.objects.filter(blog=cur_blog) 286 return render(request,'backend_add_article.html',{'categories':categories,'tags':tags,'blog_nid':cur_blog.nid}) 287 elif request.method == 'POST': 288 res={'status':True,'error':None,'data':None} 289 # data = {} 290 # for k in request.POST.keys(): 291 # if k == 'tag': 292 # data[k] = request.POST.getlist(k,None) 293 # else: 294 # data[k] = request.POST.get(k,None) 295 # # data['blog_nid'] = request.session['blog_nid'] 296 297 # 验证 298 # af = forms.ArticleForm(data);print(af.__dict__) 299 af = forms.ArticleForm(request.POST) # ;print(af.__dict__) 300 if af.is_valid(): 301 with transaction.atomic(): 302 # 创建Article表数据 303 obj = models.Article.objects.create( 304 blog_id = af.cleaned_data['blog_nid'], 305 title = af.cleaned_data['title'], 306 summary = af.cleaned_data['summary'], 307 article_type = af.cleaned_data['article_type'], 308 category_id = af.cleaned_data['category'], 309 ) 310 # 创建Article表与Tag表的多对多关系 311 tag_list = [] 312 for tag_id in af.cleaned_data['tags']: 313 tag_list.append(models.Article2Tag(article_id = obj.nid,tag_id = tag_id)) 314 315 models.Article2Tag.objects.bulk_create(tag_list) 316 317 318 # 创建ArticleDetail表数据 319 models.ArticleDetail.objects.create( 320 content = af.cleaned_data['content'], 321 article = obj 322 ) 323 else: 324 res['status'] = False 325 res['error'] = af.errors.as_data() 326 return HttpResponse(json.dumps(res,cls=CustomJsonEncoder)) 327 else: 328 return redirect('/') 329 330 331 @auth 332 def edit_article(request,nid): 333 """ 334 编辑文章 335 :param request: 336 :return: 337 """ 338 cur_blog = models.Blog.objects.filter(nid=request.session['blog_nid']).first() 339 cur_article = models.Article.objects.filter(nid=nid).select_related('category','articledetail').first() # ;print(cur_article.tags.all()) 340 if request.method == 'GET': 341 categories = models.Category.objects.filter(blog=cur_blog) 342 tags = models.Tag.objects.filter(blog=cur_blog) 343 return render(request, 'backend_edit_article.html',{'article':cur_article,'categories':categories,'tags':tags,'blog_nid':cur_blog.nid}) 344 elif request.method == 'POST': 345 res = {'status':True,'error':None,'data':None} 346 af = forms.ArticleForm(request.POST) # ;print(request.POST) 347 if af.is_valid(): 348 cur_article.title = af.cleaned_data['title'] 349 cur_article.summary = af.cleaned_data['summary'] 350 cur_article.articledetail.content = af.cleaned_data['content'] 351 cur_article.article_type = af.cleaned_data['article_type'] 352 cur_article.category_id = af.cleaned_data['category'] 353 cur_article.save() 354 355 with transaction.atomic(): 356 # 重设Article表 与 tag 表的多对多关系 357 models.Article2Tag.objects.filter(article=cur_article).delete() 358 for tid in af.cleaned_data['tags']: 359 models.Article2Tag.objects.create( 360 article=cur_article, 361 tag_id=tid 362 ) 363 364 # 修改文章内容 365 models.ArticleDetail.objects.filter(article_id = nid).update(content = af.cleaned_data['content'],) 366 367 else: 368 res['status'] = False 369 res['error'] = af.errors.as_data() 370 return HttpResponse(json.dumps(res,cls=CustomJsonEncoder)) 371 else: 372 return redirect('/') 373 374 375 376 @auth 377 def del_article(request): 378 ''' 379 删除文章 380 :param request: 381 :return: 382 ''' 383 if request.method == 'POST': 384 res = {'status':True,'error':None,'data':None} 385 try: 386 a_id = request.POST.get('article_id',None) 387 models.Article.objects.filter(nid=a_id).delete() 388 except Exception as e: 389 res['status'] = True 390 res['error'] = str(e) 391 return HttpResponse(json.dumps(res,cls=CustomJsonEncoder)) 392 else: 393 return redirect('/') 394 395 @auth 396 def keUploadFile(request): 397 ''' 398 KindEditor 上传文件API 399 :param request: 400 :return: 401 ''' 402 if request.method == 'POST': 403 res = {'error':0,'url':None,'message':None} 404 file_type = request.GET.get('dir', None) 405 if file_type == 'image' or file_type == 'flash' or file_type == 'file': # 暂时不考虑 flash 和 file 的情况 406 uf = request.FILES.get('imgFile',None) 407 save_path = os.path.join('static/imgs',uf.name) 408 with open(save_path,'wb') as f: 409 for line in uf: 410 f.write(line) 411 res['url'] = save_path 412 else: 413 res['error'] = 1 414 res['message'] = '上传类型不存在' 415 return HttpResponse(json.dumps(res)) 416 else: 417 return redirect('/') 418 419 420 421 @auth 422 def keFileManager(request): 423 ''' 424 KindEditor 文件管理API 425 :param request: 426 :return: 427 ''' 428 429 dic = {} 430 root_path = r'E:学习Python_Practice51cto第七模块EdmureBlogstatic/' 431 static_root_path = '/static/' 432 request_path = request.GET.get('path') 433 if request_path: 434 abs_current_dir_path = os.path.join(root_path, request_path) 435 move_up_dir_path = os.path.dirname(request_path.rstrip('/')) 436 dic['moveup_dir_path'] = move_up_dir_path + '/' if move_up_dir_path else move_up_dir_path 437 438 else: 439 abs_current_dir_path = root_path 440 dic['moveup_dir_path'] = '' 441 442 dic['current_dir_path'] = request_path 443 dic['current_url'] = os.path.join(static_root_path, request_path) 444 445 file_list = [] 446 for item in os.listdir(abs_current_dir_path): 447 abs_item_path = os.path.join(abs_current_dir_path, item) 448 a, exts = os.path.splitext(item) 449 is_dir = os.path.isdir(abs_item_path) 450 if is_dir: 451 temp = { 452 'is_dir': True, 453 'has_file': True, 454 'filesize': 0, 455 'dir_path': '', 456 'is_photo': False, 457 'filetype': '', 458 'filename': item, 459 'datetime': time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(os.path.getctime(abs_item_path))) 460 } 461 else: 462 temp = { 463 'is_dir': False, 464 'has_file': False, 465 'filesize': os.stat(abs_item_path).st_size, 466 'dir_path': '', 467 'is_photo': True if exts.lower() in ['.jpg', '.png', '.jpeg'] else False, 468 'filetype': exts.lower().strip('.'), 469 'filename': item, 470 'datetime': time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(os.path.getctime(abs_item_path))) 471 } 472 473 file_list.append(temp) 474 dic['file_list'] = file_list 475 return HttpResponse(json.dumps(dic))
1 #! /usr/bin/env python3 2 # -*- coding:utf-8 -*- 3 # Author:Jailly 4 5 from django.forms import Form 6 from django.forms import fields 7 from django.forms import widgets 8 from repository import models 9 from django.core.validators import ValidationError 10 from django.db.models import Q 11 from django.forms.models import ModelChoiceField 12 13 class BlogForm(Form): 14 username = fields.CharField() 15 nickname = fields.CharField(max_length=64,required=False) 16 site = fields.CharField(max_length=32, 17 error_messages={ 18 'max_length':'博客地址不能超过32个字符', 19 'required':'请填写博客地址' 20 }) 21 theme = fields.ChoiceField( 22 choices=((1,''),(2,''),(3,''),(4,''),(5,'')) 23 ) 24 title = fields.CharField(max_length=64,required=False,error_messages={'max_length':'内容过长'}) 25 26 def clean_site(self): 27 # print(self.cleaned_data) 28 if models.Blog.objects.exclude(user__username=self.cleaned_data['username']).filter(site=self.cleaned_data['site']).count(): 29 raise ValidationError('该地址已存在') 30 else: 31 return self.cleaned_data['site'] 32 33 34 class CategoryForm(Form): 35 blog_nid = fields.CharField() 36 title = fields.CharField(max_length=32, 37 error_messages={ 38 'max_length':'分类名称太长', 39 'required':'请填写分类名称', 40 }) 41 42 def clean_title(self): 43 if models.Category.objects.filter(blog__nid=self.cleaned_data['blog_nid'],title=self.cleaned_data['title']).count(): 44 raise ValidationError('您已经在使用该分类') 45 else: 46 return self.cleaned_data['title'] 47 48 49 class CategoryForm2(Form): 50 nid = fields.CharField() 51 title = fields.CharField(max_length=32, 52 error_messages={ 53 'max_length':'分类名称太长', 54 'required':'请填写分类名称', 55 }) 56 57 58 59 class TagForm(Form): 60 blog_nid = fields.CharField() 61 title = fields.CharField(max_length=32, 62 error_messages={ 63 'max_length': '标签名称太长', 64 'required': '请填写标签名称', 65 }) 66 67 def clean_title(self): 68 if models.Tag.objects.filter(blog__nid=self.cleaned_data['blog_nid'],title=self.cleaned_data['title']).count(): 69 raise ValidationError('您已经拥有该标签') 70 else: 71 return self.cleaned_data['title'] 72 73 74 class TagForm2(Form): 75 nid = fields.CharField() 76 title = fields.CharField(max_length=32, 77 error_messages={ 78 'max_length': '标签名称太长', 79 'required': '请填写标签名称', 80 }) 81 82 class ArticleForm(Form): 83 blog_nid = fields.CharField() 84 title = fields.CharField(max_length=128, 85 error_messages={ 86 'max_length':'文章标题不能超过128个字符', 87 'required':'请填写文章标题' 88 }) 89 summary = fields.CharField(max_length=255, 90 error_messages={ 91 'max_length':'文章简介不能超过255个字符', 92 'required':'请填写文件简介', 93 }) 94 content = fields.CharField(error_messages={'required':'请填写内容'}) 95 96 article_type = fields.ChoiceField( 97 choices=((1,'Python'),(2,'Linux'),(3,'OpenStack'),(4,'GoLang'),), 98 required=False 99 ) 100 101 category = fields.ChoiceField() 102 103 tags = fields.MultipleChoiceField() 104 105 def __init__(self,*args,**kwargs): 106 super(ArticleForm, self).__init__(*args,**kwargs) 107 self.fields['category'].choices = models.Category.objects.filter(blog__nid=self.data['blog_nid']).values_list('nid','title') 108 self.fields['tags'].choices = models.Tag.objects.filter(blog__nid=self.data['blog_nid']).values_list('nid','title') 109 # print(self.fields['tag'].choices)
1 from django.conf.urls import url 2 from django.conf.urls import include 3 from .views import user 4 5 urlpatterns = [ 6 url(r'^base-info.html$', user.base_info), 7 8 url(r'^tag.html$', user.tag), 9 url(r'^del_tag.html$',user.del_tag), 10 url(r'^edit_tag.html$',user.edit_tag), 11 12 url(r'^category.html$', user.category), 13 url(r'^del_ctgy.html$', user.del_ctgy), 14 url(r'^edit_ctgy.html$', user.edit_ctgy), 15 16 url(r'^article-(?P<article_type>d+)-(?P<category_id>d+).html$', user.article), 17 url(r'^add-article.html$', user.add_article), 18 url(r'^del-article.html$', user.del_article), 19 url(r'^edit-article-(d+).html$', user.edit_article), 20 21 url(r'^uploadAvatar.html$',user.uploadAvatar), 22 23 url(r'^ke-upload-file.html$',user.keUploadFile), 24 url(r'^ke-file-manager.html$',user.keFileManager), 25 ]
1 """EdmureBlog URL Configuration 2 3 The `urlpatterns` list routes URLs to views. For more information please see: 4 https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 Examples: 6 Function views 7 1. Add an import: from my_app import views 8 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 Class-based views 10 1. Add an import: from other_app.views import Home 11 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 Including another URLconf 13 1. Import the include() function: from django.conf.urls import url, include 14 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 """ 16 from django.conf.urls import url 17 from django.conf.urls import include 18 from django.contrib import admin 19 20 urlpatterns = [ 21 url(r'^admin/docs/', include('django.contrib.admindocs.urls')), 22 url(r'^admin/', admin.site.urls), 23 url(r'^backend/', include('backend.urls')), 24 url(r'^', include('web.urls')), 25 ]
1 from django.db import models 2 3 4 class UserInfo(models.Model): 5 """ 6 用户表 7 """ 8 nid = models.BigAutoField(primary_key=True) 9 username = models.CharField(verbose_name='用户名', max_length=32, unique=True) 10 password = models.CharField(verbose_name='密码', max_length=64) 11 nickname = models.CharField(verbose_name='昵称', max_length=32,null=True) 12 email = models.EmailField(verbose_name='邮箱', unique=True) 13 avatar = models.ImageField(verbose_name='头像',null=True) 14 15 create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) 16 17 fans = models.ManyToManyField(verbose_name='粉丝们', to='UserInfo', through='UserFans', 18 through_fields=('user', 'follower')) 19 20 21 class Blog(models.Model): 22 """ 23 博客信息 24 """ 25 nid = models.BigAutoField(primary_key=True) 26 title = models.CharField(verbose_name='个人博客标题', max_length=64,null=True) 27 site = models.CharField(verbose_name='个人博客前缀', max_length=32, unique=True) 28 theme = models.CharField(verbose_name='博客主题', max_length=32) 29 user = models.OneToOneField(to='UserInfo', to_field='nid',on_delete=models.CASCADE) 30 31 32 class UserFans(models.Model): 33 """ 34 互粉关系表 35 """ 36 user = models.ForeignKey(verbose_name='博主', to='UserInfo', to_field='nid', related_name='users',on_delete=models.CASCADE) 37 follower = models.ForeignKey(verbose_name='粉丝', to='UserInfo', to_field='nid', related_name='followers',on_delete=models.CASCADE) 38 39 class Meta: 40 unique_together = [ 41 ('user', 'follower'), 42 ] 43 44 45 class Category(models.Model): 46 """ 47 博主个人文章分类表 48 """ 49 nid = models.AutoField(primary_key=True) 50 title = models.CharField(verbose_name='分类标题', max_length=32) 51 52 blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid',on_delete=models.CASCADE) 53 54 55 class ArticleDetail(models.Model): 56 """ 57 文章详细表 58 """ 59 content = models.TextField(verbose_name='文章内容', ) 60 61 article = models.OneToOneField(verbose_name='所属文章', to='Article', to_field='nid',on_delete=models.CASCADE) 62 63 64 class UpDown(models.Model): 65 """ 66 文章顶或踩 67 """ 68 article = models.ForeignKey(verbose_name='文章', to='Article', to_field='nid',on_delete=models.CASCADE) 69 user = models.ForeignKey(verbose_name='赞或踩用户', to='UserInfo', to_field='nid',on_delete=models.CASCADE) 70 up = models.BooleanField(verbose_name='是否赞') 71 72 class Meta: 73 unique_together = [ 74 ('article', 'user'), 75 ] 76 77 78 class Comment(models.Model): 79 """ 80 评论表 81 """ 82 nid = models.BigAutoField(primary_key=True) 83 content = models.CharField(verbose_name='评论内容', max_length=255) 84 create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) 85 86 # reply = models.ForeignKey(verbose_name='回复评论', to='self', related_name='back', null=True,on_delete=models.CASCADE) 87 reply = models.ManyToManyField(verbose_name='回复评论', to='self', related_name='back', null=True,symmetrical=False) 88 article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid',on_delete=models.CASCADE) 89 user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid',on_delete=models.CASCADE) 90 91 92 class Tag(models.Model): 93 nid = models.AutoField(primary_key=True) 94 title = models.CharField(verbose_name='标签名称', max_length=32) 95 blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid',on_delete=models.CASCADE) 96 97 98 class Article(models.Model): 99 nid = models.BigAutoField(primary_key=True) 100 title = models.CharField(verbose_name='文章标题', max_length=128) 101 summary = models.CharField(verbose_name='文章简介', max_length=255) 102 read_count = models.IntegerField(default=0) 103 comment_count = models.IntegerField(default=0) 104 up_count = models.IntegerField(default=0) 105 down_count = models.IntegerField(default=0) 106 create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) 107 108 blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid',on_delete=models.CASCADE) 109 category = models.ForeignKey(verbose_name='文章类型', to='Category', to_field='nid', null=True,on_delete=models.CASCADE) 110 111 type_choices = [ 112 (1, "Python"), 113 (2, "Linux"), 114 (3, "OpenStack"), 115 (4, "GoLang"), 116 ] 117 118 article_type = models.IntegerField(choices=type_choices, default=None) 119 120 tags = models.ManyToManyField( 121 to="Tag", 122 through='Article2Tag', 123 through_fields=('article', 'tag'), 124 ) 125 126 127 class Article2Tag(models.Model): 128 article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid',on_delete=models.CASCADE) 129 tag = models.ForeignKey(verbose_name='标签', to="Tag", to_field='nid',on_delete=models.CASCADE) 130 131 class Meta: 132 unique_together = [ 133 ('article', 'tag'), 134 ]
static - 略
templates - 略
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 from io import BytesIO 4 from django.shortcuts import HttpResponse, redirect, render 5 from utils.check_code import create_validate_code 6 from web import forms 7 import json 8 from utils.custom_json_encoder import CustomJsonEncoder 9 from repository import models 10 11 12 def check_code(request): 13 """ 14 验证码 15 :param request: 16 :return: 17 """ 18 stream = BytesIO() 19 img, code = create_validate_code() 20 img.save(stream, 'PNG') 21 request.session['checkCode'] = code 22 print(code) 23 return HttpResponse(stream.getvalue()) 24 25 26 def save_session(request): 27 ''' 28 注册/登录成功时,保存session 29 :param request: 30 :return: 31 ''' 32 # username = request.POST['username'] 33 # cur_userS = models.UserInfo.objects.filter(username=username) 34 # cur_user = cur_userS.values('username', 'email', 'avatar','nickname').first() 35 # request.session['cur_user'] = cur_user 36 # 37 # cur_blogS = models.Blog.objects.filter(user__username=username) 38 # cur_blog = cur_blogS.values('title').first() 39 # request.session['cur_blog'] = cur_blog if cur_blog else '' # django的session只能接受可以被json序列化的对象,None不能被序列化 40 # 41 # cur_categoriesS = models.Category.objects.filter(blog__title=cur_blogS.first().title) 42 # cur_categories = cur_categoriesS.values('title') 43 # request.session['cur_categories'] = cur_categories if cur_categories else '' 44 # 45 # cur_tagsS = models.Tag.objects.filter(blog=cur_blogS.first()) 46 # cur_tags = cur_tagsS.values('title') 47 # request.session['cur_tags'] = cur_tags if cur_tags else '' 48 # 49 # cur_articlesS = models.Article.objects.filter(blog=cur_blogS.first()) 50 # cur_articles = cur_articlesS.values('title','summary','read_count','comment_count','up_count','down_count','blog__site','category__title','tags__title') 51 # request.session['cur_articles'] = cur_articles if cur_articles else '' 52 53 request.session['username'] = request.POST['username'] 54 cur_blog = models.Blog.objects.filter(user__username=request.POST['username']) 55 request.session['blog_nid'] = cur_blog.first().nid if cur_blog else '' 56 57 58 def login(request): 59 """ 60 登陆 61 :param request: 62 :return: 63 """ 64 if request.method == 'GET': 65 return render(request, 'login.html') 66 elif request.method == 'POST': 67 68 lf = forms.LoginForm(request.POST) 69 res = {'status': True, 'error': None, 'data': None} 70 if lf.is_valid(): 71 if request.POST['checkCode'].upper() == request.session['checkCode'].upper(): 72 # 判断是否勾选自动登陆 73 if request.POST.get('autoLogin', None): 74 request.session.set_expiry(60 * 60 * 24 * 30) 75 # 保存session 76 save_session(request) 77 else: 78 res['status'] = False # 方法2:重构传入 Form 中的参数,将session中的checkcode传入,由Form来验证。哪种方法更好? 79 res['error'] = {'checkCode': ['验证码错误', ]} 80 else: 81 res['status'] = False 82 res['error'] = lf.errors.as_data() 83 if request.POST['checkCode'].upper() != request.session['checkCode'].upper(): 84 res['error']['checkCode'] = ['验证码错误', ] 85 86 return HttpResponse(json.dumps(res, cls=CustomJsonEncoder)) 87 else: 88 return redirect('/') 89 90 91 def register(request): 92 """ 93 注册 94 :param request: 95 :return: 96 """ 97 98 if request.method == 'GET': 99 return render(request, 'register.html') 100 elif request.method == 'POST': 101 res = {'status': True, 'error': None, 'data': None} 102 rf = forms.RegisterForm(request.POST) 103 # print(request.POST) 104 if rf.is_valid(): 105 # 判断验证码是否输入正确 106 if request.session['checkCode'].upper() != request.POST.get('checkCode', None).upper(): 107 res['status'] = False 108 res['error'] = {'checkCode': ['验证码错误', ]} 109 else: 110 request.session['username'] = request.POST.get('username', None) 111 # 将注册信息存储入数据库 112 username = request.POST.get('username', None) 113 password = request.POST.get('password', None) 114 email = request.POST.get('email', None) 115 models.UserInfo.objects.create(username=username, password=password, email=email) 116 save_session(request) 117 118 else: 119 res['status'] = False 120 res['error'] = rf.errors.as_data() 121 # 向ErrorDict中添加验证码项 122 res['error']['checkCode'] = ['验证码错误', ] if request.session['checkCode'].upper() != request.POST.get( 123 'checkCode', None).upper() else '' 124 125 return HttpResponse(json.dumps(res, cls=CustomJsonEncoder)) 126 else: 127 return redirect('/index.html') 128 129 130 def logout(request): 131 ''' 132 注销 133 :param request: 134 :return: 135 ''' 136 request.session.clear() 137 return redirect('/') 138 139 # def t(request): 140 # if request.method == 'GET': 141 # f = forms.LoginForm() 142 # return render(request,'t.html',{'f':f}) 143 # 144 # elif request.method == 'POST': 145 # print(request.POST) 146 # f = forms.LoginForm(request.POST) 147 # if f.is_valid(): 148 # print(f.cleaned_data) 149 # return HttpResponse(f.cleaned_data) 150 # else: 151 # print(f.errors) 152 # return HttpResponse(f.errors)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 from django.shortcuts import render, redirect, HttpResponse 4 from repository import models 5 from web import forms 6 import json,datetime 7 from utils.custom_json_encoder import CustomJsonEncoder 8 from dateutil.relativedelta import relativedelta 9 from django.db.models import F 10 from django.db import transaction 11 from django.utils.safestring import mark_safe 12 13 def index(request): 14 """ 15 博客首页,展示全部博文 16 :param request: 17 :return: 18 """ 19 article_list = models.Article.objects.all() # ;print('index') 20 21 if request.session.get('blog_nid',None): 22 visitor_blog = models.Blog.objects.filter(nid = request.session['blog_nid']).values('site').first() 23 else: 24 visitor_blog = None 25 26 return render(request, 'index.html', {'article_list': article_list,'visitor_blog':visitor_blog}) 27 28 29 def home(request, site): 30 """ 31 博主个人首页 32 :param request: 33 :param site: 博主的网站后缀如:http://xxx.com/wupeiqi.html 34 :return: 35 """ 36 if request.method == 'GET': 37 if request.session.get('username', None): 38 visitor = models.UserInfo.objects.filter(username=request.session['username']).select_related('blog').first() 39 visitor_blog = visitor.blog 40 else: 41 visitor = '' 42 visitor_blog = '' 43 44 cur_blog = models.Blog.objects.filter(site=site).select_related('user').first() 45 cur_user = cur_blog.user 46 articles = models.Article.objects.filter(blog=cur_blog) 47 48 # d_c 是 由日期(年+月)和文章数组成的元组们,组成的列表 49 dates = models.Article.objects.filter(blog=cur_blog).dates('create_time','month',order='DESC') 50 d_c = [] 51 for d in dates: 52 c = models.Article.objects.filter(create_time__date__gte = d,create_time__date__lte=d+relativedelta(months=1)).count() 53 d_c.append((d,c)) 54 55 return render(request, 'home.html', {'blog': cur_blog,'user':cur_user,'visitor_blog': visitor_blog,'visitor':visitor,'d_c':d_c,'articles':articles}) 56 else: 57 return redirect('/') 58 59 60 def filter(request, site, condition, val): 61 """ 62 分类显示 63 :param request: 64 :param site: 65 :param condition: 66 :param val: 67 :return: 68 """ 69 cur_blog = models.Blog.objects.filter(site=site).select_related('user').first() 70 cur_user = cur_blog.user 71 template_name = "home_summary_list.html" 72 73 if request.session.get('username', None): 74 visitor = models.UserInfo.objects.filter(username=request.session['username']).select_related('blog').first() 75 visitor_blog = visitor.blog 76 else: 77 visitor = '' 78 visitor_blog = '' 79 80 # d_c 是 由日期(年+月)和文章数组成的元组们,组成的列表 81 dates = models.Article.objects.filter(blog=cur_blog).dates('create_time','month',order='DESC') 82 d_c = [] 83 for d in dates: 84 c = models.Article.objects.filter(create_time__date__gte = d,create_time__date__lte=d+relativedelta(months=1)).count() 85 d_c.append((d,c)) 86 87 if condition == 'tag': 88 article_list = models.Article.objects.filter(tags__title=val, blog=cur_blog).all() 89 elif condition == 'category': 90 article_list = models.Article.objects.filter(category__title=val, blog=cur_blog).all() 91 elif condition == 'date': 92 article_list = models.Article.objects.filter(blog=cur_blog).extra( 93 where=['date_format(create_time,"%%Y-%%m")=%s'], params=[val, ]).all() 94 else: 95 article_list = [] 96 97 98 return render(request, template_name,{'article_list':article_list,'blog': cur_blog,'user':cur_user,'visitor_blog': visitor_blog,'visitor':visitor,'d_c':d_c}) 99 100 101 def detail(request, site, nid): 102 """ 103 博文详细页 104 :param request: 105 :param site: 106 :param nid: 107 :return: 108 """ 109 cur_blog = models.Blog.objects.filter(site=site).select_related('user').first() 110 cur_article = models.Article.objects.filter(nid=nid).first() 111 cur_user = cur_blog.user 112 cur_comments = models.Comment.objects.filter(article=cur_article) #;print( mark_safe(cur_comments[5].content)) 113 if request.method == 'GET': 114 if request.session.get('username', None): 115 visitor = models.UserInfo.objects.filter(username=request.session['username']).select_related('blog').first() 116 visitor_blog = visitor.blog 117 else: 118 visitor = '' 119 visitor_blog = '' 120 121 # 阅读数 +1 122 cur_article.read_count += 1 123 cur_article.save() 124 125 # d_c 是 由日期(年+月)和文章数组成的元组们,组成的列表 126 dates = models.Article.objects.filter(blog=cur_blog).dates('create_time', 'month', order='DESC') 127 d_c = [] 128 for d in dates: 129 c = models.Article.objects.filter(create_time__date__gte=d, 130 create_time__date__lte=d + relativedelta(months=1)).count() 131 d_c.append((d, c)) 132 133 return render(request, 'home_detail.html', 134 {'article': cur_article, 'user': cur_user, 'blog': cur_blog, 'comments': cur_comments, 135 'visitor_blog': visitor_blog,'visitor':visitor,'d_c':d_c}) 136 elif request.method == 'POST': # 可以做对 提交评论 的处理,或者 让 提交评论 由单独的视图函数处理 137 pass 138 else: 139 return redirect('/') 140 141 def auth(func): 142 ''' 143 验证 ajax请求的操作者是否是登录用户 144 :param request: 145 :return: 146 ''' 147 148 def wrapper(request,*args,**kwargs): 149 if request.session.get('username',None): 150 return func(request,*args,**kwargs) 151 else: 152 res = {'status': False, 'error': '请登录', 'data': None} 153 return HttpResponse(json.dumps(res)) 154 return wrapper 155 156 @auth 157 def comment(request): 158 ''' 159 对 用户对某文章提交评论 的处理 160 :param request: 161 :return: 162 ''' 163 if request.method == 'POST': 164 res = {'status': True, 'error': None, 'data': None} 165 cf = forms.CommentForm(request.POST) 166 if cf.is_valid(): 167 with transaction.atomic(): 168 cur_user = models.UserInfo.objects.filter(username=cf.cleaned_data['username']).first() 169 c_obj = models.Comment.objects.create( 170 content=cf.cleaned_data['content'], 171 user=cur_user, 172 article_id=cf.cleaned_data['article_id'] 173 ) 174 print(cf.cleaned_data['content']) 175 # print('reply:',cf.cleaned_data['reply']);print('reply_class:',type(cf.cleaned_data['reply'])) 176 reply_id_list = cf.cleaned_data['reply'].split(',') if cf.cleaned_data['reply'] else [] 177 c_obj.reply.add(*reply_id_list) 178 179 # 评论数+1 180 models.Article.objects.filter(nid=cf.cleaned_data['article_id']).update(comment_count = F('read_count')+1 ) 181 182 else: 183 res['status'] = False 184 res['error'] = cf.errors.as_data() 185 return HttpResponse(json.dumps(res, cls=CustomJsonEncoder)) 186 else: 187 return redirect('/') 188 189 @auth 190 def updown(request): 191 ''' 192 点赞/踩 193 :param request: 194 :return: 195 ''' 196 res = {'status':True,'error':None,'data':None} 197 data = {};print(request.POST) 198 for k,v in request.POST.items(): 199 data[k] = v 200 data['username'] = request.session['username'];print('data:',data) 201 udf = forms.UpDownForm(data) 202 203 if udf.is_valid(): 204 with transaction.atomic(): 205 article = models.Article.objects.filter(nid=udf.cleaned_data['article_id']).first() 206 if udf.cleaned_data['up'] == '0': 207 up = False 208 res['data'] = article.down_count = article.down_count + 1 209 else: 210 up = True 211 res['data'] = article.up_count = article.up_count + 1 212 213 article.save() 214 models.UpDown.objects.create( 215 article=article, 216 user = models.UserInfo.objects.filter(username=udf.cleaned_data['username']).first(), 217 up=up, 218 ) 219 220 else: 221 res['status'] = False 222 res['error'] = udf.errors.as_data() 223 224 return HttpResponse(json.dumps(res,cls=CustomJsonEncoder)) 225 226 227 def t(request): 228 # print(request.GET) 229 230 func = request.GET.get('callback',None) 231 232 # return HttpResponse('callback(123456)') 233 return HttpResponse('%s("From EdmureBlog")'%func)
1 #! /usr/bin/env python3 2 # -*- coding:utf-8 -*- 3 4 from django.forms import Form 5 from django.forms import fields 6 from django.forms import widgets 7 from repository import models 8 from django.core.validators import ValidationError,RegexValidator 9 10 11 class RegisterForm(Form): 12 '''验证注册信息''' 13 username = fields.CharField(max_length=32,error_messages={'required':'请填写用户名'}) 14 password = fields.CharField(max_length=64,error_messages={'required':'请填写密码'}) 15 confirm_password = fields.CharField(max_length=64,error_messages={'required':'请再次填写密码'}) 16 email = fields.EmailField(error_messages={'required':'请填写邮箱'}) 17 checkCode = fields.CharField(max_length=10,error_messages={'required':'请填写验证码'}) 18 19 def clean_username(self): 20 if models.UserInfo.objects.filter(username=self.cleaned_data['username']).count(): 21 raise ValidationError('用户名已存在') 22 else: 23 return self.cleaned_data['username'] 24 25 def clean_confirm_password(self): 26 if self.cleaned_data['password'] == self.cleaned_data['confirm_password']: 27 return self.cleaned_data['confirm_password'] 28 else: 29 raise ValidationError(message='两次密码输入不一致') 30 31 32 class LoginForm(Form): 33 '''验证登陆信息''' 34 username = fields.CharField(max_length=32,error_messages={'required':'请填写用户名'}) 35 password = fields.CharField(max_length=64,error_messages={'required':'请填写密码'}) 36 checkCode = fields.CharField(max_length=10, 37 error_messages={'required':'请填写验证码'}, 38 # validators={r'':''} 39 ) 40 autoLogin = fields.CharField(max_length=1,required=False) 41 42 def clean_username(self): 43 if models.UserInfo.objects.filter(username = self.cleaned_data['username']).count(): 44 return self.cleaned_data['username'] 45 else: 46 raise ValidationError('用户名不存在') 47 48 def clean(self): 49 if self.cleaned_data.get('username',None): 50 if models.UserInfo.objects.filter(username=self.cleaned_data['username'],password=self.cleaned_data['password']).count(): 51 return self.cleaned_data 52 else: 53 raise ValidationError('用户名或密码错误') 54 55 56 class CommentForm(Form): 57 '''验证文章评论''' 58 content = fields.CharField(error_messages={'required':'请填写评论内容',}) 59 article_id = fields.CharField() 60 username = fields.CharField() 61 reply = fields.CharField(required=False, 62 # validators={RegexValidator(r'(d+,)*d+'):'回复评论的id错误'} 63 ) 64 65 66 class UpDownForm(Form): 67 '''验证文章点赞/踩''' 68 article_id = fields.CharField(max_length=65535,error_messages={'required':'文章id 不存在'}) 69 up = fields.CharField() 70 username = fields.CharField() 71 72 def clean_atrticle_id(self): 73 if self.cleaned_data['article_id'].isdigit(): 74 return self.cleaned_data['article_id'] 75 else: 76 raise ValidationError('文章id必须是正整数') 77 78 def clean_up(self): 79 if self.cleaned_data['up'] in ['0','1']: 80 return self.cleaned_data['up'] 81 else: 82 raise ValidationError('up字段必须是0或1') 83 84 def clean(self): 85 print(models.UpDown.objects.filter( user__username=self.cleaned_data['username'],article_id = self.cleaned_data['article_id'] ).count()) 86 if models.UpDown.objects.filter( user__username=self.cleaned_data['username'],article_id = self.cleaned_data['article_id'] ).count(): 87 raise ValidationError('请不要重复评价') 88 else: 89 return self.cleaned_data
1 """EdmureBlog URL Configuration 2 3 The `urlpatterns` list routes URLs to views. For more information please see: 4 https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 Examples: 6 Function views 7 1. Add an import: from my_app import views 8 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 Class-based views 10 1. Add an import: from other_app.views import Home 11 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 Including another URLconf 13 1. Import the include() function: from django.conf.urls import url, include 14 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 """ 16 from django.conf.urls import url 17 from .views import home 18 from .views import account 19 20 urlpatterns = [ 21 # url(r'^t$', account.t), 22 url(r'^t$',home.t), 23 url(r'^login.html$', account.login), 24 url(r'^register.html$', account.register), 25 url(r'^check_code.html$', account.check_code), 26 url(r'^logout.html$', account.logout), 27 url(r'^comment.html$', home.comment), 28 29 url(r'^updown.html$',home.updown), 30 31 url(r'^(?P<site>w+)/(?P<condition>((tag)|(date)|(category)))/(?P<val>(w+-)*w+).html', home.filter), 32 url(r'^(?P<site>w+)/(?P<nid>d+).html', home.detail), 33 url(r'^(?P<site>w+).html', home.home), 34 url(r'^', home.index), 35 ]