Skip to content

Commit 5f99bcc

Browse files
authored
Merge pull request #6 from pythonkr/feature/cms-admin
feat: cms model Page, Sitemap, Section에 대한 admin 코드 추가
2 parents 8724127 + b5d8609 commit 5f99bcc

6 files changed

Lines changed: 120 additions & 5 deletions

File tree

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ local-makemigrations:
6464
local-migrate:
6565
@ENV_PATH=envfile/.env.local uv run python app/manage.py migrate
6666

67+
# Create admin superuser
68+
local-createsuperuser:
69+
@ENV_PATH=envfile/.env.local uv run python app/manage.py createsuperuser
70+
6771
# Devtools
6872
hooks-install: local-setup
6973
uv run pre-commit install

app/cms/admin.py

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from cms.admin_mixins import RelatedReadonlyFieldsMixin
12
from cms.models import Page, Section, Sitemap
23
from django import forms
34
from django.contrib import admin
@@ -149,10 +150,70 @@ class Meta:
149150
widgets = {"body": CodeEditorWidget()}
150151

151152

153+
@admin.register(Sitemap)
154+
class SitemapAdmin(RelatedReadonlyFieldsMixin, admin.ModelAdmin):
155+
fields = ["id", "parent_sitemap", "page", "name", "order", "display_start_at", "display_end_at"]
156+
readonly_fields = ["id"]
157+
related_readonly_config = {
158+
"page": ["id", "is_active", "css", "title", "subtitle"],
159+
"parent_sitemap": ["id", "name", "order", "display_start_at", "display_end_at"],
160+
}
161+
162+
def get_fieldsets(self, request, obj=...):
163+
original_fieldsets = super().get_fieldsets(request, obj)
164+
if obj and obj.parent_sitemap:
165+
original_fieldsets.append(
166+
(
167+
"Parent Sitemap 정보",
168+
{
169+
"fields": [f"get_parent_sitemap_{f}" for f in self.related_readonly_config["parent_sitemap"]],
170+
"classes": ("collapse",),
171+
},
172+
)
173+
)
174+
if obj and obj.page:
175+
original_fieldsets.append(
176+
(
177+
"Page 정보",
178+
{
179+
"fields": [f"get_page_{f}" for f in self.related_readonly_config["page"]],
180+
"classes": ("collapse",),
181+
},
182+
)
183+
)
184+
return original_fieldsets
185+
186+
def get_queryset(self, request):
187+
return super().get_queryset(request).select_related("page").select_related("parent_sitemap")
188+
189+
190+
class PageAdmin(admin.ModelAdmin):
191+
pass
192+
193+
152194
@admin.register(Section)
153-
class SectionAdmin(admin.ModelAdmin):
195+
class SectionAdmin(RelatedReadonlyFieldsMixin, admin.ModelAdmin):
154196
form = SectionAdminForm
197+
fields = ["id", "page", "order", "css", "body"]
198+
readonly_fields = ["id"]
199+
related_readonly_config = {"page": ["id", "is_active", "css", "title", "subtitle"]}
200+
201+
def get_fieldsets(self, request, obj=...):
202+
original_fieldsets = super().get_fieldsets(request, obj)
203+
if obj and obj.page:
204+
original_fieldsets.append(
205+
(
206+
"Page 정보",
207+
{
208+
"fields": [f"get_page_{f}" for f in self.related_readonly_config["page"]],
209+
"classes": ("collapse",),
210+
},
211+
)
212+
)
213+
return original_fieldsets
214+
215+
def get_queryset(self, request):
216+
return super().get_queryset(request).select_related("page")
155217

156218

157219
admin.site.register(Page)
158-
admin.site.register(Sitemap)

app/cms/admin_mixins.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class RelatedReadonlyFieldsMixin:
2+
related_readonly_config = {}
3+
4+
def _generate_related_getter(self, rel, field, prefix=""):
5+
def _func(admin_self, obj):
6+
related = getattr(obj, rel)
7+
return getattr(related, field) if related else None
8+
9+
_func.short_description = f"{prefix} {field.replace('_', ' ')}"
10+
return _func
11+
12+
def _register_dynamic_readonly_fields(self):
13+
for rel, fields in self.related_readonly_config.items():
14+
for field in fields:
15+
method_name = f"get_{rel}_{field}"
16+
getter = self._generate_related_getter(rel, field, prefix=rel.capitalize())
17+
setattr(self.__class__, method_name, getter)
18+
19+
def __init__(self, *args, **kwargs):
20+
super().__init__(*args, **kwargs)
21+
self._register_dynamic_readonly_fields()
22+
23+
def get_readonly_fields(self, request, obj=None):
24+
base = super().get_readonly_fields(request, obj)
25+
generated = [f"get_{rel}_{field}" for rel, fields in self.related_readonly_config.items() for field in fields]
26+
return list(base) + generated
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 5.2 on 2025-05-07 11:52
2+
3+
import django.core.validators
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
dependencies = [
9+
("cms", "0002_historicalpage_historicalsection_historicalsitemap"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="historicalsitemap",
15+
name="order",
16+
field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)]),
17+
),
18+
migrations.AlterField(
19+
model_name="sitemap",
20+
name="order",
21+
field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)]),
22+
),
23+
]

app/cms/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import uuid
22

3+
from django.core.validators import MinValueValidator
34
from django.db import models
45

56

@@ -21,7 +22,7 @@ class Sitemap(models.Model):
2122
"self", null=True, blank=True, default=None, on_delete=models.SET_NULL, related_name="children"
2223
)
2324
name = models.CharField(max_length=256)
24-
order = models.IntegerField(default=0)
25+
order = models.IntegerField(default=0, validators=[MinValueValidator(0)])
2526
page = models.ForeignKey(Page, on_delete=models.PROTECT)
2627
display_start_at = models.DateTimeField(null=True, blank=True)
2728
display_end_at = models.DateTimeField(null=True, blank=True)

app/core/settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,13 +308,13 @@
308308
SESSION_COOKIE_SAMESITE = COOKIE_SAMESITE
309309
SESSION_COOKIE_SECURE = COOKIE_SECURE
310310
SESSION_COOKIE_HTTPONLY = COOKIE_HTTPONLY
311-
SESSION_COOKIE_DOMAIN = COOKIE_DOMAIN
311+
SESSION_COOKIE_DOMAIN = None if IS_LOCAL else COOKIE_DOMAIN
312312

313313
CSRF_COOKIE_NAME = f"{COOKIE_PREFIX}csrftoken"
314314
CSRF_COOKIE_SAMESITE = COOKIE_SAMESITE
315315
CSRF_COOKIE_SECURE = COOKIE_SECURE
316316
CSRF_COOKIE_HTTPONLY = COOKIE_HTTPONLY
317-
CSRF_COOKIE_DOMAIN = COOKIE_DOMAIN
317+
CSRF_COOKIE_DOMAIN = None if IS_LOCAL else COOKIE_DOMAIN
318318
CSRF_TRUSTED_ORIGINS = set(env.list("CSRF_TRUSTED_ORIGINS", default=["https://pycon.kr"])) | {
319319
"https://local.dev.pycon.kr:3000",
320320
"https://localhost:3000",

0 commit comments

Comments
 (0)