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:
parent
9f9c5f814f
commit
13b1ca2b9b
14
README.md
14
README.md
@ -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`.
|
||||||
|
|
||||||
|
|||||||
@ -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 = "账号"
|
||||||
@ -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 = (
|
||||||
@ -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 对齐
|
||||||
@ -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):
|
||||||
@ -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 = "站点内容"
|
||||||
@ -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):
|
||||||
@ -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
|
||||||
@ -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 = "公共"
|
||||||
@ -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]
|
||||||
@ -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 = [
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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 = "内容"
|
||||||
@ -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):
|
||||||
Loading…
x
Reference in New Issue
Block a user