使用Django开发个人博客系统,博客系统包括用户(博主)注册和登录、博主资料信息、图片墙功能、留言板功能、文章列表、文章正文内容和Admin后台系统。
完整项目源码:
github
项目架构设计
功能配置
下一步将上述设置写入Django的配置文件settings.py,当Django运行的时候能自动加载相应的功能应用。
将项目应用account、album、article和interflow写入配置属性INSTALLED_APPS,并在配置属性MIDDLEWARE中添加中间件LocaleMiddleware,使Admin后台系统支持中文语言,配置代码如下:
# myblog的settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'article',
'album',
'account',
'interflow',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
# 添加中间件LocaleMiddleware
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
然后连接sql server数据库:
具体连接参考其他博客。
pip install pymssql
pip install mssql-django==1.2
pip install pyodbc django-pyodbc-azure
配置:
DATABASES = {
'default': {
'ENGINE': 'sql_server.pyodbc',
'NAME': 'blogdb', # 数据库名称
'HOST': '127.0.0.1', # 数据库地址,本机 ip 地址 127.0.0.1
'PORT': 1433, # 端口
'USER': 'bloger', # 数据库用户名
'PASSWORD': '123456', # 数据库密码
'OPTIONS': {
'driver': 'ODBC Driver 17 for SQL Server',
},
}
}
# account的models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
name=models.CharField('姓名',max_length=50,default='匿名用户')
introduce = models.TextField('简介', default='暂无介绍')
company=models.CharField('公司',max_length=100,default='暂无信息')
profession=models.CharField('职业',max_length=100,default='暂无信息')
address=models.CharField('住址',max_length=100,default='暂无信息')
telephone=models.CharField('电话',max_length=11,default='暂无信息')
wx = models.CharField('微信', max_length=50, default='暂无信息')
qq = models.CharField('QQ', max_length=50, default='暂无信息')
wb = models.CharField('微博', max_length=100, default='暂无信息')
photo=models.ImageField('头像',blank=True,upload_to='images/user/')
# 设置返回值
def __str__(self):
return self.name
继续配置:
# 配置自定义用户模型MyUser
AUTH_USER_MODEL = 'account.MyUser'
STATICFILES_DIRS = [BASE_DIR / 'publicStatic']
# 设置媒体资源的保存路径
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
项目应用album使用模型AlbumInfo存储图片墙的图片信息,它设有外键字段关联模型MyUser,与模型MyUser组成一对多的数据关系,使每个用户(博主)的图片墙只能显示自己上传的图片信息。我们在项目应用album的models.py中定义模型AlbumInfo:
# album 的models.py
from django.db import models
from account.models import MyUser
class AlbumInfo(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(MyUser, on_delete=models.CASCADE,verbose_name='用户')
title = models.CharField('标题',max_length=50,blank=True)
introduce = models.CharField('描述',max_length=200,blank=True)
photo = models.ImageField('图片',blank=True,upload_to='images/album/')
def __str__(self):
return str(self.id)
class Meta:
verbose_name = '图片墙管理'
verbose_name_plural ='图片墙管理'
项目应用article实现用户(博主)的文章管理,每篇文章设有分类标签、正文内容和评论信息,三者分别对应模型ArticleTag、ArticleInfo和Comment,每个模型之间的数据关系说明如下:
(1)模型ArticleTag设有外键字段关联模型MyUser,与模型MyUser组成一对多的数据关系。
(2)模型ArticleInfo不仅与模型MyUser组成一对多的数据关系,并且与模型ArticleTag组成多对多的数据关系。
(3)模型Comment只对模型ArticleInfo组成一对多的数据关系。
# article 的models.py
from django.db import models
from django.utils import timezone
from account.models import MyUser
class ArticleTag(models.Model):
id = models.AutoField(primary_key=True)
tag = models.CharField('标签',max_length=500)
user = models.ForeignKey(MyUser,on_delete=models.CASCADE,verbose_name='用户')
def __str__(self):
return self.tag
class Meta:
verbose_name = '博文分类'
verbose_name_plural ='博文分类'
class ArticleInfo(models.Model):
author = models.ForeignKey(MyUser,on_delete=models.CASCADE,verbose_name='用户')
title = models.CharField('标题',max_length=200)
content = models.TextField('内容')
articlephoto = models.ImageField('文章图片',blank=True,upload_to='images/article/')
reading =models.IntegerField('阅读量',default=0)
liking = models.IntegerField('点赞量',default=0)
created = models.DateTimeField('创建时间',default=timezone.now)
updated = models.DateTimeField('更新时间',auto_now=True)
article_tag = models.ManyToManyField(ArticleTag,blank=True,verbose_name='文章标签')
def __str__(self):
return self.title
class Meta:
verbose_name = '博文管理'
verbose_name_plural = '博文管理'
class Comment(models.Model):
article = models.ForeignKey(ArticleInfo,on_delete=models.CASCADE,verbose_name='所属文章')
commentor = models.CharField('评论用户',max_length=90)
content = models.TextField('评论内容')
created = models.DateTimeField('创建时间',auto_now_add=True)
def __str__(self):
return self.article.title
class Meta:
verbose_name = '评论管理'
verbose_name_plural = '评论管理'
项目应用interflow使用模型Board存储留言板信息,它与模型MyUser组成一对多的数据关系,从而区分每个用户(博主)的留言板信息。在项目应用interflow的models.py中定义模型Board,定义过程如下:
# interflow的 models.py
from django.db import models
from django.utils import timezone
from account.models import MyUser
class Board(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField('留言用户',max_length=50)
email = models.CharField('邮箱地址',max_length=50)
content = models.CharField('留言内容',max_length=500)
created = models.DateTimeField('创建时间',default=timezone.now)
user = models.ForeignKey(MyUser,on_delete=models.CASCADE,verbose_name='用户')
def __str__(self):
return self.email
class Meta:
verbose_name = '博客留言'
verbose_name_plural = '博客留言'
# 根据models.py生成相关的.py文件,该文件用于创建数据表
python manage.py makemigrations
# 创建数据表
python manage.py migrate
刷新:
接下来定义路由列表:
from django.contrib import admin
from django.urls import path, include,re_path
from django.views.static import serve
from django.conf import settings
urlpatterns = [
# Admin 后台系统
path('admin/', admin.site.urls),
# 用户注册和登录
path('user/',include('account.urls')),
# 博客文章
path('',include('article.urls')),
# 图片墙
path('ablum/',include('album.urls')),
# 留言板
path('board/',include('interflow.urls')),
# 配置媒体资源的路由信息
re_path('media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT},name='media'),
]
然后编写共用模板:
<html>
<head>
{% load static %}
<title> {% block title %} 我的博客 {% endblock %}</title>
<link href="{% static "css/base.css" %}" rel="stylesheet">
<link href="{% static "css/index.css" %}" rel="stylesheet">
<link href="{% static "css/m.css" %}" rel="stylesheet">
<link href="{% static "css/info.css" %}" rel="stylesheet">
<script src="{% static "js/jquery.min.js" %}"></script>
<script src="{% static "js/comm.js" %}"></script>
<script src="{% static "js/modernizr.js" %}"></script>
<script src="{% static "js/scrollReveal.js" %}"></script>
</head>
<body>
<header class="header-navigation" id="header">
<nav>
<div class="logo">
<a href="javascript:;">博客首页</a>
</div>
<h2 id="mnavh"><span class="navicon"></span></h2>
<ul id="startlist">
<li><a href="{% url 'article' id 1 %}">我的日记</a></li>
<li><a href="{% url 'album' id 1 %}">我的相册</a></li>
<li><a href="{% url 'about' id %}">关于我</a></li>
<li><a href="{% url 'board' id 1 %}">留言</a></li>
<li><a href="{% url 'admin:index' %}">博客后台管理</a></li>
</ul>
</nav>
</header>
{% block body %}
<article>
<aside class="l_box">
<div class="about_me">
<h2>关于我</h2>
<ul>
{% if user.photo %}
<i><img src="{{ user.photo.url }}"></i>
{% else %}
<i><img src="{% static 'image/user.jpg' %}"></i>
{% endif %}
<p>
<b>{{ user.name }}</b>,{{ user.introduce }}
</p>
</ul>
</div>
<div class="wdxc">
<h2>我的相册</h2>
<ul>
{% for a in album %}
<li>
<a href="javascript:;"></a>
<img src="{{ a.photo.url }}">
</li>
{% endfor %}
</ul>
</div>
<div class="fenlei">
<h2>文章分类</h2>
<ul>
{% for t in tag %}
<li><a href="/"></a></li>
{% endfor %}
</ul>
</div>
<div class="tuijian">
<h2>站长推荐</h2>
<ul>
<li>
<a href="https://gitbook.cn/gitchat/author/5b28bff00ac95342c847f199" target="new">个人专题</a>
</li>
<li><a href="https://item.jd.com/12460562.html" target="new">玩转django2.0-京东</a></li>
<li><a href="http://product.dangdang.com/25535043.html" target="new">玩转django2.0-当当网</a></li>
</ul>
</div>
<div class="links">
<h2>友情链接</h2>
<ul>
<a href="https//blog.csdn.net/HuangZhang_123" target="new">CSDN博客</a>
<a herf="https://book.jd.com/writer">出版图书</a>
</ul>
</div>
</aside>
{% endblock %}
{% block content %} {% endblock %}
</article>
<a href="#"class="cd-top">Top</a>
{% block script %} {% endblock %}
</body>
</html>
在项目应用account的urls.py中定义用户注册与登录的路由信息,分别命名为路由register和userLogin,两者的HTTP请求处理由视图函数register和userLogin执行,代码如下:
# account的urls.py
from django.urls import path
from .views import *
urlpatterns = [
# 用户注册
path('register.html', register, name='register'),
# 用户登录
path('login.html', userLogin, name='userLogin'),
]
打开项目应用account的views.py定义视图函数register和userLogin,功能代码如下:
from django.shortcuts import render,redirect
from .models import MyUser
from django.contrib.auth import authenticate
from django.contrib.auth import login,logout
from django.urls import reverse
def register(request):
title='注册博客'
pageTitle='用户注册'
confirmPassword = True
urlText = '注册'
urlName = 'userLogin'
if request.method == 'POST':
u = request.POST.get('username','')
p = request.POST.get('password','')
cp = request.POST.get('cp','')
if MyUser.objects.filter(username=u):
tips = '用户已存在'
elif cp != p:
tips = '两次密码输入不一致'
else:
d={
'username':u,'password':p,
'is_superuser':1,'is_staff':1
}
user = MyUser.objects.create_user(**d)
user.save()
tips = '注册成功,请登录'
logout(request)
return redirect(reverse('userLogin'))
return render(request,'user.html',locals())
def userLogin(request):
title='登录博客'
pageTitle='用户登录'
button ='登录'
urlText = '用户注册'
urlName = 'register'
if request.method == 'POST':
u = request.POST.get('username','')
p = request.POST.get('password','')
if MyUser.objects.filter(username=u):
user = authenticate(username=u,password=p)
if user:
if user.is_active:
login(request,user)
kwargs = {'id':request.user.id,'page':1}
return redirect(reverse('article',kwargs=kwargs))
else:
tips = '账号密码错误,请重新输入'
else:
tips = '用户不存在,请注册'
else:
if request.user.username:
kwargs = {'id':request.user.id,'page':1}
return redirect(reverse('article',kwargs=kwargs))
return render(request,'user.html',locals())
视图函数register和userLogin都是使用模板文件user.html生成不同的网页内容。打开模板文件夹templates的user.html并编写用户注册和登录的页面内容,代码如下:
<!DOCTYPE html>
<html>
<head>
{% load staticfiles %}
<title>{{ title }}</title>
<link rel="stylesheet" href="{% static "css/reset.css" %}">
<link rel="stylesheet" href="{% static "css/user.css" %}">
<script src="{% static "js/jquery.min.js" %}"></script>
<script src="{% static "js/user.js" %}"></script>
</head>
<body>
<div class="page">
<div class="loginwarrp">
<div class="logo">{{ pageTitle }}</div>
<div class="login_form">
<form name="Login" method="post" action="">
{% csrf_token %}
<li class="login-item">
<span>用户名:</span>
<input type="text" name="username" class="login_input">
<span id="count-msg" class="error"></span>
</li>
<li class="login-item">
<span>密码:</span>
<input type="password" name="password" class="login_input">
<span id="password-msg" class="error"></span>
</li>
{% if confirmPassword %}
<li class="login-item">
<span>确认密码:</span>
<input type="password" name="cp" class="login_input" >
<span id="password-msg" class="error"></span>
</li>
{% endif %}
<div>{{ tips }}</div>
<li class="login-sun">
<input type="submit" name="Submit" value="{{ button }}">
<div class="turn-url">
<a style="color: #45B572;"
href="{% url urlName %}">>>{{ urlText }}</a>
</div>
</li>
</form>
</div>
</div>
</div>
<script type="text/javascript">
window.onload = function (){
var config={
vx : 4,
vy : 4,
height:2,
width:2,
count:100,
color:"121,162,185",
stroke:"100,200,180",
dist:6000,
e_dist:20000,
max_conn:10
}
CanvasParticle(config);
}
</script>
<script src="{% static "js/canvas-particle.js" %}"></script>
</body>
</html>
博主资料信息使用模板文件about.html生成网页内容,用户信息来自模型MyUser,在项目应用account中实现博主资料信息的网页功能。我们已在项目应用account的urls.py中定义了路由register和userLogin,两者分别实现用户注册和用户登录功能。首先在项目应用account的urls.py中新增路由about,代码如下:
# account的urls.py
from django.urls import path
from .views import *
urlpatterns = [
# 用户注册
path('register.html', register, name='register'),
# 用户登录
path('login.html', userLogin, name='userLogin'),
# 关于我
path('about/<int:id>.html', about, name='about'),
]
博客系统的所有页面(除了用户注册和登录页面之外)都需要设置路由变量id,这样便于区分不同用户的博客网站。
视图函数about接收并处理路由about的HTTP请求,我们在account的views.py中定义视图函数about,函数代码新增如下:
# account的views.py
from album.models import AlbumInfo
from article.models import ArticleTag
from django.shortcuts import render
def about(request, id):
album = AlbumInfo.objects.filter(user_id=id)
tag = ArticleTag.objects.filter(user_id=id)
user = MyUser.objects.filter(id=id).first()
return render(request, 'about.html', locals())
共用模板文件base.html设置了博客系统的网页架构,我们只需继承并重写模板文件base.html的接口即可实现博主资料信息页。
博主资料信息页只重写模板文件base.html的content接口,其他的模板继承接口使用默认值即可,在模板文件about.html中编写以下代码:
{% extends "base.html" %}
{% block content %}
<main class="r_box">
<div class="about">
<div>{{ user.introduce }}</div>
<br><hr><br>
<h2>我的资料</h2>
<br>
<p>名字:{{ user.name }}</p>
<br/>
<p>微博:{{ user.wb }}</p>
<br/>
<p>微信:{{ user.wx }}</p>
<br/>
<p>Q Q:{{ user.qq}}</p>
</div>
</main>
{% endblock %}
接下来,写照片墙功能:
# album的urls.py
from django.urls import path
from .views import *
urlpatterns = [
# 图片墙
path('<int:id>/<int:page>.html', album, name='album'),
]
路由album设置路由变量id和page,路由变量id代表模型MyUser的主键id,它可获取某个用户(博主)的博客信息;路由变量page代表所有图片分页后的某一页的页数,它可获取不同页数的图片信息
在album的views.py中定义视图函数album,函数代码如下:
from django.shortcuts import render
from django.core.paginator import Paginator, PageNotAnInteger,EmptyPage
from .models import AlbumInfo
def album(request,id,page):
albumList = AlbumInfo.objects.filter(user_id=id).order_by('id')
paginator = Paginator(albumList,8)
try:
pageInfo = paginator.page(page)
except PageNotAnInteger:
# 如果参数page的数据结构不是整型,就返回第一页数据
pageInfo = paginator.page(1)
except EmptyPage:
# 如果用户访问的页数大于实际页数,则返回最后一页的数据
pageInfo = paginator.page(paginator.num_pages)
return render(request,'ablum.html'.locals())
# templates 的album.html
{% extends "base.html" %}
{% block body %} {% endblock %}
{% block content %}
<article>
<div class="picbox">
{% for list in pageInfo.object_list %}
<div class="picvalue" data-scroll-reveal="enter bottom over 1s">
<a href="javascript:;">
<i><img src="{{ list.photo.url }}"></i>
<div class="picinfo">
{% if list.title %}
<h3>{{ list.title }}</h3>
{% else %}
<h3>相册图片</h3>
{% endif %}
{% if list.introduce %}
<span>{{ list.introduce }}</span>
{% else %}
<span>图片简介</span>
{% endif %}
</div>
</a>
</div>
{% endfor %}
</div>
<!--分页功能-->
<div class="pagelist">
{% if pageInfo.has_previous %}
<a href="{% url 'album' id pageInfo.previous_page_number %}">上一页</a>
{% endif %}
{% for page in pageInfo.paginator.page_range %}
{% if pageInfo.number == page %}
<a href="javascript:;" class="curPage">{{ page }}</a>
{% else %}
<a href="{% url 'album' id page %}">{{ page }}</a>
{% endif %}
{% endfor %}
{% if pageInfo.has_next %}
<a href="{% url 'album' id pageInfo.next_page_number %}">下一页</a>
{% endif %}
</div>
</article>
{% endblock %}
{% block script %}
<script>
if (!(/msie [6|7|8|9]/i.test(navigator.userAgent))){
(function (){
window.scrollReveal = new scrollReveal({reset:true});
})();
};
</script>
{% endblock %}
留言板功能在项目应用interflow中实现,网页功能包括:留言信息展示和留言提交。我们在项目应用interflow的urls.py中定义留言板的路由信息。打开interflow的urls.py定义路由board,代码如下:
# interflow的urls.py
from django.urls import path
from .views import *
urlpatterns = [
# 留言板
path('<int:id>/<int:page>.html', board, name='board'),
]
# interflow的views.py
from django.shortcuts import render,redirect
from django.core.paginator import Paginator,PageNotAnInteger,EmptyPage
from article.models import ArticleTag
from account.models import MyUser
from album.models import AlbumInfo
from .models import Board
from django.urls import reverse
def board(request,id,page):
album = AlbumInfo.objects.filter(user_id=id)
tag = ArticleTag.objects.filter(user_id=id)
user = MyUser.objects.filter(id=id).first()
if not user:
return redirect(reverse('register'))
if request.method == "GET":
boardList = Board.objects.filter(user_id=id).order_by('-created')
paginator = Paginator(boardList,10)
try:
pageInfo = paginator.page(page)
except PageNotAnInteger:
pageInfo = paginator.page(1)
except EmptyPage:
pageInfo = paginator.page(paginator.num_pages)
return render(request,'board.html',locals())
else:
name = request.POST.get('name')
email = request.POST.get('email')
content = request.POST.get('content')
value = {'name':name,'email':email,'content':content,'user_id':id}
Board.objects.create(**value)
kwargs = {'id':id,'page':1}
return redirect(reverse('board',kwargs=kwargs))
{% extends "base.html" %}
{% load static %}
{% block content %}
<main class="r_box">
<div class="gbook">
{% for list in pageInfo.object_list %}
<div class="fb">
<ul style="background: url({% static "image/user.jpg" %}) no-repeat top 20px left 10px;">
<p class="fbtime">
<span>{{ list.created|data:"Y-m-d" }}</span>
{{ list.name }}
</p>
<p class="fbinfo">{{ list.content }}</p>
</ul>
</div>
{% endfor %}
<!--分页功能-->
<div class="pagelist">
{% if pageInfo.has_previous %}
<a href="{% url 'board' id pageInfo.previous_page_number %}">上一页</a>
{% endif %}
{% if pageInfo.object_list %}
{% for page in pageInfo.paginator.page_range %}
{% if pageInfo.number == page %}
<a href="javascript:;" class="curPage">{{ page }}</a>
{% else %}
<a href="{% url 'board' id page %}">{{ page }}</a>
{% endif %}
{% endfor %}
{% endif %}
{% if pageInfo.has_next %}
<a href="{% url 'board' id pageInfo.next_page_number %}">下一页</a>
{% endif %}
</div>
<hr>
<!--网页表单-->
<div class="gbox">
<form action="" method="post" name="saypl" onsubmit="return CheckPl(document.saypl)">
{% csrf_token %}
<p><strong>来说点什么吧...</strong></p>
<p><span>您的姓名:</span>
<input name="name" type="text" id="name">
*</p>
<p><span>联系邮箱:</span>
<input name="email" type="text" id="email">
*</p>
<p><span class="tnr">留言内容:</span>
<textarea name="content" cols="60" rows="12" id="lytext"></textarea>
</p>
<p>
<input type="submit" name="Submit3" value="提交">
</p>
</form>
</div>
</div>
</main>
{% endblock %}
<!--模板重写-->
{% block script %}
<script>
function CheckPl(obj){
if(obj.lytext.value==""){
alert("请写下您想说的话!");
obj.lytext.focus();
return false;
}
if(obj.name.value==""){
alter("请写下您的名字!");
obj.name.focus();
return false;
}
if(obj.email.value==""){
alter("请写下您的邮箱地址!");
obj.email.focus();
return false;
}
return true;
}
</script>
{% endblock %}
文章列表以模板文件base.html作为网页框架,它将当前用户(博主)的所有文章进行分页显示。我们在项目应用article中实现文章列表的功能开发,首先在article的urls.py中定义文章列表的路由article,代码如下:
# article的urls.py
from django.urls import path
from django.views.generic import RedirectView
from .views import *
urlpatterns = [
# 首页地址自动跳转到用户登录页面
path('', RedirectView.as_view(url='user/login.html')),
# 文章列表
path('<int:id>/<int:page>.html', article, name='article'),
]
路由article由视图函数article负责接收和处理HTTP请求,下一步在项目应用article的views.py中定义视图函数article,代码如下:
from django.shortcuts import render,redirect
from account.models import MyUser
from album.models import AlbumInfo
from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger
from .models import ArticleInfo,ArticleTag
from django.urls import reverse
def article(request,id,page):
album = AlbumInfo.objects.filter(user_id=id)
tag = ArticleTag.objects.filter(user_id=id)
user = MyUser.objects.filter(id=id).first()
if not user:
return redirect(reverse('register'))
ats = ArticleInfo.objects.filter(user_id=id).order_by('-created')
paginator = Paginator(ats,10)
try:
pageInfo = paginator.page(page)
except PageNotAnInteger:
pageInfo = paginator.page(1)
except EmptyPage:
pageInfo = paginator.page(paginator.num_pages)
return render(request,'article.html',locals())
# templates 的article.html
{% extends "base.html" %}
{% load static %}
{% block content %}
<main class="r_box">
{% for list in pageInfo.object_list %}
<li>
<i><a href="{% url 'detail' id list.id %}">
{% if list.articlephoto %}
<img src="{% static 'images/pic.png' %}">
{% endif %}
</a></i>
<h3>
<a href="{% url 'detail' id list.id %}">{{ list.title }}</a>
</h3>
<p>{{ list.content|safe }}</p>
</li>
{% endfor %}
<div class="pagelist">
{% if pageInfo.has_previous %}
<a href="{% url 'article' id pageInfo.previous_page_number %}">上一页</a>
{% endif %}
{% if pageInfo.object_list %}
{% for page in pageInfo.paginator.page_range %}
{% if pageInfo.number == page %}
<a href="javascript:;" class="curPage">{{ page }}</a>
{% else %}
<a href="{% url 'article' id page %}">{{ page }}</a>
{% endif %}
{% endfor %}
{% endif %}
{% if pageInfo.has_next %}
<a href="{% url 'article' id pageInfo.next_page_number %}">下一页</a>
{% endif %}
</div>
</main>
{% endblock %}
文章正文内容显示文章的标签、阅读量、发布时间、作者、文章内容和评论内容,我们在项目应用article中实现文章正文内容的功能开发,在article的urls.py中定义添加路由detail,代码如下:
# article的urls.py
from django.urls import path
from django.views.generic import RedirectView
from .views import *
urlpatterns = [
# 首页地址自动跳转到用户登录页面
path('', RedirectView.as_view(url='user/login.html')),
# 文章列表
path('<int:id>/<int:page>.html', article, name='article'),
# 文章正文内容
path('detail/<int:id>/<int:aId>.html', detail, name='detail')
]
# article的views.py
from django.shortcuts import render,redirect
from account.models import MyUser
from album.models import AlbumInfo
from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger
from .models import ArticleInfo,ArticleTag,Comment
from django.db.models import F
from django.urls import reverse
def article(request,id,page):
album = AlbumInfo.objects.filter(user_id=id)
tag = ArticleTag.objects.filter(user_id=id)
user = MyUser.objects.filter(id=id).first()
if not user:
return redirect(reverse('register'))
ats = ArticleInfo.objects.filter(user_id=id).order_by('-created')
paginator = Paginator(ats,10)
try:
pageInfo = paginator.page(page)
except PageNotAnInteger:
pageInfo = paginator.page(1)
except EmptyPage:
pageInfo = paginator.page(paginator.num_pages)
return render(request,'article.html',locals())
def detail(request,id,aId):
album = AlbumInfo.objects.filter(user_id=id)
tag = ArticleTag.objects.filter(user_id=id)
user = MyUser.objects.filter(id=id).first()
if request.method=='GET':
ats = ArticleInfo.objects.filter(id=aId).first()
atags = ArticleTag.objects.get(id=aId).article_tag.all()
cms = Comment.objects.filter(article_id=aId).order_by('-created')
# 添加阅读量
if not request.session.get('reading'+str(id)+str(aId),''):
reading = ArticleInfo.objects.filter(id=aId)
reading.update(reading=F('reading')+1)
request.session['reading'+str(id)+str(aId)] = True
return render(request,'detail.html',locals())
else:
commentator = request.POST.get('name')
email = request.POST.get('email')
content = request.POST.get('content')
value = {'commentator':commentator,'content':content,'article_id':aId}
Comment.objects.create(**value)
kwargs = {'id':id,'aId':aId}
return redirect(reverse('detail',kwargs=kwargs))
# templates 的detail.html
{% extends "base.html" %}
{% load static %}
<!--模板重写-->
{% block content %}
<main>
<div class="infosbox">
<div class="newsview">
<!--文章标题-->
<h3 class="news_title">{{ ats.title }}</h3>
<!--作者 发布时间 阅读量-->
<div class="bloginfo">
<ul>
<li class="author">作者:
<a href="javascript:;">{{ user.name }}</a></li>
<li class="timer">
时间:{{ ats.created|date:"Y-m-d" }}
</li>
<li class="view">
阅读量:{{ ats.reading }}
</li>
</ul>
</div>
<!--文章标签-->
<div class="tags">
{% for t in atags %}
<a href="javascript:;">{{ t }}</a>
{% endfor %}
</div>
<!--正文内容-->
<div class="news_con">
{{ ats.content|safe }}
</div>
</div>
<div class="news_pl">
<h2>文章评论</h2>
<div class="gbko">
<!--评论展示-->
{% for c in cms %}
<div class="fb">
<ul style="background: url({% static "images/user.jpg" %}) no-repeat top 20px left 10px;">
<p class="fbtime">
<span>{{ c.created|date:"Y-m-d" }}</span>
{{ c.commentator }}
</p>
</ul>
</div>
{% endfor %}
<!--提交评论的网页表单-->
<form action="" method="post" name="saypl" onsubmit="return CheckPl(document.saypl)">
<div id="plpost">
{% csrf_token %}
<p class="saying">
<span>
<a href="javascript:;">共有{{ cms|length }}条评论</a>
</span>来说两句吧...
</p>
<p class="yname"><span>名称:</span>
<input name="name" id="name" type="text" class="inputText" size="16">
</p>
<textarea name="content" row="6" id="saytext"></textarea>
<inpu name="submit" type="submit" value="提交"></inpu>
</div>
</form>
</div>
</div>
</div>
</main>
{% endblock %}
{% block script %}
<script>
function CheckPl(obj)
{
if(obj.saytext.value==""){
alter("请写下您想说的话!");
obj.saytext.focus();
return false;
}
if(obj.name.value==""){
alter("请写下您的名字!");
obj.name.focus();
return false;
}
return true;
}
</script>
{% endblock %}
项目的模型存储了所有用户的数据信息,但每个用户登录Admin后台系统只能管理自己的数据信息,因此每个模型的ModelAdmin必须重写方法formfield_for_foreignkey()和get_queryset()。
每个模型的ModelAdmin设置了属性list_display,并且重写类方法get_queryset()和formfield_for_foreignkey(),具体说明如下:
(1)list_display:在模型的数据列表页设置显示的模型字段。
(2)get_queryset():在模型的数据列表页过滤数据,只显示当前用户的数据。(3)formfield_for_foreignkey():在模型的数据修改页或数据新增页设置外键字段的值,确保新增或修改数据隶属于当前用户。
# account 的admin.py
from django.contrib import admin
from .models import MyUser
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _
@admin.register(MyUser)
class MyUserAdmin(UserAdmin):
list_display = ['username', 'email', 'name','introduce','company','profession','address','telephone','wx','qq','wb','photo']
# 在修改页面添加'mobile','qq','weChat'的信息输入框
# 将源码的UserAdmin.fieldsets转换为列表格式
fieldsets = list(UserAdmin.fieldsets)
# 重写UserAdmin的fieldsets,添加模型字段信息录入
fieldsets[1] = (_('Personal info'), {'fields': ('name', 'introduce','email','company','profession','address','telephone','wx','qq','wb','photo')})
#根据当前用户名设置数据访问权限
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(id=request.user.id)
# album 的admin.py
from django.contrib import admin
from .models import AlbumInfo
from account.models import MyUser
@admin.register(AlbumInfo)
class AlbumInfoAdmin(admin.ModelAdmin):
list_display = ['id','user','title','introduce','photo']
# 根据当前用户名设置数据的访问权限
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(user_id=request.user.id)
# 新增或修改数据时,设置外键可选值
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'user':
id = request.user.id
kwargs['queryset'] = MyUser.objects.filter(id=id)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
# article 的admin.py
from django.contrib import admin
from .models import *
admin.site.site_title='博客管理后台'
admin.site.site_header='博客管理'
@admin.register(ArticleTag)
class ArticleTagAdmin(admin.ModelAdmin):
list_display = ['id','tag','user']
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(user_id=request.user.id)
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == 'article_tag':
id = request.user.id
kwargs["queryset"] = ArticleTag.objects.filter(user_id=id)
return super().formfield_for_manytomany(db_field, request, **kwargs)
def formfield_for_foreignkey(self, db_field, request,**kwargs):
if db_field.name == 'author':
id = request.user.id
kwargs["queryset"] = MyUser.objects.filter(id=id)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ['article','commentator','content','created']
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(article_author_id=request.user.id)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'article':
id = request.user.id
kwargs["queryset"] = Comment.objects.filter(article_author_id=id)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
# interflow 的admin.py
from django.contrib import admin
from .models import Board
from account.models import MyUser
@admin.register(Board)
class BoardAdmin(admin.ModelAdmin):
list_display = ['id','name','email','content','created','user']
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.filter(user_id=request.user.id)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'user':
id = request.user.id
kwargs['queryset'] = MyUser.objects.filter(id=id)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
下一步在每个项目应用的初始化文件__init__.py中设置项目应用名称,项目应用名称将显示在Admin后台系统的首页,每个项目应用的代码如下:
# account的__init__.py
from django.apps import AppConfig
import os
# 修改app在admin后台显示的名称
# default_app_config的值来自apps.py的类名
default_app_config = 'account.IndexConfig'
# 获取当前app的命名
def get_current_app_name(_file):
return os.path.split(os.path.dirname(_file))[-1]
# 重写类IndexConfig
class IndexConfig(AppConfig):
name = get_current_app_name(__file__)
verbose_name = '用户管理'
# album的__init__.py
from django.apps import AppConfig
import os
# 修改app在admin后台显示的名称
# default_app_config的值来自apps.py的类名
default_app_config = 'album.IndexConfig'
# 获取当前app的命名
def get_current_app_name(_file):
return os.path.split(os.path.dirname(_file))[-1]
# 重写类IndexConfig
class IndexConfig(AppConfig):
name = get_current_app_name(__file__)
verbose_name = '我的图片墙'
# article的__init__.py
from django.apps import AppConfig
import os
# 修改app在admin后台显示的名称
# default_app_config的值来自apps.py的类名
default_app_config = 'article.IndexConfig'
# 获取当前app的命名
def get_current_app_name(_file):
return os.path.split(os.path.dirname(_file))[-1]
# 重写类IndexConfig
class IndexConfig(AppConfig):
name = get_current_app_name(__file__)
verbose_name = '博客管理'
# interflow的__init__.py
from django.apps import AppConfig
import os
# 修改app在admin后台显示的名称
# default_app_config的值来自apps.py的类名
default_app_config = 'interflow.IndexConfig'
# 获取当前app的命名
def get_current_app_name(_file):
return os.path.split(os.path.dirname(_file))[-1]
# 重写类IndexConfig
class IndexConfig(AppConfig):
name = get_current_app_name(__file__)
verbose_name = '留言管理'
将Admin后台系统的登录页面改为项目应用account实现的登录页面。
在myadmin.py文件中定义MyAdminSite类,该类继承父类AdminSite并重写admin_view()和get_urls()方法,从而更改Admin后台系统的用户登录地址,实现代码如下:
# myblog的myadmin.py
from django.contrib import admin
from functools import update_wrapper
from django.views.generic import RedirectView
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.http import HttpResponseRedirect
from django.urls import include, path,re_path
from django.contrib.contenttypes import views as contenttype_views
from django.contrib.auth.views import redirect_to_login
class MyAdminSite(admin.AdminSite):
def admin_view(self,view,cacheable=False):
def inner(request,*args,**kwargs):
if not self.has_permission(request):
if request.path == reverse('admin:logout',current_app=self.name):
index_path = reverse('admin:index',current_app=self.name)
return HttpResponseRedirect(index_path)
# 修改注销后重新登录的路由地址
return redirect_to_login(request.get_full_path(),'/user/login.html')
return view(request,*args,**kwargs)
if not cacheable:
inner = never_cache(inner)
if not getattr(view,'csrf_exempt',False):
inner = csrf_protect(inner)
return update_wrapper(inner,view)
def get_urls(self):
def wrap(view,cacheable=False):
def wrapper(*args,**kwargs):
return self.admin_view(view,cacheable)(*args,**kwargs)
wrapper.admin_site = self
return update_wrapper(wrapper,view)
urlpatterns = [
path('',wrap(self.index),name='index'),
# 修改登录页面的路由地址
path('login/',RedirectView.as_view(url='/user/login.html')),
path('logout/',wrap(self.logout),name='logout'),
path('password_change/',wrap(self.password_change,cacheable=True),name='password_change'),
path('password_change/done/',wrap(self.password_change_done,cacheable=True),name='password_change_done'),
path('jsi18n/',wrap(self.i18n_javascript, cacheable=True),name='jsi18n'),
path('r/<int:content_type_id>/<path:object_id>',wrap(contenttype_views.shortcut),name='view_on_site',),
]
valid_app_labels = []
for model,model_admin in self._registry.items():
urlpatterns+=[
path('%s/%s/' % (model._meta.app_label,model._meta.model_name),include(model_admin.urls)),
]
if model._meta.app_label not in valid_app_labels:
valid_app_labels.append(model._meta.app_label)
if valid_app_labels:
regex=r'^(?P<app_label>'+'|'.join(valid_app_labels)+')/$'
urlpatterns+=[
re_path(regex,wrap(self.app_index),name='app_list'),
]
return urlpatterns
# myblog的myapps.py
from django.contrib.admin.apps import AdminConfig
class MyAdminConfig(AdminConfig):
default_site = 'myblog.myadmin.MyAdminSite'
最后在配置文件settings.py的INSTALLED_APPS中配置系统注册类MyAdminConfig,代码如下:
# myblog的settings.py
INSTALLED_APPS = [
# 'django.contrib.admin',
'myblog.myapps.MyAdminConfig',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'article',
'album',
'account',
'interflow',
]
模型字段content只负责存储文章内容,它不会对文章内容进行排版布局,如果要编写排版整齐、布局精美的博客文章,就需要引入Django的第三方功能应用Django CKEditor
生成文章编辑器。
# 安装Django CKEditor
pip install django-ckeditor
完成Django CKEditor安装后,在项目的配置文件settings.py中添加Django CKEditor功能应用,并且设置该应用的功能配置,配置过程如下:
INSTALLED_APPS = [
# 'django.contrib.admin',
'myblog.myapps.MyAdminConfig',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'article',
'album',
'account',
'interflow',
# 添加Django CKEditor
'ckeditor',
'ckeditor_uploader',
]
# 编辑器的配置信息
CKEDITOR_UPLOAD_PATH = "article_images"
CKEDITOR_CONFIGS = {
'default':{
'toolbar':'Full'
}
}
CKEDITOR_ALLOW_NONIMAGE_FILTER = False
CKEDITOR_BROWSE_SHOW_DIRS = True
如果需要在文章正文内容中添加图片,那么可以使用Django CKEditor上传的图片,并且上传的图片都会保存在配置属性CKEDITOR_UPLOAD_PATH设置的文件夹中,该文件夹必须在媒体资源文件夹(项目的media文件夹)的目录下。
想深入了解Django
CKEditor的功能配置和使用方法,那么可以查阅官方文档(pypi.org/project/django-ckeditor/)。
下一步在项目中定义Django CKEditor的路由信息,打开myblog的urls.py并添加路由ckeditor,路由信息如下:
from django.contrib import admin
from django.urls import path, include,re_path
from django.views.static import serve
from django.conf import settings
urlpatterns = [
# Admin 后台系统
path('admin/', admin.site.urls),
# 用户注册和登录
path('user/',include('account.urls')),
# 博客文章
path('',include('article.urls')),
# 图片墙
path('ablum/',include('album.urls')),
# 留言板
path('board/',include('interflow.urls')),
# 配置媒体资源的路由信息
re_path('media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT},name='media'),
# 设置编辑器的路由信息
path('ckeditor/',include('ckeditor_uploader.urls')),
]
最后将模型ArticleInfo的content字段重新定义,将字段改为Django CKEditor定义的字段类型,代码如下:
# article 的models.py
from django.db import models
from django.utils import timezone
from account.models import MyUser
# 导入编辑器定义的字段类型
from ckeditor_uploader.fields import RichTextUploadingField
class ArticleTag(models.Model):
id = models.AutoField(primary_key=True)
tag = models.CharField('标签',max_length=500)
user = models.ForeignKey(MyUser,on_delete=models.CASCADE,verbose_name='用户')
def __str__(self):
return self.tag
class Meta:
verbose_name = '博文分类'
verbose_name_plural ='博文分类'
class ArticleInfo(models.Model):
author = models.ForeignKey(MyUser,on_delete=models.CASCADE,verbose_name='用户')
title = models.CharField('标题',max_length=200)
content = RichTextUploadingField('内容')
articlephoto = models.ImageField('文章图片',blank=True,upload_to='images/article/')
reading =models.IntegerField('阅读量',default=0)
liking = models.IntegerField('点赞量',default=0)
created = models.DateTimeField('创建时间',default=timezone.now)
updated = models.DateTimeField('更新时间',auto_now=True)
article_tag = models.ManyToManyField(ArticleTag,blank=True,verbose_name='文章标签')
def __str__(self):
return self.title
class Meta:
verbose_name = '博文管理'
verbose_name_plural = '博文管理'
class Comment(models.Model):
article = models.ForeignKey(ArticleInfo,on_delete=models.CASCADE,verbose_name='所属文章')
commentator = models.CharField('评论用户',max_length=90)
content = models.TextField('评论内容')
created = models.DateTimeField('创建时间',auto_now_add=True)
def __str__(self):
return self.article.title
class Meta:
verbose_name = '评论管理'
verbose_name_plural = '评论管理'
测试和部署
如果网站系统在试运行阶段符合开发需求,并且没有检测出BUG,我们就可以将网站系统设置为上线模式。首先在配置文件settings.py中设置STATIC_ROOT,并修改运行模式,代码如下:
#myblog的settings.py
DEBUG = False
ALLOWED_HOSTS = ['*']
STATIC_ROOT = BASE_DIR / 'static'
现在项目中存在两个静态资源文件夹static和publicStatic,Django根据不同的运行模式读取不同的静态资源文件夹,详细说明如下:
(1)如果将Django设为调试模式(DEBUG=True),那么项目运行时将读取publicStatic文件夹的静态资源。
(2)如果将Django设为上线模式(DEBUG=False),那么项目运行时将读取static文件夹的静态资源。
配置属性STATIC_ROOT指向根目录的static文件夹,下一步使用Django指令创建static文件夹,在PyCharm的Terminal中输入collectstatic指令
python manage.py collectstatic
当Django设为上线模式时,它不再提供静态资源服务,该服务应交由Nginx或Apache服务器完成,因此在项目的路由列表添加静态资源的路由信息,让Django知道如何找到静态资源文件,否则无法在浏览器上访问static文件夹的静态资源文件,路由信息如下:
# myblog 的urls.py
from django.contrib import admin
from django.urls import path, include, re_path
from django.views.static import serve
from django.conf import settings
urlpatterns = [
# Admin 后台系统
path('admin/', admin.site.urls),
# 用户注册和登录
path('user/',include('account.urls')),
# 博客文章
path('',include('article.urls')),
# 图片墙
path('ablum/',include('album.urls')),
# 留言板
path('board/',include('interflow.urls')),
# 配置媒体资源的路由信息
re_path('media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT},name='media'),
# 配置静态资源的路由信息
re_path('static/(?P<path>.*)',serve,{'document_root':settings.STATIC_ROOT},name='static'),
# 设置编辑器的路由信息
path('ckeditor/',include('ckeditor_uploader.urls'))
]
到此为止,博客项目完成。
前端代码以及完整代码,详见:
完整项目源码:
github