add back office subscription management (#14093)

This commit is contained in:
Josue Kouka 2018-04-25 18:34:09 +02:00
parent bcda0d040a
commit 323fc37c5b
9 changed files with 156 additions and 3 deletions

View File

@ -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<slug>[\w-]+)/import-subscriptions/$', subscriptions_import,
name='subscriptions-import'),
url(r'^subscription/delete/(?P<pk>[\w-]+)/$', subscription_delete,
name='subscription-delete'),
url(r'^subscriptions/search$', subscription_search,
name='subscription-search'),
url(r'^menu.json$', menu_json),
)

View File

@ -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(':')

View File

@ -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):

View File

@ -93,6 +93,10 @@ ul.objects-list.single-links li .actions a {
padding: 0;
}
ul.objects-list.single-links li {
padding: 0 1em;
}
.actions a:hover {
color: #333;
}
@ -166,6 +170,10 @@ form ul li label {
text-transform: uppercase;
}
.subscriptions-search a {
float: right;
}
.announce_dashboard {
font-size: 1.1em;
margin-left: 0.5em;

View File

@ -12,6 +12,7 @@
</ul>
{% endif %}
<a href="{% url 'add_category' %}" rel='popup'>{% trans 'New category' %}</a>
<a href="{% url 'subscription-search' %}">{% trans 'Search subscribers' %}</a>
{% endblock %}
{% block content %}

View File

@ -0,0 +1,17 @@
{% extends "corbo/manage.html" %}
{% load i18n %}
{% block appbar %}
<h2>{{ view.model.get_verbose_name }}</h2>
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
<p>{% blocktrans %}Are you sure you want to unsubscribe <em>{{identifier}}</em> from <em>{{category}}</em> ?{% endblocktrans %}</p>
<div class="buttons">
<button class="delete-button">{% trans 'Confirm' %}</button>
<a class="cancel" href="{{ object.get_absolute_url }}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends "corbo/base.html" %}
{% load i18n %}
{% block appbar %}
<h2>{% trans "Search subscriptions by email or phone number" %}</h2>
{% endblock %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'subscription-search' %}">{% trans 'Search' %}</a>
{% endblock %}
{% block content %}
<form action="{% url 'subscription-search' %}">
<p><input name="q" type="search" value="{{query}}" /> <button>{% trans 'Search' %}</button>
<span class="help_text"> {% trans "(type an email or a phone number)" %} </span></p>
</form>
{% if subscriptions %}
<div class="subscriptions-search">
<p>{% blocktrans %} Subscriptions of <em>{{query}}</em> : {% endblocktrans %}</p>
<ul class="objects-list single-links">
{% for subscription in subscriptions %}
<li>{{ subscription.category.name}}<a rel="popup" href="{% url 'subscription-delete' pk=subscription.pk %}">{% trans "unsubscribe" %}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endblock %}

View File

@ -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'

View File

@ -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-search'}) is None
# unknow identifier
resp.form['q'] = 'toto'
resp = resp.form.submit()
assert resp.html.find('div', {'class': 'subscriptions-search'}) 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-search'}).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-search'}).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