Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 13 additions & 2 deletions apps/procollab_skills/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
]

INSTALLED_APPS = [
# "django_summernote",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
Expand Down Expand Up @@ -139,6 +138,15 @@
SELECTEL_UPLOAD_URL = f"https://swift.ru-1.storage.selcloud.ru/v1/{SELECTEL_PROJECT_ID}/{SELECTEL_CONTAINER_NAME}/"



SELECTEL_STORAGES = {
"default": {
"USERNAME": SELECTEL_SERVICE_USERNAME,
"PASSWORD": SELECTEL_SERVICE_PASSWORD,
"CONTAINER_NAME": SELECTEL_CONTAINER_NAME,
},
}

if 0:
DATABASES = {
"default": {
Expand Down Expand Up @@ -207,7 +215,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": {
Expand Down
54 changes: 33 additions & 21 deletions apps/questions/admin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from abc import ABC

from django.contrib import admin
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 (
Expand All @@ -25,19 +24,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:
Expand All @@ -46,32 +53,35 @@ 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},
}

@admin.register(QuestionConnect)
class QuestionConnectAdmin(AbstractQuestionShowcase, TextEditorMixin):
class QuestionConnectAdmin(AbstractQuestionShowcase, SummernoteModelAdmin):
inlines = [ConnectAnswersInline]

formfield_overrides = {
models.CharField: {'widget': SummernoteWidget},
}



class SingleAnswersInline(admin.StackedInline):
Expand All @@ -80,19 +90,21 @@ class SingleAnswersInline(admin.StackedInline):


@admin.register(QuestionSingleAnswer)
class QuestionSingleAnswerAdmin(AbstractQuestionShowcase, TextEditorMixin):
class QuestionSingleAnswerAdmin(AbstractQuestionShowcase, SummernoteModelAdmin):
inlines = [SingleAnswersInline]


@admin.register(InfoSlide)
class InfoSlideAdmin(AbstractQuestionShowcase, TextEditorMixin):


@admin.register(InfoSlide)
class InfoSlideAdmin(AbstractQuestionShowcase, SummernoteModelAdmin):
def short_description(self, obj) -> str:
"""Сокращенное описание вопроса."""
return obj.text[:50] + "..." if len(obj.text) > 50 else obj.text

short_description.short_description = "Описание"


@admin.register(QuestionWrite)
class QuestionWriteAdmin(AbstractQuestionShowcase, TextEditorMixin):
class QuestionWriteAdmin(AbstractQuestionShowcase, SummernoteModelAdmin):
pass
2 changes: 1 addition & 1 deletion apps/questions/models/questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
71 changes: 32 additions & 39 deletions apps/subscription/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -112,59 +110,62 @@ 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,
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

Expand All @@ -178,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,
Expand Down Expand Up @@ -216,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,
Expand All @@ -246,9 +241,7 @@ def create(self, request, *args, **kwargs):

else:
return Response(
{
"error": "Вы не можете отменить подписку после 15 дней пользования, извините"
},
{"error": "Вы не можете отменить подписку после 15 дней пользования, извините"},
status=400,
)
return Response(status=202)
Expand Down
8 changes: 2 additions & 6 deletions apps/tests/fixtures/questions/fixtures_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
4 changes: 1 addition & 3 deletions apps/tests/fixtures/questions/fixtures_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 3 additions & 9 deletions apps/tests/questions_tests/test_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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"], "порядок вопросов нарушен"
Loading