From fb31f640fd3e7e7112dde64e07e95304eca857b9 Mon Sep 17 00:00:00 2001 From: Jorran de Wit <jorrandewit@outlook.com> Date: Sun, 23 Apr 2017 16:35:54 +0200 Subject: [PATCH] Add editorial actions page to sync lists --- SciPost_v1/settings/base.py | 6 ++- SciPost_v1/settings/local_jorran.py | 1 + SciPost_v1/urls.py | 2 +- mailchimp/admin.py | 23 -------- mailchimp/views.py | 3 -- {mailchimp => mailing_lists}/__init__.py | 0 mailing_lists/admin.py | 14 +++++ {mailchimp => mailing_lists}/apps.py | 2 +- {mailchimp => mailing_lists}/constants.py | 2 - mailing_lists/forms.py | 31 +++++++++++ mailing_lists/managers.py | 8 +++ .../migrations/0001_initial.py | 32 +++++++---- .../migrations/__init__.py | 0 {mailchimp => mailing_lists}/models.py | 19 +++++-- .../mailing_lists/mailchimplist_form.html | 34 ++++++++++++ .../templates/mailing_lists/overview.html | 48 +++++++++++++++++ {mailchimp => mailing_lists}/tests.py | 0 mailing_lists/urls.py | 10 ++++ mailing_lists/views.py | 54 +++++++++++++++++++ requirements.txt | 1 + .../commands/add_groups_and_permissions.py | 7 +++ scipost/templates/scipost/personal_page.html | 3 ++ 22 files changed, 253 insertions(+), 47 deletions(-) delete mode 100644 mailchimp/admin.py delete mode 100644 mailchimp/views.py rename {mailchimp => mailing_lists}/__init__.py (100%) create mode 100644 mailing_lists/admin.py rename {mailchimp => mailing_lists}/apps.py (72%) rename {mailchimp => mailing_lists}/constants.py (71%) create mode 100644 mailing_lists/forms.py create mode 100644 mailing_lists/managers.py rename {mailchimp => mailing_lists}/migrations/0001_initial.py (69%) rename {mailchimp => mailing_lists}/migrations/__init__.py (100%) rename {mailchimp => mailing_lists}/models.py (60%) create mode 100644 mailing_lists/templates/mailing_lists/mailchimplist_form.html create mode 100644 mailing_lists/templates/mailing_lists/overview.html rename {mailchimp => mailing_lists}/tests.py (100%) create mode 100644 mailing_lists/urls.py create mode 100644 mailing_lists/views.py diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index 99d4f66a7..669f3bc7e 100644 --- a/SciPost_v1/settings/base.py +++ b/SciPost_v1/settings/base.py @@ -87,13 +87,13 @@ INSTALLED_APPS = ( 'commentaries', 'comments', 'journals', - 'mailchimp', + 'mailing_lists', 'news', 'scipost', 'submissions', 'theses', 'virtualmeetings', - 'webpack_loader' + 'webpack_loader', ) HAYSTACK_CONNECTIONS = { @@ -228,6 +228,8 @@ WEBPACK_LOADER = { # Email EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' EMAIL_FILE_PATH = 'local_files/email/' +MAILCHIMP_DATABASE_CODE = 'us6' +MAILCHIMP_API_USER = 'test_API-user' MAILCHIMP_API_KEY = 'test_API-key' # Own settings diff --git a/SciPost_v1/settings/local_jorran.py b/SciPost_v1/settings/local_jorran.py index 391456755..1d47922ef 100644 --- a/SciPost_v1/settings/local_jorran.py +++ b/SciPost_v1/settings/local_jorran.py @@ -18,4 +18,5 @@ MEDIA_ROOT = '/Users/jorranwit/Develop/SciPost/scipost_v1/local_files/media/' WEBPACK_LOADER['DEFAULT']['BUNDLE_DIR_NAME'] =\ '/Users/jorranwit/Develop/SciPost/scipost_v1/local_files/static/bundles/' +MAILCHIMP_API_USER = get_secret("MAILCHIMP_API_USER") MAILCHIMP_API_KEY = get_secret("MAILCHIMP_API_KEY") diff --git a/SciPost_v1/urls.py b/SciPost_v1/urls.py index c9de03f6f..f930e4a93 100644 --- a/SciPost_v1/urls.py +++ b/SciPost_v1/urls.py @@ -35,7 +35,7 @@ urlpatterns = [ url(r'^commentary/', include('commentaries.urls', namespace="commentaries")), url(r'^comments/', include('comments.urls', namespace="comments")), url(r'^journals/', include('journals.urls.general', namespace="journals")), - + url(r'^mailing_list/', include('mailing_lists.urls', namespace="mailing_lists")), url(r'^submissions/', include('submissions.urls', namespace="submissions")), url(r'^submission/', include('submissions.urls', namespace="submissions")), url(r'^theses/', include('theses.urls', namespace="theses")), diff --git a/mailchimp/admin.py b/mailchimp/admin.py deleted file mode 100644 index 2ba8cbc94..000000000 --- a/mailchimp/admin.py +++ /dev/null @@ -1,23 +0,0 @@ -from django.contrib import admin, messages - -from .models import ActiveMailchimpList - - -class ActiveMailchimpListAdmin(admin.ModelAdmin): - list_display = ['__str__', 'mailchimp_list_id', 'status'] - list_filter = ['status'] - actions = ['update_lists'] - - def message_user(self, request, *args): - return messages.warning(request, 'Sorry, Deposit\'s are readonly.') - - def has_add_permission(self, *args): - return False - - def update_lists(self, request, queryset): - messages.success(request, 'Test') - - # def has_delete_permission(self, *args): - # return False - -admin.site.register(ActiveMailchimpList, ActiveMailchimpListAdmin) diff --git a/mailchimp/views.py b/mailchimp/views.py deleted file mode 100644 index 91ea44a21..000000000 --- a/mailchimp/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/mailchimp/__init__.py b/mailing_lists/__init__.py similarity index 100% rename from mailchimp/__init__.py rename to mailing_lists/__init__.py diff --git a/mailing_lists/admin.py b/mailing_lists/admin.py new file mode 100644 index 000000000..0730bce3e --- /dev/null +++ b/mailing_lists/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin + +from .models import MailchimpList + + +class MailchimpListAdmin(admin.ModelAdmin): + list_display = ['__str__', 'mailchimp_list_id', 'status'] + list_filter = ['status'] + + def has_add_permission(self, *args): + return False + + +admin.site.register(MailchimpList, MailchimpListAdmin) diff --git a/mailchimp/apps.py b/mailing_lists/apps.py similarity index 72% rename from mailchimp/apps.py rename to mailing_lists/apps.py index 6825eca97..7b4422d51 100644 --- a/mailchimp/apps.py +++ b/mailing_lists/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class MailchimpConfig(AppConfig): - name = 'mailchimp' + name = 'mailing_lists' diff --git a/mailchimp/constants.py b/mailing_lists/constants.py similarity index 71% rename from mailchimp/constants.py rename to mailing_lists/constants.py index dc3d1b990..2488a147d 100644 --- a/mailchimp/constants.py +++ b/mailing_lists/constants.py @@ -1,8 +1,6 @@ MAIL_LIST_STATUS_ACTIVE = 'active' -MAIL_LIST_STATUS_REMOVED = 'removed' MAIL_LIST_STATUS_DEACTIVATED = 'deactivated' MAIL_LIST_STATUSES = ( (MAIL_LIST_STATUS_ACTIVE, 'Active'), - (MAIL_LIST_STATUS_REMOVED, 'Removed'), (MAIL_LIST_STATUS_DEACTIVATED, 'Deactivated'), ) diff --git a/mailing_lists/forms.py b/mailing_lists/forms.py new file mode 100644 index 000000000..1c7672357 --- /dev/null +++ b/mailing_lists/forms.py @@ -0,0 +1,31 @@ +from django import forms +from django.conf import settings + +from mailchimp3 import MailChimp + +from .constants import MAIL_LIST_STATUS_ACTIVE, MAIL_LIST_STATUS_DEACTIVATED +from .models import MailchimpList + + +class MailchimpUpdateForm(forms.Form): + """ + This form does the synchronizing of mailing lists in the database. + """ + def __init__(self): + self.client = MailChimp(settings.MAILCHIMP_API_USER, settings.MAILCHIMP_API_KEY) + self.objects = MailchimpList.objects.active() + + def sync(self): + response = self.client.lists.all(get_all=True, fields="lists.name,lists.id") + + # Deactivate all mailing lists by default + self.objects.update(status=MAIL_LIST_STATUS_DEACTIVATED) + count = 0 + while response['lists']: + _list = response['lists'].pop() + chimplist, created = MailchimpList.objects.get_or_create(mailchimp_list_id=_list['id']) + chimplist.name = _list['name'] + chimplist.status = MAIL_LIST_STATUS_ACTIVE + chimplist.save() + count += 1 + return count diff --git a/mailing_lists/managers.py b/mailing_lists/managers.py new file mode 100644 index 000000000..351514f91 --- /dev/null +++ b/mailing_lists/managers.py @@ -0,0 +1,8 @@ +from django.db import models + +from .constants import MAIL_LIST_STATUS_ACTIVE + + +class MailListManager(models.Manager): + def active(self): + return self.filter(status=MAIL_LIST_STATUS_ACTIVE) diff --git a/mailchimp/migrations/0001_initial.py b/mailing_lists/migrations/0001_initial.py similarity index 69% rename from mailchimp/migrations/0001_initial.py rename to mailing_lists/migrations/0001_initial.py index 041205910..1c909a2c1 100644 --- a/mailchimp/migrations/0001_initial.py +++ b/mailing_lists/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.3 on 2017-04-23 10:10 +# Generated by Django 1.10.3 on 2017-04-23 14:08 from __future__ import unicode_literals from django.db import migrations, models @@ -19,31 +19,41 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='ActiveMailchimpList', + name='ActiveMailchimpSubscription', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', models.DateTimeField(default=django.utils.timezone.now)), ('latest_activity', scipost.db.fields.AutoDateTimeField(blank=True, default=django.utils.timezone.now, editable=False)), - ('name', models.CharField(max_length=255)), - ('mailchimp_list_id', models.CharField(max_length=255)), - ('status', models.CharField(choices=[('active', 'Active'), ('removed', 'Removed'), ('deactivated', 'Deactivated')], default='active', max_length=255)), - ('allowed_groups', models.ManyToManyField(related_name='allowed_mailchimp_lists', to='auth.Group')), ], options={ - 'default_permissions': ('delete',), + 'abstract': False, }, ), migrations.CreateModel( - name='ActiveMailchimpSubscription', + name='MailchimpList', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('created', models.DateTimeField(default=django.utils.timezone.now)), ('latest_activity', scipost.db.fields.AutoDateTimeField(blank=True, default=django.utils.timezone.now, editable=False)), - ('active_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mailchimp.ActiveMailchimpList')), - ('contributor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='scipost.Contributor')), + ('name', models.CharField(max_length=255)), + ('internal_name', models.CharField(blank=True, max_length=255)), + ('supporting_text', models.TextField(blank=True)), + ('mailchimp_list_id', models.CharField(max_length=255)), + ('status', models.CharField(choices=[('active', 'Active'), ('deactivated', 'Deactivated')], default='active', max_length=255)), + ('allowed_groups', models.ManyToManyField(related_name='allowed_mailchimp_lists', to='auth.Group')), ], options={ - 'abstract': False, + 'default_permissions': ('delete',), }, ), + migrations.AddField( + model_name='activemailchimpsubscription', + name='active_list', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mailing_lists.MailchimpList'), + ), + migrations.AddField( + model_name='activemailchimpsubscription', + name='contributor', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='scipost.Contributor'), + ), ] diff --git a/mailchimp/migrations/__init__.py b/mailing_lists/migrations/__init__.py similarity index 100% rename from mailchimp/migrations/__init__.py rename to mailing_lists/migrations/__init__.py diff --git a/mailchimp/models.py b/mailing_lists/models.py similarity index 60% rename from mailchimp/models.py rename to mailing_lists/models.py index 6d33b8cdd..16b65c8ed 100644 --- a/mailchimp/models.py +++ b/mailing_lists/models.py @@ -1,34 +1,45 @@ from django.db import models from django.contrib.auth.models import Group +from django.urls import reverse from .constants import MAIL_LIST_STATUSES, MAIL_LIST_STATUS_ACTIVE +from .managers import MailListManager from scipost.behaviors import TimeStampedModel -class ActiveMailchimpList(TimeStampedModel): +class MailchimpList(TimeStampedModel): """ This model is a copy of the current lists in the Mailchimp account. It will be used to map the Contributor's preferences to the Mailchimp's lists and keeping both up to date. """ name = models.CharField(max_length=255) - mailchimp_list_id = models.CharField(max_length=255) + internal_name = models.CharField(max_length=255, blank=True) + supporting_text = models.TextField(blank=True) + mailchimp_list_id = models.CharField(max_length=255, unique=True) status = models.CharField(max_length=255, choices=MAIL_LIST_STATUSES, default=MAIL_LIST_STATUS_ACTIVE) allowed_groups = models.ManyToManyField(Group, related_name='allowed_mailchimp_lists') + objects = MailListManager() + class Meta: - default_permissions = ('delete',) + ordering = ['status', 'internal_name', 'name'] def __str__(self): + if self.internal_name: + return self.internal_name return self.name + def get_absolute_url(self): + return reverse('mailing_lists:list_detail', args=[self.mailchimp_list_id]) + class ActiveMailchimpSubscription(TimeStampedModel): """ Track the Contributors' settings on wheter he/she wants to have an active subscription to a specific (public) list. """ - active_list = models.ForeignKey('mailchimp.ActiveMailchimpList') + active_list = models.ForeignKey('mailing_lists.MailchimpList') contributor = models.ForeignKey('scipost.Contributor') diff --git a/mailing_lists/templates/mailing_lists/mailchimplist_form.html b/mailing_lists/templates/mailing_lists/mailchimplist_form.html new file mode 100644 index 000000000..0bdb6839d --- /dev/null +++ b/mailing_lists/templates/mailing_lists/mailchimplist_form.html @@ -0,0 +1,34 @@ +{% extends 'scipost/_personal_page_base.html' %} + +{% block pagetitle %}: mailing lists{% endblock pagetitle %} + +{% load bootstrap %} + +{% block breadcrumb_items %} + {{block.super}} + <a href="{% url 'mailing_lists:overview' %}" class="breadcrumb-item">Mailing lists</a> + <span class="breadcrumb-item">{{object}}</span> +{% endblock %} + +{% block content %} + + +<div class="row"> + <div class="col-12"> + <h1>Edit the mailing list <i>{{object}}</i></h1> + <h3>Mailchimp configuration:</h3> + <pre><code>ID: {{object.mailchimp_list_id}}<br>name: {{object.name}}<br>Status: {{object.get_status_display}}</code></pre> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <form method="post"> + {% csrf_token %} + {{form|bootstrap}} + <input type="submit" value="Update" class="btn btn-secondary" /> + </form> + </div> +</div> + +{% endblock content %} diff --git a/mailing_lists/templates/mailing_lists/overview.html b/mailing_lists/templates/mailing_lists/overview.html new file mode 100644 index 000000000..34e641808 --- /dev/null +++ b/mailing_lists/templates/mailing_lists/overview.html @@ -0,0 +1,48 @@ +{% extends 'scipost/_personal_page_base.html' %} + +{% block pagetitle %}: mailing lists{% endblock pagetitle %} + +{% block breadcrumb_items %} + {{block.super}} + <span class="breadcrumb-item">Mailing lists</span> +{% endblock %} + +{% block content %} + + +<div class="row"> + <div class="col-12"> + <h1>All mailing lists known to SciPost</h1> + <h3>Actions:</h3> + <ul class="actions"> + <li><a href="{% url 'mailing_lists:sync_lists' %}">Syncronize lists with the mail server</a></li> + </ul> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <div class="card border-0"> + <h3 class="card-title">All lists in the database:</h3> + <ul class="list-group list-group-flush"> + {% for mail_list in object_list %} + <li class="list-group-item"> + <div class="py-1"> + <h3><a href="{{mail_list.get_absolute_url}}">{{mail_list}}</a> <small><code>ID: {{mail_list.mailchimp_list_id}}</code></small></h3> + <p class="text-muted mb-0">{{mail_list.get_status_display}} | {{mail_list.allowed_groups.count}} group{{mail_list.allowed_groups.count|pluralize}} | last update: {{mail_list.latest_activity}}</p> + {% if mail_list.supporting_text %} + <p class="my-1">{{mail_list.supporting_text|linebreaks}}</p> + {% endif %} + </div> + </li> + {% empty %} + <li class="list-group-item"> + <p class="mb-0">No mailing lists known. Please <a href="{% url 'mailing_lists:sync_lists' %}">update the list</a>.</p> + </li> + {% endfor %} + </ul> + </div> + </div> +</div> + +{% endblock content %} diff --git a/mailchimp/tests.py b/mailing_lists/tests.py similarity index 100% rename from mailchimp/tests.py rename to mailing_lists/tests.py diff --git a/mailing_lists/urls.py b/mailing_lists/urls.py new file mode 100644 index 000000000..0e3a4825d --- /dev/null +++ b/mailing_lists/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls import url + +from . import views + +urlpatterns = [ + # Mailchimp + url(r'^$', views.MailchimpListView.as_view(), name='overview'), + url(r'^sync/$', views.syncronize_lists, name='sync_lists'), + url(r'^(?P<list_id>[0-9a-zA-Z]+)/$', views.ListDetailView.as_view(), name='list_detail'), +] diff --git a/mailing_lists/views.py b/mailing_lists/views.py new file mode 100644 index 000000000..f5dcc64e8 --- /dev/null +++ b/mailing_lists/views.py @@ -0,0 +1,54 @@ +from django.contrib import messages +from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from django.views.generic import UpdateView +from django.views.generic.list import ListView +from django.core.urlresolvers import reverse +from django.shortcuts import redirect + +from .forms import MailchimpUpdateForm +from .models import MailchimpList + + +class MailchimpMixin(LoginRequiredMixin, PermissionRequiredMixin): + permission_required = 'scipost.can_manage_mailchimp' + raise_exception = True + + +class MailchimpListView(MailchimpMixin, ListView): + """ + List all lists of Mailchimp known to the current database. + This is part of the editorial actions for SciPost Administrators. + It should act as a main page from which the admin can to action to update + some general mailchimp settings. + """ + template_name = 'mailing_lists/overview.html' + model = MailchimpList + + +@login_required +@permission_required('scipost.can_manage_mailchimp', raise_exception=True) +def syncronize_lists(request): + """ + Syncronize the Mailchimp lists in the database with the lists known in + the mailchimp account which is related to the API_KEY. + """ + form = MailchimpUpdateForm() + updated = form.sync() + messages.success(request, '%i mailing lists have succesfully been updated.' % updated) + return redirect(reverse('mailing_lists:overview')) + + +class ListDetailView(MailchimpMixin, UpdateView): + """ + The detail view of a certain Mailchimp list. This allows the admin to i.e. manage group + permissions to the group. + """ + slug_field = 'mailchimp_list_id' + slug_url_kwarg = 'list_id' + fields = ('allowed_groups', 'internal_name', 'supporting_text') + model = MailchimpList + + def form_valid(self, form): + messages.success(self.request, 'List succesfully updated') + return super().form_valid(form) diff --git a/requirements.txt b/requirements.txt index ab8676cb6..ba25dba14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,6 +21,7 @@ fake-factory==0.7.2 feedparser==5.2.1 imagesize==0.7.1 Jinja2==2.8 +mailchimp3==2.0.11 Markdown==2.6.7 MarkupSafe==0.23 pep8==1.7.0 diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index 9d8d1f39b..8ca05f6df 100644 --- a/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost/management/commands/add_groups_and_permissions.py @@ -159,6 +159,12 @@ class Command(BaseCommand): name='Can view docs: scipost', content_type=content_type) + # Mailchimp + can_manage_mailchimp, created = Permission.objects.get_or_create( + codename='can_manage_mailchimp', + name='Can manage Mailchimp settings', + content_type=content_type) + # Assign permissions to groups SciPostAdmin.permissions.add( can_manage_registration_invitations, @@ -174,6 +180,7 @@ class Command(BaseCommand): can_prepare_recommendations_for_voting, can_fix_College_decision, can_attend_VGMs, + can_manage_mailchimp, ) AdvisoryBoard.permissions.add( can_manage_registration_invitations, diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html index 41a53da70..97119aa60 100644 --- a/scipost/templates/scipost/personal_page.html +++ b/scipost/templates/scipost/personal_page.html @@ -221,6 +221,9 @@ <li><a href="{% url 'scipost:send_precooked_email' %}">Send a precooked email</a></li> <li><a href="{% url 'scipost:email_particular' %}">Email a particular individual/address</a></li> {% endif %} + {% if perms.scipost.can_manage_mailchimp %} + <li><a href="{% url 'mailing_lists:overview' %}">Manage mailing lists</a></li> + {% endif %} </ul> </div> {% endif %} -- GitLab