博客系统(一) 博客系统分析, 主页展示
转自 : https://www.cnblogs.com/xiao987334176/
一、博客系统分析
数据库的构建
首先,我们分析一个博客系统的功能:
- 一个博客可以有多个标签(多对多)
- 一个博客可以有多条评论(一对多)
- 一个博客只可以有一个类别(多对一)
接下来,我们分析关系的属性:
博客:标题,作者,内容,发布时间,分类(外键),标签(多对多)等
标签:标签名
类别:分类名
评论:作者,博客(外键),邮箱,内容,发布时间等。
有8张表,表关系如下:
图中箭头开始的英文字母表示关联字段
按照箭头方向查询,表示正向查询,否则为反向查询
新建项目cnblog,应用名为blog
修改models.py,必须导入模块
from django.contrib.auth.models import AbstractUser
因为有一个表userinfo需要继承它。django自带的auth_user表也是继承AbstractUser
表模型如下:
from django.db import models # Create your models here. from django.db import models # Create your models here. from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): """ 用户信息 """ nid = models.AutoField(primary_key=True) telephone = models.CharField(max_length=11, null=True, unique=True) avatar = models.FileField(upload_to='avatars/', default="avatars/default.png") create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) blog = models.OneToOneField(to='Blog', to_field='nid', null=True, on_delete=models.CASCADE) def __str__(self): return self.username class Blog(models.Model): """ 博客信息 """ nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='个人博客标题', max_length=64) site_name = models.CharField(verbose_name='站点名称', max_length=64) theme = models.CharField(verbose_name='博客主题', max_length=32) def __str__(self): return self.title class Category(models.Model): """ 博主个人文章分类表 """ nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='分类标题', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid', on_delete=models.CASCADE) def __str__(self): return self.title class Tag(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(verbose_name='标签名称', max_length=32) blog = models.ForeignKey(verbose_name='所属博客', to='Blog', to_field='nid', on_delete=models.CASCADE) def __str__(self): return self.title class Article(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=50, verbose_name='文章标题') desc = models.CharField(max_length=255, verbose_name='文章描述') create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) content = models.TextField() comment_count = models.IntegerField(default=0) up_count = models.IntegerField(default=0) down_count = models.IntegerField(default=0) user = models.ForeignKey(verbose_name='作者', to='UserInfo', to_field='nid', on_delete=models.CASCADE) category = models.ForeignKey(to='Category', to_field='nid', null=True, on_delete=models.CASCADE) tags = models.ManyToManyField( to="Tag", #through参数可以指定用作中介的中间模型 through='Article2Tag', ) def __str__(self): return self.title class Article2Tag(models.Model): nid = models.AutoField(primary_key=True) article = models.ForeignKey(verbose_name='文章', to="Article", to_field='nid', on_delete=models.CASCADE) tag = models.ForeignKey(verbose_name='标签', to="Tag", to_field='nid', on_delete=models.CASCADE) class Meta: #组合唯一约束 unique_together = [ ('article', 'tag'), ] def __str__(self): v = self.article.title + "---" + self.tag.title return v class ArticleUpDown(models.Model): """ 点赞表 """ nid = models.AutoField(primary_key=True) user = models.ForeignKey('UserInfo', null=True, on_delete=models.CASCADE) article = models.ForeignKey("Article", null=True, on_delete=models.CASCADE) is_up = models.BooleanField(default=True) class Meta: # 组合唯一约束 unique_together = [ ('article', 'user'), ] class Comment(models.Model): """ 评论表 """ nid = models.AutoField(primary_key=True) article = models.ForeignKey(verbose_name='评论文章', to='Article', to_field='nid', on_delete=models.CASCADE) user = models.ForeignKey(verbose_name='评论者', to='UserInfo', to_field='nid', on_delete=models.CASCADE) content = models.CharField(verbose_name='评论内容', max_length=255) create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) parent_comment = models.ForeignKey('Comment', null=True, on_delete=models.CASCADE) def __str__(self): return self.content
相关参数解释:
through 表示orm,不要创建关系表。而制定一个表,这个表自己来创建!为什么呢?因为orm创建多对多关系表时,只有3个字段。那么需要关系表需要扩充字段时,就不行了!
所以设置through 字段,是为了方便添加额外的字段。使用through,那么这个表,称之为中间模型。
在Comment模型表中,有一个字段parent_comment。它关联的是本身表中的主键nid,它是一个父级评论id,用来展示谁评论谁!to='Comment'等同于to='self'
修改settings.py配置文件,覆盖默认的User模型。最后一行添加,否则执行创建表命令时会报错!
AUTH_USER_MODEL="blog.UserInfo"
Django允许你通过修改setting.py文件中的 AUTH_USER_MODEL 设置覆盖默认的User模型,其值引用一个自定义的模型。
bolg,是应用名。
使用下面2个命令,生成表
python manage.py makemigrations
python manage.py migrate
生成的表如下:
注意:django自带的auth_user表没有了,取而代之的是blog_userinfo表。
查看表字段
它在auth_user表的基础上,增加了5个字段!
登录验证
添加超级用户,密码必须是8位或者以上!
python manage.py createsuperuser
效果如下:
再创建2个用户,zhang和lisi。用来测试多个用户登录!
lisi用户
修改urls.py,增加路径
from blog import views urlpatterns = [ path('admin/', admin.site.urls), path('index/', views.index), path('login/', views.login), ]
修改views.py,增加视图函数
from django.shortcuts import render,HttpResponse,redirect from django.contrib import auth from blog.models import Article,UserInfo # Create your views here. def login(request): if request.method=="POST": user=request.POST.get("user") pwd=request.POST.get("pwd") # 用户验证成功,返回user对象,否则返回None user=auth.authenticate(username=user,password=pwd) if user: # 登录,注册session # 全局变量 request.user=当前登陆对象(session中) auth.login(request,user) return redirect("/index/") return render(request,"login.html") def index(request): return render(request,"index.html")
修改login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="" method="post"> {% csrf_token %} <lable>用户名</lable><input type="text" name="user"> <lable>密码</lable><input type="password" name="pwd"> <input type="submit"> </form> </body> </html>
修改index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>INDEX</h3> </body> </html>
访问登录页面
跳转到首页
首页修饰
修改settings.py,设置静态文件目录。最后一行添加:
STATICFILES_DIRS=[ os.path.join(BASE_DIR,"static") ]
下载bootsrtap,将压缩的包内容放到satic目录
img和js是新建的
目录如下:
修改urls.py,增加路径
urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('logout/', views.logout), path('', views.index), ]
修改views.py,增加注销
def logout(request): # 注销 auth.logout(request) return redirect("/index/")
修改index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> <script src="/static/js/jquery.js"></script> <script src="/static/bootstrap/js/bootstrap.js"></script> <style> .desc{ text-align: justify; } .info{ margin-top: 10px; } </style> </head> <body> <nav class="navbar navbar-default"> <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.username %} <li><a href="#"><span class="glyphicon glyphicon-user"></span> {{ request.user.username }}</a> </li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">修改密码</a></li> <li><a href="#">个人信息</a></li> <li><a href="/logout/">注销</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </li> {% else %} <li><a href="/login/">登陆</a></li> <li><a href="#">注册</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container-fluid"> <div class="row"> <div class="col-md-3"> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> </div> <div class="col-md-6"> 222 </div> <div class="col-md-3"> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-success"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> </div> </div> </div> </body> </html>
重新登录,效果如下:
添加内容
使用django自带的admin后台,快速添加数据。
访问后台页面,这里必须是超级用户。上面已经创建了超级用户xiao
默认是空的
操作表,必须要注册
修改views.py同级目录下的admin.py
注册所有的模型表
from django.contrib import admin # Register your models here. from blog import models admin.site.register(models.UserInfo) admin.site.register(models.Blog) admin.site.register(models.Category) admin.site.register(models.Tag) admin.site.register(models.Article2Tag) admin.site.register(models.Article) admin.site.register(models.ArticleUpDown) admin.site.register(models.Comment)
再次刷新页面
点击Articles,添加文章
从http://www.py3study.com 上面copy一篇博客
注意内容都是html代码
添加分类
添加站点
保存
点击保存
添加成功
多添加几篇博客
首页文章展示
修改settings.py,更改时区
TIME_ZONE = 'Asia/Shanghai'
修改index视图函数
def index(request): article_list=Article.objects.all() return render(request,"index.html",{"article_list":article_list})
修改index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> <script src="/static/js/jquery.js"></script> <script src="/static/bootstrap/js/bootstrap.js"></script> <style> .desc{ text-align: justify; } .info{ margin-top: 10px; } </style> </head> <body> <nav class="navbar navbar-default"> <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.username %} <li><a href="#"><span class="glyphicon glyphicon-user"></span> {{ request.user.username }}</a> </li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">修改密码</a></li> <li><a href="#">个人信息</a></li> <li><a href="/logout/">注销</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </li> {% else %} <li><a href="/login/">登陆</a></li> <li><a href="#">注册</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container-fluid"> <div class="row"> <div class="col-md-3"> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> </div> <div class="col-md-6"> <div class="article_list"> {% for article in article_list %} <div class="article_item"> <h5><a href="">{{ article.title }}</a></h5> <div> <span class="media-left"><a href=""><img width="60" height="60" src="https://ss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=1758343206,1224786249&fm=58&bpow=1024&bpoh=1536" alt=""></a></span> <span class="media-right small desc "> {{ article.desc }} </span> </div> <div class="info small"> <span><a href="">{{ article.user.username }}</a></span> 发布于 <span>{{ article.create_time|date:'Y-m-d H:i' }}</span> <span class="glyphicon glyphicon-comment"></span><a href="">评论({{ article.comment_count }})</a> <span class="glyphicon glyphicon-thumbs-up"></span><a href="">点赞({{ article.up_count }})</a> </div> </div> <hr> {% endfor %} </div> </div> <div class="col-md-3"> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-success"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> </div> </div> </div> </body> </html>
访问首页,效果如下:
个人站点展示
比如博客园范围个人站点是域名加用户名,就可以了,比如:
https://www.cnblogs.com/xiao987334176
如果用户不存在,会提示404页面
增加404页面
修改urls.py,增加路径。注意导入re_path
from django.contrib import admin from django.urls import path,re_path from blog import views urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('logout/', views.logout), path('', views.index), re_path('(?P<username>\w+)', views.homesite), ]
修改views.py,增加视图函数
def homesite(request,username): """ 查询 :param request: :param username: :return: """ # 查询当前站点的用户对象 user=UserInfo.objects.filter(username=username).first() if not user: return render(request,"not_found.html") return render(request,"homesite.html",{"user":user})
在templates目录创建文件not_found.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> </head> <body> <div class="container" style="margin-top: 100px"> <div class="text-center"> <a href="http://www.cnblogs.com/"><img src="/static/img/logo_small.gif" alt="cnblogs"></a> <p><b>404.</b> 抱歉! 您访问的资源不存在!</p> <p class="d">请确认您输入的网址是否正确,如果问题持续存在,请发邮件至 xx@qq.com 与 <strong style="font-size: 28px">xiao</strong> 联系。</p> <p><a href="/">返回网站首页</a></p> </div> </div> </body> </html>
创建homesite.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>{{ user }}</h3> </body> </html>
访问网页:http://127.0.0.1:8000/xiao
访问一个不存在的用户
http://127.0.0.1:8000/123
页面提示404
展示文章列表
修改homesite视图函数
def homesite(request,username): """ 查询 :param request: :param username: :return: """ # 查询当前站点的用户对象 user=UserInfo.objects.filter(username=username).first() if not user: return render(request,"not_found.html") # 查询当前站点对象 blog=user.blog # 查询当前用户发布的所有文章 article_list=Article.objects.filter(user__username=username) return render(request,"homesite.html",{"blog":blog,"article_list":article_list})
修改homesite.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> *{ margin: 0; padding: 0; } .header{ width: 100%; height: 59px; background-color: #369; } .header .title{ line-height: 59px; color: white; font-weight: lighter; margin-left: 20px; font-size: 18px; } .left_region{ margin-top: 10px; } .info{ margin-top: 10px; color: darkgray; } </style> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> <script src="/static/js/jquery.js"></script> <script src="/static/bootstrap/js/bootstrap.js"></script> </head> <body> <div class="header"> <p class="title">{{ blog.title }}</p> </div> <div class="container-fluid"> <div class="row"> <div class="col-md-3"> <div class="left_region"> <div class="panel panel-success"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div> </div> </div> <div class="col-md-9"> <div class="article_list"> {% for article in article_list %} <div class="article_item clearfix"> <h5><a href="">{{ article.title }}</a></h5> <div> <span class="small desc "> {{ article.desc }} </span> </div> <div class="info small pull-right"> 发布于 <span>{{ article.create_time|date:'Y-m-d H:i' }}</span> <span class="glyphicon glyphicon-comment"></span><a href="">评论({{ article.comment_count }})</a> <span class="glyphicon glyphicon-thumbs-up"></span><a href="">点赞({{ article.up_count }})</a> </div> </div> <hr> {% endfor %} </div> </div> </div> </div> </body> </html>
访问xiao的个人站点,效果如下:
这个时候发现,左上角没有显示博客标题。那是因为用户认证表的blog_id字段默认为空,需要在admin后台绑定一下
登录到后台,点击用户表,点击指定的用户xiao
拉到最下面,绑定blog,点击保存
再次刷新个人站点,发现出来了!
作业:
查询当前站点每一个分类的名称以及对应的文章数
查询当前站点每一个标签的名称以及对应的文章数
答案:
修改homesite视图函数
def homesite(request,username): """ 查询 :param request: :param username: :return: """ # 查询当前站点的用户对象 user=UserInfo.objects.filter(username=username).first() if not user: return render(request,"not_found.html") # 查询当前站点对象 blog=user.blog # 查询当前用户发布的所有文章 article_list=Article.objects.filter(user__username=username) # 查询当前站点每一个分类的名称以及对应的文章数 cate_list = Category.objects.filter(blog=blog).annotate(c=Count("article__title")).values("title","c") print(cate_list) # 查询当前站点每一个标签的名称以及对应的文章数 tag_list = Tag.objects.filter(blog=blog).annotate(c=Count("article__title")).values("title","c") print(tag_list) dict = {"blog":blog, "article_list":article_list, "category_count":cate_list, "tag_count":tag_list, } return render(request,"homesite.html",dict)
修改homesite.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> *{ margin: 0; padding: 0; } .header{ width: 100%; height: 59px; background-color: #369; } .header .title{ line-height: 59px; color: white; font-weight: lighter; margin-left: 20px; font-size: 18px; } .left_region{ margin-top: 10px; } .info{ margin-top: 10px; color: darkgray; } h5 a { color: #105cb6; font-size: 14px; font-weight: bold; text-decoration: underline; } </style> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> <script src="/static/js/jquery.js"></script> <script src="/static/bootstrap/js/bootstrap.js"></script> </head> <body> <div class="header"> <p class="title">{{ blog.title }}</p> </div> <div class="container-fluid"> <div class="row"> <div class="col-md-3"> <div class="left_region"> <div class="panel panel-success"> <div class="panel-heading"> <h3 class="panel-title">我的标签</h3> </div> {% for foo in category_count %} <div class="panel-body"> {{ foo.title }}({{ foo.c }}) </div> {% endfor %} </div> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">随笔分类</h3> </div> {% for foo in tag_count %} <div class="panel-body"> {{ foo.title }}({{ foo.c }}) </div> {% endfor %} </div> <div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">随笔档案</h3> </div> <div class="panel-body"> Panel content </div> </div> </div> </div> <div class="col-md-9"> <div class="article_list"> {% for article in article_list %} <div class="article_item clearfix"> <h5><a href="">{{ article.title }}</a></h5> <div> <span class="small desc "> {{ article.desc }} </span> </div> <div class="info small pull-right"> 发布于 <span>{{ article.create_time|date:'Y-m-d H:i' }}</span> <img src="/static/img/icon_comment.gif" alt=""><a href="">评论({{ article.comment_count }})</a> <span class="glyphicon glyphicon-thumbs-up"></span><a href="">点赞({{ article.up_count }})</a> </div> </div> <hr> {% endfor %} </div> </div> </div> </div> </body> </html>
进入admin后台,添加一个tag。增加一篇文章
重新访问个人站点网页,效果如下: