Django+Bootstrap 博客 首页、详情、评论

1.功能模块

1.1.写博客

1.2.首页banner、推荐

1.3.首页博客列表

1.4.分类、标签(添加、列表、点击)

1.5.博客详情、评论

 

 

2.URL配置 

3.models

class Category(models.Model):
    name = models.CharField(max_length=32, verbose_name='分类名')
    des = models.CharField(max_length=100, verbose_name='备注', null=True)

    def __str__(self):
        return self.name

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


class Tag(models.Model):
    name = models.CharField(max_length=32, verbose_name='标签名')
    des = models.CharField(max_length=100, verbose_name='备注', null=True)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '标签'
        verbose_name_plural = '标签'


class Blog(models.Model):
    title = models.CharField(max_length=70, verbose_name='文章标题')
    body = RichTextUploadingField(verbose_name='文章正文', config_name='default')
    created_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
    modified_time = models.DateTimeField(verbose_name='修改时间', auto_now=True)
    excerpt = models.CharField(max_length=200, blank=True, verbose_name='摘要')  # CharField类型默认不能为空,指定blank=True允许为空
    # on_delete=models.CASCADE,指明如果在Category中删除一条记录与这条记录有关联的博客记录也被删除
    category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='分类')
    tags = models.ManyToManyField(Tag, blank=True, verbose_name='标签')
    author = models.ForeignKey(LogUser, on_delete=models.CASCADE, verbose_name='作者')
    views = models.IntegerField(default=0, verbose_name='查看次数')
    cover_img = models.ImageField(upload_to='coverimage', blank=True, null=True, verbose_name='封面')
    blog_type = [
        ("1", "banner"),
        ("2", "recommend"),
        ("3", "common")
    ]
    type = models.CharField(max_length=1, choices=blog_type, default="common", verbose_name='类型')

    def get_absolute_url(self):
        """
        调用redirect(obj)函数时,如果obj是这个数据模型实例对象(相当一条表记录),
        redirect()执行后重定向到该obj对象的get_absolute_url()方法返回的URL.
        reverse()函数是一个URL反向解析函数
        """
        return reverse('blog:detail', kwargs={'pk': self.pk})

    def increase_views(self):
        self.views += 1
        self.save(update_fields=['views'])

    def save(self, *args, **kwargs):
        # 如果没有填写博客文章的摘要内容
        if not self.excerpt:
            """
            由于博客文章是由富文本编辑器编写的,文件中带有大量HTML标签
            用strip()函数可能会把HTML标签截断这样博客文章的摘要在页面显示时,可能会有乱码或不易查看
            strip_tags() 会把字段中的HTML 标签删去,然后在纯文本中截取字符串
            """
            self.excerpt = strip_tags(self.body)[:118]
            # 调用父类的save()方法将数据保存到数据库中
            super(Blog, self).save(*args, **kwargs)
        else:
            # 重写save()必须调用父类的save()方法,否则数据不会保存到数据库
            super(Blog, self).save(*args, **kwargs)

    def __str__(self):
        return self.title

    class Meta:
        # 设置按created_time的值倒序排列,这样最新的博客文章排在前面
        # 指定倒序需在字段名前加负号
        ordering = ['-created_time']
        verbose_name = '文档管理表'
        verbose_name_plural = '文档管理表'

 

4.配置settings.py

第一行命令安装CKEditor富文本编辑器,第二行命令安装图形处理模块。

pip install django-ckeditor

pip install pillow

注册富文本编辑器:

 配置富文本编辑器:

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

AUTH_USER_MODEL = "blog.LogUser"

# CKEDITOR_UPLOAD_PATH设置富文本编辑器的上传文件的相对路径
# 它与MEDIA_ROOT组成完整的路径,也就是/djangoProject_blog/media/ upload/
CKEDITOR_UPLOAD_PATH = 'upload/'
# 设置图片处理的引擎为pillow,用于生成图片缩略图,在编辑器里浏览上传的图片
CKEDITOR_IMAGE_BACKEND = 'pillow'
CKEDITOR_CONFIGS = {
    # 配置名是default时,django-ckeditor默认使用这个配置
    'default': {
        # 使用简体中文
        'language': 'zh-cn',
        # 设置富文本编辑器的宽度与高度
        'width': '860px',
        'height': '600px',
        # 设置工具栏为自定义,名字为Custom
        'toolbar': 'Custom',
        # 添加富文本编辑器的工具栏上的按钮
        'toolbar_Custom': [
            # 字体风格
            ['Bold', 'Italic', 'Underline'],
            # 字体颜色
            ['TextColor', 'BGColor'],
            # 格式、字体、大小
            ['Format', 'Font', 'FontSize'],
            # 列表
            ['Numbered List', 'Bulleted List'],
            # 链接
            ['Image', 'Link', 'Unlink'],
            # 居左,居中,居右
            ['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
            # 最大化
            ['Maximize']
        ]
    },
    # 设置另一个django-ckeditor配置,名为test
    'test': {}
}

5.博客首页通用视图函数

视图IndexView继承于ListView通用视图类,ListView视图类主要用于获取数据模型的记录集合并以列表显示场景

class IndexView(ListView):
    # 指定数据模型(数据库表),默认取全部记录
    model = models.Blog
    # 指定模块文件的地址与名字
    template_name = 'blog/index.html'
    # 指定传递给模板文件的模板变量名
    context_object_name = 'blog_list'

6.博客母版文件

base.html:

{% load static %}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>博客</title>
    <!-- 导入Bootstrap框架样式-->
    <link href="{% static '/css/bootstrap.min.css' %}" rel="stylesheet">
    <script src="{% static '/js/jquery-3.6.0.js' %}"></script>
    <script src="{% static '/js/bootstrap.min.js' %}"></script>
    <script src="{% static '/js/bootstrap.bundle.js' %}"></script>

</head>
<body>
<div class="container">
  <header class="blog-header lh-1 py-3">
    <div class="row flex-nowrap justify-content-between align-items-center">
      <div class="col-4 pt-1">
        <a class="link-secondary" href="/blog/write_blog/">写博客</a>
      </div>
      <div class="col-4 text-center">
        <h1 class="blog-header-logo text-dark" href="#">blog</h1>
      </div>
      <div class="col-4 d-flex justify-content-end align-items-center">
        <a class="link-secondary" href="#" aria-label="Search">
          <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="mx-3" role="img" viewBox="0 0 24 24"><title>Search</title><circle cx="10.5" cy="10.5" r="7.5"/><path d="M21 21l-5.2-5.2"/></svg>
        </a>
          {% if request.user.username %}
            <div class="dropdown">
                <a class="btn btn-secondary dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">{{ request.user.nikename }}</a>
                <ul class="dropdown-menu">
                    <li><a class="dropdown-item" href="#">个人资料</a></li>
                    <li><a class="dropdown-item" href="#">博客管理</a></li>

                    <li><a class="dropdown-item" href="/blog/logout">退出登录</a></li>
                </ul>
            </div>


          {% else %}
          <a class="btn btn-sm btn-outline-secondary" href="/blog/login">登录</a>
          <a class="btn btn-sm btn-outline-secondary" href="/blog/register">注册</a>
          {% endif %}

      </div>


    </div>
  </header>
    {% block main %}{% endblock main %}
</div>
<footer class="blog-footer">
  <p>Blog built for <a href="#">Bootstrap</a> by <a href="#">zj</a>.</p>
  <p>
    <a href="#">Back to top</a>
  </p>
</footer>
</body>
</html>

7.博客首页模板文件

{% extends 'base.html' %}
<!-- 导入自定义模板标签 -->
{% load custom_tags %}
{% block main %}
{% load static %}


<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>博客首页</title>
    <!-- 导入Bootstrap框架样式-->
    <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet" >
    <meta name="theme-color" content="#712cf9">


    <style>
      .bd-placeholder-img {
        font-size: 1.125rem;
        text-anchor: middle;
        -webkit-user-select: none;
        -moz-user-select: none;
        user-select: none;
      }

      @media (min-width: 768px) {
        .bd-placeholder-img-lg {
          font-size: 3.5rem;
        }
      }

      .b-example-divider {
        height: 3rem;
        background-color: rgba(0, 0, 0, .1);
        border: solid rgba(0, 0, 0, .15);
        border-width: 1px 0;
        box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
      }

      .b-example-vr {
        flex-shrink: 0;
        width: 1.5rem;
        height: 100vh;
      }

      .bi {
        vertical-align: -.125em;
        fill: currentColor;
      }

      .nav-scroller {
        position: relative;
        z-index: 2;
        height: 2.75rem;
        overflow-y: hidden;
      }

      .nav-scroller .nav {
        display: flex;
        flex-wrap: nowrap;
        padding-bottom: 1rem;
        margin-top: -1px;
        overflow-x: auto;
        text-align: center;
        white-space: nowrap;
        -webkit-overflow-scrolling: touch;
      }
    </style>
    <link href="https://fonts.googleapis.com/css?family=Playfair&#43;Display:700,900&amp;display=swap" rel="stylesheet">
    <link href="{% static 'css/blog.css' %}" rel="stylesheet">
</head>
<body>
<div class="container">
    <ul class="nav nav-pills mb-3 nav-justified" id="pills-tab" role="tablist">
        <li class="nav-item" role="presentation">
            <button class="nav-link active" id="pills-article-tab" data-bs-toggle="pill" data-bs-target="#pills-article" type="button" role="tab" aria-controls="pills-article" aria-selected="true" onclick="location.href='/blog/index/'">Article</button>
        </li>
        <li class="nav-item" role="presentation">
            <button class="nav-link" id="pills-author-tab" data-bs-toggle="pill" data-bs-target="#pills-author" type="button" role="tab" aria-controls="pills-author" aria-selected="false" onclick="location.href='/blog/author/'">Author</button>
        </li>
    </ul>
    <div class="tab-content" id="pills-tabContent">
        <div class="tab-pane fade show active" id="pills-article" role="tabpanel" aria-labelledby="pills-article-tab" tabindex="0"></div>
        <div class="tab-pane fade" id="pills-author" role="tabpanel" aria-labelledby="pills-author-tab" tabindex="0"></div>
    </div>


</div>

<main class="container">
<div>
<!-- 调用自定义标签文件custom_tags中定义的get_banner_blog()函数,获取type=banner的博客 -->
    {% get_banner_blog as banner_blog %}
    {% if banner_blog %}
      <div class="mb-4" style="position: relative;">
        <img src="/media/{{ banner_blog.cover_img }}" alt= "" width="100%" height="600px">
        <div class="col-md-6 px-0" style="position: absolute;font-size: 40px;letter-spacing: 10px;top: 40%;left: 5%;">
          <h1 class="display-4 fst-italic">{{ banner_blog.title }}</h1>
          <p class="lead my-3">{{ banner_blog.excerpt }}</p>
          <p class="lead mb-0"><a href="{{ banner_blog.get_absolute_url }}" class="text-white fw-bold">Continue reading...</a></p>
        </div>
      </div>
    {% else %}
        <p>暂无banner</p>
    {% endif %}

<!-- 调用自定义标签文件custom_tags中定义的get_recommend_blog()函数,获取type=banner的博客 -->
<div class="row mb-2">
    {% get_recommend_blog as recommend_blog_list %}
    {% for recommend_blog in recommend_blog_list %}

        <div class="col-md-6">
          <div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
            <div class="col p-4 d-flex flex-column position-static">
              <strong class="d-inline-block mb-2 text-primary">{{ recommend_blog.category.name }}</strong>
              <h3 class="mb-0">{{ recommend_blog.title }}</h3>
              <div class="mb-1 text-muted">{{ recommend_blog.created_time }}</div>
              <p class="card-text mb-auto" style="overflow: hidden;text-overflow: ellipsis;">{{ recommend_blog.excerpt }}</p>
              <a href="{{ recommend_blog.get_absolute_url }}" class="stretched-link">Continue reading</a>
            </div>
            <div class="col-auto d-none d-lg-block">
                <img class="media-object bd-placeholder-img" src="/media/{{ recommend_blog.cover_img }}" width="200" height="250">

            </div>
          </div>
        </div>

    {% empty %}
        <p>暂无recommend</p>
    {% endfor %}
</div>

  <div class="row g-5">
    <div class="col-md-8">
      <h3 class="pb-4 mb-4 fst-italic border-bottom">
        博客列表
      </h3>

      <article class="blog-post">
          {% for blog in blog_list %}
              <!-- h2 class="blog-post-title mb-1" onclick="location.href='{{blog.get_absolute_url }}'">{{ blog.title }}</h2 -->
              <h2 class="blog-post-title mb-1"><a href="{{ blog.get_absolute_url }}" style="text-decoration: none">{{ blog.title }}</a></h2>
              <p class="blog-post-meta">
                  <time class="entry-date" datetime="{{ blog.created_time }}">{{blog.created_time }}</time>by <a href="#">{{ blog.author.nikename }}</a>
              </p>
              <p>{{ blog.excerpt|safe }}......</p>
              <hr>
          {% endfor %}

      </article>

    </div>

    <div class="col-md-4">
      <div class="position-sticky" style="top: 2rem;">
        <div class="p-4 mb-3 bg-light rounded">
            <h4 class="fst-italic">About</h4>
            <p class="mb-0">博客列表</p>
            <ul>
                <li>默认查询所有博客数据</li>
                <li>点击作者,跳转到该作者的博客列表</li>
            </ul>
            <p>分类列表</p>
            <ol>
                <li>默认查询全部分类,通过自定义custom_tags实现</li>
                <li>点击分类,左侧博客列表查询该分类下的博客</li>
            </ol>
            <p>标签列表</p>
            <ol>
                <li>默认查询有博客数据的标签,通过自定义custom_tags实现</li>
                <li>点击标签,左侧博客列表查询该标签下的博客</li>
            </ol>

        </div>

        <div class="p-4">

            <div class="d-grid gap-2 d-md-flex justify-content-between">
                <h4 class="">分类管理</h4>
                <button class="btn btn-outline-primary h-25" type="button"
                        style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;"
                        onclick="show_add_category()">添加分类
                </button>
            </div>
            <form id="add_category" style="display: none" method="post" action="{% url 'blog:add_category' %}">
                {% csrf_token %}
                <div class="form-floating mb-3">
                    <input type="text" class="form-control" id="floatingInput" placeholder="name@example.com" name="name">
                    <label for="floatingInput">分类名称</label>
                </div>
                <div class="form-floating mb-3">
                    <input type="text" class="form-control" id="floatingInput" placeholder="name@example.com" name="des">
                    <label for="floatingInput">分类描述</label>
                </div>


                    <button  type="submit" onclick="cancel_add_category()">保存</button>
                    <button type="button" onclick="cancel_add_category()">取消</button>

            </form>
            <div>
                <!-- 调用自定义标签文件custom_tags中定义的get_categories()函数,显示每个类中的文章篇数 -->
                {% get_categories as category_list %}

                <a href="/blog/">全部分类</a>
                {% for category in category_list %}
                <ol class="list-unstyled mb-0">

                    <li><a href="{% url 'blog:category' category.pk %}">{{ category.name }}
                        <!-- 显示每个类中的文章篇数-->
                        <span class="post-count">({{ category.num_blogs }})</span>
                    </a></li>
                </ol>
                {% empty %}
                <span>暂无分类</span>
                {% endfor %}

            </div>
        </div>

        <div class="p-4">
            <div class="d-grid gap-2 d-md-flex justify-content-between">
                <h4 class="">标签管理</h4>
                <button class="btn btn-outline-primary h-25" type="button"
                        style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;"
                        onclick="show_add_tag()">添加标签</button>
            </div>
            <form id="add_tag" style="display: none" method="post" action="{% url 'blog:add_tag' %}">
                {% csrf_token %}
                <div class="form-floating mb-3">
                    <input type="text" class="form-control" id="floatingInput" placeholder="name@example.com" name="name">
                    <label for="floatingInput">标签名称</label>
                </div>
                <div class="form-floating mb-3">
                    <input type="text" class="form-control" id="floatingInput" placeholder="name@example.com" name="des">
                    <label for="floatingInput">标签描述</label>
                </div>


                    <button  type="submit" onclick="cancel_add_tag()">保存</button>
                    <button type="button" onclick="cancel_add_tag()">取消</button>

            </form>
            <div>
                <!-- 调用自定义标签文件custom_tags中定义的get_categories()函数,显示每个类中的文章篇数 -->
                {% get_tags as tag_list %}


                {% for tag in tag_list %}
                <ol class="list-unstyled mb-0">

                    <li><a href="{% url 'blog:tag' tag.pk %}">{{ tag.name }}
                        <!-- 显示每个类中的文章篇数-->
                        <span class="post-count">({{ tag.num_blogs }})</span>
                    </a></li>
                </ol>

                {% empty %}
                <span>暂无标签</span>
                {% endfor %}
                <a href="/blog/">全部标签</a>
            </div>

        </div>
      </div>
    </div>
  </div>

</main>



</body>
<script>
    function show_add_category() {
        document.getElementById("add_category").style.display="block";
    }
    function cancel_add_category() {
        document.getElementById("add_category").style.display="none";
    }
    function show_add_tag() {
        document.getElementById("add_tag").style.display="block";
    }
    function cancel_add_tag() {
        document.getElementById("add_tag").style.display="none";
    }

</script>
</html>
{% endblock main %}

8.分类、标签

8.1.添加分类、标签表单forms.py

class CategoryForm(forms.ModelForm):
    class Meta:
        # 指定数据模型
        model = models.Category
        fields = "__all__"


class TagForm(forms.ModelForm):
    class Meta:
        # 指定数据模型
        model = models.Tag
        fields = "__all__"

8.2.添加分类、标签视图函数views.py

def add_category(request):
    if request.method == 'POST':
        form_category = CategoryForm(request.POST)

        if form_category.is_valid():
            form_category.save()
            return redirect('/blog')
        else:
            print("添加失败")
            return redirect('/blog')
    form_category = forms.CategoryForm()
    return render(request, 'blog/index.html', {'form_category': form_category})


def add_tag(request):
    if request.method == 'POST':
        form = TagForm(request.POST)

        if form.is_valid():
            form.save()

            return redirect('/blog')
        else:
            print("添加失败")
            return redirect('/blog')
    form = forms.TagForm()
    return form

html:

 

8.3.分类列表、标签列表

新建blog/templatetags文件夹,在这个文件夹下创建一个custom_tags.py文件,存放自定义的模板标签

# 导入 template 这个模块
from django import template
# 导入用到的数据模型,由于models文件存放在本文件的上一级目录,所以在models前加上..
from ..models import Blog, Category, Tag
# 导入聚合模块相关函数
from django.db.models.aggregates import Count
# 实例化了一个 template.Library 类,是固定写法
register = template.Library()


# 将函数装饰为 register.simple_tag
# 这样就可以在模板文件中使用 {% get_categories %} 调用这个函数
@register.simple_tag
def get_categories():
    # 通过Django分类聚合函数统计每个分类中的文章的数量
    return Category.objects.annotate(num_blogs=Count('blog'))


@register.simple_tag
def get_tags():
    # 通过Django分类聚合函数统计每个标签中的文章的数量,并过滤掉没有文章的标签
    return Tag.objects.annotate(num_blogs=Count('blog')).filter(num_blogs__gt=0)

html:

 8.4.点击分类/标签,左侧博客列表显示对应博客数据

html:

 视图函数:

class CategoryView(ListView):
    # 设置数据模型,指定数据取自Blog,默认取全部记录
    model = models.Blog
    # 指定模板文件
    template_name = 'blog/index.html'
    # 指定传递给模板文件的参数名
    context_object_name = 'blog_list'

    def get_queryset(self):
        """
        在普通视图函数中将模板变量传递给模板
        通过给 render() 函数向模板文件传递一个字典来实现
        例如 render(request, 'blog/index.html', context={'Blog_list':blog_list})
        或者 render(request, 'blog/index.html',{'Blog_list':blog_list})
        在通用类视图中,如果不是获取数据模型的全部记录
        需要重写 get_context_data()方法,获得条件过滤后的记录
        在复写该方法时,还可以增加一些自定义的模板变量
        """
        cate = get_object_or_404(models.Category, pk=self.kwargs.get('pk'))
        # 继承父类的get_queryset()方法,并通过filter()函数对记录进行过滤
        # 通过order_by()进行排序
        return super(CategoryView, self).get_queryset().filter(category=cate).order_by('-created_time')


class TagView(ListView):
    model = models.Blog
    template_name = 'blog/index.html'
    context_object_name = 'blog_list'

    def get_queryset(self):
        tag = get_object_or_404(models.Tag, pk=self.kwargs.get('pk'))
        return super(TagView, self).get_queryset().filter(tags=tag).order_by('created_time')

9.写博客

博客模型:

class Blog(models.Model):
    title = models.CharField(max_length=70, verbose_name='文章标题')
    body = RichTextUploadingField(verbose_name='文章正文', config_name='default')
    created_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
    modified_time = models.DateTimeField(verbose_name='修改时间', auto_now=True)
    excerpt = models.CharField(max_length=200, blank=True, verbose_name='摘要')  # CharField类型默认不能为空,指定blank=True允许为空
    # on_delete=models.CASCADE,指明如果在Category中删除一条记录与这条记录有关联的博客记录也被删除
    category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='分类')
    tags = models.ManyToManyField(Tag, blank=True, verbose_name='标签')
    author = models.ForeignKey(LogUser, on_delete=models.CASCADE, verbose_name='作者')
    views = models.IntegerField(default=0, verbose_name='查看次数')
    cover_img = models.ImageField(upload_to='coverimage', blank=True, null=True, verbose_name='封面')
    blog_type = [
        ("1", "banner"),
        ("2", "recommend"),
        ("3", "common")
    ]
    type = models.CharField(max_length=1, choices=blog_type, default="common", verbose_name='类型')

    def get_absolute_url(self):
        """
        调用redirect(obj)函数时,如果obj是这个数据模型实例对象(相当一条表记录),
        redirect()执行后重定向到该obj对象的get_absolute_url()方法返回的URL.
        reverse()函数是一个URL反向解析函数
        """
        return reverse('blog:detail', kwargs={'pk': self.pk})

    def increase_views(self):
        self.views += 1
        self.save(update_fields=['views'])

    def save(self, *args, **kwargs):
        # 如果没有填写博客文章的摘要内容
        if not self.excerpt:
            """
            由于博客文章是由富文本编辑器编写的,文件中带有大量HTML标签
            用strip()函数可能会把HTML标签截断这样博客文章的摘要在页面显示时,可能会有乱码或不易查看
            strip_tags() 会把字段中的HTML 标签删去,然后在纯文本中截取字符串
            """
            self.excerpt = strip_tags(self.body)[:118]
            # 调用父类的save()方法将数据保存到数据库中
            super(Blog, self).save(*args, **kwargs)
        else:
            # 重写save()必须调用父类的save()方法,否则数据不会保存到数据库
            super(Blog, self).save(*args, **kwargs)

    def __str__(self):
        return self.title

    class Meta:
        # 设置按created_time的值倒序排列,这样最新的博客文章排在前面
        # 指定倒序需在字段名前加负号
        ordering = ['-created_time']
        verbose_name = '文档管理表'
        verbose_name_plural = '文档管理表'

视图函数:

save() 方法接受一个可选参数 commit ,它的值是 True 或者 False ,默认为 True。如果调用 save() 的时候使用 commit=False ,那么它会返回一个尚未保存到数据库的对象。在这种情况下,需要在生成的模型实例上调用 save() 。 

如果模型具有多对多关系,并且在保存表单时指定了 commit=False ,Django无法立即保存多对多关系的表单数据。这是因为实例的多对多数据只有实例在数据库中存在时才能保存。

要解决这个问题,在手动保存表单生成的实例后,可以调用 save_m2m() 来保存多对多的表单数据。

def write_blog(request):
    if request.method == 'POST':
        blog_form = forms.BlogForm(request.POST, request.FILES)
        if blog_form.is_valid():
            blog = blog_form.save(commit=False)
            blog.author = request.user
            blog.save()
            blog_form.save_m2m()
            return redirect('/blog')
        else:
            print("添加失败")
    form = forms.BlogForm()
    return render(request, 'blog/write_blog.html', {'form_blog': form})

html:

{% load static %}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!-- 导入Bootstrap框架样式-->
    <link href="{% static '/css/bootstrap.min.css' %}" rel="stylesheet">
    <!--导入ckeditor的初始化Java Script脚本,src的值是默认路径 -->
    <script src="{% static 'ckeditor/ckeditor-init.js' %}"></script>
    <!--导入ckeditor的Java Script脚本,src的值是默认路径 -->
    <script src="{% static 'ckeditor/ckeditor/ckeditor.js'%}"></script>
    <title>写博客</title>
</head>
<body>
<div class="row">
    <div class="col-md-offset-2 col-md-8">
        <!--<form>标签中的enctype属性设为"multipart/form-data",才能实现图片上传-->
        <form novalidate action="" method="post" class="form-horizontal" enctype="multipart/form-data">
            {% csrf_token %}
            <!-- 以下几个字段调用了Bootstrap样式 -->
            <div class="form-group">
                <label for="title" class="col-sm-2 control-label">{{ form_blog.title.label }}</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" id="title" name="title">
                </div>
            </div>
            <div class="form-group">
                <label for="title" class="col-sm-2 control-label">{{ form_blog.excerpt.label }}</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" id="excerpt" name="excerpt">
                </div>
            </div>
            <div class="form-group">
                <label for="title" class="col-sm-2 control-label">{{ form_blog.category.label }}</label>
                <div class="col-sm-10">
                    {{ form_blog.category }}
                </div>
            </div>
            <div class="form-group">
                <label for="title" class="col-sm-2 control-label">{{ form_blog.tags.label }}</label>
                <div class="col-sm-10">
                    {{ form_blog.tags }}
                </div>
            </div>

            <div class="form-group">
                <label for="title" class="col-sm-2 control-label">{{ form_blog.type.label }}</label>
                <div class="col-sm-10">
                    {{ form_blog.type }}
                </div>
            </div>

            <div class="form-group">
                <label for="title" class="col-sm-2 control-label">{{ form_blog.body.label }}</label>
                <div class="col-sm-10">
                    {{ form_blog.body }}
                </div>
            </div>

            <div class="form-group">
                <label>
                    {{ form_blog.cover_img.label }}
                </label>
                <div>
                    <label for="{{ form_blog.cover_img.id_for_label }}" class="col-sm-2 control-label">
                        {{ form_blog.cover_img }}
                        <!-- <image>标签的src属性先指向一个默认图片地址 //-->
                        <img id="file-img" src="{% static 'images/image-regular.svg' %}" style="height:100px;width:100px;">
                        <script>
                            var fileInput = document.querySelector('input[type="file"]'),
                                previewImg = document.querySelector('img');
                            fileInput.addEventListener('change', function () {
                                var file = this.files[0];
                                var reader = new FileReader();
                                // 监听reader对象的的onload事件,当图片加载完成时,把base64编码賦值给预览图片
                                reader.addEventListener("load", function () {
                                    previewImg.src = reader.result;
                                    }, false);
                                // 调用reader.readAsDataURL()方法,把图片转成base64
                                reader.readAsDataURL(file);
                                }, false);
                        </script>
                    </label>
                </div>
            </div>


            <button type="submit">保存</button>
            <button type="button" onclick="location.href='/blog/'">取消</button>
        </form>
    </div>
</div>



</body>
<script src="{% static '/js/jquery-3.6.0.js' %}"></script>
<script src="{% static '/js/bootstrap.min.js' %}"></script>
</html>

10.博客详情

视图函数

class BlogDetailView(DetailView):
    # 指定数据模型
    model = models.Blog
    # 指定模板文件
    template_name = 'blog/detail.html'
    # 指定模板变量名
    context_object_name = 'blog'
    # 指定主键,'pk'为配置文件中的URL参数名
    pk_url_kwarg = 'pk'
    # 重写父类的get_object()方法,这个方法可以返回主键等于URL参数值的记录对象
    # 可以进一步对这个记录对象进行操作,如调用该对象的方法

    def get_object(self,queryset=None):
        # 调用父类get_object()取得一条记录,主键等于URL参数pk的值
        blog=super(BlogDetailView,self).get_object(queryset=None)
        # 调用这条记录对象的increase_views()方法,把views字段值加1
        blog.increase_views()
        return blog

    def get_context_data(self,**kwargs):
        # 通过调用父类方法得到一个包含模板变量的字典
        context=super(BlogDetailView,self).get_context_data(**kwargs)
        # 初始化Commetn Form表单
        form=CommentForm()
        # 取得本条记录对象的所有评论
        comment_list=self.object.comment_set.all()
        # 在模板变量字典中加入新的字典项
        context.update({'form':form,'comment_list':comment_list})
        return context

html:

{% extends 'base.html' %}
{% load static %}
{% block main %}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>博客详情</title>
    <!-- 导入Bootstrap框架样式-->
    <link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet" >
    <link href="{% static 'css/blog.css' %}" rel="stylesheet">
</head>
<body>
<article class="blog blog-{{ blog.pk }}">
    <header class="entry-header">
        <h1 class="entry-title">{{ blog.title }}</h1>
        <div class="entry-meta-detail">
            <!-- 用5个<span>标签显示文章类别、发布时间、作者、评论数、阅读数-->
            <span class="blog-category">
                <a href="#">{{blog.category.name }}</a>
            </span>
            <span class="blog-date">
                <a href="#"><time class="entry-date" datetime="{{ blog.created_time }}">{{ blog.created_time }}</time></a>
            </span>
            <span class="blog-author"><a href="#">{{blog.author.nikename }}</a></span>
            <span class="comments-link">
                <a href="#">共
                    <span>{{ comment_list | length }}</span> 条评论</a>
            </span>
            <span class="views-count">
                <a href="#">{{ blog.views }} 阅读</a>
            </span>
        </div>
    </header>
    <div class="entry-content clearfix">
        <!-- 文章内容,用safe过滤器防止文章的样式被转义-->
        {{ blog.body| safe }}
    </div>
</article>
<!-- 判断用户是否登录,用户登录后才可以发表评论-->
{% if request.user.username %}
<section class="comment-area">
    <h3>发表评论</h3>
    <hr>
    <!-- 页面表单,{% url 'comments:blog_comment' blog.pk %}反向解析成 URL, blog.pk 作为参数传给URL表达式-->
    <form action="{% url 'comments:blog_comment' blog.pk %}" method="post">
        {% csrf_token %}
        <div class="form-group">
            <label class="col-md-2">名字:</label>
            {{ request.user.nikename }}
        </div>
        <div class="form-group">
            <label class="col-md-2">邮箱:</label>
            {{ request.user.email }}
        </div>
        <div class="form-group">
            <label for="{{ form.text.id_for_label }}" class="col-md-2">评论:</label>
            {{ form.text }}
            {{ form.text.errors.0 }}
        </div>
        <div class="form-group">
            <div class="col-8 mx-auto">
                <button type="submit" class="btn btn-outline-primary btn-sm">发表</button>
            </div>
        </div>
    </form>
{% endif %}

<div class="panel panel-default">
    <div class="panel-heading">
        <h5>评论列表,共 <span>{{comment_list|length}}</span> 条评论</h5>
    </div>
    <div class="panel-body">
        <ul class="comment-list list-unstyled">
            <!--  通过for循环取出每一条记录-->
            {% for comment in comment_list %}
                <li><span style="color: #777;font-size: 14px;">{{comment.name }}&nbsp;·&nbsp;</span>
                    <time style="color: #777;font-size: 14px;">{{comment.created_time }}</time>
                    <div style="padding-top: 5px;font-size: 16px;">{{ comment.text }}</div>
                </li>
            {% empty %}
                暂无评论
            {% endfor %}
        </ul>
    </div>
</div>
</section>
</body>
</html>
{% endblock main %}

11.评论

创建评论应用程序,数据模型:

from django.db import models


# Create your models here.
class Comment(models.Model):
    name = models.CharField(max_length=32)
    email = models.EmailField(max_length=60)
    # 评论可能有较长的文本,因此用TextField类型
    text = models.TextField()
    # created_time字段的auto_now_add=True,这样自动取出本记录保存时的时间
    created_time = models.DateTimeField(auto_now_add=True)
    # 一条评论只能属于一篇文章,一篇文章可以有多条评论# 文章与评论是一对多的关系,所以使用 ForeignKey类型
    blog = models.ForeignKey('blog.Blog', on_delete=models.CASCADE,)

    def __str__(self):
        # 取评论前20个字符
        return self.text[:20]

URL配置:

from django.urls import path
from . import views
# 指定命名空间
app_name = 'comments'
urlpatterns = [
    path('comment/post/<int:blog_pk>/', views.blog_comment, name='blog_comment'),
]

文章评论视图函数:

from django.shortcuts import render,get_object_or_404, redirect
from blog.models import Blog
from . forms import CommentForm


# blog_pk是URL实名参数
def blog_comment(request, blog_pk):
    # get_object_or_404()函数的作用是当要获取的文章(Blog)存在时,则获取该文章
    # 否则返回 404 页面给用户
    blog = get_object_or_404(Blog, pk=blog_pk)
    # HTTP 请求使用最多的是 GET 和 POST 两种
    # 如果是POST 请求,说明是前端页面提交数据
    # 因此当请求为 POST时才需要处理表单数据
    if request.method == 'POST':
        # request.POST是一个字典类型的对象,表单提交的数据也保存在这个对象中
        # 因此可用request.POST给CommentForm对象赋值
        form = CommentForm(request.POST)
        # form.is_valid() 方法检查表单的数据是否符合格式要求
        if form.is_valid():
            # 由于form是ModelForm类型,可以直接调用save() 方法保存数据到数据库表中
            # commit=False 的作用是生成 Comment 类的实例对象
            # 但不立刻保存数据到数据库表中
            comment = form.save(commit=False)
            # 用户登录后,request.user保存用户的nikename、email等值
            comment.name = request.user.nikename
            comment.email = request.user.email
            # 通过外键关系将评论和被评论的文章关联起来
            comment.blog = blog
            # 真正保存到数据库表中
            comment.save()
            # redirect(blog)调用数据模型Blog的实例对象blog的get_absolute_url()方法
            # 然后重定向到 get_absolute_url()方法返回的 URL
            return redirect(blog)
        else:
            # 数据校验不通过,需要重新渲染页面
            # 需要传递3个模板变量给 detail.html,文章(blog)、评论列表、表单对象(form)
            comment_list = blog.comment_set.all()
            context = {'blog': blog,'form': form,'comment_list': comment_list}
            return render(request, 'blog/detail.html',context=context)
    # 如果请求方法不是POST,说明是第一次打开页面
    # 重定向到实例对象blog的 get_absolute_url()方法返回的地址
    return redirect(blog)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这里提供一个简单的 Django+Bootstrap 注册登录的示例代码。 1. 首先在 Django 项目中创建一个 app,命名为 accounts。 2. 在 accounts 下创建 templates/accounts 目录,用于存放注册登录模板。 3. 在 accounts 下创建 views.py 文件,实现注册和登录的逻辑。 ```python from django.shortcuts import render, redirect from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from django.contrib.auth import login, authenticate def signup_view(request): if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save() login(request, user) return redirect('home') else: form = UserCreationForm() return render(request, 'accounts/signup.html', {'form': form}) def login_view(request): if request.method == 'POST': form = AuthenticationForm(data=request.POST) if form.is_valid(): user = form.get_user() login(request, user) return redirect('home') else: form = AuthenticationForm() return render(request, 'accounts/login.html', {'form': form}) ``` 4. 在 accounts 下的 urls.py 文件中配置路由。 ```python from django.urls import path from . import views urlpatterns = [ path('signup/', views.signup_view, name='signup'), path('login/', views.login_view, name='login'), ] ``` 5. 在 templates/accounts 目录下创建注册登录模板,分别命名为 signup.html 和 login.html。 signup.html: ```html {% extends 'base.html' %} {% block content %} <h2>Sign up</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Sign up</button> </form> {% endblock %} ``` login.html: ```html {% extends 'base.html' %} {% block content %} <h2>Login</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Login</button> </form> {% endblock %} ``` 6. 在模板中引入 Bootstrap,可以在 base.html 中引入。 ```html {% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}{% endblock %}</title> <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}"> </head> <body> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="#">My Site</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li class="nav-item active"> <a class="nav-link" href="{% url 'home' %}">Home</a> </li> {% if user.is_authenticated %} <li class="nav-item"> <a class="nav-link" href="{% url 'logout' %}">Logout</a> </li> {% else %} <li class="nav-item"> <a class="nav-link" href="{% url 'login' %}">Login</a> </li> <li class="nav-item"> <a class="nav-link" href="{% url 'signup' %}">Sign up</a> </li> {% endif %} </ul> </div> </nav> <div class="container"> {% block content %} {% endblock %} </div> <script src="{% static 'js/jquery.min.js' %}"></script> <script src="{% static 'js/bootstrap.min.js' %}"></script> </body> </html> ``` 以上就是一个简单的 Django+Bootstrap 注册登录示例。注意,这只是一个基础示例,实际开发中需要进行更多的安全、验证、错误处理等方面的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值