简体   繁体   中英

For the Django template {%url%} tag, how do I find all URL arguments from generic view contexts, not just the last?

In Django, I am trying to make an app with a tree hierarchy of models ie Books containing Chapters containing Sections etc. I want my URLs to mirror the structure, like books/3/chapters/5/sections .

I want to use the generic class-based views. In my HTML templates, I will use the {% url %} command to specify link targets.

For URLs deep in the structure, I need to provide {% url %} with all the keys that appear in the URL. For the above example: {% url 'pattern-name' 3 5 %}

However, the generic class-based views only provide (if that) the primary key of the object they are concerned with in the template context (ie in the above: 5).

How do I retrieve the foreign keys of parent objects (ie in the above: 3)?

I have the following:

My Models in models.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)

My URL patterns in urls.py:

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'),

My Views in 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;

In my HTML-Template used for the list of chapters, I want to have a link to create a new chapter. Currently, that template looks as follows:

Template 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>

Obviously, I don't want to add all new chapters to the book with ID 42 , rather I would like to use the book ID from the chapter list, where I am at. For example, for an URL

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

I would like to have the link point to:

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

The only way I have found of doing this dynamically, is to add extra context to the chapter list view:

Updated 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

...

And then have the template say:

Updated 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>

Here is my question:

Is there no easier (ie more Django-ish built-in) way to do this?

It seems to me, that this must be a very commonplace task, and that with the beautiful and fine-grained design of the generic view classes and URL (reverse-)lookups, I must be missing something.

I would consider defining get_absolute_url for the book_detail and chapter_detail , and extra methods for other URLs like 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])

Then in the template, you can use {{ chapter.get_absolute_url }} , {{ book.get_absolute_url }} , {{ chapter.book.get_create_chapter_url }} and so on, and you don't need to worry about what parameters are in the URLs.

In the chapter detail view, you can use the foreign key to access methods on the Book model:

{{ chapter.book.get_absolute_url }}

In the chapter list view, you need to add the book to the context (in case there are no chapters in the book):

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

Then you can access the {{ book }} in the context.

Or you can use a DetailView for the chapter list view, using the Book model:

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

Then book is already in the template context, and you can loop through the related chapters with:

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

As an aside, it might be simpler to leave out the book field in the create view. Then set it in the form_valid method before saving the form.

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)

您可以将Book作为模型查看,并且:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM