# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
from datetime import datetime, timedelta
+from django.apps import apps
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.mail import send_mail
def save(self, *args, **kwargs):
if not self.key:
self.key = get_random_hash(self.email)
- return super(Schedule, self).save(*args, **kwargs)
+ super(Schedule, self).save(*args, **kwargs)
+ self.update_contact()
def initiate_payment(self, request):
return self.get_payment_method().initiate(request, self)
self.email_sent = True
self.save()
+ def update_contact(self):
+ Contact = apps.get_model('messaging', 'Contact')
+ if not self.payed_at:
+ level = Contact.TRIED
+ since = self.started_at
+ else:
+ since = self.payed_at
+ if self.is_recurring():
+ level = Contact.RECURRING
+ else:
+ level = Contact.SINGLE
+ Contact.update(self.email, level, since, self.expires_at)
+
class Membership(models.Model):
""" Represents a user being recognized as a member of the club. """
from django.contrib import admin
-from .models import EmailTemplate, EmailSent
+from django.utils.translation import ugettext_lazy as _
+from . import models
class EmailSentInline(admin.TabularInline):
- model = EmailSent
+ model = models.EmailSent
fields = ['timestamp', 'email', 'subject']
readonly_fields = ['timestamp', 'email', 'subject']
extra = 0
class EmailTemplateAdmin(admin.ModelAdmin):
- list_display = ['state', 'days', 'subject', 'hour']
+ list_display = ['state', 'min_days_since', 'subject', 'min_hour']
inlines = [EmailSentInline]
-
-
-admin.site.register(EmailTemplate, EmailTemplateAdmin)
+ fieldsets = [
+ (None, {"fields": [
+ 'state',
+ ('min_days_since', 'max_days_since'),
+ 'is_active',
+ ]}),
+ (_('E-mail content'), {"fields": [
+ 'subject', 'body'
+ ]}),
+ (_('Sending constraints'), {"fields": [
+ ('min_day_of_month', 'max_day_of_month'),
+ ('dow_1', 'dow_2', 'dow_3', 'dow_4', 'dow_5', 'dow_6', 'dow_7'),
+ ('min_hour', 'max_hour'),
+ ]}),
+ ]
+
+
+admin.site.register(models.EmailTemplate, EmailTemplateAdmin)
class EmailSentAdmin(admin.ModelAdmin):
change_links = ['template']
-admin.site.register(EmailSent, EmailSentAdmin)
+admin.site.register(models.EmailSent, EmailSentAdmin)
+
+
+class ContactAdmin(admin.ModelAdmin):
+ list_filter = ['level']
+ list_display = ['email', 'level', 'since', 'expires_at']
+ search_fields = ['email']
+ date_hierarchy = 'since'
+
+admin.site.register(models.Contact, ContactAdmin)
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-01-17 13:27+0100\n"
-"PO-Revision-Date: 2020-01-17 13:27+0100\n"
+"POT-Creation-Date: 2020-01-28 22:49+0100\n"
+"PO-Revision-Date: 2020-01-28 22:51+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: pl\n"
"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
"X-Generator: Poedit 2.2.4\n"
+#: admin.py:27
+msgid "E-mail content"
+msgstr "Zawartość e-maila"
+
+#: admin.py:30
+msgid "Sending constraints"
+msgstr "Ograniczenia wysyłki"
+
#: models.py:11
msgid "state"
msgstr "stan"
-#: models.py:12 models.py:69
+#: models.py:12 models.py:79
msgid "subject"
msgstr "temat"
-#: models.py:13 models.py:70
+#: models.py:13 models.py:80
msgid "body"
msgstr "treść"
#: models.py:14
-msgid "days"
-msgstr "dni"
+msgid "min days since"
+msgstr "dni po, od"
#: models.py:15
-msgid "hour"
-msgstr "godzina"
+msgid "max days since"
+msgstr "dni po, do"
#: models.py:16
+msgid "max hour"
+msgstr "do godziny"
+
+#: models.py:17
+msgid "min hour"
+msgstr "od godziny"
+
+#: models.py:18
+msgid "min day of month"
+msgstr "od dnia miesiąca"
+
+#: models.py:19
+msgid "max day of month"
+msgstr "do dnia miesiąca"
+
+#: models.py:20
+msgid "Monday"
+msgstr "poniedziałek"
+
+#: models.py:21
+msgid "Tuesday"
+msgstr "wtorek"
+
+#: models.py:22
+msgid "Wednesday"
+msgstr "środa"
+
+#: models.py:23
+msgid "Thursday"
+msgstr "czwartek"
+
+#: models.py:24
+msgid "Friday"
+msgstr "piątek"
+
+#: models.py:25
+msgid "Saturday"
+msgstr "sobota"
+
+#: models.py:26
+msgid "Sunday"
+msgstr "niedziela"
+
+#: models.py:27
msgid "active"
msgstr "aktywny"
-#: models.py:19
+#: models.py:30
msgid "email template"
msgstr "szablon e-maila"
-#: models.py:20
+#: models.py:31
msgid "email templates"
msgstr "szablony e-maili"
-#: models.py:68
+#: models.py:78
msgid "e-mail"
msgstr "e-mail"
-#: models.py:73
+#: models.py:83
msgid "email sent"
msgstr "wysłany e-mail"
-#: models.py:74
+#: models.py:84
msgid "emails sent"
msgstr "wysłane e-maile"
-#: states.py:47
-msgid "club membership expiring"
-msgstr "członkostwo w Towarzystwie wygasa"
+#: models.py:101
+msgid "Would-be donor"
+msgstr "Niedoszły darczyńca"
+
+#: models.py:102
+msgid "One-time donor"
+msgstr "Darczyńca z jednorazową wpłatą"
+
+#: models.py:103
+msgid "Recurring donor"
+msgstr "Darczyńca z wpłatą cykliczną"
+
+#: models.py:104
+msgid "Cold"
+msgstr "Lodówka"
+
+#: models.py:105
+msgid "Opt out"
+msgstr "Opt-out"
-#: states.py:62
-msgid "club payment unfinished"
-msgstr "niedokończona płatność w Towarzystwie"
+#: states.py:46
+msgid "club one-time donors"
+msgstr "darczyńcy z jednorazową wpłatą"
-#: states.py:74
-msgid "club recurring payment problem"
-msgstr "problem z płatnością cykliczną w Towarzystwie"
+#: states.py:52
+msgid "club one-time donors with donation expiring"
+msgstr "darczyńcy z wygasającą jednorazową wpłatą"
+
+#: states.py:67
+msgid "club would-be donors"
+msgstr "niedoszli darczyńcy"
+
+#: states.py:79
+msgid "club recurring donors"
+msgstr "darczyńcy z wpłatą cykluczną"
+
+#: states.py:84
+msgid "club recurring donors with donation expired"
+msgstr "darczyńcy z wygasającą wpłatą cykliczną"
+
+#: states.py:93
+msgid "cold group"
+msgstr "lodówka"
#: views.py:19
#, python-format
"z notacji z kropką, np.:<br>\n"
" <code>{{ %(model_name)s.id }}</code>."
+#~ msgid "days"
+#~ msgstr "dni"
+
+#~ msgid "club payment unfinished"
+#~ msgstr "niedokończona płatność w Towarzystwie"
+
#~ msgid "New member"
#~ msgstr "Nowy członek"
--- /dev/null
+# Generated by Django 2.2.9 on 2020-01-28 21:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('messaging', '0002_auto_20200117_1326'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Contact',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('email', models.EmailField(max_length=254, unique=True)),
+ ('level', models.PositiveSmallIntegerField(choices=[(20, 'Tried'), (30, 'Single'), (40, 'Recurring'), (10, 'Cold'), (50, 'Opt out')])),
+ ('since', models.DateTimeField()),
+ ('expires_at', models.DateTimeField(blank=True, null=True)),
+ ],
+ ),
+ migrations.RemoveField(
+ model_name='emailtemplate',
+ name='days',
+ ),
+ migrations.RemoveField(
+ model_name='emailtemplate',
+ name='hour',
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='dow_1',
+ field=models.BooleanField(default=True, verbose_name='monday'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='dow_2',
+ field=models.BooleanField(default=True, verbose_name='tuesday'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='dow_3',
+ field=models.BooleanField(default=True, verbose_name='wednesday'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='dow_4',
+ field=models.BooleanField(default=True, verbose_name='thursday'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='dow_5',
+ field=models.BooleanField(default=True, verbose_name='friday'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='dow_6',
+ field=models.BooleanField(default=True, verbose_name='saturday'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='dow_7',
+ field=models.BooleanField(default=True, verbose_name='sunday'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='max_day_of_month',
+ field=models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='max day of month'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='max_days_since',
+ field=models.SmallIntegerField(blank=True, null=True, verbose_name='max_days_since'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='max_hour',
+ field=models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='max hour'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='min_day_of_month',
+ field=models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='min day of month'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='min_days_since',
+ field=models.SmallIntegerField(blank=True, null=True, verbose_name='min_days_since'),
+ ),
+ migrations.AddField(
+ model_name='emailtemplate',
+ name='min_hour',
+ field=models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='min hour'),
+ ),
+ migrations.AlterField(
+ model_name='emailtemplate',
+ name='state',
+ field=models.CharField(choices=[('cold', 'cold group'), ('club-payment-unfinished', 'club would-be donor'), ('club-single', 'club one-time donors'), ('club-membership-expiring', 'club one-time donors with donation expiring'), ('club-recurring', 'club recurring donor'), ('club-recurring-payment-problem', 'club recurring donors with donation expired')], help_text='?', max_length=128, verbose_name='state'),
+ ),
+ ]
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)
+ min_days_since = models.SmallIntegerField(_('min days since'), null=True, blank=True)
+ max_days_since = models.SmallIntegerField(_('max days since'), null=True, blank=True)
+ min_hour = models.PositiveSmallIntegerField(_('min hour'), null=True, blank=True)
+ max_hour = models.PositiveSmallIntegerField(_('max hour'), null=True, blank=True)
+ min_day_of_month = models.PositiveSmallIntegerField(_('min day of month'), null=True, blank=True)
+ max_day_of_month = models.PositiveSmallIntegerField(_('max day of month'), null=True, blank=True)
+ dow_1 = models.BooleanField(_('Monday'), default=True)
+ dow_2 = models.BooleanField(_('Tuesday'), default=True)
+ dow_3 = models.BooleanField(_('Wednesday'), default=True)
+ dow_4 = models.BooleanField(_('Thursday'), default=True)
+ dow_5 = models.BooleanField(_('Friday'), default=True)
+ dow_6 = models.BooleanField(_('Saturday'), default=True)
+ dow_7 = models.BooleanField(_('Sunday'), default=True)
is_active = models.BooleanField(_('active'), default=False)
class Meta:
)
-
class EmailSent(models.Model):
template = models.ForeignKey(EmailTemplate, models.CASCADE)
hash_value = models.CharField(max_length=1024)
def __str__(self):
return '%s %s' % (self.email, self.timestamp)
+
+
+class Contact(models.Model):
+ COLD = 10
+ TRIED = 20
+ SINGLE = 30
+ RECURRING = 40
+ OPT_OUT = 50
+
+ email = models.EmailField(unique=True)
+ level = models.PositiveSmallIntegerField(
+ choices=[
+ (TRIED, _('Would-be donor')),
+ (SINGLE, _('One-time donor')),
+ (RECURRING, _('Recurring donor')),
+ (COLD, _('Cold')),
+ (OPT_OUT, _('Opt out')),
+ ])
+ since = models.DateTimeField()
+ expires_at = models.DateTimeField(null=True, blank=True)
+
+ @classmethod
+ def update(cls, email, level, since, expires_at=None):
+ obj, created = cls.objects.get_or_create(email=email, defaults={
+ "level": level,
+ "since": since,
+ "expires_at": expires_at
+ })
+ if not created:
+ obj.ascend(level, since, expires_at)
+
+ def ascend(self, level, since, expires_at=None):
+ if level < self.level:
+ return
+ if level == self.level:
+ self.since = min(since, self.since)
+
+ if expires_at and self.expires_at:
+ self.expires_at = max(expires_at, self.expires_at)
+ else:
+ self.expires_at = expires_at
+ else:
+ self.level = level
+ self.since = since
+ self.expires_at = expires_at
+ self.save()
+
return ctx
-class ClubMembershipExpiring(State):
+class ClubSingle(State):
+ slug = 'club-single'
+ name = _('club one-time donors')
+
+
+class ClubSingleExpired(State):
slug = 'club-membership-expiring'
allow_negative_offset = True
- name = _('club membership expiring')
+ name = _('club one-time donors with donation expiring')
def get_objects(self):
from club.models import Schedule
return '%s:%s' % (obj.pk, obj.expires_at.isoformat())
-class ClubPaymentUnfinished(State):
+class ClubTried(State):
slug = 'club-payment-unfinished'
- name = _('club payment unfinished')
+ name = _('club would-be donors')
def get_objects(self):
from club.models import Schedule
)
-class ClubRecurringPaymentProblem(State):
+class ClubRecurring(State):
+ slug = 'club-recurring'
+ name = _('club recurring donors')
+
+
+class ClubRecurringExpired(State):
slug = 'club-recurring-payment-problem'
- name = _('club recurring payment problem')
+ name = _('club recurring donors with donation expired')
def get_objects(self):
from club.models import Schedule
return Schedule.objects.none()
+class Cold(State):
+ slug = 'cold'
+ name = _('cold group')
+
+
states = [
- ClubMembershipExpiring,
- ClubPaymentUnfinished,
- ClubRecurringPaymentProblem,
+ Cold,
+ ClubTried,
+ ClubSingle,
+ ClubSingleExpired,
+ ClubRecurring,
+ ClubRecurringExpired,
]