general: add support for Python 3 (#23678)

This commit is contained in:
Frédéric Péters 2018-03-25 11:26:47 +02:00
parent 427a4e25d9
commit f4203d05f3
16 changed files with 204 additions and 162 deletions

View File

@ -14,6 +14,8 @@
# 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/>.
from __future__ import print_function
import sys
from chrono.agendas.models import Desk, ICSError
@ -28,4 +30,4 @@ class Command(BaseCommand):
try:
desk.create_timeperiod_exceptions_from_remote_ics(desk.timeperiod_exceptions_remote_url)
except ICSError as e:
print >> sys.stderr, u'unable to create timeperiod exceptions for "%s": %s' % (desk, e)
print(u'unable to create timeperiod exceptions for "%s": %s' % (desk, e), file=sys.stderr)

View File

@ -27,7 +27,7 @@ from django.core.urlresolvers import reverse
from django.db import models, transaction
from django.db.models import Q
from django.utils.dates import WEEKDAYS
from django.utils.encoding import force_text
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.formats import date_format, get_format
from django.utils.text import slugify
from django.utils.timezone import localtime, now, make_aware, make_naive, is_aware
@ -157,6 +157,7 @@ class Agenda(models.Model):
WEEKDAYS_LIST = sorted(WEEKDAYS.items(), key=lambda x: x[0])
@python_2_unicode_compatible
class TimeSlot(object):
def __init__(self, start_datetime, meeting_type, desk):
self.start_datetime = start_datetime
@ -165,10 +166,11 @@ class TimeSlot(object):
self.id = '%s:%s' % (self.meeting_type.id, start_datetime.strftime('%Y-%m-%d-%H%M'))
self.desk = desk
def __unicode__(self):
def __str__(self):
return date_format(self.start_datetime, format='DATETIME_FORMAT')
@python_2_unicode_compatible
class TimePeriod(models.Model):
weekday = models.IntegerField(_('Week day'), choices=WEEKDAYS_LIST)
start_time = models.TimeField(_('Start'))
@ -178,7 +180,7 @@ class TimePeriod(models.Model):
class Meta:
ordering = ['weekday', 'start_time']
def __unicode__(self):
def __str__(self):
return u'%s / %s%s' % (
force_text(WEEKDAYS[self.weekday]),
date_format(self.start_time, 'TIME_FORMAT'),
@ -265,6 +267,7 @@ class MeetingType(models.Model):
}
@python_2_unicode_compatible
class Event(models.Model):
agenda = models.ForeignKey(Agenda)
start_datetime = models.DateTimeField(_('Date/time'))
@ -280,7 +283,7 @@ class Event(models.Model):
class Meta:
ordering = ['agenda', 'start_datetime', 'label']
def __unicode__(self):
def __str__(self):
if self.label:
return self.label
return date_format(localtime(self.start_datetime), format='DATETIME_FORMAT')
@ -374,6 +377,7 @@ class Booking(models.Model):
self.save()
@python_2_unicode_compatible
class Desk(models.Model):
agenda = models.ForeignKey(Agenda)
label = models.CharField(_('Label'), max_length=150)
@ -382,7 +386,7 @@ class Desk(models.Model):
_('URL to fetch time period exceptions from'),
blank=True, max_length=500)
def __unicode__(self):
def __str__(self):
return self.label
class Meta:
@ -468,9 +472,7 @@ class Desk(models.Model):
with transaction.atomic():
update_datetime = now()
for vevent in parsed.contents.get('vevent', []):
summary = vevent.contents['summary'][0].value
if not isinstance(summary, unicode):
summary = unicode(summary, 'utf-8')
summary = force_text(vevent.contents['summary'][0].value)
try:
start_dt = vevent.dtstart.value
if not isinstance(start_dt, datetime.datetime):
@ -562,6 +564,7 @@ class Desk(models.Model):
return openslots.search(aware_date, aware_next_date)
@python_2_unicode_compatible
class TimePeriodException(models.Model):
desk = models.ForeignKey(Desk)
external_id = models.CharField(_('External ID'), max_length=256, blank=True)
@ -574,7 +577,7 @@ class TimePeriodException(models.Model):
class Meta:
ordering = ['start_datetime']
def __unicode__(self):
def __str__(self):
if is_midnight(self.start_datetime) and is_midnight(self.end_datetime):
# if both dates are at midnight don't include the time part
if self.end_datetime == self.start_datetime + datetime.timedelta(days=1):

View File

@ -22,7 +22,9 @@ import operator
from django.core.urlresolvers import reverse
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.utils import six
from django.utils.dateparse import parse_date
from django.utils.encoding import force_text
from django.utils.timezone import now, make_aware, localtime
from rest_framework import permissions, serializers, status
@ -68,7 +70,7 @@ def get_all_slots(agenda, meeting_type):
# remove excluded slot
excluded_slot_by_desk = get_exceptions_by_desk(agenda)
for desk, excluded_interval in excluded_slot_by_desk.iteritems():
for desk, excluded_interval in excluded_slot_by_desk.items():
for interval in excluded_interval:
begin, end = interval
open_slots_by_desk[desk].remove_overlap(localtime(begin), localtime(end))
@ -176,7 +178,7 @@ class Datetimes(APIView):
datetime.datetime.combine(parse_date(request.GET['date_end']), datetime.time(0, 0))))
response = {'data': [{'id': x.id,
'text': unicode(x),
'text': force_text(x),
'datetime': localtime(x.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
'disabled': bool(x.full),
'api': {
@ -240,7 +242,7 @@ class MeetingDatetimes(APIView):
response = {'data': [{'id': x.id,
'datetime': localtime(x.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
'text': unicode(x),
'text': force_text(x),
'disabled': bool(x.full),
'api': {
'fillslot_url': fillslot_url.replace(fake_event_pk, str(x.id)),
@ -339,7 +341,7 @@ class Fillslot(APIView):
except ValueError:
return Response({
'err': 1,
'reason': 'invalid value for count (%r)' % request.GET['count'],
'reason': 'invalid value for count (%s)' % request.GET['count'],
}, status=status.HTTP_400_BAD_REQUEST)
available_desk = None

View File

@ -90,7 +90,7 @@ class Intervals(object):
'Add an interval object'
a = self.__insert_point(interval.begin, interval)
b = self.__insert_point(interval.end, interval)
for i in xrange(a + 1, b):
for i in range(a + 1, b):
self.container[i].append(interval)
def __iter_interval(self, begin, end, modify=False):
@ -181,5 +181,5 @@ class Intervals(object):
# check some invariants
assert self.points[a] == interval.begin
assert self.points[b] == interval.end
for i in xrange(a, b + 1):
for i in range(a, b + 1):
self.container[i].remove(interval)

View File

@ -14,12 +14,16 @@
# 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/>.
from __future__ import unicode_literals
import csv
import datetime
from django import forms
from django.contrib.auth.models import Group
from django.forms import ValidationError
from django.utils import six
from django.utils.encoding import force_text
from django.utils.timezone import make_aware
from django.utils.translation import ugettext_lazy as _
@ -143,9 +147,11 @@ class ImportEventsForm(forms.Form):
def clean_events_csv_file(self):
content = self.cleaned_data['events_csv_file'].read()
if '\0' in content:
if b'\0' in content:
raise ValidationError(_('Invalid file format.'))
if six.PY3:
content = content.decode('utf-8')
try:
dialect = csv.Sniffer().sniff(content)
except csv.Error:
@ -181,7 +187,7 @@ class ImportEventsForm(forms.Form):
except ValueError:
raise ValidationError(_('Invalid file format. (number of places in waiting list, line %d)') % (i+1))
if len(csvline) >= 5:
event.label = ' '.join(csvline[4:])
event.label = ' '.join([force_text(x) for x in csvline[4:]])
events.append(event)
self.events = events

View File

@ -234,8 +234,8 @@ class AgendaDayView(DayArchiveView):
info['opening_hours'] = opening_hours = []
for opening_hour in desk.get_opening_hours(current_date.date()):
opening_hours.append({
'css_top': 100 * (opening_hour.begin - start_date).seconds / 3600,
'css_height': 100 * (opening_hour.end - opening_hour.begin).seconds / 3600,
'css_top': 100 * (opening_hour.begin - start_date).seconds // 3600,
'css_height': 100 * (opening_hour.end - opening_hour.begin).seconds // 3600,
})
infos.append(info)
info['bookings'] = bookings = [] # bookings for this desk
@ -565,13 +565,14 @@ class DeskImportTimePeriodExceptionsView(ManagedAgendaSubobjectMixin, UpdateView
exceptions = None
try:
if form.cleaned_data['ics_file']:
exceptions = form.instance.create_timeperiod_exceptions_from_ics(form.cleaned_data['ics_file'])
ics_file_content = force_text(form.cleaned_data['ics_file'].read())
exceptions = form.instance.create_timeperiod_exceptions_from_ics(ics_file_content)
elif form.cleaned_data['ics_url']:
exceptions = form.instance.create_timeperiod_exceptions_from_remote_ics(form.cleaned_data['ics_url'])
else:
form.instance.remove_timeperiod_exceptions_from_remote_ics()
except ICSError as e:
form.add_error(None, unicode(e))
form.add_error(None, force_text(e))
return self.form_invalid(form)
form.instance.timeperiod_exceptions_remote_url = form.cleaned_data['ics_url']
form.instance.save()

View File

@ -167,4 +167,4 @@ REQUESTS_PROXIES = None
local_settings_file = os.environ.get('CHRONO_SETTINGS_FILE',
os.path.join(os.path.dirname(__file__), 'local_settings.py'))
if os.path.exists(local_settings_file):
execfile(local_settings_file)
exec(open(local_settings_file).read())

View File

@ -14,13 +14,12 @@
# 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 urllib
from django.conf import settings
from django.contrib.auth import logout as auth_logout
from django.contrib.auth import views as auth_views
from django.http import HttpResponseRedirect
from django.shortcuts import resolve_url
from django.utils.six.moves.urllib.parse import quote
if 'mellon' in settings.INSTALLED_APPS:
from mellon.utils import get_idps
@ -33,7 +32,7 @@ def login(request, *args, **kwargs):
if not 'next' in request.GET:
return HttpResponseRedirect(resolve_url('mellon_login'))
return HttpResponseRedirect(resolve_url('mellon_login') + '?next='
+ urllib.quote(request.GET.get('next')))
+ quote(request.GET.get('next')))
return auth_views.login(request, *args, **kwargs)
def logout(request, next_page=None):

19
getlasso3.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/sh
# Get venv site-packages path
DSTDIR=`python3 -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib())'`
# Get not venv site-packages path
# Remove first path (assuming that is the venv path)
NONPATH=`echo $PATH | sed 's/^[^:]*://'`
SRCDIR=`PATH=$NONPATH python3 -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib())'`
# Clean up
rm -f $DSTDIR/lasso.*
rm -f $DSTDIR/_lasso.*
# Link
ln -sv /usr/lib/python3/dist-packages/lasso.py $DSTDIR/
ln -sv /usr/lib/python3/dist-packages/_lasso.cpython-36m-x86_64-linux-gnu.so $DSTDIR/
exit 0

View File

@ -36,7 +36,7 @@ def get_version():
p = subprocess.Popen(['git', 'describe', '--dirty', '--match=v*'], stdout=subprocess.PIPE)
result = p.communicate()[0]
if p.returncode == 0:
version = result.split()[0][1:]
version = str(result.split()[0][1:])
version = version.replace('-', '.')
return version
return '0'

View File

@ -266,8 +266,8 @@ def test_timeexception_creation_from_ics_with_dates():
exceptions_count = desk.create_timeperiod_exceptions_from_ics(ics_sample)
assert exceptions_count == 2
for exception in TimePeriodException.objects.filter(desk=desk):
assert localtime(exception.start_datetime) == make_aware(datetime.datetime(2018, 01, 01, 00, 00))
assert localtime(exception.end_datetime) == make_aware(datetime.datetime(2018, 01, 01, 00, 00))
assert localtime(exception.start_datetime) == make_aware(datetime.datetime(2018, 1, 1, 0, 0))
assert localtime(exception.end_datetime) == make_aware(datetime.datetime(2018, 1, 1, 0, 0))
def test_timeexception_create_from_invalid_ics():
agenda = Agenda(label=u'Test 6 agenda')

View File

@ -1,12 +1,12 @@
import datetime
import pytest
import sys
import urlparse
from django.contrib.auth import get_user_model
from django.db import connection
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from django.utils.six.moves.urllib import parse as urlparse
from django.utils.timezone import now, make_aware, localtime
from chrono.agendas.models import (Agenda, Event, Booking,
@ -686,7 +686,7 @@ def test_multiple_booking_api(app, some_data, user):
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/?count=NaN' % (agenda.slug, event.id), status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == "invalid value for count (u'NaN')"
assert resp.json['reason'] == "invalid value for count (NaN)"
resp = app.post('/api/agenda/%s/fillslot/%s/?count=3' % (agenda.slug, event.id))
Booking.objects.get(id=resp.json['booking_id'])

View File

@ -1,4 +1,5 @@
from cStringIO import StringIO
from __future__ import unicode_literals
import datetime
import json
import os
@ -8,6 +9,8 @@ import tempfile
import pytest
from django.core.management import call_command
from django.utils.encoding import force_bytes
from django.utils.six import StringIO
from django.utils.timezone import make_aware
from chrono.agendas.models import (Agenda, Event, MeetingType, TimePeriod,
@ -53,7 +56,7 @@ def test_import_export(app, some_data, meetings_agenda):
assert Agenda.objects.count() == 0
with tempfile.NamedTemporaryFile() as f:
f.write(output)
f.write(force_bytes(output))
f.flush()
call_command('import_site', f.name)

View File

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib.auth.models import User, Group
from django.utils.timezone import make_aware, now, localtime
import datetime
@ -98,8 +100,8 @@ def test_home_redirect(app):
def test_access(app, admin_user):
app = login(app)
resp = app.get('/manage/', status=200)
assert '<h2>Agendas</h2>' in resp.body
assert "This site doesn't have any agenda yet." in resp.body
assert '<h2>Agendas</h2>' in resp.text
assert "This site doesn't have any agenda yet." in resp.text
def test_logout(app, admin_user):
app = login(app)
@ -112,7 +114,7 @@ def test_menu_json(app, admin_user):
assert resp.json[0]['url'] == 'http://testserver/manage/'
assert resp.json[0]['label'] == 'Agendas'
resp2 = app.get('/manage/menu.json?callback=Q', status=200)
assert resp2.body == 'Q(%s);' % resp.body
assert resp2.text == 'Q(%s);' % resp.text
def test_view_agendas_as_manager(app, manager_user):
agenda = Agenda(label=u'Foo Bar')
@ -124,9 +126,9 @@ def test_view_agendas_as_manager(app, manager_user):
app = login(app, username='manager', password='manager')
resp = app.get('/manage/', status=200)
assert 'Foo Bar' in resp.body
assert 'Bar Foo' not in resp.body
assert 'New' not in resp.body
assert 'Foo Bar' in resp.text
assert 'Bar Foo' not in resp.text
assert 'New' not in resp.text
# check user doesn't have access
app.get('/manage/agendas/%s/' % agenda2.id, status=403)
@ -134,8 +136,8 @@ def test_view_agendas_as_manager(app, manager_user):
# check view gives access to the settings page for "events" agenda
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
# but there's no links to actions
assert not '>New Event<' in resp.body
assert not '>Options<' in resp.body
assert not '>New Event<' in resp.text
assert not '>Options<' in resp.text
app.get('/manage/agendas/%s/add-event' % agenda.id, status=403)
app.get('/manage/agendas/%s/edit' % agenda.id, status=403)
@ -157,8 +159,8 @@ def test_add_agenda(app, admin_user):
agenda = Agenda.objects.get(label='Foo bar')
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
resp = resp.follow()
assert 'Foo bar' in resp.body
assert '<h2>Settings' in resp.body
assert 'Foo bar' in resp.text
assert '<h2>Settings' in resp.text
def test_add_agenda_as_manager(app, manager_user):
# open /manage/ access to manager_user, and check agenda creation is not
@ -182,8 +184,8 @@ def test_options_agenda(app, admin_user):
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
resp = resp.follow()
assert 'Foo baz' in resp.body
assert '<h2>Settings' in resp.body
assert 'Foo baz' in resp.text
assert '<h2>Settings' in resp.text
def test_options_agenda_as_manager(app, manager_user):
agenda = Agenda(label=u'Foo bar')
@ -192,11 +194,12 @@ def test_options_agenda_as_manager(app, manager_user):
app = login(app, username='manager', password='manager')
resp = app.get('/manage/', status=200)
resp = resp.click('Foo bar')
assert not 'Settings' in resp.body
assert not 'Settings' in resp.text
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200) # ok for "events" agendas
resp = app.get('/manage/agendas/%s/edit' % agenda.id, status=403)
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=403)
resp = app.get('/manage/agendas/%s/edit' % agenda.id, status=403)
@ -214,8 +217,8 @@ def test_options_agenda_as_manager(app, manager_user):
resp = resp.form.submit()
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
resp = resp.follow()
assert 'Foo baz' in resp.body
assert '<h2>Settings' in resp.body
assert 'Foo baz' in resp.text
assert '<h2>Settings' in resp.text
def test_delete_agenda(app, admin_user):
agenda = Agenda(label=u'Foo bar')
@ -227,7 +230,7 @@ def test_delete_agenda(app, admin_user):
resp = resp.form.submit()
assert resp.location.endswith('/manage/')
resp = resp.follow()
assert not 'Foo bar' in resp.body
assert not 'Foo bar' in resp.text
def test_delete_busy_agenda(app, admin_user):
agenda = Agenda(label=u'Foo bar')
@ -240,21 +243,21 @@ def test_delete_busy_agenda(app, admin_user):
resp = app.get('/manage/', status=200)
resp = resp.click('Foo bar').follow()
resp = resp.click('Delete')
assert 'Are you sure you want to delete this?' in resp.body
assert 'Are you sure you want to delete this?' in resp.text
booking = Booking(event=event)
booking.save()
resp = app.get('/manage/', status=200)
resp = resp.click('Foo bar').follow()
resp = resp.click('Delete')
assert 'This cannot be removed' in resp.body
assert 'This cannot be removed' in resp.text
booking.cancellation_datetime = now()
booking.save()
resp = app.get('/manage/', status=200)
resp = resp.click('Foo bar').follow()
resp = resp.click('Delete')
assert 'Are you sure you want to delete this?' in resp.body
assert 'Are you sure you want to delete this?' in resp.text
# suddenly the booking is no longer cancelled, but the admin clicks on the
# delete button.
@ -269,8 +272,8 @@ def test_delete_agenda_as_manager(app, manager_user):
app = login(app, username='manager', password='manager')
resp = app.get('/manage/', status=200)
resp = resp.click('Foo bar').follow()
assert 'Options' in resp.body
assert 'Delete' not in resp.body
assert 'Options' in resp.text
assert 'Delete' not in resp.text
resp = app.get('/manage/agendas/%s/delete' % agenda.id, status=403)
def test_add_event(app, admin_user):
@ -279,7 +282,7 @@ def test_add_event(app, admin_user):
agenda.save()
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
assert "This agenda doesn't have any event yet." in resp.body
assert "This agenda doesn't have any event yet." in resp.text
year = now().year + 1
resp = resp.click('New Event')
resp.form['start_datetime'] = '%s-02-15 17:00' % year
@ -287,10 +290,10 @@ def test_add_event(app, admin_user):
resp = resp.form.submit()
resp = resp.follow()
event = Event.objects.get(places=10)
assert not "This agenda doesn't have any event yet." in resp.body
assert '/manage/events/%s/' % event.id in resp.body
assert ('Feb. 15, %s, 5 p.m.' % year) in resp.body
assert '10 places' in resp.body
assert not "This agenda doesn't have any event yet." in resp.text
assert '/manage/events/%s/' % event.id in resp.text
assert ('Feb. 15, %s, 5 p.m.' % year) in resp.text
assert '10 places' in resp.text
resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id)
assert resp_datetimes.json['data'][0]['text'] == 'Feb. 15, %s, 5 p.m.' % year
@ -312,17 +315,17 @@ def test_add_event_as_manager(app, manager_user):
agenda.save()
resp = app.get('/manage/agendas/%s/' % agenda.id).follow()
assert '<h2>Settings' in resp.body
assert '<h2>Settings' in resp.text
resp = resp.click('New Event')
resp.form['start_datetime'] = '2016-02-15 17:00'
resp.form['places'] = 10
resp = resp.form.submit()
resp = resp.follow()
event = Event.objects.get(places=10)
assert not "This agenda doesn't have any event yet." in resp.body
assert '/manage/events/%s/' % event.id in resp.body
assert 'Feb. 15, 2016, 5 p.m.' in resp.body
assert '10 places' in resp.body
assert not "This agenda doesn't have any event yet." in resp.text
assert '/manage/events/%s/' % event.id in resp.text
assert 'Feb. 15, 2016, 5 p.m.' in resp.text
assert '10 places' in resp.text
def test_edit_event(app, admin_user):
agenda = Agenda(label=u'Foo bar')
@ -339,9 +342,9 @@ def test_edit_event(app, admin_user):
resp.form['places'] = 20
resp = resp.form.submit()
resp = resp.follow()
assert '/manage/events/%s/' % event.id in resp.body
assert 'Feb. 16, 2016, 5 p.m.' in resp.body
assert '20 places' in resp.body
assert '/manage/events/%s/' % event.id in resp.text
assert 'Feb. 16, 2016, 5 p.m.' in resp.text
assert '20 places' in resp.text
def test_edit_missing_event(app, admin_user):
app = login(app)
@ -366,9 +369,9 @@ def test_edit_event_as_manager(app, manager_user):
resp.form['places'] = 20
resp = resp.form.submit()
resp = resp.follow()
assert '/manage/events/%s/' % event.id in resp.body
assert 'Feb. 16, 2016, 5 p.m.' in resp.body
assert '20 places' in resp.body
assert '/manage/events/%s/' % event.id in resp.text
assert 'Feb. 16, 2016, 5 p.m.' in resp.text
assert '20 places' in resp.text
def test_booked_places(app, admin_user):
agenda = Agenda(label=u'Foo bar')
@ -380,8 +383,8 @@ def test_booked_places(app, admin_user):
Booking(event=event).save()
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
assert '10 places' in resp.body
assert '2 booked places' in resp.body
assert '10 places' in resp.text
assert '2 booked places' in resp.text
def test_event_classes(app, admin_user):
agenda = Agenda(label=u'Foo bar')
@ -394,20 +397,20 @@ def test_event_classes(app, admin_user):
app = login(app)
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
assert not 'full' in resp.body
assert not 'overbooking' in resp.body
assert not 'full' in resp.text
assert not 'overbooking' in resp.text
for i in range(8):
Booking(event=event).save()
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
assert 'full' in resp.body
assert not 'overbooking' in resp.body
assert 'full' in resp.text
assert not 'overbooking' in resp.text
Booking(event=event).save()
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
assert 'full' in resp.body
assert 'overbooking' in resp.body
assert 'full' in resp.text
assert 'overbooking' in resp.text
def test_delete_event(app, admin_user):
agenda = Agenda(label=u'Foo bar')
@ -435,21 +438,21 @@ def test_delete_busy_event(app, admin_user):
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
resp = resp.click(href=r'/manage/events/%s/$' % event.id)
resp = resp.click('Delete')
assert 'Are you sure you want to delete this?' in resp.body
assert 'Are you sure you want to delete this?' in resp.text
booking = Booking(event=event)
booking.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
resp = resp.click(href=r'/manage/events/%s/$' % event.id)
resp = resp.click('Delete')
assert 'This cannot be removed' in resp.body
assert 'This cannot be removed' in resp.text
booking.cancellation_datetime = now()
booking.save()
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
resp = resp.click(href=r'/manage/events/%s/$' % event.id)
resp = resp.click('Delete')
assert 'Are you sure you want to delete this?' in resp.body
assert 'Are you sure you want to delete this?' in resp.text
# suddenly the booking is no longer cancelled, but the admin clicks on the
# delete button.
@ -482,39 +485,39 @@ def test_import_events(app, admin_user):
resp = resp.click('Import Events')
sample_csv_resp = resp.click('Download sample file')
assert sample_csv_resp.content_type == 'text/csv'
assert sample_csv_resp.body.startswith('date,time,')
assert sample_csv_resp.text.startswith('date,time,')
resp.form['events_csv_file'] = Upload('t.csv', sample_csv_resp.body, 'text/csv')
resp.form['events_csv_file'] = Upload('t.csv', sample_csv_resp.content, 'text/csv')
resp = resp.form.submit(status=302)
assert Event.objects.count() == 0
resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200)
resp.form['events_csv_file'] = Upload('t.csv', 'xx', 'text/csv')
resp.form['events_csv_file'] = Upload('t.csv', b'xx', 'text/csv')
resp = resp.form.submit(status=200)
assert 'Invalid file format.' in resp.body
assert 'Invalid file format.' in resp.text
resp.form['events_csv_file'] = Upload('t.csv', 'xxxx\0\0xxxx', 'text/csv')
resp.form['events_csv_file'] = Upload('t.csv', b'xxxx\0\0xxxx', 'text/csv')
resp = resp.form.submit(status=200)
assert 'Invalid file format.' in resp.body
assert 'Invalid file format.' in resp.text
resp.form['events_csv_file'] = Upload('t.csv', '2016-14-16,18:00', 'text/csv')
resp.form['events_csv_file'] = Upload('t.csv', b'2016-14-16,18:00', 'text/csv')
resp = resp.form.submit(status=200)
assert 'Invalid file format.' in resp.body
assert 'Invalid file format.' in resp.text
resp.form['events_csv_file'] = Upload('t.csv', '2016-14-16,18:00,10', 'text/csv')
resp.form['events_csv_file'] = Upload('t.csv', b'2016-14-16,18:00,10', 'text/csv')
resp = resp.form.submit(status=200)
assert 'Invalid file format. (date/time format' in resp.body
assert 'Invalid file format. (date/time format' in resp.text
resp.form['events_csv_file'] = Upload('t.csv', '2016-09-16,18:00,blah', 'text/csv')
resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,blah', 'text/csv')
resp = resp.form.submit(status=200)
assert 'Invalid file format. (number of places,' in resp.body
assert 'Invalid file format. (number of places,' in resp.text
resp.form['events_csv_file'] = Upload('t.csv', '2016-09-16,18:00,10,blah', 'text/csv')
resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,blah', 'text/csv')
resp = resp.form.submit(status=200)
assert 'Invalid file format. (number of places in waiting list,' in resp.body
assert 'Invalid file format. (number of places in waiting list,' in resp.text
resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200)
resp.form['events_csv_file'] = Upload('t.csv', '2016-09-16,18:00,10', 'text/csv')
resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10', 'text/csv')
resp = resp.form.submit(status=302)
assert Event.objects.count() == 1
assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0))
@ -522,7 +525,7 @@ def test_import_events(app, admin_user):
Event.objects.all().delete()
resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200)
resp.form['events_csv_file'] = Upload('t.csv', '2016-09-16,18:00,10,5', 'text/csv')
resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5', 'text/csv')
resp = resp.form.submit(status=302)
assert Event.objects.count() == 1
assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0))
@ -531,20 +534,21 @@ def test_import_events(app, admin_user):
Event.objects.all().delete()
resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200)
resp.form['events_csv_file'] = Upload('t.csv', '2016-09-16,18:00,10,5,bla bla bla', 'text/csv')
resp.form['events_csv_file'] = Upload('t.csv',
u'2016-09-16,18:00,10,5,éléphant'.encode('utf-8'), 'text/csv')
resp = resp.form.submit(status=302)
assert Event.objects.count() == 1
assert Event.objects.all()[0].start_datetime == make_aware(datetime.datetime(2016, 9, 16, 18, 0))
assert Event.objects.all()[0].places == 10
assert Event.objects.all()[0].waiting_list_places == 5
assert Event.objects.all()[0].label == 'bla bla bla'
assert Event.objects.all()[0].label == u'éléphant'
Event.objects.all().delete()
resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200)
resp.form['events_csv_file'] = Upload('t.csv', 'date,time,etc.\n'
'2016-09-16,18:00,10,5,bla bla bla\n'
'\n'
'2016-09-19,18:00,10', 'text/csv')
resp.form['events_csv_file'] = Upload('t.csv', b'date,time,etc.\n'
b'2016-09-16,18:00,10,5,bla bla bla\n'
b'\n'
b'2016-09-19,18:00,10', 'text/csv')
resp = resp.form.submit(status=302)
assert Event.objects.count() == 2
Event.objects.all().delete()
@ -560,9 +564,9 @@ def test_add_meetings_agenda(app, admin_user):
agenda = Agenda.objects.get(label='Foo bar')
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
resp = resp.follow()
assert 'Foo bar' in resp.body
assert '<h2>Settings' in resp.body
assert 'Meeting Types' in resp.body
assert 'Foo bar' in resp.text
assert '<h2>Settings' in resp.text
assert 'Meeting Types' in resp.text
agenda = Agenda.objects.get(label='Foo bar')
assert agenda.kind == 'meetings'
@ -572,7 +576,7 @@ def test_meetings_agenda_add_meeting_type(app, admin_user):
app = login(app)
resp = app.get('/manage/agendas/%s/' % agenda.id).follow()
resp = resp.click('Settings')
assert "This agenda doesn't have any meeting type yet." in resp.body
assert "This agenda doesn't have any meeting type yet." in resp.text
resp = resp.click('New Meeting Type')
resp.form['label'] = 'Blah'
resp.form['duration'] = '60'
@ -580,7 +584,7 @@ def test_meetings_agenda_add_meeting_type(app, admin_user):
assert MeetingType.objects.get(agenda=agenda).label == 'Blah'
assert MeetingType.objects.get(agenda=agenda).duration == 60
resp = resp.follow()
assert 'Blah' in resp.body
assert 'Blah' in resp.text
# and edit
resp = resp.click('Blah')
@ -633,11 +637,11 @@ def test_meetings_agenda_add_time_period(app, admin_user):
resp = resp.follow()
assert u'Monday / 10 a.m. → 1 p.m.' in resp.text
assert u'Wednesday / 10 a.m. → 5 p.m.' in resp.text
assert resp.body.index('Monday') < resp.body.index('Wednesday')
assert resp.text.index('Monday') < resp.text.index('Wednesday')
# and edit
resp = resp.click(u'Wednesday / 10 a.m. → 5 p.m.')
assert 'Edit Time Period' in resp.body
assert 'Edit Time Period' in resp.text
resp.form['start_time'] = '9:00'
resp = resp.form.submit()
assert TimePeriod.objects.get(desk=desk, weekday=2).start_time.hour == 9
@ -679,7 +683,7 @@ def test_meetings_agenda_add_time_period_as_manager(app, manager_user):
desk = Desk.objects.create(agenda=agenda, label='Desk A')
app = login(app, username='manager', password='manager')
resp = app.get('/manage/agendas/%d/' % agenda.id)
assert not 'Settings' in resp.body
assert not 'Settings' in resp.text
resp = app.get('/manage/agendas/%d/settings' % agenda.id, status=403)
MeetingType(agenda=agenda, label='Blah').save()
app.get('/manage/agendas/%d/desk/%d/add-time-period' % (agenda.id, desk.id), status=403)
@ -695,9 +699,9 @@ def test_meetings_agenda_add_time_period_as_manager(app, manager_user):
resp = app.get('/manage/agendas/%d/' % agenda.id).follow()
resp = resp.click('Settings')
assert 'Add a time period' in resp.content
assert '/manage/timeperiods/%s/edit' % time_period.id in resp.body
assert '/manage/timeperiods/%s/delete' % time_period.id in resp.body
assert 'Add a time period' in resp.text
assert '/manage/timeperiods/%s/edit' % time_period.id in resp.text
assert '/manage/timeperiods/%s/delete' % time_period.id in resp.text
app.get('/manage/agendas/%d/desk/%d/add-time-period' % (agenda.id, desk.id), status=200)
app.get('/manage/timeperiods/%d/edit' % time_period.id, status=200)
@ -797,11 +801,11 @@ def test_meetings_agenda_add_time_period_exception(app, admin_user):
resp.form['end_datetime'] = future.replace(hour=16).strftime(dt_format)
resp = resp.form.submit().follow()
assert TimePeriodException.objects.count() == 2
assert 'Exception 1' in resp.content
assert 'Exception 2' not in resp.content
assert 'Exception 1' in resp.text
assert 'Exception 2' not in resp.text
resp = resp.click(href="/manage/time-period-exceptions/%d/exception-list" % agenda.desk_set.first().pk)
assert 'Exception 1' in resp.content
assert 'Exception 2' in resp.content
assert 'Exception 1' in resp.text
assert 'Exception 2' in resp.text
def test_meetings_agenda_add_time_period_exception_when_booking_exists(app, admin_user):
@ -819,12 +823,12 @@ def test_meetings_agenda_add_time_period_exception_when_booking_exists(app, admi
resp = resp.click('Add a time period exception')
resp = resp.form.submit() # submit empty form
# fields should be marked with errors
assert resp.body.count('This field is required.') == 2
assert resp.text.count('This field is required.') == 2
# try again with data in fields
resp.form['start_datetime'] = '2017-05-22 08:00'
resp.form['end_datetime'] = '2017-05-26 17:30'
resp = resp.form.submit()
assert 'One or several bookings exists within this time slot.' in resp.content
assert 'One or several bookings exists within this time slot.' in resp.text
assert TimePeriodException.objects.count() == 0
# check it's possible to add an exception on another desk
@ -856,7 +860,7 @@ def test_meetings_agenda_add_time_period_exception_when_cancelled_booking_exists
resp.form['start_datetime'] = '2017-05-22 08:00'
resp.form['end_datetime'] = '2017-05-26 17:30'
resp = resp.form.submit()
assert 'One or several bookings exists within this time slot.' not in resp.content
assert 'One or several bookings exists within this time slot.' not in resp.text
assert TimePeriodException.objects.count() == 1
def test_meetings_agenda_add_invalid_time_period_exception(app, admin_user):
@ -872,7 +876,7 @@ def test_meetings_agenda_add_invalid_time_period_exception(app, admin_user):
resp.form['start_datetime'] = '2017-05-26 17:30'
resp.form['end_datetime'] = '2017-05-22 08:00'
resp = resp.form.submit()
assert 'End datetime must be greater than start datetime.' in resp.content
assert 'End datetime must be greater than start datetime.' in resp.text
def test_meetings_agenda_delete_time_period_exception(app, admin_user):
@ -908,24 +912,24 @@ def test_agenda_import_time_period_exception_from_ics(app, admin_user):
login(app)
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
assert 'Import exceptions from .ics' not in resp.content
assert 'Import exceptions from .ics' not in resp.text
TimePeriod.objects.create(weekday=1, desk=desk,
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
assert 'Import exceptions from .ics' in resp.content
assert 'Import exceptions from .ics' in resp.text
resp = resp.click('upload')
assert "You can upload a file or specify an address to a remote calendar." in resp
resp = resp.form.submit(status=302)
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
resp = resp.click('upload')
resp.form['ics_file'] = Upload('exceptions.ics', 'invalid content', 'text/calendar')
resp.form['ics_file'] = Upload('exceptions.ics', b'invalid content', 'text/calendar')
resp = resp.form.submit(status=200)
assert 'File format is invalid' in resp.content
ics_with_no_start_date = """BEGIN:VCALENDAR
assert 'File format is invalid' in resp.text
ics_with_no_start_date = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
@ -935,16 +939,16 @@ END:VEVENT
END:VCALENDAR"""
resp.form['ics_file'] = Upload('exceptions.ics', ics_with_no_start_date, 'text/calendar')
resp = resp.form.submit(status=200)
assert 'Event &quot;New Year&#39;s Eve&quot; has no start date.' in resp.content
ics_with_no_events = """BEGIN:VCALENDAR
assert 'Event &quot;New Year&#39;s Eve&quot; has no start date.' in resp.text
ics_with_no_events = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
END:VCALENDAR"""
resp.form['ics_file'] = Upload('exceptions.ics', ics_with_no_events, 'text/calendar')
resp = resp.form.submit(status=200)
assert "The file doesn&#39;t contain any events." in resp.content
assert "The file doesn&#39;t contain any events." in resp.text
ics_with_exceptions = """BEGIN:VCALENDAR
ics_with_exceptions = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
@ -960,7 +964,7 @@ END:VCALENDAR"""
resp = resp.form.submit(status=302)
assert TimePeriodException.objects.filter(desk=desk).count() == 1
resp = resp.follow()
assert 'An exception has been imported.' in resp.content
assert 'An exception has been imported.' in resp.text
@pytest.mark.freeze_time('2017-12-01')
@ -975,7 +979,7 @@ def test_agenda_import_time_period_exception_from_ics_recurrent(app, admin_user)
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
resp = resp.click('upload')
ics_with_recurrent_exceptions = """BEGIN:VCALENDAR
ics_with_recurrent_exceptions = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
@ -998,7 +1002,7 @@ def test_agenda_import_time_period_exception_with_remote_ics(mocked_get, app, ad
login(app)
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
assert 'Import exceptions from .ics' not in resp.content
assert 'Import exceptions from .ics' not in resp.text
TimePeriod.objects.create(weekday=1, desk=desk,
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
@ -1042,7 +1046,7 @@ def test_agenda_import_time_period_exception_with_remote_ics_no_events(mocked_ge
login(app)
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
assert 'Import exceptions from .ics' not in resp.content
assert 'Import exceptions from .ics' not in resp.text
TimePeriod.objects.create(weekday=1, desk=desk,
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
@ -1087,7 +1091,7 @@ def test_agenda_update_time_period_exception_from_remote_ics(mocked_get, app, ad
login(app)
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
assert 'Import exceptions from .ics' not in resp.content
assert 'Import exceptions from .ics' not in resp.text
TimePeriod.objects.create(weekday=1, desk=desk,
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
@ -1142,7 +1146,7 @@ def test_agenda_import_time_period_exception_from_remote_ics_with_connection_err
login(app)
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
assert 'Import exceptions from .ics' not in resp.content
assert 'Import exceptions from .ics' not in resp.text
TimePeriod.objects.create(weekday=1, desk=desk,
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
@ -1160,7 +1164,7 @@ def test_agenda_import_time_period_exception_from_remote_ics_with_connection_err
raise requests.exceptions.ConnectionError('unreachable')
mocked_get.side_effect = mocked_requests_connection_error
resp = resp.form.submit(status=200)
assert 'Failed to retrieve remote calendar (unreachable).' in resp.content
assert 'Failed to retrieve remote calendar (unreachable).' in resp.text
@mock.patch('chrono.agendas.models.requests.get')
def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_get, app, admin_user):
@ -1170,7 +1174,7 @@ def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_ge
login(app)
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
assert 'Import exceptions from .ics' not in resp.content
assert 'Import exceptions from .ics' not in resp.text
TimePeriod.objects.create(weekday=1, desk=desk,
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
@ -1186,7 +1190,7 @@ def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_ge
raise requests.exceptions.HTTPError(response=mocked_response)
mocked_get.side_effect = mocked_requests_http_forbidden_error
resp = resp.form.submit(status=200)
assert 'Failed to retrieve remote calendar (HTTP error 403).' in resp.content
assert 'Failed to retrieve remote calendar (HTTP error 403).' in resp.text
@mock.patch('chrono.agendas.models.requests.get')
def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mocked_get, app, admin_user):
@ -1196,7 +1200,7 @@ def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mock
login(app)
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
assert 'Import exceptions from .ics' not in resp.content
assert 'Import exceptions from .ics' not in resp.text
TimePeriod.objects.create(weekday=1, desk=desk,
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
@ -1210,7 +1214,7 @@ def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mock
raise requests.exceptions.SSLError('SSL error')
mocked_get.side_effect = mocked_requests_http_ssl_error
resp = resp.form.submit(status=200)
assert 'Failed to retrieve remote calendar (SSL error).' in resp.content
assert 'Failed to retrieve remote calendar (SSL error).' in resp.text
def test_agenda_day_view(app, admin_user, manager_user, api_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
@ -1225,25 +1229,25 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
today = datetime.date.today()
assert resp.location.endswith('%s/%s/%s/' % (today.year, today.month, today.day))
resp = resp.follow()
assert 'No opening hours this day.' in resp.body # no time pediod
assert 'No opening hours this day.' in resp.text # no time pediod
timeperiod = TimePeriod(desk=desk, weekday=today.weekday(),
start_time=datetime.time(10, 0),
end_time=datetime.time(18, 0))
timeperiod.save()
resp = app.get('/manage/agendas/%s/' % agenda.id, status=302).follow()
assert not 'No opening hours this day.' in resp.body
assert not 'div class="booking' in resp.body
assert resp.body.count('<tr') == 9 # 10->18 (not included)
assert not 'No opening hours this day.' in resp.text
assert not 'div class="booking' in resp.text
assert resp.text.count('<tr') == 9 # 10->18 (not included)
timeperiod.end_time = datetime.time(18, 30) # end during an hour
timeperiod.save()
resp = app.get('/manage/agendas/%s/' % agenda.id, status=302).follow()
assert resp.body.count('<tr') == 10 # 10->18 (included)
assert resp.text.count('<tr') == 10 # 10->18 (included)
# check opening hours cells
assert '<div class="opening-hours"' in resp.body
assert 'style="height: 850%; top: 0%;"' in resp.body
assert '<div class="opening-hours"' in resp.text
assert 'style="height: 850%; top: 0%;"' in resp.text
# book some slots
app.reset()
@ -1260,9 +1264,9 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
date = Booking.objects.all()[0].event.start_datetime
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
agenda.id, date.year, date.month, date.day))
assert resp.body.count('div class="booking') == 2
assert 'hourspan-2' in resp.body # table CSS class
assert 'height: 50%; top: 0%;' in resp.body # booking cells
assert resp.text.count('div class="booking') == 2
assert 'hourspan-2' in resp.text # table CSS class
assert 'height: 50%; top: 0%;' in resp.text # booking cells
# create a shorter meeting type, this will change the table CSS class
# (and visually this will give more room for events)
@ -1270,8 +1274,8 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
meetingtype.save()
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
agenda.id, date.year, date.month, date.day))
assert resp.body.count('div class="booking') == 2
assert 'hourspan-4' in resp.body # table CSS class
assert resp.text.count('div class="booking') == 2
assert 'hourspan-4' in resp.text # table CSS class
# cancel a booking
app.reset()
@ -1284,7 +1288,7 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
login(app)
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
agenda.id, date.year, date.month, date.day))
assert resp.body.count('div class="booking') == 1
assert resp.text.count('div class="booking') == 1
# wrong type
agenda2 = Agenda(label=u'Foo bar')

View File

@ -4,6 +4,7 @@ import datetime
import pytest
from django.test import override_settings
from django.utils.encoding import force_text
from django.utils.timezone import localtime, make_aware
from chrono.agendas.models import Agenda, TimePeriod, TimePeriodException, MeetingType, Desk
@ -104,25 +105,25 @@ def test_timeperiod_time_slots():
@override_settings(LANGUAGE_CODE='fr-fr')
def test_time_period_exception_as_string():
# single day
assert unicode(TimePeriodException(
assert force_text(TimePeriodException(
start_datetime=make_aware(datetime.datetime(2018, 1, 18)),
end_datetime=make_aware(datetime.datetime(2018, 1, 19)))
) == u'18 jan. 2018'
# multiple full days
assert unicode(TimePeriodException(
assert force_text(TimePeriodException(
start_datetime=make_aware(datetime.datetime(2018, 1, 18)),
end_datetime=make_aware(datetime.datetime(2018, 1, 20)))
) == u'18 jan. 2018 → 20 jan. 2018'
# a few hours in a day
assert unicode(TimePeriodException(
assert force_text(TimePeriodException(
start_datetime=make_aware(datetime.datetime(2018, 1, 18, 10, 0)),
end_datetime=make_aware(datetime.datetime(2018, 1, 18, 12, 0)))
) == u'18 jan. 2018 10:00 → 12:00'
# multiple days and different times
assert unicode(TimePeriodException(
assert force_text(TimePeriodException(
start_datetime=make_aware(datetime.datetime(2018, 1, 18, 10, 0)),
end_datetime=make_aware(datetime.datetime(2018, 1, 20, 12, 0)))
) == u'18 jan. 2018 10:00 → 20 jan. 2018 12:00'

View File

@ -1,5 +1,5 @@
[tox]
envlist = coverage-django18-pylint,coverage-django111
envlist = py2-coverage-django18-pylint,{py2,py3}-coverage-django111
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/chrono/
[testenv]
@ -22,9 +22,11 @@ deps =
pylint<1.8
pylint-django<0.9
django-webtest<1.9.3
django-mellon
py2: django-mellon
py3: django-mellon>=1.2.35
pytest-freezegun
commands =
./getlasso.sh
py2: ./getlasso.sh
py3: ./getlasso3.sh
py.test {env:COVERAGE:} {posargs:tests/}
pylint: ./pylint.sh chrono/