add back office subscription management (#14093)
This commit is contained in:
parent
bcda0d040a
commit
323fc37c5b
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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(':')
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
Reference in New Issue