Django5博客网站开发实践2—网站基础功能开发概要

发布时间:2024-08

浏览量:120

本文字数:11056

读完约 37 分钟

2024年最新django5.1版本,python3.12

前提:熟悉掌握基本的python知识、django知识

网站上线后的效果:

首页:包含导航站点、随机语录、搜索框、文章列表

文章详情页:左侧目录导航、文章内容区域、右侧同分类相关文章、文章访问量统计

image.png

image.png

功能模块

1、导航站功能

在项目中新建django应用,用来实现导航站功能

功能主要包含:导航分类、站点具体信息

模型设计如下:

分类字段:(WebCate模型)

分类名称:name

父分类:parent

分类简介:description

分类缩略图:thumbnail_img(非必须)

分类级别(实现N级分类) :level

显示序号(用以人工控制后续在前端的显示顺序) :order


站点信息字段:(WebInfo模型)

站点名称:title

站点分类:category

描述:description

缩略图:thumbnail_img

站点url:url

创建时间:create_time

修改时间:modified_time

显示序号(人工控制排序顺序):order

是否置顶(用以后续人工控制选择常用站点在页面进行置顶展示):put_top

from django.db import models


class WebCate(models.Model):
    name = models.CharField(max_length=100, unique=True, verbose_name='分类名')
    parent = models.ForeignKey('self', on_delete=models.SET_NULL, blank=True, null=True, related_name='children', verbose_name='父分类')
    description = models.TextField(blank=True, verbose_name='简介')
    thumbnail_img = models.ImageField(upload_to='webcate_img/', verbose_name='缩略图', blank=True)
    level = models.IntegerField(blank=True, editable=False)
    order = models.IntegerField(default=100, blank=True, db_index=False, verbose_name='显示序号')

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = verbose_name_plural = '网址分类'

    def save(self, *args, **kwargs):
        self.level = self.get_level()
        super(WebCate, self).save(*args, **kwargs)  # 调用父类的save方法

    def get_level(self):
        level = 1  # 初始层级为1
        parent_category = self.parent  # 获取当前分类的父分类
        while parent_category is not None:
            level += 1  # 每找到一个父分类,层级加1
            parent_category = parent_category.parent  # 继续找上一个父分类
        return level  # 返回计算出的层级

    #    获取分类下的所有子孙分类
    def get_descendants(self, include_self=True):
        queryset = self.children.all()
        if include_self:
            queryset = WebCate.objects.filter(pk=self.pk)
        else:
            queryset = WebCate.objects.none()

        stack = list(queryset)
        while stack:
            current = stack.pop(0)
            children = current.children.all()
            stack.extend(children)
            queryset |= children

        return queryset


class WebInfo(models.Model):
    title = models.CharField(max_length=100, verbose_name="网址名称")
    category = models.ForeignKey(WebCate, on_delete=models.SET_NULL, verbose_name='分类', null=True, related_name='sites')
    description = models.TextField(blank=True, verbose_name="网址描述")
    thumbnail_img = models.ImageField(upload_to='webinfo_img/', verbose_name='缩略图', blank=True)
    url = models.URLField(verbose_name="网址URL")
    create_time = models.DateTimeField(auto_now_add=True)
    modified_time = models.DateTimeField(auto_now=True)
    order = models.IntegerField(default=100, blank=True, db_index=False, verbose_name='显示序号')
    put_top = models.BooleanField(default=False, verbose_name='是否置顶')

    class Meta:
        verbose_name = verbose_name_plural = '网址信息'

    def __str__(self):
        return self.title

2、搜索框(使用外部搜索引擎

直接在html中嵌入表单,采用必应搜索

<form target="_blank" method="get" action="https://cn.bing.com/search">
    <input class="search_text" type="text" placeholder="请输入关键词" name="q" />
    <input class="search_button" type="submit" value="必应搜索">
</form>

3、语录功能

在项目中新建django应用,用来实现语录功能,每次访问首页时,随机展示一条语录

主要为自己添加的语录内容,第三方接口一个是有请求限制另外是内容不太满足个人喜好

功能主要包含:语录分类、语录书籍管理、语录具体信息

模型设计如下:

from django.db import models
from django.contrib.auth.models import User


class Category(models.Model):
    name = models.CharField(max_length=100, unique=True, verbose_name='分类名')
    parent = models.ForeignKey('self', on_delete=models.SET_NULL, blank=True, null=True, related_name='children', verbose_name='父分类')
    description = models.TextField(blank=True, verbose_name='简介')
    url_token = models.CharField(max_length=100, unique=True, verbose_name='分类urltoken')
    seo_title = models.CharField(max_length=200, verbose_name='seo标题', blank=True)
    seo_keyword = models.CharField(max_length=100, verbose_name='seo关键词', blank=True)
    seo_description = models.CharField(max_length=500, verbose_name='seo描述', blank=True)
    level = models.IntegerField(blank=True, editable=False)
    order = models.IntegerField(default=100, blank=True, db_index=False, verbose_name='显示序号')
    create_time = models.DateTimeField(auto_now_add=True)
    modified_time = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = verbose_name_plural = '语录分类'

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.level = self.get_level()
        super(Category, self).save()  # 调用父类的save方法

    def get_level(self):
        level = 1  # 初始层级为1
        parent_category = self.parent  # 获取当前分类的父分类
        while parent_category is not None:
            level += 1  # 每找到一个父分类,层级加1
            parent_category = parent_category.parent  # 继续找上一个父分类
        return level  # 返回计算出的层级


class Book(models.Model):
    name = models.CharField(max_length=100, unique=True, verbose_name='书籍名')
    author = models.CharField(max_length=100, unique=True, verbose_name='作者')
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, related_name='books', verbose_name='分类')
    description = models.TextField(blank=True, verbose_name='简介')
    url_token = models.CharField(max_length=100, unique=True, verbose_name='书籍urltoken')
    seo_title = models.CharField(max_length=200, verbose_name='seo标题', blank=True)
    seo_keyword = models.CharField(max_length=100, verbose_name='seo关键词', blank=True)
    seo_description = models.CharField(max_length=500, verbose_name='seo描述', blank=True)
    order = models.IntegerField(default=1000, blank=True, db_index=False, verbose_name='显示序号')
    create_time = models.DateTimeField(auto_now_add=True)
    modified_time = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = verbose_name_plural = '语录书籍'

    def __str__(self):
        return self.name


class Quote(models.Model):

    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, related_name='quotes', verbose_name='分类')
    book = models.ForeignKey(Book, on_delete=models.SET_NULL, null=True, blank=True, related_name='quotes', verbose_name='书籍')
    content = models.TextField(verbose_name='内容')
    q_author = models.CharField(max_length=100, blank=True, verbose_name='作者')

    create_time = models.DateTimeField(auto_now_add=True)
    modified_time = models.DateTimeField(auto_now=True)

    is_show = models.BooleanField(default=True, verbose_name='是否展示')

    class Meta:
        verbose_name = verbose_name_plural = '语录'

image.png

4、文章列表页

主要展示分类下的文章列表以及该分类所对应的上级分类及下级分类

文章分类模型设计如下:

主要功能:

基础分类字段:name、parent等

在模型内部实现了3个方法

get_absolute_url # 获取分类的网址url
get_level # 设置分类的等级,默认等级为1(一级分类),如有父等级则循环+1
get_descendants # 获取任意分类下的子孙分类(用于在页面上展示其子孙分类)
class Category(models.Model):
    name = models.CharField(max_length=100, unique=True, verbose_name='分类名')
    parent = models.ForeignKey('self', on_delete=models.SET_NULL, blank=True, null=True, related_name='children', verbose_name='父分类')
    description = models.TextField(blank=True)
    url_token = models.CharField(max_length=100, unique=True, verbose_name='分类urltoken')
    seo_title = models.CharField(max_length=200, verbose_name='seo标题', blank=True)
    seo_keyword = models.CharField(max_length=100, verbose_name='seo关键词', blank=True)
    seo_description = models.CharField(max_length=500, verbose_name='seo描述', blank=True)
    level = models.IntegerField(blank=True, editable=False)
    order = models.IntegerField(default=100, blank=True, db_index=False, verbose_name='显示序号')

    class Meta:
        verbose_name = verbose_name_plural = '文章分类'

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('wiki:cate_url', kwargs={'cate_url_token': self.url_token})

    def save(self, *args, **kwargs):
        self.level = self.get_level()
        super(Category, self).save()  # 调用父类的save方法

    def get_level(self):
        level = 1  # 初始层级为1
        parent_category = self.parent  # 获取当前分类的父分类
        while parent_category is not None:
            level += 1  # 每找到一个父分类,层级加1
            parent_category = parent_category.parent  # 继续找上一个父分类
        return level  # 返回计算出的层级

    def get_descendants(self, include_self=True):
        queryset = self.children.all()
        if include_self:
            queryset = Category.objects.filter(pk=self.pk)
        else:
            queryset = Category.objects.none()

        stack = list(queryset)
        while stack:
            current = stack.pop(0)
            children = current.children.all()
            stack.extend(children)
            queryset |= children

        return queryset

5、文章详情页

主要展示文章内容信息、文章目录导航锚点、文章访问次数统计、相关文章

文章模型设计如下:

基础字段模型:标题、分类、作者、seo相关的自定义字段

特殊字段:

is_show:布尔类型,用来人工控制内容后续是否在前端显示(在 views 里用 filter 进行过滤)

content:文章主题内容字段,引入了富文本编辑器ueditor(django自带的编辑器不具备富文本能力)

函数功能:

get_pv():结合视图里进行处理,每次请求内容成功以后访问量+1。get_object_or_404()获取内容成功后调用get_pv()实现浏览量+1(初期简单实现,不考虑其他异常情况)

image.png

get_url_token():随机生成由数字+字母组成的 10-18 位字符串,并且在文章保存的时候自动保存此值,用来当做文章的url地址。

重写save函数,实现文章urltoken为空的话就自动生成一个,也可以人工修改。

image.pngimage.png


class Article(models.Model):
    title = models.CharField(max_length=200, verbose_name='标题')
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, verbose_name='分类', null=True, related_name='articles')
    author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    is_show = models.BooleanField(default=True, verbose_name='是否展示')

    # DjangoUeditor编辑器
    content = UEditorField(width=900, height=300, toolbars='full', imagePath='ueditor_img/', verbose_name='内容', blank=True, null=True)

    seo_title = models.CharField(max_length=200, verbose_name='seo标题', blank=True)
    seo_keyword = models.CharField(max_length=200, verbose_name='seo关键词', blank=True)
    seo_description = models.CharField(max_length=500, verbose_name='seo描述', blank=True)

    tags = models.ManyToManyField(Tag, blank=True)

    abstract = models.CharField(max_length=200, blank=True, verbose_name='摘要')
    create_time = models.DateTimeField(auto_now_add=True)
    modified_time = models.DateTimeField(auto_now=True)

    url_token = models.CharField(max_length=50, unique=True, blank=True)

    thumbnail_img = models.ImageField(upload_to='thumbnail/', verbose_name='缩略图', blank=True)
    pv_num = models.IntegerField(default=0, editable=False)


    class Meta:
        verbose_name = verbose_name_plural = '文章'

    def get_pv(self):
        self.pv_num += 1
        self.save(update_fields=['pv_num'])

    def save(self, *args, **kwargs):
        # 如果是新创建的实例,且url_token为空,则生成一个新的token
        if not self.id and not self.url_token:
            self.url_token = self.get_url_token()
        super(Article, self).save(*args, **kwargs)  # 调用父类的save方法

    def get_url_token(self):
        """
            Returns:
                token:随机生成由数字+字母组成的 10-18 位字符串
        """
        token = ""
        n = random.randint(10, 20)
        for i in range(n):
            num = random.randint(0, 9)
            # num = chr(random.randint(48,57))#ASCII表示数字
            letter = chr(random.randint(97, 122))  # 取小写字母

            s = str(random.choice([num, letter]))
            token += s
        return token

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('wiki:article_detail', kwargs={
            'cate_url_token': self.category.url_token,
            'article_url_token': self.url_token,

        })

6、文章目录功能

在视图里进行处理,模板中调用

6.1 使用BeautifulSoup解析文章内容(富文本带html标签的内容)

6.2 找出h2、h3标签

6.3 对h2、h3标签进行处理,存为一个文章目录信息的列表,供后续模板中使用

from django.shortcuts import render, get_object_or_404
from .models import Category, Article
from django.core.paginator import Paginator
from bs4 import BeautifulSoup
from slugify import slugify

def article_detail(request, cate_url_token, article_url_token):
    category = get_object_or_404(Category, url_token=cate_url_token)
    article = get_object_or_404(Article, url_token=article_url_token, is_show=True)
    # 统计阅读量
    article.get_pv()
    related_articles = category.articles.exclude(id=article.id, is_show=False)
    
    article_content = article.content
    
    # 生成内容的h标签-目录,并在原内容中添加锚点
    soup = BeautifulSoup(article_content, 'lxml')
    headings = soup.find_all(['h2', 'h3'])
    
    # 创建目录(TOC)列表
    toc = []
    
    for tag in headings:
        # 使用 slugify 生成 id 属性的值
        anchor = slugify(tag.get_text())
        # 直接在原内容标签上设置 id 属性
        tag['id'] = anchor
        # 将标签信息添加到目录列表
        toc.append({'tag_level': tag.name, 'tag_text': tag.get_text(), 'tag_anchor': anchor})
        # 如果需要将修改后的 HTML 字符串传递给模板
        content_modified_html = str(soup).replace('<html><body>', '').replace('</body></html>', '')
    context = {
        "category": category,
        "article": article,
        'request': request,
        'related_articles': related_articles,
        'toc': toc,
        'content_modified_html': content_modified_html,
        "wiki_cate_list": get_cate_list()
    }
    return render(request, "article_detail.html", context)
# 处理锚点的文字转友好的url格式
pip install awesome-slugify

模板代码:

<div class="left">
    <ul class="toc">{% for item in toc %}
            <li class="toc-level-{{ item.tag_level }}">
                <a href="#{{ item.tag_anchor }}">{{ item.tag_text }}</a>
            </li>{% endfor %}
    </ul>
</div>

7、后台富文本编辑器

对于写文章来说,需要具备一般的富文本编辑器功能(格式化段落、文字、插入图片、视频等);

对于网站发布文章有需要具备下面的必备功能:

(图片粘贴复制/上传、图片展示尺寸修改、远程图片自动转存到本服务器、代码块、代码高亮)

综合诉求和易用性试用了多款编辑器后,最后采用优化版的DjangoUeditor编辑器(支持python3版本)

# 'froala_editor',
# 'mdeditor',
# 'ckeditor',
# 'ckeditor_uploader',
# 'wangeditor',
# 'django_ckeditor_5',
# 'django_ckeditors',
'DjangoUeditor',

image.png

模板继承

注意:模板继承后相关变量需要写在继承后的 block 内才会生效

^