Skip to main content
 首页 » 编程设计

python之对于 Django 模板 {%url%} 标记,如何从通用 View 上下文中找到所有 URL 参数,而不仅仅是最后一个

2024年09月07日27dudu

在 Django 中,我正在尝试制作一个具有模型树层次结构的应用程序,即 Books 包含 Chapter 包含 Sections 等。我想要我的 URL 来反射(reflect)结构,例如 books/3/chapters/5/sections。

我想使用通用的基于类的 View 。在我的 HTML 模板中,我将使用 {% url %} 命令来指定链接目标。

对于结构深层的 URL,我需要为 {% url %} 提供出现在 URL 中的所有键。对于上面的示例:{% url 'pattern-name' 3 5 %}

但是,基于类的通用 View 仅提供(如果有的话)它们在模板上下文中关注的对象的主键(即上面的:5)。

如何检索父对象的外键(即上面的 3)?

我有以下内容:

我的模型 模型.py:

class Book(models.Model): 
  name = models.CharField("Book Name", max_length = 80) 
 
class Chapter(models.Model): 
  name = models.CharField("Book Name", max_length = 80) 
  book = models.ForeignKey('Book', on_delete = models.CASCADE) 

我在 urls.py 中的 URL 模式:

urlpatterns = [ 
  path('books/',  
       views.BookListView.as_view(), name='book_list'), 
  path('books/<int:book>/',  
       views.BookDetailView.as_view(), name='book_detail'), 
  path('books/<int:book>/chapters/',  
       views.ChapterListView.as_view(), name='chapter_list'), 
  path('books/<int:book>/chapters/<int:chapter>/', 
       views.ChapterDetailView.as_view(), name='chapter_detail'), 
  path('books/<int:book>/chapters/create/', 
       views.ChapterCreateView.as_view(), name='chapter_create'), 

我在 views.py 中的观点:

class BookListView(generic.ListView): 
  model = 'Book' 
 
class BookDetailView(generic.DetailView): 
  model = 'Book' 
  pk_url_kwarg = 'book' 
 
class ChapterListView(generic.ListView): 
  model = 'Chapter' 
  def get_queryset(self): 
    return Chapter.objects.filter(book = self.kwargs['book']) 
 
class ChapterDetailView(generic.DetailView): 
  model = 'Chapter' 
  pk_url_kwarg = 'chapter' 
 
class ChapterCreateView(generic.CreateView): 
  model = 'Chapter' 
  fields = ['name', 'book'] 
  def get_initial(self): 
    initial = super().get_initial().copy() 
    initial['book'] = self.kwargs.['book'] 
    return initial; 

在我用于章节列表的 HTML 模板中,我想要一个创建新章节的链接。目前,该模板如下所示:

模板chapter_list.html:

<ul> 
{% for chapter in chapter_list %} 
  <li>{{ chapter.name }}</li> 
{% endfor %} 
</ul> 
<a href="{% url 'chapter_create' 42 %}">Add new chapter</a> 

显然,我不想将所有新章节添加到 ID 为 42 的书中,而是我想使用我所在章节列表中的图书 ID。例如,对于一个 URL

.../books/17/chapters/create/ 

我希望链接指向:

<a href="{% url 'chapter_create' 17 %}">Add new chapter</a> 

我发现动态执行此操作的唯一方法是向章节 ListView 添加额外的上下文:

更新的views.py:

... 
 
class ChapterListView(generic.ListView): 
  model = 'Chapter' 
  def get_queryset(self): 
    return Chapter.objects.filter(book = self.kwargs['book']) 
 
  # Additional Context 
  def get_context_data(self, **kwargs): 
    context = super().get_context_data(**kwargs) 
    context['my_current_book_id'] = self.kwargs['book'] 
    return context 
 
... 

然后让模板说:

更新chapter_list.html:

<ul> 
{% for chapter in chapter_list %} 
  <li>{{ chapter.name }}</li> 
{% endfor %} 
</ul> 
<a href="{% url 'chapter_create' my_current_book_id %}">Add new chapter</a> 

这是我的问题:

有没有更简单的(即更像 Django 内置的)方法来做到这一点?

在我看来,这一定是一项非常普通的任务,而且由于通用 View 类和 URL(反向)查找的精美而细粒度的设计,我一定遗漏了一些东西。

请您参考如下方法:

我会考虑为 book_detail 和 chapter_detail 定义 get_absolute_url,并为其他 URL 定义额外的方法,例如 chapter_create :

class Book(models.Model): 
    name = models.CharField("Book Name", max_length = 80) 
 
    def get_absolute_url(self): 
        return reverse('book_detail', args=[self.pk]) 
 
    def get_create_chapter_url(self): 
        return reverse('chapter_create', args=[self.pk]) 
 
 
class Chapter(models.Model): 
    name = models.CharField("Book Name", max_length = 80) 
    book = models.ForeignKey('Book', on_delete = models.CASCADE) 
 
    def get_absolute_url(self): 
        return reverse('chapter_detail', args=[self.book_pk, self.pk]) 

然后在模板中,可以使用{{ chapter.get_absolute_url }}, {{ book.get_absolute_url }}, {{ chapter.book. get_create_chapter_url }} 等等,您无需担心 URL 中有哪些参数。

在章节详细 View 中,您可以使用外键访问Book 模型上的方法:

{{ chapter.book.get_absolute_url }} 

在章节 ListView 中,需要将书籍添加到上下文中(以防书中没有章节):

from django.shortcuts import get_object_or_404 
 
class ChapterListView(generic.ListView): 
    model = 'Chapter' 
 
    def get_queryset(self): 
        return Chapter.objects.filter(book = self.kwargs['book']) 
 
  def get_context_data(self, **kwargs): 
      context = super().get_context_data(**kwargs) 
      context['book'] = get_object_or_404(pk=self.kwargs['book']) 
      return context 

然后就可以在上下文中访问{{ book }}了。

或者您可以使用 DetailView 作为章节 ListView ,使用 Book 模型:

class ChapterListView(generic.DetailView): 
    model = 'Book' 
    pk_url_kwarg = 'book' 

那么 book 已经在模板上下文中了,你可以循环遍历相关章节:

{% for chapter in book.chapter_set.all %} 
  {{ chapter }} 
{% endfor %} 

顺便说一句,在创建 View 中省略 book 字段可能更简单。然后在保存表单前在form_valid方法中设置。

class ChapterCreateView(generic.CreateView): 
    model = 'Chapter' 
    fields = ['name'] 
 
    def form_valid(self, form): 
        book = get_object_or_404(Book, pk=self.kwargs['book']) 
        form.instance.book = book 
        return super(ChapterCreateView, self).form_valid(form)