使用 Django,我是否应该在我的 REST API 和 Web 应用程序中实现两种不同的 url 映射逻辑,以及如何实现?

Using Django, should I implement two different url mapping logics in my REST API and web application, and how?

我有一个 Book 模型实例,其中 Chapter 模型的几个实例可以通过外键链接:

class Book(models.Model):
    pass

class Chapter(models.Model):
    book = models.ForeignKey(to=Book, ...)
    
    class Meta:
        order_with_respect_to = "book"

我决定将 Django 用于 RESTful API,使用 Django Rest Framework 和 Web 应用程序,使用 Django Template。我希望它们分开,因为应该为另一个潜在的应用程序打开方式来使用 API.

出于包括管理目的在内的多种原因,API 需要这种 url 映射逻辑:

mylibrary.org/api/books/
mylibrary.org/api/books/<book_id>/
mylibrary.org/api/chapters/
mylibrary.org/api/chapters/<chapter_id>/

然而,对于我的 Web 应用程序,我希望用户通过此 url 映射逻辑访问书籍及其内容:

mylibrary.org/books/
mylibrary.org/books/<book_id>-esthetic-slug/
mylibrary.org/books/<book_id>-esthetic-slug/chapter_<chapter_order>/

这个想法是路由器从 获取书籍,无论是什么 slug 和章节,根据它的顺序而不是它的 ID。

现在我需要一些建议,如果这是可取的,或者我一定会遇到障碍。 例如,Web 应用程序的 / 应如何以及在何处“翻译”为 API 的 ?或者,如果我希望网络应用程序的图书列表提供自动生成的图书链接,应该在 API 还是网络应用程序级别完成?

我对 Django/DRF 和一般的 Web 开发还很陌生。

双URL完全不是问题。每当程序呈现视图并具有 API(可能用于移动应用程序)时都会使用它。我通常做的是让 id 字段也位于 'web' 页面的 URL 中,这对我来说更容易跟踪。但是,如果你想在 URL 中添加一个 'nice' slug,试试这个:

"""
models.py
"""

import uuid

from django.db import models


class Book(models.Model):
    id = models.UUIDField(primary_key=True, null=False, editable=False, default=uuid.uuid4)
    slug = models.SlugField(max_length=50)

class Chapter(models.Model):
    id = models.UUIDField(primary_key=True, null=False, editable=False, default=uuid.uuid4)
    book = models.ForeignKey(to=Book, ...)
    sort = models.PositiveIntegerField()
    
    class Meta:
        order_with_respect_to = "book"
        unique_together = [("book", "sort"), ]  # so that there is never two chapters with the same number
"""
urls.py
"""

from django.urls import path

from . import apiviews, views

urlpatterns = [
    # 'web' URLs
    path("books/", views.books, "book_list"),  # mylibrary.org/books/
    path("books/<str:book_slug>", views.book, "book"),  # mylibrary.org/books/harry-potter-and-the-prisoner-of-azkaban
    path("books/<str:book_slug>/chapter_<int:chapter>", views.chapter, "chapter"),  # mylibrary.org/books/harry-potter-and-the-prisoner-of-azkaban/chapter_2
    # API URLs
    path("api/books/", apiviews.books, "api_book_list"),  # mylibrary.org/api/books/
    path("api/books/<uuid:id>", apiviews.book, "api_book"),  # mylibrary.org/api/books/b21a0af6-4e6c-4c67-b44d-50f8b52cd459
    path("api/chapters/", apiviews.chapters, "api_chapters"),  # mylibrary.org/api/chapters/
    path("api/chapters/<uuid:id>", apiviews.chapter, "api_chapter"),  # mylibrary.org/api/chapters/c76a89ae-ad73-4752-991c-8a82e47d3307
]
"""
views.py
"""

from .models import Book, Chapter


def books(request):
    book_list = Book.objects.all()


def book(request, book_slug)
    try:
        book = Book.objects.get(slug=book_slug)
    except Book.DoesNotExist:
        pass


def chapter(request, book_slug, chapter)
    try:
        book = Book.objects.get(slug=book_slug)
        chapter = book.chapter_set.get(sort=chapter)
    except Book.DoesNotExist:
        pass
    except Chapter.DoesNotExist:
        pass
"""
apiviews.py
"""

from .models import Book, Chapter


def books(request):
    book_list = Book.objects.all()


def book(request, id)
    try:
        book = Book.objects.get(id=id)
    except Book.DoesNotExist:
        pass


def chapters(request):
    chapter_list = Chapter.objects.all()


def chapter(request, id)
    try:
        chapter = Chapter.objects.get(id=id)
    except Chapter.DoesNotExist:
        pass

这些文件反映了您建议 URL 的方式。我个人(我不认为这是正确的方法)会像这样设置 API 视图:

mylibrary.org/books/
mylibrary.org/books/b21a0af6-4e6c-4c67-b44d-50f8b52cd459
mylibrary.org/books/b21a0af6-4e6c-4c67-b44d-50f8b52cd459/chapters/
mylibrary.org/books/b21a0af6-4e6c-4c67-b44d-50f8b52cd459/chapters/3
# and then for example:
mylibrary.org/books/b21a0af6-4e6c-4c67-b44d-50f8b52cd459/comments/
mylibrary.org/books/b21a0af6-4e6c-4c67-b44d-50f8b52cd459/comments/295

我觉得这个结构更全面,但我看不出你的版本有任何优点或缺点。两者都工作得很好,并且它们背后都有一个很好的推理。一个是 "API 只是一种访问数据库对象的方式,所以如果我知道 ID",我应该能够提取任意章节,另一个是 “我有书,书有章节,所以章节是书的子项”