在 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)
