From a1ca41a7b6332254c541cd8cd01dfa68ef0702c3 Mon Sep 17 00:00:00 2001 From: Josue Kouka Date: Wed, 25 Apr 2018 18:34:09 +0200 Subject: [PATCH] add back office subscription management (#14093) --- corbo/manage_urls.py | 6 ++- corbo/models.py | 3 ++ corbo/settings.py | 3 ++ corbo/templates/corbo/manage.html | 1 + corbo/templates/corbo/subscription_delete.html | 17 +++++++++ corbo/templates/corbo/subscription_search.html | 30 +++++++++++++++ corbo/views.py | 52 +++++++++++++++++++++++++- tests/test_manager.py | 38 ++++++++++++++++++- 8 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 corbo/templates/corbo/subscription_delete.html create mode 100644 corbo/templates/corbo/subscription_search.html diff --git a/corbo/manage_urls.py b/corbo/manage_urls.py index 7b126d0..cb7abe3 100644 --- a/corbo/manage_urls.py +++ b/corbo/manage_urls.py @@ -3,7 +3,7 @@ from django.conf.urls import patterns, include, url from .views import add_announce, edit_announce, delete_announce, \ add_category, edit_category, view_category, delete_category, manage, \ subscriptions_import, view_announce, email_announce, sms_announce, \ - menu_json + menu_json, subscription_delete, subscription_search urlpatterns = patterns('', url(r'^$', manage, name='manage'), @@ -29,5 +29,9 @@ urlpatterns = patterns('', name='delete_category'), url(r'^category/(?P[\w-]+)/import-subscriptions/$', subscriptions_import, name='subscriptions-import'), + url(r'^subscription/delete/(?P[\w-]+)/$', subscription_delete, + name='subscription-delete'), + url(r'^subscriptions/search$', subscription_search, + name='subscription-search'), url(r'^menu.json$', menu_json), ) diff --git a/corbo/models.py b/corbo/models.py index 7d6f5fa..1830878 100644 --- a/corbo/models.py +++ b/corbo/models.py @@ -188,6 +188,9 @@ class Subscription(models.Model): identifier = models.CharField(_('identifier'), max_length=128, blank=True, help_text=_('ex.: mailto, ...')) + def __unicode__(self): + return '%s - %s - %s' % (self.uuid, self.identifier, self.category.name) + def get_identifier_display(self): try: scheme, identifier = self.identifier.split(':') diff --git a/corbo/settings.py b/corbo/settings.py index 0edebf4..26057cb 100644 --- a/corbo/settings.py +++ b/corbo/settings.py @@ -192,6 +192,9 @@ SMS_EXPEDITOR = 'Corbo' REQUESTS_PROXIES = None +CORBO_PHONE_SEARCH_DIGITS = 9 + + local_settings_file = os.environ.get('CORBO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')) if os.path.exists(local_settings_file): diff --git a/corbo/templates/corbo/manage.html b/corbo/templates/corbo/manage.html index 992908d..3929414 100644 --- a/corbo/templates/corbo/manage.html +++ b/corbo/templates/corbo/manage.html @@ -12,6 +12,7 @@ {% endif %} {% trans 'New category' %} +{% trans 'Search subscribers' %} {% endblock %} {% block content %} diff --git a/corbo/templates/corbo/subscription_delete.html b/corbo/templates/corbo/subscription_delete.html new file mode 100644 index 0000000..58467d1 --- /dev/null +++ b/corbo/templates/corbo/subscription_delete.html @@ -0,0 +1,17 @@ +{% extends "corbo/manage.html" %} +{% load i18n %} + +{% block appbar %} +

{{ view.model.get_verbose_name }}

+{% endblock %} + +{% block content %} +
+ {% csrf_token %} +

{% blocktrans %}Are you sure you want to unsubscribe {{identifier}} from {{category}} ?{% endblocktrans %}

+
+ + {% trans 'Cancel' %} +
+
+{% endblock %} diff --git a/corbo/templates/corbo/subscription_search.html b/corbo/templates/corbo/subscription_search.html new file mode 100644 index 0000000..f4102fe --- /dev/null +++ b/corbo/templates/corbo/subscription_search.html @@ -0,0 +1,30 @@ +{% extends "corbo/base.html" %} +{% load i18n %} + +{% block appbar %} +

{% trans "Search subscriptions by email or phone number" %}

+{% endblock %} + +{% block breadcrumb %} +{{ block.super }} +{% trans 'Search' %} +{% endblock %} + +{% block content %} + +
+

+ {% trans "(type an email or a phone number)" %}

+
+ +{% if subscriptions %} +
+ +
+{% endif %} + +{% endblock %} diff --git a/corbo/views.py b/corbo/views.py index 3f15171..683a4c3 100644 --- a/corbo/views.py +++ b/corbo/views.py @@ -2,11 +2,12 @@ import urllib import json from django.conf import settings +from django.contrib import messages from django.core import signing from django.utils import timezone from django.core.urlresolvers import reverse from django.views.generic import CreateView, UpdateView, DeleteView, \ - ListView, TemplateView, RedirectView, DetailView, FormView, RedirectView + ListView, TemplateView, RedirectView, DetailView, FormView from django.contrib.syndication.views import Feed from django.shortcuts import resolve_url from django.utils.encoding import force_text @@ -256,6 +257,55 @@ class SubscriptionsImportView(FormView): subscriptions_import = SubscriptionsImportView.as_view() +class SubscriptionSearchView(TemplateView): + + template_name = 'corbo/subscription_search.html' + + def get_context_data(self, **kwargs): + context = super(SubscriptionSearchView, self).get_context_data(**kwargs) + query = self.request.GET.get('q', '').strip() + context['query'] = query + if not query: + return context + context['subscriptions'] = models.Subscription.objects.filter(identifier='mailto:' + query) + phonenumber = utils.format_phonenumber(query) + if not context['subscriptions'] and phonenumber: + # search by last n digits + context['subscriptions'] = models.Subscription.objects.filter( + identifier__endswith=phonenumber[-settings.CORBO_PHONE_SEARCH_DIGITS:]) + return context + + +subscription_search = SubscriptionSearchView.as_view() + + +class SubscriptionDeleteView(DeleteView): + + model = models.Subscription + template_name = 'corbo/subscription_delete.html' + + def get_context_data(self, **kwargs): + context = super(SubscriptionDeleteView, self).get_context_data(**kwargs) + context['identifier'] = self.object.identifier.split(':', 1)[1] + context['category'] = self.object.category.name + return context + + def get_success_url(self): + identifier = self.object.identifier.split(':', 1)[1] + return reverse('subscription-search') + '?q=%s' % identifier + + def delete(self, request, *args, **kwargs): + response = super(SubscriptionDeleteView, self).delete(request, *args, **kwargs) + success_message = _('%s successfuly unsubscribed from %s') % ( + self.object.identifier.split(':', 1)[1], self.object.category.name) + messages.success(request, success_message) + return response + + + +subscription_delete = SubscriptionDeleteView.as_view() + + class AnnounceView(DetailView): model = models.Announce template_name = 'corbo/announce_view.html' diff --git a/tests/test_manager.py b/tests/test_manager.py index 85a2996..169bf30 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -7,7 +7,8 @@ from django.core.urlresolvers import reverse from django.contrib.auth.models import User from django.test import override_settings -from corbo.models import Broadcast +from corbo.models import Broadcast, Subscription +from corbo.utils import format_phonenumber pytestmark = pytest.mark.django_db @@ -352,3 +353,38 @@ def test_sms_announce_with_invalid_gateway_url(app, admin_user, settings, caplog assert record.name == 'corbo.utils' assert record.levelno == logging.WARNING assert 'Invalid URL' in record.getMessage() + + +@pytest.fixture(params=['foo@example.net', '06 07 08 09 00', '+33 6 07 08 09 00', + '0033607080900', '+33 (0) 6 07 08 09 00', '0033+607080900+']) +def search_identifier(request, subscriptions): + return request.param + + +def test_subscriptions_search(app, admin_user, search_identifier): + app = login(app) + resp = app.get('/manage/') + resp = resp.click('Search') + if format_phonenumber(search_identifier): + search_identifier = format_phonenumber(search_identifier)[-9:] + # empty search + resp = resp.form.submit() + assert resp.html.find('div', {'class': 'subscriptions'}) is None + # unknow identifier + resp.form['q'] = 'toto' + resp = resp.form.submit() + assert resp.html.find('div', {'class': 'subscriptions'}) is None + # search by email + assert Subscription.objects.filter(identifier__contains=search_identifier).count() == 2 + resp.form['q'] = search_identifier + resp = resp.form.submit() + assert len(resp.html.find('div', {'class': 'subscriptions'}).find_all('li')) == 2 + # delete sub + first_sub = Subscription.objects.filter(identifier__contains=search_identifier).first() + resp = resp.click(href='/manage/subscription/delete/%d/' % first_sub.pk) + resp = resp.form.submit().follow() + assert Subscription.objects.filter(identifier__contains=search_identifier).count() == 1 + # ensure we're back on the previous page with less result + assert len(resp.html.find('div', {'class': 'subscriptions'}).find_all('li')) == 1 + assert len(resp.html.find_all('li', {'class': 'success'})) == 1 + assert '%s successfuly unsubscribed from' % search_identifier in resp.html.find('li', {'class': 'success'}).text -- 2.14.2