一、需求分析
1.首页(显示文章)
文章详情
点赞,点踩
文章评论(子评论,评论的展示)
登录功能(图片验证码)
注册功能(基于form验证,ajax)
个人站点(不同人不同样式,文章过滤)
后台管理(文章展示)
新增文章(富文本编辑器)
2.设计程序(框架,数据库设计)
UserInfo用户表
blog个人站点表
Aticle文章表
commit评论表
upanddown点赞点踩表
category文章分类表
tag文章标签表
表关系
userinfo跟blog一对一
article跟blog一对多
article跟category一对多
article跟tag多对多
commit跟article一对多
upanddown跟article一对多
user跟commit一对多
user跟upanddown一对多
category跟blog一对多
tag跟blog一对多
二、settings配置和models里建表
settings配置
#设置LANGUAGE_CODE = 'zh-hans',则后台管理变成中文LANGUAGE_CODE = 'zh-hans'#设置时区TIME_ZONE = 'Asia/Shanghai'USE_I18N = TrueUSE_L10N = True#不用UTC时间USE_TZ = False# Static files (CSS, JavaScript, Images)# https://docs.djangoproject.com/en/1.11/howto/static-files/STATIC_URL = '/static/'STATICFILES_DIRS=[ os.path.join(BASE_DIR,'static'),]#auth模块未登录时转到的路由LOGIN_URL='/login/'#舍弃系统给的user表,自定义auth认证的userinfo表AUTH_USER_MODEL='blog.UserInfo'EMAIL_HOST='smtp.qq.com'#设置以什么邮箱发送EMAIL_PORT=465#端口号EMAIL_HOST_USER='1500519302@qq.com'#发送者邮箱账号EMAIL_HOST_PASSWORD='odokxrzlfejkcbbh'#授权码DEFAULT_FROM_EMAIL=EMAIL_HOST_USER#显示收件人EMAIL_USE_SSL=True#使用ssl加密,qq邮箱只支持这个# avatar = models.FileField(upload_to='avatar/', default='/static/img/default.png')#如果不配置MEDIA_ROOT,和MEDIA_URL,默认会存到avatar#如果不配置MEDIA_ROOT,和MEDIA_URL,默认会存到media/avatarMEDIA_URL='/media/'MEDIA_ROOT=os.path.join(BASE_DIR,'media')MEDIA_BBS=os.path.join(BASE_DIR,'BBS')
models建表
from django.db import modelsfrom django.contrib.auth.models import AbstractUser# Create your models here.# UserInfo这个表,继承AbstractUser,因为要用auth组件class UserInfo(AbstractUser): nid = models.AutoField(primary_key=True) # username=models.CharField(max_length=32,unique=True) # 该字段可以为空,为该字段设置默认值,default='123455666' # blank=True 只是admin中表单提交的时候,做校验,如果设置成True,就是不校验了 phone = models.CharField(max_length=32,null=True,blank=True) # upload_to需要传一个路径(avatar文件夹会自动创建) avatar = models.FileField(upload_to='avatar/', default='avatar/default.png') # 如果avatar用charfield,如何处理? # 一对一关联blog表,to_field如果不写,默认主键 # blog_id字段存的数据是什么?blog表的---nid这个字段 blog = models.OneToOneField(to='Blog', to_field='nid',null=True) class Meta: # admin中显示表名 verbose_name='用户表' # admin中表名s去掉 verbose_name_plural = verbose_name # user表 # id name blog_id # 1 111 1 # 2 111 1class Blog(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=64) site_name = models.CharField(max_length=32) theme = models.CharField(max_length=64) def __str__(self): return self.site_nameclass Category(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=64) # ForeignKey跟OneToOneField的区别? #OneToOneField unique=True blog = models.ForeignKey(to='Blog', to_field='nid', null=True) def __str__(self): return self.titleclass Tag(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=64) blog = models.ForeignKey(to='Blog', to_field='nid', null=True) def __str__(self): return self.titleclass Article(models.Model): nid = models.AutoField(primary_key=True) # verbose_name='文章标题' 修改admin中表单的文字显示 title = models.CharField(max_length=64,verbose_name='文章标题') # 摘要,简单描述 desc = models.CharField(max_length=255) # 大文本TextField() content = models.TextField() # 存时间类型,auto_now_add每插入一条数据,时间自动写入当前时间, # auto_now,这条数据修改的时候,会更新成当前时间 create_time = models.DateTimeField(auto_now_add=True) # 因为查询多,写入少,所以加这三个字段,以后不需要再连表查询了 commit_num=models.IntegerField(default=0) up_num=models.IntegerField(default=0) down_num=models.IntegerField(default=0) blog = models.ForeignKey(to='Blog', to_field='nid', null=True) category = models.ForeignKey(to='Category', to_field='nid', null=True) # through_fields应该怎么写? # 中介模型,手动创建第三张表,through是通过哪个表跟Tag建立关系,through_fields为了查询用的 tag = models.ManyToManyField(to='Tag', through='ArticleTOTag', through_fields=('article', 'tag')) # 这样写,会自动创建第三张表 # tag = models.ManyToManyField(to='Tag') def __str__(self): return self.title+'----'+self.blog.userinfo.username# 手动创建第三张表class ArticleTOTag(models.Model): nid = models.AutoField(primary_key=True) article = models.ForeignKey(to='Article', to_field='nid') tag = models.ForeignKey(to='Tag', to_field='nid') # article和tag应不应该联合唯一? # article_id 1 # tag_id 1class Commit(models.Model): nid = models.AutoField(primary_key=True) user = models.ForeignKey(to='UserInfo', to_field='nid') article = models.ForeignKey(to='Article', to_field='nid') content = models.CharField(max_length=255) create_time = models.DateTimeField(auto_now_add=True) # 这样写是可以的 # parent_id=models.IntegerField() # 自关联 # parent_id=models.ForeignKey(to='Commit',to_field='nid') parent = models.ForeignKey(to='self', to_field='nid',null=True,blank=True)class UpAndDown(models.Model): nid = models.AutoField(primary_key=True) user = models.ForeignKey(to='UserInfo', to_field='nid') article = models.ForeignKey(to='Article', to_field='nid') is_up = models.BooleanField() class Meta: # 写这些,只是为了不写脏数据,联合唯一 unique_together = (('user', 'article'),)
models中 class Meta 定义的是元数据的属性,不会生成字段
建表时,一对多关系可以这么理解:找出哪个是一,哪个是多。在多的那个对象建表,然后建字段
django中的orm自关联:表内自关联是指表内数据相关联的对象和表是相同字段,这样我们就直接用表内关联将外键关联设置成自身表的字段。比如:微博评论,每条评论都可能有自评论,但每条子评论都只有一个父评论,这就满足了,一对多的情形。父评论为关联字段,可以对应多个子评论,这就是一对多的自关联。
parent = models.ForeignKey(to='self', to_field='nid',null=True,blank=True)
三、生成图片验证码
首先要下载Pillow
后台代码如下
def login(request): if request.method=='GET': return render(request,'login.html') elif request.is_ajax(): response={ 'user':None,'msg':None} name=request.POST.get('name') password=request.POST.get('password') valid_code=request.POST.get('valid_code') ret=request.session.get('valid_session') print(ret) if valid_code.upper()==request.session.get('valid_session').upper(): user=auth.authenticate(request,username=name,password=password) #这里auth.authenticate如果没有登录成功,user是None。如果登录成功,user则是登录的user对象 if user: # print(user) #ajax请求,不能再返回 #校验通过,一定要登录 auth.login(request,user) response['user']=name response['msg']='登录成功' else: # print(user) response['msg']='用户名或密码错误' else: response['msg']='验证码错误' return JsonResponse(response)def get_random_color(): return (random.randint(0,255),random.randint(0,255),random.randint(0,255))def get_valid_code(request): #图片验证码 # 第一种方式,直接用服务器的图片 # with open('static/media/img/315557.jpg','rb') as f: # data=f.read() # return HttpResponse(data) #第二种方式 #生成一张图片,第一个参数是模式:'rgb',第二个参数是图片大小(宽,高),第三个参数是图片颜色 # img=Image.new('RGB',(200,35),color=get_random_color()) # with open('valid_code.png','wb') as f: # #img直接封装了保存图片的方法 # img.save(f,'png') # with open('valid_code.png','rb') as f: # data=f.read() # return HttpResponse(data) #第二种方式有个漏洞,它要将随机生成的图片保存在服务器,这不合理,因此不用第二种方法 #第三种方式 # 在内存中生成一个空文件(把它想象成 open('valid_code.png', 'wb') as f:) # 一个是在硬盘上,一个是在内存中 #先导入io模块,用内存生成文件 # from io import BytesIO # img=Image.new('RGB',(200,35),color=get_random_color()) # f=BytesIO() # #把图片保存到f中, # #放到内存中,存取比较快,而且有自动清理 # img.save(f,'png') # data=f.getvalue() # return HttpResponse(data) #然后再第三种模式的基础上,再在生成的图片上进行写出随机字符串 # img=Image.new('RGB',(200,35),color=get_random_color()) # img_draw=ImageDraw.Draw(img) # #生成一个字体对象,第一个参数是字体文件的路径,第二个参数是大小 # font=ImageFont.truetype('static/font/ss.TTF',size=25) # #第一个参数:xy(x,y)的坐标,第二个参数:要写的文字,第三个参数:写文字的颜色,第四个参数:字体 # img_draw.text((0,0),'python',get_random_color(),font=font) # f=BytesIO() # img.save(f,'png') # data=f.getvalue() # return HttpResponse(data) #终极版本 img=Image.new('RGB',(200,35),color=get_random_color()) img_draw=ImageDraw.Draw(img) font=ImageFont.truetype('static/font/ss.TTF',size=25) random_code='' for i in range(5): char_num=random.randint(0,9) char_lower=chr(random.randint(97,122)) char_upper=chr(random.randint(65,90)) char_str=str(random.choice([char_num,char_lower,char_upper])) img_draw.text((i*30+20,0),char_str,get_random_color(),font=font) random_code+=char_str #在图片上画线画点画圆等 # width = 200 # height = 35 # for i in range(2): # x1 = random.randint(0, width) # x2 = random.randint(0, width) # y1 = random.randint(0, height) # y2 = random.randint(0, height) # # 在图片上画线。x1,y1表示起始坐标,x2,y2表示终止坐标 # img_draw.line((x1, y1, x2, y2), fill=get_random_color()) # # for i in range(5): # # 画点 # img_draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color()) # x = random.randint(0, width) # y = random.randint(0, height) # # 画弧形 # img_draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_random_color()) #把验证码保存到session中。验证码需要保存 print(random_code) request.session['valid_session']=random_code f=BytesIO() img.save(f,'png') data=f.getvalue() return HttpResponse(data)
前台代码如下
登录页面
四、注册功能
注册功能用ajax进行提交,要有局部刷新功能,使用forms组件对前台进行校验
前端html中,accept要配合input file联用,表示接收哪种格式文件
前端代码如下
注册
后台代码如下
def register(request): if request.method=='GET': my_form=myforms.MyForm() return render(request,'register.html',locals()) elif request.is_ajax(): print(request.POST) response={ 'status':100,'msg':None} my_form=myforms.MyForm(request.POST)#校验POST里的所有数据,forms可以直接接字典 if my_form.is_valid(): dic=my_form.cleaned_data dic.pop('re_password') my_file=request.FILES.get('my_file') #如果上传的文件为空,这个字段不穿,数据库里存默认值。 if my_file: dic['avatar']=my_file user=models.UserInfo.objects.create_user(**dic) #看看存没存进去 print(user.username) #要跳转的路径 response['url']='/login/' else: response['status']=101 response['msg']=my_form.errors return JsonResponse(response)def check_username(request): response={ 'status':100,'msg':None} name=request.POST.get('name') user=models.UserInfo.objects.filter(username=name).first() if user: response['status']=101 response['msg']='用户名已被占用' return JsonResponse(response)
五、admin表配置
要在app下的admin.py进行配置注册一下,这样就能控制相应的表了
from django.contrib import adminfrom blog import models# Register your models here.admin.site.register(models.UserInfo)admin.site.register(models.Blog)admin.site.register(models.Article)admin.site.register(models.Tag)admin.site.register(models.Category)admin.site.register(models.Commit)admin.site.register(models.UpAndDown)admin.site.register(models.ArticleTOTag)
六、meida路径配置
用户上传的文件最好配置一个文件夹,在路由层做一下配置。
可以可以把导入的serve当一个views函数,就是帮meidia开了一个路由接口,path分组是serve函数必须传的一个参数
from django.views.static import servefrom ff188 import settingsurlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/', views.login), url(r'^get_valid_code/', views.get_valid_code), url(r'^make/', views.make), url(r'^register/', views.register), url(r'^check_username/', views.check_username), url(r'^index/', views.index), url(r'^logout/', views.logout), #url第一个参数,正则表达式,第二个参数是函数的内存地址,第三个参数是一个字典,它会以关键字参数的形式传到第二个参数中,第四个参数是名字,反向解析用 #当进行这些设置后,就可以访问media下的资源了 url(r'^media/(?P.*)', serve,{ 'document_root':settings.MEDIA_ROOT}),]
七、个人主页
在static文件夹下创建css文件夹,把所有用户自定义的css样式都保存进去
{ { username }}的个人博客 { { blog.title }}
我的分类请联系:199999999
年入60w随笔档案
八、文章归类
#所有分类对应文章数 category_num=models.Category.objects.all().filter(blog=blog).annotate(coun=Count('article__title')).values('title','coun') #所有标签对应文章数 tag_num=models.Tag.objects.all().filter(blog=blog).annotate(coun=Count('article__title')).values('title','coun') #按日期分类 from django.db.models.functions import TruncMonth #切分时间 #这样查询到的y_m是一个datetime对象,到前端用date过滤即可 time_article=models.Article.objects.filter(blog=blog).annotate(y_m=TruncMonth('create_time')).values('y_m').annotate(coun=Count('y_m')).values('y_m','coun')
这里补充一下ajax中$(this)的意思,像面向对象语言(如JAVA),简单的说,谁在调用它,它就代表谁
$("#btnConfirm").click(function(){
alert($(this).val()); //看这里,this代表的其实就是这个为btnConfirm的按钮,因为你现在点击的是为btnConfirm的按钮,那么this就是它});jquery中html(),text(),val()是有一些区别的
html就是可以对添加了<a></a>,<p></p>等设置值
val()是只有有value属性的才能取到或设置值
text()方法设置或返回被选元素的文本内容