首页 Django Django2 By Example 01 Blog Project
文章
取消

Django Django2 By Example 01 Blog Project

创建一个blog应用

1. 创建应用

python manage.py startapp blog

2. 设计博客的数据架构

定义数据模型,模型类继承值django.db.models.Model 每个属性表示数据库的一个字段 django为models.py中的每一个模型创建一张数据库表,创建模型后,django会提用api进行数据库查询。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User


class Post(models.Model):
    STATUS_CHOICES = (
        ('draft', 'Draft'),
        ('published', 'Published'),
    )

    title = models.CharField(max_length=250)
    slug = models.SlugField(max_length=250,
                            unique_for_date='publish')
    author = models.ForeignKey(User,
                               related_name='blog_posts', 
                               on_delete=models.CASCADE)
    body = models.TextField()
    publish = models.DateTimeField(default=timezone.now)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=10,
                              choices=STATUS_CHOICES,
                              default='draft')

    class Meta:
        ordering = ('-publish',)

    def __str__(self):
        return self.title

user 类 相关参考 django内置的权限系统包括 users permissions groups

1
2
3
4
5
6
7
8
9
10
11
12
13
14
django 升级到2.0之后,表与表之间关联的时候,必须要写on_delete参数,否则会报异常:on_delete=None, # 删除关联表中的数据时,当前表与其关联的field的行为

on_delete=models.CASCADE, # 删除关联数据,与之关联也删除
on_delete=models.DO_NOTHING, # 删除关联数据,什么也不做
on_delete=models.PROTECT, # 删除关联数据,引发错误ProtectedError
# models.ForeignKey('关联表', on_delete=models.SET_NULL, blank=True, null=True)
on_delete=models.SET_NULL, # 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空,一对一同理)
# models.ForeignKey('关联表', on_delete=models.SET_DEFAULT, default='默认值')
on_delete=models.SET_DEFAULT, # 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值,一对一同理)
on_delete=models.SET, # 删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

由于多对多(ManyToManyField)没有 on_delete 参数,所以以上只针对外键(ForeignKey)和一对一(OneToOneField)

3. 激活应用

settings.py中的installed_apps中添加blog

4. 创建以及应用数据库迁移

Django自带一个迁移系统,追踪模型变化并同步到数据库中

创建迁移 使用 makemigrations命令

python manage.py makemigrations blog

得到输出:

1
2
3
Migrations for 'blog':
  blog\migrations\0001_initial.py
    - Create model Post

python manage.py sqlmigrate blog 0001可以查看django将要执行的sql语句

python manage.py migrate 将会应用数据库迁移,保持数据库与模型的同步

5. 为模型创建管理站点

Django已经内置了管理界面

5.1 创建超级用户

python manage.py createsuperuser

5.2 添加模型到管理站点

1
2
3
4
5
from django.contrib import admin
from .models import Post

# Register your models here.
admin.site.register(Post)

localhost:8000/admin上面会有对应的模型管理

5.3 自定义模型显示方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.contrib import admin
from .models import Post


# Register your models here.
class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'slug', 'author', 'publish', 'status')
    list_filter = ('status', 'created', 'publish', 'author')
    search_fields = ('title', 'body')
    prepopulated_fields = {'slug': ('title',)}
    raw_id_fields = ('author',)
    date_hierarchy = 'publish'
    ordering = ['status', 'publish']


admin.site.register(Post, PostAdmin)
  • list_display 展示指定的字段

  • list_filter 用指定字段过滤文章

  • search_fields 指定搜索的字段

  • prepopulated_fields 预填充字段,用title字段预填充slug字段

  • raw_id_fields 字段变成了搜索控件

  • date_hierarchy 多了一个根据指定字段的快速导航栏

  • ordering 按指定字段进行默认排序

6. 使用queryset和管理器

django的ORM(object-relational mapper)兼容mysql postgresql sqlite和oracle,可以在settings.py中编辑databases设置

提前配置好对象和数据库之间的映射关系,orm就可以自动生成sql语句;

orm是双向的数据交互技术,可以将对象中的数据存储到数据库中,数据库的数据也可以提取到对象中。

6.1 创建对象

先尝试在内存中进行对象创建

1
2
3
4
5
6
7
8
>>> from django.contrib.auth.models import User
>>> from blog.models import Post
>>> user = User.objects.get(username='beyond') 
>>> post = Post(title='orm test',  
...             slug='orm test',
...             body='haha orm test',
...             author=user)
>>> post.save()

在管理网站上已经可以看见该帖子记录。

  • get方法检索单个对象(没结果和多结果都会报错)

  • save方法将内存中的该对象存储到数据库中

6.2 更新对象

1
2
>>> post.title='a new title'
>>> post.save()

此时调用save方法会调用update

6.3 检索对象

之前写到的User.objects.get中的objects称为django模型的默认管理器(每个模型至少有一个),通过该管理器可以获取一个QuertSet对象(django的orm是基于queryset的)

通过调用all()方法可以检索所有对象(queryset是惰性的,只有强制执行时才会执行)

通过使用filter()方法可以进行过滤,相关例子:

1
2
# 通过两个下划线可以获取相关字段名
Post.objects.filter(publish__year=2022,author__username='beyond')

通过使用exclude()方法可以进行排除某些结果,通过order_by可以指定字段排序(字段前-表示倒序)

1
Post.objects.filter(publish_year=2022).exclude(title__startswith='haha').order_by('-title')

6.4 删除对象

通过delete()方法删除对象

删除对象会删除所有依赖关系

6.5 自定义管理器

比如用以检索所有published状态的

1
2
3
4
5
6
7
class PublishedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(status='published')

class Post(models.Model):
    objects = models.Manager()
    published = PublishedManager()

测试一下

1
2
3
4
5
>>> from blog.models import Post
>>> Post.objects.all()            
<QuerySet [<Post: a new title>, <Post: test2>, <Post: 123>]>
>>> Post.published.all() 
<QuerySet [<Post: a new title>]>

7. 构建列表和详情视图

django视图就是一个python函数,接收web请求,返回web响应

步骤:

  • 创建应用视图

  • 定义视图的url模式

  • 创建html模板渲染视图时所需的数据

7.1 创建列表和详情视图

1
2
3
4
5
def post_list(request):
    posts = Post.published.all()
    return render(request,
                  'blog/post/list.html',
                  {'posts': posts})

request是所有视图必需的参数,render快捷方法考虑了请求上下文,由模板上下文处理器设置的任何变量都可以由给定的模板访问

7.2 为视图添加URL模式

一个url模式有一个python的正则表达式,一个视图和一个项目范围内的名字组成

可以为blog应用单独创建一个urls.py

1
2
3
4
5
6
7
8
9
10
from django.urls import path

from blog import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
    re_path(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<post>[-\w]+)/$',
         views.post_detail,
         name='post_detail')
]

新版的不用url了 用path 正则用 re_path

在主url模式中include

1
2
3
4
5
urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
    path('blog/', include(('blog.urls', 'blog'))
]

7.3 模型的标准URLs

Django的惯例是在模型中添加 get_absolute_url(),该方法返回对象的标准url

1
2
3
4
5
6
    def get_absolute_url(self):
        return reverse('blog:post_detail',
                       args=[self.publish.year,
                             self.publish.strftime('%m'),
                             self.publish.strftime('%d'),
                             self.slug])

strftime 函数构造使用0开头的月份和日期

reverse方法运行通过名字以及传递参数构造URLs

8. 为视图创建模板

根目录下创建如下所示的模板文件(模板文件也可以放到blog应用下)

1
2
3
4
5
6
templates/
    blog/
        base.html
        post/
            list.html
            detail.html

需要在settings中的templates中写明templates路径,否则会找不到

再提一句,若要使pycharm的settings.py生效,需要在pycharm的django支持里指明settings.py文件

类似的,如果要添加静态文件,比如css样式之类的,同样需要在settings.py中写明static地址 STATICFILES_DIRS = [BASE_DIR/"static"]

django的模板控制语言包括:

  • 模板标签 {\% tag \%}

  • 模板变量 {{ variable }}

  • 模板过滤器 {{ variable|filter }}

具体参考 [Built-in template tags and filters Django documentation Django](https://docs.djangoproject.com/en/4.1/ref/templates/builtins/)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}{% endblock %}</title>
   <link href="{% static "css/blog.css" %}" rel="stylesheet">
</head>
<body>
    <div id="content">
        {% block content %}
        {% endblock %}
    </div>
    <div id="sidebar">
        <h2>My blog</h2>
        <p>This is my blog.</p>
    </div>
</body>
</html>

block表示一个块,继承该模板的模块将用具体内容替换这两个块,对应的名称是title和content

list.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{% extends "blog/base.html" %}
{% block title %}My Blog{% endblock %}
{% block content %}
    <h1>My Blog</h1>
    {% for post in posts %}
        <h2>
            <a href="{{ post.get_absolute_url }}">
                {{ post.title }}
            </a>
        </h2>
        <p class="date">
        Published {{ post.publish }} by {{ post.author }}
        </p>
        {{ post.body|truncatewords:30|linebreaks }}
    {% endfor %}
{% endblock %}
  • extends来继承模板,定义了两个块的具体内容,for循环中的posts,html可以读到views文件中传的posts参数

detail.html

1
2
3
4
5
6
7
8
9
10
11
12
{% extends 'blog/base.html' %}
{% block title %}
{{ post.title }}
{% endblock %}

{% block content %}
    <h1>{{ post.title }}</h1>
    <p class="date">
    Published {{ post.publish }} by {{ post.author }}
    </p>
    {{ post.body|linebreaks }}
{% endblock %}

效果:

9. 分页

view

1
2
3
4
5
6
7
8
9
10
11
12
13
def post_list(request):
    object_list = Post.published.all()
    paginator = Paginator(object_list, 3)
    page = request.GET.get('page')
    try:
        posts = paginator.page(page)
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)
    return render(request,
                  'blog/post/list.html',
                  {'posts': posts})

分页工作原理:

  • 初始化:显示对象,以及显示数量

  • 获得GET中的page参数,表示当前页码

  • paginator.page 获得显示页对象

  • page不是整数 显示第一页,超过页码,显示最后一页

创建pagination.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="pagination">
    <span class="step-links">
        {% if page.has_previous %}
            <a href="?page={{ page.previous_page_number }}">Previous</a>
        {% endif %}
        <span class="current">
            Page {{ page.number }} of {{ page.paginator.num_pages }}.
        </span>
        {% if page.has_next %}
            <a href="?page={{ page.next_page_number }}">Next</a>
            {% endif %}
    </span>
</div>

// 这几个class 没找到 不过功能不影响

在需要分页的地方引入pagination.html,比如list.html

1
2
3
4
{% block content %}
    ...
    {% include "pagination.html" with page=posts %}
{% endblock %}

10. 基于类的视图

暂略

增强博客功能

1. 通过邮件分享文章

步骤:

  • 创建表单让用户填写内容

  • 视图层控制表单,发送邮件

  • 配置url

  • 创建展示表单的模板

1.1 创建表单

django 提供表单框架

  • Form 生成标准的表单

  • ModelForm 用于从模型生成表单

创建forms.py

1
2
3
4
5
6
7
8
from django import forms


class EmailPostForm(forms.Form):
    name = forms.CharField(max_length=25)
    email = forms.EmailField()
    to = forms.EmailField()
    comments = forms.CharField(required=False, widget=forms.Textarea)

表单可以写在任何地方,但习惯是编写在应用目录下forms.py中

CharField会被渲染为<input type='text'>,但每个字段都有一个默认的widget字段来修改渲染,比如Textarea 会渲染为<textarea>元素,可以在后边添加更多内容来自定义,比如手机号:

1
2
3
4
5
6
7
phone = forms.CharField(widget=forms.TextInput(
        attrs={"class": "form-control", "placeholder": "请输入手机号", "required": "required", }),
        min_length=11, max_length=11,
        validators=[  # 自定义校验规则
            RegexValidator(r'(13[0-9]|15[7-9]|153|156|18[7-9])[0-9]{8}', '请输入正确的手机号'),  # 第一个参数定义正则规则,第二个参数为错误信息
      ],
    )

1.2 通过视图控制表单

1
2
3
4
5
6
7
8
9
10
11
def post_share(request, post_id):
    post = get_object_or_404(Post, id=post_id, status='published')
    if request.method == 'POST':
        form = EmailPostForm(request.POST)
        if form.is_valid():
            # 获取验证后的数据
            cd = form.cleaned_data
            # 发送邮件...
    else:
        form = EmailPostForm()
    return render(request, 'blog/post/share.html', {'post': post, 'form': form})

1.3 通过django发送邮件

需要配置邮件服务器,要么本地搭建,要么使用外部的smtp服务器,这里可以拿qq邮箱的smtp服务器进行测试,在settings.py文件中添加如下配置

1
2
3
4
5
EMAIL_HOST = 'smtp.qq.com'
EMAIL_HOST_USER = 'your_account@qq.com'
EMAIL_HOST_PASSWORD = 'your_password'
EMAIL_PORT = 25
EMAIL_USE_TLS = True
1
2
3
>>> from django.core.mail import send_mail
>>> send_mail('Django mail', 'This e-mail was sent with Django.', 'xxxx@qq.com', ['xxxx@gmail.com'], fail_silently=False)          
1

第一个是标题,第二个是内容,发件人,收件人列表,最后一个发送失败就抛出异常,返回1 说明发送成功

在views中添加相关代码:

1
2
3
4
5
6
    # 发送邮件...
    post_url = request.build_absolute_uri(post.get_absolute_url())
    subject = '{}({}) recommends you reading "{}"'.format(cd['name'],cd['email'],post.title)
    message = 'Read "{}" at {}\n\n{}\'s comments:{}'.format(post.title, post_url, cd['name'], cd['comments'])
    send_mail(subject, message, 'xxxx@qq.com', [cd['to']])
    sent = True

1.4 模板中渲染表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{% extends "blog/base.html" %}

{% block title %}Share a post{% endblock %}

{% block content %}
    {% if sent %}
        <h1>E-mail successfully sent</h1>
        <p>
            "{{ post.title }}" was successfully sent to {{ form.cleaned_data.to }}.
        </p>
    {% else %}
        <h1>Share "{{ post.title }}" by e-mail</h1>
        <form action="." method="post">
        {{ form.as_p }}
            {% csrf_token %}
            <input type="submit" value="Send e-mail">
        </form>
    {% endif %}
{% endblock %}

form.as_p 以p标签渲染,还包括as_ul as_table

csrf_token 隐藏的input元素 自动生成的防御csrf的token

也可以渲染每个表单元素

迭代:

1
2
3
4
5
6
{% for field in form %}
    <div>
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
    </div>
{% endfor %}

按字段名指定:

1
2
3
4
5
6
7
<div class="mb-4">
    <label class="form-label" for="phone1">Phone</label>
    {{ form.phone }}
    {% if form.errors.phone %}
        <span class="error_msg" style="color:red">{{ form.errors.phone.0 }}</span>
    {% endif %}
</div>

2. 创建评论系统

用户可以对文章进行评论,前提:

  • 创建模型存储评论数据

  • 创建表单用于提交评论和验证数据

  • 创建视图用于处理数据并存入数据库

  • 编辑模板页展示评论和提供写新评论的表单

模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Comment(models.Model):
    # CASCADE 级联删除 related_name 不定义 默认 模型名小写_set
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
    name = models.CharField(max_length=80)
    email = models.EmailField()
    body = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    active = models.BooleanField(default=True)

    class Meta:
        ordering = ("created",)

    def __str__(self):
        return 'Comment by {} on {}'.format(self.name, self.post)

related_name 定义了通过文章找评论的时候引用该关联关系的名称,定义该外键后 comment.post 可以获得对应的文章,通过 post.comments.all() 获取该文章对应的所有评论,不显式定义,默认模型名小写_set comment_set+

active 用于 手动关闭 不合适的评论

创建后,执行migrate命令 进行数据库迁移,可以在admin.py中注册comment模型

1
2
3
4
5
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
    list_display = ('name', 'email', 'post', 'created', 'active')
    list_filter = ('active', 'created', 'updated')
    search_fields = ('name', 'email', 'body')

admin.site.register(Comment, CommentAdmin)可以用 admin.site.register(Post, PostAdmin)来替代

2.1 根据模型创建表单

Django有两个表单类,Form和ModelFrom,这里使用ModelForm根据Comment模型动态地生成表单

1
2
3
4
5
6
7
class CommentForm(forms.ModelForm):
    # Meta是模型内置类,一般是用来提供一些元数据
    class Meta:
        # 说明基于哪个类,django会创建对饮表单,每个字段一般都会创建表单元素,可以通过field来指定字段
        model = Comment
        # 也可以使用exclude来排除不想显示的字段
        fields = ('name', 'email', 'body')

2.2 视图中处理表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def post_detail(request, year, month, day, post):
    post = get_object_or_404(Post,
                             slug=post,
                             status='published',
                             publish__year=year,
                             publish__month=month,
                             publish__day=day)
    comments = post.comments.filter(active=True)
    new_comment = None
    if request.method == "POST":
        comment_form = CommentForm(data=request.POST)
        if comment_form.is_valid():
            # 通过表单直接创建数据对象
            new_comment = comment_form.save(commit=False)
            # 设置comment的外键为当前post
            new_comment.post = post
            # 将数据保存到数据库中
            new_comment.save()
    else:
        comment_form = CommentForm()
    return render(request,
                  'blog/post/detail.html',
                  {'post': post, 'comments': comments, 'new_comment': new_comment, 'comment_form': comment_form})

save()方法仅对ModelForm生效,因为Form类没有关联到任何数据模型

2.3 页面上添加评论

展示内容:所有评论数 所有评论 展示表单用于添加评论

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{# with标签可以防止对数据库进行反复查询 #}
    {# 模板中调用count方法不用加()也意味着不能模板中执行方法不能带参数 #}
    {% with comments.count as total_comments %}
        <h2>
        {# pluralize模板过滤器,根据数量加不加啊s #}

            {{ total_comments }} comment{{ total_comments|pluralize }}
        </h2>
    {% endwith %}

    {% for comment in comments %}
        <div class="info">
            <p>
                Comment {{ forloop.counter }}  by {{ comment.name }}
                {{ comment.created }}
            </p>
            {{ comment.body|linebreaks }}
        </div>
    {% empty %}
        <p>There are no comments yet.</p>
    {% endfor %}
    {% if new_comment %}
        <h2>成功添加评论</h2>
    {% else %}
        <h2>add a new comment</h2>
        <form action="." method="post">
        {{ comment_form.as_p }}
        {% csrf_token %}
        <p><input type="submit" value="add comment"></p>
        </form>
    {% endif %}

可以尝试在admin中将active改为false 会取消显示

3. 添加标签功能

主要通过集成django-taggit第三方应用模块

该模块通过Tag数据模型和一个管理器来给任何模型加上标签

1
pip install django_taggit

需要在 INSTALLED_APP中激活 加上 taggit 即可

post模型 添加 tags = TaggableManager(),接着进行数据库迁移

可以给标签进行一个简单测试 python manage.py shell进入shell

1
2
3
4
5
6
7
8
9
10
>>> from blog.models import Post             
>>> post = Post.objects.get(id=1)
>>> post.tags.add('music', 'jazz', 'django') 
>>> post.tags.all()
<QuerySet [<Tag: music>, <Tag: django>, <Tag: jazz>]>
>>> post.tags.remove('django')
>>> post.tags.all()            
<QuerySet [<Tag: music>, <Tag: jazz>]>
>>>

页面中展示标签

1
<p class="tags">Tags: {{ post.tags.all|join:", " }}</p>

image-20221026120046845

通过标签过滤文章,修改视图post_list(),并将tag传给render

1
2
3
4
5
6
7
def post_list(request,tag_slug=None):
    object_list = Post.published.all()
    tag=None
    if tag_slug:
        tag=get_object_or_404(Tag, slug=tag_slug)
        object_list=object_list.filter(tags__in=[tag])
    # ...

url 添加:

1
path('tag/<slug:tag_slug>/', views.post_list, name='post_list_by_tag'),

list.html模板中添加代码来展示标签,每个文章的标签,添加链接来过滤文章:

1
2
3
4
5
6
7
8
9
10
11
{% if tag %}
	<h2>Posts tagged with "{{ tag.name }}"</h2>
{% endif %}
....
<p class="tag">
    Tags:
    {% for tag in post.tags.all %}
    <a href="{% url "blog:post_list_by_tag" tag.slug %}">{{ tag.name }}</a>
    {% if not forloop.last %}, {% endif %}
    {% endfor %}
</p>

{% url "blog:post_list_by_tag" tag.slug %} 访问blog应用下namepost_list_by_tag的url,tag.slug就是带入的参数

image-20221026141849249

4. 通过相似性获取文章

添加标签功能后,相同主题的文章通常会有相同的标签,可以根据共同标签的数量的多少来推荐文章

步骤如下:

  • 获取当前文章所有标签
  • 获取所有具备这些标签的文章
  • 去掉当前文章,防止重复显示
  • 根据标签的数量多少进行排序,再根据时间进行排序
  • 限制数目

在admin中多添加些文章和标签

可以创建这些文章进行测试

image-20221026142748071

更复杂的QuerySet

django.db.models包含下列聚合函数 Count Avg Min Max Sum聚合函数文档

post_detail视图的render上方添加,并将similar_posts传入render

1
2
3
post_tags_ids = post.tags.values_list('id', flat=True)
similar_tags = Post.published.filter(tags__in=post_tags_ids).exclude(id=post.id)
similar_posts = similar_tags.annotate(same_tags=Count('tags')).order_by('-same_tags', '-publish')[:4]
  • 第一行 获取当前文章的所有tags:values_list 可以返回指定字段的值构成的元组,flat=True使结果变成一个列表
  • 第二行 获取拥有相关标签的文章,并去重自身
  • 第三行 完成 按数量排序 按时间排序 最后切片截取前四篇

编辑detail.html 将相似文章添加到 评论列表前

1
2
3
4
5
6
7
8
	<h2>Similar posts</h2>
    {% for post in similar_posts %}
        <p>
            <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
        </p>
    {% empty %}
        There are no similar posts yet.
    {% endfor %}

image-20221026150641539

扩展博客功能

1. 自定义模板标签和过滤器

内置模板标签和过滤器 https://docs.djangoproject.com/zh-hans/4.1/ref/templates/builtins/

1.1 自定义模板标签

django 提供两个函数来快速创建自定义模板标签:

  • simple_tag 处理数据并返回结果
  • inclusion_tag 处理数据并返回一个渲染的模板

需要在应用下创建目录 templatetags 并且创建一个 __init__.py,再创建一个 blog_tags.py

模板载入自定义标签时会使用 templatetags这个包名

返回简单结果

blog_tags.py:

1
2
3
4
5
6
7
8
9
from django import template
from ..models import Post

register = template.Library()


@register.simple_tag
def total_posts():
    return Post.published.count()

装饰器将其注册为一个简单标签,默认会用函数名作为标签名称,如果要用其他名字 @register.simple_tag(name='my_tag')

需要用 {% load blog_tags %}在模板中引入自定义的标签(py文件) 后续使用tag 直接 {% total_tags %}即可

自定义标签的强大之处在于不需要通过视图就可以处理数据或者添加数据到模板中

返回渲染模板

下面是在侧边栏显示最新发布的文章的自定义标签 ,通过inclusion_tag来渲染一段html代码

1
2
3
4
@register.inclusion_tag('blog/post/latest_posts.html')
def show_latest_posts(count=5):
    latest_posts = Post.published.order_by('-publish')[:count]
    return {'latest_posts': latest_posts}

使用方法和之前的类似,接收的参数count 直接空格接着标签后面即可 比如 {% show_latest_posts 3 %}

创建 latest_posts.html

1
2
3
4
5
6
7
8
<ul>
    {% for post in latest_posts %}
    <li>
        <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
    </li>
    {% endfor %}

</ul>

在base.html中添加:

1
2
3
4
5
6
7
<div id="sidebar">
    <h2>My blog</h2>
    <p>This is my blog.</p>
    <p>I've written {% total_posts %}</p>
    <h3>Latest posts</h3>
    {% show_latest_posts 3 %}
</div>

image-20221104145140141

返回数据作为变量

1
2
3
@register.simple_tag
def get_most_commented_posts(count=5):
    return Post.published.annotate(total_comments=Count('comments')).order_by('-total_comments')[:count]

同样加在base.html侧边栏中:

1
2
3
4
5
6
7
<h3>Most Commented Posts</h3>
{% get_most_commented_posts 3 as most_commented_posts %}
<ul>
    {% for post in most_commented_posts %}
    <li><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></li>
    {% endfor %}
</ul>

image-20221104150401138

使用了 as 将结果保存在变量中,再在后续的for循环读取返回的内容

1.2 自定义模板过滤器

模板过滤器本质是Python函数,格式是 两个参数 一个是变量, 一个是可选的变量 用 |隔开,类似 {{ variable|my_filter }} 过滤器可以连着用,比如 {{ variable|filter1|filter2|filter3 }}

这里来自定义过滤器使得文章可以支持markdown语法,并将其转为html代码

先安装Markdown模块 pipenv install Markdown

同样是在blog_tags.py中修改

1
2
3
4
5
6
from django.utils.safestring import mark_safe
import markdown

@register.filter(name='markdown')
def markdown_format(text):
    return mark_safe(markdown.markdown(text))

mark_safe 是为了告诉django这段html代码是安全的,django默认会对生成的html代码进行转义,而不会当作html代码执行以防止页面出现危险代码

创建一个简单的文章:

1
2
3
4
5
6
7
8
9
10
This is a post formatted with markdown
--------------------------------------
*This is emphasized* and **this is more emphasized**.
Here is a list:

* One
* Two
* Three

And a [link to the Django website](https://www.djangoproject.com/)

将list.html和detail.html中关于 {{post.body}}的加上 |markdown过滤器

image-20221104165055663

2. 站点地图 暂略

3. 订阅功能 暂略

4. PostgreSQL 实现全文搜索

Django的全文检索功能是基于PostgreSQL的,其他数据库可能不支持

安装PostgreSQL

下载地址:https://www.enterprisedb.com/downloads/postgres-postgresql-downloads

本文由作者按照 CC BY 4.0 进行授权

Iot Mqtt入门

Bootstrap入门