Projet

Général

Profil

0001-add-calendar-cell-model-16393.patch

Josué Kouka, 06 juin 2017 11:33

Télécharger (27,8 ko)

Voir les différences:

Subject: [PATCH] add calendar cell model (#16393)

 combo/apps/chrono/README                           |   4 +
 combo/apps/chrono/__init__.py                      |  28 +++++
 combo/apps/chrono/forms.py                         |  68 ++++++++++++
 combo/apps/chrono/migrations/0001_initial.py       |  43 ++++++++
 combo/apps/chrono/migrations/__init__.py           |   0
 combo/apps/chrono/models.py                        |  68 ++++++++++++
 combo/apps/chrono/templates/__init__.py            |   0
 .../chrono/templates/chrono/calendar_cell.html     |  12 +++
 .../templates/chrono/includes/calendar_table.html  |  55 ++++++++++
 combo/apps/chrono/templatetags/__init__.py         |   0
 combo/apps/chrono/templatetags/chrono.py           |  48 +++++++++
 combo/apps/chrono/urls.py                          |  24 +++++
 combo/apps/chrono/utils.py                         | 118 +++++++++++++++++++++
 combo/apps/chrono/views.py                         |  61 +++++++++++
 combo/settings.py                                  |   1 +
 tests/test_calendar.py                             |  97 +++++++++++++++++
 16 files changed, 627 insertions(+)
 create mode 100644 combo/apps/chrono/README
 create mode 100644 combo/apps/chrono/__init__.py
 create mode 100644 combo/apps/chrono/forms.py
 create mode 100644 combo/apps/chrono/migrations/0001_initial.py
 create mode 100644 combo/apps/chrono/migrations/__init__.py
 create mode 100644 combo/apps/chrono/models.py
 create mode 100644 combo/apps/chrono/templates/__init__.py
 create mode 100644 combo/apps/chrono/templates/chrono/calendar_cell.html
 create mode 100644 combo/apps/chrono/templates/chrono/includes/calendar_table.html
 create mode 100644 combo/apps/chrono/templatetags/__init__.py
 create mode 100644 combo/apps/chrono/templatetags/chrono.py
 create mode 100644 combo/apps/chrono/urls.py
 create mode 100644 combo/apps/chrono/utils.py
 create mode 100644 combo/apps/chrono/views.py
 create mode 100644 tests/test_calendar.py
combo/apps/chrono/README
1
Combo calendar cell
2
===================
3

  
4
To be visible, this cell needs a 'chrono' entry in settings.KNOWN_SERVICES
combo/apps/chrono/__init__.py
1
# combo - content management system
2
# Copyright (C) 2017  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

  
19

  
20
class AppConfig(django.apps.AppConfig):
21
    name = 'combo.apps.chrono'
22

  
23
    def get_before_urls(self):
24
        from . import urls
25
        return urls.urlpatterns
26

  
27

  
28
default_app_config = 'combo.apps.chrono.AppConfig'
combo/apps/chrono/forms.py
1
# combo - content management system
2
# Copyright (C) 2017  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 import forms
18
from django.utils.translation import ugettext_lazy as _
19

  
20
from .models import CalendarCell
21
from .utils import (get_agendas, get_formsdef,
22
                    is_datetime_diff_valid)
23

  
24

  
25
class CalendarCellForm(forms.ModelForm):
26

  
27
    class Meta:
28
        model = CalendarCell
29
        fields = (
30
            'title', 'agenda_reference', 'formdef_reference',
31
            'formdef_url_params', 'slot_duration', 'minimal_event_duration',
32
            'business_hour_start', 'business_hour_start')
33

  
34
    def __init__(self, *args, **kwargs):
35
        super(CalendarCellForm, self).__init__(*args, **kwargs)
36
        agenda_references = get_agendas()
37
        formdef_references = get_formsdef()
38
        self.fields['agenda_reference'].widget = forms.Select(choices=agenda_references)
39
        self.fields['formdef_reference'].widget = forms.Select(choices=formdef_references)
40

  
41

  
42
class BookingForm(forms.Form):
43

  
44
    def __init__(self, *args, **kwargs):
45
        self.cell = kwargs.pop('cell')
46
        super(BookingForm, self).__init__(*args, **kwargs)
47

  
48
    def is_valid(self):
49
        # assure that at least slot is selected
50
        if len(self.data.keys()) == 1:
51
            raise ValueError(_('Please select slots'))
52
        # check that selected slots are part of the same day
53
        if len(self.data.keys()) > 2:
54
            raise ValueError(_('Please select slot from the same day'))
55
        data = {}
56
        for key in self.data.keys():
57
            if key == 'csrfmiddlewaretoken':
58
                continue
59
            values = self.data.getlist(key)
60
            data['start'] = values[0]
61
            data['end'] = values[-1]
62

  
63
        # check that booking duration is above minimum limitation
64
        if not is_datetime_diff_valid(data['end'], data['start'], self.cell.minimal_event_duration):
65
            raise ValueError(_(
66
                'Minimal booking duration is %s' % self.cell.minimal_event_duration.strftime('%H:%M')))
67
        self.cleaned_data = data
68
        return True
combo/apps/chrono/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import migrations, models
5
import jsonfield.fields
6
import datetime
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('auth', '0006_require_contenttypes_0002'),
13
        ('data', '0025_jsoncell_varnames_str'),
14
    ]
15

  
16
    operations = [
17
        migrations.CreateModel(
18
            name='CalendarCell',
19
            fields=[
20
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
21
                ('placeholder', models.CharField(max_length=20)),
22
                ('order', models.PositiveIntegerField()),
23
                ('slug', models.SlugField(verbose_name='Slug', blank=True)),
24
                ('extra_css_class', models.CharField(max_length=100, verbose_name='Extra classes for CSS styling', blank=True)),
25
                ('public', models.BooleanField(default=True, verbose_name='Public')),
26
                ('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
27
                ('last_update_timestamp', models.DateTimeField(auto_now=True)),
28
                ('title', models.CharField(max_length=128, verbose_name='Title')),
29
                ('agenda_reference', models.URLField(verbose_name='Events source URL')),
30
                ('formdef_reference', models.URLField(verbose_name='Application form URL')),
31
                ('formdef_url_params', jsonfield.fields.JSONField(default=dict, help_text='{{start}} will take booking start datetime and {{end}} will take booking end datetime', verbose_name='Session vars')),
32
                ('slot_duration', models.TimeField(default=datetime.time(0, 30), verbose_name='Slot duration')),
33
                ('minimal_event_duration', models.TimeField(default=datetime.time(2, 0), verbose_name='Minimal event duration')),
34
                ('business_hour_start', models.TimeField(default=datetime.time(8, 0), verbose_name='Business hour start')),
35
                ('business_hour_end', models.TimeField(default=datetime.time(18, 0), verbose_name='Business hour end')),
36
                ('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
37
                ('page', models.ForeignKey(to='data.Page')),
38
            ],
39
            options={
40
                'verbose_name': 'Calendar',
41
            },
42
        ),
43
    ]
combo/apps/chrono/models.py
1
# combo - content management system
2
# Copyright (C) 2017  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.db import models
18
from django.utils.translation import ugettext_lazy as _
19
from django.utils.dateparse import parse_time
20
from django.utils.text import slugify
21

  
22
from combo.data.models import CellBase
23
from combo.data.library import register_cell_class
24
from .utils import is_chrono_enabled, get_chrono_events
25

  
26
from jsonfield import JSONField
27

  
28

  
29
@register_cell_class
30
class CalendarCell(CellBase):
31

  
32
    title = models.CharField(_('Title'), max_length=128)
33
    agenda_reference = models.URLField(_('Events source URL'))
34
    formdef_reference = models.URLField(_('Application form URL'))
35
    formdef_url_params = JSONField(_('Session vars'),
36
        help_text=_("{{start}} will take booking start datetime and {{end}} will take booking end datetime"))
37
    slot_duration = models.TimeField(
38
        _('Slot duration'), default=parse_time('00:30'))
39
    minimal_event_duration = models.TimeField(
40
        _('Minimal event duration'), default=parse_time('02:00'))
41
    business_hour_start = models.TimeField(
42
        _('Business hour start'), default=parse_time('08:00'))
43
    business_hour_end = models.TimeField(
44
        _('Business hour end'), default=parse_time('18:00'))
45

  
46
    template_name = 'chrono/calendar_cell.html'
47

  
48
    class Meta:
49
        verbose_name = _('Calendar')
50

  
51
    def get_default_form_class(self):
52
        from .forms import CalendarCellForm
53
        return CalendarCellForm
54

  
55
    @classmethod
56
    def is_enabled(cls):
57
        return is_chrono_enabled()
58

  
59
    def save(self, *args, **kwargs):
60
        if not self.slug:
61
            self.slug = slugify(self.title)
62
        return super(CalendarCell, self).save(*args, **kwargs)
63

  
64
    def get_calendar(self):
65
        return get_chrono_events(self.agenda_reference)
66

  
67
    def render(self, context):
68
        return super(CalendarCell, self).render(context)
combo/apps/chrono/templates/chrono/calendar_cell.html
1
{% load i18n  chrono %}
2

  
3
<div id="cal-{{cell.pk}}">
4
    <h3>{{cell.title}}</h3>
5
    <div>
6
        {% block calendar_table %}
7
        {% calendar_table cell=cell %}
8
        {% endblock %}
9
    </div>
10
</div>
11

  
12

  
combo/apps/chrono/templates/chrono/includes/calendar_table.html
1
{% load i18n %}
2

  
3
<div>
4
{% if calendar.has_other_pages %}
5
<p class="paginator">
6
{% if calendar.has_previous %}
7
<a href="?week_{{cell.slug}}={{ calendar.previous_page_number }}">{% trans "previews week" %}</a>
8
{% else %}
9
<span>&lt;&lt;</span>
10
{% endif %}
11
&nbsp;
12
<span class="current">
13
    {{ calendar.number }} / {{ calendar.paginator.num_pages }}
14
</span>
15
&nbsp;
16
{% if calendar.has_next %}
17
<a href="?week_{{cell.slug}}={{ calendar.next_page_number }}">{% trans "next week" %}</a>
18
{% else %}
19
<span>&gt;&gt;</span>
20
{% endif %}
21
</div>
22
{% endif %}
23
</div>
24

  
25
<div id="calendar_{{cell.pk}}" data-calendar="{{cell.pk}}">
26
    <form id="form-cal-{{cell.pk}}" method="POST" action="{% url 'chrono-booking' cell.pk %}">
27
        {% csrf_token %}
28
        <table id="cal-table-{{cell.pk}}">
29
            <thead>
30
                {% if calendar_table_headers %}
31
                <tr>
32
                {% for header in calendar_table_headers %}
33
                    <td>{{header}}</td>
34
                {% endfor %}
35
                </tr>
36
                {% endif %}
37
            </thead>
38
            <tbody>
39
                {% for week, slots in calendar %}
40
                {% for slot, choices in slots.items %}
41
                <tr>
42
                    {% for choice in choices %}
43
                    <td>
44
                        <input id="{{week}}_{{choice.value}}" name="{{choice.label}}" value="{{choice.value}}" type="checkbox" {{choice.disabled}}>
45
                        <label for="{{week}}_{{choice.value}}">{{slot}}</label>
46
                    </td>
47
                    {% endfor %}
48
                </tr>
49
                {% endfor %}
50
                {% endfor %}
51
            </tbody>
52
        </table>
53
        <input type="submit" value="{% trans 'Validate' %}">
54
    </form>
55
</div>
combo/apps/chrono/templatetags/chrono.py
1
# combo - content management system
2
# Copyright (C) 2017  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 import template
18
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
19

  
20
from combo.apps.chrono.utils import get_calendar
21

  
22
register = template.Library()
23

  
24

  
25
@register.inclusion_tag('chrono/includes/calendar_table.html', takes_context=True)
26
def calendar_table(context, cell):
27
    request = context['request']
28
    page = request.GET.get('week_%s' % cell.slug, 1)
29
    # get calendar
30
    calendar = get_calendar(cell.get_calendar())
31

  
32
    paginator = Paginator(tuple(calendar.items()), 1)
33
    try:
34
        calendar = paginator.page(page)
35
    except PageNotAnInteger:
36
        calendar = paginator.page(1)
37
    except (EmptyPage,):
38
        calendar = paginator.page(paginator.num_pages)
39

  
40
    # build calendar tablle headers
41
    if calendar.object_list:
42
        calendar_table_headers = []
43
        for choice in calendar.object_list[0][1].values()[0]:
44
            calendar_table_headers.append(choice['label'])
45
        context['calendar_table_headers'] = calendar_table_headers
46

  
47
    context['calendar'] = calendar
48
    return context
combo/apps/chrono/urls.py
1
# combo - content management system
2
# Copyright (C) 2017  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.conf.urls import url
18

  
19
from .views import EventsView, BookingView
20

  
21
urlpatterns = [
22
    url(r'^api/chrono/events/(?P<pk>[\w,-]+)/', EventsView.as_view(), name='chrono-events'),
23
    url(r'^api/chrono/book/(?P<pk>[\w,-]+)/', BookingView.as_view(), name='chrono-booking'),
24
]
combo/apps/chrono/utils.py
1
# combo - content management system
2
# Copyright (C) 2017 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
import json
17
from collections import OrderedDict
18
import datetime
19

  
20

  
21
from django.conf import settings
22
from django.utils.dateparse import parse_datetime
23
from django.utils import formats
24
from django.template import Context, Template
25

  
26
from combo.utils import requests
27

  
28

  
29
def get_service(service_name):
30
    if hasattr(settings, 'KNOWN_SERVICES') and settings.KNOWN_SERVICES.get(service_name):
31
        return settings.KNOWN_SERVICES[service_name].values()[0]
32

  
33

  
34
def get_chrono_service():
35
    return get_service('chrono')
36

  
37

  
38
def get_wcs_service():
39
    return get_service('wcs')
40

  
41

  
42
def is_chrono_enabled():
43
    return get_chrono_service()
44

  
45

  
46
def get_agendas():
47
    chrono = get_chrono_service()
48
    url = chrono['url'] + 'api/agenda/'
49
    response = requests.get(url, headers={'accept': 'application/json'})
50
    data = response.json()
51
    agendas = [(item['api']['datetimes_url'], item['text']) for item in data['data']]
52
    return agendas
53

  
54

  
55
def get_formsdef():
56
    wcs = get_wcs_service()
57
    url = wcs['url'] + '/api/formdefs/'
58
    response = requests.get(url, headers={'accept': 'application/json'})
59
    data = response.json()
60
    forms = [(item['url'], item['title']) for item in data]
61
    return forms
62

  
63

  
64
def get_chrono_events(chrono_url, **kwargs):
65
    response = requests.get(
66
        chrono_url,
67
        headers={'accept': 'application/json'},
68
        **kwargs)
69
    return response.json().get('data', [])
70

  
71

  
72
def get_day_time_slots(start, end, offset):
73
    dstart = start
74
    # offset in minute
75
    offset = offset.hour * 60 + offset.minute
76
    while dstart <= end:
77
        yield dstart.strftime('%H:%M')
78
        dstart = datetime.datetime.combine(
79
            datetime.date.today(), dstart) + datetime.timedelta(minutes=offset)
80
        dstart = dstart.time()
81

  
82

  
83
def get_calendar(events):
84
    calendar = {}
85
    for event in events:
86
        human_day = formats.date_format(parse_datetime(event['datetime']).date())
87
        event_datetime = parse_datetime(event['datetime'])
88
        event_time = event_datetime.strftime('%H:%M')
89
        # get week ref
90
        week_ref = event_datetime.isocalendar()[1]
91
        if week_ref not in calendar:
92
            calendar[week_ref] = {}
93
        if event_time not in calendar[week_ref]:
94
            calendar[week_ref][event_time] = []
95
        choice = {}
96
        choice['value'] = event_datetime.isoformat()
97
        choice['label'] = human_day
98
        # add disabled if no more slot available for that day
99
        choice['disabled'] = 'disabled' if event.get('disabled', False) else ''
100
        calendar[week_ref][event_time].append(choice)
101
    # sort days
102
    for week in calendar:
103
        calendar[week] = OrderedDict(sorted(calendar[week].items()))
104
    # sort weeks
105
    calendar = OrderedDict(sorted(calendar.items()))
106
    return calendar
107

  
108

  
109
def build_session_vars(cell, data):
110
    tpl = Template(json.dumps(cell.formdef_url_params))
111
    session_vars = json.loads(tpl.render(Context(data)))
112
    return session_vars
113

  
114

  
115
def is_datetime_diff_valid(end, start, valid):
116
    end = parse_datetime(end)
117
    start = parse_datetime(start)
118
    return (end - start) >= datetime.timedelta(hours=valid.hour)
combo/apps/chrono/views.py
1
# combo - content management system
2
# Copyright (C) 2017  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 urllib
18

  
19
from django.http import HttpResponseRedirect, JsonResponse
20
from django.views.decorators.csrf import csrf_exempt
21
from django.views.generic import View
22
from django.views.generic.detail import SingleObjectMixin
23
from django.contrib import messages
24

  
25
from .models import CalendarCell
26
from .utils import get_chrono_events, build_session_vars
27
from .forms import BookingForm
28

  
29

  
30
class EventsView(SingleObjectMixin, View):
31

  
32
    http_method_names = ['get']
33
    model = CalendarCell
34

  
35
    def get(self, request, *args, **kwargs):
36
        cell = self.get_object()
37
        data = get_chrono_events(cell.agenda_reference)
38
        return JsonResponse(data, safe=False)
39

  
40

  
41
class BookingView(SingleObjectMixin, View):
42

  
43
    http_method_names = ['post']
44
    model = CalendarCell
45

  
46
    @csrf_exempt
47
    def dispatch(self, request, *args, **kwargs):
48
        return super(BookingView, self).dispatch(request, *args, **kwargs)
49

  
50
    def post(self, request, *args, **kwargs):
51
        cell = self.get_object()
52
        form = BookingForm(request.POST, cell=cell)
53
        try:
54
            form.is_valid()
55
        except ValueError as exc:
56
            messages.error(request, exc.message)
57
            return HttpResponseRedirect(cell.page.get_online_url())
58
        data = form.cleaned_data
59
        params = build_session_vars(cell, data)
60
        url = '%s?%s' % (cell.formdef_reference, urllib.urlencode(params))
61
        return HttpResponseRedirect(url)
combo/settings.py
75 75
    'combo.apps.notifications',
76 76
    'combo.apps.search',
77 77
    'combo.apps.usersearch',
78
    'combo.apps.chrono',
78 79
    'haystack',
79 80
    'xstatic.pkg.chartnew_js',
80 81
)
tests/test_calendar.py
1
import json
2
import urlparse
3

  
4
import pytest
5
import mock
6

  
7
from django.utils.dateparse import parse_time
8

  
9
from combo.data.models import Page
10
from combo.apps.chrono.models import CalendarCell
11

  
12
pytestmark = pytest.mark.django_db
13

  
14
CHRONO_EVENTS = {
15
    "data": [
16
        {
17
            "disabled": True,
18
            "text": "19 mai 2017 08:00",
19
            "api": {
20
                "fillslot_url": "http://example.net/api/agenda/whatever/fillslot/86/"
21
            },
22
            "id": 86,
23
            "datetime": "2017-05-19 08:00:00"
24
        },
25
        {
26
            "disabled": True,
27
            "text": "19 mai 2017 08:30",
28
            "api": {
29
                "fillslot_url": "http://example.net/api/agenda/whatever/fillslot/87/"
30
            },
31
            "id": 87,
32
            "datetime": "2017-05-19 08:30:00"
33
        },
34
        {
35
            "disabled": True,
36
            "text": "19 mai 2017 09:00",
37
            "api": {
38
                "fillslot_url": "http://example.net/api/agenda/whatever/fillslot/88/"
39
            },
40
            "id": 88,
41
            "datetime": "2017-05-19 09:00:00"
42
        },
43
        {
44
            "disabled": True,
45
            "text": "19 mai 2017 09:30",
46
            "api": {
47
                "fillslot_url": "http://example.net/api/agenda/whatever/fillslot/89/"
48
            },
49
            "id": 89,
50
            "datetime": "2017-05-19 09:30:00"
51
        }
52
    ]
53
}
54

  
55

  
56
class MockedRequestResponse(mock.Mock):
57

  
58
    def json(self):
59
        return json.loads(self.content)
60

  
61

  
62
@pytest.fixture
63
def cell(db):
64
    page = Page.objects.create()
65
    cell = CalendarCell(
66
        page=page, title='Example Of Calendar', order=1,
67
        agenda_reference='http://example.net/api/events/',
68
        formdef_reference='http://example.net/form/whatever/',
69
        formdef_url_params={
70
            "session_var_start_dt": "{{start}}", "session_var_end_dt": "{{end}}",
71
            "session_var_whatever_slug": "whatever"
72
        },
73
        slot_duration=parse_time('00:30'),
74
        business_hour_start=parse_time('08:00'),
75
        business_hour_end=parse_time('18:00'),
76
        minimal_event_duration=parse_time('00:30')
77
    )
78
    cell.save()
79
    return cell
80

  
81

  
82
@mock.patch('combo.apps.chrono.utils.requests.get')
83
def test_get_events(mocked_get, app, cell):
84
    mocked_get.return_value = MockedRequestResponse(content=json.dumps(CHRONO_EVENTS))
85
    resp = app.get('/api/chrono/events/%s/' % cell.pk)
86
    assert len(resp.json) == 4
87

  
88

  
89
def test_redirection_session_vars(app, cell, settings):
90
    params = {'19 Mai 2017': ['2017-05-19T10:30:23', '2017-05-19T12:30:14'],
91
              'csrfmiddleware': 'shgfsufysgfjsvfhsvfgv'}
92
    resp = app.post('/api/chrono/book/%s/' % cell.pk, params=params, status=302)
93
    parsed = urlparse.urlparse(resp.url)
94
    qs = urlparse.parse_qs(parsed.query)
95
    assert qs['session_var_whatever_slug'] == ['whatever']
96
    assert qs['session_var_start_dt'] == [params['19 Mai 2017'][0]]
97
    assert qs['session_var_end_dt'] == [params['19 Mai 2017'][1]]
0
-