From 15f481430737eee78ad3d75628b82517aefed164 Mon Sep 17 00:00:00 2001 From: earthyoung Date: Thu, 24 Apr 2025 20:46:43 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20cms=20Page=20&=20Sitemap=20list,=20?= =?UTF-8?q?retrieve=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- app/cms/apps.py | 12 ++ ...age_historicalsection_historicalsitemap.py | 191 ++++++++++++++++++ app/cms/models.py | 7 +- app/cms/serializers.py | 14 ++ app/cms/test/conftest.py | 23 +++ app/cms/test/page_api_test.py | 20 ++ app/cms/test/sitemap_api_test.py | 21 ++ app/cms/urls.py | 24 +++ app/cms/views.py | 13 ++ app/core/urls.py | 1 + pyproject.toml | 5 + uv.lock | 49 ++++- 13 files changed, 371 insertions(+), 11 deletions(-) create mode 100644 app/cms/migrations/0002_historicalpage_historicalsection_historicalsitemap.py create mode 100644 app/cms/serializers.py create mode 100644 app/cms/test/conftest.py create mode 100644 app/cms/test/page_api_test.py create mode 100644 app/cms/test/sitemap_api_test.py create mode 100644 app/cms/urls.py create mode 100644 app/cms/views.py diff --git a/.gitignore b/.gitignore index 00e4d1b..5daadca 100644 --- a/.gitignore +++ b/.gitignore @@ -99,7 +99,7 @@ ipython_config.py # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock +poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. diff --git a/app/cms/apps.py b/app/cms/apps.py index 6e90a36..0dc102b 100644 --- a/app/cms/apps.py +++ b/app/cms/apps.py @@ -1,5 +1,17 @@ +import importlib + from django.apps import AppConfig class CmsConfig(AppConfig): name = "cms" + + def ready(self): + importlib.import_module("cms.translation") + + from cms.models import Page, Section, Sitemap + from simple_history import register + + register(Page) + register(Sitemap) + register(Section) diff --git a/app/cms/migrations/0002_historicalpage_historicalsection_historicalsitemap.py b/app/cms/migrations/0002_historicalpage_historicalsection_historicalsitemap.py new file mode 100644 index 0000000..d60549e --- /dev/null +++ b/app/cms/migrations/0002_historicalpage_historicalsection_historicalsitemap.py @@ -0,0 +1,191 @@ +# Generated by Django 5.2 on 2025-04-21 13:38 + +import uuid + +import django.db.models.deletion +import simple_history.models +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("cms", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="HistoricalPage", + fields=[ + ( + "id", + models.UUIDField(db_index=True, default=uuid.uuid4, editable=False), + ), + ("is_active", models.BooleanField(default=False)), + ("css", models.TextField(blank=True, default=None, null=True)), + ("title", models.CharField(max_length=256)), + ("title_ko", models.CharField(max_length=256, null=True)), + ("title_en", models.CharField(max_length=256, null=True)), + ("subtitle", models.CharField(max_length=512)), + ("subtitle_ko", models.CharField(max_length=512, null=True)), + ("subtitle_en", models.CharField(max_length=512, null=True)), + ("history_id", models.AutoField(primary_key=True, serialize=False)), + ("history_date", models.DateTimeField(db_index=True)), + ("history_change_reason", models.CharField(max_length=100, null=True)), + ( + "history_type", + models.CharField( + choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], + max_length=1, + ), + ), + ( + "history_user", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "historical page", + "verbose_name_plural": "historical pages", + "ordering": ("-history_date", "-history_id"), + "get_latest_by": ("history_date", "history_id"), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name="HistoricalSection", + fields=[ + ( + "id", + models.UUIDField(db_index=True, default=uuid.uuid4, editable=False), + ), + ("order", models.IntegerField(default=0)), + ("css", models.TextField(blank=True, default=None, null=True)), + ( + "body", + models.TextField(help_text="Content of the page, Written in markdown format"), + ), + ( + "body_ko", + models.TextField( + help_text="Content of the page, Written in markdown format", + null=True, + ), + ), + ( + "body_en", + models.TextField( + help_text="Content of the page, Written in markdown format", + null=True, + ), + ), + ("history_id", models.AutoField(primary_key=True, serialize=False)), + ("history_date", models.DateTimeField(db_index=True)), + ("history_change_reason", models.CharField(max_length=100, null=True)), + ( + "history_type", + models.CharField( + choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], + max_length=1, + ), + ), + ( + "history_user", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "page", + models.ForeignKey( + blank=True, + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="cms.page", + ), + ), + ], + options={ + "verbose_name": "historical section", + "verbose_name_plural": "historical sections", + "ordering": ("-history_date", "-history_id"), + "get_latest_by": ("history_date", "history_id"), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name="HistoricalSitemap", + fields=[ + ( + "id", + models.UUIDField(db_index=True, default=uuid.uuid4, editable=False), + ), + ("name", models.CharField(max_length=256)), + ("name_ko", models.CharField(max_length=256, null=True)), + ("name_en", models.CharField(max_length=256, null=True)), + ("order", models.IntegerField(default=0)), + ("display_start_at", models.DateTimeField(blank=True, null=True)), + ("display_end_at", models.DateTimeField(blank=True, null=True)), + ("history_id", models.AutoField(primary_key=True, serialize=False)), + ("history_date", models.DateTimeField(db_index=True)), + ("history_change_reason", models.CharField(max_length=100, null=True)), + ( + "history_type", + models.CharField( + choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], + max_length=1, + ), + ), + ( + "history_user", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "page", + models.ForeignKey( + blank=True, + db_constraint=False, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="cms.page", + ), + ), + ( + "parent_sitemap", + models.ForeignKey( + blank=True, + db_constraint=False, + default=None, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="+", + to="cms.sitemap", + ), + ), + ], + options={ + "verbose_name": "historical sitemap", + "verbose_name_plural": "historical sitemaps", + "ordering": ("-history_date", "-history_id"), + "get_latest_by": ("history_date", "history_id"), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/app/cms/models.py b/app/cms/models.py index 83fb295..c10914f 100644 --- a/app/cms/models.py +++ b/app/cms/models.py @@ -1,7 +1,6 @@ import uuid from django.db import models -from simple_history.models import HistoricalRecords class Page(models.Model): @@ -10,7 +9,7 @@ class Page(models.Model): css = models.TextField(null=True, blank=True, default=None) title = models.CharField(max_length=256) subtitle = models.CharField(max_length=512) - history = HistoricalRecords() + # history = HistoricalRecords() def __str__(self): return str(self.title) @@ -26,7 +25,7 @@ class Sitemap(models.Model): page = models.ForeignKey(Page, on_delete=models.PROTECT) display_start_at = models.DateTimeField(null=True, blank=True) display_end_at = models.DateTimeField(null=True, blank=True) - history = HistoricalRecords() + # history = HistoricalRecords() def __str__(self): return str(self.name) @@ -38,7 +37,7 @@ class Section(models.Model): order = models.IntegerField(default=0) css = models.TextField(null=True, blank=True, default=None) body = models.TextField(help_text="Content of the page, Written in markdown format") - history = HistoricalRecords() + # history = HistoricalRecords() def __str__(self): return f"Section {self.order} of {self.page}" diff --git a/app/cms/serializers.py b/app/cms/serializers.py new file mode 100644 index 0000000..ec21529 --- /dev/null +++ b/app/cms/serializers.py @@ -0,0 +1,14 @@ +from cms.models import Page, Sitemap +from rest_framework import serializers + + +class SitemapSerializer(serializers.ModelSerializer): + class Meta: + model = Sitemap + fields = "__all__" + + +class PageSerializer(serializers.ModelSerializer): + class Meta: + model = Page + fields = "__all__" diff --git a/app/cms/test/conftest.py b/app/cms/test/conftest.py new file mode 100644 index 0000000..9f56a32 --- /dev/null +++ b/app/cms/test/conftest.py @@ -0,0 +1,23 @@ +import pytest +from cms.models import Page, Sitemap +from rest_framework.test import APIClient + + +@pytest.fixture +def api_client(): + return APIClient() + + +@pytest.fixture +def create_page(): + page = Page.objects.create( + title="제목", + subtitle="부제목", + ) + return page + + +@pytest.fixture +def create_sitemap(create_page): + sitemap = Sitemap.objects.create(page=create_page) + return sitemap diff --git a/app/cms/test/page_api_test.py b/app/cms/test/page_api_test.py new file mode 100644 index 0000000..65e5dfb --- /dev/null +++ b/app/cms/test/page_api_test.py @@ -0,0 +1,20 @@ +import pytest +from django.urls import reverse + +PAGE_SITEMAP = "cms-page" + + +@pytest.mark.django_db +def test_list_view(api_client, create_page): + url = reverse(PAGE_SITEMAP) + response = api_client.get(url) + if response.status_code != 200: + raise Exception("cms Page list API raised error") + + +@pytest.mark.django_db +def test_retrieve_view(api_client, create_page): + url = reverse(PAGE_SITEMAP) + response = api_client.get(url, kwargs={"pk": create_page.id}) + if response.status_code != 200: + raise Exception("cms Page retrieve API raised error") diff --git a/app/cms/test/sitemap_api_test.py b/app/cms/test/sitemap_api_test.py new file mode 100644 index 0000000..d1bb2d3 --- /dev/null +++ b/app/cms/test/sitemap_api_test.py @@ -0,0 +1,21 @@ +import pytest +from django.urls import reverse + +CMS_SITEMAP = "cms-sitemap" + + +@pytest.mark.django_db +def test_list_view(api_client, create_sitemap): + url = reverse(CMS_SITEMAP) + response = api_client.get(url) + if response.status_code != 200: + raise Exception("cms Sitemap list API raised error") + + +@pytest.mark.django_db +def test_retrieve_view(api_client, create_sitemap): + url = reverse(CMS_SITEMAP) + print("create_sitemap_id", create_sitemap.id) + response = api_client.get(url, kwargs={"pk": create_sitemap.id}) + if response.status_code != 200: + raise Exception("cms Sitemap retrieve API raised error") diff --git a/app/cms/urls.py b/app/cms/urls.py new file mode 100644 index 0000000..9bd8569 --- /dev/null +++ b/app/cms/urls.py @@ -0,0 +1,24 @@ +""" +URL configuration for core project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" + +from cms import views +from django.urls import path + +urlpatterns = [ + path("sitemap/", views.SitemapListRetrieveViewSet.as_view({"get": "list"}), name="cms-sitemap"), + path("page/", views.PageListRetrieveViewSet.as_view({"get": "list"}), name="cms-page"), +] diff --git a/app/cms/views.py b/app/cms/views.py new file mode 100644 index 0000000..c48956a --- /dev/null +++ b/app/cms/views.py @@ -0,0 +1,13 @@ +from cms.models import Page, Sitemap +from cms.serializers import PageSerializer, SitemapSerializer +from rest_framework.viewsets import ReadOnlyModelViewSet + + +class SitemapListRetrieveViewSet(ReadOnlyModelViewSet): + serializer_class = SitemapSerializer + queryset = Sitemap.objects.all() + + +class PageListRetrieveViewSet(ReadOnlyModelViewSet): + serializer_class = PageSerializer + queryset = Page.objects.all() diff --git a/app/core/urls.py b/app/core/urls.py index 715103c..cda6622 100644 --- a/app/core/urls.py +++ b/app/core/urls.py @@ -32,6 +32,7 @@ path("admin/", admin.site.urls), # V1 API re_path("^v1/", include((v1_apis, "v1"), namespace="v1")), + path("cms/", include("cms.urls")), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) if settings.DEBUG: diff --git a/pyproject.toml b/pyproject.toml index 09fa2fe..21239f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,8 @@ dependencies = [ "setuptools>=78.1.0", "zappa>=0.59.0", "zappa-django-utils>=0.4.1", + "boto3 (>=1.37.37,<2.0.0)", + "pytest (>=8.3.5,<9.0.0)", ] [dependency-groups] @@ -51,6 +53,9 @@ deployment = [ package = false default-groups = ["dev", "deployment"] +[tool.poetry.group.dev.dependencies] +pytest-django = "^4.11.1" + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" diff --git a/uv.lock b/uv.lock index 0f0f4d6..73097d9 100644 --- a/uv.lock +++ b/uv.lock @@ -106,6 +106,7 @@ source = { virtual = "." } dependencies = [ { name = "argon2-cffi" }, { name = "awslambdaric" }, + { name = "boto3" }, { name = "django" }, { name = "django-constance" }, { name = "django-cors-headers" }, @@ -122,6 +123,7 @@ dependencies = [ { name = "httpx" }, { name = "packaging" }, { name = "psycopg", extra = ["binary"] }, + { name = "pytest" }, { name = "sentry-sdk", extra = ["django"] }, { name = "setuptools" }, { name = "zappa" }, @@ -150,6 +152,7 @@ dev = [ requires-dist = [ { name = "argon2-cffi", specifier = ">=23.1.0" }, { name = "awslambdaric", specifier = ">=3.0.2" }, + { name = "boto3", specifier = ">=1.37.37,<2.0.0" }, { name = "django", specifier = ">=5.2" }, { name = "django-constance", specifier = ">=4.3.2" }, { name = "django-cors-headers", specifier = ">=4.7.0" }, @@ -166,6 +169,7 @@ requires-dist = [ { name = "httpx", specifier = ">=0.28.1" }, { name = "packaging", specifier = ">=24.2" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.2.6" }, + { name = "pytest", specifier = ">=8.3.5,<9.0.0" }, { name = "sentry-sdk", extras = ["django"], specifier = ">=2.25.1" }, { name = "setuptools", specifier = ">=78.1.0" }, { name = "zappa", specifier = ">=0.59.0" }, @@ -192,16 +196,16 @@ dev = [ [[package]] name = "boto3" -version = "1.37.28" +version = "1.37.37" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/f5/dd50ed0a20019fa38c22797718c80d38e8b75b5e97c971a908c638e819aa/boto3-1.37.28.tar.gz", hash = "sha256:09ee85ba70a88286bba0d1bf5f0460a4b3bde52d162216accfe637b8bfac351b", size = 111385 } +sdist = { url = "https://files.pythonhosted.org/packages/82/8c/2ca661db6c9e591d9dc46149b43a91385283c852436ccba62e199643e196/boto3-1.37.37.tar.gz", hash = "sha256:752d31105a45e3e01c8c68471db14ae439990b75a35e72b591ca528e2575b28f", size = 111666 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/76/2723dede8c69d04e37f0897e9c05b597b6906df3d4c80186e39a0bc1b914/boto3-1.37.28-py3-none-any.whl", hash = "sha256:e584d9d33808633e73af3d962e22cf2cea91a38bc5a17577bb25618f8ded504f", size = 139562 }, + { url = "https://files.pythonhosted.org/packages/e4/5f/032d93e74949222ffbfbc3270f29a3ee423fe648de8a31c49cce0cbb0a09/boto3-1.37.37-py3-none-any.whl", hash = "sha256:d125cb11e22817f7a2581bade4bf7b75247b401888890239ceb5d3e902ccaf38", size = 139917 }, ] [[package]] @@ -236,16 +240,16 @@ ssm = [ [[package]] name = "botocore" -version = "1.37.28" +version = "1.37.37" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/90/557082a8379ece106b37eb00766efc7a32cbfcdaa0d1d78f38f99eefd218/botocore-1.37.28.tar.gz", hash = "sha256:69ea327f70f0607d174c4c2b1dcc87327b9c48e413c9d322179172b614b28e03", size = 13799915 } +sdist = { url = "https://files.pythonhosted.org/packages/96/d0/70969515e3ae8ff0fcccf22827d5d131bc7b8729331127415cf8f2861d63/botocore-1.37.37.tar.gz", hash = "sha256:3eadde6fed95c4cb469cc39d1c3558528b7fa76d23e7e16d4bddc77250431a64", size = 13828530 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/c3/29ffcb4c90492bcfcff1b7e5ddb5529846acc0e627569432db9842c47675/botocore-1.37.28-py3-none-any.whl", hash = "sha256:c26b645d7b125bf42ffc1671b862b47500ee658e3a1c95d2438cb689fc85df15", size = 13467675 }, + { url = "https://files.pythonhosted.org/packages/fe/17/602915b29cb695e1e66f65e33b1026f1534e49975d99ea4e32e58d963542/botocore-1.37.37-py3-none-any.whl", hash = "sha256:eb730ff978f47c02f0c8ed07bccdc0db6d8fa098ed32ac31bee1da0e9be480d1", size = 13495584 }, ] [[package]] @@ -701,6 +705,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454 }, ] +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + [[package]] name = "ipython" version = "9.0.2" @@ -987,6 +1000,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, ] +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + [[package]] name = "pre-commit" version = "4.2.0" @@ -1087,6 +1109,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, ] +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" From 5e03f1b0736d6f4804c47e059dc28d62d5a0d63a Mon Sep 17 00:00:00 2001 From: earthyoung Date: Sun, 27 Apr 2025 19:24:27 +0900 Subject: [PATCH 2/4] =?UTF-8?q?chore:=20print=EB=AC=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/cms/test/sitemap_api_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/cms/test/sitemap_api_test.py b/app/cms/test/sitemap_api_test.py index d1bb2d3..34248b3 100644 --- a/app/cms/test/sitemap_api_test.py +++ b/app/cms/test/sitemap_api_test.py @@ -15,7 +15,6 @@ def test_list_view(api_client, create_sitemap): @pytest.mark.django_db def test_retrieve_view(api_client, create_sitemap): url = reverse(CMS_SITEMAP) - print("create_sitemap_id", create_sitemap.id) response = api_client.get(url, kwargs={"pk": create_sitemap.id}) if response.status_code != 200: raise Exception("cms Sitemap retrieve API raised error") From 7040db031a1a06478f122f13bf12fa3e65d5c66f Mon Sep 17 00:00:00 2001 From: earthyoung Date: Fri, 2 May 2025 23:11:14 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20PR=20review=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 149 ++++++++++++++++--------------- app/cms/test/page_api_test.py | 8 +- app/cms/test/sitemap_api_test.py | 6 +- app/cms/urls.py | 12 +-- app/core/urls.py | 4 +- bandit.yaml | 4 + pyproject.toml | 2 +- uv.lock | 35 -------- 8 files changed, 98 insertions(+), 122 deletions(-) create mode 100644 bandit.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 97e92ca..9bcb045 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,81 +1,84 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks default_language_version: - python: python3.12 + python: python3.12 default_stages: [pre-commit, pre-push] repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: check-toml - - id: check-xml - - id: check-yaml - - id: check-added-large-files - - id: detect-aws-credentials - args: - - --allow-missing-credentials - - id: end-of-file-fixer - - id: mixed-line-ending - - id: pretty-format-json - args: - - --autofix - - id: trailing-whitespace - exclude_types: - - javascript - - markdown -- repo: https://github.com/PyCQA/flake8 - rev: 7.2.0 - hooks: - - id: flake8 - additional_dependencies: - - flake8-bugbear - - flake8-noqa - args: - - --max-line-length=120 - - --max-complexity=18 -- repo: https://github.com/psf/black - rev: 25.1.0 - hooks: - - id: black - language_version: python3.12 - args: - - --line-length=120 -- repo: https://github.com/PyCQA/bandit - rev: '1.8.3' - hooks: - - id: bandit -- repo: https://github.com/PyCQA/isort - rev: '6.0.1' - hooks: - - id: isort -- repo: https://github.com/dosisod/refurb - rev: v2.0.0 - hooks: - - id: refurb - additional_dependencies: - - boto3 - - django-constance - - django-cors-headers - - django-environ - - django-extensions - - django-filter - - django-simple-history - - django-stubs[compatible-mypy] - - drf-spectacular - - drf-standardized-errors - - djangorestframework-stubs[compatible-mypy] - - zappa-django-utils -- repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.6.12 - hooks: - - id: uv-lock -- repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.4 - hooks: - - id: ruff - args: - - --fix - - id: ruff-format + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-toml + - id: check-xml + - id: check-yaml + - id: check-added-large-files + - id: detect-aws-credentials + args: + - --allow-missing-credentials + - id: end-of-file-fixer + - id: mixed-line-ending + - id: pretty-format-json + args: + - --autofix + - id: trailing-whitespace + exclude_types: + - javascript + - markdown + - repo: https://github.com/PyCQA/flake8 + rev: 7.2.0 + hooks: + - id: flake8 + additional_dependencies: + - flake8-bugbear + - flake8-noqa + args: + - --max-line-length=120 + - --max-complexity=18 + - repo: https://github.com/psf/black + rev: 25.1.0 + hooks: + - id: black + language_version: python3.12 + args: + - --line-length=120 + - repo: https://github.com/PyCQA/bandit + rev: "1.8.3" + hooks: + - id: bandit + args: + - -c + - bandit.yaml + - repo: https://github.com/PyCQA/isort + rev: "6.0.1" + hooks: + - id: isort + - repo: https://github.com/dosisod/refurb + rev: v2.0.0 + hooks: + - id: refurb + additional_dependencies: + - boto3 + - django-constance + - django-cors-headers + - django-environ + - django-extensions + - django-filter + - django-simple-history + - django-stubs[compatible-mypy] + - drf-spectacular + - drf-standardized-errors + - djangorestframework-stubs[compatible-mypy] + - zappa-django-utils + - repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.6.12 + hooks: + - id: uv-lock + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.4 + hooks: + - id: ruff + args: + - --fix + - id: ruff-format # - repo: https://github.com/pre-commit/mirrors-mypy # rev: 'v1.15.0' # hooks: diff --git a/app/cms/test/page_api_test.py b/app/cms/test/page_api_test.py index 65e5dfb..b75c11a 100644 --- a/app/cms/test/page_api_test.py +++ b/app/cms/test/page_api_test.py @@ -1,3 +1,5 @@ +import http + import pytest from django.urls import reverse @@ -8,13 +10,11 @@ def test_list_view(api_client, create_page): url = reverse(PAGE_SITEMAP) response = api_client.get(url) - if response.status_code != 200: - raise Exception("cms Page list API raised error") + assert response.status_code == http.HTTPStatus.OK @pytest.mark.django_db def test_retrieve_view(api_client, create_page): url = reverse(PAGE_SITEMAP) response = api_client.get(url, kwargs={"pk": create_page.id}) - if response.status_code != 200: - raise Exception("cms Page retrieve API raised error") + assert response.status_code == http.HTTPStatus.OK diff --git a/app/cms/test/sitemap_api_test.py b/app/cms/test/sitemap_api_test.py index 34248b3..3d03b64 100644 --- a/app/cms/test/sitemap_api_test.py +++ b/app/cms/test/sitemap_api_test.py @@ -1,3 +1,5 @@ +import http + import pytest from django.urls import reverse @@ -8,7 +10,7 @@ def test_list_view(api_client, create_sitemap): url = reverse(CMS_SITEMAP) response = api_client.get(url) - if response.status_code != 200: + if response.status_code != http.HTTPStatus.OK: raise Exception("cms Sitemap list API raised error") @@ -16,5 +18,5 @@ def test_list_view(api_client, create_sitemap): def test_retrieve_view(api_client, create_sitemap): url = reverse(CMS_SITEMAP) response = api_client.get(url, kwargs={"pk": create_sitemap.id}) - if response.status_code != 200: + if response.status_code != http.HTTPStatus.OK: raise Exception("cms Sitemap retrieve API raised error") diff --git a/app/cms/urls.py b/app/cms/urls.py index 9bd8569..0fba03c 100644 --- a/app/cms/urls.py +++ b/app/cms/urls.py @@ -16,9 +16,11 @@ """ from cms import views -from django.urls import path +from django.urls import include, path +from rest_framework import routers -urlpatterns = [ - path("sitemap/", views.SitemapListRetrieveViewSet.as_view({"get": "list"}), name="cms-sitemap"), - path("page/", views.PageListRetrieveViewSet.as_view({"get": "list"}), name="cms-page"), -] +cms_router = routers.SimpleRouter() +cms_router.register("sitemap/", views.SitemapListRetrieveViewSet, base_name="cms-sitemap") +cms_router.register("page/", views.PageListRetrieveViewSet, base_name="cms-page") + +urlpatterns = [path("", include(cms_router.urls))] diff --git a/app/core/urls.py b/app/core/urls.py index cda6622..942f940 100644 --- a/app/core/urls.py +++ b/app/core/urls.py @@ -22,7 +22,8 @@ from django.urls import include, path, re_path, resolvers from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView -v1_apis: list[resolvers.URLPattern | resolvers.URLResolver] = [] # type: ignore[assignment] +# type: ignore[assignment] +v1_apis: list[resolvers.URLPattern | resolvers.URLResolver] = [path("cms/", include("cms.urls"))] urlpatterns = [ # Health Check @@ -32,7 +33,6 @@ path("admin/", admin.site.urls), # V1 API re_path("^v1/", include((v1_apis, "v1"), namespace="v1")), - path("cms/", include("cms.urls")), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) if settings.DEBUG: diff --git a/bandit.yaml b/bandit.yaml new file mode 100644 index 0000000..25eb09c --- /dev/null +++ b/bandit.yaml @@ -0,0 +1,4 @@ +assert_used: + skips: + - "test*.py" + - "*_test.py" diff --git a/pyproject.toml b/pyproject.toml index 21239f9..21e80f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,6 @@ dependencies = [ "zappa>=0.59.0", "zappa-django-utils>=0.4.1", "boto3 (>=1.37.37,<2.0.0)", - "pytest (>=8.3.5,<9.0.0)", ] [dependency-groups] @@ -54,6 +53,7 @@ package = false default-groups = ["dev", "deployment"] [tool.poetry.group.dev.dependencies] +pytest = ">=8.3.5,<9.0.0" pytest-django = "^4.11.1" [build-system] diff --git a/uv.lock b/uv.lock index 73097d9..a35e44a 100644 --- a/uv.lock +++ b/uv.lock @@ -123,7 +123,6 @@ dependencies = [ { name = "httpx" }, { name = "packaging" }, { name = "psycopg", extra = ["binary"] }, - { name = "pytest" }, { name = "sentry-sdk", extra = ["django"] }, { name = "setuptools" }, { name = "zappa" }, @@ -169,7 +168,6 @@ requires-dist = [ { name = "httpx", specifier = ">=0.28.1" }, { name = "packaging", specifier = ">=24.2" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.2.6" }, - { name = "pytest", specifier = ">=8.3.5,<9.0.0" }, { name = "sentry-sdk", extras = ["django"], specifier = ">=2.25.1" }, { name = "setuptools", specifier = ">=78.1.0" }, { name = "zappa", specifier = ">=0.59.0" }, @@ -705,15 +703,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454 }, ] -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, -] - [[package]] name = "ipython" version = "9.0.2" @@ -1000,15 +989,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, ] -[[package]] -name = "pluggy" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, -] - [[package]] name = "pre-commit" version = "4.2.0" @@ -1109,21 +1089,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, ] -[[package]] -name = "pytest" -version = "8.3.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" From 7e2c3160febee0d444c598eea2e9386249deba54 Mon Sep 17 00:00:00 2001 From: earthyoung Date: Fri, 2 May 2025 23:39:56 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20test=EC=97=90=EC=84=9C=20=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=ED=95=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/cms/test/page_api_test.py | 8 +++----- app/cms/test/sitemap_api_test.py | 8 +++----- app/cms/urls.py | 4 ++-- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/cms/test/page_api_test.py b/app/cms/test/page_api_test.py index b75c11a..e652958 100644 --- a/app/cms/test/page_api_test.py +++ b/app/cms/test/page_api_test.py @@ -3,18 +3,16 @@ import pytest from django.urls import reverse -PAGE_SITEMAP = "cms-page" - @pytest.mark.django_db def test_list_view(api_client, create_page): - url = reverse(PAGE_SITEMAP) + url = reverse("v1:cms-page-list") response = api_client.get(url) assert response.status_code == http.HTTPStatus.OK @pytest.mark.django_db def test_retrieve_view(api_client, create_page): - url = reverse(PAGE_SITEMAP) - response = api_client.get(url, kwargs={"pk": create_page.id}) + url = reverse("v1:cms-page-detail", kwargs={"pk": create_page.id}) + response = api_client.get(url) assert response.status_code == http.HTTPStatus.OK diff --git a/app/cms/test/sitemap_api_test.py b/app/cms/test/sitemap_api_test.py index 3d03b64..3566afe 100644 --- a/app/cms/test/sitemap_api_test.py +++ b/app/cms/test/sitemap_api_test.py @@ -3,12 +3,10 @@ import pytest from django.urls import reverse -CMS_SITEMAP = "cms-sitemap" - @pytest.mark.django_db def test_list_view(api_client, create_sitemap): - url = reverse(CMS_SITEMAP) + url = reverse("v1:cms-sitemap-list") response = api_client.get(url) if response.status_code != http.HTTPStatus.OK: raise Exception("cms Sitemap list API raised error") @@ -16,7 +14,7 @@ def test_list_view(api_client, create_sitemap): @pytest.mark.django_db def test_retrieve_view(api_client, create_sitemap): - url = reverse(CMS_SITEMAP) - response = api_client.get(url, kwargs={"pk": create_sitemap.id}) + url = reverse("v1:cms-sitemap-detail", kwargs={"pk": create_sitemap.id}) + response = api_client.get(url) if response.status_code != http.HTTPStatus.OK: raise Exception("cms Sitemap retrieve API raised error") diff --git a/app/cms/urls.py b/app/cms/urls.py index 0fba03c..08930ac 100644 --- a/app/cms/urls.py +++ b/app/cms/urls.py @@ -20,7 +20,7 @@ from rest_framework import routers cms_router = routers.SimpleRouter() -cms_router.register("sitemap/", views.SitemapListRetrieveViewSet, base_name="cms-sitemap") -cms_router.register("page/", views.PageListRetrieveViewSet, base_name="cms-page") +cms_router.register("sitemap", views.SitemapListRetrieveViewSet, basename="cms-sitemap") +cms_router.register("page", views.PageListRetrieveViewSet, basename="cms-page") urlpatterns = [path("", include(cms_router.urls))]