糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > Python Day 70 利用Django框架做的一个bbs小项目

Python Day 70 利用Django框架做的一个bbs小项目

时间:2022-02-20 02:45:24

相关推荐

Python Day 70 利用Django框架做的一个bbs小项目

##项目开发流程

#1.项目需求分析产品经理+架构师+开发经理/组长 去到客户的公司谈需求(博弈的过程) #2.项目架构设计 架构师设计(数据库(主库:MySQL,从库:redis,mongodb),框架的选择,项目功能划分...) 报价:每个开发人员1500~3000300万多部门联合审批 #3.分组开发 开会 分任务 小组人员开始搬砖 (忘生成好的框架内填写代码即可)#4.测试1.开发人员自己测试(显而易见的bug)2.测试人员(妹纸) #5.交付上线1.自己的服务器2.对方的服务器

##项目表结构设计

#项目最最重要是表结构设计 如果一张表内的数据分成两块 一块是经常使用的 一块是不经常使用的,这个时候考虑数据优化的问题一对一关系1.一张表拆成了两张表2.两张表中的数据是一一对应的用户表(利用auth_user表)phoneavatarcreate_timeblog 一对一个人站点ps:DateField()auto_now:每次操作数据都会将当前时间更新auto_now_add:自动将创建该数据的时间记录下来之后不再改变个人站点表站点名称 site_name站点标题 site_title站点样式 site_theme文章表 文章标题 title文章简介 desc文章内容 content发布时间 create_timeblog 一对多个人站点category 一对多分类表tag 多对多标签表# 数据库设计优化(******) up_numdown_numcomment_num # 当你操作点赞点踩表或者评论表的时候 只要保证上面三个同步更新 标签表tag_nameblog一对多个人站点分类表category_nameblog 一对多个人站点点赞点踩表user 一对多user表article 一对多article表 is_up 0/1文章评论表user 一对多user表article 一对多article表 content create_timeparent自关联评论表 (to='self') # self表示的就是当前表 表与表之间关系判断表中的一条数据能否对于另外一张表的多条数

##表之间的关系

##各个功能分析及技术点

#1、forms组件models是models forms是forms 两者没有任何关系只是我们认为用forms组件校验models里面的数据通常是用来校验模型表的数据 ModelForm1.渲染标签2.校验数据3.展示信息from django import formsclass MyForm(forms.Form):username = forms.CharField(...)将数据当做字典传入类中is_valid() 校验数据多传不校验少传默认下不行的 cleaned_dataerrors渲染标签三种方式(render渲染)form_obj = MyForm() # get请求和post变量名必须是一样的if request.method == "POST":form_obj = MyForm(request.POST)return render(request,'...html',locals())1.{{ form_obj.as_p }}2.{{ form_obj.username.label }}{{ form_obj.username }}3.{% for foo in form_obj %}{{foo.label}}{{foo}}{{foo.errors.0}}{%endfor%}钩子函数#2、注册功能1.利用forms组件渲染前端页面1.局部钩子 校验用户名是否存在2.全局钩子 校验密码是否一致2.自己写前端获取用户头像1.利用label标签能够自动指向指定的input2.将img标签放入了label标签中,将默认的type=file框隐藏了3.用户选择头像之后动态展示出来"""img标签的src的值1.图片的路径地址2.图片的二进制数据3.后端url"""1.利用ajax给input绑定了一个change事件2.利用内置对象FileReader读取文件 var fileReader = new FileReader();var fileObj = $('input[type="file"]')[0].files[0];# 交给文件阅读器读取文件内容fileReader.readAsDataURL(fileObj) # 异步操作# 等待文件阅读器读取完毕fileReader.onload = function(){$('#img_avatar').attr('src',fileReader.result)}3.利用ajax提交post请求1.当用户点击提交按钮你需要做的事1.利用form标签的内置的可序列化方法将普通获取用户输入的文本框键值对自动获取出来$('#myform').serializeArray() # 获取到的是一个数组内部是一个个自定义对象2.利用each循环循环取出自定义对象里面的键值对(username,password,confirm_password,email,csrfmiddlewaretoken)3.既要传文件有要传数据 利用内置的FormData对象循环普通键值对var formData = new FormData();formData.append('','')# 添加文件数据formData.append('my_avatar',$('input[type="file"]')[0].files[0])4.发送formdata类型的数据 应该考虑 前后端传输数据的编码格式ps:1.urlencoded(form与ajax默认的提交数据编码格式),2.formdata,3.application/jsondata:formDatacontentType:false,processData:false,4.post请求后端逻辑1.将reuqest.POST中的数据直接交给forms组件校验2.如果校验通过 cleaned_data ps:在存储数据的时候models.Userinfo.objects.create_user(username='',password='')3.向将多余的键值对的去除 将文件键值对手动添加进去4.直接将字典打散传入 5.当数据校验不通过 返回错误信息错误信息(form_obj.errors)以json的形式返回给ajax的回调函数forms组件在渲染input框的时候 id值有固定格式 id_字段名6.回调函数中利用each循环手动拼接input框的id值将错误渲染到对应的input下面的span标签中7.健壮性功能 用户再次输入的时候 将报错信息移除$('input').focus(function(){$(this).next().html('').parent().removeClass('has-error')})#4、登陆功能1.手动搭建获取用户登录的前端页面 用户名 密码 验证码2.验证码功能ps:利用的是img标签src属性能够支持后端url书写 自动访问该url1.直接将后端一个图片以二进制形式发送2.利用pillow模块 动态生成图片pip3 install pillowfrom PIL import Image,ImageDraw,ImageFont# 生成图片Image.new('RGB',(280,40),'red')/Image.new('RGB',(280,40),(255,255,255))# 生成画笔对象 将生成好的图片对象传入ImageDraw.draw(img_obj)# 生成字体对象 将字体文件(.ttf)和字体大小传入ImageFont.truetype(字体文件,字体大小)# 如何生成随机验证码 ps:chr,ordimport randomcode = ''for i in range(5):upper_str = chr(random.randint(65,90))lower_str = chr(random.randint(97,122))random_int = str(random.randint(0,9))# 随机获取上面的一个 写到图片上res = random.choice([upper_str,lower_str,random_int])draw_obj.text((20+i*45,20),res,get_random(),font_obj)code += res# 需要将验证码存入session中以便后续的校验request.session['code'] = code"""1.自动生成一个随机字符串2.将键值对存入django自己创建session表中ps:django session默认的超时时间 两周 3.将生成好随机字符串返回给浏览器保存"""3.ajax提交数据1 先校验验证码是否一致2 在校验用户名和密码是否正确(auth模块自动auth.authenticate(username='',password=''))3.登陆成功之后记录用户状态 auth.login(request,user_obj)#5、主页1.将网站所有的文章都展示出来借助于django admin快速录入数据1.createsuperuser2.将需要操作的模型表注册到对应的应用下的admin.py文件中admin.site.register(models.Userinfo)3.录入数据的时候 关系绑定一定要考虑周全eg:用户与个人站点你得手动去绑定2.网站用的静态的资源默认放在static文件夹下用户上传的静态文件应该单独再开一个文件夹存放(media)3.去settings.py中配置路径MEDIA_ROOT = os.path.join(BASE_DIR,'media')# 用户上传的文件就会自动方法media文件夹4.将media文件夹暴露给用户手动开路由from django.views.static import servefrom BBS import settingsurl(r'^media/(?P<path>.*)',serve,{'document_root':settins.MEDIA_ROOT})5.media配置可以暴露后端任意文件夹中的资源#6、个人站点1.展示的是当前用户自己写的所有文章2.侧边栏展示article_list # 当前用户所有的文章1.文章标签# 查询当前用户下的每一个标签及标签下的文章数models.Tag.objects.filter(blog=blog).annotate(c=Count('article__id')).values_list('c','tag_name')2.文章分类# 同上 理3.日期归档# 参考的是官方文档3.侧边栏筛选功能1.视图函数 共用siteurl(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<params>.*)/',views.site)2.site视图函数形参修改site(request,username,**kwargs)3.基于当前用户所有的文章再做一层筛选 利用queryset对象可以无限制的调用queryset方法1.判断kwargs有没有值2.获取kwargs里面的值condition = kwargs.get('condition')params = kwargs.get('params')3.判断condition从而决定按照什么条件筛选category_idtag__id基于双下滑线 __year,__month#7、文章详情1.模板继承 需要解决侧边栏展示的问题2.自定义inclusion_tagfrom django.template import Libraryregister = Library()@register.inclusion_tag('left_menu.html')def left_menu(username):# 将个人站点视图函数中侧边栏相关代码直接拷贝过来 需要什么补什么 ...3.使用 {% load my_tag %} {% left_menu username %}4.点赞点踩1.拷贝博客园样式(html+css)2.给赞和踩设置一个公共的类属性 然后给这个类属性绑定点击事件3.通过$(this).hasClass()判断用户点击的是赞还是踩4.ajax发送5.后端接收1.is_up是一个字符串形式的js布尔值类型你可以利用json直接转成python布尔值2.首先判断当前全球是否是ajax全球3.判断用户是否登陆request.user.is_authenticated()4.判断当前文章是否是当前用户自己写的根据前端传过来的id值 查询出对应的文章对象文章对象获取用户对象跟request.user比较5.判断当前文章是否已经被当前用户点过只需要去点赞点踩查数据 如果有值就意味着 点过没有 就没有点过6.数据库操作文章表里面的普通字段根据is_up判断是up_num还是down_num更新点赞点踩表记录数据user articleis_up6.前端渲染成功或者失败的信息如果点赞点踩成功 你应该讲 对应的数字加1 注意数字应该先转成number类型再加一如果错误 找到span标签 将信息填入即可5.评论1.根评论1.前端页面搭建(参考博客园样式)2.给提交按钮绑点击事件1.将用户评论内容 当前文章id 发送给后端2.判断是否登陆(login_required)局部和全局配置@login_required(login_url='/login/')LOGIN_URL = '/login/'3.判断是否ajax请求4.利用事务"""事务四大特性:ACID"""from django.db import transactionwith transaction.atomic():# 事务操作3.渲染评论楼render渲染查询当前文章所有的评论 前端循环展示forloopcounter0counterfirstlastDOM临时渲染利用ecs6新语法 模板字符串var userName = 'jason'`my name is ${userName}`2.子评论1.parent字段设置的是null=True 所以在创建数据的时候该字段传None也不会有问题2.点击回复按钮要做的事情1.将回复按钮对应的评论的人名渲染到textarea中@人名2.textarea自动聚焦 并换行$('input').focus()ps:将评论对应的用户名和评论id存到了回复按钮3.将全局parentID设值3.每次提交成功成功将parentId再次清空4.将子评论中的@人名文本去除1.前端利用slice去除2.后端5.render渲染子评论1.判断当前评论对象是否有parant2.跨表查询 利用parent跨自己查根评论的用户名#8、后台管理1.只有登录的用户才能看到自己的后台管理2.罗列出当前用户写的所有的文章3.添加文章"""XSS攻击文章简介"""1.kindeditor编辑器2.直接利用form表单发送post请求3.文章标题 文章内容 文章简介1.利用bs4模块from bs4 import Beautifulsoupres = Beautifulsoup(content,'html.parser')res = res.find_all()for tag in res:print(tag.name)if tag.name == 'script':tag.decompose()desc = res.text[0:150]models.Article.objects.create(...content=str(res))4.上传图片 去文档里面找指定方法5.编辑6.删除#9、用户头像修改 如果用queryset的update方法 不会自动拼接avatar前缀可以使用对象点的方式修改

功能需求技术点

##代码实现

import os# Build paths inside the project like this: os.path.join(BASE_DIR, ...)BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))# Quick-start development settings - unsuitable for production# See /en/1.11/howto/deployment/checklist/# SECURITY WARNING: keep the secret key used in production secret!SECRET_KEY = 'm3^_^92%936ajzk9o#+f%(2858aw_yyjimi1#ncr(tghqghdr!'# SECURITY WARNING: don't run with debug turned on in production!DEBUG = TrueALLOWED_HOSTS = []# Application definitionINSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','app01.apps.App01Config',]MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','monMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',]ROOT_URLCONF = 'bbs.urls'TEMPLATES = [{'BACKEND': 'django.template.backends.django.DjangoTemplates','DIRS': [os.path.join(BASE_DIR, 'templates')],'APP_DIRS': True,'OPTIONS': {'context_processors': ['django.template.context_processors.debug','django.template.context_processors.request','django.contrib.auth.context_processors.auth','django.contrib.messages.context_processors.messages',],},},]WSGI_APPLICATION = 'bbs.wsgi.application'# Database# /en/1.11/ref/settings/#databasesDATABASES = {# 'default': {#'ENGINE': 'django.db.backends.sqlite3',#'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),# }'default': {'ENGINE': 'django.db.backends.mysql','NAME': 'bbs','USER':'root','PASSWORD':'123','HOST':'127.0.0.1',}}# Password validation# /en/1.11/ref/settings/#auth-password-validatorsAUTH_PASSWORD_VALIDATORS = [{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',},{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',},{'NAME': 'django.contrib.monPasswordValidator',},{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',},]# Internationalization# /en/1.11/topics/i18n/LANGUAGE_CODE = 'en-us'TIME_ZONE = 'UTC'USE_I18N = TrueUSE_L10N = TrueUSE_TZ = True# Static files (CSS, JavaScript, Images)# /en/1.11/howto/static-files/STATIC_URL = '/static/'STATICFILES_DIRS = [os.path.join(BASE_DIR,'static'),]#auth模块AUTH_USER_MODEL = 'app01.Userinfo'#规定用户上传到的静态资源统一放到media文件夹MEDIA_ROOT = os.path.join(BASE_DIR,'media')#配置默认的登录地址LOGIN_URL = '/login/'

settings

from django.db import modelsfrom django.contrib.auth.models import AbstractUser# Create your models here.#定义用户表,扩展auth模块class Userinfo(AbstractUser):phone = models.CharField(max_length=11,null=True,blank=True)#blank=True是用来admin后台管理 该参数可以为空#当用户上传自己的投降的时候 会将用户上传的头像文件自动存入avatar文件夹下avatar = models.FileField(upload_to='avatar',default='avatar/default.jpg')#创建数据的时候自动添加当前时间,注意auto_now_add(创建时自动创建时间,后续不会自动修改)和auto_now(实时更新)的区别create_time = models.DateField(auto_now_add=True)#用户表和个人站点表一对一关系blog = models.OneToOneField(to='Blog',null=True)class Meta:verbose_name_plural = '用户表' #跟数据库无关,仅仅根admin后台相关#定义个人站点表class Blog(models.Model):# 站点名称site_name = models.CharField(max_length=32)# 站点标题site_title = models.CharField(max_length=32)#站点样式:存放css文件路径site_theme = models.CharField(max_length=64)def __str__(self):return self.site_nameclass Meta:verbose_name_plural = '个人站点表' #跟数据库无关,仅仅根admin后台相关#定义文件标签表class Tag(models.Model):#标题名称tag_name = models.CharField(max_length=32)# 个人站点外键:一对多blog = models.ForeignKey(to='Blog')def __str__(self):return self.tag_nameclass Meta:verbose_name_plural = '标签表' #跟数据库无关,仅仅根admin后台相关#定义文章分类表class Category(models.Model):# 分类名称category_name = models.CharField(max_length=32)# 个人站点外键:一对多blog = models.ForeignKey(to='Blog')def __str__(self):return self.category_nameclass Meta:verbose_name_plural = '分类表' #跟数据库无关,仅仅根admin后台相关#定义文章表class Article(models.Model):# 文章标题title = models.CharField(max_length=32)# 文章描述desc = models.CharField(max_length=255)# 文本内容:存放大段文本content = models.TextField()create_time = models.DateField(auto_now_add=True)# 数据库优化相关字段(不需要跨表查询啦),# 当你操作点赞点踩表或者评论表的时候 只要保证上面三个同步更新# 点赞up_num = models.IntegerField(default=0)# 点踩down_num = models.IntegerField(default=0)# 评论数comment_num = models.IntegerField(default=0)# 个人站点表:一对多blog = models.ForeignKey(to='Blog',null=True)# 分类表:一对多category = models.ForeignKey(to='Category',null=True)# 标签表:多对多,这里使用到了through属性,因为django中建立多对多表时不需要手动创建,这种是全自动,但是我们该如何给# 自动创建的第三张表添加自己需要的字段呢?所以需要到了该属性tag = models.ManyToManyField(to='Tag',through='Artical2Tag',through_fields=('article','tag'))class Meta:verbose_name_plural = '文章' #跟数据库无关,仅仅根admin后台相关def __str__(self):return self.title# 定义多对多表中的第三张表class Artical2Tag(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')# 传布尔类型 数据库里面会存成 0/1is_up = models.BooleanField()class Meta:verbose_name_plural = '点赞点踩表' #跟数据库无关,仅仅根admin后台相关# 定义评论表class Comment(models.Model):# 外键用户表:一对多user = models.ForeignKey(to='Userinfo')# 外键文章表:一对多article = models.ForeignKey(to='Article')# 评论内容content = models.TextField()create_time = models.DateField(auto_now_add=True)# 自关联,可以指使用'self',更能通俗易懂,也可以用表明parent = models.ForeignKey(to='self',null=True)class Meta:verbose_name_plural = '评论表' #跟数据库无关,仅仅根admin后台相关

models

from django.conf.urls import urlfrom django.contrib import adminfrom django.views.static import servefrom bbs import settingsfrom app01 import viewsurlpatterns = [url(r'^admin/', admin.site.urls),url(r'^register/', views.register),url(r'^login/', views.login),url(r'^get_code/', views.get_code),url(r'^home/', views.home),#用户相关功能url(r'^set_password/', views.set_password),url(r'^logout/', views.logout),#路由相关配置url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT}),url(r'^up_or_down/',views.up_or_down),#评论url(r'^comment/',ment),#后台管理url(r'^backend/',views.backend),url(r'^add_article/',views.add_article),url(r'^upload_img/',views.upload_img),#用户修改头像url(r'^set_avatar/',views.set_avatar),#个人站点,正则匹配起别名占位url(r'^(?P<username>\w+)/$',views.site),#侧边栏帅选功能# url(r'^(?P<username>\w+)/(?P<category>category)/(?P<paream>\d+)/',views.site),# url(r'^(?P<username>\w+)/(?P<tag>tag)/(?P<paream>\d+)/',views.site),# url(r'^(?P<username>\w+)/(?P<archive>archive)/(?P<paream>\w+)/',views.site),# 一条路由实现上面三条路由的功能#url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<pareams>.*)/',views.site),url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<params>.*)/',views.site),#文章详情页url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/',views.article_detail),]

url

#forms组件验证模板类from django import formsfrom app01 import modelsclass RegForm(forms.Form):username = forms.CharField(max_length=8,min_length=3,label='用户名',error_messages={'max_length':'用户最长八位','min_length':'用户最短三位','required':'用户名不能为空',},widget=forms.widgets.TextInput(attrs={'class':'form-control'}))password = forms.CharField(max_length=8, min_length=3, label='密码',error_messages={'max_length': '密码最长八位','min_length': '密码最短三位','required': '密码不能为空',}, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) #forms.widgets.PasswordInput密码不显示 )confirm_password = forms.CharField(max_length=8, min_length=3, label='确认密码',error_messages={'max_length': '密码最长八位','min_length': '密码最短三位','required': '密码不能为空',}, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})# forms.widgets.PasswordInput密码不显示 )email = forms.EmailField(label='邮箱',error_messages={'invalid':'邮箱格式不正确','required':'邮箱不能为空'},widget=forms.widgets.EmailInput(attrs={'class':'form-control'}))# 局部钩子 判断当前用户名是否存在def clean_username(self):#判断当前用户是否存在username = self.cleaned_data.get('username')user_obj = models.Userinfo.objects.filter(username=username).first()if user_obj:self.add_error('username','用户已存在')else: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','两次密码不一致')else:return self.cleaned_data

myforms

from django.contrib import adminfrom app01 import models# Register your models here.admin.site.register(models.Userinfo)admin.site.register(models.Blog)admin.site.register(models.Tag)admin.site.register(models.Category)admin.site.register(models.Article)admin.site.register(models.Artical2Tag)admin.site.register(models.UpAndDown)admin.site.register(ment)

admin

import jsonimport randomfrom django.shortcuts import render,HttpResponse,redirectfrom django.utils.safestring import mark_safefrom app01.myforms import RegFormfrom app01 import modelsfrom django.http import JsonResponsefrom django.contrib import authfrom django.db.models.functions import TruncMonthfrom django.db.models import Ffrom app01.utils.my_page import Pagination# Create your views here.def register(request):# 当你用ajax进行数据交互 通常 试图函数都会事先定义一个字典back_dic = {'code':None,"msg":None}form_obj = RegForm()if request.method == 'POST':#利用forms组件校验普通字段的数据,即把传过来的数据放到ReForm去校验form_obj = RegForm(request.POST)if form_obj.is_valid():# 校验通过,从claend_data拿到所有数据clean_data = form_obj.cleaned_data"""clean_data = {'username':'','password':'','confirm_password':'','email':''}"""# 将confirm_password键值对从clean_data去除掉clean_data.pop('confirm_password')#先获取头像文件对象 用户是否上传avatar = request.FILES.get('my_avatar')if avatar:clean_data['avatar'] = avatar"""clean_data = {'username':'','password':'','email':'','avatar':'文件对象'}"""# print(clean_data)#将数据保存到数据库中models.Userinfo.objects.create_user(**clean_data)back_dic['code'] = 2000back_dic['msg'] = '注册成功'back_dic['url'] = '/login/'else:back_dic['code'] = 2001back_dic['msg'] = form_obj.errorsreturn JsonResponse(back_dic)return render(request,'register.html',locals())def login(request):back_dic = {'code':None,"msg":None}# request.is_ajax() # 判断当前请求是ajax请求if request.method == 'POST':username = request.POST.get('username')password = request.POST.get('password')code = request.POST.get('code')# 验证码忽略大小写进行比较if request.session.get('code').upper() == code.upper():#利用auth认证验证用户信息,因为密码也是密文存入数据库中的user_obj = auth.authenticate(username=username,password=password)# 类似于但不是等于,注意哈models.Userinfo.objects.filter(username=username,password=password).first()if user_obj:# print(request.user.is_authenticated()) # 判断当前用户是否登录 这条语句在auth.login之前,结果为Falseauth.login(request,user_obj)"""作用:1. 设置cookie, session,类似于request.session['username'] = user_obj2. 生成request.user的对象, 这个对象可以再视图函数中使用 3. request.user这个对象 相当于 request.session,后续可以在任意位置通过request.user拿到当前登录对象"""back_dic['code'] =100back_dic['msg'] = '登录成功'back_dic['url'] = '/home/' #设置一个让前端登陆成功后跳转到home主页# print(request.user.is_authenticated()) # 判断当前用户是否登录 这条语句在auth.login之后,结果为Trueelse:back_dic['code'] = 200back_dic['msg'] = '用户名或密码错误'else:back_dic['code'] = 300back_dic['msg'] = '验证码错误'return JsonResponse(back_dic)return render(request,'login.html')from PIL import Image,ImageDraw,ImageFontfrom io import BytesIO# 生成三个随机数def get_random():return random.randint(0,255),random.randint(0,255),random.randint(0,255)def get_code(request):# 生成一个图片对象img_obj = Image.new('RGB',(260,30),get_random())#在当前图片上生成一个画笔img_draw = ImageDraw.Draw(img_obj)# 设置样式img_font = ImageFont.truetype('static/font/222.ttf',30)# 图片验证码 (数字 小写字母 大写字母) 五位验证码 1aZd2# A-Z:65-90 a-z:97-122# 定义一个变量用来存储验证码code = ''for i in range(5):upper_str = chr(random.randint(65,90))lower_str = chr(random.randint(97,122))random_int = str(random.randint(0,9))# 从上面三个里面随机选择一个res = random.choice([upper_str,lower_str,random_int])#是用画笔对象在生成的图片上写一个一个的验证码,注意书写的坐标位置是在变化的img_draw.text((40+ i * 40,-5),res,get_random(),img_font)code += res#保存到内存管理器中,需要用到BytesIO模块# 就把它当成文件句柄io_obj = BytesIO()img_obj.save(io_obj,'png')# 图片格式必须得传#找一个公共的地方存储验证码 以便后续其他视图函数校验request.session['code']=code#讲些好的图片返回到前端img标签src属性中(再次说一下src属性可以存放三种:1、静态路径地址 2、二进制数据 3、url地址)# io_obj.getvalue() 获取二进制数据,return HttpResponse(io_obj.getvalue())def home(request):article_list = models.Article.objects.all()return render(request,'home.html',locals())def set_password(request):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')if new_password == confirm_password:if request.user.check_password(old_password):request.user.set_password(new_password)request.user.save()return redirect('/login/')return render(request,'set_password.html')def logout(request):auth.logout(request)return redirect('/login/')from django.db.models import Countdef site(request,username,*args,**kwargs):user_obj = models.Userinfo.objects.filter(username=username).first()blog = user_obj.blog#查询当前用户下对应的文章列表article_list = models.Article.objects.filter(blog=blog)# 当前用户所有的文章if not user_obj:return render(request,'error.html')username = user_obj.username#判断kwagrs是否有值,如果有值你应该对上面的article_list在做一层筛选if kwargs:#两个值# {'condition':'','params':''}condition = kwargs.get('condition')params = kwargs.get('params')print(condition,params)if condition == 'category':#链式过滤查询,得到分类下对应的文章 注意category_id位文章表中真实存在的字段article_list = article_list.filter(category_id=params)elif condition == 'tag':# 链式过滤查询,得到标签下对应的文章 注意tag__id:有文章表跨表到标签表使用神奇双下划线article_list = article_list.filter(tag__id=params)elif condition == 'archive':# 链式过滤查询,得到年和月下对应的文章 -1year,month = params.split('-')#解压赋值 需要分别对年和月进行刷选article_list = article_list.filter(create_time__year=year,create_time__month=month)#下面是侧边栏渲染相关#查询当前用户每一个的分类及分类下的文章数,分类查文章:表明小写# category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values('category_name','c') 列表套字典# print(category_list) <QuerySet [{'category_name': 'icon分类一', 'c': 0}, {'category_name': 'icon分类二', 'c': 1}, {'category_name': 'icon分类三', 'c': 2}]>#列表套元组category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values_list('category_name', 'c','pk')#查询当前用户每一个标签下的文章数tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('tag_name', 'c','pk')# print(tag_list) <QuerySet [('icon标签一', 1), ('icon标签二', 1), ('icon标签三', 1)]>#日期归档# data_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values_list('month','c')date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values_list('month', 'c')"""pk:会自动帮你查找到当前表的主键字段"""# print(date_list)return render(request,'site.html',locals())def article_detail(request,username,article_id):#获取具体文章的对象article_obj = models.Article.objects.filter(pk=article_id).first()blog = article_obj.blog#将当前文章的所有评论内容查询出来发给前端渲染comment_list = ment.objects.filter(article=article_obj)#分页page_obj = Pagination(current_page=request.GET.get('page',1),all_count=comment_list.count())page_queryset = comment_list[page_obj.start:page_obj.end]return render(request,'article_detail.html',locals())#点赞点踩def up_or_down(request):back_dic = {"code": None, 'msg': ''}#这里可以来个骚操作,判断ajax请求if request.is_ajax():"""1.用户是否登陆2.有没有点过3.自己不能给自己点赞4.数据库同步articleupanddown数据要同步"""is_up = request.POST.get('is_up') # 你拿到的是js格式的布尔值 对应到python里面就是字符串is_up = json.loads(is_up) # 利用json模块 将字符串形式转成python里面的布尔值article_id = request.POST.get('article_id')#1、判断用户是否登陆if request.user.is_authenticated():#2、判断当前文章是不是用户自己写的,即判断当前文章对象和当前登录用户是否是一个人#先拿到当前文章对象article_obj = models.Article.objects.filter(pk=article_id).first()if article_obj:if not article_obj.blog.userinfo.pk == request.user.pk:#3、判断当前文章是否被点过了,即去点赞表查是否被点过了 逆向思维is_click = models.UpAndDown.objects.filter(user=request.user,article=article_obj)if not is_click:#没有点过,记录数据,问题:到底是点赞还是点踩if is_up:#如果是点赞models.Article.objects.filter(pk=article_id).update(up_num = F('up_num')+1)else:models.Article.objects.filter(pk=article_id).update(up_num=F('down_num') + 1)#点赞点踩操作点赞点踩表写入数据models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up)back_dic['code'] = 2000back_dic['msg'] = '点赞成功' if is_up else '点踩成功'else:back_dic['code'] = 2001back_dic['msg'] = '你已经点过了'else:back_dic['code'] = 2002back_dic['msg'] = '你个臭不要脸的 不能给自己点'else:back_dic['code'] = back_dic['msg'] = '未知错误'else:back_dic['code'] = #这里使用后端取消转义字符back_dic['msg'] = mark_safe('请先<a href="//login">登录</a>')return JsonResponse(back_dic)"""事务:ACID原子性一致性隔离性持久性将评论内容同步写入到评论表和文章表,这里用到事务的原子性,要么一起成功,要么一起失败"""from django.contrib.auth.decorators import login_requiredfrom django.db import transaction # django中开启事务 需要先倒入该模块@login_required #全局登录认证装饰器def comment(request):back_dic = {'code':None,'msg':''}if request.is_ajax():comment = request.POST.get('comment')article_id = request.POST.get('article_id')parent_id = request.POST.get('parent_id')with transaction.atomic():# 在with代码块写的就是一个事务# 文章表修改comment_num字段models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num')+1)# 评论表里面新增数据ment.objects.create(user=request.user,article_id=article_id,content=comment,parent_id=parent_id)back_dic['code'] = 2000back_dic['msg'] = '评论成功'return JsonResponse(back_dic)@login_requireddef 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_requireddef add_article(request):if request.method == 'POST':title = request.POST.get('title')content = request.POST.get('content')# 能够帮我拿到当前用户写的所有的标签 将script删除res = BeautifulSoup(content,'html.parser')#获取所有标签tags = res.find_all()for tag in tags:# 将script全部删除if tag.name == 'script':# 删除指定的标签tag.decompose()# 获取用户输入的文本值desc = res.text[0:150]models.Article.objects.create(title=title,content=str(res),desc=desc,blog=request.user.blog)return redirect('/backend/')return render(request,'backend/add_article.html')from bbs import settingsimport os@login_requireddef upload_img(request):back_dic = {'error': ''}if request.method == 'POST':# print(request.FILES) 查看字典的key 和文件值img_obj = request.FILES.get('imgFile')# 规定编辑器上传的图片全部放到media文件夹里面的upload_img文件夹下# 1.将文件存储到media文件夹下path = os.path.join(settings.BASE_DIR, 'media', 'upload_img')if not os.path.exists(path):os.mkdir(path)file_path = os.path.join(path, img_obj.name)with open(file_path, 'wb') as f:for line in img_obj:f.write(line)# 2.将文件路径返回给前端back_dic['error'] = 0back_dic['url'] = '/media/upload_img/%s' % img_obj.name"""//成功时{"error" : 0,"url" : "/path/to/file.ext"}//失败时{"error" : 1,"message" : "错误信息"}"""return JsonResponse(back_dic)#用户修改头像@login_requireddef set_avatar(request):username = request.user.usernameif request.method == 'POST':new_avatar = request.FILES.get('new_avatar')# 如果用queryset对象更新头像 不会自动帮你拼接前缀,要手动拼接# models.Userinfo.objects.filter(pk=request.user.pk).update(avatar = new_avatar)#或者直接通过对象点属性来进行修改,但是这样的缺点是该对象的整个信息都会从新写一遍user_obj = models.Userinfo.objects.filter(pk=request.user.pk).first()user_obj.avatar = new_avataruser_obj.save()return redirect('/home/')return render(request,'set_avatar.html',locals())

views

from django.template import Libraryfrom app01 import modelsfrom django.db.models import Countfrom django.db.models.functions import TruncMonthregister = Library()#自定义inclusion_tag@register.inclusion_tag('left_menu.html')def left_menu(username):blog = models.Blog.objects.filter(site_name=username).first()# 列表套元组category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values_list('category_name','c', 'pk')# 查询当前用户每一个标签下的文章数tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('tag_name', 'c', 'pk')# print(tag_list) <QuerySet [('icon标签一', 1), ('icon标签二', 1), ('icon标签三', 1)]># 日期归档# data_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values_list('month','c')date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values_list('month', 'c')"""pk:会自动帮你查找到当前表的主键字段"""# print(date_list)return locals()

my_tag include_tag过滤器

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><script src="/jquery/3.4.1/jquery.min.js"></script><link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css"><script src="/static/bs-3.3.7/js/bootstrap.min.js"></script></head><body><div class="container"><div class="row"><h1 class="text-center">注册</h1><hr><form action="" id="myform" novalidate>{% csrf_token %}{% for foo in form_obj %}<div class="form-group"><label for="{{ foo.auto_id }}">{{ foo.label }}</label>{{ foo }}<span class="pull-right" style="color: red"></span></div>{% endfor %}<div class="form-group">{# label标签for属性指向input,点击头像调用input功能#}<label for="id_file">头像<img src="/static/image/default.jpg" alt="" width="80px" style="margin-left: 20px" id="id_img"></label><input type="file" name="myfile" id="id_file" class="hidden"></div><input type="button" class="btn btn-success pull-right" value="注册" id="id_submit"></form></div></div></body><script>$('#id_file').change(function () {{#获取文件对象#}var fileObj = $(this)[0].files[0];{#利用内置对象:文件阅读器FileReader #}var fileReader = new FileReader();{#将文件对象交给文件阅读器对象,生成文件对象的二进制数据 #}fileReader.readAsDataURL(fileObj); //异步,不会等待加载完毕就继续执行下面的语句{#DOM操作修改img标签的src属性值 #}fileReader.onload = function () {$('#id_img').attr('src',fileReader.result)};});// 点击注册按钮 触发点击事件$('#id_submit').click(function () {// 利用内置对象FormData完成既有普通键值又有文件数据的发送var formData = new FormData()// 添加普通键值对// console.log($('#myform').serializeArray()); // 会将form标签内 普通的键值对 自动组成一个数组的形式返回给你$.each($('#myform').serializeArray(),function (index,obj) {// $.each(你想要被循环的对象,函数(索引,单个单个的对象))formData.append(obj.name,obj.value) // 仅仅是将普通的键值对添加进去});{# 添加文件数据 #}formData.append('my_avatar',$('#id_file')[0].files[0])//ajax发送数据$.ajax({url:'',type:'post',data:formData,contentType:false,processData: false,success:function (data) {if (data.code == 2000){window.location.href=data.url}else {{#console.log(data.msg)#}$.each(data.msg,function (index,obj) {{#console.log(index,obj)#}//字符串拼接得到input对应的id值,在前端可查到input的id格式位id_username等var targetid = '#id_'+ index;//查找span标签,添加文本信息这里使用html()方法,也可使用text()方法添加$(targetid).next().html(obj[0]).parent().addClass('has-error')})}}})})// input框获取焦点事件$('input').focus(function () {// 将当前input所在的div移除has-error属性 并将下面的span标签内的内容也移除了$(this).next().html('').parent().removeClass('has-error')})</script></html>

register.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><script src="/jquery/3.4.1/jquery.min.js"></script><link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css"><script src="/static/bs-3.3.7/js/bootstrap.min.js"></script></head><body><div class="container"><div class="row"><div class="col-md-6 col-md-offset-3"><h1 class="text-center">登录</h1><hr><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-froup"><label for="id_code">验证码</label><div class="row"><div class="col-md-6"><input type="text" name="code" id="id_code" class="form-control"></div><div class="col-md-6"><img src="/get_code/" alt="" width="260px" height="30px" id="id_img"></div></div></div><br><input type="button" class="btn btn-info" value="登录" id="id_submit"><span style="color: red" id="id_errro" class="pull-right"></span></div></div></div></body><script>{#点击验证码局部双薪验证码 #}$('#id_img').click(function () {var oldPath = $(this).attr('src');{#方法一:直接在src属性后面添加任意字符,这里添加一个问好#}{#$(this).attr('src',oldPath += '?');#}// 方法二:优化思路 判断当前url后面是否有问号 有问号就去掉 没有问号就加上if( oldPath.includes('?')){oldPath.substring(0,oldPath.length-1)}else {oldPath += '?'}$(this).attr('src',oldPath)})$('#id_submit').click(function () {$.ajax({url:'',type:'post',data:{'username':$('#id_username').val(),'password':$('#id_password').val(),'code':$('#id_code').val(),'csrfmiddlewaretoken':'{{ csrf_token }}'},success:function (data) {if (data.code == 100){{# 登录成功跳转到主页 #}window.location.href = data.url}else {$('#id_errro').html(data.msg)}}})})</script></html>

login.html

{% extends 'base.html' %}{% block css %}<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/image/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/image/downdown.gif') no-repeat;text-align: center;cursor: pointer;margin-top: 2px;padding-top: 5px;}.clear {clear: both;}.diggword {margin-top: 5px;margin-left: 0;font-size: 12px;color: gray;}</style>{% endblock %}{% block content %}<h1>{{ article_obj.title }}</h1>{# 取消转义 #}{{ article_obj.content |safe }}{#点赞点踩 #}<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.dowm_num }}</span></div><div class="clear"></div><div class="diggword" id="digg_tips"><span style="color: red" id="id_info"></span></div></div></div>{#评论楼渲染 #}{# #51楼 -12-05 15:50 漏网的小鱼 #}<h4>评论列表</h4><ul class="list-group">{% for comment in page_queryset %}<li class="list-group-item"><div><span>#{{ forloop.counter }}楼</span><span>{{ comment.create_time|date:'Y-m-d' }}</span><span><a href="/{{ comment.user.username }}/">{{ comment.user.username }}</a></span><span class="pull-right reply" username="{{ comment.user.username }}" comment_id="{{ comment.id }}"><a>回复</a></span></div>{#comment.parent判断评论表中的parent字段存不存在#}{% if comment.parent %}{#comment.parent.user.username 从评论表跨到用户表查询根评论的用户名#}<p><a href="#">@</a>{{ comment.parent.user.username }}</p>{% endif %}{{ comment.content }}</li>{% endfor %}{{ page_obj.page_html|safe }}</ul>{#评论样式#}<div><p><span class="glyphicon glyphicon-comment">发表评论</span></p><p>昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"value="{{ request.user.username }}"></p><p>评论内容:</p><p><textarea name="" id="id_comment" cols="60" rows="10"></textarea></p><p><input type="button" class="btn btn-primary" value="提交评论" id="id_submit"></p></div><script>//点赞点踩$('.action').click(function () {{#判断是点赞还是点踩的关键代码#}var isUp = $(this).hasClass('diggit');{#获取点赞和点踩所在的div层,要对数值进行操作#}var $spanEle = $(this).children();$.ajax({url: '/up_or_down/',type: 'post',data: {'is_up': isUp, 'article_id':{{article_obj.pk}}, 'csrfmiddlewaretoken': '{{ csrf_token }}'},success: function (data) {if (data.code == 2000) {{#给span标签设置值#}$('#id_info').html(data.msg);$spanEle.text(Number($spanEle.text()) + 1);} else {{#点赞失败后给span设置值#}$('#id_info').html(data.msg)}}})})//评论逻辑//定义一个全局的parentid变量名parentId = null;//点击回复按钮发生了几件事// 1.将根评论的人的名字放到了textarea中(@用户名)// 2.自动换行// 3.textarea自动获取焦点//1.在存储数据的时候 应该将@用户名去掉(前端 后端)//2.textarea应该清空//3.全局的parentid每次发送评论之后应该自动清空$('#id_submit').click(function () {//获取评论内容var comment = $('#id_comment').val();if(parentId){// 将comment中的@人名清空掉// 先获取\n所在的索引值var nIndex = comment.indexOf('\n'); // 根据内容查索引comment = comment.slice(nIndex)}$.ajax({url: '/comment/',type: 'post',data: {'comment': comment,'article_id':{{ article_id }},'csrfmiddlewaretoken': '{{ csrf_token }}','parent_id':parentId},success: function (data) {if(data.code ==2000){//DOM操作渲染标签//获取用户名var username = '{{ request.user.username }}';//获取评论内容var comment =$('#id_comment').val()//动态生成li标签var tempStr = `<li class="list-group-item"><div><span class='glyphicon glyphicon-comment'>${username}</span></div>${comment}</li>`;// 查找url标签 将上面的字符串追加到ul标签内$('.list-group').append(tempStr)$('#id_comment').val('')parentId = null;}}})})//点击恢复按钮发生了下面三件事$('.reply').click(function () {// 获取当前回复按钮所对应的根评论的用户名var username = $(this).attr('username');//评论内容区域设置@用户名换行,focus自动获取焦点$('#id_comment').html('@'+username+'\n').focus();//将全局的parentId修改为被评论对象的id,这个id设置在回复按钮的span属性中,为啥呢,因为这里用起来方便parentId=$(this).attr('comment_id')})</script>{% endblock %}

article_detail.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><script src="/jquery/3.4.1/jquery.min.js"></script><link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css"><script src="/static/bs-3.3.7/js/bootstrap.min.js"></script><link rel="stylesheet" href="/media/css/{{ blog.site_theme }}"><style>.d1{margin-top: 5px;}</style>{% block css %}{% endblock %}</head><body><nav class="navbar navbar-inverse"><div class="container-fluid"><!-- Brand and toggle get grouped for better mobile display --><div class="navbar-header"><button type="button" class="navbar-toggle collapsed" data-toggle="collapse"data-target="#bs-example-navbar-collapse-1" aria-expanded="false"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a class="navbar-brand" href="#">{{ blog.site_title }}</a></div><!-- Collect the nav links, forms, and other content for toggling -->{# <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">#}{# <ul class="nav navbar-nav">#}{#<li class="active"><a href="#">文章<span class="sr-only">(current)</span></a></li>#}{#<li><a href="#">随笔</a></li>#}{# </ul>#}{##}{# <ul class="nav navbar-nav navbar-right">#}{#{% if request.user.is_authenticated %}#}{#<li><a href="#">{{ request.user.username }}</a></li>#}{#<li class="dropdown">#}{# <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"#}{# aria-expanded="false">更多操作 <span class="caret"></span></a>#}{# <ul class="dropdown-menu">#}{# <li><a href="/set_password/">修改密码</a></li>#}{# <li><a href="#">修改头像</a></li>#}{# <li role="separator" class="divider"></li>#}{# <li><a href="/logout/">注销</a></li>#}{# </ul>#}{#</li>#}{#{% else %}#}{#<li><a href="/login/">登录</a></li>#}{#<li><a href="/register/">注册</a></li>#}{#{% endif %}#}{# </ul>#}{# </div><!-- /.navbar-collapse -->#}</div><!-- /.container-fluid --></nav><div class="container-fluid"><div class="row"><div class="col-md-3">{% load my_tag %}{% left_menu username %}</div><div class="col-md-9">{% block content %}{% endblock %}</div></div></div></body></html>

base.html

<!DOCTYPE html><html><head><meta charset="utf-8"><title>Error_404_资源不存在</title><style type="text/css">body{margin:8% auto 0;max-width: 550px; min-height: 200px;padding:10px;font-family: Verdana,Arial,Helvetica,sans-serif;font-size:14px;}p{color:#555;margin:10px 10px;}img {border:0px;}.d{color:#404040;}</style></head><body><a href="/"><img src="///images/logo_small.gif" alt="cnblogs"/></a><p><b>404.</b> 抱歉! 您访问的资源不存在!</p><p class="d">请确认您输入的网址是否正确,如果问题持续存在,请发邮件至contact&#64;与我们联系。</p><p><a href="/home/">返回网站首页</a></p></body></html>

error.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><script src="/jquery/3.4.1/jquery.min.js"></script><link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css"><script src="/static/bs-3.3.7/js/bootstrap.min.js"></script><style>.d1{margin-top: 5px;}</style></head><body><nav class="navbar navbar-inverse"><div class="container-fluid"><!-- Brand and toggle get grouped for better mobile display --><div class="navbar-header"><button type="button" class="navbar-toggle collapsed" data-toggle="collapse"data-target="#bs-example-navbar-collapse-1" aria-expanded="false"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a class="navbar-brand" href="#">博客园</a></div><!-- Collect the nav links, forms, and other content for toggling --><div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"><ul class="nav navbar-nav"><li class="active"><a href="#">文章<span class="sr-only">(current)</span></a></li><li><a href="#">随笔</a></li></ul><ul class="nav navbar-nav navbar-right">{% if request.user.is_authenticated %}<li><a href="#">{{ request.user.username }}</a></li><li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"aria-expanded="false">更多操作 <span class="caret"></span></a><ul class="dropdown-menu"><li><a href="/set_password/">修改密码</a></li><li><a href="/set_avatar/">修改头像</a></li><li><a href="/backend/">后台管理</a></li><li role="separator" class="divider"></li><li><a href="/logout/">注销</a></li></ul></li>{% else %}<li><a href="/login/">登录</a></li><li><a href="/register/">注册</a></li>{% endif %}</ul></div><!-- /.navbar-collapse --></div><!-- /.container-fluid --></nav><div class="container-fluid"><div class="row"><div class="col-md-2"><div class="panel panel-primary"><div class="panel-heading"><h3 class="panel-title">托儿所</h3></div><div class="panel-body">我的剑就是你的剑</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">儿童劫</h3></div><div class="panel-body">六一快乐</div></div><div class="panel panel-danger"><div class="panel-heading"><h3 class="panel-title">皮皮瞎</h3></div><div class="panel-body">无形之刃最为致命</div></div></div><div class="col-md-8">{% for article in article_list %}<div class="media"><h4 class="media-heading"><a href="/{{ article.blog.userinfo.username }}/article/{{ article.pk }}/">{{ article.title }}</a></h4><div class="media-left"><a href="#"><img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" alt="..." width="60"></a></div><div class="media-body">{{ article.desc }}</div><dev class="d1 pull-right">{# MouseDong 发布于 -07-24 17:50 评论(0)阅读(128) #}<span><a href="">{{ article.blog.userinfo.username }}&nbsp;&nbsp;</a></span><span>发布于&nbsp;&nbsp;{{ article.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span><span><span class="glyphicon glyphicon-comment"></span>评论{{ ment_num }}&nbsp;&nbsp;</span><span><span class="glyphicon glyphicon-thumbs-up"></span>点赞{{ article.up_num }}&nbsp;&nbsp;</span></dev></div><hr>{% endfor %}</div><div class="col-md-2"><div class="panel panel-primary"><div class="panel-heading"><h3 class="panel-title">贾科斯</h3></div><div class="panel-body">你太小看我啦</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">草丛伦</h3></div><div class="panel-body">我的大刀已经饥渴难耐</div></div><div class="panel panel-danger"><div class="panel-heading"><h3 class="panel-title">德邦</h3></div><div class="panel-body">看我的</div></div></div></div></div></body></html>

home.html

<div class="panel panel-primary"><div class="panel-heading"><h3 class="panel-title">文章分类</h3></div><div class="panel-body">{% for category in category_list %}<p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }}</a>({{ category.1 }})</p>{% endfor %}</div></div><div class="panel panel-info"><div class="panel-heading"><h3 class="panel-title">文章标题</h3></div><div class="panel-body">{% for tag in tag_list %}<p><a href="/{{ username }}/tag/{{ tag.2 }}">{{ tag.0 }}</a>({{ tag.1 }})</p>{% endfor %}</div></div><div class="panel panel-danger"><div class="panel-heading"><h3 class="panel-title">日期归档</h3></div><div class="panel-body">{% for date in date_list %}<p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}/">{{ date.0|date:'Y年m月'}}</a>({{ date.1 }})</p>{% endfor %}</div></div>

left_menu.html

{% extends 'base.html' %}{% block content %}<h2 class="text-center">修改头像</h2><form action="" method="post" enctype="multipart/form-data">{% csrf_token %}{#显示用户当前头像 ,头像路径在数据库中存在形式为:avatar/2.jpg ,所以直接获取就可以不需要在拼接全路径#}<img src="/media/{{ request.user.avatar }}" alt=""><p><label for="d1">选择头像:<input type="file" id="d1" name="new_avatar"></label></p><input type="submit" class="btn btn-success" value="修改"></form>{% endblock %}

set_avatar.html

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><script src="/jquery/3.4.1/jquery.min.js"></script><link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css"><script src="/static/bs-3.3.7/js/bootstrap.min.js"></script></head><body><div class="container"><div class="row"><div class="col-md-6 col-md-offset-3"><h1 class="text-center">修改密码</h1><hr><form action="" method="post">{% csrf_token %}<div class="form-group"><label for="id_username">用户名</label><input type="text" name="username" id="id_username" class="form-control"value="{{ request.user.username }}" disabled></div><div class="form-group"><label for="id_password">原密码</label><input type="password" name="old_password" id="id_password" class="form-control"></div><div class="form-group"><label for="id_new_password">新密码</label><input type="password" name="new_password" id="id_new_password" class="form-control"></div><div class="form-group"><label for="id_confirm_password">确认密码</label><input type="password" name="confirm_password" id="id_confirm_password" class="form-control"></div><br><input type="submit" class="btn btn-info" value="提交" id="id_submit"><span style="color: red" id="id_errro" class="pull-right"></span></form></div></div></div></body></html>

set_password.html

{% extends 'base.html' %}{% block content %}{% for article in article_list %}<div class="media"><h4 class="media-heading"><a href="/{{ username }}/article/{{ article.pk }}">{{ article.title }}</a></h4><div class="media-left"><a href="#"><img class="media-object" src="/media/{{ article.blog.userinfo.avatar }}" alt="..." width="60"></a></div><div class="media-body">{{ article.desc }}</div><dev class="d1 pull-right">{# MouseDong 发布于 -07-24 17:50 评论(0)阅读(128) #}<span><a href="">{{ article.blog.userinfo.username }}&nbsp;&nbsp;</a></span><span>@{{ article.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span><span><span class="glyphicon glyphicon-comment"></span>评论{{ ment_num }}&nbsp;&nbsp;</span><span><span class="glyphicon glyphicon-thumbs-up"></span>点赞{{ article.up_num }}&nbsp;&nbsp;</span><span><a href="">编辑</a></span></dev></div><hr>{% endfor %}{% endblock %}

site.html

{% extends 'backend/backendbase.html' %}{% block content %}<h3>文章标题</h3><hr><form action="" method="post" enctype="multipart/form-data">{% csrf_token %}<p>标题</p><input type="text" name="title" id="id_title" class="form-control"><p>内容(kindeditor编辑器,支持拖放/粘贴上传图片)</p><p><textarea name="content" id="id_content" cols="30" rows="10"></textarea></p><input type="submit" value="发布" class="btn btn-danger"></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:'450px',{#0 表示不能拖拽#}resizeType:0,// 控制文件上传的位置uploadJson : '/upload_img/',extraFileUploadParams : {'csrfmiddlewaretoken':'{{ csrf_token }}'}});});</script>{% endblock %}

add_article.html

{% extends 'backend/backendbase.html' %}{% block content %}<table class="table table-hover table-striped"><thead><tr><th>标题</th><th>评论数</th><th>点赞数</th><th>点踩数</th><th>操作</th><th>操作</th></tr></thead><tbody>{% for article in page_queryset %}<tr><td><a href="/{{ request.user.username }}/article/{{ article.pk }}/">{{ article.title }}</a></td><td>{{ ment_num }}</td><td>{{ article.up_num }}</td><td>{{ article.down_num }}</td><td><a href="#">编辑</a></td><td><a href="#">删除</a></td></tr>{% endfor %}</tbody></table>{{ page_obj.page_html|safe }}{% endblock %}

backend.html

<!DOCTYPE html><html><head><meta charset="utf-8"><title>Title</title><script src="/jquery/3.4.1/jquery.min.js"></script><link rel="stylesheet" href="/static/bs-3.3.7/css/bootstrap.css"><script src="/static/bs-3.3.7/js/bootstrap.min.js"></script></head><body><nav class="navbar navbar-inverse"><div class="container-fluid"><!-- Brand and toggle get grouped for better mobile display --><div class="navbar-header"><button type="button" class="navbar-toggle collapsed" data-toggle="collapse"data-target="#bs-example-navbar-collapse-1" aria-expanded="false"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a class="navbar-brand" href="#">后台管理</a></div><!-- Collect the nav links, forms, and other content for toggling --><div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"><ul class="nav navbar-nav navbar-right"><li><a href="#">{{ request.user.username }}</a></li></ul></div><!-- /.navbar-collapse --></div><!-- /.container-fluid --></nav><div class="container-fluid"><div class="row"><div class="col-md-2"><div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"><div class="panel panel-default"><div class="panel-heading" role="tab" id="headingOne"><h4 class="panel-title"><a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"aria-expanded="true" aria-controls="collapseOne">操作</a></h4></div><div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"aria-labelledby="headingOne"><div class="panel-body"><a href="/add_article/">添加文章</a></div></div><div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"aria-labelledby="headingOne"><div class="panel-body"><a href="#">添加随笔</a></div></div><div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"aria-labelledby="headingOne"><div class="panel-body"><a href="#">个人设计</a></div></div><div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"aria-labelledby="headingOne"><div class="panel-body"><a href="#">重金求子</a></div></div></div></div></div><div class="col-md-10"><div><!-- Nav tabs --><ul class="nav nav-tabs" role="tablist"><li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab"data-toggle="tab">文章</a></li><li role="presentation"><a href="#profile" aria-controls="profile" role="tab"data-toggle="tab">随笔</a></li><li role="presentation"><a href="#messages" aria-controls="messages" role="tab"data-toggle="tab">设置</a></li><li role="presentation"><a href="#settings" aria-controls="settings" role="tab"data-toggle="tab">更多</a></li></ul><!-- Tab panes --><div class="tab-content"><div role="tabpanel" class="tab-pane active" id="home">{% block content %}{% endblock %}</div><div role="tabpanel" class="tab-pane" id="profile">随笔页面</div><div role="tabpanel" class="tab-pane" id="messages">设置页面</div><div role="tabpanel" class="tab-pane" id="settings">更多页面</div></div></div></div></div></div></body></html>

backendbase.html

##源码地址(百度云)

如果觉得《Python Day 70 利用Django框架做的一个bbs小项目》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。