Projet

Général

Profil

0001-misc-remove-newsletter-app-53541.patch

Lauréline Guérin, 29 avril 2021 15:26

Télécharger (38,8 ko)

Voir les différences:

Subject: [PATCH] misc: remove newsletter app (#53541)

 combo/apps/newsletters/README                 |  75 -----
 combo/apps/newsletters/__init__.py            |  31 --
 combo/apps/newsletters/forms.py               |  83 ------
 .../newsletters/migrations/0001_initial.py    |  58 ----
 .../0002_newsletterscell_extra_css_class.py   |  19 --
 ...3_newsletterscell_last_update_timestamp.py |  23 --
 combo/apps/newsletters/migrations/__init__.py |   0
 combo/apps/newsletters/models.py              | 165 -----------
 .../templates/newsletters/newsletters.html    |  43 ---
 combo/apps/newsletters/urls.py                |  12 -
 combo/apps/newsletters/views.py               |  51 ----
 combo/settings.py                             |   2 -
 tests/settings.py                             |   1 -
 tests/test_manager.py                         |   8 +-
 tests/test_newsletters_cell.py                | 275 ------------------
 tests/test_search.py                          |   2 +-
 16 files changed, 5 insertions(+), 843 deletions(-)
 delete mode 100644 combo/apps/newsletters/README
 delete mode 100644 combo/apps/newsletters/__init__.py
 delete mode 100644 combo/apps/newsletters/forms.py
 delete mode 100644 combo/apps/newsletters/migrations/0001_initial.py
 delete mode 100644 combo/apps/newsletters/migrations/0002_newsletterscell_extra_css_class.py
 delete mode 100644 combo/apps/newsletters/migrations/0003_newsletterscell_last_update_timestamp.py
 delete mode 100644 combo/apps/newsletters/migrations/__init__.py
 delete mode 100644 combo/apps/newsletters/models.py
 delete mode 100644 combo/apps/newsletters/templates/newsletters/newsletters.html
 delete mode 100644 combo/apps/newsletters/urls.py
 delete mode 100644 combo/apps/newsletters/views.py
 delete mode 100644 tests/test_newsletters_cell.py
combo/apps/newsletters/README
1
Combo newsletters cell
2
======================
3

  
4
This cell is enabled by default.
5

  
6
It expects a webservice returning newsletters and user subscriptions in the
7
following format:
8

  
9
  [{'id': '1', 'text': 'Democratie locale',
10
    'transports': [{'id': 'mail', 'text': 'mail'}]},
11
  {'id': '2', 'text': 'Rencontres de quartiers',
12
   'transports': [{'id': 'mail', 'text': 'mail'}]},
13
  {'id': '3', 'text': 'Environnement',
14
   'transports': [{'id': 'mail', 'text': 'mail'},
15
                  {'id': 'sms', 'text': 'sms'},
16
                  {'id': 'rss', 'text': 'rss'}]},
17
  {'id': '4', 'text': u'Marchés publics',
18
   'transports': [{'id': 'mail', 'text': 'mail'},
19
                  {'id': 'rss', 'text': 'rss'}]},
20
  {'id': '5', 'text': "Offres d'emploi",
21
   'transports': [{'id': 'mail', 'text': 'mail'},
22
                  {'id': 'rss', 'text': 'rss'}]},
23
  {'id': '6', 'text': 'Infos créche',
24
   'transports': [{'id': 'sms', 'text': 'sms'},
25
                  {'id': 'rss', 'text': 'rss'}]},
26
  {'id': '7', 'text': 'Familles',
27
   'transports': [{'id': 'mail', 'text': 'mail'},
28
                  {'id': 'sms', 'text': 'sms'}]},
29
  {'id': '8', 'text': 'Travaux',
30
   'transports': [{'id': 'mail', 'text': 'mail'},
31
                  {'id': 'sms', 'text': 'sms'},
32
                  {'id': 'rss', 'text': 'rss'}]}]
33

  
34

  
35
The url to the webservice should be provided in the instatiation form. The
36
fields **resources_restrictions** and **transports_restrictions** allow to
37
filter the newsletters by their name and transport means.
38

  
39
**resources_restrictions** field is a comma separated list of newsletters
40
slugs. For example: __rencontres-de-quartiers,infos-creche__.
41

  
42
In this case only the following newsletters will be exposed in the
43
subscriptions form:
44
  [{'id': '2', 'text': 'Rencontres de quartiers',
45
   'transports': [{'id': 'mail', 'text': 'mail'}]},
46
   {'id': '6', 'text': 'Infos créche',
47
   'transports': [{'id': 'sms', 'text': 'sms'},
48
                  {'id': 'rss', 'text': 'rss'}]}]
49

  
50
**transport_restrictions** field is a comma separated list of transport types.
51
Example: __sms,rss__
52

  
53
In this case only the newsletters containing one of these transports will be
54
shown:
55

  
56
  [{'id': '3', 'text': 'Environnement',
57
   'transports': [{'id': 'mail', 'text': 'mail'},
58
                  {'id': 'sms', 'text': 'sms'},
59
                  {'id': 'rss', 'text': 'rss'}]},
60
  {'id': '4', 'text': u'Marchés publics',
61
   'transports': [{'id': 'mail', 'text': 'mail'},
62
                  {'id': 'rss', 'text': 'rss'}]},
63
  {'id': '5', 'text': "Offres d'emploi",
64
   'transports': [{'id': 'mail', 'text': 'mail'},
65
                  {'id': 'rss', 'text': 'rss'}]},
66
  {'id': '6', 'text': 'Infos créche',
67
   'transports': [{'id': 'sms', 'text': 'sms'},
68
                  {'id': 'rss', 'text': 'rss'}]},
69
  {'id': '7', 'text': 'Familles',
70
   'transports': [{'id': 'mail', 'text': 'mail'},
71
                  {'id': 'sms', 'text': 'sms'}]},
72
  {'id': '8', 'text': 'Travaux',
73
   'transports': [{'id': 'mail', 'text': 'mail'},
74
                  {'id': 'sms', 'text': 'sms'},
75
                  {'id': 'rss', 'text': 'rss'}]}]
combo/apps/newsletters/__init__.py
1
# combo - content management system
2
# Copyright (C) 2015  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import django.apps
18
from django.utils.translation import ugettext_lazy as _
19

  
20

  
21
class AppConfig(django.apps.AppConfig):
22
    name = 'combo.apps.newsletters'
23
    verbose_name = _('Newsletters')
24

  
25
    def get_before_urls(self):
26
        from . import urls
27

  
28
        return urls.urlpatterns
29

  
30

  
31
default_app_config = 'combo.apps.newsletters.AppConfig'
combo/apps/newsletters/forms.py
1
# combo - content management system
2
# Copyright (C) 2015  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import logging
18

  
19
from django import forms
20
from django.utils.translation import ugettext_lazy as _
21

  
22

  
23
class NewslettersManageForm(forms.Form):
24
    def __init__(self, *args, **kwargs):
25
        logger = logging.getLogger(__name__)
26
        self.request = kwargs.pop('request')
27
        self.user = self.request.user
28
        self.instance = kwargs.pop('instance')
29
        self.themes = set()
30
        super(NewslettersManageForm, self).__init__(*args, **kwargs)
31

  
32
        # initialize cleane data in order to be able to add errors
33
        self.cleaned_data = {}
34
        try:
35
            newsletters = self.instance.get_newsletters()
36
        except Exception as e:
37
            self.add_error(None, _('An error occured while getting newsletters. Please try later.'))
38
            logger.error('Error occured while getting newsletters: %r', e)
39
            return
40
        self.params = {}
41
        user_name_id = self.user.get_name_id()
42
        if user_name_id:
43
            self.params['uuid'] = user_name_id
44

  
45
        # get mobile number from mellon session as it is not user attribute
46
        if self.request.session.get('mellon_session'):
47
            self.params['mobile'] = self.request.session['mellon_session'].get('mobile', '')
48
        try:
49
            subscriptions = self.instance.get_subscriptions(self.user, **self.params)
50
        except Exception as e:
51
            self.add_error(None, _('An error occured while getting subscriptions. Please try later.'))
52
            logger.error('Error occured while getting subscriptions: %r', e)
53
            return
54

  
55
        for newsletter in newsletters:
56
            if not self.instance.check_resource(newsletter['text']):
57
                continue
58
            choices = []
59
            initial = []
60
            for transport in newsletter['transports']:
61
                if not self.instance.check_transport(transport['id']):
62
                    continue
63
                self.themes.add((transport['id'], transport['text']))
64
                choices.append((transport['id'], ''))
65
                if transport in newsletter['transports']:
66
                    for subscription in subscriptions:
67
                        if subscription['id'] == newsletter['id']:
68
                            initial = [t['id'] for t in subscription['transports']]
69
                self.fields[newsletter['id']] = forms.MultipleChoiceField(
70
                    label=newsletter['text'],
71
                    help_text=transport['id'],
72
                    choices=choices,
73
                    initial=initial,
74
                    widget=forms.CheckboxSelectMultiple(),
75
                    required=False,
76
                )
77

  
78
    def save(self):
79
        self.full_clean()
80
        subscriptions = []
81
        for key, value in self.cleaned_data.items():
82
            subscriptions.append({'id': key, 'transports': value})
83
        self.instance.set_subscriptions(subscriptions, self.user, **self.params)
combo/apps/newsletters/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('auth', '0001_initial'),
11
        ('data', '0013_parameterscell'),
12
    ]
13

  
14
    operations = [
15
        migrations.CreateModel(
16
            name='NewslettersCell',
17
            fields=[
18
                (
19
                    'id',
20
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
21
                ),
22
                ('placeholder', models.CharField(max_length=20)),
23
                ('order', models.PositiveIntegerField()),
24
                ('slug', models.SlugField(verbose_name='Slug', blank=True)),
25
                ('public', models.BooleanField(default=True, verbose_name='Public')),
26
                (
27
                    'restricted_to_unlogged',
28
                    models.BooleanField(default=False, verbose_name='Restrict to unlogged users'),
29
                ),
30
                ('title', models.CharField(max_length=128, verbose_name='Title')),
31
                ('url', models.URLField(max_length=128, verbose_name='Newsletters service url')),
32
                (
33
                    'resources_restrictions',
34
                    models.CharField(
35
                        help_text='list of resources(themes) separated by commas',
36
                        max_length=1024,
37
                        verbose_name='resources restrictions',
38
                        blank=True,
39
                    ),
40
                ),
41
                (
42
                    'transports_restrictions',
43
                    models.CharField(
44
                        help_text='list of transports separated by commas',
45
                        max_length=1024,
46
                        verbose_name='transports restrictions',
47
                        blank=True,
48
                    ),
49
                ),
50
                ('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
51
                ('page', models.ForeignKey(to='data.Page', on_delete=models.CASCADE)),
52
            ],
53
            options={
54
                'verbose_name': 'Newsletters',
55
            },
56
            bases=(models.Model,),
57
        ),
58
    ]
combo/apps/newsletters/migrations/0002_newsletterscell_extra_css_class.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('newsletters', '0001_initial'),
11
    ]
12

  
13
    operations = [
14
        migrations.AddField(
15
            model_name='newsletterscell',
16
            name='extra_css_class',
17
            field=models.CharField(max_length=100, verbose_name='Extra classes for CSS styling', blank=True),
18
        ),
19
    ]
combo/apps/newsletters/migrations/0003_newsletterscell_last_update_timestamp.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
import datetime
5

  
6
from django.db import migrations, models
7
from django.utils.timezone import utc
8

  
9

  
10
class Migration(migrations.Migration):
11

  
12
    dependencies = [
13
        ('newsletters', '0002_newsletterscell_extra_css_class'),
14
    ]
15

  
16
    operations = [
17
        migrations.AddField(
18
            model_name='newsletterscell',
19
            name='last_update_timestamp',
20
            field=models.DateTimeField(default=datetime.datetime.now(utc), auto_now=True),
21
            preserve_default=False,
22
        ),
23
    ]
combo/apps/newsletters/models.py
1
# combo - content management system
2
# Copyright (C) 2015  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
import json
18
import logging
19

  
20
from django.conf import settings
21
from django.db import models
22
from django.forms import models as model_forms
23
from django.template.defaultfilters import slugify
24
from django.utils.http import urlencode
25
from django.utils.translation import ugettext_lazy as _
26
from requests.exceptions import HTTPError, RequestException
27

  
28
from combo.data.library import register_cell_class
29
from combo.data.models import CellBase
30
from combo.utils import requests
31

  
32
from .forms import NewslettersManageForm
33

  
34

  
35
class SubscriptionsSaveError(Exception):
36
    pass
37

  
38

  
39
@register_cell_class
40
class NewslettersCell(CellBase):
41
    title = models.CharField(verbose_name=_('Title'), max_length=128)
42
    url = models.URLField(verbose_name=_('Newsletters service url'), max_length=128)
43
    resources_restrictions = models.CharField(
44
        _('resources restrictions'),
45
        blank=True,
46
        max_length=1024,
47
        help_text=_('list of resources(themes) separated by commas'),
48
    )
49
    transports_restrictions = models.CharField(
50
        _('transports restrictions'),
51
        blank=True,
52
        max_length=1024,
53
        help_text=_('list of transports separated by commas'),
54
    )
55

  
56
    template_name = 'newsletters/newsletters.html'
57
    user_dependant = True
58

  
59
    @classmethod
60
    def is_enabled(cls):
61
        return settings.NEWSLETTERS_CELL_ENABLED
62

  
63
    class Meta:
64
        verbose_name = _('Newsletters')
65

  
66
    def get_default_form_class(self):
67
        model_fields = ('title', 'url', 'resources_restrictions', 'transports_restrictions')
68
        return model_forms.modelform_factory(self.__class__, fields=model_fields)
69

  
70
    def simplify(self, name):
71
        return slugify(name.strip())
72

  
73
    def get_resources_restrictions(self):
74
        return list(filter(None, map(self.simplify, self.resources_restrictions.strip().split(','))))
75

  
76
    def get_transports_restrictions(self):
77
        return list(filter(None, map(self.simplify, self.transports_restrictions.strip().split(','))))
78

  
79
    def check_resource(self, resource):
80
        restrictions = self.get_resources_restrictions()
81
        if restrictions and self.simplify(resource) not in restrictions:
82
            return False
83
        return True
84

  
85
    def check_transport(self, transport):
86
        restrictions = self.get_transports_restrictions()
87
        if restrictions and transport not in restrictions:
88
            return False
89
        return True
90

  
91
    def filter_data(self, data):
92
        filtered = []
93
        for item in data:
94
            if not self.check_resource(item['text']):
95
                continue
96
            for t in item['transports']:
97
                if self.check_transport(t['id']):
98
                    filtered.append(item)
99
        return filtered
100

  
101
    def get_newsletters(self):
102
        endpoint = self.url + 'newsletters/'
103
        try:
104
            response = requests.get(endpoint, remote_service='auto', cache_duration=60, without_user=True)
105
            response.raise_for_status()
106
        except RequestException:
107
            return []
108
        json_response = response.json()
109
        return self.filter_data(json_response['data'])
110

  
111
    def get_subscriptions(self, user, **kwargs):
112
        endpoint = self.url + 'subscriptions/'
113
        try:
114
            response = requests.get(
115
                endpoint, remote_service='auto', user=user, cache_duration=0, params=kwargs
116
            )
117
            response.raise_for_status()
118
        except RequestException:
119
            return []
120
        json_response = response.json()
121
        return self.filter_data(json_response['data'])
122

  
123
    def set_subscriptions(self, subscriptions, user, **kwargs):
124
        logger = logging.getLogger(__name__)
125
        # uuid is mandatory to store subscriptions
126
        if 'uuid' not in kwargs:
127
            raise SubscriptionsSaveError
128
        headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
129
        endpoint = self.url + 'subscriptions/'
130
        try:
131
            response = requests.post(
132
                endpoint,
133
                remote_service='auto',
134
                data=json.dumps(subscriptions),
135
                user=user,
136
                federation_key='email',
137
                params=kwargs,
138
                headers=headers,
139
            )
140
            response.raise_for_status()
141
            if not response.json()['data']:
142
                raise SubscriptionsSaveError
143
        except HTTPError as e:
144
            logger.error(
145
                u'set subscriptions on %s returned an HTTP error code: %s',
146
                e.response.request.url,
147
                e.response.status_code,
148
            )
149
            raise SubscriptionsSaveError
150
        except RequestException as e:
151
            logger.error(u'set subscriptions on %s failed with exception: %s', endpoint, e)
152
            raise SubscriptionsSaveError
153

  
154
    def render(self, context):
155
        user = context.get('user')
156
        if user and user.is_authenticated:
157
            form = NewslettersManageForm(instance=self, request=context['request'])
158
            context['form'] = form
159
        return super(NewslettersCell, self).render(context)
160

  
161
    def is_visible(self, **kwargs):
162
        user = kwargs.get('user')
163
        if user is None or not user.is_authenticated:
164
            return False
165
        return super(NewslettersCell, self).is_visible(**kwargs)
combo/apps/newsletters/templates/newsletters/newsletters.html
1
{% load i18n %}
2
<h2>{{ cell.title }}</h2>
3
{% if form %}
4
{% if form.non_field_errors %}
5
{{ form.non_field_errors }}
6
{% else %}
7
<form method="post" action={% url 'newsletters-update' pk=cell.pk %}>
8
  {% csrf_token %}
9

  
10
  {% for field in form %}
11
  {{ field.error }}
12
  {% endfor %}
13
  <table class="newsletters-form">
14
    <thead>
15
      <tr>
16
        <td>{% trans "Theme" %}</td>
17
        {% for id, theme in form.themes %}
18
        <td data-id="{{ id }}">{{ theme }}</td>
19
        {% endfor %}
20
      </tr>
21
    </thead>
22
    <tbody>
23
      {% for field in form %}
24
      <tr>
25
        <td>{{ field.label }}</td>
26
        {% for id, theme in form.themes %}<td data-id="{{ id }}">
27
        {% for w in field %}
28
          {% with choice_value=w.data.value choice_value18=w.choice_value %}
29
            {% if choice_value == id or choice_value18 == id %}{{ w }}{% endif %}
30
          {% endwith %}
31
        {% endfor %}
32
        </td>
33
        {% endfor %}
34
      </tr>
35
      {% endfor %}
36
    </tbody>
37
  </table>
38
  <button>{% trans "Modify" %}</button>
39
</form>
40
{% endif %}
41
{% else %}
42
<div>{% trans "No subscriptions" %}</div>
43
{% endif %}
combo/apps/newsletters/urls.py
1
from django.conf.urls import url
2
from django.contrib.auth.decorators import login_required
3

  
4
from .views import NewslettersView
5

  
6
urlpatterns = [
7
    url(
8
        r'^newsletters/(?P<pk>\w+)/update$',
9
        login_required(NewslettersView.as_view()),
10
        name='newsletters-update',
11
    ),
12
]
combo/apps/newsletters/views.py
1
# combo - content management system
2
# Copyright (C) 2015  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from django.contrib import messages
18
from django.http import HttpResponseRedirect
19
from django.utils.translation import ugettext_lazy as _
20
from django.views.generic import FormView
21

  
22
from .forms import NewslettersManageForm
23
from .models import NewslettersCell, SubscriptionsSaveError
24

  
25

  
26
class NewslettersView(FormView):
27
    http_method_names = ['post']
28
    form_class = NewslettersManageForm
29

  
30
    def form_valid(self, form):
31
        try:
32
            form.save()
33
            messages.info(self.request, _('Your subscriptions are successfully saved'))
34
        except SubscriptionsSaveError:
35
            messages.error(
36
                self.request, _('An error occured while saving your subscriptions. Please try later.')
37
            )
38
        return super(NewslettersView, self).form_valid(form)
39

  
40
    def get_form_kwargs(self):
41
        kwargs = super(NewslettersView, self).get_form_kwargs()
42
        self.instance = NewslettersCell.objects.get(pk=self.kwargs['pk'])
43
        kwargs.update({'request': self.request, 'instance': self.instance})
44
        return kwargs
45

  
46
    def form_invalid(self, form):
47
        messages.error(self.request, _('An error occured while saving your subscriptions. Please try later.'))
48
        return HttpResponseRedirect(self.get_success_url())
49

  
50
    def get_success_url(self):
51
        return self.instance.page.get_online_url()
combo/settings.py
70 70
    'combo.apps.family',
71 71
    'combo.apps.dataviz',
72 72
    'combo.apps.lingo',
73
    'combo.apps.newsletters',
74 73
    'combo.apps.fargo',
75 74
    'combo.apps.notifications',
76 75
    'combo.apps.search',
......
363 362
# hide work-in-progress/experimental/broken/legacy/whatever cells for now
364 363
BOOKING_CALENDAR_CELL_ENABLED = False
365 364
LEGACY_CHART_CELL_ENABLED = False
366
NEWSLETTERS_CELL_ENABLED = False
367 365

  
368 366

  
369 367
def debug_show_toolbar(request):
tests/settings.py
82 82

  
83 83
BOOKING_CALENDAR_CELL_ENABLED = True
84 84
LEGACY_CHART_CELL_ENABLED = True
85
NEWSLETTERS_CELL_ENABLED = True
86 85

  
87 86
USER_PROFILE_CONFIG = {
88 87
    'fields': [
tests/test_manager.py
862 862
    resp.form['site_file'] = Upload('site-export.json', site_export, 'application/json')
863 863
    with CaptureQueriesContext(connection) as ctx:
864 864
        resp = resp.form.submit()
865
        assert len(ctx.captured_queries) in [303, 304]
865
        assert len(ctx.captured_queries) in [298, 299]
866 866
    assert Page.objects.count() == 4
867 867
    assert PageSnapshot.objects.all().count() == 4
868 868

  
......
873 873
    resp.form['site_file'] = Upload('site-export.json', site_export, 'application/json')
874 874
    with CaptureQueriesContext(connection) as ctx:
875 875
        resp = resp.form.submit()
876
        assert len(ctx.captured_queries) == 272
876
        assert len(ctx.captured_queries) == 268
877 877
    assert set(Page.objects.get(slug='one').related_cells['cell_types']) == set(
878 878
        ['data_textcell', 'data_linkcell']
879 879
    )
......
2178 2178

  
2179 2179
    with CaptureQueriesContext(connection) as ctx:
2180 2180
        resp2 = resp.click('view', index=1)
2181
        assert len(ctx.captured_queries) == 71
2181
        assert len(ctx.captured_queries) == 70
2182 2182
    assert Page.snapshots.latest('pk').related_cells == {'cell_types': ['data_textcell']}
2183 2183
    assert resp2.text.index('Hello world') < resp2.text.index('Foobar3')
2184 2184

  
......
2239 2239
    resp = resp.click('restore', index=6)
2240 2240
    with CaptureQueriesContext(connection) as ctx:
2241 2241
        resp = resp.form.submit().follow()
2242
        assert len(ctx.captured_queries) == 144
2242
        assert len(ctx.captured_queries) == 142
2243 2243

  
2244 2244
    resp2 = resp.click('See online')
2245 2245
    assert resp2.text.index('Foobar1') < resp2.text.index('Foobar2') < resp2.text.index('Foobar3')
tests/test_newsletters_cell.py
1
# -*- coding: utf-8 -*-
2

  
3
import json
4

  
5
import mock
6
import pytest
7
import requests
8
from django.contrib.auth.models import User
9
from django.template.defaultfilters import slugify
10

  
11
from combo.apps.newsletters.forms import NewslettersManageForm
12
from combo.apps.newsletters.models import NewslettersCell, SubscriptionsSaveError
13
from combo.data.models import Page
14
from combo.utils import check_query
15

  
16
pytestmark = pytest.mark.django_db
17

  
18
NEWSLETTERS = [
19
    {'id': '1', 'text': 'Democratie locale', 'transports': [{'id': 'mail', 'text': 'mail'}]},
20
    {'id': '2', 'text': 'Rencontres de quartiers', 'transports': [{'id': 'mail', 'text': 'mail'}]},
21
    {
22
        'id': '3',
23
        'text': 'Environnement',
24
        'transports': [
25
            {'id': 'mail', 'text': 'mail'},
26
            {'id': 'sms', 'text': 'sms'},
27
            {'id': 'rss', 'text': 'rss'},
28
        ],
29
    },
30
    {
31
        'id': '4',
32
        'text': u'Marchés publics',
33
        'transports': [{'id': 'mail', 'text': 'mail'}, {'id': 'rss', 'text': 'rss'}],
34
    },
35
    {
36
        'id': '5',
37
        'text': "Offres d'emploi",
38
        'transports': [{'id': 'mail', 'text': 'mail'}, {'id': 'rss', 'text': 'rss'}],
39
    },
40
    {
41
        'id': '6',
42
        'text': 'Infos créche',
43
        'transports': [{'id': 'sms', 'text': 'sms'}, {'id': 'rss', 'text': 'rss'}],
44
    },
45
    {
46
        'id': '7',
47
        'text': 'Familles',
48
        'transports': [{'id': 'mail', 'text': 'mail'}, {'id': 'sms', 'text': 'sms'}],
49
    },
50
    {
51
        'id': '8',
52
        'text': 'Travaux',
53
        'transports': [
54
            {'id': 'mail', 'text': 'mail'},
55
            {'id': 'sms', 'text': 'sms'},
56
            {'id': 'rss', 'text': 'rss'},
57
        ],
58
    },
59
]
60

  
61
SUBSCRIPTIONS = [
62
    {'id': '3', 'text': 'Environnement', 'transports': [{'id': 'mail', 'text': 'mail'}]},
63
    {'id': '7', 'text': 'Familles', 'transports': [{'id': 'mail', 'text': 'mail'}]},
64
    {'id': '5', 'text': "Offres d'emploi", 'transports': [{'id': 'mail', 'text': 'mail'}]},
65
    {
66
        'id': '6',
67
        'text': 'Infos créche',
68
        'transports': [{'id': 'sms', 'text': 'sms'}, {'id': 'rss', 'text': 'rss'}],
69
    },
70
]
71

  
72
USER_EMAIL = 'foobar@example.com'
73

  
74

  
75
@pytest.fixture
76
def cell():
77
    page = Page()
78
    page.save()
79
    cell = NewslettersCell(title='Newsletter test', url='http://example.org/', page=page, order=0)
80
    cell.save()
81
    return cell
82

  
83

  
84
@pytest.fixture
85
def user():
86
    try:
87
        user = User.objects.get(username='foo')
88
    except User.DoesNotExist:
89
        user = User.objects.create(username='foo', email=USER_EMAIL)
90
    return user
91

  
92

  
93
@mock.patch('combo.apps.newsletters.models.requests.get')
94
def test_get_newsletters_by_transports(mock_get, cell):
95
    restrictions = ('mail', 'sms')
96
    cell.transports_restrictions = ','.join(restrictions)
97
    expected_newsletters = []
98
    for n in NEWSLETTERS:
99
        for t in n['transports']:
100
            if t['id'] in restrictions:
101
                expected_newsletters.append(n)
102
                continue
103
    mock_json = mock.Mock()
104
    mock_json.json.return_value = {'err': 0, 'data': NEWSLETTERS}
105
    mock_get.return_value = mock_json
106
    assert cell.get_newsletters() == expected_newsletters
107
    assert mock_get.call_args[1]['without_user']
108
    assert 'user' not in mock_get.call_args[1]
109

  
110
    mock_get.side_effect = requests.RequestException
111
    assert cell.get_newsletters() == []
112

  
113

  
114
@mock.patch('combo.apps.newsletters.models.requests.get')
115
def test_get_newsletters_by_unrestricted_transports(mock_get, cell):
116
    cell.transports_restrictions = ''
117
    expected_newsletters = []
118
    for n in NEWSLETTERS:
119
        for t in n['transports']:
120
            expected_newsletters.append(n)
121
    mock_json = mock.Mock()
122
    mock_json.json.return_value = {'err': 0, 'data': NEWSLETTERS}
123
    mock_get.return_value = mock_json
124
    assert cell.get_newsletters() == expected_newsletters
125

  
126

  
127
@mock.patch('combo.apps.newsletters.models.requests.get')
128
def test_get_newsletters_by_resources(mock_get, cell):
129
    restrictions = ('marches-publics', 'familles', 'democratie-locale')
130
    cell.transports_restrictions = 'mail'
131
    cell.resources_restrictions = ','.join(restrictions)
132
    expected_newsletters = []
133
    for n in NEWSLETTERS:
134
        if slugify(n['text']) in restrictions:
135
            expected_newsletters.append(n)
136
    mock_json = mock.Mock()
137
    mock_json.json.return_value = {'err': 0, 'data': NEWSLETTERS}
138
    mock_get.return_value = mock_json
139
    assert cell.get_newsletters() == expected_newsletters
140

  
141

  
142
@mock.patch('combo.apps.newsletters.models.requests.get')
143
def test_get_subscriptions(mock_get, cell, user):
144
    restrictions = ('mail', 'sms')
145
    cell.transports_restrictions = ','.join(restrictions)
146
    expected_subscriptions = []
147
    for n in SUBSCRIPTIONS:
148
        for t in n['transports']:
149
            if t['id'] in restrictions:
150
                expected_subscriptions.append(n)
151
                continue
152
    mock_json = mock.Mock()
153
    mock_json.json.return_value = {'err': 0, 'data': SUBSCRIPTIONS}
154
    mock_get.return_value = mock_json
155
    assert cell.get_subscriptions(user) == expected_subscriptions
156
    assert mock_get.call_args[1]['user'].email == USER_EMAIL
157

  
158
    mock_get.side_effect = requests.RequestException
159
    assert cell.get_subscriptions(user) == []
160

  
161

  
162
@mock.patch('combo.utils.requests_wrapper.RequestsSession.send')
163
def test_get_subscriptions_signature_check(mock_send, cell, user):
164
    restrictions = ('mail', 'sms')
165
    cell.transports_restrictions = ','.join(restrictions)
166
    expected_subscriptions = []
167
    for n in SUBSCRIPTIONS:
168
        for t in n['transports']:
169
            if t['id'] in restrictions:
170
                expected_subscriptions.append(n)
171
                continue
172
    mock_json = mock.Mock(status_code=200)
173
    mock_json.json.return_value = {'err': 0, 'data': SUBSCRIPTIONS}
174
    mock_send.return_value = mock_json
175
    cell.get_subscriptions(user)
176
    url = mock_send.call_args[0][0].url
177
    assert check_query(url.split('?', 1)[-1], 'combo')
178

  
179

  
180
@mock.patch('combo.apps.newsletters.models.requests.post')
181
def test_failed_set_subscriptions(mock_post, cell, user):
182
    restrictions = ('sms', 'mail')
183
    cell.transports_restrictions = ','.join(restrictions)
184
    subscriptions = [
185
        {'id': '1', 'transports': [{'id': 'mail', 'text': 'mail'}]},
186
        {'id': '7', 'transports': [{'id': 'sms', 'text': 'sms'}]},
187
        {'id': '8', 'transports': [{'id': 'sms', 'text': 'sms'}, {'id': 'mail', 'text': 'mail'}]},
188
    ]
189
    mock_post.side_effect = requests.ConnectionError
190
    with pytest.raises(SubscriptionsSaveError):
191
        cell.set_subscriptions(subscriptions, user, uuid='useruuid')
192

  
193
    mock_post.side_effect = requests.HTTPError(response=mock.MagicMock())
194
    with pytest.raises(SubscriptionsSaveError):
195
        cell.set_subscriptions(subscriptions, user, uuid='useruuid')
196

  
197

  
198
def test_set_subscriptions_with_no_uuid(cell, user):
199
    restrictions = ('sms', 'mail')
200
    cell.transports_restrictions = ','.join(restrictions)
201
    subscriptions = [
202
        {'id': '1', 'transports': [{'id': 'mail', 'text': 'mail'}]},
203
        {'id': '8', 'transports': [{'id': 'sms', 'text': 'sms'}, {'id': 'mail', 'text': 'mail'}]},
204
    ]
205
    with pytest.raises(SubscriptionsSaveError):
206
        cell.set_subscriptions(subscriptions, user)
207

  
208

  
209
@mock.patch('combo.apps.newsletters.models.requests.post')
210
def test_set_subscriptions(mock_post, cell, user):
211
    restrictions = ('sms', 'mail')
212
    cell.transports_restrictions = ','.join(restrictions)
213
    subscriptions = [
214
        {'id': '1', 'transports': [{'id': 'mail', 'text': 'mail'}]},
215
        {'id': '7', 'transports': [{'id': 'sms', 'text': 'sms'}]},
216
        {'id': '8', 'transports': [{'id': 'sms', 'text': 'sms'}, {'id': 'mail', 'text': 'mail'}]},
217
    ]
218
    mock_json = mock.Mock()
219
    mock_json.json.return_value = {'err': 0, 'data': True}
220
    mock_post.return_value = mock_json
221
    cell.set_subscriptions(subscriptions, user, uuid='useruuid')
222
    args, kwargs = mock_post.call_args
223
    assert kwargs['params']['uuid'] == 'useruuid'
224
    assert kwargs['federation_key'] == 'email'
225
    assert kwargs['user'].email == USER_EMAIL
226

  
227

  
228
@mock.patch('combo.apps.newsletters.models.requests.get')
229
def test_get_subscriptions_with_name_id_and_mobile(mock_get, cell, user):
230
    restrictions = ('sms', 'mail')
231
    cell.transports_restrictions = ','.join(restrictions)
232
    mock_json = mock.Mock()
233
    mock_json.json.return_value = {'err': 0, 'data': NEWSLETTERS}
234
    mock_get.return_value = mock_json
235

  
236
    fake_saml_request = mock.Mock()
237
    fake_saml_request.user = mock.Mock(email=USER_EMAIL)
238
    fake_saml_request.user.get_name_id.return_value = 'nameid'
239
    fake_saml_request.session = {'mellon_session': {'mobile': '0607080900'}}
240

  
241
    form = NewslettersManageForm(instance=cell, request=fake_saml_request)
242
    args, kwargs = mock_get.call_args
243
    assert kwargs['params'] == {'uuid': 'nameid', 'mobile': '0607080900'}
244

  
245

  
246
def mocked_requests_get(*args, **kwargs):
247
    url = args[0]
248

  
249
    class MockResponse(mock.Mock):
250
        status_code = 200
251

  
252
        def json(self):
253
            return json.loads(self.content)
254

  
255
    if 'newsletters' in url:
256
        return MockResponse(content=json.dumps({'data': NEWSLETTERS}))
257
    else:
258
        return MockResponse(content=json.dumps({'data': SUBSCRIPTIONS}))
259

  
260

  
261
@mock.patch('combo.apps.newsletters.models.requests.get', side_effect=mocked_requests_get)
262
def test_subscriptions_form(mock_get, cell, user):
263
    restrictions = ('sms', 'mail')
264
    cell.transports_restrictions = ','.join(restrictions)
265
    newsletters = [n['id'] for n in cell.get_newsletters()]
266
    assert mock_get.call_args[1]['without_user']
267
    subscriptions = [s['id'] for s in cell.get_subscriptions(user)]
268
    fake_request = mock.Mock(user=mock.Mock(username='username', email=USER_EMAIL), session={})
269
    form = NewslettersManageForm(instance=cell, request=fake_request)
270
    # test if all newsletters are present
271
    for f_id, field in form.fields.items():
272
        assert f_id in newsletters
273
    # test if initial fields are in the restrictions
274
    for s in subscriptions:
275
        assert set(form.fields[s].initial).intersection(set(restrictions))
tests/test_search.py
1364 1364
    assert IndexedCell.objects.count() == 50
1365 1365
    with CaptureQueriesContext(connection) as ctx:
1366 1366
        index_site()
1367
        assert len(ctx.captured_queries) == 224
1367
        assert len(ctx.captured_queries) == 223
1368 1368

  
1369 1369
    SearchCell.objects.create(
1370 1370
        page=page, placeholder='content', order=0, _search_services={'data': ['search1']}
1371
-