From 818b13104ca16d829724d91a9842f7ec8bf15e97 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Wed, 1 Feb 2023 23:55:40 +0900 Subject: [PATCH 01/47] =?UTF-8?q?init:=20=ED=9B=84=EC=9B=90=EC=82=AC=20dja?= =?UTF-8?q?ngo=20app=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + pyconkr/settings-dev.py | 1 + pyconkr/settings-prod.py | 1 + pyconkr/settings.py | 11 +++- pyconkr/urls.py | 2 + requirements.txt | 3 +- sponsor/__init__.py | 0 sponsor/admin.py | 25 +++++++ sponsor/apps.py | 6 ++ sponsor/models.py | 138 +++++++++++++++++++++++++++++++++++++++ sponsor/tests.py | 3 + sponsor/urls.py | 6 ++ sponsor/views.py | 3 + 13 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 sponsor/__init__.py create mode 100644 sponsor/admin.py create mode 100644 sponsor/apps.py create mode 100644 sponsor/models.py create mode 100644 sponsor/tests.py create mode 100644 sponsor/urls.py create mode 100644 sponsor/views.py diff --git a/.gitignore b/.gitignore index 8faa4d2..f39d21a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /.idea /db.sqlite3 +/sponsor/migrations diff --git a/pyconkr/settings-dev.py b/pyconkr/settings-dev.py index 343d2ed..6b4cc89 100644 --- a/pyconkr/settings-dev.py +++ b/pyconkr/settings-dev.py @@ -16,6 +16,7 @@ } # django-storages: S3 +del MEDIA_ROOT DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" STATICFILES_STORAGE = "storages.backends.s3boto3.S3StaticStorage" AWS_S3_ACCESS_KEY_ID = os.getenv("AWS_S3_ACCESS_KEY_ID") diff --git a/pyconkr/settings-prod.py b/pyconkr/settings-prod.py index 1e107f6..0c752e5 100644 --- a/pyconkr/settings-prod.py +++ b/pyconkr/settings-prod.py @@ -16,6 +16,7 @@ } # django-storages: S3 +del MEDIA_ROOT DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" STATICFILES_STORAGE = "storages.backends.s3boto3.S3StaticStorage" AWS_S3_ACCESS_KEY_ID = os.getenv("AWS_S3_ACCESS_KEY_ID") diff --git a/pyconkr/settings.py b/pyconkr/settings.py index 6b0b6b1..5b72b94 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -37,8 +37,13 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - # djangorestframework + + # add-on "rest_framework", + "django_summernote" + + # apps + "sponsor", ] MIDDLEWARE = [ @@ -123,3 +128,7 @@ # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# django-summernote +MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') diff --git a/pyconkr/urls.py b/pyconkr/urls.py index ecbd711..8d2711b 100644 --- a/pyconkr/urls.py +++ b/pyconkr/urls.py @@ -18,5 +18,7 @@ urlpatterns = [ path("api-auth/", include("rest_framework.urls")), + path('summernote/', include("django_summernote.urls")), path("admin/", admin.site.urls), + path("sponsors/", include("sponsor.urls")), ] diff --git a/requirements.txt b/requirements.txt index e5c04b1..71dd51c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,5 @@ Markdown==3.4.1 mysql-connector-python==8.0.32 mysqlclient==2.1.1 sqlparse==0.4.3 -tzdata==2022.7 \ No newline at end of file +tzdata==2022.7 +sorl-thumbnail==12.9.0 \ No newline at end of file diff --git a/sponsor/__init__.py b/sponsor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sponsor/admin.py b/sponsor/admin.py new file mode 100644 index 0000000..52de445 --- /dev/null +++ b/sponsor/admin.py @@ -0,0 +1,25 @@ +from django.contrib import admin +from sponsor.models import Sponsor, SponsorLevel + + +class SponsorAdmin(SummernoteModelAdmin): + formfield_overrides = {models.TextField: { + 'widget': SummernoteWidgetWithCustomToolbar}} + autocomplete_fields = ('creator', 'manager_id',) + list_display = ('creator', 'name', 'level', 'manager_name', 'manager_email', 'manager_id', + 'submitted', 'accepted', 'paid_at',) + list_filter = ('accepted',) + ordering = ('-created_at',) + + +admin.site.register(Sponsor, SponsorAdmin) + + +class SponsorLevelAdmin(SummernoteModelAdmin): + list_display = ('id', 'order', 'name', 'slug', 'price', 'limit',) + list_editable = ('order', 'slug',) + ordering = ('order',) + search_fields = ('name',) + + +admin.site.register(SponsorLevel, SponsorLevelAdmin) diff --git a/sponsor/apps.py b/sponsor/apps.py new file mode 100644 index 0000000..674f284 --- /dev/null +++ b/sponsor/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SponsorConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "sponsor" diff --git a/sponsor/models.py b/sponsor/models.py new file mode 100644 index 0000000..65d9ffb --- /dev/null +++ b/sponsor/models.py @@ -0,0 +1,138 @@ +from django.db import models +from django.contrib.auth import get_user_model +from sorl.thumbnail import ImageField as SorlImageField + +User = get_user_model() + + +class SponsorLevelManager(models.Manager): + def get_queryset(self): + return super(SponsorLevelManager, self).get_queryset().all().order_by("order") + + +class SponsorLevel(models.Model): + name = models.CharField(max_length=255, blank=True, default="", help_text="후원 등급명") + desc = models.TextField( + null=True, blank=True, help_text="후원 혜택을 입력하면 될 거 같아요 :) 후원사가 등급을 정할 때 볼 문구입니다." + ) + visible = models.BooleanField(default=True) + price = models.IntegerField(default=0) + limit = models.IntegerField(default=0, help_text="후원사 등급 별 구좌수") + order = models.IntegerField(default=1) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + objects = SponsorLevelManager() + + @property + def current_remaining_number(self): + return ( + 0 + if self.limit - self.accepted_count < 0 + else self.limit - self.accepted_count + ) + + @property + def paid_count(self): + return Sponsor.objects.filter( + level=self, submitted=True, accepted=True, paid_at__isnull=False + ).count() + + @property + def accepted_count(self): + return Sponsor.objects.filter(level=self, submitted=True, accepted=True).count() + + def __str__(self): + return self.name + + +def registration_file_upload_to(instance, filename): + return f"sponsor/business_registration/{instance.id}/{filename}" + + +def logo_image_upload_to(instance, filename): + return f"sponsor/logo/{instance.id}/{filename}" + + +class Sponsor(models.Model): + class Meta: + ordering = ["paid_at", "id"] + + creator = models.ForeignKey( + User, + null=True, # TODO: 추루 로그인 적용 후 입력 + blank=True, # TODO: 추루 로그인 적용 후 입력 + on_delete=models.CASCADE, + help_text="후원사를 등록한 유저", + related_name="sponsor_creator", + ) + name = models.CharField( + max_length=255, help_text="후원사의 이름입니다. 서비스나 회사 이름이 될 수 있습니다." + ) + level = models.ForeignKey( + SponsorLevel, + null=True, + on_delete=models.SET_NULL, + blank=True, + help_text="후원을 원하시는 등급을 선택해주십시오. 모두 판매된 등급은 선택할 수 없습니다.", + ) + desc = models.TextField( + null=True, blank=True, help_text="후원사 설명입니다. 이 설명은 국문 홈페이지에 게시됩니다." + ) + eng_desc = models.TextField( + null=True, blank=True, help_text="후원사 영문 설명입니다. 이 설명은 영문 홈페이지에 게시됩니다." + ) + manager_name = models.CharField( + max_length=100, help_text="준비위원회와 후원과 관련된 논의를 진행할 담당자의 이름을 입력해주십시오." + ) + manager_email = models.CharField( + max_length=100, + help_text="입력하신 메일로 후원과 관련된 안내 메일이나 문의를 보낼 예정입니다. 후원 담당자의 이메일 주소를 입력해주십시오.", + ) + manager_id = models.ForeignKey( + User, + null=True, + blank=True, + on_delete=models.CASCADE, + help_text="후원사를 위한 추가 아이디", + related_name="sponsor_temp_id", + ) + business_registration_number = models.CharField( + max_length=100, + null=True, + blank=True, + help_text="후원사 사업자 등록번호입니다. 세금 계산서 발급에 사용됩니다.", + ) + business_registration_file = models.FileField( + null=True, + blank=True, + upload_to=registration_file_upload_to, + help_text="후원사 사업자 등록증 스캔본입니다. 세금 계산서 발급에 사용됩니다.", + ) + url = models.CharField( + max_length=255, + null=True, + blank=True, + help_text="파이콘 홈페이지에 공개되는 후원사 홈페이지 주소입니다.", + ) + logo_image = SorlImageField( + upload_to=logo_image_upload_to, + null=True, + blank=True, + help_text="홈페이지에 공개되는 후원사 로고 이미지입니다.", + ) + submitted = models.BooleanField( + default=False, + help_text="사용자가 제출했는지 여부를 저장합니다. 요청이 제출되면 준비위원회에서 검토하고 받아들일지를 결정합니다.", + ) + accepted = models.BooleanField( + default=False, help_text="후원사 신청이 접수되었고, 입금 대기 상태인 경우 True로 설정됩니다." + ) + paid_at = models.DateTimeField( + null=True, blank=True, help_text="후원금이 입금된 일시입니다. 아직 입금되지 않았을 경우 None이 들어갑니다." + ) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str_(self): + return f"{self.name}/{self.level}" diff --git a/sponsor/tests.py b/sponsor/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/sponsor/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/sponsor/urls.py b/sponsor/urls.py new file mode 100644 index 0000000..dcfc3c4 --- /dev/null +++ b/sponsor/urls.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + # path("", ), # TODO +] \ No newline at end of file diff --git a/sponsor/views.py b/sponsor/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/sponsor/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From a99e206b325cbed5896cf08d497acebb3b15fd18 Mon Sep 17 00:00:00 2001 From: Dong-Young Kim <31337.persona@gmail.com> Date: Thu, 2 Feb 2023 23:30:39 +0900 Subject: [PATCH 02/47] ci: run `isort` when `pull_request` event triggered --- .github/workflows/pull-request-merge-precondition.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/pull-request-merge-precondition.yml b/.github/workflows/pull-request-merge-precondition.yml index ddf7478..7af1ccd 100644 --- a/.github/workflows/pull-request-merge-precondition.yml +++ b/.github/workflows/pull-request-merge-precondition.yml @@ -15,3 +15,8 @@ jobs: - uses: psf/black@stable with: options: "--check --verbose" + + - uses: isort/isort-action@master + with: + configuration: "--check-only --diff --profile black" + requirementsFiles: "requirements.txt" From 8106e264f63b03df8765ee4fa612904d51b222c9 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Fri, 3 Feb 2023 20:27:58 +0900 Subject: [PATCH 03/47] =?UTF-8?q?fix:=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/settings.py | 2 +- requirements.txt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyconkr/settings.py b/pyconkr/settings.py index 5b72b94..643ef2f 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -9,7 +9,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.1/ref/settings/ """ - +import os from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. diff --git a/requirements.txt b/requirements.txt index 71dd51c..e03bd10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ mysql-connector-python==8.0.32 mysqlclient==2.1.1 sqlparse==0.4.3 tzdata==2022.7 -sorl-thumbnail==12.9.0 \ No newline at end of file +sorl-thumbnail==12.9.0 +django-summernote==0.8.20.0 From 276540c5667a17db274931f33a1698813199ceea Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Fri, 3 Feb 2023 20:35:08 +0900 Subject: [PATCH 04/47] =?UTF-8?q?fix:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20summ?= =?UTF-8?q?ernote=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/settings.py | 2 +- requirements.txt | 1 + sponsor/admin.py | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pyconkr/settings.py b/pyconkr/settings.py index 643ef2f..aab73c7 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -40,7 +40,7 @@ # add-on "rest_framework", - "django_summernote" + "django_summernote", # apps "sponsor", diff --git a/requirements.txt b/requirements.txt index e03bd10..cd79985 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ sqlparse==0.4.3 tzdata==2022.7 sorl-thumbnail==12.9.0 django-summernote==0.8.20.0 +Pillow==9.4.0 diff --git a/sponsor/admin.py b/sponsor/admin.py index 52de445..a319389 100644 --- a/sponsor/admin.py +++ b/sponsor/admin.py @@ -1,10 +1,10 @@ from django.contrib import admin from sponsor.models import Sponsor, SponsorLevel +from django_summernote.admin import SummernoteModelAdmin class SponsorAdmin(SummernoteModelAdmin): - formfield_overrides = {models.TextField: { - 'widget': SummernoteWidgetWithCustomToolbar}} + summernote_fields = "__all__" autocomplete_fields = ('creator', 'manager_id',) list_display = ('creator', 'name', 'level', 'manager_name', 'manager_email', 'manager_id', 'submitted', 'accepted', 'paid_at',) @@ -16,8 +16,8 @@ class SponsorAdmin(SummernoteModelAdmin): class SponsorLevelAdmin(SummernoteModelAdmin): - list_display = ('id', 'order', 'name', 'slug', 'price', 'limit',) - list_editable = ('order', 'slug',) + list_display = ('id', 'order', 'name', 'price', 'limit',) + list_editable = ('order',) ordering = ('order',) search_fields = ('name',) From 549a1d2ba53e37751fe27094298d69663929f340 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Fri, 3 Feb 2023 20:39:12 +0900 Subject: [PATCH 05/47] =?UTF-8?q?lint:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A9=A7=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/settings.py | 6 ++---- pyconkr/urls.py | 2 +- sponsor/admin.py | 36 +++++++++++++++++++++++++++--------- sponsor/models.py | 4 ++-- sponsor/urls.py | 2 +- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/pyconkr/settings.py b/pyconkr/settings.py index aab73c7..c26de81 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -37,11 +37,9 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - # add-on "rest_framework", "django_summernote", - # apps "sponsor", ] @@ -130,5 +128,5 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # django-summernote -MEDIA_URL = '/media/' -MEDIA_ROOT = os.path.join(BASE_DIR, 'media/') +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(BASE_DIR, "media/") diff --git a/pyconkr/urls.py b/pyconkr/urls.py index 8d2711b..ef9e872 100644 --- a/pyconkr/urls.py +++ b/pyconkr/urls.py @@ -18,7 +18,7 @@ urlpatterns = [ path("api-auth/", include("rest_framework.urls")), - path('summernote/', include("django_summernote.urls")), + path("summernote/", include("django_summernote.urls")), path("admin/", admin.site.urls), path("sponsors/", include("sponsor.urls")), ] diff --git a/sponsor/admin.py b/sponsor/admin.py index a319389..24b1750 100644 --- a/sponsor/admin.py +++ b/sponsor/admin.py @@ -5,21 +5,39 @@ class SponsorAdmin(SummernoteModelAdmin): summernote_fields = "__all__" - autocomplete_fields = ('creator', 'manager_id',) - list_display = ('creator', 'name', 'level', 'manager_name', 'manager_email', 'manager_id', - 'submitted', 'accepted', 'paid_at',) - list_filter = ('accepted',) - ordering = ('-created_at',) + autocomplete_fields = ( + "creator", + "manager_id", + ) + list_display = ( + "creator", + "name", + "level", + "manager_name", + "manager_email", + "manager_id", + "submitted", + "accepted", + "paid_at", + ) + list_filter = ("accepted",) + ordering = ("-created_at",) admin.site.register(Sponsor, SponsorAdmin) class SponsorLevelAdmin(SummernoteModelAdmin): - list_display = ('id', 'order', 'name', 'price', 'limit',) - list_editable = ('order',) - ordering = ('order',) - search_fields = ('name',) + list_display = ( + "id", + "order", + "name", + "price", + "limit", + ) + list_editable = ("order",) + ordering = ("order",) + search_fields = ("name",) admin.site.register(SponsorLevel, SponsorLevelAdmin) diff --git a/sponsor/models.py b/sponsor/models.py index 65d9ffb..d51ccd3 100644 --- a/sponsor/models.py +++ b/sponsor/models.py @@ -60,8 +60,8 @@ class Meta: creator = models.ForeignKey( User, - null=True, # TODO: 추루 로그인 적용 후 입력 - blank=True, # TODO: 추루 로그인 적용 후 입력 + null=True, # TODO: 추루 로그인 적용 후 입력 + blank=True, # TODO: 추루 로그인 적용 후 입력 on_delete=models.CASCADE, help_text="후원사를 등록한 유저", related_name="sponsor_creator", diff --git a/sponsor/urls.py b/sponsor/urls.py index dcfc3c4..77e186f 100644 --- a/sponsor/urls.py +++ b/sponsor/urls.py @@ -3,4 +3,4 @@ urlpatterns = [ # path("", ), # TODO -] \ No newline at end of file +] From 3e32827e20f8608f03f160a27f5002904fcf89a1 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Fri, 3 Feb 2023 20:51:38 +0900 Subject: [PATCH 06/47] lint: isort --- pyconkr/settings-dev.py | 1 + pyconkr/settings-prod.py | 1 + pyconkr/urls.py | 2 +- sponsor/admin.py | 3 ++- sponsor/models.py | 2 +- sponsor/urls.py | 2 +- 6 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyconkr/settings-dev.py b/pyconkr/settings-dev.py index 6b4cc89..3554a1f 100644 --- a/pyconkr/settings-dev.py +++ b/pyconkr/settings-dev.py @@ -1,4 +1,5 @@ import os + from pyconkr.settings import * DEBUG = True diff --git a/pyconkr/settings-prod.py b/pyconkr/settings-prod.py index 0c752e5..38ee09c 100644 --- a/pyconkr/settings-prod.py +++ b/pyconkr/settings-prod.py @@ -1,4 +1,5 @@ import os + from pyconkr.settings import * DEBUG = False diff --git a/pyconkr/urls.py b/pyconkr/urls.py index ef9e872..8a42b4c 100644 --- a/pyconkr/urls.py +++ b/pyconkr/urls.py @@ -14,7 +14,7 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path, include +from django.urls import include, path urlpatterns = [ path("api-auth/", include("rest_framework.urls")), diff --git a/sponsor/admin.py b/sponsor/admin.py index 24b1750..b0dceb5 100644 --- a/sponsor/admin.py +++ b/sponsor/admin.py @@ -1,7 +1,8 @@ from django.contrib import admin -from sponsor.models import Sponsor, SponsorLevel from django_summernote.admin import SummernoteModelAdmin +from sponsor.models import Sponsor, SponsorLevel + class SponsorAdmin(SummernoteModelAdmin): summernote_fields = "__all__" diff --git a/sponsor/models.py b/sponsor/models.py index d51ccd3..63a8ed2 100644 --- a/sponsor/models.py +++ b/sponsor/models.py @@ -1,5 +1,5 @@ -from django.db import models from django.contrib.auth import get_user_model +from django.db import models from sorl.thumbnail import ImageField as SorlImageField User = get_user_model() diff --git a/sponsor/urls.py b/sponsor/urls.py index 77e186f..1160f1c 100644 --- a/sponsor/urls.py +++ b/sponsor/urls.py @@ -1,5 +1,5 @@ from django.contrib import admin -from django.urls import path, include +from django.urls import include, path urlpatterns = [ # path("", ), # TODO From d9fb543658e1041afd17c5b3d2f58b82afa7ef98 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sat, 4 Feb 2023 01:12:12 +0900 Subject: [PATCH 07/47] =?UTF-8?q?docs:=20readme=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..1cabdd3 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# pyconkr-api-v2 + +파이콘 한국 행사를 위한 API 서비스입니다. (2023 ~ ) + +## PR 이전에 +* 컨벤션 유지를 위해 `black`과 `isort`를 적용하고 있습니다. From 19a6340c89abf26248a8534650d8a65c84ba7856 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sun, 5 Feb 2023 11:00:04 +0900 Subject: [PATCH 08/47] =?UTF-8?q?update:=20=ED=99=98=EA=B2=BD=EB=B3=84=20A?= =?UTF-8?q?LLOW=5FHOSTS=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/settings-dev.py | 4 ++++ pyconkr/settings-prod.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/pyconkr/settings-dev.py b/pyconkr/settings-dev.py index 3554a1f..3b712de 100644 --- a/pyconkr/settings-dev.py +++ b/pyconkr/settings-dev.py @@ -4,6 +4,10 @@ DEBUG = True +ALLOWED_HOSTS += [ + "api-dev.pycon.kr", +] + # RDS DATABASES = { "default": { diff --git a/pyconkr/settings-prod.py b/pyconkr/settings-prod.py index 38ee09c..9655b74 100644 --- a/pyconkr/settings-prod.py +++ b/pyconkr/settings-prod.py @@ -4,6 +4,10 @@ DEBUG = False +ALLOWED_HOSTS += [ + "api.pycon.kr", +] + # RDS DATABASES = { "default": { From ed351bae14fb2fedb07d8b5a6e848ea82333cbe7 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sun, 5 Feb 2023 21:20:08 +0900 Subject: [PATCH 09/47] =?UTF-8?q?update:=20=EC=9B=B9=20=ED=8A=B8=EB=A6=AC?= =?UTF-8?q?=EA=B1=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_dev.yml | 1 + .github/workflows/deploy_on_prod.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/deploy_on_dev.yml b/.github/workflows/deploy_on_dev.yml index a844f2e..f9b4f5c 100644 --- a/.github/workflows/deploy_on_dev.yml +++ b/.github/workflows/deploy_on_dev.yml @@ -3,6 +3,7 @@ name: Deploy Django with zappa on DEV stage on: push: branches: [devdev] + workflow_dispatch: jobs: build: diff --git a/.github/workflows/deploy_on_prod.yml b/.github/workflows/deploy_on_prod.yml index 729a0e4..0d9518a 100644 --- a/.github/workflows/deploy_on_prod.yml +++ b/.github/workflows/deploy_on_prod.yml @@ -3,6 +3,8 @@ name: Deploy Django with zappa on PRODUCTION stage on: push: branches: [main] + workflow_dispatch: + jobs: build: runs-on: ubuntu-latest From 60d10388be6efc938a704389f7dc815437763977 Mon Sep 17 00:00:00 2001 From: oleveloper Date: Sun, 5 Feb 2023 22:36:01 +0900 Subject: [PATCH 10/47] Feature: Add sponser viewset --- pyconkr/urls.py | 4 +++- sponsor/routers.py | 10 ++++++++++ sponsor/serializers.py | 23 +++++++++++++++++++++++ sponsor/viewsets.py | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 sponsor/routers.py create mode 100644 sponsor/serializers.py create mode 100644 sponsor/viewsets.py diff --git a/pyconkr/urls.py b/pyconkr/urls.py index 8a42b4c..d00d408 100644 --- a/pyconkr/urls.py +++ b/pyconkr/urls.py @@ -16,9 +16,11 @@ from django.contrib import admin from django.urls import include, path +import sponsor.routers + urlpatterns = [ path("api-auth/", include("rest_framework.urls")), path("summernote/", include("django_summernote.urls")), path("admin/", admin.site.urls), - path("sponsors/", include("sponsor.urls")), + path("sponsors/", include(sponsor.routers.get_router().urls)), ] diff --git a/sponsor/routers.py b/sponsor/routers.py new file mode 100644 index 0000000..f3fcd9a --- /dev/null +++ b/sponsor/routers.py @@ -0,0 +1,10 @@ +from rest_framework.routers import DefaultRouter + +from sponsor.viewsets import * + + +def get_router(): + router = DefaultRouter() + router.register("", SponsorViewSet, basename="sponsor") + + return router diff --git a/sponsor/serializers.py b/sponsor/serializers.py new file mode 100644 index 0000000..7794cc9 --- /dev/null +++ b/sponsor/serializers.py @@ -0,0 +1,23 @@ +from rest_framework.serializers import ModelSerializer + +from sponsor.models import Sponsor + + +class SponsorSerializer(ModelSerializer): + class Meta: + model = Sponsor + fields = "__all__" + + +class SponsorListSerializer(ModelSerializer): + class Meta: + model = Sponsor + fields = [ + "name", + "level", + "desc", + "eng_desc", + "url", + "logo_image", + "id", + ] diff --git a/sponsor/viewsets.py b/sponsor/viewsets.py new file mode 100644 index 0000000..aa74907 --- /dev/null +++ b/sponsor/viewsets.py @@ -0,0 +1,37 @@ +from django.shortcuts import get_object_or_404 + +from rest_framework.viewsets import ReadOnlyModelViewSet +from rest_framework.permissions import IsAuthenticatedOrReadOnly +from rest_framework.response import Response + +from sponsor.serializers import ( + SponsorSerializer, + SponsorListSerializer, +) +from sponsor.models import Sponsor + + +class SponsorViewSet(ReadOnlyModelViewSet): + serializer_class = SponsorSerializer + permission_classes = [IsAuthenticatedOrReadOnly] # 로그인된 사용자에게만 허용 + + def get_queryset(self): + return Sponsor.objects.all() + + def list(self, request, *args, **kwargs): + queryset = Sponsor.objects.filter(accepted=True).order_by("name") + serializer = SponsorListSerializer(queryset, many=True) + return Response(serializer.data) + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(serializer.data) + + def retrieve(self, request, *args, **kwargs): + pk = kwargs["pk"] + sponsor_data = get_object_or_404(Sponsor, pk=pk) + + serializer = SponsorSerializer(sponsor_data) + return Response(serializer.data) From 5f8a6394a52702805326313008d8fb27dffa5435 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Mon, 6 Feb 2023 22:53:14 +0900 Subject: [PATCH 11/47] =?UTF-8?q?update:=20baseViewSet=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sponsor/viewsets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sponsor/viewsets.py b/sponsor/viewsets.py index aa74907..6267f22 100644 --- a/sponsor/viewsets.py +++ b/sponsor/viewsets.py @@ -1,6 +1,6 @@ from django.shortcuts import get_object_or_404 -from rest_framework.viewsets import ReadOnlyModelViewSet +from rest_framework.viewsets import ModelViewSet from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.response import Response @@ -11,7 +11,7 @@ from sponsor.models import Sponsor -class SponsorViewSet(ReadOnlyModelViewSet): +class SponsorViewSet(ModelViewSet): serializer_class = SponsorSerializer permission_classes = [IsAuthenticatedOrReadOnly] # 로그인된 사용자에게만 허용 From a4a6bb93f7b0dbb6f61c77276ff4481d144c5efb Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Tue, 7 Feb 2023 00:00:05 +0900 Subject: [PATCH 12/47] =?UTF-8?q?update:=20=EA=B6=8C=ED=95=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sponsor/permissions.py | 17 +++++++++++++++++ sponsor/viewsets.py | 25 +++++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 sponsor/permissions.py diff --git a/sponsor/permissions.py b/sponsor/permissions.py new file mode 100644 index 0000000..84a6aef --- /dev/null +++ b/sponsor/permissions.py @@ -0,0 +1,17 @@ +from rest_framework import permissions + +from sponsor.models import Sponsor + + +class IsOwnerOrReadOnly(permissions.BasePermission): + # https://stackoverflow.com/questions/72691826/djnago-rest-framework-how-to-allow-only-update-user-own-content-only + def has_object_permission(self, request, view, obj: Sponsor): + if request.method in permissions.SAFE_METHODS: + return True + + return obj.manager_id == request.user or obj.creator == request.user + + +class OwnerOnly(permissions.BasePermission): + def has_object_permission(self, request, view, obj: Sponsor): + return obj.manager_id == request.user or obj.creator == request.user diff --git a/sponsor/viewsets.py b/sponsor/viewsets.py index 6267f22..9b1b222 100644 --- a/sponsor/viewsets.py +++ b/sponsor/viewsets.py @@ -1,19 +1,16 @@ from django.shortcuts import get_object_or_404 - -from rest_framework.viewsets import ModelViewSet from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.response import Response +from rest_framework.viewsets import ModelViewSet -from sponsor.serializers import ( - SponsorSerializer, - SponsorListSerializer, -) from sponsor.models import Sponsor +from sponsor.permissions import IsOwnerOrReadOnly, OwnerOnly +from sponsor.serializers import SponsorListSerializer, SponsorSerializer class SponsorViewSet(ModelViewSet): serializer_class = SponsorSerializer - permission_classes = [IsAuthenticatedOrReadOnly] # 로그인된 사용자에게만 허용 + permission_classes = [IsOwnerOrReadOnly] # 본인 소유만 수정가능 def get_queryset(self): return Sponsor.objects.all() @@ -33,5 +30,17 @@ def retrieve(self, request, *args, **kwargs): pk = kwargs["pk"] sponsor_data = get_object_or_404(Sponsor, pk=pk) - serializer = SponsorSerializer(sponsor_data) + # 본인 소유인 경우는 모든 필드 + # 그렇지 않은 경우는 공개 가능한 필드만 응답 + serializer = ( + SponsorSerializer(sponsor_data) + if self.check_owner_permission(request, sponsor_data) + else SponsorListSerializer(sponsor_data) + ) + return Response(serializer.data) + + def check_owner_permission(self, request, sponsor_data: Sponsor): + return OwnerOnly.has_object_permission( + self=OwnerOnly, request=request, view=self, obj=sponsor_data + ) From 8dcde8f3ce6f991c98a4c365314ec7e6720e8d3e Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Tue, 7 Feb 2023 00:08:34 +0900 Subject: [PATCH 13/47] =?UTF-8?q?update:=20=ED=97=88=EC=9A=A9=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sponsor/viewsets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sponsor/viewsets.py b/sponsor/viewsets.py index 9b1b222..117fe5b 100644 --- a/sponsor/viewsets.py +++ b/sponsor/viewsets.py @@ -11,6 +11,7 @@ class SponsorViewSet(ModelViewSet): serializer_class = SponsorSerializer permission_classes = [IsOwnerOrReadOnly] # 본인 소유만 수정가능 + http_method_names = ["get", "post"] # 지금은 조회/등록만 가능 TODO: 추후 수정기능 추가 def get_queryset(self): return Sponsor.objects.all() From c01feb4bc71a7a5fe6f923ac2fc37a322e052848 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Tue, 7 Feb 2023 00:12:09 +0900 Subject: [PATCH 14/47] black --- sponsor/viewsets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sponsor/viewsets.py b/sponsor/viewsets.py index 117fe5b..2a5a40a 100644 --- a/sponsor/viewsets.py +++ b/sponsor/viewsets.py @@ -11,7 +11,7 @@ class SponsorViewSet(ModelViewSet): serializer_class = SponsorSerializer permission_classes = [IsOwnerOrReadOnly] # 본인 소유만 수정가능 - http_method_names = ["get", "post"] # 지금은 조회/등록만 가능 TODO: 추후 수정기능 추가 + http_method_names = ["get", "post"] # 지금은 조회/등록만 가능 TODO: 추후 수정기능 추가 def get_queryset(self): return Sponsor.objects.all() From 69ece23cc3f9cc622d834f4c79526077a9fd39bf Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Thu, 9 Feb 2023 22:29:24 +0900 Subject: [PATCH 15/47] =?UTF-8?q?update:=20sponsor=20migrations=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - sponsor/migrations/0001_initial.py | 198 +++++++++++++++++++++++++++++ sponsor/migrations/__init__.py | 0 3 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 sponsor/migrations/0001_initial.py create mode 100644 sponsor/migrations/__init__.py diff --git a/.gitignore b/.gitignore index f39d21a..8faa4d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ /.idea /db.sqlite3 -/sponsor/migrations diff --git a/sponsor/migrations/0001_initial.py b/sponsor/migrations/0001_initial.py new file mode 100644 index 0000000..da0e59d --- /dev/null +++ b/sponsor/migrations/0001_initial.py @@ -0,0 +1,198 @@ +# Generated by Django 4.1.5 on 2023-02-09 13:28 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import sorl.thumbnail.fields +import sponsor.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="SponsorLevel", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + blank=True, default="", help_text="후원 등급명", max_length=255 + ), + ), + ( + "desc", + models.TextField( + blank=True, + help_text="후원 혜택을 입력하면 될 거 같아요 :) 후원사가 등급을 정할 때 볼 문구입니다.", + null=True, + ), + ), + ("visible", models.BooleanField(default=True)), + ("price", models.IntegerField(default=0)), + ("limit", models.IntegerField(default=0, help_text="후원사 등급 별 구좌수")), + ("order", models.IntegerField(default=1)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ], + ), + migrations.CreateModel( + name="Sponsor", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + help_text="후원사의 이름입니다. 서비스나 회사 이름이 될 수 있습니다.", max_length=255 + ), + ), + ( + "desc", + models.TextField( + blank=True, + help_text="후원사 설명입니다. 이 설명은 국문 홈페이지에 게시됩니다.", + null=True, + ), + ), + ( + "eng_desc", + models.TextField( + blank=True, + help_text="후원사 영문 설명입니다. 이 설명은 영문 홈페이지에 게시됩니다.", + null=True, + ), + ), + ( + "manager_name", + models.CharField( + help_text="준비위원회와 후원과 관련된 논의를 진행할 담당자의 이름을 입력해주십시오.", + max_length=100, + ), + ), + ( + "manager_email", + models.CharField( + help_text="입력하신 메일로 후원과 관련된 안내 메일이나 문의를 보낼 예정입니다. 후원 담당자의 이메일 주소를 입력해주십시오.", + max_length=100, + ), + ), + ( + "business_registration_number", + models.CharField( + blank=True, + help_text="후원사 사업자 등록번호입니다. 세금 계산서 발급에 사용됩니다.", + max_length=100, + null=True, + ), + ), + ( + "business_registration_file", + models.FileField( + blank=True, + help_text="후원사 사업자 등록증 스캔본입니다. 세금 계산서 발급에 사용됩니다.", + null=True, + upload_to=sponsor.models.registration_file_upload_to, + ), + ), + ( + "url", + models.CharField( + blank=True, + help_text="파이콘 홈페이지에 공개되는 후원사 홈페이지 주소입니다.", + max_length=255, + null=True, + ), + ), + ( + "logo_image", + sorl.thumbnail.fields.ImageField( + blank=True, + help_text="홈페이지에 공개되는 후원사 로고 이미지입니다.", + null=True, + upload_to=sponsor.models.logo_image_upload_to, + ), + ), + ( + "submitted", + models.BooleanField( + default=False, + help_text="사용자가 제출했는지 여부를 저장합니다. 요청이 제출되면 준비위원회에서 검토하고 받아들일지를 결정합니다.", + ), + ), + ( + "accepted", + models.BooleanField( + default=False, + help_text="후원사 신청이 접수되었고, 입금 대기 상태인 경우 True로 설정됩니다.", + ), + ), + ( + "paid_at", + models.DateTimeField( + blank=True, + help_text="후원금이 입금된 일시입니다. 아직 입금되지 않았을 경우 None이 들어갑니다.", + null=True, + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "creator", + models.ForeignKey( + blank=True, + help_text="후원사를 등록한 유저", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="sponsor_creator", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "level", + models.ForeignKey( + blank=True, + help_text="후원을 원하시는 등급을 선택해주십시오. 모두 판매된 등급은 선택할 수 없습니다.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="sponsor.sponsorlevel", + ), + ), + ( + "manager_id", + models.ForeignKey( + blank=True, + help_text="후원사를 위한 추가 아이디", + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="sponsor_temp_id", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["paid_at", "id"], + }, + ), + ] diff --git a/sponsor/migrations/__init__.py b/sponsor/migrations/__init__.py new file mode 100644 index 0000000..e69de29 From 6f80b0e9c25bb3c138735147627079b4f0adf798 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Thu, 9 Feb 2023 23:36:21 +0900 Subject: [PATCH 16/47] =?UTF-8?q?update:=20sponsor=20migrations=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pull-request-merge-precondition.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request-merge-precondition.yml b/.github/workflows/pull-request-merge-precondition.yml index 7af1ccd..e41810c 100644 --- a/.github/workflows/pull-request-merge-precondition.yml +++ b/.github/workflows/pull-request-merge-precondition.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - uses: psf/black@stable with: - options: "--check --verbose" + options: "--check --verbose --exclude migrations" - uses: isort/isort-action@master with: From 6c31ab350effbaa3d236eead2faddb4c2a16bb34 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Thu, 9 Feb 2023 23:37:41 +0900 Subject: [PATCH 17/47] =?UTF-8?q?update:=20sponsor=20migrations=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sponsor/migrations/0001_initial.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sponsor/migrations/0001_initial.py b/sponsor/migrations/0001_initial.py index da0e59d..ac2af39 100644 --- a/sponsor/migrations/0001_initial.py +++ b/sponsor/migrations/0001_initial.py @@ -1,9 +1,10 @@ # Generated by Django 4.1.5 on 2023-02-09 13:28 -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import sorl.thumbnail.fields +from django.conf import settings +from django.db import migrations, models + import sponsor.models From 4ecc6e27f2b85d9086b927ed31cce8791c8ac4c8 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Thu, 9 Feb 2023 23:40:29 +0900 Subject: [PATCH 18/47] =?UTF-8?q?fix:=20migrations=EC=9D=80=20black=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=EC=97=90=EC=84=9C=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy_on_dev.yml | 2 +- .github/workflows/deploy_on_prod.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy_on_dev.yml b/.github/workflows/deploy_on_dev.yml index f9b4f5c..41ca0fb 100644 --- a/.github/workflows/deploy_on_dev.yml +++ b/.github/workflows/deploy_on_dev.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v2 - uses: psf/black@stable with: - options: "--check --verbose" + options: "--check --verbose --exclude migrations" - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 diff --git a/.github/workflows/deploy_on_prod.yml b/.github/workflows/deploy_on_prod.yml index 0d9518a..e2e66ab 100644 --- a/.github/workflows/deploy_on_prod.yml +++ b/.github/workflows/deploy_on_prod.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v2 - uses: psf/black@stable with: - options: "--check --verbose" + options: "--check --verbose --exclude migrations" - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 From 3c71bf168ce3fb61dd7d44dddf3b26e7680313b1 Mon Sep 17 00:00:00 2001 From: Bae KwonHan Date: Thu, 9 Feb 2023 09:43:23 -0500 Subject: [PATCH 19/47] update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 1cabdd3..4e40c38 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,9 @@ ## PR 이전에 * 컨벤션 유지를 위해 `black`과 `isort`를 적용하고 있습니다. + +## 개발 환경 +* mysql-client 설치 + * mac + * brew install mysql-client +* pip install -r requirements.txt From dbe88200b3311344ce43a063d349b814dc6dd977 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sat, 11 Feb 2023 11:27:16 +0900 Subject: [PATCH 20/47] =?UTF-8?q?update:=20=EB=AA=A8=EB=8D=B8=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=EB=A5=BC=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=EC=9D=98=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 삭제 대상 submitted accepted paid_at creator manager_id --- sponsor/serializers.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/sponsor/serializers.py b/sponsor/serializers.py index 7794cc9..c82b30c 100644 --- a/sponsor/serializers.py +++ b/sponsor/serializers.py @@ -6,7 +6,18 @@ class SponsorSerializer(ModelSerializer): class Meta: model = Sponsor - fields = "__all__" + fields = [ + "name", + "desc", # 국문/영문 모두 한 필드에 담아 제공하는 것으로 결정 + "manager_name", # 상세에만 포함되는 필드 + "manager_email", # 상세에만 포함되는 필드 + "business_registration_number", # 상세에만 포함되는 필드 + "business_registration_file", # 상세에만 포함되는 필드 + "url", + "logo_image", + "level", + "id", + ] class SponsorListSerializer(ModelSerializer): @@ -15,8 +26,7 @@ class Meta: fields = [ "name", "level", - "desc", - "eng_desc", + "desc", # 국문/영문 모두 한 필드에 담아 제공하는 것으로 결정 "url", "logo_image", "id", From 1eb4d2c51f04f412de9b929161b3c1da7c0a594e Mon Sep 17 00:00:00 2001 From: oleveloper Date: Sat, 11 Feb 2023 22:07:49 +0900 Subject: [PATCH 21/47] Feature: Add SponsorLevelViewSet --- sponsor/routers.py | 1 + sponsor/serializers.py | 21 ++++++++++++++++++++- sponsor/viewsets.py | 19 ++++++++++++++++--- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/sponsor/routers.py b/sponsor/routers.py index f3fcd9a..4b3aa89 100644 --- a/sponsor/routers.py +++ b/sponsor/routers.py @@ -5,6 +5,7 @@ def get_router(): router = DefaultRouter() + router.register("prospectus", SponsorLevelViewSet, basename="prospectus") router.register("", SponsorViewSet, basename="sponsor") return router diff --git a/sponsor/serializers.py b/sponsor/serializers.py index c82b30c..81d2081 100644 --- a/sponsor/serializers.py +++ b/sponsor/serializers.py @@ -1,6 +1,7 @@ +from rest_framework.fields import SerializerMethodField from rest_framework.serializers import ModelSerializer -from sponsor.models import Sponsor +from sponsor.models import Sponsor, SponsorLevel class SponsorSerializer(ModelSerializer): @@ -31,3 +32,21 @@ class Meta: "logo_image", "id", ] + + +class SponsorLevelSerializer(ModelSerializer): + remaining = SerializerMethodField() + + class Meta: + model = SponsorLevel + fields = [ + "name", + "price", + "desc", + "limit", + "remaining", + "id", + ] + + def get_remaining(self, obj): + return obj.current_remaining_number diff --git a/sponsor/viewsets.py b/sponsor/viewsets.py index 2a5a40a..63018d7 100644 --- a/sponsor/viewsets.py +++ b/sponsor/viewsets.py @@ -1,11 +1,10 @@ from django.shortcuts import get_object_or_404 -from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet -from sponsor.models import Sponsor +from sponsor.models import Sponsor, SponsorLevel from sponsor.permissions import IsOwnerOrReadOnly, OwnerOnly -from sponsor.serializers import SponsorListSerializer, SponsorSerializer +from sponsor.serializers import SponsorListSerializer, SponsorSerializer, SponsorLevelSerializer class SponsorViewSet(ModelViewSet): @@ -45,3 +44,17 @@ def check_owner_permission(self, request, sponsor_data: Sponsor): return OwnerOnly.has_object_permission( self=OwnerOnly, request=request, view=self, obj=sponsor_data ) + + +class SponsorLevelViewSet(ModelViewSet): + serializer_class = SponsorLevelSerializer + http_method_names = ["get"] + + def get_queryset(self): + return SponsorLevel.objects.all() + + def list(self, request, *args, **kwargs): + queryset = SponsorLevel.objects.all().order_by("-price") + serializer = SponsorLevelSerializer(queryset, many=True) + + return Response(serializer.data) From 4ec93c5f0b6718fbb36068127e7ab666d821bd4d Mon Sep 17 00:00:00 2001 From: oleveloper Date: Sat, 11 Feb 2023 23:37:29 +0900 Subject: [PATCH 22/47] Feature: Add feature to view remaining accounts - Prospectus API does not show the remaining accounts --- sponsor/routers.py | 1 + sponsor/serializers.py | 13 ++++++++++++- sponsor/viewsets.py | 17 ++++++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/sponsor/routers.py b/sponsor/routers.py index 4b3aa89..00d6291 100644 --- a/sponsor/routers.py +++ b/sponsor/routers.py @@ -5,6 +5,7 @@ def get_router(): router = DefaultRouter() + router.register("remaining", SponsorRemainingAccountViewSet, basename="remaining") router.register("prospectus", SponsorLevelViewSet, basename="prospectus") router.register("", SponsorViewSet, basename="sponsor") diff --git a/sponsor/serializers.py b/sponsor/serializers.py index 81d2081..865018a 100644 --- a/sponsor/serializers.py +++ b/sponsor/serializers.py @@ -35,6 +35,18 @@ class Meta: class SponsorLevelSerializer(ModelSerializer): + class Meta: + model = SponsorLevel + fields = [ + "name", + "price", + "desc", + "limit", + "id", + ] # TODO: Add fields to show + + +class SponsorRemainingAccountSerializer(ModelSerializer): remaining = SerializerMethodField() class Meta: @@ -42,7 +54,6 @@ class Meta: fields = [ "name", "price", - "desc", "limit", "remaining", "id", diff --git a/sponsor/viewsets.py b/sponsor/viewsets.py index 63018d7..5c5e720 100644 --- a/sponsor/viewsets.py +++ b/sponsor/viewsets.py @@ -4,7 +4,8 @@ from sponsor.models import Sponsor, SponsorLevel from sponsor.permissions import IsOwnerOrReadOnly, OwnerOnly -from sponsor.serializers import SponsorListSerializer, SponsorSerializer, SponsorLevelSerializer +from sponsor.serializers import ( + SponsorListSerializer, SponsorSerializer, SponsorLevelSerializer, SponsorRemainingAccountSerializer) class SponsorViewSet(ModelViewSet): @@ -58,3 +59,17 @@ def list(self, request, *args, **kwargs): serializer = SponsorLevelSerializer(queryset, many=True) return Response(serializer.data) + + +class SponsorRemainingAccountViewSet(ModelViewSet): + serializer_class = SponsorLevelSerializer + http_method_names = ["get"] + + def get_queryset(self): + return SponsorLevel.objects.all() + + def list(self, request, *args, **kwargs): + queryset = SponsorLevel.objects.all().order_by("-price") + serializer = SponsorRemainingAccountSerializer(queryset, many=True) + + return Response(serializer.data) From 4cca2769a53bad407771d265e0d534e54046560b Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sun, 12 Feb 2023 00:24:10 +0900 Subject: [PATCH 23/47] =?UTF-8?q?update:=20=ED=9B=84=EC=9B=90=EC=82=AC=20?= =?UTF-8?q?=EB=AA=A8=EC=A7=91=EC=97=90=20=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._book_file_sponsor_manager_tel_and_more.py | 44 +++++++++++++++++++ sponsor/models.py | 14 ++++++ sponsor/serializers.py | 2 + 3 files changed, 60 insertions(+) create mode 100644 sponsor/migrations/0002_sponsor_bank_book_file_sponsor_manager_tel_and_more.py diff --git a/sponsor/migrations/0002_sponsor_bank_book_file_sponsor_manager_tel_and_more.py b/sponsor/migrations/0002_sponsor_bank_book_file_sponsor_manager_tel_and_more.py new file mode 100644 index 0000000..c33b9a7 --- /dev/null +++ b/sponsor/migrations/0002_sponsor_bank_book_file_sponsor_manager_tel_and_more.py @@ -0,0 +1,44 @@ +# Generated by Django 4.1.5 on 2023-02-11 15:16 + +from django.db import migrations, models +import sponsor.models + + +class Migration(migrations.Migration): + + dependencies = [ + ("sponsor", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="sponsor", + name="bank_book_file", + field=models.FileField( + blank=True, + help_text="후원사 사업자 등록증 스캔본입니다. 세금 계산서 발급에 사용됩니다.", + null=True, + upload_to=sponsor.models.bank_book_file_upload_to, + ), + ), + migrations.AddField( + model_name="sponsor", + name="manager_tel", + field=models.CharField( + default="", + help_text="메일에 회신이 없거나, 긴급한 건의 경우, 문자나 유선으로 안내드릴 수 있습니다. 후원 담당자의 유선 연락처를 입력해주십시오.", + max_length=20, + ), + ), + migrations.AlterField( + model_name="sponsor", + name="business_registration_file", + field=models.FileField( + blank=True, + default=None, + help_text="후원사 사업자 등록증 스캔본입니다. 세금 계산서 발급에 사용됩니다.", + null=True, + upload_to=sponsor.models.registration_file_upload_to, + ), + ), + ] diff --git a/sponsor/models.py b/sponsor/models.py index 63a8ed2..c574efa 100644 --- a/sponsor/models.py +++ b/sponsor/models.py @@ -49,6 +49,8 @@ def __str__(self): def registration_file_upload_to(instance, filename): return f"sponsor/business_registration/{instance.id}/{filename}" +def bank_book_file_upload_to(instance, filename): + return f"sponsor/bank_book/{instance.id}/{filename}" def logo_image_upload_to(instance, filename): return f"sponsor/logo/{instance.id}/{filename}" @@ -89,6 +91,11 @@ class Meta: max_length=100, help_text="입력하신 메일로 후원과 관련된 안내 메일이나 문의를 보낼 예정입니다. 후원 담당자의 이메일 주소를 입력해주십시오.", ) + manager_tel = models.CharField( + max_length=20, + default="", + help_text="메일에 회신이 없거나, 긴급한 건의 경우, 문자나 유선으로 안내드릴 수 있습니다. 후원 담당자의 유선 연락처를 입력해주십시오.", + ) manager_id = models.ForeignKey( User, null=True, @@ -106,9 +113,16 @@ class Meta: business_registration_file = models.FileField( null=True, blank=True, + default=None, upload_to=registration_file_upload_to, help_text="후원사 사업자 등록증 스캔본입니다. 세금 계산서 발급에 사용됩니다.", ) + bank_book_file = models.FileField( + null=True, + blank=True, + upload_to=bank_book_file_upload_to, + help_text="후원사 사업자 등록증 스캔본입니다. 세금 계산서 발급에 사용됩니다.", + ) url = models.CharField( max_length=255, null=True, diff --git a/sponsor/serializers.py b/sponsor/serializers.py index c82b30c..9e443f5 100644 --- a/sponsor/serializers.py +++ b/sponsor/serializers.py @@ -11,8 +11,10 @@ class Meta: "desc", # 국문/영문 모두 한 필드에 담아 제공하는 것으로 결정 "manager_name", # 상세에만 포함되는 필드 "manager_email", # 상세에만 포함되는 필드 + "manager_tel", # 상세에만 포함되는 필드 "business_registration_number", # 상세에만 포함되는 필드 "business_registration_file", # 상세에만 포함되는 필드 + "bank_book_file", # 상세에만 포함되는 필드 "url", "logo_image", "level", From b0b3114f970d125f870bce5d15fed07a52136dd0 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sun, 12 Feb 2023 00:32:31 +0900 Subject: [PATCH 24/47] =?UTF-8?q?update:=20=ED=9B=84=EC=9B=90=EC=82=AC=20?= =?UTF-8?q?=EB=AA=A8=EC=A7=91=EC=97=90=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=95=84=EB=93=9C=20=EB=B9=84=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sponsor/serializers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sponsor/serializers.py b/sponsor/serializers.py index 9e443f5..45b5795 100644 --- a/sponsor/serializers.py +++ b/sponsor/serializers.py @@ -8,7 +8,7 @@ class Meta: model = Sponsor fields = [ "name", - "desc", # 국문/영문 모두 한 필드에 담아 제공하는 것으로 결정 + # "desc", # 국문/영문 모두 한 필드에 담아 제공하는 것으로 결정 # TODO: 상세 페이지 오픈 후 활성화 "manager_name", # 상세에만 포함되는 필드 "manager_email", # 상세에만 포함되는 필드 "manager_tel", # 상세에만 포함되는 필드 @@ -28,7 +28,6 @@ class Meta: fields = [ "name", "level", - "desc", # 국문/영문 모두 한 필드에 담아 제공하는 것으로 결정 "url", "logo_image", "id", From 6980d3c67d3cdac709f58764226b0185469ec599 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sun, 12 Feb 2023 00:35:39 +0900 Subject: [PATCH 25/47] lint --- .../0002_sponsor_bank_book_file_sponsor_manager_tel_and_more.py | 1 + sponsor/models.py | 2 ++ sponsor/serializers.py | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/sponsor/migrations/0002_sponsor_bank_book_file_sponsor_manager_tel_and_more.py b/sponsor/migrations/0002_sponsor_bank_book_file_sponsor_manager_tel_and_more.py index c33b9a7..475aeb2 100644 --- a/sponsor/migrations/0002_sponsor_bank_book_file_sponsor_manager_tel_and_more.py +++ b/sponsor/migrations/0002_sponsor_bank_book_file_sponsor_manager_tel_and_more.py @@ -1,6 +1,7 @@ # Generated by Django 4.1.5 on 2023-02-11 15:16 from django.db import migrations, models + import sponsor.models diff --git a/sponsor/models.py b/sponsor/models.py index c574efa..f1ecc80 100644 --- a/sponsor/models.py +++ b/sponsor/models.py @@ -49,9 +49,11 @@ def __str__(self): def registration_file_upload_to(instance, filename): return f"sponsor/business_registration/{instance.id}/{filename}" + def bank_book_file_upload_to(instance, filename): return f"sponsor/bank_book/{instance.id}/{filename}" + def logo_image_upload_to(instance, filename): return f"sponsor/logo/{instance.id}/{filename}" diff --git a/sponsor/serializers.py b/sponsor/serializers.py index 45b5795..d5d9f03 100644 --- a/sponsor/serializers.py +++ b/sponsor/serializers.py @@ -14,7 +14,7 @@ class Meta: "manager_tel", # 상세에만 포함되는 필드 "business_registration_number", # 상세에만 포함되는 필드 "business_registration_file", # 상세에만 포함되는 필드 - "bank_book_file", # 상세에만 포함되는 필드 + "bank_book_file", # 상세에만 포함되는 필드 "url", "logo_image", "level", From 4fc33f36179eec75b3b4b30f102fdf16fa21bff8 Mon Sep 17 00:00:00 2001 From: oleveloper Date: Sun, 12 Feb 2023 10:07:03 +0900 Subject: [PATCH 26/47] Fix: Apply staticmethod to get_remaining method --- sponsor/serializers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sponsor/serializers.py b/sponsor/serializers.py index 865018a..57c9c94 100644 --- a/sponsor/serializers.py +++ b/sponsor/serializers.py @@ -43,7 +43,7 @@ class Meta: "desc", "limit", "id", - ] # TODO: Add fields to show + ] # TODO: Add fields to show class SponsorRemainingAccountSerializer(ModelSerializer): @@ -59,5 +59,6 @@ class Meta: "id", ] - def get_remaining(self, obj): + @staticmethod + def get_remaining(obj): return obj.current_remaining_number From 0f9d63c454bf301a13675974970358dea49b7236 Mon Sep 17 00:00:00 2001 From: oleveloper Date: Sun, 12 Feb 2023 10:33:38 +0900 Subject: [PATCH 27/47] Fix: Apply typing --- sponsor/viewsets.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sponsor/viewsets.py b/sponsor/viewsets.py index 5c5e720..ee6d021 100644 --- a/sponsor/viewsets.py +++ b/sponsor/viewsets.py @@ -1,3 +1,5 @@ +from typing import Type + from django.shortcuts import get_object_or_404 from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet @@ -5,7 +7,11 @@ from sponsor.models import Sponsor, SponsorLevel from sponsor.permissions import IsOwnerOrReadOnly, OwnerOnly from sponsor.serializers import ( - SponsorListSerializer, SponsorSerializer, SponsorLevelSerializer, SponsorRemainingAccountSerializer) + SponsorListSerializer, + SponsorSerializer, + SponsorLevelSerializer, + SponsorRemainingAccountSerializer, +) class SponsorViewSet(ModelViewSet): @@ -43,7 +49,7 @@ def retrieve(self, request, *args, **kwargs): def check_owner_permission(self, request, sponsor_data: Sponsor): return OwnerOnly.has_object_permission( - self=OwnerOnly, request=request, view=self, obj=sponsor_data + self=Type[OwnerOnly], request=request, view=self, obj=sponsor_data ) From e3461daf505a4e7f1517d9812a9a6bbabad5f53c Mon Sep 17 00:00:00 2001 From: oleveloper Date: Sun, 12 Feb 2023 10:36:53 +0900 Subject: [PATCH 28/47] Style: Apply isort to viewsets --- sponsor/viewsets.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sponsor/viewsets.py b/sponsor/viewsets.py index ee6d021..64d28b8 100644 --- a/sponsor/viewsets.py +++ b/sponsor/viewsets.py @@ -6,12 +6,9 @@ from sponsor.models import Sponsor, SponsorLevel from sponsor.permissions import IsOwnerOrReadOnly, OwnerOnly -from sponsor.serializers import ( - SponsorListSerializer, - SponsorSerializer, - SponsorLevelSerializer, - SponsorRemainingAccountSerializer, -) +from sponsor.serializers import (SponsorLevelSerializer, SponsorListSerializer, + SponsorRemainingAccountSerializer, + SponsorSerializer) class SponsorViewSet(ModelViewSet): From 058df8f88460dbc3a68808743e6c314882112c44 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sun, 12 Feb 2023 15:37:59 +0900 Subject: [PATCH 29/47] =?UTF-8?q?lint=20=EC=88=98=EC=A0=95=20(black)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sponsor/viewsets.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sponsor/viewsets.py b/sponsor/viewsets.py index 64d28b8..5871824 100644 --- a/sponsor/viewsets.py +++ b/sponsor/viewsets.py @@ -6,9 +6,12 @@ from sponsor.models import Sponsor, SponsorLevel from sponsor.permissions import IsOwnerOrReadOnly, OwnerOnly -from sponsor.serializers import (SponsorLevelSerializer, SponsorListSerializer, - SponsorRemainingAccountSerializer, - SponsorSerializer) +from sponsor.serializers import ( + SponsorLevelSerializer, + SponsorListSerializer, + SponsorRemainingAccountSerializer, + SponsorSerializer, +) class SponsorViewSet(ModelViewSet): From 83d671390340e638b1da7d1b2793fc3f4655cc5a Mon Sep 17 00:00:00 2001 From: Bae KwonHan Date: Wed, 15 Feb 2023 15:50:34 -0500 Subject: [PATCH 30/47] init localtesting with `pytest-django` --- .../pull-request-merge-precondition.yml | 27 ++++++++++++++++++- README.md | 8 ++++++ conftest.py | 10 +++++++ manage.py | 0 pyconkr/settings-localtest.py | 21 +++++++++++++++ pytest.ini | 5 ++++ requirements-dev.txt | 4 +++ sponsor/tests.py | 25 ++++++++++++++++- 8 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 conftest.py mode change 100644 => 100755 manage.py create mode 100644 pyconkr/settings-localtest.py create mode 100644 pytest.ini create mode 100644 requirements-dev.txt diff --git a/.github/workflows/pull-request-merge-precondition.yml b/.github/workflows/pull-request-merge-precondition.yml index e41810c..c47999d 100644 --- a/.github/workflows/pull-request-merge-precondition.yml +++ b/.github/workflows/pull-request-merge-precondition.yml @@ -3,6 +3,10 @@ name: Pull Request Merge Precondition on: pull_request: +permissions: + contents: read + pull-requests: write + jobs: build: runs-on: ubuntu-latest @@ -11,7 +15,11 @@ jobs: python-version: [3.8] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + with: + persist-credentials: true # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token + fetch-depth: 0 # otherwise, you will failed to push refs to dest repo + - uses: psf/black@stable with: options: "--check --verbose --exclude migrations" @@ -20,3 +28,20 @@ jobs: with: configuration: "--check-only --diff --profile black" requirementsFiles: "requirements.txt" + + - name: install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -r requirements-dev.txt + pip install pytest-cov + + - name: run pytest + run: | + pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=pyconkr ./ | tee pytest-coverage.txt + + #- name: Pytest coverage comment + # uses: MishaKav/pytest-coverage-comment@main + # with: + # pytest-coverage-path: ./pytest-coverage.txt + # junitxml-path: ./pytest.xml diff --git a/README.md b/README.md index 4e40c38..25ab02b 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,11 @@ * mac * brew install mysql-client * pip install -r requirements.txt + +## how to run localtesting ( sqlite3 based ) +``` +# to setup pytest and requirements +pip install -r requirements-dev.txt +# run test +pytest +``` diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..98a6f21 --- /dev/null +++ b/conftest.py @@ -0,0 +1,10 @@ +import pytest + + +# TODO +# https://djangostars.com/blog/django-pytest-testing/#header17 +@pytest.fixture +def api_client(): + from rest_framework.test import APIClient + + return APIClient() diff --git a/manage.py b/manage.py old mode 100644 new mode 100755 diff --git a/pyconkr/settings-localtest.py b/pyconkr/settings-localtest.py new file mode 100644 index 0000000..1b7ccd6 --- /dev/null +++ b/pyconkr/settings-localtest.py @@ -0,0 +1,21 @@ +import os + +from pyconkr.settings import * + +DEBUG = True + +ALLOWED_HOSTS += [ + "*", +] + + +# RDS +DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}} + +# django-storages: TODO fix to in memory? +del MEDIA_ROOT +DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" +STATICFILES_STORAGE = "storages.backends.s3boto3.S3StaticStorage" +AWS_S3_ACCESS_KEY_ID = os.getenv("AWS_S3_ACCESS_KEY_ID") +AWS_S3_SECRET_ACCESS_KEY = os.getenv("AWS_S3_SECRET_ACCESS_KEY") +AWS_STORAGE_BUCKET_NAME = "pyconkr-api-v2-static-dev" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..3a662ff --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +DJANGO_SETTINGS_MODULE = pyconkr.settings-localtest +# -- recommended but optional: +python_files = tests.py test_*.py *_tests.py +addopts = --no-migrations diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..46f4e2f --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,4 @@ +black +isort +pytest +pytest-django diff --git a/sponsor/tests.py b/sponsor/tests.py index 7ce503c..d2f24a3 100644 --- a/sponsor/tests.py +++ b/sponsor/tests.py @@ -1,3 +1,26 @@ -from django.test import TestCase +import pytest +from django.contrib.auth import get_user_model + +from sponsor.models import SponsorLevel + +pytestmark = pytest.mark.django_db + +UserModel = get_user_model() + + +@pytest.mark.django_db +class TestSponsorLevelModel: + pytestmark = pytest.mark.django_db + + def test_sponsor_level_creation_success(self): + assert SponsorLevel.objects.count() == 0 + SponsorLevel.objects.create( + name="test", + desc="test desc", + visible=True, + limit=1, + ) + assert SponsorLevel.objects.count() != 0 + # Create your tests here. From 092955b591fd827a8377b76d75fdef488b5f226a Mon Sep 17 00:00:00 2001 From: Bae KwonHan Date: Thu, 16 Feb 2023 20:12:32 -0500 Subject: [PATCH 31/47] fix requirements-dev.txt install requirements.txt --- .github/workflows/pull-request-merge-precondition.yml | 1 - requirements-dev.txt | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request-merge-precondition.yml b/.github/workflows/pull-request-merge-precondition.yml index c47999d..ddad109 100644 --- a/.github/workflows/pull-request-merge-precondition.yml +++ b/.github/workflows/pull-request-merge-precondition.yml @@ -32,7 +32,6 @@ jobs: - name: install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements.txt pip install -r requirements-dev.txt pip install pytest-cov diff --git a/requirements-dev.txt b/requirements-dev.txt index 46f4e2f..112c1ef 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,4 @@ +-r requirements.txt black isort pytest From 3a1e185d45af6c8adaa9c857d67120a5d1c57026 Mon Sep 17 00:00:00 2001 From: Bae KwonHan Date: Thu, 16 Feb 2023 20:19:09 -0500 Subject: [PATCH 32/47] following github public gitignore for python - https://github.com/github/gitignore/blob/main/Python.gitignore --- .gitignore | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8faa4d2..68bc17f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,160 @@ -/.idea -/db.sqlite3 +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# 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 + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ From 29e0cb3e71ef1a6bca0ae575ab09460ec269545f Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Thu, 23 Feb 2023 00:44:21 +0900 Subject: [PATCH 33/47] =?UTF-8?q?lint=20=EC=88=98=EC=A0=95=20(black)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/settings.py | 4 ++++ requirements.txt | 1 + 2 files changed, 5 insertions(+) diff --git a/pyconkr/settings.py b/pyconkr/settings.py index c26de81..0d441ee 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -130,3 +130,7 @@ # django-summernote MEDIA_URL = "/media/" MEDIA_ROOT = os.path.join(BASE_DIR, "media/") + +CONSTANCE_CONFIG = { + 'SPONSOR_NOTI_CHANNEL': ("", "후원사 변동사항에 대한 알림을 보낼 채널",), +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index cd79985..b644d1d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ tzdata==2022.7 sorl-thumbnail==12.9.0 django-summernote==0.8.20.0 Pillow==9.4.0 +django-constance==2.9.1 From 7668889fdf0449f18db066bb4945dde6422d49d8 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Thu, 23 Feb 2023 00:48:01 +0900 Subject: [PATCH 34/47] =?UTF-8?q?.gitignore=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 68bc17f..2dc53ca 100644 --- a/.gitignore +++ b/.gitignore @@ -157,4 +157,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ From 591c87a682ff552ff2437b87faaae78e96e98fb7 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Thu, 23 Feb 2023 00:48:18 +0900 Subject: [PATCH 35/47] =?UTF-8?q?constance=20=EA=B4=80=EB=A6=AC=EB=8C=80?= =?UTF-8?q?=EC=83=81=EC=97=90=20SLACK=5FSECRET=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyconkr/settings.py b/pyconkr/settings.py index 0d441ee..8d74963 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -132,5 +132,6 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "media/") CONSTANCE_CONFIG = { - 'SPONSOR_NOTI_CHANNEL': ("", "후원사 변동사항에 대한 알림을 보낼 채널",), + "SLACK_SECRET": ("", "Slack 알림 전송에 사용할 Secret",), + "SPONSOR_NOTI_CHANNEL": ("", "후원사 변동사항에 대한 알림을 보낼 채널",), } \ No newline at end of file From 64f6ba0f70245428c847493145665023f414e45e Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Thu, 23 Feb 2023 00:48:53 +0900 Subject: [PATCH 36/47] lint --- pyconkr/settings.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pyconkr/settings.py b/pyconkr/settings.py index 8d74963..5a20ef2 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -132,6 +132,12 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "media/") CONSTANCE_CONFIG = { - "SLACK_SECRET": ("", "Slack 알림 전송에 사용할 Secret",), - "SPONSOR_NOTI_CHANNEL": ("", "후원사 변동사항에 대한 알림을 보낼 채널",), -} \ No newline at end of file + "SLACK_SECRET": ( + "", + "Slack 알림 전송에 사용할 Secret", + ), + "SPONSOR_NOTI_CHANNEL": ( + "", + "후원사 변동사항에 대한 알림을 보낼 채널", + ), +} From 699374c1bbfaca3f35b7f5635364fc2d8be931bc Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Thu, 23 Feb 2023 00:54:08 +0900 Subject: [PATCH 37/47] =?UTF-8?q?update:=20add-on=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyconkr/settings.py b/pyconkr/settings.py index 5a20ef2..8ec72fd 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -40,6 +40,7 @@ # add-on "rest_framework", "django_summernote", + "constance", # apps "sponsor", ] From eb08c61a8128edbf13c6a53e87effc08f446d088 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Thu, 23 Feb 2023 00:57:24 +0900 Subject: [PATCH 38/47] =?UTF-8?q?update:=20constance=EC=9D=98=20backend?= =?UTF-8?q?=EB=A1=9C=20DB=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/settings.py | 3 +++ requirements.txt | 1 + 2 files changed, 4 insertions(+) diff --git a/pyconkr/settings.py b/pyconkr/settings.py index 8ec72fd..c3351fa 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -41,6 +41,7 @@ "rest_framework", "django_summernote", "constance", + "constance.backends.database", # apps "sponsor", ] @@ -132,6 +133,8 @@ MEDIA_URL = "/media/" MEDIA_ROOT = os.path.join(BASE_DIR, "media/") +# django-constance +CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' CONSTANCE_CONFIG = { "SLACK_SECRET": ( "", diff --git a/requirements.txt b/requirements.txt index b644d1d..895cfb4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,4 @@ sorl-thumbnail==12.9.0 django-summernote==0.8.20.0 Pillow==9.4.0 django-constance==2.9.1 +django-picklefield==3.1 From 4818b42b7918b56359a5c189e5edb89dc86e989b Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Thu, 23 Feb 2023 21:14:59 +0900 Subject: [PATCH 39/47] =?UTF-8?q?update:=20.gitignore=20=EB=A7=88=EC=A7=80?= =?UTF-8?q?=EB=A7=89=20=EB=B9=88=20=EC=A4=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 커밋 생성을 위해 급조된 수정 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f39d21a..ec01c18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.idea /db.sqlite3 /sponsor/migrations + From f4d14f0fea0daa6d3a2ff5636539e578d45713d7 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Fri, 24 Feb 2023 21:55:46 +0900 Subject: [PATCH 40/47] lint --- pyconkr/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyconkr/settings.py b/pyconkr/settings.py index c3351fa..2b193e8 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -134,7 +134,7 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "media/") # django-constance -CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' +CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend" CONSTANCE_CONFIG = { "SLACK_SECRET": ( "", From 2ee8cebfb10427d723bb991bae96f00a634a661e Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sat, 25 Feb 2023 01:58:51 +0900 Subject: [PATCH 41/47] =?UTF-8?q?update:=20=EA=B5=AC=EC=A2=8C=EC=88=98=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sponsor/validators.py | 16 ++++++++++++++++ sponsor/viewsets.py | 9 ++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 sponsor/validators.py diff --git a/sponsor/validators.py b/sponsor/validators.py new file mode 100644 index 0000000..0f79877 --- /dev/null +++ b/sponsor/validators.py @@ -0,0 +1,16 @@ +from collections import OrderedDict + +from sponsor.models import Sponsor + + +class SponsorValidater: + def assert_create(self, sponsor: OrderedDict): + target = [ + self.check_remain_slot(sponsor) + ] + + def check_remain_slot(self, sponsor: OrderedDict): + target_level = sponsor.get("level") + + if target_level.limit <= len(Sponsor.objects.filter(level=target_level, accepted=True)): + raise RuntimeError("ERROR: 남은 슬롯 없음") diff --git a/sponsor/viewsets.py b/sponsor/viewsets.py index 5871824..5cbceaf 100644 --- a/sponsor/viewsets.py +++ b/sponsor/viewsets.py @@ -1,5 +1,6 @@ from typing import Type +from django.db.transaction import atomic from django.shortcuts import get_object_or_404 from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet @@ -12,12 +13,14 @@ SponsorRemainingAccountSerializer, SponsorSerializer, ) +from sponsor.validators import SponsorValidater class SponsorViewSet(ModelViewSet): serializer_class = SponsorSerializer permission_classes = [IsOwnerOrReadOnly] # 본인 소유만 수정가능 http_method_names = ["get", "post"] # 지금은 조회/등록만 가능 TODO: 추후 수정기능 추가 + validator = SponsorValidater() def get_queryset(self): return Sponsor.objects.all() @@ -27,10 +30,14 @@ def list(self, request, *args, **kwargs): serializer = SponsorListSerializer(queryset, many=True) return Response(serializer.data) + @atomic def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - serializer.save() + self.validator.assert_create(serializer.validated_data) + + new_sponsor = serializer.save() + return Response(serializer.data) def retrieve(self, request, *args, **kwargs): From 5a571a8a245ef67e327b310b117b3ff11973dc9e Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sat, 25 Feb 2023 02:01:30 +0900 Subject: [PATCH 42/47] lint --- sponsor/validators.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sponsor/validators.py b/sponsor/validators.py index 0f79877..c7ba366 100644 --- a/sponsor/validators.py +++ b/sponsor/validators.py @@ -5,12 +5,12 @@ class SponsorValidater: def assert_create(self, sponsor: OrderedDict): - target = [ - self.check_remain_slot(sponsor) - ] + target = [self.check_remain_slot(sponsor)] def check_remain_slot(self, sponsor: OrderedDict): target_level = sponsor.get("level") - if target_level.limit <= len(Sponsor.objects.filter(level=target_level, accepted=True)): + if target_level.limit <= len( + Sponsor.objects.filter(level=target_level, accepted=True) + ): raise RuntimeError("ERROR: 남은 슬롯 없음") From 3ffb90ecc2589016bac1e0dabc05a85b025da89c Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sat, 25 Feb 2023 02:23:16 +0900 Subject: [PATCH 43/47] =?UTF-8?q?update:=20swagger=20(drf-spectacular)=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/settings.py | 24 ++++++++++++++++++++++++ pyconkr/urls.py | 23 +++++++++++++++++++++++ requirements.txt | 1 + 3 files changed, 48 insertions(+) diff --git a/pyconkr/settings.py b/pyconkr/settings.py index 2b193e8..d2c7ff1 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -44,6 +44,8 @@ "constance.backends.database", # apps "sponsor", + # swagger + "drf_spectacular", ] MIDDLEWARE = [ @@ -145,3 +147,25 @@ "후원사 변동사항에 대한 알림을 보낼 채널", ), } + +# drf-spectacular +REST_FRAMEWORK = { + # YOUR SETTINGS + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", +} + +SPECTACULAR_SETTINGS = { + "TITLE": "pyconkr-api-v2", + "DESCRIPTION": "파이콘 한국 웹서비스용 API (2023 ~ )", + "VERSION": "1.0.0", + "SERVE_INCLUDE_SCHEMA": True, + # available SwaggerUI configuration parameters + # https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/ + "SWAGGER_UI_SETTINGS": { + "deepLinking": True, + "persistAuthorization": True, + "displayOperationId": True, + }, + # available SwaggerUI versions: https://github.com/swagger-api/swagger-ui/releases + "SWAGGER_UI_DIST": "//unpkg.com/swagger-ui-dist@3.35.1", +} diff --git a/pyconkr/urls.py b/pyconkr/urls.py index d00d408..34d9fb0 100644 --- a/pyconkr/urls.py +++ b/pyconkr/urls.py @@ -16,7 +16,14 @@ from django.contrib import admin from django.urls import include, path +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularRedocView, + SpectacularSwaggerView, +) + import sponsor.routers +import pyconkr.settings as settings urlpatterns = [ path("api-auth/", include("rest_framework.urls")), @@ -24,3 +31,19 @@ path("admin/", admin.site.urls), path("sponsors/", include(sponsor.routers.get_router().urls)), ] + +if settings.DEBUG is True: + urlpatterns += [ + path("api/schema/", SpectacularAPIView.as_view(), name="schema"), + # Optional UI: + path( + "api/schema/swagger-ui/", + SpectacularSwaggerView.as_view(url_name="schema"), + name="swagger-ui", + ), + path( + "api/schema/redoc/", + SpectacularRedocView.as_view(url_name="schema"), + name="redoc", + ), + ] diff --git a/requirements.txt b/requirements.txt index 895cfb4..523fdff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ django-summernote==0.8.20.0 Pillow==9.4.0 django-constance==2.9.1 django-picklefield==3.1 +drf-spectacular==0.25.1 From 45535b0508274e82204949a8116dff01e395b68a Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sat, 25 Feb 2023 02:29:16 +0900 Subject: [PATCH 44/47] =?UTF-8?q?update:=20=ED=99=9C=EC=84=B1=ED=99=94=20?= =?UTF-8?q?=EB=90=9C=20settings=20=EA=B0=92=EC=9D=84=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyconkr/urls.py b/pyconkr/urls.py index 34d9fb0..f9be88c 100644 --- a/pyconkr/urls.py +++ b/pyconkr/urls.py @@ -15,6 +15,7 @@ """ from django.contrib import admin from django.urls import include, path +from django.conf import settings from drf_spectacular.views import ( SpectacularAPIView, @@ -23,7 +24,6 @@ ) import sponsor.routers -import pyconkr.settings as settings urlpatterns = [ path("api-auth/", include("rest_framework.urls")), From 0f503f5f1f30da1b864eb92429795363e47bd379 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sat, 25 Feb 2023 02:31:31 +0900 Subject: [PATCH 45/47] isort --- pyconkr/urls.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pyconkr/urls.py b/pyconkr/urls.py index f9be88c..cb6db2c 100644 --- a/pyconkr/urls.py +++ b/pyconkr/urls.py @@ -13,15 +13,11 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ +from django.conf import settings from django.contrib import admin from django.urls import include, path -from django.conf import settings - -from drf_spectacular.views import ( - SpectacularAPIView, - SpectacularRedocView, - SpectacularSwaggerView, -) +from drf_spectacular.views import (SpectacularAPIView, SpectacularRedocView, + SpectacularSwaggerView) import sponsor.routers From 716eef7f1083491c111eb1c2673cd41a4dc43f9f Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sat, 25 Feb 2023 03:10:06 +0900 Subject: [PATCH 46/47] =?UTF-8?q?update:=20=EA=B0=81=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EC=9D=98=20Open=20=EC=97=AC=EB=B6=80=EB=A5=BC=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=ED=95=98=EB=8A=94=20status=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyconkr/settings.py | 1 + pyconkr/urls.py | 2 ++ status/__init__.py | 0 status/admin.py | 16 ++++++++++++++++ status/apps.py | 6 ++++++ status/migrations/0001_initial.py | 30 ++++++++++++++++++++++++++++++ status/migrations/__init__.py | 0 status/models.py | 7 +++++++ status/tests.py | 3 +++ status/urls.py | 23 +++++++++++++++++++++++ status/views.py | 22 ++++++++++++++++++++++ 11 files changed, 110 insertions(+) create mode 100644 status/__init__.py create mode 100644 status/admin.py create mode 100644 status/apps.py create mode 100644 status/migrations/0001_initial.py create mode 100644 status/migrations/__init__.py create mode 100644 status/models.py create mode 100644 status/tests.py create mode 100644 status/urls.py create mode 100644 status/views.py diff --git a/pyconkr/settings.py b/pyconkr/settings.py index 2b193e8..66f96be 100644 --- a/pyconkr/settings.py +++ b/pyconkr/settings.py @@ -44,6 +44,7 @@ "constance.backends.database", # apps "sponsor", + "status", ] MIDDLEWARE = [ diff --git a/pyconkr/urls.py b/pyconkr/urls.py index d00d408..dce2934 100644 --- a/pyconkr/urls.py +++ b/pyconkr/urls.py @@ -17,10 +17,12 @@ from django.urls import include, path import sponsor.routers +import status.urls urlpatterns = [ path("api-auth/", include("rest_framework.urls")), path("summernote/", include("django_summernote.urls")), path("admin/", admin.site.urls), path("sponsors/", include(sponsor.routers.get_router().urls)), + path("status/", include(status.urls)), ] diff --git a/status/__init__.py b/status/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/status/admin.py b/status/admin.py new file mode 100644 index 0000000..608954c --- /dev/null +++ b/status/admin.py @@ -0,0 +1,16 @@ +from django.contrib import admin + +from status.models import Status + + +class StatusAdmin(admin.ModelAdmin): + list_display = ("name", "open_at", "close_at") + list_editable = ( + "open_at", + "close_at", + ) + ordering = ("open_at",) + search_fields = ("name",) + + +admin.site.register(Status, StatusAdmin) diff --git a/status/apps.py b/status/apps.py new file mode 100644 index 0000000..cb23d09 --- /dev/null +++ b/status/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class StatusConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "status" diff --git a/status/migrations/0001_initial.py b/status/migrations/0001_initial.py new file mode 100644 index 0000000..d259696 --- /dev/null +++ b/status/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 4.1.5 on 2023-02-24 17:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Status", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100)), + ("open_at", models.DateTimeField()), + ("close_at", models.DateTimeField()), + ], + ), + ] diff --git a/status/migrations/__init__.py b/status/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/status/models.py b/status/models.py new file mode 100644 index 0000000..4e8c97d --- /dev/null +++ b/status/models.py @@ -0,0 +1,7 @@ +from django.db import models + + +class Status(models.Model): + name = models.CharField(max_length=100) + open_at = models.DateTimeField() + close_at = models.DateTimeField() diff --git a/status/tests.py b/status/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/status/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/status/urls.py b/status/urls.py new file mode 100644 index 0000000..9ba260f --- /dev/null +++ b/status/urls.py @@ -0,0 +1,23 @@ +"""pyconkr URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.1/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 django.contrib import admin +from django.urls import include, path + +from status.views import StatusView + +urlpatterns = [ + path("", StatusView.as_view()), +] diff --git a/status/views.py b/status/views.py new file mode 100644 index 0000000..7e8daad --- /dev/null +++ b/status/views.py @@ -0,0 +1,22 @@ +import datetime + +from pytz import timezone +from rest_framework.response import Response +from rest_framework.views import APIView + +from status.models import Status + + +class StatusView(APIView): + def get(self, request, name: str): + status = Status.objects.get(name=name) + now = datetime.datetime.now(tz=timezone("Asia/Seoul")) + + flag = None + + if status.open_at < now < status.close_at: + flag = True + else: + flag = False + + return Response({"name": name, "open": flag}) From c3c8c81c399c4a745af7f0b5cae54dc55b47d9c6 Mon Sep 17 00:00:00 2001 From: parkseongheum Date: Sat, 25 Feb 2023 03:12:05 +0900 Subject: [PATCH 47/47] lint: black --- pyconkr/urls.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyconkr/urls.py b/pyconkr/urls.py index cb6db2c..7f70d1c 100644 --- a/pyconkr/urls.py +++ b/pyconkr/urls.py @@ -16,8 +16,11 @@ from django.conf import settings from django.contrib import admin from django.urls import include, path -from drf_spectacular.views import (SpectacularAPIView, SpectacularRedocView, - SpectacularSwaggerView) +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularRedocView, + SpectacularSwaggerView, +) import sponsor.routers