[Mobilar] 03 - Multiple Users: post & pagination

Ref: Python Django Tutorial: Full-Featured Web App Part 10 - Create, Update, and Delete Posts

Ref: Python Django Tutorial: Full-Featured Web App Part 11 - Pagination

 

 

更健壮的 View

一、之前的方案

文件 blog/urls.py

urlpatterns = [
    path('', views.home, name='blog-home'),
    path('about/', views.about, name='blog-about'),
]

文件 blog/views.py

def home(request):
    context = {
        'posts': Post.objects.all()
    }
    return render(request, 'blog/home.html', context)

 

Notice: 这里设置了 'posts',默认就是object list,但这是 原始的 方法。

 

 

二、现在的方案

文件 blog/urls.py

from django.urls import path
from .views import (
    PostListView,
    PostDetailView,
    PostCreateView,
    PostUpdateView,
    PostDeleteView
)
from . import views

urlpatterns = [
    path('', PostListView.as_view(), name='blog-home'),
    path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
    path('post/new/', PostCreateView.as_view(), name='post-create'),
    path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
    path('post/<int:pk>/delete/', PostDeleteView.as_view(), name='post-delete'),
    path('about/', views.about, name='blog-about'),
]

 

 

 

Post 的 CURD 操作

 

一、post 列表

  • URL

http://127.0.0.1:8000/

 

  • 定义 view

按照列表的方式,暂时Post的方法。注意,Post是个list。

文件 blog/views.py

class PostListView(ListView):
    model = Post
    template_name = 'blog/home.html'  # <app>/<model>_<viewtype>.html,如果不设置 template_name,就会按照这个规则。
    context_object_name = 'posts'     # 不然,默认就是object list,内部的单个元素就是 object
    ordering = ['-date_posted']

 

Notice: 与上面的“原始” 方法不同,这里的 context 用了overwrite的方式,去重写 context_object_name 变量。

 

  • 扩展学习

Ref: Django—ListView

queryset属性,用于渲染模板所需对象列表,也可以重写get_queryset方法获取
model,如果没指定queryset,则根据指定model获取对象列表
context_object_name 模板中对象列表的名称,如果不指定,则根据model获取对象列表名称:model_list

 

  • 模板

获得对象列表的名字,然后作为变量帮助渲染模板。

文件:home.html

{% extends "blog/base.html" %}
{% block content %}
    {% for post in posts %}
        <article class="media content-section">
          <img class="rounded-circle article-img" src="{{ post.author.profile.image.url }}">
          <div class="media-body">
            <div class="article-metadata">
              <a class="mr-2" href="#">{{ post.author }}</a>
              <small class="text-muted">{{ post.date_posted|date:"F d, Y" }}</small>
            </div>
            <h2><a class="article-title" href="{% url 'post-detail' post.id %}">{{ post.title }}</a></h2>  # <---- 点击 进入 url 'post-detail'
            <p class="article-content">{{ post.content }}</p>
          </div>
        </article>
    {% endfor %}
{% endblock content %}

post相当于一个组件,如下。

 

 

二、显示 post

  • URL

http://127.0.0.1:8000/post/3/

 

  • 定义 view

文件 blog/views.py

class PostDetailView(DetailView):
    model = Post

 

Notice: <app>/<model>_<viewtype>.html,如果不设置 template_name,就会按照这个规则。

继承的 DetailView,默认的viewtype就是‘detail’。

 

  • 模板

文件:post_detail.html [14:55 / 53:14]

{% extends "blog/base.html" %}
{% block content %}
  <article class="media content-section">
    <img class="rounded-circle article-img" src="{{ object.author.profile.image.url }}">
    <div class="media-body">
      <div class="article-metadata">
        <a class="mr-2" href="#">{{ object.author }}</a>
        <small class="text-muted">{{ object.date_posted|date:"F d, Y" }}</small>
        {% if object.author == user %}
          <div>
            <a class="btn btn-secondary btn-sm mt-1 mb-1" href="{% url 'post-update' object.id %}">Update</a>    # ----> 
            <a class="btn btn-danger btn-sm mt-1 mb-1" href="{% url 'post-delete' object.id %}">Delete</a>    # ----> 
          </div>
        {% endif %}
      </div>
      <h2 class="article-title">{{ object.title }}</h2>
      <p class="article-content">{{ object.content }}</p>
    </div>
  </article>
{% endblock content %}

 

 

三、新建、更新 post

  • Create

LoginRequiredMixin,未登录直接通过url使用,会自动跳转到 login界面。

文件 blog/views.py

class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ['title', 'content']

    def form_valid(self, form):
        form.instance.author = self.request.user  # <---- (1) 权限判断
        return super().form_valid(form)

 

Notice: <app>/<model>_<viewtype>.html,如果不设置 template_name,就会按照这个规则。

继承的 CreateView,默认的viewtype就是‘create’,但这里没有使用 blog/post_create.html,因为 create 与 update 会公用一个界面,所以这里默认改为是 blog/post_form.html。

 

  • Update

UserPassesTestMixin,即使登录了却想修改他人的post,则禁止该操作。

文件 blog/views.py

class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Post

    fields = ['title', 'content']

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

    def test_func(self):
        post = self.get_object()
        if self.request.user == post.author:  # <---- (2) 做了check,不能随便改变他人的post
            return True
        return False

提示:403

 

 Notice: 与 create 共用了 post_form.html。

 

  • 反解析

from django.urls import reverse


class Post(models.Model):
    title       = models.CharField(max_length=100)
    content     = models.TextField()
    date_posted = models.DateTimeField(default=timezone.now)
    author      = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return self.title
    #
    # 当新建post时,新建完毕直接返回新建的post,是需要知道其url的。
    #
    def get_absolute_url(self):
        return reverse('post-detail', kwargs={'pk': self.pk})  # <----

 

 

四、删除 post

  • 定义 view

文件 blog/views.py

class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
    model = Post
success_url
= '/'  # 删除成功后返回的 URL def test_func(self): post = self.get_object() if self.request.user == post.author:  # 如果请求的user和当前post的拥有者是同一个人,则返回 True return True return False

 

  • double check

一般是 view与template相关联,如下是怎么关联起来的呢?

文件 post_confirm_delete.html

{% extends "blog/base.html" %}
{% block content %}
    <div class="content-section">
        <form method="POST">
            {% csrf_token %}
            <fieldset class="form-group">
                <legend class="border-bottom mb-4">Delete Post</legend>
                <h2>Are you sure you want to delete the post "{{ object.title }}"</h2>
            </fieldset>
            <div class="form-group">
                <button class="btn btn-outline-danger" type="submit">Yes, Delete</button>
                <a class="btn btn-outline-secondary" href="{% url 'post-detail' object.id %}">Cancel</a>
            </div>
        </form>
    </div>
{% endblock content %}

Double check 界面:

 

 

 

Pagination

一、导入数据

同时创建较多post,采用json进行批量导入。

$ python manage.py shell
Python 3.7.5 (default, Nov  7 2019, 10:50:52) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 
>>> import json
>>> from blog.models import Post
>>> with open('posts.json') as f:
...     posts_json = json.load(f)
... 
>>> for post in posts_json:
...     post = Post(title=post['title'], content=post['content'], author_id=post['user_id'])
...     post.save()  # 内建方法 save
... 
>>> exit()

 

 

二、Paginator 类

$ python manage.py shell
Python 3.7.5 (default, Nov  7 2019, 10:50:52) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)

>>> from django.core.paginator import Paginator
>>> posts = ['a', 'b', 'c', 'd', 'e']  # <---- mock data
>>> p = Paginator(posts, 2)  # 规定每个页面只显示两个posts
>>> p.num_pages
3

>>> for page in p.page_range:
...     print(page)
... 
1
2
3

>>> p1 = p.page(1)  # 提取第一页的内容,显示相关信息,之后用于 template 展示
>>> p1
<Page 1 of 3>
>>> p1.object_list
['a', 'b']
>>> p1.has_previous() False >>> p1.has_next() True >>> p1.next_page_number() 2
>>> exit()

 

 

三、View 的页码

paginate_by 用于设置每个页面有五个 post。

from django.shortcuts import render, get_object_or_400


class PostListView(ListView):
    model = Post
    template_name = 'blog/home.html'  # <app>/<model>_<viewtype>.html
    context_object_name = 'posts'
    ordering = ['-date_posted']
    paginate_by = 5


class UserPostListView(ListView):
    model = Post
    template_name = 'blog/user_posts.html'  # <app>/<model>_<viewtype>.html
    context_object_name = 'posts'
    paginate_by = 5

    def get_queryset(self):  # 自定义 filter 列表
        user = get_object_or_404(User, username=self.kwargs.get('username'))
        return Post.objects.filter(author=user).order_by('-date_posted')

 

 

四、template 的页码 

 

实现代码如下。其中 page_obj 从哪里来?貌似是默认的。

    {% if is_paginated %}

      {% if page_obj.has_previous %}
        <a class="btn btn-outline-info mb-4" href="?page=1">First</a>
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.previous_page_number }}">Previous</a>
      {% endif %}

      {% for num in page_obj.paginator.page_range %}
        {% if page_obj.number == num %}
          <a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
        {% elif num > page_obj.number|add:'-3' and num <page_obj.number|add:'3' %}  # ----> |add 这个表达有点意思
          <a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
        {% endif %}
      {% endfor %}

      {% if page_obj.has_next %}
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.next_page_number }}">Next</a>
        <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.paginator.num_pages }}">Last</a>
      {% endif %}

    {% endif %}

 

End.

posted @ 2021-02-05 09:09  郝壹贰叁  阅读(70)  评论(0编辑  收藏  举报