Revision 8759c981
Added by Serghei Mihai over 9 years ago
| corbo/api_urls.py | ||
|---|---|---|
|
|
||
|
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'),
|
||
|
)
|
||
| corbo/api_views.py | ||
|---|---|---|
|
# You should have received a copy of the GNU Affero General Public License
|
||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
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
|
||
|
|
||
| ... | ... | |
|
'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})
|
||
| corbo/forms.py | ||
|---|---|---|
|
|
||
|
|
||
|
class AnnounceForm(forms.ModelForm):
|
||
|
transport_channel = forms.MultipleChoiceField(required=False,
|
||
|
choices=channel_choices,
|
||
|
widget=forms.CheckboxSelectMultiple())
|
||
|
|
||
|
class Meta:
|
||
|
model = Announce
|
||
| ... | ... | |
|
}
|
||
|
fields = '__all__'
|
||
|
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
super(AnnounceForm, self).__init__(*args, **kwargs)
|
||
|
self.fields['transport_channel'].initial = [b.channel for \
|
||
|
b in self.instance.broadcast_set.all()]
|
||
|
|
||
|
def clean_transport_channel(self):
|
||
|
channels = self.cleaned_data['transport_channel']
|
||
|
limit = 160
|
||
|
if 'sms' in channels and \
|
||
|
len(self.cleaned_data['text']) > limit:
|
||
|
raise forms.ValidationError(_('Announce content exceeds %s chars'
|
||
|
' and cannot be sent by SMS' % limit))
|
||
|
return channels
|
||
|
|
||
|
def save(self, *args, **kwargs):
|
||
|
instance = super(AnnounceForm, self).save(*args, **kwargs)
|
||
|
if instance:
|
||
|
print "CD: %s" % self.cleaned_data
|
||
|
channels = self.cleaned_data['transport_channel']
|
||
|
for channel in channels:
|
||
|
Broadcast.objects.get_or_create(announce=instance,
|
||
|
channel=channel)
|
||
|
self.instance.broadcast_set.exclude(announce=instance,
|
||
|
channel__in=channels).delete()
|
||
|
Broadcast.objects.get_or_create(announce=instance)
|
||
|
return instance
|
||
|
|
||
|
|
||
|
class CategoryForm(forms.ModelForm):
|
||
|
class Meta:
|
||
|
fields = ('name', )
|
||
| corbo/migrations/0004_auto_20160504_1744.py | ||
|---|---|---|
|
# -*- 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='subscription',
|
||
|
name='category',
|
||
|
field=models.ForeignKey(verbose_name='Category', to='corbo.Category'),
|
||
|
preserve_default=True,
|
||
|
),
|
||
|
migrations.AlterUniqueTogether(
|
||
|
name='broadcast',
|
||
|
unique_together=set([]),
|
||
|
),
|
||
|
migrations.RemoveField(
|
||
|
model_name='broadcast',
|
||
|
name='channel',
|
||
|
),
|
||
|
migrations.AlterUniqueTogether(
|
||
|
name='subscription',
|
||
|
unique_together=set([('category', 'identifier', 'uuid')]),
|
||
|
),
|
||
|
migrations.RemoveField(
|
||
|
model_name='subscription',
|
||
|
name='user',
|
||
|
),
|
||
|
]
|
||
| corbo/models.py | ||
|---|---|---|
|
|
||
|
class Broadcast(models.Model):
|
||
|
announce = models.ForeignKey(Announce, verbose_name=_('announce'))
|
||
|
channel = models.CharField(_('channel'), max_length=32,
|
||
|
choices=channel_choices, blank=False)
|
||
|
time = models.DateTimeField(_('sent time'), auto_now_add=True)
|
||
|
result = models.TextField(_('result'), blank=True)
|
||
|
|
||
| ... | ... | |
|
class Meta:
|
||
|
verbose_name = _('sent')
|
||
|
ordering = ('-time',)
|
||
|
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')
|
||
| tests/test_api.py | ||
|---|---|---|
|
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
|
||
|
|
||
| ... | ... | |
|
announces = []
|
||
|
for category in Category.objects.all():
|
||
|
a = Announce.objects.create(category=category, title='By email')
|
||
|
Broadcast.objects.create(announce=a, channel='mailto')
|
||
|
Broadcast.objects.create(announce=a)
|
||
|
announces.append(a)
|
||
|
a = Announce.objects.create(category=category, title='On homepage')
|
||
|
Broadcast.objects.create(announce=a, channel='homepage')
|
||
|
Broadcast.objects.create(announce=a)
|
||
|
announces.append(a)
|
||
|
return 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'] == []
|
||
Also available in: Unified diff
api: subscriptions management endpoint (#10794)