From 3e84f973fa49aa8157e69de0ba3109f8c2e915a1 Mon Sep 17 00:00:00 2001 From: Anton Lebedev <wrawka@gmail.com> Date: Tue, 21 Jan 2025 18:41:27 +0700 Subject: [PATCH 1/6] marked missing strings for translation --- frontend/html/admin/report.html | 17 ++++++---- frontend/html/posts/show/layout.html | 18 +++++----- .../static/js/components/PostBookmark.vue | 2 +- misc/forms.py | 9 ++--- posts/forms/admin.py | 33 ++++++++++--------- posts/views/admin.py | 9 ++--- 6 files changed, 48 insertions(+), 40 deletions(-) diff --git a/frontend/html/admin/report.html b/frontend/html/admin/report.html index e6638a8..f998650 100644 --- a/frontend/html/admin/report.html +++ b/frontend/html/admin/report.html @@ -1,19 +1,20 @@ {% extends "layout.html" %} +{% load i18n %} {% block title %} - Ð”Ð»Ñ Ð¼Ð¾Ð´ÐµÑ€Ð°Ñ‚Ð¾Ñ€Ð° — {{ block.super }} + {% translate "Ð”Ð»Ñ Ð¼Ð¾Ð´ÐµÑ€Ð°Ñ‚Ð¾Ñ€Ð°" %} — {{ block.super }} {% endblock %} {% block content %} <div class="content compose"> - <div class="content-header">Сообщение модератору</div> + <div class="content-header">{% translate "Сообщение модератору" %}</div> {% if not submitted %} <div class="content-description content-description-left"> <p> - ЕÑли вы здеÑÑŒ, то наверное в ÑообщеÑтве ÑлучилоÑÑŒ что-то неприÑтное. + {% translate "ЕÑли вы здеÑÑŒ, то наверное в ÑообщеÑтве ÑлучилоÑÑŒ что-то неприÑтное." %} </p> <p> - РаÑÑкажите нам вÑе подробноÑти, и Ñто обращение обÑзательно увидÑÑ‚ модераторы. + {% translate "РаÑÑкажите нам вÑе подробноÑти, и Ñто обращение обÑзательно увидÑÑ‚ модераторы." %} </p> </div> @@ -25,8 +26,10 @@ <label for="{{ form.text.id_for_label }}" class="form-label">{{ form.text.label }}</label> {{ form.text }} <span class="form-row-help form-row-help-wide"> + {% blocktranslate %} Можно иÑпользовать <a href="https://www.markdownguide.org/basic-syntax/" target="_blank">Markdown</a>. Ð”Ð»Ñ Ð·Ð°Ð³Ñ€ÑƒÐ·ÐºÐ¸ картинок проÑто перетащите их в редактор. + {% endblocktranslate %} </span> </div> @@ -40,7 +43,7 @@ <div class="form-row form-row-space-between"> <button type="submit" class="button"> - Отправить + {% translate "Отправить" %} </button> </div> @@ -49,10 +52,10 @@ {% else %} <div class="content-description content-description-left"> <p> - СпаÑибо! Мы получили ваше Ñообщение. + {% translate "СпаÑибо! Мы получили ваше Ñообщение." %} </p> <p> - Здоровое ÑообщеÑтво очень важно Ð´Ð»Ñ Ð½Ð°Ñ Ð¸ мы обÑзательно что-нибудь предпримем. + {% translate "Здоровое ÑообщеÑтво очень важно Ð´Ð»Ñ Ð½Ð°Ñ Ð¸ мы обÑзательно что-нибудь предпримем." %} </p> </div> {% endif %} diff --git a/frontend/html/posts/show/layout.html b/frontend/html/posts/show/layout.html index 812355d..0003121 100644 --- a/frontend/html/posts/show/layout.html +++ b/frontend/html/posts/show/layout.html @@ -46,19 +46,19 @@ <div class="post-comments-title-left"> {% if post.comment_count > 0 %} <a href="#comments" class="post-comments-count"> - {{ post.comment_count }} {% translate "комментариев" %} 👇 + {{ post.comment_count }} {% translate "комментариев" %} 👇 </a> <form action=".#comments" method="get" class="post-comments-order"> <select name="comment_order" onchange="this.form.submit()"> - <option value="-upvotes" {% if comment_order == "-upvotes" %}selected{% endif %}>по крутоÑти</option> - <option value="-created_at" {% if comment_order == "-created_at" %}selected{% endif %}>по ÑвежеÑти</option> - <option value="created_at" {% if comment_order == "created_at" %}selected{% endif %}>по порÑдку</option> + <option value="-upvotes" {% if comment_order == "-upvotes" %}selected{% endif %}>{% translate "по крутоÑти" %}</option> + <option value="-created_at" {% if comment_order == "-created_at" %}selected{% endif %}>{% translate "по ÑвежеÑти" %}</option> + <option value="created_at" {% if comment_order == "created_at" %}selected{% endif %}>{% translate "по порÑдку" %}</option> </select> </form> {% elif post.is_commentable %} <a href="#comments" class="post-comments-count"> - Откомментируйте первым 👇 + {% translate "Откомментируйте первым" %} 👇 </a> {% endif %} </div> @@ -70,7 +70,7 @@ {% if subscription %}is-active-by-default{% endif %} class="post-comments-subscription" > - подпиÑка на комментарии + {% translate "подпиÑка на комментарии" %} </post-subscription> {% endif %} </div> @@ -89,9 +89,11 @@ <div class="post-comments-rules"> <ul> - <li>Помните, что за оÑкорбление других учаÑтников, паÑÑивную агреÑÑию, недружелюбноÑть и прочие Ð½Ð°Ñ€ÑƒÑˆÐµÐ½Ð¸Ñ <!--<a href="{% url "docs" "about" %}">-->правил Ñтикета<!--</a>--> — будет бан.</li> - <li>ЕÑли кто-то ведёт ÑÐµÐ±Ñ Ð¿Ð»Ð¾Ñ…Ð¾ — Ñкажите им. ЕÑли они продолжают, можно <a href="{% url "report" post.slug %}">пожаловатьÑÑ</a>.</li> + {% blocktranslate with post.slug|report_url as report_url %} + <li>Помните, что за оÑкорбление других учаÑтников, паÑÑивную агреÑÑию, недружелюбноÑть и прочие Ð½Ð°Ñ€ÑƒÑˆÐµÐ½Ð¸Ñ Ð¿Ñ€Ð°Ð²Ð¸Ð» Ñтикета — будет бан.</li> + <li>ЕÑли кто-то ведёт ÑÐµÐ±Ñ Ð¿Ð»Ð¾Ñ…Ð¾ — Ñкажите им. ЕÑли они продолжают, можно <a href="{{ report_url }}">пожаловатьÑÑ</a>.</li> <li>Можно иÑпользовать <a href="https://www.markdownguide.org/basic-syntax/" target="_blank">Markdown</a>.</li> + {% endblocktranslate %} </ul> </div> {% endif %} diff --git a/frontend/static/js/components/PostBookmark.vue b/frontend/static/js/components/PostBookmark.vue index 16891de..351f62b 100644 --- a/frontend/static/js/components/PostBookmark.vue +++ b/frontend/static/js/components/PostBookmark.vue @@ -1,7 +1,7 @@ <template> <a :href="bookmarkUrl" @click.prevent="toggle"> <span v-if="isLoading">🤔</span> - <span v-if="isBookmarked"><i class="fas fa-bookmark"></i> Убрать из закладок</span> + <span v-if="isBookmarked"><i class="fas fa-bookmark"></i> {{ $gettext('Убрать из закладок') }}</span> <span v-else><i class="far fa-bookmark"></i> {{ $gettext('Ð’ закладки') }}</span> </a> </template> diff --git a/misc/forms.py b/misc/forms.py index 5cb32f0..33751fc 100644 --- a/misc/forms.py +++ b/misc/forms.py @@ -1,22 +1,23 @@ from django import forms +from django.utils.translation import gettext_lazy as _ class UserReportForm(forms.Form): text = forms.CharField( - label="ТекÑÑ‚ обращениÑ", + label=_("ТекÑÑ‚ обращениÑ"), required=True, max_length=5000, widget=forms.Textarea( attrs={ "maxlength": 5000, "class": "markdown-editor-full", - "placeholder": "РаÑÑкажите, что тут ÑлучилоÑÑŒ?" + "placeholder": _("РаÑÑкажите, что тут ÑлучилоÑÑŒ?") } ), ) post = forms.SlugField( widget=forms.HiddenInput(), - label="ПоÑÑ‚ из жалобы", max_length=250) + label=_("ПоÑÑ‚ из жалобы"), max_length=250) user = forms.SlugField( widget=forms.HiddenInput(), - label="Ðвтор жалобы", max_length=250) + label=_("Ðвтор жалобы"), max_length=250) diff --git a/posts/forms/admin.py b/posts/forms/admin.py index b334229..ce663ff 100644 --- a/posts/forms/admin.py +++ b/posts/forms/admin.py @@ -1,4 +1,5 @@ from django import forms +from django.utils.translation import gettext_lazy as _ from common.data.labels import LABELS from posts.models.post import Post @@ -6,79 +7,79 @@ from posts.models.post import Post class PostCuratorForm(forms.Form): change_type = forms.ChoiceField( - label="Сменить тип поÑта", + label=_("Сменить тип поÑта"), choices=[(None, "---")] + Post.TYPES, required=False, ) new_label = forms.ChoiceField( - label="Выдать лейбл", + label=_("Выдать лейбл"), choices=[(None, "---")] + [(key, value.get("title")) for key, value in LABELS.items()], required=False, ) remove_label = forms.BooleanField( - label="Удалить текуший лейбл", + label=_("Удалить текуший лейбл"), required=False ) add_pin = forms.BooleanField( - label="Запинить", + label=_("Запинить"), required=False ) pin_days = forms.IntegerField( - label="Ðа Ñколько дней пин?", + label=_("Ðа Ñколько дней пин?"), initial=1, required=False ) remove_pin = forms.BooleanField( - label="Отпинить обратно", + label=_("Отпинить обратно"), required=False ) move_up = forms.BooleanField( - label="ПодброÑить на главной", + label=_("ПодброÑить на главной"), required=False ) move_down = forms.BooleanField( - label="ОпуÑтить на главной", + label=_("ОпуÑтить на главной"), required=False ) shadow_ban = forms.BooleanField( - label="Шадоу бан (редко!)", + label=_("Шадоу бан (редко!)"), required=False, ) hide_from_feeds = forms.BooleanField( - label="Скрыть Ñ Ð³Ð»Ð°Ð²Ð½Ð¾Ð¹", + label=_("Скрыть Ñ Ð³Ð»Ð°Ð²Ð½Ð¾Ð¹"), required=False, ) class PostAdminForm(PostCuratorForm): toggle_is_commentable = forms.BooleanField( - label="Закрыть комменты (повторный клик переоткроет заново)", + label=_("Закрыть комменты (повторный клик переоткроет заново)"), required=False, ) transfer_ownership = forms.CharField( - label="Передать владение поÑтом другому юзернейму", + label=_("Передать владение поÑтом другому юзернейму"), required=False, ) refresh_linked = forms.BooleanField( - label="Обновить ÑвÑзанные поÑты", + label=_("Обновить ÑвÑзанные поÑты"), required=False, ) class PostAnnounceForm(forms.Form): text = forms.CharField( - label="ТекÑÑ‚ анонÑа", + label=_("ТекÑÑ‚ анонÑа"), required=True, max_length=500000, widget=forms.Textarea( @@ -88,11 +89,11 @@ class PostAnnounceForm(forms.Form): ), ) image = forms.CharField( - label="Картинка", + label=_("Картинка"), required=False, ) with_image = forms.BooleanField( - label="ПоÑтим Ñ ÐºÐ°Ñ€Ñ‚Ð¸Ð½ÐºÐ¾Ð¹?", + label=_("ПоÑтим Ñ ÐºÐ°Ñ€Ñ‚Ð¸Ð½ÐºÐ¾Ð¹?"), required=False, initial=True, ) diff --git a/posts/views/admin.py b/posts/views/admin.py index 077abb6..86d6db2 100644 --- a/posts/views/admin.py +++ b/posts/views/admin.py @@ -1,4 +1,5 @@ from django.shortcuts import get_object_or_404, render +from django.utils.translation import gettext_lazy as _ from auth.helpers import auth_required, moderator_role_required, curator_role_required from notifications.telegram.common import render_html_message @@ -22,7 +23,7 @@ def curate_post(request, post_slug): form = PostCuratorForm() return render(request, "admin/simple_form.html", { - "title": "Курирование поÑта", + "title": _("Курирование поÑта"), "post": post, "form": form }) @@ -41,7 +42,7 @@ def admin_post(request, post_slug): form = PostAdminForm() return render(request, "admin/simple_form.html", { - "title": "Ðдминить поÑÑ‚", + "title": _("Ðдминить поÑÑ‚"), "post": post, "form": form }) @@ -66,13 +67,13 @@ def announce_post(request, post_slug): image=form.cleaned_data["image"] if form.cleaned_data["with_image"] else None ) return render(request, "message.html", { - "title": "Материал анонÑирован!" + "title": _("Материал анонÑирован!") }) else: form = PostAnnounceForm(initial=initial) return render(request, "admin/simple_form.html", { - "title": "ÐнонÑировать материал на канале", + "title": _("ÐнонÑировать материал на канале"), "post": post, "form": form }) -- GitLab From 557927147273f88830d6ca808300e60f5e24b0cd Mon Sep 17 00:00:00 2001 From: Anton Lebedev <wrawka@gmail.com> Date: Tue, 21 Jan 2025 18:42:01 +0700 Subject: [PATCH 2/6] added 'report post' url template tag --- posts/templatetags/posts.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/posts/templatetags/posts.py b/posts/templatetags/posts.py index c78e20f..2be2bac 100644 --- a/posts/templatetags/posts.py +++ b/posts/templatetags/posts.py @@ -128,3 +128,8 @@ def og_image(post): }) return f"{settings.OG_IMAGE_GENERATOR_URL}?{params}" + + +@register.filter +def report_url(slug): + return reverse("report", args=[slug]) -- GitLab From b98d7844adb14307d898f9c3bcfa7203665e63e0 Mon Sep 17 00:00:00 2001 From: Anton Lebedev <wrawka@gmail.com> Date: Tue, 21 Jan 2025 18:43:28 +0700 Subject: [PATCH 3/6] added feature flag for timed membership --- auth/helpers.py | 9 ++++++--- auth/models.py | 6 +++++- club/features.py | 5 +++++ club/settings.py | 6 ++++++ frontend/html/admin/invite.html | 34 +++++++++++++++++++++++++++++++++ frontend/html/users/admin.html | 2 ++ landing/forms.py | 7 +++++-- landing/views.py | 19 +++++++++++------- users/models/user.py | 5 ++++- 9 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 frontend/html/admin/invite.html diff --git a/auth/helpers.py b/auth/helpers.py index d7906e1..555c5db 100644 --- a/auth/helpers.py +++ b/auth/helpers.py @@ -5,7 +5,7 @@ import jwt from django.shortcuts import redirect, render, get_object_or_404 from auth.models import Session, Apps -from club import settings +from club import features, settings from club.exceptions import AccessDenied, ApiAuthRequired, ClubException, ApiException from posts.models.post import Post from users.models.user import User @@ -111,7 +111,7 @@ def check_user_permissions(request, **context): and not request.path.startswith("/about/") \ and not request.path.startswith("/messages/"): - if request.me and request.me.membership_expires_at < datetime.utcnow(): + if request.me and features.MEMBERSHIP_ENABLED and request.me.membership_expires_at < datetime.utcnow(): log.info("User membership expired.") return render(request, "auth/membership_expired.html", context) @@ -198,10 +198,13 @@ def auth_switch(yes, no): def set_session_cookie(response, user, session): + expires_at = datetime.utcnow() + timedelta(days=30) + if features.MEMBERSHIP_ENABLED: + expires_at = max(user.membership_expires_at, expires_at) response.set_cookie( key="token", value=session.token, - expires=max(user.membership_expires_at, datetime.utcnow() + timedelta(days=30)), + expires=expires_at, httponly=True, secure=not settings.DEBUG, ) diff --git a/auth/models.py b/auth/models.py index f74ea57..e1240b3 100644 --- a/auth/models.py +++ b/auth/models.py @@ -5,6 +5,7 @@ from django.conf import settings from django.contrib.postgres.fields import ArrayField from django.db import models +from club import features from club.exceptions import RateLimitException, InvalidCode from users.models.user import User from utils.strings import random_string, random_number @@ -40,11 +41,14 @@ class Session(models.Model): @classmethod def create_for_user(cls, user): + expires_at = datetime.utcnow() + timedelta(days=30) + if features.MEMBERSHIP_ENABLED: + expires_at = max(user.membership_expires_at, expires_at) return Session.objects.create( user=user, token=random_string(length=32), created_at=datetime.utcnow(), - expires_at=max(user.membership_expires_at, datetime.utcnow() + timedelta(days=30)), + expires_at=expires_at, ) diff --git a/club/features.py b/club/features.py index 4918a53..14479d6 100644 --- a/club/features.py +++ b/club/features.py @@ -6,3 +6,8 @@ # True — feed is only visible to club members, other users will be redirected to landing page # False — everyone can view the feed, it becomes the main page PRIVATE_FEED = False + +# Enable/disable timed membership for users +# True — users can buy a membership for a certain period of time +# False — membership is disabled, all users have access to all features +MEMBERSHIP_ENABLED = False diff --git a/club/settings.py b/club/settings.py index 0f6eac8..4b44a23 100644 --- a/club/settings.py +++ b/club/settings.py @@ -177,6 +177,10 @@ EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD") EMAIL_USE_TLS = True DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", EMAIL_HOST_USER) +if DEBUG: + EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend" + EMAIL_FILE_PATH = os.path.join(BASE_DIR, "emails") + # These emails will receive notifications on new help requests HELP_REQUESTS_NOTIFICATION_RECIPIENTS = set(os.getenv("HELP_REQUESTS_NOTIFICATION_RECIPIENTS", "").split(",")) @@ -277,6 +281,8 @@ CLEARED_POST_TEXT = _("```\n" \ "ЕÑли вы хотите приютить и развить Ñту тему как новый автор, напишите модераторам: moderator@plus7toolkit.com." \ "\n```") +DEFAULT_MEMBERSHIP_DAYS = 365 # days + MODERATOR_USERNAME = "moderator" DELETED_USERNAME = "dev" diff --git a/frontend/html/admin/invite.html b/frontend/html/admin/invite.html new file mode 100644 index 0000000..437379a --- /dev/null +++ b/frontend/html/admin/invite.html @@ -0,0 +1,34 @@ +{% extends "layout.html" %} +{% load static %} +{% load i18n %} + +{% block title %} + {{ title | default:"Secret place" }} — {{ block.super }} +{% endblock %} + +{% block content %} + <div class="content"> + <div class="profile-header">{{ title | default:"Secret place" }}</div> + <div class="block"> + <form action="." method="post" enctype="multipart/form-data"> + {% csrf_token %} + + <div class="form-row"> + {{ form.email.label_tag }} + {{ form.email }} + {% if form.email.errors %}<span class="form-row-errors">{{ form.email.errors }}</span>{% endif %} + </div> + + {% if features.MEMBERSHIP_ENABLED %} + <div class="form-row"> + {{ form.days.label_tag }} + {{ form.days }} + {% if form.days.errors %}<span class="form-row-errors">{{ form.days.errors }}</span>{% endif %} + </div> + {% endif %} + + <button type="submit" class="button">{% translate "Готово" %}</button> + </form> + </div> + </div> +{% endblock %} diff --git a/frontend/html/users/admin.html b/frontend/html/users/admin.html index d5a736a..559e2c9 100644 --- a/frontend/html/users/admin.html +++ b/frontend/html/users/admin.html @@ -220,6 +220,7 @@ </div> {% endif %} + {% if features.MEMBERSHIP_ENABLED %} <div class="profile-header">{% translate "Продлить членÑтво" %}</div> <div class="block"> <form action="." method="post" enctype="multipart/form-data"> @@ -233,6 +234,7 @@ <button type="submit" class="button">{% translate "Добавить дней" %}</button> </form> </div> + {% endif %} <div class="profile-header">{% translate "Отправить Ñообщение" %}</div> <div class="block"> diff --git a/landing/forms.py b/landing/forms.py index b55e124..77ed290 100644 --- a/landing/forms.py +++ b/landing/forms.py @@ -1,6 +1,8 @@ from django import forms +from django.conf import settings from django.utils.translation import gettext_lazy as _ +from club import features from landing.models import GodSettings @@ -58,6 +60,7 @@ class GodmodeInviteForm(forms.Form): days = forms.IntegerField( label=_("Дней"), - required=True, - initial=365, + required=features.MEMBERSHIP_ENABLED, + disabled=not features.MEMBERSHIP_ENABLED, + initial=settings.DEFAULT_MEMBERSHIP_DAYS, ) diff --git a/landing/views.py b/landing/views.py index 808eba1..731a17e 100644 --- a/landing/views.py +++ b/landing/views.py @@ -5,11 +5,14 @@ from django.conf import settings from django.core.cache import cache from django.db.models import Count from django.http import Http404 -from django.shortcuts import render, redirect +from django.shortcuts import redirect, render +from django.utils.translation import gettext_lazy as _ from auth.helpers import auth_required +from club import features from club.exceptions import AccessDenied -from landing.forms import GodmodeAboutSettingsEditForm, GodmodeDigestEditForm, GodmodeInviteForm +from landing.forms import (GodmodeAboutSettingsEditForm, GodmodeDigestEditForm, + GodmodeInviteForm) from landing.models import GodSettings from notifications.email.invites import send_invited_email from users.models.user import User @@ -96,7 +99,9 @@ def godmode_invite(request): form = GodmodeInviteForm(request.POST, request.FILES) if form.is_valid(): email = form.cleaned_data["email"] - days = form.cleaned_data["days"] + days = 365 * 100 # 100 years + if features.MEMBERSHIP_ENABLED: + days = form.cleaned_data["days"] now = datetime.utcnow() user, is_created = User.objects.get_or_create( email=email, @@ -111,11 +116,11 @@ def godmode_invite(request): ) send_invited_email(request.me, user) return render(request, "message.html", { - "title": "ÐкÑперт приглашен", - "message": f"Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¾Ð½ получит на почту '{email}' уведомление об Ñтом. " - f"Ему будет нужно залогинитьÑÑ Ð¿Ð¾ Ñтой почте и заполнить интро." + "title": _("ÐкÑперт приглашен"), + "message": _(f"Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¾Ð½ получит на почту '{email}' уведомление об Ñтом. " + f"Ему будет нужно залогинитьÑÑ Ð¿Ð¾ Ñтой почте и заполнить интро.") }) else: form = GodmodeInviteForm() - return render(request, "admin/simple_form.html", {"form": form}) + return render(request, "admin/invite.html", {"form": form}) diff --git a/users/models/user.py b/users/models/user.py index 007a9b5..4c27bf0 100644 --- a/users/models/user.py +++ b/users/models/user.py @@ -6,6 +6,7 @@ from django.contrib.postgres.fields import ArrayField from django.db import models from django.db.models import F +from club import features from users.models.geo import Geo from common.models import ModelDiffMixin from utils.slug import generate_unique_slug @@ -175,7 +176,9 @@ class User(models.Model, ModelDiffMixin): @property def is_active_member(self): - return self.is_member and self.membership_expires_at >= datetime.utcnow() + if features.MEMBERSHIP_ENABLED: + return self.is_member and self.membership_expires_at >= datetime.utcnow() + return self.is_member @property def secret_auth_code(self): -- GitLab From 14d9a086a6db09e7e6be4ccde304bfd5e87f311d Mon Sep 17 00:00:00 2001 From: Anton Lebedev <wrawka@gmail.com> Date: Tue, 21 Jan 2025 18:43:51 +0700 Subject: [PATCH 4/6] fixed missing translation --- locale/en/LC_MESSAGES/django.po | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 25a1929..50a351a 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -2514,10 +2514,8 @@ msgid "Удалить аккаунт и обнулить подпиÑку" msgstr "Delete user account and revoke membership" #: users/forms/admin.py:104 -#, fuzzy -#| msgid "Добавить дней" msgid "Добавить дней членÑтва" -msgstr "Add days" +msgstr "Add membership days" #: users/forms/admin.py:117 users/forms/intro.py:15 msgid "Ðикнейм" -- GitLab From 7d6902c51c74c9e5e53931694059a42f3f80ded8 Mon Sep 17 00:00:00 2001 From: Anton Lebedev <wrawka@gmail.com> Date: Tue, 21 Jan 2025 12:21:49 +0000 Subject: [PATCH 5/6] added related manage command --- club/features.py | 1 + club/management/__init__.py | 0 club/management/commands/__init__.py | 0 .../commands/process_feature_flags.py | 19 +++++++++++++++++++ 4 files changed, 20 insertions(+) create mode 100644 club/management/__init__.py create mode 100644 club/management/commands/__init__.py create mode 100644 club/management/commands/process_feature_flags.py diff --git a/club/features.py b/club/features.py index 14479d6..c53ae52 100644 --- a/club/features.py +++ b/club/features.py @@ -10,4 +10,5 @@ PRIVATE_FEED = False # Enable/disable timed membership for users # True — users can buy a membership for a certain period of time # False — membership is disabled, all users have access to all features +# Also see process_membership_flag() in club/management/commands/process_feature_flags.py MEMBERSHIP_ENABLED = False diff --git a/club/management/__init__.py b/club/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/club/management/commands/__init__.py b/club/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/club/management/commands/process_feature_flags.py b/club/management/commands/process_feature_flags.py new file mode 100644 index 0000000..b6540e9 --- /dev/null +++ b/club/management/commands/process_feature_flags.py @@ -0,0 +1,19 @@ +from datetime import datetime + +from django.core.management.base import BaseCommand + +from club import features +from users.models.user import User + + +def process_membership_flag(): + """If membership feature is disabled, extend expiration date for all existing users.""" + if not features.MEMBERSHIP_ENABLED: + User.objects.filter(deleted_at__isnull=True).update(membership_expires_at=datetime(2099, 1, 1)) + + +class Command(BaseCommand): + help = "Process feature flags" + + def handle(self, *args, **options): + process_membership_flag() -- GitLab From d95764ff01fb935c9ba8d521c4c1c4c2cbc4e354 Mon Sep 17 00:00:00 2001 From: Anton Lebedev <wrawka@gmail.com> Date: Wed, 22 Jan 2025 07:51:03 +0000 Subject: [PATCH 6/6] added missing migrations --- ...ter_historicalpost_type_alter_post_type.py | 23 +++++++++++++ ...en_tag_name_ru_usertag_name_en_and_more.py | 33 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 posts/migrations/0006_alter_historicalpost_type_alter_post_type.py create mode 100644 users/migrations/0004_tag_name_en_tag_name_ru_usertag_name_en_and_more.py diff --git a/posts/migrations/0006_alter_historicalpost_type_alter_post_type.py b/posts/migrations/0006_alter_historicalpost_type_alter_post_type.py new file mode 100644 index 0000000..7d36677 --- /dev/null +++ b/posts/migrations/0006_alter_historicalpost_type_alter_post_type.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.17 on 2025-01-22 07:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('posts', '0005_auto_20220908_1950'), + ] + + operations = [ + migrations.AlterField( + model_name='historicalpost', + name='type', + field=models.CharField(choices=[('post', 'СтатьÑ'), ('intro', '#intro'), ('link', 'СÑылка'), ('question', 'ВопроÑ'), ('idea', 'ИдеÑ'), ('project', 'Проект'), ('event', 'Событие'), ('weekly_digest', 'Журнал Toolkit'), ('toolkit', 'Тулкит')], db_index=True, default='post', max_length=32), + ), + migrations.AlterField( + model_name='post', + name='type', + field=models.CharField(choices=[('post', 'СтатьÑ'), ('intro', '#intro'), ('link', 'СÑылка'), ('question', 'ВопроÑ'), ('idea', 'ИдеÑ'), ('project', 'Проект'), ('event', 'Событие'), ('weekly_digest', 'Журнал Toolkit'), ('toolkit', 'Тулкит')], db_index=True, default='post', max_length=32), + ), + ] diff --git a/users/migrations/0004_tag_name_en_tag_name_ru_usertag_name_en_and_more.py b/users/migrations/0004_tag_name_en_tag_name_ru_usertag_name_en_and_more.py new file mode 100644 index 0000000..d28c9da --- /dev/null +++ b/users/migrations/0004_tag_name_en_tag_name_ru_usertag_name_en_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.17 on 2025-01-22 07:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0003_alter_user_email_digest_type'), + ] + + operations = [ + migrations.AddField( + model_name='tag', + name='name_en', + field=models.CharField(max_length=64, null=True), + ), + migrations.AddField( + model_name='tag', + name='name_ru', + field=models.CharField(max_length=64, null=True), + ), + migrations.AddField( + model_name='usertag', + name='name_en', + field=models.CharField(max_length=64, null=True), + ), + migrations.AddField( + model_name='usertag', + name='name_ru', + field=models.CharField(max_length=64, null=True), + ), + ] -- GitLab