Projet

Général

Profil

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

Voir les différences:

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

 corbo/api_urls.py                           |  3 +-
 corbo/api_views.py                          | 66 +++++++++++++++++++++++++++++
 corbo/channels.py                           |  5 +++
 corbo/migrations/0004_auto_20160502_1706.py | 42 ++++++++++++++++++
 corbo/models.py                             | 10 ++++-
 tests/test_api.py                           | 49 ++++++++++++++++++++-
 6 files changed, 172 insertions(+), 3 deletions(-)
 create mode 100644 corbo/migrations/0004_auto_20160502_1706.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.utils.translation import ugettext_lazy as _
17 21
from django.views.generic.base import View
22
from django.core.exceptions import PermissionDenied
23
from django.views.decorators.csrf import csrf_exempt
18 24

  
19 25
from .models import Category, Subscription
20 26
from .utils import to_json, get_channels
27
from .channels import get_channel_display_name, get_channel_choices
28

  
21 29

  
22 30
class NewslettersView(View):
23 31

  
......
29 37
                          'transports': get_channels()}
30 38
            newsletters.append(newsletter)
31 39
        return newsletters
40

  
41

  
42
class SubscriptionsView(View):
43

  
44
    @csrf_exempt
45
    def dispatch(self, request, *args, **kwargs):
46
        return super(SubscriptionsView, self).dispatch(request, *args, **kwargs)
47

  
48
    def get_subscriptions(self, email):
49
        subscriptions = defaultdict(dict)
50
        for s in Subscription.objects.filter(identifier=email):
51
            cat_id = s.category.pk
52
            subscriptions[cat_id]['id'] = str(cat_id)
53
            subscriptions[cat_id]['text'] = s.category.name
54
            transport = {'id': s.channel, 'text': get_channel_display_name(s.channel)}
55
            if 'transports' in subscriptions[cat_id]:
56
                subscriptions[cat_id]['transports'].append(transport)
57
            else:
58
                subscriptions[cat_id]['transports'] = [transport]
59

  
60
        return subscriptions.values()
61

  
62
    def update_subscriptions(self, category_id, transports, email):
63
        cat = Category.objects.get(pk=category_id)
64
        available_transports = [channel_id for channel_id, channel_name in get_channel_choices()]
65
        for transport in transports:
66
            s, created = cat.subscription_set.get_or_create(channel=transport,
67
                                                identifier=email)
68
        to_remove = set(available_transports) - set(transports)
69
        # unsubscribe categories with empty transports
70
        cat.subscription_set.filter(identifier=email, channel__in=to_remove).delete()
71

  
72
    @to_json('api')
73
    def get(self, request):
74
        email = request.GET.get('email')
75
        if not email:
76
            raise PermissionDenied('Email parameter required')
77
        return self.get_subscriptions(email)
78

  
79
    @to_json('api')
80
    def post(self, request):
81
        email = request.GET.get('email')
82
        if not email:
83
            raise PermissionDenied('Email parameter required')
84
        data = json.loads(request.body)
85
        for subscription in data:
86
            self.update_subscriptions(subscription['id'], subscription['transports'], email)
87
        return True
88

  
89
    @to_json('api')
90
    def delete(self, request):
91
        email = request.GET.get('email')
92
        if not email:
93
            raise PermissionDenied('Email parameter required')
94

  
95
        for subscription in self.get_subscriptions(email):
96
            self.update_subscriptions(subscription['id'], [], email)
97
        return True
corbo/channels.py
9 9
        for identifier, display_name in channel.get_choices():
10 10
            yield (identifier, display_name)
11 11

  
12
def get_channel_display_name(channel_id):
13
    for identifier, display_name in get_channel_choices():
14
        if channel_id in identifier:
15
            return unicode(display_name)
16

  
12 17

  
13 18
class HomepageChannel(object):
14 19
    identifier = 'homepage'
corbo/migrations/0004_auto_20160502_1706.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.AddField(
15
            model_name='subscription',
16
            name='channel',
17
            field=models.CharField(default='', max_length=32, verbose_name='channel', choices=[(b'homepage', 'Homepage'), (b'sms', 'SMS'), (b'email', 'Email')]),
18
            preserve_default=False,
19
        ),
20
        migrations.AddField(
21
            model_name='subscription',
22
            name='identifier',
23
            field=models.CharField(help_text='ex.: email, mobile phone number, jabber id', max_length=128, verbose_name='identifier', blank=True),
24
            preserve_default=True,
25
        ),
26
        migrations.AddField(
27
            model_name='subscriptiontype',
28
            name='category',
29
            field=models.ForeignKey(default='', to='corbo.Category'),
30
            preserve_default=False,
31
        ),
32
        migrations.AddField(
33
            model_name='subscriptiontype',
34
            name='channel',
35
            field=models.CharField(default='', max_length=32, verbose_name='channel', choices=[(b'homepage', 'Homepage'), (b'sms', 'SMS'), (b'email', 'Email')]),
36
            preserve_default=False,
37
        ),
38
        migrations.AlterUniqueTogether(
39
            name='subscription',
40
            unique_together=set([('channel', 'category', 'identifier')]),
41
        ),
42
    ]
corbo/models.py
66 66

  
67 67

  
68 68
class SubscriptionType(models.Model):
69
    category = models.ForeignKey('Category')
69 70
    subscription = models.ForeignKey('Subscription')
71
    channel = models.CharField(_('channel'), max_length=32,
72
            choices=channels.get_channel_choices(), blank=False)
70 73
    identifier = models.CharField(_('identifier'), max_length=128, blank=True,
71 74
            help_text=_('ex.: email, mobile phone number, jabber id'))
72 75

  
......
75 78
    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
76 79
                             blank=True, null=True)
77 80
    category = models.ForeignKey('Category', verbose_name=_('category'))
81
    channel = models.CharField(_('channel'), max_length=32,
82
            choices=channels.get_channel_choices(), blank=False)
83
    identifier = models.CharField(_('identifier'), max_length=128, blank=True,
84
            help_text=_('ex.: email, mobile phone number, jabber id'))
85

  
78 86
    class Meta:
79
        unique_together = ('user', 'category')
87
        unique_together = ('channel', 'category', 'identifier')
tests/test_api.py
3 3

  
4 4

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

  
7
from corbo.models import Category, Announce, Broadcast
8
from corbo.models import Category, Announce, Broadcast, Subscription
8 9
from corbo.utils import get_channels
9 10

  
10 11
pytestmark = pytest.mark.django_db
......
43 44
        assert category['text'] in CATEGORIES
44 45
        assert 'transports' in category
45 46
        assert category['transports'] == get_channels()
47

  
48

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

  
65

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

  
85

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