Projet

Général

Profil

0002-api-subscriptions-management-endpoint-10794.patch

Voir les différences:

Subject: [PATCH 2/3] api: subscriptions management endpoint (#10794)

 corbo/api_urls.py                           |  3 +-
 corbo/api_views.py                          | 69 +++++++++++++++++++++++++++++
 corbo/channels.py                           |  5 +++
 corbo/migrations/0004_auto_20160504_1231.py | 53 ++++++++++++++++++++++
 corbo/models.py                             | 15 ++-----
 tests/test_api.py                           | 54 +++++++++++++++++++++-
 6 files changed, 185 insertions(+), 14 deletions(-)
 create mode 100644 corbo/migrations/0004_auto_20160504_1231.py
corbo/api_urls.py
16 16

  
17 17
from django.conf.urls import patterns, include, url
18 18

  
19
from .api_views import NewslettersView
19
from .api_views import NewslettersView, SubscriptionsView
20 20

  
21 21
urlpatterns = patterns('',
22 22
            url(r'^newsletters/', NewslettersView.as_view(), name='newsletters'),
23
            url(r'^subscriptions/', SubscriptionsView.as_view(), name='subscriptions'),
23 24
)
corbo/api_views.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
import json
18
from collections import defaultdict
19

  
20
from django.core.exceptions import PermissionDenied
21
from django.db.models import Q
22

  
17 23
from rest_framework.views import APIView
18 24
from rest_framework.response import Response
19 25

  
20 26
from .models import Category, Subscription
27
from .channels import get_channel_display_name, get_channel_choices
21 28
from .channels import get_channels
22 29

  
23 30

  
......
30 37
                          'transports': get_channels()}
31 38
            newsletters.append(newsletter)
32 39
        return Response({'data': newsletters})
40

  
41

  
42
class SubscriptionsView(APIView):
43

  
44
    def get_subscriptions(self, email, uuid=None):
45
        subscriptions = defaultdict(dict)
46
        for s in Subscription.objects.filter(Q(identifier='mailto:'+email)|Q(uuid=uuid)):
47
            cat_id = s.category.pk
48
            subscriptions[cat_id]['id'] = str(cat_id)
49
            subscriptions[cat_id]['text'] = s.category.name
50
            transport_id, transport_name = identifier.split(':')
51
            transport = {'id': transport_id,
52
                         'text': get_channel_display_name(transport_id)}
53
            if 'transports' in subscriptions[cat_id]:
54
                subscriptions[cat_id]['transports'].append(transport)
55
            else:
56
                subscriptions[cat_id]['transports'] = [transport]
57

  
58
        return subscriptions.values()
59

  
60
    def update_subscriptions(self, category_id, transports, email, uuid=None):
61
        cat = Category.objects.get(pk=category_id)
62
        available_transports = [channel_id for channel_id, channel_name in get_channel_choices()]
63
        for transport in transports:
64
            uri = '%s:%s' % (transport, email)
65
            s, created = cat.subscription_set.get_or_create(identifier=uri)
66
            if not s.uuid and uuid:
67
                s.uuid = uuid
68
                s.save()
69
        to_remove = set(available_transports) - set(transports)
70
        # unsubscribe categories with empty transports
71
        for to in to_remove:
72
            uri = '%s:%s' % (transport, email)
73
            Subscription.objects.filter(category=cat, identifier=uri).delete()
74

  
75
    def get(self, request):
76
        email = request.GET.get('email')
77
        uuid = request.GET.get('uuid')
78
        if not email:
79
            raise PermissionDenied('Email parameter required')
80
        return Response({'data': self.get_subscriptions(email, uuid)})
81

  
82
    def post(self, request):
83
        email = request.GET.get('email')
84
        uuid = request.GET.get('uuid')
85
        if not email:
86
            raise PermissionDenied('Email parameter required')
87
        data = json.loads(request.body)
88
        for subscription in data:
89
            self.update_subscriptions(subscription['id'], subscription['transports'],
90
                                      email, uuid)
91
        return Response({'data': True})
92

  
93
    def delete(self, request):
94
        email = request.GET.get('email')
95
        uuid = request.GET.get('uuid')
96
        if not email:
97
            raise PermissionDenied('Email parameter required')
98

  
99
        for subscription in self.get_subscriptions(email):
100
            self.update_subscriptions(subscription['id'], [], email, uuid)
101
        return Response({'data': True})
corbo/channels.py
12 12
def get_channels():
13 13
    return [{'id': c_id, 'text': unicode(c_name)}  for c_id, c_name in get_channel_choices()]
14 14

  
15
def get_channel_display_name(channel_id):
16
    for identifier, display_name in get_channel_choices():
17
        if channel_id in identifier:
18
            return unicode(display_name)
19

  
15 20

  
16 21
class HomepageChannel(object):
17 22
    identifier = 'homepage'
corbo/migrations/0004_auto_20160504_1231.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import models, migrations
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('corbo', '0003_auto_20160427_1342'),
11
    ]
12

  
13
    operations = [
14
        migrations.RemoveField(
15
            model_name='subscriptiontype',
16
            name='subscription',
17
        ),
18
        migrations.DeleteModel(
19
            name='SubscriptionType',
20
        ),
21
        migrations.AddField(
22
            model_name='subscription',
23
            name='identifier',
24
            field=models.CharField(help_text='ex.: mail, mobile, ...', max_length=128, verbose_name='identifier', blank=True),
25
            preserve_default=True,
26
        ),
27
        migrations.AddField(
28
            model_name='subscription',
29
            name='uuid',
30
            field=models.CharField(max_length=128, verbose_name='User identifier', blank=True),
31
            preserve_default=True,
32
        ),
33
        migrations.AlterField(
34
            model_name='broadcast',
35
            name='channel',
36
            field=models.CharField(max_length=32, verbose_name='channel', choices=[(b'homepage', 'Homepage'), (b'mobile', 'SMS'), (b'mail', 'Email')]),
37
            preserve_default=True,
38
        ),
39
        migrations.AlterField(
40
            model_name='subscription',
41
            name='category',
42
            field=models.ForeignKey(verbose_name='Category', to='corbo.Category'),
43
            preserve_default=True,
44
        ),
45
        migrations.AlterUniqueTogether(
46
            name='subscription',
47
            unique_together=set([]),
48
        ),
49
        migrations.RemoveField(
50
            model_name='subscription',
51
            name='user',
52
        ),
53
    ]
corbo/models.py
65 65
        unique_together = ('announce', 'channel')
66 66

  
67 67

  
68
class SubscriptionType(models.Model):
69
    subscription = models.ForeignKey('Subscription')
70
    identifier = models.CharField(_('identifier'), max_length=128, blank=True,
71
            help_text=_('ex.: email, mobile phone number, jabber id'))
72

  
73

  
74 68
class Subscription(models.Model):
75
    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
76
                             blank=True, null=True)
77
    category = models.ForeignKey('Category', verbose_name=_('category'))
78
    class Meta:
79
        unique_together = ('user', 'category')
69
    category = models.ForeignKey('Category', verbose_name=_('Category'))
70
    uuid = models.CharField(_('User identifier'), max_length=128, blank=True)
71
    identifier = models.CharField(_('identifier'), max_length=128, blank=True,
72
            help_text=_('ex.: mail, mobile, ...'))
tests/test_api.py
1 1
import pytest
2 2
import json
3
from uuid import uuid4
3 4

  
4 5

  
5 6
from django.core.urlresolvers import reverse
7
from django.utils.http import urlencode
6 8

  
7
from corbo.models import Category, Announce, Broadcast
8
from corbo.channels import get_channels
9
from corbo.models import Category, Announce, Broadcast, Subscription
10
from corbo.channels import get_channels, get_channel_choices
9 11

  
10 12
pytestmark = pytest.mark.django_db
11 13

  
......
43 45
        assert category['text'] in CATEGORIES
44 46
        assert 'transports' in category
45 47
        assert category['transports'] == get_channels()
48

  
49

  
50
def test_get_subscriptions_by_email(app, categories, announces):
51
    resp = app.get(reverse('subscriptions'), status=403)
52
    foo = 'foo@example.com'
53
    for identifier, name in get_channel_choices():
54
        for category in categories:
55
            uri = '%s:%s' % (identifier, foo)
56
            subscription = Subscription.objects.create(identifier=uri,
57
                                category=category)
58
            resp = app.get(reverse('subscriptions'), {'email': foo}, status=200)
59
            assert 'data' in resp.json
60
            data = resp.json['data']
61
            for d in data:
62
                assert d['id'] in [str(category.id) for category in categories]
63
                assert d['text'] in [category.name for category in categories]
64
                for t in d['transports']:
65
                    assert t['id'] == identifier
66

  
67

  
68
def test_update_subscriptions(app, categories, announces):
69
    params = urlencode({'email': 'foo@example.com',
70
                        'uuid': str(uuid4())})
71
    subscriptions_url = reverse('subscriptions') + '?' + params
72
    for category in categories:
73
        transports = []
74
        for identifier, name in get_channel_choices():
75
            transports.append(identifier)
76
            category_id = str(category.id)
77
            subscriptions = [{'id': category_id,
78
                              'text': category.name,
79
                              'transports': transports}]
80
            resp = app.post_json(subscriptions_url , subscriptions)
81
            if resp.json['data']:
82
                resp = app.get(subscriptions_url, status=200)
83
                for cat in resp.json['data']:
84
                    if cat['id'] == category_id:
85
                        sub_transports = [c['id'] for c in cat['transports']]
86
                        assert sub_transports == transports
87

  
88

  
89
def test_delete_subscriptions(app, categories, announces):
90
    params = urlencode({'email': 'foo@example.com', 'uuid': str(uuid4())})
91
    subscriptions_url = reverse('subscriptions') + '?' + params
92
    resp = app.delete(subscriptions_url)
93
    if resp.json['data']:
94
        resp = app.get(subscriptions_url, status=200)
95
        assert resp.json['data'] == []
46
-