[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
-
模板
获得对象列表的名字,然后作为变量帮助渲染模板。
文件: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.