From 06dc52adc8f9071a8dbb5b94e6f69b451aab3513 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Fri, 15 Nov 2024 20:15:34 +0300 Subject: [PATCH 1/7] fixed --- apps/procollab_skills/settings.py | 27 +++++++++++-- apps/questions/admin.py | 40 +++++++++++++------ apps/questions/models/questions.py | 2 +- .../fixtures/questions/fixtures_connect.py | 8 +--- .../fixtures/questions/fixtures_write.py | 4 +- apps/tests/questions_tests/test_connect.py | 12 ++---- apps/tests/questions_tests/test_exclude.py | 20 +++------- apps/tests/questions_tests/test_infoslide.py | 16 ++------ apps/tests/questions_tests/test_single.py | 20 +++------- apps/tests/questions_tests/test_write.py | 16 ++------ .../test_view_subscription.py | 10 +---- 11 files changed, 78 insertions(+), 97 deletions(-) diff --git a/apps/procollab_skills/settings.py b/apps/procollab_skills/settings.py index 1a587fa..fe3e019 100644 --- a/apps/procollab_skills/settings.py +++ b/apps/procollab_skills/settings.py @@ -2,6 +2,7 @@ from datetime import timedelta from pathlib import Path from decouple import config +from django_summernote.apps import DjangoSummernoteConfig from yookassa import Configuration import mimetypes @@ -51,7 +52,6 @@ ] INSTALLED_APPS = [ - # "django_summernote", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", @@ -112,7 +112,9 @@ "procollab_skills.auth.CustomAuth", "rest_framework.authentication.BasicAuthentication", ], - "DEFAULT_PERMISSION_CLASSES": ["procollab_skills.permissions.IfSubscriptionOutdatedPermission"], + "DEFAULT_PERMISSION_CLASSES": [ + "procollab_skills.permissions.IfSubscriptionOutdatedPermission" + ], } CACHES = { @@ -132,13 +134,27 @@ SELECTEL_SERVICE_USERNAME = config("SELECTEL_SERVICE_USERNAME", cast=str, default="PWD") SELECTEL_SERVICE_PASSWORD = config("SELECTEL_SERVICE_PASSWORD", cast=str, default="PWD") SELECTEL_PROJECT_NAME = config("SELECTEL_PROJECT_NAME", cast=str, default="PWD") -SELECTEL_READ_FILES_DOMAIN = config("SELECTEL_READ_FILES_DOMAIN", cast=str, default="PWD") +SELECTEL_READ_FILES_DOMAIN = config( + "SELECTEL_READ_FILES_DOMAIN", cast=str, default="PWD" +) SELECTEL_PROJECT_ID = config("SELECTEL_PROJECT_ID", cast=str, default="PWD") SELECTEL_NEW_AUTH_TOKEN = "https://cloud.api.selcloud.ru/identity/v3/auth/tokens" SELECTEL_UPLOAD_URL = f"https://swift.ru-1.storage.selcloud.ru/v1/{SELECTEL_PROJECT_ID}/{SELECTEL_CONTAINER_NAME}/" +SUMMERNOTE_CONFIG = { + # 'attachment_upload_to':lambda x: None, + 'disable_attachment': True, +} +SELECTEL_STORAGES = { + 'default': { + 'USERNAME': SELECTEL_SERVICE_USERNAME, + 'PASSWORD': SELECTEL_SERVICE_PASSWORD, + 'CONTAINER_NAME': SELECTEL_CONTAINER_NAME, + }, +} + if 0: DATABASES = { "default": { @@ -207,7 +223,10 @@ "REDOC_DIST": "SIDECAR", "SERVE_INCLUDE_SCHEMA": False, "COMPONENT_SPLIT_REQUEST": True, - "SERVE_AUTHENTICATION": ["rest_framework.authentication.SessionAuthentication", "procollab_skills.auth.CustomAuth"], + "SERVE_AUTHENTICATION": [ + "rest_framework.authentication.SessionAuthentication", + "procollab_skills.auth.CustomAuth", + ], # OTHER SETTINGS "SECURITY_DEFINITIONS": { "Bearer": { diff --git a/apps/questions/admin.py b/apps/questions/admin.py index c9486e6..c240612 100644 --- a/apps/questions/admin.py +++ b/apps/questions/admin.py @@ -1,5 +1,3 @@ -from abc import ABC - from django.contrib import admin from django.contrib.contenttypes.models import ContentType @@ -25,19 +23,27 @@ class AbstractQuestionShowcase(admin.ModelAdmin): def short_description(self, obj) -> str: """Сокращенное описание вопроса.""" - return obj.description[:50] + "..." if len(obj.description) > 50 else obj.description + return ( + obj.description[:50] + "..." + if len(obj.description) > 50 + else obj.description + ) + short_description.short_description = "Описание" def related_task_object(self, obj) -> int | None: """ID части задачи, если вопрос уже привязан.""" content_type: ContentType = ContentType.objects.get_for_model(obj) try: - task_object: TaskObject = TaskObject.objects.get(content_type=content_type, object_id=obj.id) + task_object: TaskObject = TaskObject.objects.get( + content_type=content_type, object_id=obj.id + ) return task_object.id except TaskObject.DoesNotExist: return None except TaskObject.MultipleObjectsReturned: return "Ошибка заполнения, 1 вопрос указан у двух разных TaskObject" + related_task_object.short_description = "ID части задачи" def related_skill(self, obj) -> Skill | None: @@ -46,31 +52,41 @@ def related_skill(self, obj) -> Skill | None: if task_object_id and isinstance(task_object_id, int): task: Task = TaskObject.objects.get(id=task_object_id).task return task.skill if task else None + related_skill.short_description = "Навык" -class ConnectAnswersInline(admin.StackedInline): # Или TabularInline для другого стиля отображения +class ConnectAnswersInline( + admin.StackedInline +): # Или TabularInline для другого стиля отображения model = AnswerConnect extra = 0 fieldsets = ( - (None, { - "fields": (("connect_left", "file_left"), ("connect_right", "file_right")), - "classes": ("wide",), - }), + ( + None, + { + "fields": ( + ("connect_left", "file_left"), + ("connect_right", "file_right"), + ), + "classes": ("wide",), + }, + ), ) class TextEditorMixin: formfield_overrides = { - models.CharField: {'widget': SummernoteWidget}, + models.CharField: {"widget": SummernoteWidget}, } + @admin.register(QuestionConnect) class QuestionConnectAdmin(AbstractQuestionShowcase, TextEditorMixin): inlines = [ConnectAnswersInline] formfield_overrides = { - models.CharField: {'widget': SummernoteWidget}, + models.CharField: {"widget": SummernoteWidget}, } @@ -86,10 +102,10 @@ class QuestionSingleAnswerAdmin(AbstractQuestionShowcase, TextEditorMixin): @admin.register(InfoSlide) class InfoSlideAdmin(AbstractQuestionShowcase, TextEditorMixin): - def short_description(self, obj) -> str: """Сокращенное описание вопроса.""" return obj.text[:50] + "..." if len(obj.text) > 50 else obj.text + short_description.short_description = "Описание" diff --git a/apps/questions/models/questions.py b/apps/questions/models/questions.py index 5477c7b..a51ea6a 100644 --- a/apps/questions/models/questions.py +++ b/apps/questions/models/questions.py @@ -4,7 +4,7 @@ class AbstractQuestion(models.Model): - text = models.CharField(max_length=100) + text = models.TextField(max_length=100) description = models.TextField(null=True, blank=True) class Meta: diff --git a/apps/tests/fixtures/questions/fixtures_connect.py b/apps/tests/fixtures/questions/fixtures_connect.py index f059d5c..1ce0275 100644 --- a/apps/tests/fixtures/questions/fixtures_connect.py +++ b/apps/tests/fixtures/questions/fixtures_connect.py @@ -22,12 +22,8 @@ def connect_question_data() -> None: ) question.save() - answer1 = AnswerConnect( - connect_left="left1", connect_right="right1", question=question - ) - answer2 = AnswerConnect( - connect_left="left2", connect_right="right2", question=question - ) + answer1 = AnswerConnect(connect_left="left1", connect_right="right1", question=question) + answer2 = AnswerConnect(connect_left="left2", connect_right="right2", question=question) answer1.save() answer2.save() diff --git a/apps/tests/fixtures/questions/fixtures_write.py b/apps/tests/fixtures/questions/fixtures_write.py index 9dc5bda..f89bc27 100644 --- a/apps/tests/fixtures/questions/fixtures_write.py +++ b/apps/tests/fixtures/questions/fixtures_write.py @@ -32,9 +32,7 @@ def write_question_data() -> QuestionWrite: @pytest.fixture -def write_question_data_answered( - write_question_data: QuestionWrite, user_with_trial_sub_token -): +def write_question_data_answered(write_question_data: QuestionWrite, user_with_trial_sub_token): with patch("progress.tasks.check_skill_done.delay"): with patch("progress.tasks.check_week_stat.delay"): TaskObjUserResult.objects.create_user_result( diff --git a/apps/tests/questions_tests/test_connect.py b/apps/tests/questions_tests/test_connect.py index 4104efa..d21f74f 100644 --- a/apps/tests/questions_tests/test_connect.py +++ b/apps/tests/questions_tests/test_connect.py @@ -5,9 +5,7 @@ @pytest.mark.usefixtures("connect_question_data", "user_with_trial_sub_token") -def test_connect_not_answered_should_succeed( - client, connect_question_data, user_with_trial_sub_token: str -) -> None: +def test_connect_not_answered_should_succeed(client, connect_question_data, user_with_trial_sub_token: str) -> None: headers = {"Authorization": f"Bearer {user_with_trial_sub_token}"} response = client.get(get_url, headers=headers) @@ -20,9 +18,7 @@ def test_connect_not_answered_should_succeed( @pytest.mark.usefixtures("connect_question_data_answered") -def test_connect_answered_should_succeed( - client, connect_question_data_answered: str -) -> None: +def test_connect_answered_should_succeed(client, connect_question_data_answered: str) -> None: headers = {"Authorization": f"Bearer {connect_question_data_answered}"} response = client.get(get_url, headers=headers) @@ -32,7 +28,5 @@ def test_connect_answered_should_succeed( assert len(response_data["connect_left"]) == 2 assert response_data["is_answered"] is True - for left, right in zip( - response_data["connect_left"], response_data["connect_right"] - ): + for left, right in zip(response_data["connect_left"], response_data["connect_right"]): assert left["id"] == right["id"], "порядок вопросов нарушен" diff --git a/apps/tests/questions_tests/test_exclude.py b/apps/tests/questions_tests/test_exclude.py index c77b143..c79f23b 100644 --- a/apps/tests/questions_tests/test_exclude.py +++ b/apps/tests/questions_tests/test_exclude.py @@ -5,34 +5,24 @@ @pytest.mark.usefixtures("user_with_trial_sub_token", "exclude_question_data") -def test_exclude_not_answered_should_succeed( - client, user_with_trial_sub_token, exclude_question_data -) -> None: +def test_exclude_not_answered_should_succeed(client, user_with_trial_sub_token, exclude_question_data) -> None: headers = {"Authorization": f"Bearer {user_with_trial_sub_token}"} response = client.get(get_url, headers=headers) response_data = response.json() assert response.status_code == 200 - assert ( - response_data["is_answered"] is False - ), "Почему-то на вопрос уже ответили, странно, такого быть не должно" - assert ( - len(response_data["answers"]) > 1 - ), "Почему-то выдало всего один ответ. Должно больше" + assert response_data["is_answered"] is False, "Почему-то на вопрос уже ответили, странно, такого быть не должно" + assert len(response_data["answers"]) > 1, "Почему-то выдало всего один ответ. Должно больше" @pytest.mark.usefixtures("exclude_question_data_answered") -def test_exclude_answered_should_succeed( - client, exclude_question_data_answered -) -> None: +def test_exclude_answered_should_succeed(client, exclude_question_data_answered) -> None: headers = {"Authorization": f"Bearer {exclude_question_data_answered}"} response = client.get(get_url, headers=headers) response_data = response.json() assert response.status_code == 200 - assert ( - response_data["is_answered"] is True - ), "Почему-то вопрос не помечен как отвеченый" + assert response_data["is_answered"] is True, "Почему-то вопрос не помечен как отвеченый" assert len(response_data["answers"]) == 1, "Почему-то выдало больше одного ответа" diff --git a/apps/tests/questions_tests/test_infoslide.py b/apps/tests/questions_tests/test_infoslide.py index 95b3e94..143bf98 100644 --- a/apps/tests/questions_tests/test_infoslide.py +++ b/apps/tests/questions_tests/test_infoslide.py @@ -5,32 +5,24 @@ @pytest.mark.usefixtures("user_with_trial_sub_token", "info_question_data") -def test_infoslide_not_answered_should_succeed( - client, user_with_trial_sub_token, info_question_data -) -> None: +def test_infoslide_not_answered_should_succeed(client, user_with_trial_sub_token, info_question_data) -> None: headers = {"Authorization": f"Bearer {user_with_trial_sub_token}"} response = client.get(get_url, headers=headers) response_data = response.json() assert response.status_code == 200 - assert ( - response_data["text"] == "123" - ), "почему-то текст не такой, какой задан в текстуре" + assert response_data["text"] == "123", "почему-то текст не такой, какой задан в текстуре" assert response_data["is_done"] is False, "почему-то задание сделано" @pytest.mark.usefixtures("info_question_answered_data") -def test_infoslide_answered_should_succeed( - client, info_question_answered_data: str -) -> None: +def test_infoslide_answered_should_succeed(client, info_question_answered_data: str) -> None: headers = {"Authorization": f"Bearer {info_question_answered_data}"} response = client.get(get_url, headers=headers) response_data = response.json() assert response.status_code == 200 - assert ( - response_data["text"] == "123" - ), "почему-то текст не такой, какой задан в текстуре" + assert response_data["text"] == "123", "почему-то текст не такой, какой задан в текстуре" assert response_data["is_done"] is True, "почему-то задание не сделано" diff --git a/apps/tests/questions_tests/test_single.py b/apps/tests/questions_tests/test_single.py index 735642c..5b7fd96 100644 --- a/apps/tests/questions_tests/test_single.py +++ b/apps/tests/questions_tests/test_single.py @@ -5,21 +5,15 @@ @pytest.mark.usefixtures("user_with_trial_sub_token", "question_data") -def test_single_not_answered_should_succeed( - client, user_with_trial_sub_token: str, question_data -) -> None: +def test_single_not_answered_should_succeed(client, user_with_trial_sub_token: str, question_data) -> None: headers = {"Authorization": f"Bearer {user_with_trial_sub_token}"} response = client.get(get_url, headers=headers) response_data = response.json() assert response.status_code == 200 - assert ( - response_data["is_answered"] is False - ), "Почему-то на вопрос уже ответили, странно, такого быть не должно" - assert ( - len(response_data["answers"]) > 1 - ), "Почему-то выдало всего один правильный ответ. Должно больше" + assert response_data["is_answered"] is False, "Почему-то на вопрос уже ответили, странно, такого быть не должно" + assert len(response_data["answers"]) > 1, "Почему-то выдало всего один правильный ответ. Должно больше" @pytest.mark.usefixtures("question_data_answered") @@ -30,9 +24,5 @@ def test_single_answered_should_succeed(client, question_data_answered: str) -> response_class = response.json() assert response.status_code == 200 - assert ( - response_class["is_answered"] is True - ), "Почему-то на вопрос не ответили, странно, такого быть не должно" - assert ( - len(response_class["answers"]) == 1 - ), "Почему-то выдало больше одного правильного ответа" + assert response_class["is_answered"] is True, "Почему-то на вопрос не ответили, странно, такого быть не должно" + assert len(response_class["answers"]) == 1, "Почему-то выдало больше одного правильного ответа" diff --git a/apps/tests/questions_tests/test_write.py b/apps/tests/questions_tests/test_write.py index 661860f..d281784 100644 --- a/apps/tests/questions_tests/test_write.py +++ b/apps/tests/questions_tests/test_write.py @@ -5,32 +5,24 @@ @pytest.mark.usefixtures("write_question_data", "user_with_trial_sub_token") -def test_write_not_answered_should_succeed( - client, user_with_trial_sub_token: str, write_question_data -) -> None: +def test_write_not_answered_should_succeed(client, user_with_trial_sub_token: str, write_question_data) -> None: headers = {"Authorization": f"Bearer {user_with_trial_sub_token}"} response = client.get(get_url, headers=headers) response_data = response.json() assert response.status_code == 200 - assert ( - response_data["text"] == "123" - ), "почему-то текст не такой, какой задан в текстуре" + assert response_data["text"] == "123", "почему-то текст не такой, какой задан в текстуре" assert response_data["answer"] is None, "почему-то ответ уже есть, быть не должен" @pytest.mark.usefixtures("write_question_data_answered") -def test_write_answered_should_succeed( - client, write_question_data_answered: str -) -> None: +def test_write_answered_should_succeed(client, write_question_data_answered: str) -> None: headers = {"Authorization": f"Bearer {write_question_data_answered}"} response = client.get(get_url, headers=headers) response_data = response.json() assert response.status_code == 200 - assert ( - response_data["text"] == "123" - ), "почему-то текст не такой, какой задан в текстуре" + assert response_data["text"] == "123", "почему-то текст не такой, какой задан в текстуре" assert response_data["answer"]["text"] == "sigma", "почему-то задание не сделано" diff --git a/apps/tests/subscription_tests/test_view_subscription.py b/apps/tests/subscription_tests/test_view_subscription.py index 4f96e12..ebd8405 100644 --- a/apps/tests/subscription_tests/test_view_subscription.py +++ b/apps/tests/subscription_tests/test_view_subscription.py @@ -9,10 +9,7 @@ def test_get_trying_subscription_should_succeed(client, user) -> None: get_url = reverse("view-subscriptions") token = CustomObtainPairSerializer.get_token(user) headers = {"Authorization": f"Bearer {str(token)}"} - response = client.get( - get_url, - headers=headers - ) + response = client.get(get_url, headers=headers) response_data: dict = response.json() assert response.status_code == 200, "Ошибка при получении подписки" @@ -25,10 +22,7 @@ def test_get_optimum_subscription_should_succeed(client, user_with_trial_sub, op get_url = reverse("view-subscriptions") token = CustomObtainPairSerializer.get_token(user_with_trial_sub) headers = {"Authorization": f"Bearer {str(token)}"} - response = client.get( - get_url, - headers=headers - ) + response = client.get(get_url, headers=headers) response_data: dict = response.json() assert response.status_code == 200, "Ошибка при получении подписки" From b94c6f6a1f8b52142e809d3f61f5a50022d3b7bb Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Tue, 19 Nov 2024 14:16:43 +0300 Subject: [PATCH 2/7] added sub and selectel --- apps/procollab_skills/settings.py | 15 +++++++-------- apps/subscription/views.py | 9 ++++++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/procollab_skills/settings.py b/apps/procollab_skills/settings.py index fe3e019..547f7c8 100644 --- a/apps/procollab_skills/settings.py +++ b/apps/procollab_skills/settings.py @@ -2,7 +2,6 @@ from datetime import timedelta from pathlib import Path from decouple import config -from django_summernote.apps import DjangoSummernoteConfig from yookassa import Configuration import mimetypes @@ -144,18 +143,18 @@ SUMMERNOTE_CONFIG = { - # 'attachment_upload_to':lambda x: None, - 'disable_attachment': True, + # 'attachment_upload_to':lambda x: None, + "disable_attachment": True, } SELECTEL_STORAGES = { - 'default': { - 'USERNAME': SELECTEL_SERVICE_USERNAME, - 'PASSWORD': SELECTEL_SERVICE_PASSWORD, - 'CONTAINER_NAME': SELECTEL_CONTAINER_NAME, + "default": { + "USERNAME": SELECTEL_SERVICE_USERNAME, + "PASSWORD": SELECTEL_SERVICE_PASSWORD, + "CONTAINER_NAME": SELECTEL_CONTAINER_NAME, }, } -if 0: +if 1: DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", diff --git a/apps/subscription/views.py b/apps/subscription/views.py index bb3614f..0bbb201 100644 --- a/apps/subscription/views.py +++ b/apps/subscription/views.py @@ -112,10 +112,13 @@ class ViewSubscriptions(ListAPIView): def list(self, request, *args, **kwargs) -> Response: is_logged_in = isinstance(self.user, CustomUser) - profile: UserProfile = self.user_profile + profile: UserProfile = self.user_profile if hasattr(self, "user_profile") else None - print(not is_logged_in, not profile.bought_trial_subscription, profile.last_subscription_date) - if (not is_logged_in) or (not profile.bought_trial_subscription) or (not profile.last_subscription_date): + if profile and profile.last_subscription_date and profile.last_subscription_date > timezone.now() - timedelta(days=31): + return Response("subscription is active", status=200) + + + if (not is_logged_in) or (not profile.bought_trial_subscription) or (profile and not profile.last_subscription_date): queryset, created = SubscriptionType.objects.get_or_create( name="Пробная", price=1, From ea4213253ac7771505a361023ab47d11c42f620e Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Tue, 19 Nov 2024 14:17:06 +0300 Subject: [PATCH 3/7] added sub and selectel --- apps/procollab_skills/settings.py | 8 +--- apps/subscription/views.py | 68 +++++++++++++------------------ 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/apps/procollab_skills/settings.py b/apps/procollab_skills/settings.py index 547f7c8..199c258 100644 --- a/apps/procollab_skills/settings.py +++ b/apps/procollab_skills/settings.py @@ -111,9 +111,7 @@ "procollab_skills.auth.CustomAuth", "rest_framework.authentication.BasicAuthentication", ], - "DEFAULT_PERMISSION_CLASSES": [ - "procollab_skills.permissions.IfSubscriptionOutdatedPermission" - ], + "DEFAULT_PERMISSION_CLASSES": ["procollab_skills.permissions.IfSubscriptionOutdatedPermission"], } CACHES = { @@ -133,9 +131,7 @@ SELECTEL_SERVICE_USERNAME = config("SELECTEL_SERVICE_USERNAME", cast=str, default="PWD") SELECTEL_SERVICE_PASSWORD = config("SELECTEL_SERVICE_PASSWORD", cast=str, default="PWD") SELECTEL_PROJECT_NAME = config("SELECTEL_PROJECT_NAME", cast=str, default="PWD") -SELECTEL_READ_FILES_DOMAIN = config( - "SELECTEL_READ_FILES_DOMAIN", cast=str, default="PWD" -) +SELECTEL_READ_FILES_DOMAIN = config("SELECTEL_READ_FILES_DOMAIN", cast=str, default="PWD") SELECTEL_PROJECT_ID = config("SELECTEL_PROJECT_ID", cast=str, default="PWD") SELECTEL_NEW_AUTH_TOKEN = "https://cloud.api.selcloud.ru/identity/v3/auth/tokens" diff --git a/apps/subscription/views.py b/apps/subscription/views.py index 0bbb201..a581356 100644 --- a/apps/subscription/views.py +++ b/apps/subscription/views.py @@ -72,9 +72,7 @@ def create(self, request, *args, **kwargs) -> Response: try: self.check_subscription(self.user_profile.last_subscription_date) - request_data: CreatePaymentViewRequestData = self.get_request_data( - self.profile_id - ) + request_data: CreatePaymentViewRequestData = self.get_request_data(self.profile_id) subscription = get_object_or_404(SubscriptionType, id=self.subscription_id) amount = AmountData(value=subscription.price) @@ -114,60 +112,60 @@ def list(self, request, *args, **kwargs) -> Response: is_logged_in = isinstance(self.user, CustomUser) profile: UserProfile = self.user_profile if hasattr(self, "user_profile") else None - if profile and profile.last_subscription_date and profile.last_subscription_date > timezone.now() - timedelta(days=31): + if ( + profile + and profile.last_subscription_date + and profile.last_subscription_date > timezone.now() - timedelta(days=31) + ): return Response("subscription is active", status=200) - - if (not is_logged_in) or (not profile.bought_trial_subscription) or (profile and not profile.last_subscription_date): + if ( + (not is_logged_in) + or (not profile.bought_trial_subscription) + or (profile and not profile.last_subscription_date) + ): queryset, created = SubscriptionType.objects.get_or_create( name="Пробная", price=1, - features=("Задания от практикующих специалистов, Нативные задания, " - "Карьерные знания дешевле стакана кофе, Общество единомышленников"), + features=( + "Задания от практикующих специалистов, Нативные задания, " + "Карьерные знания дешевле стакана кофе, Общество единомышленников" + ), ) else: queryset, created = SubscriptionType.objects.get_or_create( name="Оптимум", price=120, - features=("Задания от практикующих специалистов, Нативные задания, " - "Карьерные знания дешевле стакана кофе, Общество единомышленников"), + features=( + "Задания от практикующих специалистов, Нативные задания, " + "Карьерные знания дешевле стакана кофе, Общество единомышленников" + ), ) serializer = self.serializer_class(queryset) return Response(serializer.data, status=200) -@extend_schema( - summary="НЕ ДЛЯ ФРОНТА. Обновление дат подписки для юзеров.", tags=["Подписка"] -) +@extend_schema(summary="НЕ ДЛЯ ФРОНТА. Обновление дат подписки для юзеров.", tags=["Подписка"]) class NotificationWebHook(CreateAPIView): serializer_class = RenewSubDateSerializer permission_classes = [AllowAny] authentication_classes = [] def get_request_data(self) -> WebHookRequest: - return WebHookRequest( - event=self.request.data["event"], object=self.request.data["object"] - ) + return WebHookRequest(event=self.request.data["event"], object=self.request.data["object"]) def create(self, request, *args, **kwargs) -> Response: # try: notification_data = self.get_request_data() - profile_to_update = UserProfile.objects.select_related( - "user", "last_subscription_type" - ).filter(id=notification_data.object["metadata"]["user_profile_id"]) - - + profile_to_update = UserProfile.objects.select_related("user", "last_subscription_type").filter( + id=notification_data.object["metadata"]["user_profile_id"] + ) - if ( - notification_data.event == "payment.succeeded" - and notification_data.object["status"] == "succeeded" - ): + if notification_data.event == "payment.succeeded" and notification_data.object["status"] == "succeeded": params_to_update = {"last_subscription_date": timezone.now()} - if sub_id := notification_data.object["metadata"].get( - "subscription_id" - ): + if sub_id := notification_data.object["metadata"].get("subscription_id"): params_to_update["last_subscription_type_id"] = sub_id params_to_update["bought_trial_subscription"] = True @@ -181,10 +179,7 @@ def create(self, request, *args, **kwargs) -> Response: f"subscription date renewed for {profile_to_update[0].user.first_name} " f"{profile_to_update[0].user.last_name}" ) - elif ( - notification_data.event == "refund.succeeded" - and notification_data.object["status"] == "succeeded" - ): + elif notification_data.event == "refund.succeeded" and notification_data.object["status"] == "succeeded": ( profile_to_update.update( last_subscription_date=None, @@ -219,10 +214,7 @@ def create(self, request, *args, **kwargs): } ) last_payment = payments.items[0] - if ( - self.user_profile.last_subscription_date - >= timezone.now().date() - timedelta(days=15) - ): + if self.user_profile.last_subscription_date >= timezone.now().date() - timedelta(days=15): response = Refund.create( { "payment_id": last_payment.id, @@ -249,9 +241,7 @@ def create(self, request, *args, **kwargs): else: return Response( - { - "error": "Вы не можете отменить подписку после 15 дней пользования, извините" - }, + {"error": "Вы не можете отменить подписку после 15 дней пользования, извините"}, status=400, ) return Response(status=202) From 65cea26c3b8606d41423589dc5f2c8e712f776bf Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Tue, 19 Nov 2024 14:36:18 +0300 Subject: [PATCH 4/7] added sub and selectel --- apps/procollab_skills/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/procollab_skills/settings.py b/apps/procollab_skills/settings.py index 199c258..b94d826 100644 --- a/apps/procollab_skills/settings.py +++ b/apps/procollab_skills/settings.py @@ -150,7 +150,7 @@ }, } -if 1: +if 0: DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", From 25b95c5264efca7998f023a7a7cb180164f5baef Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Tue, 19 Nov 2024 15:04:05 +0300 Subject: [PATCH 5/7] added sub and selectel --- apps/procollab_skills/settings.py | 7 ++----- apps/questions/admin.py | 20 ++++++++------------ poetry.lock | 16 +++++++++++++++- pyproject.toml | 1 + 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/procollab_skills/settings.py b/apps/procollab_skills/settings.py index b94d826..5fe6ca2 100644 --- a/apps/procollab_skills/settings.py +++ b/apps/procollab_skills/settings.py @@ -138,10 +138,7 @@ SELECTEL_UPLOAD_URL = f"https://swift.ru-1.storage.selcloud.ru/v1/{SELECTEL_PROJECT_ID}/{SELECTEL_CONTAINER_NAME}/" -SUMMERNOTE_CONFIG = { - # 'attachment_upload_to':lambda x: None, - "disable_attachment": True, -} + SELECTEL_STORAGES = { "default": { "USERNAME": SELECTEL_SERVICE_USERNAME, @@ -150,7 +147,7 @@ }, } -if 0: +if 1: DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", diff --git a/apps/questions/admin.py b/apps/questions/admin.py index c240612..5cef957 100644 --- a/apps/questions/admin.py +++ b/apps/questions/admin.py @@ -2,6 +2,7 @@ from django.contrib.contenttypes.models import ContentType from django.db import models +from django_summernote.admin import SummernoteModelAdmin from django_summernote.widgets import SummernoteWidget from questions.models import ( @@ -75,19 +76,12 @@ class ConnectAnswersInline( ) -class TextEditorMixin: - formfield_overrides = { - models.CharField: {"widget": SummernoteWidget}, - } - @admin.register(QuestionConnect) -class QuestionConnectAdmin(AbstractQuestionShowcase, TextEditorMixin): +class QuestionConnectAdmin(AbstractQuestionShowcase, SummernoteModelAdmin): inlines = [ConnectAnswersInline] - formfield_overrides = { - models.CharField: {"widget": SummernoteWidget}, - } + class SingleAnswersInline(admin.StackedInline): @@ -96,12 +90,14 @@ class SingleAnswersInline(admin.StackedInline): @admin.register(QuestionSingleAnswer) -class QuestionSingleAnswerAdmin(AbstractQuestionShowcase, TextEditorMixin): +class QuestionSingleAnswerAdmin(AbstractQuestionShowcase, SummernoteModelAdmin): inlines = [SingleAnswersInline] + + @admin.register(InfoSlide) -class InfoSlideAdmin(AbstractQuestionShowcase, TextEditorMixin): +class InfoSlideAdmin(AbstractQuestionShowcase, SummernoteModelAdmin): def short_description(self, obj) -> str: """Сокращенное описание вопроса.""" return obj.text[:50] + "..." if len(obj.text) > 50 else obj.text @@ -110,5 +106,5 @@ def short_description(self, obj) -> str: @admin.register(QuestionWrite) -class QuestionWriteAdmin(AbstractQuestionShowcase, TextEditorMixin): +class QuestionWriteAdmin(AbstractQuestionShowcase, SummernoteModelAdmin): pass diff --git a/poetry.lock b/poetry.lock index 4f47760..552f1f1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -632,6 +632,20 @@ files = [ [package.dependencies] Django = ">=4.2" +[[package]] +name = "django-selectel-storage" +version = "1.0.2" +description = "A Django storage backend allowing you to easily save user-generated and static files inside Selectel Cloud storage rather than a local filesystem, as Django does by default." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "django-selectel-storage-1.0.2.tar.gz", hash = "sha256:44c02584e5a5261d46a3f495db088df8780e05ce990bbfdfbe8b8f6171270126"}, + {file = "django_selectel_storage-1.0.2-py2.py3-none-any.whl", hash = "sha256:7069a65aff4d7ac8afb6f9f966b54d26fc312ef364a71295223ea4773ab8f5cb"}, +] + +[package.dependencies] +requests = ">=2.23.0,<3.0.0" + [[package]] name = "django-stubs" version = "5.0.4" @@ -2106,4 +2120,4 @@ urllib3 = "*" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "96a2c80a731c8949ab362ba74bace1efeb29ebf169783ea2b51f3059174e385d" +content-hash = "9410954684d9c8cfd2b40d167846a1174daedbbe0cfbcbe1e4c9fa60888682d8" diff --git a/pyproject.toml b/pyproject.toml index 3fe12fb..25d99ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,7 @@ bcrypt = "^4.1.3" pytest-django = "^4.9.0" model-bakery = "^1.20.0" django-summernote = "^0.8.20.0" +django-selectel-storage = "^1.0.2" [tool.poetry.group.dev.dependencies] flake8 = "^7.0.0" From 26ef5db7cefc5dd41ee4b35e8949863bf1e486b9 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Tue, 19 Nov 2024 15:04:41 +0300 Subject: [PATCH 6/7] added sub and selectel --- apps/procollab_skills/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/procollab_skills/settings.py b/apps/procollab_skills/settings.py index 5fe6ca2..0b8692f 100644 --- a/apps/procollab_skills/settings.py +++ b/apps/procollab_skills/settings.py @@ -147,7 +147,7 @@ }, } -if 1: +if 0: DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", From 8ac89d9b945a7f07052b390f4b7f0f963aa5ce63 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Tue, 19 Nov 2024 15:10:42 +0300 Subject: [PATCH 7/7] added sub and selectel --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e88e3de..323669f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ WORKDIR /procollab COPY poetry.lock pyproject.toml /procollab/ RUN poetry config virtualenvs.create false \ - && poetry install --no-root + && poetry lock --no-update RUN mkdir /procollab/static