【Django】开发个人博客系统【1】

使用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

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值