From c26450a927d90b42f60cdf85556078249fc98bd2 Mon Sep 17 00:00:00 2001 From: Serghei Mihai Date: Mon, 2 May 2016 15:32:52 +0200 Subject: [PATCH 2/3] api: subscriptions management endpoint (#10794) --- corbo/api_urls.py | 3 +- corbo/api_views.py | 82 +++++++++++++++++++++++++++++ corbo/migrations/0004_auto_20160504_1701.py | 53 +++++++++++++++++++ corbo/models.py | 15 ++---- tests/test_api.py | 54 ++++++++++++++++++- 5 files changed, 195 insertions(+), 12 deletions(-) create mode 100644 corbo/migrations/0004_auto_20160504_1701.py diff --git a/corbo/api_urls.py b/corbo/api_urls.py index cb275e5..6f77fc4 100644 --- a/corbo/api_urls.py +++ b/corbo/api_urls.py @@ -16,8 +16,9 @@ from django.conf.urls import patterns, include, url -from .api_views import NewslettersView +from .api_views import NewslettersView, SubscriptionsView urlpatterns = patterns('', url(r'^newsletters/', NewslettersView.as_view(), name='newsletters'), + url(r'^subscriptions/', SubscriptionsView.as_view(), name='subscriptions'), ) diff --git a/corbo/api_views.py b/corbo/api_views.py index a1f15be..b168e83 100644 --- a/corbo/api_views.py +++ b/corbo/api_views.py @@ -14,6 +14,13 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import json +from collections import defaultdict + +from django.core.exceptions import PermissionDenied +from django.db.models import Q +from django.db import transaction + from rest_framework.views import APIView from rest_framework.response import Response @@ -30,3 +37,78 @@ class NewslettersView(APIView): 'transports': transports} newsletters.append(newsletter) return Response({'data': newsletters}) + + +class SubscriptionsView(APIView): + + def get_subscriptions(self, email, uuid=None): + subscriptions = defaultdict(dict) + identifier = 'mailto:'+email + for s in Subscription.objects.filter(Q(identifier=identifier)|Q(uuid=uuid)): + cat_id = s.category.pk + subscriptions[cat_id]['id'] = str(cat_id) + subscriptions[cat_id]['text'] = s.category.name + transport_id, transport_name = identifier.split(':') + transport = {'id': transport_id, + 'text': transport_id} + if 'transports' in subscriptions[cat_id]: + subscriptions[cat_id]['transports'].append(transport) + else: + subscriptions[cat_id]['transports'] = [transport] + + return subscriptions.values() + + @transaction.atomic + def update_subscriptions(self, category_id, transports, email, uuid=None): + uuid = uuid or u'' + identifier = u'mailto:' + if email: + identifier += email + try: + cat = Category.objects.get(pk=category_id) + except Category.DoesNotExist: + return + + subcriptions = cat.subscription_set + + if email: + if 'mailto' in transports: + subcriptions.get_or_create(identifier=identifier, uuid=uuid) + if uuid: + subcriptions.exclude(uuid=uuid).filter(identifier=identifier).delete() + else: + subcriptions.filter(identifier=identifier).delete() + + if uuid: + if 'homepage' in transports: + subcriptions.get_or_create(uuid=uuid) + else: + subcriptions.filter(identifier='', uuid=uuid).delete() + + def get(self, request): + email = request.GET.get('email') + uuid = request.GET.get('uuid') + if not email: + raise PermissionDenied('Email parameter required') + return Response({'data': self.get_subscriptions(email, uuid)}) + + def post(self, request): + email = request.GET.get('email') + uuid = request.GET.get('uuid') + if not email: + raise PermissionDenied('Email parameter required') + data = json.loads(request.body) + for subscription in data: + self.update_subscriptions(subscription['id'], subscription['transports'], + email, uuid) + return Response({'data': True}) + + def delete(self, request): + email = request.GET.get('email') + uuid = request.GET.get('uuid') + if not email: + raise PermissionDenied('Email parameter required') + + for subscription in self.get_subscriptions(email): + self.update_subscriptions(subscription['id'], [], email, uuid) + return Response({'data': True}) diff --git a/corbo/migrations/0004_auto_20160504_1701.py b/corbo/migrations/0004_auto_20160504_1701.py new file mode 100644 index 0000000..15ffc21 --- /dev/null +++ b/corbo/migrations/0004_auto_20160504_1701.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('corbo', '0003_auto_20160427_1342'), + ] + + operations = [ + migrations.RemoveField( + model_name='subscriptiontype', + name='subscription', + ), + migrations.DeleteModel( + name='SubscriptionType', + ), + migrations.AddField( + model_name='subscription', + name='identifier', + field=models.CharField(help_text='ex.: mailto, homepage, ...', max_length=128, verbose_name='identifier', blank=True), + preserve_default=True, + ), + migrations.AddField( + model_name='subscription', + name='uuid', + field=models.CharField(max_length=128, verbose_name='User identifier', blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name='broadcast', + name='channel', + field=models.CharField(max_length=32, verbose_name='channel', choices=[(b'mailto', 'Email'), (b'homepage', 'Homepage')]), + preserve_default=True, + ), + migrations.AlterField( + model_name='subscription', + name='category', + field=models.ForeignKey(verbose_name='Category', to='corbo.Category'), + preserve_default=True, + ), + migrations.AlterUniqueTogether( + name='subscription', + unique_together=set([('category', 'identifier', 'uuid')]), + ), + migrations.RemoveField( + model_name='subscription', + name='user', + ), + ] diff --git a/corbo/models.py b/corbo/models.py index 1f48e60..2bb1826 100644 --- a/corbo/models.py +++ b/corbo/models.py @@ -68,15 +68,10 @@ class Broadcast(models.Model): unique_together = ('announce', 'channel') -class SubscriptionType(models.Model): - subscription = models.ForeignKey('Subscription') - identifier = models.CharField(_('identifier'), max_length=128, blank=True, - help_text=_('ex.: email, mobile phone number, jabber id')) - - class Subscription(models.Model): - user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), - blank=True, null=True) - category = models.ForeignKey('Category', verbose_name=_('category')) + category = models.ForeignKey('Category', verbose_name=_('Category')) + uuid = models.CharField(_('User identifier'), max_length=128, blank=True) + identifier = models.CharField(_('identifier'), max_length=128, blank=True, + help_text=_('ex.: mailto, homepage, ...')) class Meta: - unique_together = ('user', 'category') + unique_together = ('category', 'identifier', 'uuid') diff --git a/tests/test_api.py b/tests/test_api.py index d72c51f..1bafca4 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,10 +1,13 @@ import pytest import json +from uuid import uuid4 from django.core.urlresolvers import reverse +from django.utils.http import urlencode -from corbo.models import Category, Announce, Broadcast +from corbo.models import Category, Announce, Broadcast, Subscription +from corbo.models import channel_choices pytestmark = pytest.mark.django_db @@ -44,3 +47,52 @@ def test_get_newsletters(app, categories, announces): assert category['transports'] == [{'id': 'mailto', 'text': 'Email'}, {'id': 'homepage', 'text': 'Homepage'} ] + + +def test_get_subscriptions_by_email(app, categories, announces): + resp = app.get(reverse('subscriptions'), status=403) + foo = 'foo@example.com' + for identifier, name in channel_choices[:1]: + for category in categories: + uri = '%s:%s' % (identifier, foo) + subscription = Subscription.objects.create(identifier=uri, + category=category) + resp = app.get(reverse('subscriptions'), {'email': foo}, status=200) + assert 'data' in resp.json + data = resp.json['data'] + for d in data: + assert d['id'] in [str(category.id) for category in categories] + assert d['text'] in [category.name for category in categories] + for t in d['transports']: + assert t['id'] == identifier + + +def test_update_subscriptions(app, categories, announces): + params = urlencode({'email': 'foo@example.com', + 'uuid': str(uuid4())}) + subscriptions_url = reverse('subscriptions') + '?' + params + for category in categories: + transports = [] + for identifier, name in channel_choices[:1]: + transports.append(identifier) + category_id = str(category.id) + subscriptions = [{'id': category_id, + 'text': category.name, + 'transports': transports}] + resp = app.post_json(subscriptions_url , subscriptions) + if resp.json['data']: + resp = app.get(subscriptions_url, status=200) + print resp.json['data'] + for cat in resp.json['data']: + if cat['id'] == category_id: + sub_transports = [c['id'] for c in cat['transports']] + assert sub_transports == transports + + +def test_delete_subscriptions(app, categories, announces): + params = urlencode({'email': 'foo@example.com', 'uuid': str(uuid4())}) + subscriptions_url = reverse('subscriptions') + '?' + params + resp = app.delete(subscriptions_url) + if resp.json['data']: + resp = app.get(subscriptions_url, status=200) + assert resp.json['data'] == [] -- 2.8.1