diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py index 99d4f66a776920360da581f0da8ea41f93ac690f..669f3bc7ed10183e7bcea091ff22551cd217818e 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 39145675556928956ed158673bd1fdd327200e82..1d47922efff81ddbc6b2157ce74b9fee93f697a6 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 c9de03f6f38e5bdc3e56cdc88761d65cf3717e5a..f930e4a9312d8e0a5326bac188cb63824c27a412 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 2ba8cbc940ab1c55645746bc95006f2d71475ab6..0000000000000000000000000000000000000000 --- 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 91ea44a218fbd2f408430959283f0419c921093e..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..0730bce3ef54291b105d1561d7938594db162021 --- /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 6825eca974a4a4ece3a2fb3acbc7ab9ae8799f9d..7b4422d51ad5206634f7e010a8d74f96dba289b5 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 dc3d1b990b39cd09f8ab401603c7a715d0495350..2488a147def52d309ce0b57d4ff47dd836f98205 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 0000000000000000000000000000000000000000..1c76723573de0d66ba8b99aafc79b9945fa86681 --- /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 0000000000000000000000000000000000000000..351514f9156dd95559f0d14d7b9d33103873c99c --- /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 041205910f589a5d3e401ad4b8f949e6d024ca8c..1c909a2c13f0f9833d0d0d04d6d18ed151d2cc0e 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 6d33b8cdd2fb4fb79fd0bf58334dbad84b9d2ab3..16b65c8ed5d8c1200b507bef44a7ca48ac824ecd 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 0000000000000000000000000000000000000000..0bdb6839dd6751246843cd1335177ff274034791 --- /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 0000000000000000000000000000000000000000..34e64180826310b5144a1352ccce2ec5f83163c6 --- /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 0000000000000000000000000000000000000000..0e3a4825d79ec5b1c38ec5c620ee214d6e1ffe6a --- /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 0000000000000000000000000000000000000000..f5dcc64e82faa45be130a99d690fbcb5592dff7c --- /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 ab8676cb6d7f18193124c876281fe70288093f81..ba25dba1447cf01d84a54298194168b368fbd502 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 9d8d1f39b96bd9d0d7ecee776198c73c01a6fb58..8ca05f6dffe5bc6f5a6c728945ee903f6d019731 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 41a53da7087a1226f810800ce475a2d7140335ac..97119aa60fb3085746cf7d6bdbacf21f591e1c31 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 %}