Skip to content

Commit f4c3a2e

Browse files
committed
feat: created_by / updated_by 추가
1 parent 61ad824 commit f4c3a2e

8 files changed

Lines changed: 66 additions & 15 deletions

File tree

app/admin_api/test/shop/categories_api_test.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@ def test_category_json_schema_exposes_choice_meta_schema(api_client, category_fi
7474
assert "choiceMetaSchema" not in ui_schema.get("group", {}).get("ui:options", {})
7575

7676

77+
@pytest.mark.django_db
78+
def test_category_choices_include_audit_meta_for_event(api_client, category_fixtures):
79+
# BaseAbstractModel 의 audit 메타(created_by/updated_by/created_at/updated_at)가 자동으로 붙어야 한다.
80+
response = api_client.get(CHOICES_URL)
81+
assert response.status_code == HTTP_200_OK
82+
event_choices = {c["const"]: c for c in response.json()["event"] if c["const"] is not None}
83+
event_meta = event_choices[str(category_fixtures["event"].id)]["meta"]
84+
assert {"created_by", "updated_by", "created_at", "updated_at"} <= set(event_meta)
85+
assert event_meta["created_at"] is not None
86+
87+
88+
@pytest.mark.django_db
89+
def test_category_json_schema_exposes_audit_meta_schema(api_client, category_fixtures):
90+
response = api_client.get(JSON_SCHEMA_URL)
91+
assert response.status_code == HTTP_200_OK
92+
meta_schema = response.json()["ui_schema"]["event"]["ui:options"]["choiceMetaSchema"]
93+
assert meta_schema["created_by"]["label"] == "생성자"
94+
assert {"created_by", "updated_by", "created_at", "updated_at"} <= set(meta_schema)
95+
96+
7797
@pytest.mark.django_db
7898
def test_category_filter_by_group(api_client, category_fixtures):
7999
response = api_client.get(LIST_URL, {"group": str(category_fixtures["g2026"].id)})

app/admin_api/test/shop/products_api_test.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -458,11 +458,11 @@ def test_admin_product_choices_include_category_meta(api_client, ticket_product)
458458

459459
# event 가 있는 카테고리 — str(event) 분기.
460460
evented_meta = category_choices[str(evented.id)]["meta"]
461-
assert evented_meta == {"group": "2026", "is_ticket": True, "event": str(event)}
461+
assert evented_meta.items() >= {"group": "2026", "is_ticket": True, "event": str(event)}.items()
462462

463463
# event 가 없는 카테고리(fixture) — None 분기.
464464
plain_meta = category_choices[str(ticket_product.category.id)]["meta"]
465-
assert plain_meta == {"group": "기본", "is_ticket": True, "event": None}
465+
assert plain_meta.items() >= {"group": "기본", "is_ticket": True, "event": None}.items()
466466

467467

468468
@pytest.mark.django_db
@@ -471,9 +471,12 @@ def test_admin_option_group_choices_include_product_meta(api_client, ticket_prod
471471
response = api_client.get(OPTION_GROUP_CHOICES_URL)
472472
assert response.status_code == HTTP_200_OK
473473
product_choices = {c["const"]: c for c in response.json()["product"]}
474-
assert product_choices[str(ticket_product.id)]["meta"] == {
475-
"category": str(ticket_product.category),
476-
"price": ticket_product.price,
477-
"stock": ticket_product.stock,
478-
"status": Product.CurrentStatus.ACTIVE.label,
479-
}
474+
assert (
475+
product_choices[str(ticket_product.id)]["meta"].items()
476+
>= {
477+
"category": str(ticket_product.category),
478+
"price": ticket_product.price,
479+
"stock": ticket_product.stock,
480+
"status": Product.CurrentStatus.ACTIVE.label,
481+
}.items()
482+
)

app/core/models.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@
66
from django.contrib.auth import get_user_model
77
from django.db import models
88
from django.db.models.functions import Now
9+
from django.utils import timezone
910

1011
if typing.TYPE_CHECKING:
1112
from user.models import UserExt # noqa: F401
1213

1314
User = get_user_model()
1415

16+
AUDIT_CHOICE_META_SCHEMA: dict[str, dict[str, str]] = {
17+
"created_by": {"label": "생성자", "type": "string", "filter": "select"},
18+
"updated_by": {"label": "수정자", "type": "string", "filter": "select"},
19+
"created_at": {"label": "생성일시", "type": "string", "filter": "search"},
20+
"updated_at": {"label": "수정일시", "type": "string", "filter": "search"},
21+
}
22+
1523

1624
class BaseAbstractModelQuerySet(models.QuerySet):
1725
def create(self, **kwargs: dict) -> models.Model:
@@ -37,7 +45,9 @@ def select_related_with_user(self, *fields) -> typing.Self:
3745
return self.select_related(*_fields)
3846

3947
def get_choices_queryset(self) -> typing.Self:
40-
fields = self.model.choices_select_related
48+
fields = set(self.model.choices_select_related)
49+
if self.model.choices_meta_schema:
50+
fields |= {"created_by", "updated_by"} # audit 메타 출력을 위한 join
4151
return self.select_related(*fields) if fields else self.all()
4252

4353

@@ -66,6 +76,22 @@ class BaseAbstractModel(models.Model):
6676
class Meta:
6777
abstract = True
6878

79+
def get_choice_meta(self) -> dict:
80+
if not self.choices_meta_schema:
81+
return {}
82+
return self._choice_meta_fields() | self.get_audit_choice_meta()
83+
84+
def _choice_meta_fields(self) -> dict:
85+
return {}
86+
87+
def get_audit_choice_meta(self) -> dict:
88+
return {
89+
"created_by": self.created_by_id and str(self.created_by),
90+
"updated_by": self.updated_by_id and str(self.updated_by),
91+
"created_at": self.created_at and timezone.localtime(self.created_at).isoformat(),
92+
"updated_at": self.updated_at and timezone.localtime(self.updated_at).isoformat(),
93+
}
94+
6995
def save( # type: ignore[override]
7096
self,
7197
*,

app/core/openapi/ui_hints.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from core.models import MarkdownField
1+
from core.models import AUDIT_CHOICE_META_SCHEMA, MarkdownField
22
from django.db.models.fields import TextField
33
from django.db.models.fields.files import FileField
44
from django.db.models.fields.related import ForeignKey, ManyToManyField
@@ -10,6 +10,8 @@ def ui_hints_for_model_field(model_field: object) -> dict:
1010

1111
if isinstance(model_field, (ForeignKey, ManyToManyField)):
1212
if meta_schema := getattr(model_field.related_model, "choices_meta_schema", None):
13+
if hasattr(model_field.related_model, "get_audit_choice_meta"):
14+
meta_schema = meta_schema | AUDIT_CHOICE_META_SCHEMA
1315
hints["ui:options"] = {"choiceMetaSchema": meta_schema}
1416

1517
if isinstance(model_field, ManyToManyField):

app/event/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class Meta:
3737
def __str__(self):
3838
return f"{self.name} by {self.organization}"
3939

40-
def get_choice_meta(self) -> dict:
40+
def _choice_meta_fields(self) -> dict:
4141
return {
4242
"organization": str(self.organization),
4343
"started_at": timezone.localtime(self.event_start_at).date().isoformat() if self.event_start_at else None,

app/event/presentation/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class Presentation(BaseAbstractModel):
108108
def __str__(self) -> str:
109109
return f"[{self.type.name}] {self.title}"
110110

111-
def get_choice_meta(self) -> dict:
111+
def _choice_meta_fields(self) -> dict:
112112
return {
113113
"type": self.type.name,
114114
"event": self.type.event.name,

app/file/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class Meta:
2626
def __str__(self) -> str:
2727
return self.file.name
2828

29-
def get_choice_meta(self) -> dict:
29+
def _choice_meta_fields(self) -> dict:
3030
return {
3131
"preview": self.file.url if self.file else None,
3232
"mimetype": self.mimetype,

app/shop/product/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class Meta:
5656
def __str__(self) -> str: # pragma: no cover
5757
return f"{self.group.name} > {self.name}"
5858

59-
def get_choice_meta(self) -> dict:
59+
def _choice_meta_fields(self) -> dict:
6060
return {
6161
"group": self.group.name,
6262
"is_ticket": self.is_ticket,
@@ -191,7 +191,7 @@ class Meta:
191191
def __str__(self) -> str: # pragma: no cover
192192
return f"{self.category} > {self.name} ({self.price}원)"
193193

194-
def get_choice_meta(self) -> dict:
194+
def _choice_meta_fields(self) -> dict:
195195
return {
196196
"category": str(self.category),
197197
"price": self.price,

0 commit comments

Comments
 (0)