From 2a7471d61df0810ac2dad0bc9b64cabbf5981d45 Mon Sep 17 00:00:00 2001 From: KlemenSpruk Date: Thu, 12 Oct 2023 11:05:18 +0200 Subject: [PATCH] saving --- django_project_base/base/event.py | 38 +++++++++++++-- django_project_base/base/models.py | 5 ++ .../commands/list_pending_settings.py | 47 ++++++++++--------- django_project_base/rest/project.py | 43 ++++++++++++++++- .../0009_projectsettings_pending_value.py | 9 ++-- vue/components/project-settings.vue | 16 ++++++- 6 files changed, 125 insertions(+), 33 deletions(-) diff --git a/django_project_base/base/event.py b/django_project_base/base/event.py index 3f4c37f0..0a0cd9ab 100644 --- a/django_project_base/base/event.py +++ b/django_project_base/base/event.py @@ -164,6 +164,30 @@ def confirm(item): confirm(payload) +class ProjectSettingActionRequiredEvent(BaseEvent): + def trigger_changed(self, old_state=None, new_state=None, payload=None, **kwargs): + super().trigger_changed(old_state=old_state, new_state=new_state, payload=payload, **kwargs) + + def trigger(self, payload=None, **kwargs): + super().trigger(payload, **kwargs) + if not payload: + return + + # if to := getattr(settings, "ADMINS", getattr(settings, "MANAGERS", [])): + # # TODO: SEND THIS AS SYSTEM MSG WHEN PR IS MERGED + # EMailNotificationWithListOfEmails( + # message=DjangoProjectBaseMessage( + # subject=gettext"Project settings action required"), + # body=f"{gettext('Action required for setting')} {payload.name} in project {payload.project.name}", + # footer="", + # content_type=DjangoProjectBaseMessage.HTML, + # ), + # recipients=to, + # project=None, + # user=None, + # ).send() + + class ProjectSettingPendingResetEvent(BaseEvent): def trigger_changed(self, old_state=None, new_state=None, payload=None, **kwargs): super().trigger_changed(old_state=old_state, new_state=new_state, payload=payload, **kwargs) @@ -174,11 +198,17 @@ def trigger(self, payload=None, **kwargs): return from django_project_base.aws.ses import AwsSes + pending_value = copy.copy(payload.python_pending_value) payload.pending_value = None - payload.save(update_fields=["value", "pending_value"]) + payload.save(update_fields=["pending_value"]) if payload.name == EMAIL_SENDER_ID_SETTING_NAME: - if payload.python_pending_value in AwsSes.list_sender_emails(): - AwsSes.remove_sender_email(payload.python_pending_value) + if pending_value in AwsSes.list_sender_emails(): + AwsSes.remove_sender_email(pending_value) + AwsSes.add_sender_email(payload.python_value) + if payload.python_value not in AwsSes.list_verified_sender_emails(): + payload.action_required = True + payload.save(update_fields=["action_required"]) if payload.name == SMS_SENDER_ID_SETTING_NAME: - pass + payload.action_required = True + payload.save(update_fields=["action_required"]) diff --git a/django_project_base/base/models.py b/django_project_base/base/models.py index 9779f0fc..476bf6db 100644 --- a/django_project_base/base/models.py +++ b/django_project_base/base/models.py @@ -281,6 +281,7 @@ def clean(self): ) pending_value = models.CharField(max_length=320, null=True, blank=True, verbose_name=_("Pending value")) + action_required = models.BooleanField(default=False, null=True, blank=True) def save(self, force_insert=False, force_update=False, using=None, update_fields=None): self.full_clean() @@ -288,6 +289,10 @@ def save(self, force_insert=False, force_update=False, using=None, update_fields self.value = validator(self.value) if self.pending_value: self.pending_value = validator(self.pending_value) + if self.action_required: + from django_project_base.base.event import ProjectSettingActionRequiredEvent + + ProjectSettingActionRequiredEvent(user=None).trigger(payload=self) super().save(force_insert, force_update, using, update_fields) def delete(self, using=None, keep_parents=False): diff --git a/django_project_base/management/commands/list_pending_settings.py b/django_project_base/management/commands/list_pending_settings.py index 668ca092..eb67c7c0 100644 --- a/django_project_base/management/commands/list_pending_settings.py +++ b/django_project_base/management/commands/list_pending_settings.py @@ -1,34 +1,35 @@ import swapper from django.core.management.base import BaseCommand -from django.shortcuts import get_object_or_404 - -from django_project_base.base.event import ProjectSettingConfirmedEvent class Command(BaseCommand): - help = "Lists pending project settings. Example: python manage.py list_pending_settings 2" - - def add_arguments(self, parser) -> None: - parser.add_argument("project-id", type=str, help="Project identifier") + help = "Lists pending project settings. Example: python manage.py list_pending_settings" def handle(self, *args, **options): - project = get_object_or_404(swapper.load_model("django_project_base", "Project"), pk=str(options["project-id"])) - setting = get_object_or_404( - swapper.load_model("django_project_base", "ProjectSettings"), - project=project, - name=str(options["setting-name"]), - ) - ProjectSettingConfirmedEvent(user=None).trigger(payload=setting) - # TODO: send email when owner is known - # SystemEMailNotification( + result = dict() + for project in swapper.load_model("django_project_base", "Project").objects.all(): + for setting in ( + swapper.load_model("django_project_base", "ProjectSettings") + .objects.filter(project=project, pending_value__isnull=False) + .exclude(pending_value="") + ): + if project.name not in result: + result[project.name] = {} + result[project.name][setting.name] = { + "value": setting.python_value, + "pending_value": setting.python_pending_value, + } + # if to := getattr(settings, "ADMINS", getattr(settings, "MANAGERS", [])): + # TODO: SEND THIS AS SYSTEM MSG WHEN PR IS MERGED + # EMailNotificationWithListOfEmails( # message=DjangoProjectBaseMessage( - # subject=f"{__('Project setting confirmed')}", - # body=f"{__('Setting')} {setting.name} {__('in project')} " - # f"{project.name} {__('has been confirmed and is now active.')}", + # subject=_("Pending settings report"), + # body=json.dumps(result), # footer="", - # content_type=DjangoProjectBaseMessage.PLAIN_TEXT, + # content_type=DjangoProjectBaseMessage.HTML, # ), - # recipients=[], # TODO: find project owner - # user=None, # TODO: find project owner + # recipients=to, + # project=None, + # user=None, # ).send() - return "ok" + self.stdout.write(self.style.WARNING(result)) diff --git a/django_project_base/rest/project.py b/django_project_base/rest/project.py index b86de0db..114ba5c9 100644 --- a/django_project_base/rest/project.py +++ b/django_project_base/rest/project.py @@ -4,6 +4,7 @@ import swapper from django.conf import settings +from django.core.management import call_command from django.db import transaction from django.http import Http404 from django.shortcuts import get_object_or_404 @@ -195,12 +196,28 @@ def __init__(self, *args, is_filter: bool = False, **kwds): ), ), ) + request = self.context.get("request") + if request and (request.user.is_superuser or request.user.is_staff): + self.actions.actions.append( + TableAction( + position=TablePosition.ROW_END, + label="Confirm active", + name="confirm-setting-active", + display_style=dict( + asButton=True, + showIcon=False, + showLabel=False, + ), + ), + ) id = fields.AutoGeneratedField(display=DisplayMode.HIDDEN) project = fields.PrimaryKeyRelatedField( display=DisplayMode.SUPPRESS, queryset=swapper.load_model("django_project_base", "Project").objects.all() ) - pending_value = fields.CharField(display=DisplayMode.SUPPRESS, required=False) + pending_value = fields.CharField( + display=DisplayMode.SUPPRESS, required=False, allow_null=True, default=False, allow_blank=True + ) table_value = fields.CharField( source="value", @@ -235,6 +252,11 @@ def save(self, **kwargs): saved = super().save(**kwargs) return saved + def get_row_css_style(self, obj): + if obj and obj.action_required: + return "background-color:red;" + return "" + def to_representation(self, instance, row_data=None): representation = super().to_representation(instance, row_data) if instance and instance.pending_value: @@ -390,3 +412,22 @@ def reset_pending_setting(self, request) -> Response: if setting.pending_value: ProjectSettingPendingResetEvent(user=request.user).trigger(payload=setting) return Response() + + @extend_schema(exclude=True) + @transaction.atomic() + @action( + detail=False, + methods=["POST"], + url_name="confirm-setting-active", + url_path="confirm-setting-active", + ) + def confirm_setting_active(self, request) -> Response: + if not (request.user.is_superuser or request.user.is_staff): + raise PermissionDenied + setting = get_object_or_404(self.get_serializer_class().Meta.model, pk=self._get_pk_from_request(request)) + call_command( + "confirm_setting", + setting.project.pk, + setting.name, + ) + return Response() diff --git a/example/demo_django_base/migrations/0009_projectsettings_pending_value.py b/example/demo_django_base/migrations/0009_projectsettings_pending_value.py index 86b2ff1e..8f700ba0 100644 --- a/example/demo_django_base/migrations/0009_projectsettings_pending_value.py +++ b/example/demo_django_base/migrations/0009_projectsettings_pending_value.py @@ -12,8 +12,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name="projectsettings", name="pending_value", - field=models.CharField( - max_length=320, null=True, verbose_name="Pending value" - ), + field=models.CharField(max_length=320, null=True, verbose_name="Pending value"), + ), + migrations.AddField( + model_name="projectsettings", + name="action_required", + field=models.BooleanField(default=False, verbose_name="Action required", null=True, blank=True), ), ] diff --git a/vue/components/project-settings.vue b/vue/components/project-settings.vue index ddfdec23..fea09491 100644 --- a/vue/components/project-settings.vue +++ b/vue/components/project-settings.vue @@ -189,7 +189,6 @@ if (selectedProjectId.value) refreshSettingsLogicAndCheckSettings(); watch(selectedProjectId, refreshSettingsLogicAndCheckSettings); const actionResetPending = async (action:Action, payload: FormPayload) => { - console.log(action, payload); const resetData = {}; // @ts-ignore resetData[PROFILE_TABLE_PRIMARY_KEY_PROPERTY_NAME] = payload[PROFILE_TABLE_PRIMARY_KEY_PROPERTY_NAME]; @@ -202,10 +201,23 @@ const actionResetPending = async (action:Action, payload: FormPayload) => { return true; }; +const actionConfirmSettingActive = async (action:Action, payload: FormPayload) => { + const activeData = {}; + // @ts-ignore + active[PROFILE_TABLE_PRIMARY_KEY_PROPERTY_NAME] = payload[PROFILE_TABLE_PRIMARY_KEY_PROPERTY_NAME]; + apiClient.post( + '/project-settings/reset-pending', + activeData, + ).then(() => { + refreshSettingsLogic(); + }); + return true; +}; + const { handler } = useActionHandler(); handler - .register('reset-pending', actionResetPending); + .register('reset-pending', actionResetPending).register('confirm-setting-active', actionConfirmSettingActive);