From a7e41fefbf46ad5bfa2eb97dbd1cc11d3a28a354 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Mon, 30 Sep 2019 16:11:38 +0200 Subject: [PATCH] Add messaging. --- requirements/requirements.txt | 1 + src/club/locale/pl/LC_MESSAGES/django.mo | Bin 2172 -> 3012 bytes src/club/locale/pl/LC_MESSAGES/django.po | 158 +++++++++++++----- src/messaging/__init__.py | 0 src/messaging/admin.py | 34 ++++ src/messaging/apps.py | 5 + src/messaging/locale/pl/LC_MESSAGES/django.mo | Bin 0 -> 1632 bytes src/messaging/locale/pl/LC_MESSAGES/django.po | 95 +++++++++++ src/messaging/management/__init__.py | 0 src/messaging/management/commands/__init__.py | 0 .../management/commands/messaging_send.py | 14 ++ src/messaging/migrations/0001_initial.py | 47 ++++++ src/messaging/migrations/__init__.py | 0 src/messaging/models.py | 77 +++++++++ src/messaging/recipient.py | 6 + src/messaging/states.py | 75 +++++++++ .../messaging/emailtemplate/change_form.html | 19 +++ src/messaging/urls.py | 7 + src/messaging/views.py | 31 ++++ src/wolnelektury/settings/apps.py | 1 + src/wolnelektury/urls.py | 1 + 21 files changed, 531 insertions(+), 40 deletions(-) create mode 100644 src/messaging/__init__.py create mode 100644 src/messaging/admin.py create mode 100644 src/messaging/apps.py create mode 100644 src/messaging/locale/pl/LC_MESSAGES/django.mo create mode 100644 src/messaging/locale/pl/LC_MESSAGES/django.po create mode 100644 src/messaging/management/__init__.py create mode 100644 src/messaging/management/commands/__init__.py create mode 100644 src/messaging/management/commands/messaging_send.py create mode 100644 src/messaging/migrations/0001_initial.py create mode 100644 src/messaging/migrations/__init__.py create mode 100644 src/messaging/models.py create mode 100644 src/messaging/recipient.py create mode 100644 src/messaging/states.py create mode 100644 src/messaging/templates/admin/messaging/emailtemplate/change_form.html create mode 100644 src/messaging/urls.py create mode 100644 src/messaging/views.py diff --git a/requirements/requirements.txt b/requirements/requirements.txt index ad07ec96f..4a9f0ccb4 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -3,6 +3,7 @@ # django Django==2.2.5 fnpdjango==0.4 +docutils #django-pipeline==1.6.13 # Because of https://github.com/jazzband/django-pipeline/pull/682 + https://github.com/getsentry/sentry-python/issues/436 diff --git a/src/club/locale/pl/LC_MESSAGES/django.mo b/src/club/locale/pl/LC_MESSAGES/django.mo index 921c37a129248805e7d826568fa04ec091fabca6..f57a55a51ca48d881e5a85c3ee2e5af1faccbab6 100644 GIT binary patch literal 3012 zcmb`IONbmr7{^OZ)J)WQu{Sf57_z&W1l>){Le?zHMg;?*w!3Dwce<;Z z>gr6oiHIR6qM#@_$OZ&Ky+nkl_&^0Qb5Xo_@*uhBO~k7gAOEkr$C+gHAlS^;zpAgQ zzE@S(Tw1&0EW=ZW+yyy*6=M_N-fQr}b7%!)*Mn2wDsUFO2|Nbg3uXCRicXF-bV zIgtFmQgR6-|8JMP0Fs?AK#KcY5I=SaFIx8#cmwz=Nb&v-QVCWf7#d#%lD+jH#m7ti z7Lf9)mmCKvu1OFk*c3?Xj(~X{V4g?W&p=xL0!Vqh3X;EbAjRZK{`Kgfn?_+5Gk_HLE4XtrT)8; z--8s_rIJ5`bnbow$=+%NOLg7|QXJbrs^2b&iCO z^A5;*2<_1jWE13m2-RvBLT6wbgiQ$L%0PN-LWfZxEm|`oJ<45SJZ^Pcsw?|(p-|fT zP2UyP*X5K!O*u&$Pnu$W-h^5z&sQxrD;K3p-kuP?g@QM=;jUIq--Lxt!99_3(X`TV zc{1`%p&s%`A1@y$=FdwQlK-?|`H0}5R(632o=RbuaIJqrG7$v2!?lv!`eCnYq?;H+ zfs0Tl%CdRwrOeHXN7ZwU6v&1P+iM=DW)fQ+BL1lvmIqzLz&zO$Nnm+t%k$$%$6`K! z%e>+;6n?<+q-9Koe8nSaB54!frs!FDLIkWWQx?i_UYdBpj|Q4C+IB&EEX2anNnU?G zHOV587J){AP>fzNLwXXqd*sVVtirbOB8tnqG&LCvHFg)Imjsga8ZnElFl8;`WS+K} zB{3ZXqp9OedCubq8Ruqwz;H{XO}{PNMUhV#P{=}&iSVoUJHAx@GDW#$B9m>wj(8?ZaAioA_qh^0(iRJi zm(gNIS4HN_?iuKLCvA!DlJYOU?)phSr2PM`HyOU2K~pjubn|b+kqig*=~;!y+z%QJbUElplbDiS@UdJ*xW zJVZgg_T)v2dJ+$|H}Nm<$E62v9=uo(?B|=TcG#KE%)YlfGrPMw{3qY}Hkf>D$T&KR z787R2;F&%Q@*67NfIghuX4Vg{z%;Bl&cj{A*WmzMa`O*ilK2VCz~?XvmtoGVW3O;h zOuTm=KEj>EpP>?cfec&6XyFgYLi+_(z+X3?Cd+o>0@S(@7mq`R9l0lq$pIYgp~57(0;FSydQSHW zRD@(-59T?ld#<@Fhq1fSgJ=QORqaRfsEXAINOW>~&^iGc?>_JmsLsbwHEKb;o_v?9 z2JM!2Eu4v(Vf;09p*S7Xq8s%^YXt4E>IGe=7WO8@mNgcFde^k#%V{sZpN_KSqy9=12 && n%100<=14) ? 2 : 3);\n" "X-Generator: Poedit 2.0.6\n" -#: models.py:25 +#: models.py:24 msgid "a month" msgstr "miesięcznie" -#: models.py:26 +#: models.py:25 msgid "a year" msgstr "raz do roku" -#: models.py:27 +#: models.py:26 msgid "in perpetuity" msgstr "raz na zawsze" -#: models.py:30 +#: models.py:29 msgid "inteval" msgstr "okres" -#: models.py:31 -msgid "min_amount" +#: models.py:30 +msgid "min amount" msgstr "minimalna kwota" +#: models.py:31 +msgid "default amount" +msgstr "domyślna kwota" + #: models.py:32 msgid "allow recurring" msgstr "płatności cykliczne" @@ -48,11 +52,15 @@ msgstr "płatności cykliczne" msgid "allow one time" msgstr "płatności jednorazowe" -#: models.py:36 models.py:69 +#: models.py:34 +msgid "active" +msgstr "aktywny" + +#: models.py:37 models.py:69 msgid "plan" msgstr "plan" -#: models.py:37 +#: models.py:38 msgid "plans" msgstr "plany" @@ -64,7 +72,7 @@ msgstr "klucz" msgid "email" msgstr "email" -#: models.py:68 models.py:123 +#: models.py:68 models.py:129 msgid "membership" msgstr "członkostwo" @@ -77,13 +85,13 @@ msgid "method" msgstr "metoda płatności" #: models.py:72 -msgid "active" -msgstr "aktywny" - -#: models.py:73 msgid "cancelled" msgstr "anulowany" +#: models.py:73 +msgid "payed at" +msgstr "opłacona" + #: models.py:74 msgid "started at" msgstr "start" @@ -92,7 +100,7 @@ msgstr "start" msgid "expires_at" msgstr "wygasa" -#: models.py:79 models.py:105 +#: models.py:79 msgid "schedule" msgstr "harmonogram" @@ -100,51 +108,39 @@ msgstr "harmonogram" msgid "schedules" msgstr "harmonogramy" -#: models.py:106 models.py:120 -msgid "created at" -msgstr "utworzone" - -#: models.py:107 -msgid "payed at" -msgstr "opłacona" - -#: models.py:110 -msgid "payment" -msgstr "płatność" - -#: models.py:111 -msgid "payments" -msgstr "płatności" - -#: models.py:119 +#: models.py:123 msgid "user" msgstr "użytkownik" #: models.py:124 +msgid "created at" +msgstr "utworzone" + +#: models.py:130 msgid "memberships" msgstr "członkostwa" -#: models.py:131 +#: models.py:152 msgid "days before" msgstr "dni przed" -#: models.py:132 +#: models.py:153 msgid "subject" msgstr "temat" -#: models.py:133 +#: models.py:154 payu/models.py:136 msgid "body" msgstr "treść" -#: models.py:136 +#: models.py:157 msgid "reminder email" msgstr "email z przypomnieniem" -#: models.py:137 +#: models.py:158 msgid "reminder emails" msgstr "emaile z przypomnieniem" -#: models.py:142 +#: models.py:163 #, python-format msgid "a day before expiration" msgid_plural "%d days before expiration" @@ -153,7 +149,7 @@ msgstr[1] "%d dni przed wygaśnięciem" msgstr[2] "%d dni przed wygaśnięciem" msgstr[3] "%d dni przed wygaśnięciem" -#: models.py:144 +#: models.py:165 #, python-format msgid "a day after expiration" msgid_plural "%d days after expiration" @@ -161,3 +157,85 @@ msgstr[0] "%d dzień po wygaśnięciu" msgstr[1] "%d dni po wygaśnięciu" msgstr[2] "%d dni po wygaśnięciu" msgstr[3] "%d dni przed wygaśnięciem" + +#: models.py:192 +msgid "Towarzystwo Wolnych Lektur" +msgstr "" + +#: payu/models.py:16 payu/models.py:28 +msgid "POS id" +msgstr "" + +#: payu/models.py:17 +msgid "disposable token" +msgstr "token jednorazowy" + +#: payu/models.py:18 +msgid "reusable token" +msgstr "token wielokrotnego użytku" + +#: payu/models.py:19 +msgid "created_at" +msgstr "utworzone" + +#: payu/models.py:23 +msgid "PayU card token" +msgstr "token PayU karty płatniczej" + +#: payu/models.py:24 +msgid "PayU card tokens" +msgstr "tokeny PayU kart płatniczych" + +#: payu/models.py:29 +msgid "customer IP" +msgstr "adres IP klienta" + +#: payu/models.py:30 +msgid "order ID" +msgstr "ID zamówienia" + +#: payu/models.py:33 +msgid "Pending" +msgstr "Czeka" + +#: payu/models.py:34 +msgid "Waiting for confirmation" +msgstr "Czeka na potwierdzenie" + +#: payu/models.py:35 +msgid "Completed" +msgstr "Ukończone" + +#: payu/models.py:36 +msgid "Canceled" +msgstr "Anulowane" + +#: payu/models.py:37 +msgid "Rejected" +msgstr "Odrzucone" + +#: payu/models.py:42 +msgid "PayU order" +msgstr "zamówienie PayU" + +#: payu/models.py:43 +msgid "PayU orders" +msgstr "zamówienia PayU" + +#: payu/models.py:137 +msgid "received_at" +msgstr "odebrana" + +#: payu/models.py:141 +msgid "PayU notification" +msgstr "notyfikacja PayU" + +#: payu/models.py:142 +msgid "PayU notifications" +msgstr "notyfikacje PayU" + +#~ msgid "payment" +#~ msgstr "płatność" + +#~ msgid "payments" +#~ msgstr "płatności" diff --git a/src/messaging/__init__.py b/src/messaging/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/messaging/admin.py b/src/messaging/admin.py new file mode 100644 index 000000000..1b2bac599 --- /dev/null +++ b/src/messaging/admin.py @@ -0,0 +1,34 @@ +from django.contrib import admin +from .models import EmailTemplate, EmailSent + + +class EmailSentInline(admin.TabularInline): + model = EmailSent + fields = ['timestamp', 'email', 'subject'] + readonly_fields = ['timestamp', 'email', 'subject'] + extra = 0 + can_delete = False + show_change_link = True + + def has_add_permission(self, request, obj): + return False + + +class EmailTemplateAdmin(admin.ModelAdmin): + list_display = ['state', 'days', 'subject', 'hour'] + inlines = [EmailSentInline] + + +admin.site.register(EmailTemplate, EmailTemplateAdmin) + + +class EmailSentAdmin(admin.ModelAdmin): + list_filter = ['template'] + list_display = ['timestamp', 'template', 'email', 'subject'] + fields = ['timestamp', 'template', 'email', 'subject', 'body', 'hash_value'] + readonly_fields = fields + change_links = ['template'] + + +admin.site.register(EmailSent, EmailSentAdmin) + diff --git a/src/messaging/apps.py b/src/messaging/apps.py new file mode 100644 index 000000000..0c0cd1cd4 --- /dev/null +++ b/src/messaging/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class MessagingConfig(AppConfig): + name = 'messaging' diff --git a/src/messaging/locale/pl/LC_MESSAGES/django.mo b/src/messaging/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..318b4a710566a31e44d7ddb6f419e65b35cf6b98 GIT binary patch literal 1632 zcmbW0&yO256vquLzfuZ_9zcS_W2LPUbtgNMma3bbq*9@U6=p`U|++I!Ty1{@xfD!9ffVfRa_zHLr zJOcg(y7=G0XTd+f=fS_gU!m~;JO=)NAm{%DL95_Bum%1JJ_9;2r?-MHSN}-OH^Eo2 zK2b9SU7a1!>0Sn%?&lz6$^l9CBFyc(*q2~OVQ#oPxO2PV?)3u9DF#VWeZxzj1@snki<Yt2|k=4z2A zEwkERYhDxiP#aO_g&Vw2dblql+kRN)GhI-`6=j7biH~w0I~KGjMG_kdMcV(kDD%P% z%!#YGbe_}ktnw5_su9K7lG2u2snt3qaw1Shn*VdBwv3n8YO^6KvnVNsl!|mHax<10 ziR+onp;K*Se3lAjsZe`TNi!BPi}}nj;iX(AOt^0{h^VJlq*=nPcw~tL7ip@{u`Y6E za09_ibz`=hYxj$J+p+fv4uY$7sc}hUI=9}A8Ohi?SB#7|&~Wd5t{VS+~<`s$MUkv*c5knrbcZ`@IewKTd8M_D;3jy!88F(4m_*%MHID zVCO6aNK$Vj|1hZE^Owm%%f;74-&y)Mmwu?BYApx7s%!^kPqwxiSG@Oy5_$Ct?rIU^ z@pjsN`*eeS=(@=~NjK@C6cbB!mKBfFSO00K9lf|O%$$nF{n^1?8fltRrV}2G%eEQd z#o4NFWaY@k;-`bVrE8rgI-k$*2KNu{L^LlOD%zKrO>&)04(_bue`-HLPuWU^Mf1gN zttQ&osXN#}PkAXgm4c?T5jUJEDPld*i?5=&R-CfMZElrzXM7}q#Y!G_+;nCTr)E@l kpqcYwqE)@*tX?1POI9a{Izq6Kj^|P#(sISnuiUbK0Sm9b^8f$< literal 0 HcmV?d00001 diff --git a/src/messaging/locale/pl/LC_MESSAGES/django.po b/src/messaging/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 000000000..d2a3c98f2 --- /dev/null +++ b/src/messaging/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,95 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-09-30 16:09+0200\n" +"PO-Revision-Date: 2019-09-30 16:10+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n" +"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n" +"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" +"X-Generator: Poedit 2.0.6\n" + +#: models.py:11 +msgid "state" +msgstr "stan" + +#: models.py:12 models.py:68 +msgid "subject" +msgstr "temat" + +#: models.py:13 models.py:69 +msgid "body" +msgstr "treść" + +#: models.py:14 +msgid "days" +msgstr "dni" + +#: models.py:15 +msgid "hour" +msgstr "godzina" + +#: models.py:18 +msgid "email template" +msgstr "szablon e-maila" + +#: models.py:19 +msgid "email templates" +msgstr "szablony e-maili" + +#: models.py:67 +msgid "e-mail" +msgstr "e-mail" + +#: models.py:72 +msgid "email sent" +msgstr "wysłany e-mail" + +#: models.py:73 +msgid "emails sent" +msgstr "wysłane e-maile" + +#: states.py:47 +msgid "club membership expiring" +msgstr "członkostwo w Towarzystwie wygasa" + +#: states.py:61 +msgid "club payment unfinished" +msgstr "niedokończona płatność w Towarzystwie" + +#: views.py:19 +#, python-format +msgid "" +"Context:
\n" +" {{ %(model_name)s }} – a " +"%(verbose_name)s object.
\n" +" You can put it in in the fields Subject and Body " +"using dot notation, like this:
\n" +" {{ %(model_name)s.id }}." +msgstr "" +"Kontest:
\n" +" {{ %(model_name)s }} – obiekt typu %(verbose_name)s.
\n" +" Możesz użyć go w polach Temat i Treść, korzystając " +"z notacji z kropką, np.:
\n" +" {{ %(model_name)s.id }}." + +#~ msgid "New member" +#~ msgstr "Nowy członek" + +#~ msgid "event" +#~ msgstr "zdarzenie" + +#~ msgid "Select an event to see more details." +#~ msgstr "Wybierz zdarzenie by zobaczyć więcej szczegółów." diff --git a/src/messaging/management/__init__.py b/src/messaging/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/messaging/management/commands/__init__.py b/src/messaging/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/messaging/management/commands/messaging_send.py b/src/messaging/management/commands/messaging_send.py new file mode 100644 index 000000000..6fb4a696c --- /dev/null +++ b/src/messaging/management/commands/messaging_send.py @@ -0,0 +1,14 @@ +from django.core.management.base import BaseCommand, CommandError +from messaging.models import EmailTemplate + + +class Command(BaseCommand): + help = 'Send emails defined in templates.' + + def add_arguments(self, parser): + parser.add_argument('--dry-run', action='store_true', help='Dry run') + + def handle(self, *args, **options): + for et in EmailTemplate.objects.all(): + et.run(verbose=True, dry_run=options['dry_run']) + diff --git a/src/messaging/migrations/0001_initial.py b/src/messaging/migrations/0001_initial.py new file mode 100644 index 000000000..9af97607c --- /dev/null +++ b/src/messaging/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 2.2.5 on 2019-09-30 13:11 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='EmailTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('state', models.CharField(choices=[('club-membership-expiring', 'club membership expiring'), ('club-payment-unfinished', 'club payment unfinished')], help_text='?', max_length=128, verbose_name='state')), + ('subject', models.CharField(max_length=1024, verbose_name='subject')), + ('body', models.TextField(verbose_name='body')), + ('days', models.SmallIntegerField(blank=True, null=True, verbose_name='days')), + ('hour', models.IntegerField(blank=True, null=True, verbose_name='hour')), + ], + options={ + 'verbose_name': 'email template', + 'verbose_name_plural': 'email templates', + }, + ), + migrations.CreateModel( + name='EmailSent', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('hash_value', models.CharField(max_length=1024)), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('email', models.CharField(max_length=1024, verbose_name='e-mail')), + ('subject', models.CharField(max_length=1024, verbose_name='subject')), + ('body', models.TextField(verbose_name='body')), + ('template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='messaging.EmailTemplate')), + ], + options={ + 'verbose_name': 'email sent', + 'verbose_name_plural': 'emails sent', + 'ordering': ('-timestamp',), + }, + ), + ] diff --git a/src/messaging/migrations/__init__.py b/src/messaging/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/messaging/models.py b/src/messaging/models.py new file mode 100644 index 000000000..feb5daaa4 --- /dev/null +++ b/src/messaging/models.py @@ -0,0 +1,77 @@ +from django.conf import settings +from django.core.mail import send_mail +from django.db import models +from django.template import Template, Context +from django.utils.translation import ugettext_lazy as _ +from sentry_sdk import capture_exception +from .states import states + + +class EmailTemplate(models.Model): + state = models.CharField(_('state'), max_length=128, choices=[(s.slug, s.name) for s in states], help_text='?') + subject = models.CharField(_('subject'), max_length=1024) + body = models.TextField(_('body')) + days = models.SmallIntegerField(_('days'), null=True, blank=True) + hour = models.IntegerField(_('hour'), null=True, blank=True) + + class Meta: + verbose_name = _('email template') + verbose_name_plural = _('email templates') + + def __str__(self): + return '%s (%+d)' % (self.get_state_display(), self.days) + return self.subject + + def run(self, time=None, verbose=False, dry_run=False): + state = self.get_state() + recipients = state(time=time, offset=self.days).get_recipients() + hash_values = set(recipient.hash_value for recipient in recipients) + sent = set(EmailSent.objects.filter( + template=self, hash_value__in=hash_values + ).values_list('hash_value', flat=True)) + for recipient in recipients: + if recipient.hash_value in sent: + continue + self.send(recipient, verbose=verbose, dry_run=dry_run) + + def get_state(self): + for s in states: + if s.slug == self.state: + return s + raise ValueError('Unknown state', s.state) + + def send(self, recipient, verbose=False, dry_run=False): + subject = Template(self.subject).render(Context(recipient.context)) + body = Template(self.body).render(Context(recipient.context)) + if verbose: + print(recipient.email, subject) + if not dry_run: + try: + send_mail(subject, body, settings.CONTACT_EMAIL, [recipient.email], fail_silently=False) + except: + capture_exception() + else: + self.emailsent_set.create( + hash_value=recipient.hash_value, + email=recipient.email, + subject=subject, + body=body, + ) + + + +class EmailSent(models.Model): + template = models.ForeignKey(EmailTemplate, models.CASCADE) + hash_value = models.CharField(max_length=1024) + timestamp = models.DateTimeField(auto_now_add=True) + email = models.CharField(_('e-mail'), max_length=1024) + subject = models.CharField(_('subject'), max_length=1024) + body = models.TextField(_('body')) + + class Meta: + verbose_name = _('email sent') + verbose_name_plural = _('emails sent') + ordering = ('-timestamp',) + + def __str__(self): + return '%s %s' % (self.email, self.timestamp) diff --git a/src/messaging/recipient.py b/src/messaging/recipient.py new file mode 100644 index 000000000..e1e83132d --- /dev/null +++ b/src/messaging/recipient.py @@ -0,0 +1,6 @@ +class Recipient: + def __init__(self, email, hash_value, context): + self.email = email + self.hash_value = hash_value + self.context = context + diff --git a/src/messaging/states.py b/src/messaging/states.py new file mode 100644 index 000000000..317be2170 --- /dev/null +++ b/src/messaging/states.py @@ -0,0 +1,75 @@ +from datetime import timedelta +from django.utils.timezone import now +from django.utils.translation import ugettext_lazy as _ +from .recipient import Recipient + + +class State: + allow_negative_offset = False + context_fields = [] + + + def __init__(self, offset=0, time=None): + self.time = time or now() + if isinstance(offset, int): + offset = timedelta(offset) + self.offset = offset + + def get_recipients(self): + return [ + Recipient( + hash_value=self.get_hash_value(obj), + email=self.get_email(obj), + context=self.get_context(obj), + ) + for obj in self.get_objects() + ] + + def get_objects(self): + raise NotImplemented + + def get_hash_value(self, obj): + return str(obj.pk) + + def get_email(self, obj): + return obj.email + + def get_context(self, obj): + ctx = { + obj._meta.model_name: obj, + } + return ctx + + +class ClubMembershipExpiring(State): + slug = 'club-membership-expiring' + allow_negative_offset = True + name = _('club membership expiring') + + def get_objects(self): + from club.models import Schedule + return Schedule.objects.filter( + is_active=True, + expires_at__lt=self.time - self.offset + ) + + def get_hashed_value(self, obj): + return '%s:%s' % (obj.pk, obj.expires_at.isoformat()) + +class ClubPaymentUnfinished(State): + slug = 'club-payment-unfinished' + name = _('club payment unfinished') + + def get_objects(self): + from club.models import Schedule + return Schedule.objects.filter( + payuorder=None, + started_at__lt=self.time - self.offset, + ) + + +states = [ + ClubMembershipExpiring, + ClubPaymentUnfinished, +] + diff --git a/src/messaging/templates/admin/messaging/emailtemplate/change_form.html b/src/messaging/templates/admin/messaging/emailtemplate/change_form.html new file mode 100644 index 000000000..89c95d21b --- /dev/null +++ b/src/messaging/templates/admin/messaging/emailtemplate/change_form.html @@ -0,0 +1,19 @@ +{% extends 'admin/change_form.html' %} + + +{% block admin_change_form_document_ready %} +{{ block.super }} + + + +{% endblock %} diff --git a/src/messaging/urls.py b/src/messaging/urls.py new file mode 100644 index 000000000..8f1fc1f85 --- /dev/null +++ b/src/messaging/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from . import views + + +urlpatterns = [ + path('states//info.json', views.state_info), +] diff --git a/src/messaging/views.py b/src/messaging/views.py new file mode 100644 index 000000000..be83d95f2 --- /dev/null +++ b/src/messaging/views.py @@ -0,0 +1,31 @@ +import json +from django.http import JsonResponse +from django.urls import reverse +from django.shortcuts import render +from django.utils.translation import ugettext as _ +from .states import states + + +def state_info(request, slug): + for state in states: + if state.slug == slug: + break + else: + return JsonResponse({}) + + meta = state().get_objects().model._meta + + + help_text = _('''Context:
+ {{ %(model_name)s }} – a %(verbose_name)s object.
+ You can put it in in the fields Subject and Body using dot notation, like this:
+ {{ %(model_name)s.id }}.''') % { + 'model_name': meta.model_name, + 'docs_url': reverse('django-admindocs-models-detail', args=(meta.app_label, meta.model_name)), + 'verbose_name': meta.verbose_name, + } + + return JsonResponse({ + "help": help_text, + }) + diff --git a/src/wolnelektury/settings/apps.py b/src/wolnelektury/settings/apps.py index fa2f82f31..5b55b049e 100644 --- a/src/wolnelektury/settings/apps.py +++ b/src/wolnelektury/settings/apps.py @@ -12,6 +12,7 @@ INSTALLED_APPS_OUR = [ 'dictionary', 'infopages', 'lesmianator', + 'messaging', 'newtagging', 'opds', 'pdcounter', diff --git a/src/wolnelektury/urls.py b/src/wolnelektury/urls.py index 6188658cd..c328c83f5 100644 --- a/src/wolnelektury/urls.py +++ b/src/wolnelektury/urls.py @@ -44,6 +44,7 @@ urlpatterns += [ url(r'^newsletter/', include('newsletter.urls')), url(r'^formularz/', include('forms_builder.forms.urls')), url(r'^isbn/', include('isbn.urls')), + url(r'^messaging/', include('messaging.urls')), url(r'^paypal/app-form/$', RedirectView.as_view( url='/towarzystwo/?app=1', permanent=False)), -- 2.20.1