From 7792674ed789fc40b5d155d8862f70709d1d8f06 Mon Sep 17 00:00:00 2001 From: Stefan Wehrmeyer Date: Fri, 11 Mar 2022 23:40:37 +0100 Subject: [PATCH] Add plan follower model and configuration --- froide_govplan/apps.py | 7 ++ froide_govplan/configuration.py | 80 +++++++++++++++++++ .../migrations/0004_auto_20220311_2330.py | 49 ++++++++++++ froide_govplan/models.py | 14 ++++ 4 files changed, 150 insertions(+) create mode 100644 froide_govplan/configuration.py create mode 100644 froide_govplan/migrations/0004_auto_20220311_2330.py diff --git a/froide_govplan/apps.py b/froide_govplan/apps.py index c9b9184..680e907 100644 --- a/froide_govplan/apps.py +++ b/froide_govplan/apps.py @@ -5,3 +5,10 @@ from django.utils.translation import gettext_lazy as _ class FroideGovPlanConfig(AppConfig): name = "froide_govplan" verbose_name = _("GovPlan App") + + def ready(self): + from froide.follow.configuration import follow_registry + + from .configuration import GovernmentPlanFollowConfiguration + + follow_registry.register(GovernmentPlanFollowConfiguration()) diff --git a/froide_govplan/configuration.py b/froide_govplan/configuration.py new file mode 100644 index 0000000..6489193 --- /dev/null +++ b/froide_govplan/configuration.py @@ -0,0 +1,80 @@ +from datetime import datetime +from typing import Iterator + +from django.utils.translation import gettext_lazy as _ + +from froide.follow.configuration import FollowConfiguration +from froide.helper.notifications import Notification, TemplatedEvent + +from .admin import get_allowed_plans +from .models import GovernmentPlanFollower, GovernmentPlanUpdate + + +class GovernmentPlanFollowConfiguration(FollowConfiguration): + model = GovernmentPlanFollower + title: str = _("Government plans") + slug: str = "govplan" + follow_message: str = _("You are now following this plan.") + unfollow_message: str = _("You are not following this plan anymore.") + confirm_email_message: str = _( + "Check your emails and click the confirmation link in order to follow this government plan." + ) + action_labels = { + "follow": _("Follow plan"), + "follow_q": _("Follow plan?"), + "unfollow": _("Unfollow plan"), + "following": _("Following plan"), + "follow_description": _( + "You will get notifications via email when something new happens with this plan. You can unsubscribe anytime." + ), + } + + def get_content_object_queryset(self, request): + return get_allowed_plans(request) + + def can_follow(self, content_object, user, request=None): + if request: + get_allowed_plans(request) + + return super().can_follow(content_object, user) + + def get_batch_updates( + self, start: datetime, end: datetime + ) -> Iterator[Notification]: + yield from get_plan_updates(start, end) + + def get_confirm_follow_message(self, content_object): + return _( + "please confirm that you want to follow the plan “{title}” by clicking this link:" + ).format(title=content_object.title) + + def email_changed(self, user): + # Move all confirmed email subscriptions of new email + # to user except own requests + self.model.objects.filter(email=user.email, confirmed=True).update( + email="", user=user + ) + + +def get_plan_updates(start: datetime, end: datetime): + plan_updates = GovernmentPlanUpdate.objects.filter( + public=True, timestamp__gte=start, timestamp__lt=end + ).select_related("plan") + + for plan_update in plan_updates: + yield Notification( + section=_("Government Plans"), + event_type="planupdate", + object=plan_update.plan, + object_label=plan_update.plan.title, + timestamp=plan_update.timestamp, + event=make_plan_event(plan_update.plan), + user_id=None, + ) + + +def make_plan_event(plan): + return TemplatedEvent( + _("An update was posted for the government plan “{title}”."), + title=plan.title, + ) diff --git a/froide_govplan/migrations/0004_auto_20220311_2330.py b/froide_govplan/migrations/0004_auto_20220311_2330.py new file mode 100644 index 0000000..92809d2 --- /dev/null +++ b/froide_govplan/migrations/0004_auto_20220311_2330.py @@ -0,0 +1,49 @@ +# Generated by Django 3.2.12 on 2022-03-11 22:30 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('froide_govplan', '0003_auto_20220228_1051'), + ] + + operations = [ + migrations.AlterField( + model_name='governmentplanscmsplugin', + name='template', + field=models.CharField(blank=True, choices=[('froide_govplan/plugins/default.html', 'Normal'), ('froide_govplan/plugins/progress.html', 'Progress'), ('froide_govplan/plugins/card_cols.html', 'Card columns')], help_text='template used to display the plugin', max_length=250, verbose_name='template'), + ), + migrations.CreateModel( + name='GovernmentPlanFollower', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('email', models.CharField(blank=True, max_length=255)), + ('confirmed', models.BooleanField(default=False)), + ('timestamp', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Timestamp of Following')), + ('context', models.JSONField(blank=True, null=True)), + ('content_object', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='followers', to='froide_govplan.governmentplan', verbose_name='Government plan')), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'Government plan follower', + 'verbose_name_plural': 'Government plan followers', + 'ordering': ('-timestamp',), + 'get_latest_by': 'timestamp', + 'abstract': False, + }, + ), + migrations.AddConstraint( + model_name='governmentplanfollower', + constraint=models.UniqueConstraint(condition=models.Q(('user__isnull', False)), fields=('content_object', 'user'), name='unique_user_follower_froide_govplan_governmentplanfollower'), + ), + migrations.AddConstraint( + model_name='governmentplanfollower', + constraint=models.UniqueConstraint(condition=models.Q(('user__isnull', True)), fields=('content_object', 'email'), name='unique_email_follower_froide_govplan_governmentplanfollower'), + ), + ] diff --git a/froide_govplan/models.py b/froide_govplan/models.py index 97dc7a9..bd100df 100644 --- a/froide_govplan/models.py +++ b/froide_govplan/models.py @@ -10,6 +10,7 @@ from taggit.managers import TaggableManager from taggit.models import TaggedItemBase from froide.foirequest.models import FoiRequest +from froide.follow.models import Follower from froide.organization.models import Organization from froide.publicbody.models import Category, Jurisdiction, PublicBody @@ -237,6 +238,19 @@ class GovernmentPlanUpdate(models.Model): return "{} - {} ({})".format(self.title, self.timestamp, self.plan) +class GovernmentPlanFollower(Follower): + content_object = models.ForeignKey( + GovernmentPlan, + on_delete=models.CASCADE, + related_name="followers", + verbose_name=_("Government plan"), + ) + + class Meta(Follower.Meta): + verbose_name = _("Government plan follower") + verbose_name_plural = _("Government plan followers") + + if CMSPlugin: PLUGIN_TEMPLATES = [