Namespace API routes by app and flatten package layout.

Move Django apps out of apps/ and mount them under auth/, common/, content/, and cms/ so endpoint paths map directly to the owning app.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
aifeilong 2026-10-15 08:42:37 +08:00
parent 9f9c5f814f
commit 13b1ca2b9b
43 changed files with 34 additions and 28 deletions

View File

@ -18,10 +18,16 @@ Default local DB is SQLite via `config/local_settings.py`. For PostgreSQL, overr
## API ## API
- Public & staff CRUD: `/api/articles/`, `/api/organizes/`, `/api/subjects/`, `/api/organize-categories/`, `/api/magazine-categories/`, `/api/work-groups/`, `/api/work-group-terms/`, `/api/columns/`, … 路由按 app 划分前缀(网关再统一挂 `/api`):
- Auth: `POST /api/auth/login/`, `GET /api/auth/me/`
- Django admin: `/django-admin/` | 前缀 | App | 示例 |
- Search: `GET /api/search/?q=` |------|-----|------|
| `/api/auth/` | `accounts` | `POST /api/auth/login/`、`GET /api/auth/me/` |
| `/api/common/` | `common` | `POST /api/common/uploads/` |
| `/api/content/` | `content` | `/api/content/articles/`、`/api/content/columns/`、… |
| `/api/cms/` | `cms` | `/api/cms/news/`、`GET /api/cms/search/?q=` |
Django admin: `/django-admin/`
Staff write operations require `Authorization: Token <key>` and `is_staff=True`. Staff write operations require `Authorization: Token <key>` and `is_staff=True`.

View File

@ -3,6 +3,6 @@ from django.apps import AppConfig
class AccountsConfig(AppConfig): class AccountsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField" default_auto_field = "django.db.models.BigAutoField"
name = "apps.accounts" name = "accounts"
label = "accounts" label = "accounts"
verbose_name = "账号" verbose_name = "账号"

View File

@ -2,7 +2,7 @@ from decimal import Decimal
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from apps.cms.models import ( from cms.models import (
AboutInfo, AboutInfo,
ContactUs, ContactUs,
CopyrightInfo, CopyrightInfo,
@ -12,8 +12,8 @@ from apps.cms.models import (
News, News,
Publication, Publication,
) )
from apps.accounts.models import Member from accounts.models import Member
from apps.content.models import ( from content.models import (
Article, Article,
ArticleComment, ArticleComment,
Column, Column,
@ -478,7 +478,7 @@ class Command(BaseCommand):
return created, updated return created, updated
def _ensure_demo_comments(self): def _ensure_demo_comments(self):
from apps.accounts.serializers import MemberSerializer from accounts.serializers import MemberSerializer
# 国际格式;同时兼容旧的 11 位国内号演示数据 # 国际格式;同时兼容旧的 11 位国内号演示数据
member = ( member = (

View File

@ -2,7 +2,7 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from apps.content.models import Column, NavPosition from content.models import Column, NavPosition
# 与 ui/src/config/nav.js 对齐 # 与 ui/src/config/nav.js 对齐

View File

@ -1,7 +1,7 @@
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from apps.common.models import SoftDeleteModel from common.models import SoftDeleteModel
class Member(SoftDeleteModel): class Member(SoftDeleteModel):

View File

@ -3,6 +3,6 @@ from django.apps import AppConfig
class CmsConfig(AppConfig): class CmsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField" default_auto_field = "django.db.models.BigAutoField"
name = "apps.cms" name = "cms"
label = "cms" label = "cms"
verbose_name = "站点内容" verbose_name = "站点内容"

View File

@ -1,6 +1,6 @@
from django.db import models from django.db import models
from apps.common.models import SoftDeleteModel from common.models import SoftDeleteModel
class News(SoftDeleteModel): class News(SoftDeleteModel):

View File

@ -9,13 +9,13 @@ from rest_framework.decorators import action
from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.response import Response from rest_framework.response import Response
from apps.content.models import Article, Organize, Professor from content.models import Article, Organize, Professor
from apps.content.serializers import ( from content.serializers import (
ArticleListSerializer, ArticleListSerializer,
OrganizeListSerializer, OrganizeListSerializer,
ProfessorSerializer, ProfessorSerializer,
) )
from apps.content.views import IsAdminOrReadOnly, PublicMixin from content.views import IsAdminOrReadOnly, PublicMixin
ALLOWED_IMAGE_EXT = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"} ALLOWED_IMAGE_EXT = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"}
MAX_UPLOAD_BYTES = 8 * 1024 * 1024 MAX_UPLOAD_BYTES = 8 * 1024 * 1024

View File

@ -3,6 +3,6 @@ from django.apps import AppConfig
class CommonConfig(AppConfig): class CommonConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField" default_auto_field = "django.db.models.BigAutoField"
name = "apps.common" name = "common"
label = "common" label = "common"
verbose_name = "公共" verbose_name = "公共"

View File

@ -32,7 +32,7 @@ CATEGORY_RE = re.compile(r"^[a-zA-Z0-9_-]{1,32}$")
class MediaUploadView(APIView): class MediaUploadView(APIView):
"""POST /api/uploads/ 表单字段: file, category(可选), kind=image|file(可选)""" """POST /uploads/ 表单字段: file, category(可选), kind=image|file(可选)"""
permission_classes = [permissions.IsAdminUser] permission_classes = [permissions.IsAdminUser]
parser_classes = [MultiPartParser, FormParser] parser_classes = [MultiPartParser, FormParser]

View File

@ -45,10 +45,10 @@ INSTALLED_APPS = [
"django_extensions", "django_extensions",
"simple_history", "simple_history",
"corsheaders", "corsheaders",
"apps.common", "common",
"apps.content", "content",
"apps.cms", "cms",
"apps.accounts", "accounts",
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -5,10 +5,10 @@ from django.urls import include, path
urlpatterns = [ urlpatterns = [
path("django-admin/", admin.site.urls), path("django-admin/", admin.site.urls),
path("api/auth/", include("apps.accounts.urls")), path("auth/", include("accounts.urls")),
path("api/", include("apps.common.urls")), path("common/", include("common.urls")),
path("api/", include("apps.content.urls")), path("content/", include("content.urls")),
path("api/", include("apps.cms.urls")), path("cms/", include("cms.urls")),
] ]
if settings.DEBUG: if settings.DEBUG:

View File

@ -3,6 +3,6 @@ from django.apps import AppConfig
class ContentConfig(AppConfig): class ContentConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField" default_auto_field = "django.db.models.BigAutoField"
name = "apps.content" name = "content"
label = "content" label = "content"
verbose_name = "内容" verbose_name = "内容"

View File

@ -1,6 +1,6 @@
from django.db import models from django.db import models
from apps.common.models import SoftDeleteModel, TimeStampedModel from common.models import SoftDeleteModel, TimeStampedModel
class NavPosition(models.IntegerChoices): class NavPosition(models.IntegerChoices):