general: reformat using black (#37464)
black--target-version py35 --skip-string-normalization --line-length 110 .
This commit is contained in:
parent
30cb3f672f
commit
30bbc8c90f
|
@ -6,20 +6,20 @@ from django.db import models, migrations
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Agenda',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('label', models.CharField(max_length=50, verbose_name='Label')),
|
||||
('slug', models.SlugField(verbose_name='Identifier')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['label'],
|
||||
},
|
||||
options={'ordering': ['label'],},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -14,14 +14,15 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Event',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('start_datetime', models.DateTimeField(verbose_name='Date/time')),
|
||||
('places', models.PositiveIntegerField(verbose_name='Places')),
|
||||
('agenda', models.ForeignKey(to='agendas.Agenda', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['agenda', 'start_datetime'],
|
||||
},
|
||||
options={'ordering': ['agenda', 'start_datetime'],},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -15,12 +15,14 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Booking',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('extra_data', jsonfield.fields.JSONField(null=True)),
|
||||
('event', models.ForeignKey(to='agendas.Event', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -14,6 +14,12 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='label',
|
||||
field=models.CharField(help_text='Optional label to identify this date.', max_length=50, null=True, blank=True, verbose_name='Label'),
|
||||
field=models.CharField(
|
||||
help_text='Optional label to identify this date.',
|
||||
max_length=50,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name='Label',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -16,19 +16,15 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='creation_datetime',
|
||||
field=models.DateTimeField(default=datetime.datetime(2016, 7, 7, 13, 57, 47, 975893, tzinfo=utc), auto_now_add=True),
|
||||
field=models.DateTimeField(
|
||||
default=datetime.datetime(2016, 7, 7, 13, 57, 47, 975893, tzinfo=utc), auto_now_add=True
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='in_waiting_list',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='full',
|
||||
field=models.BooleanField(default=False),
|
||||
model_name='booking', name='in_waiting_list', field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(model_name='event', name='full', field=models.BooleanField(default=False),),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='waiting_list_places',
|
||||
|
|
|
@ -12,8 +12,6 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='agenda',
|
||||
name='label',
|
||||
field=models.CharField(max_length=100, verbose_name='Label'),
|
||||
model_name='agenda', name='label', field=models.CharField(max_length=100, verbose_name='Label'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -14,30 +14,51 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='MeetingType',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('label', models.CharField(max_length=100, verbose_name='Label')),
|
||||
('duration', models.IntegerField(default=30, verbose_name='Duration (in minutes)')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['label'],
|
||||
},
|
||||
options={'ordering': ['label'],},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TimePeriod',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('weekday', models.IntegerField(verbose_name='Week day', choices=[(0, 'Monday'), (1, 'Tuesday'), (2, 'Wednesday'), (3, 'Thursday'), (4, 'Friday'), (5, 'Saturday'), (6, 'Sunday')])),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
(
|
||||
'weekday',
|
||||
models.IntegerField(
|
||||
verbose_name='Week day',
|
||||
choices=[
|
||||
(0, 'Monday'),
|
||||
(1, 'Tuesday'),
|
||||
(2, 'Wednesday'),
|
||||
(3, 'Thursday'),
|
||||
(4, 'Friday'),
|
||||
(5, 'Saturday'),
|
||||
(6, 'Sunday'),
|
||||
],
|
||||
),
|
||||
),
|
||||
('start_time', models.TimeField(verbose_name='Start')),
|
||||
('end_time', models.TimeField(verbose_name='End')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['weekday', 'start_time'],
|
||||
},
|
||||
options={'ordering': ['weekday', 'start_time'],},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='agenda',
|
||||
name='kind',
|
||||
field=models.CharField(default=b'events', max_length=20, verbose_name='Kind', choices=[(b'events', 'Events'), (b'meetings', 'Meetings')]),
|
||||
field=models.CharField(
|
||||
default=b'events',
|
||||
max_length=20,
|
||||
verbose_name='Kind',
|
||||
choices=[(b'events', 'Events'), (b'meetings', 'Meetings')],
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='timeperiod',
|
||||
|
|
|
@ -13,17 +13,32 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='event',
|
||||
options={'ordering': ['agenda', 'start_datetime', 'label']},
|
||||
name='event', options={'ordering': ['agenda', 'start_datetime', 'label']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='agenda',
|
||||
name='edit_role',
|
||||
field=models.ForeignKey(related_name='+', default=None, verbose_name='Edit Role', to='auth.Group', blank=True, null=True, on_delete=models.CASCADE),
|
||||
field=models.ForeignKey(
|
||||
related_name='+',
|
||||
default=None,
|
||||
verbose_name='Edit Role',
|
||||
to='auth.Group',
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='agenda',
|
||||
name='view_role',
|
||||
field=models.ForeignKey(related_name='+', default=None, verbose_name='View Role', to='auth.Group', blank=True, null=True, on_delete=models.CASCADE),
|
||||
field=models.ForeignKey(
|
||||
related_name='+',
|
||||
default=None,
|
||||
verbose_name='View Role',
|
||||
to='auth.Group',
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -12,8 +12,6 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='meetingtype',
|
||||
name='slug',
|
||||
field=models.SlugField(verbose_name='Identifier'),
|
||||
model_name='meetingtype', name='slug', field=models.SlugField(verbose_name='Identifier'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -14,6 +14,11 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='primary_booking',
|
||||
field=models.ForeignKey(related_name='secondary_booking_set', to='agendas.Booking', null=True, on_delete=models.CASCADE),
|
||||
field=models.ForeignKey(
|
||||
related_name='secondary_booking_set',
|
||||
to='agendas.Booking',
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -12,14 +12,18 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='agenda',
|
||||
name='label',
|
||||
field=models.CharField(max_length=150, verbose_name='Label'),
|
||||
model_name='agenda', name='label', field=models.CharField(max_length=150, verbose_name='Label'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='label',
|
||||
field=models.CharField(help_text='Optional label to identify this date.', max_length=150, null=True, verbose_name='Label', blank=True),
|
||||
field=models.CharField(
|
||||
help_text='Optional label to identify this date.',
|
||||
max_length=150,
|
||||
null=True,
|
||||
verbose_name='Label',
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='meetingtype',
|
||||
|
|
|
@ -14,13 +14,14 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='Desk',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
('label', models.CharField(max_length=150, verbose_name='Label')),
|
||||
('slug', models.SlugField(max_length=150, verbose_name='Identifier')),
|
||||
('agenda', models.ForeignKey(to='agendas.Agenda', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['label'],
|
||||
},
|
||||
options={'ordering': ['label'],},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -9,7 +9,8 @@ def set_timeperiod_desk(apps, schema_editor):
|
|||
Desk = apps.get_model('agendas', 'Desk')
|
||||
for time_period in TimePeriod.objects.all():
|
||||
desk, created = Desk.objects.get_or_create(
|
||||
label='Guichet 1', slug='guichet-1', agenda=time_period.agenda)
|
||||
label='Guichet 1', slug='guichet-1', agenda=time_period.agenda
|
||||
)
|
||||
time_period.desk = desk
|
||||
time_period.save()
|
||||
|
||||
|
@ -36,8 +37,5 @@ class Migration(migrations.Migration):
|
|||
name='desk',
|
||||
field=models.ForeignKey(to='agendas.Desk', on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='timeperiod',
|
||||
name='agenda',
|
||||
),
|
||||
migrations.RemoveField(model_name='timeperiod', name='agenda',),
|
||||
]
|
||||
|
|
|
@ -11,8 +11,7 @@ def set_event_desk(apps, schema_editor):
|
|||
if not event.agenda.kind == 'meetings':
|
||||
continue
|
||||
|
||||
desk, created = Desk.objects.get_or_create(
|
||||
label='Guichet 1', slug='guichet-1', agenda=event.agenda)
|
||||
desk, created = Desk.objects.get_or_create(label='Guichet 1', slug='guichet-1', agenda=event.agenda)
|
||||
event.desk = desk
|
||||
event.save()
|
||||
|
||||
|
@ -33,5 +32,5 @@ class Migration(migrations.Migration):
|
|||
name='desk',
|
||||
field=models.ForeignKey(to='agendas.Desk', null=True, on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.RunPython(set_event_desk, unset_event_desk)
|
||||
migrations.RunPython(set_event_desk, unset_event_desk),
|
||||
]
|
||||
|
|
|
@ -14,14 +14,18 @@ class Migration(migrations.Migration):
|
|||
migrations.CreateModel(
|
||||
name='TimePeriodException',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('label', models.CharField(max_length=150, null=True, verbose_name='Optional Label', blank=True)),
|
||||
(
|
||||
'id',
|
||||
models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
|
||||
),
|
||||
(
|
||||
'label',
|
||||
models.CharField(max_length=150, null=True, verbose_name='Optional Label', blank=True),
|
||||
),
|
||||
('start_datetime', models.DateTimeField(verbose_name='Exception start time')),
|
||||
('end_datetime', models.DateTimeField(verbose_name='Exception end time')),
|
||||
('desk', models.ForeignKey(to='agendas.Desk', on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['start_datetime'],
|
||||
},
|
||||
options={'ordering': ['start_datetime'],},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -26,7 +26,9 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='timeperiodexception',
|
||||
name='update_datetime',
|
||||
field=models.DateTimeField(default=datetime.datetime(2017, 11, 2, 10, 21, 1, 826837, tzinfo=utc), auto_now=True),
|
||||
field=models.DateTimeField(
|
||||
default=datetime.datetime(2017, 11, 2, 10, 21, 1, 826837, tzinfo=utc), auto_now=True
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -11,19 +11,11 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(model_name='booking', name='backoffice_url', field=models.URLField(blank=True),),
|
||||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='backoffice_url',
|
||||
field=models.URLField(blank=True),
|
||||
model_name='booking', name='label', field=models.CharField(max_length=250, blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='label',
|
||||
field=models.CharField(max_length=250, blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='user_name',
|
||||
field=models.CharField(max_length=250, blank=True),
|
||||
model_name='booking', name='user_name', field=models.CharField(max_length=250, blank=True),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -17,9 +17,7 @@ class Migration(migrations.Migration):
|
|||
field=models.SlugField(max_length=160, verbose_name='Identifier'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='desk',
|
||||
name='slug',
|
||||
field=models.SlugField(max_length=160, verbose_name='Identifier'),
|
||||
model_name='desk', name='slug', field=models.SlugField(max_length=160, verbose_name='Identifier'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='meetingtype',
|
||||
|
|
|
@ -14,6 +14,8 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='desk',
|
||||
name='timeperiod_exceptions_remote_url',
|
||||
field=models.URLField(max_length=500, verbose_name='URL to fetch time period exceptions from', blank=True),
|
||||
field=models.URLField(
|
||||
max_length=500, verbose_name='URL to fetch time period exceptions from', blank=True
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -12,10 +12,7 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='meetingtype',
|
||||
options={'ordering': ['duration', 'label']},
|
||||
),
|
||||
migrations.AlterModelOptions(name='meetingtype', options={'ordering': ['duration', 'label']},),
|
||||
migrations.AddField(
|
||||
model_name='timeperiodexception',
|
||||
name='recurrence_id',
|
||||
|
|
|
@ -16,11 +16,27 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='agenda',
|
||||
name='edit_role',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='auth.Group', verbose_name='Edit Role'),
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='+',
|
||||
to='auth.Group',
|
||||
verbose_name='Edit Role',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='agenda',
|
||||
name='view_role',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='auth.Group', verbose_name='View Role'),
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='+',
|
||||
to='auth.Group',
|
||||
verbose_name='View Role',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -15,6 +15,8 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, help_text='Optional event description.', null=True, verbose_name='Description'),
|
||||
field=models.TextField(
|
||||
blank=True, help_text='Optional event description.', null=True, verbose_name='Description'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -15,7 +15,9 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='slug',
|
||||
field=models.SlugField(default=None, null=True, blank=True, max_length=160, verbose_name='Identifier'),
|
||||
field=models.SlugField(
|
||||
default=None, null=True, blank=True, max_length=160, verbose_name='Identifier'
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
|
|
|
@ -15,6 +15,8 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='slug',
|
||||
field=models.SlugField(default=None, null=True, blank=True, max_length=160, verbose_name='Identifier'),
|
||||
field=models.SlugField(
|
||||
default=None, null=True, blank=True, max_length=160, verbose_name='Identifier'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -39,7 +39,11 @@ def set_slug_on_desks(apps, schema_editor):
|
|||
def set_slug_on_meetingtypes(apps, schema_editor):
|
||||
MeetingType = apps.get_model('agendas', 'MeetingType')
|
||||
for meetingtype in MeetingType.objects.all().order_by('-pk'):
|
||||
if not MeetingType.objects.filter(slug=meetingtype.slug, agenda=meetingtype.agenda).exclude(pk=meetingtype.pk).exists():
|
||||
if (
|
||||
not MeetingType.objects.filter(slug=meetingtype.slug, agenda=meetingtype.agenda)
|
||||
.exclude(pk=meetingtype.pk)
|
||||
.exists()
|
||||
):
|
||||
continue
|
||||
meetingtype.slug = generate_slug(meetingtype, agenda=meetingtype.agenda)
|
||||
meetingtype.save(update_fields=['slug'])
|
||||
|
|
|
@ -17,12 +17,6 @@ class Migration(migrations.Migration):
|
|||
name='slug',
|
||||
field=models.SlugField(max_length=160, unique=True, verbose_name='Identifier'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='desk',
|
||||
unique_together=set([('agenda', 'slug')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='meetingtype',
|
||||
unique_together=set([('agenda', 'slug')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(name='desk', unique_together=set([('agenda', 'slug')]),),
|
||||
migrations.AlterUniqueTogether(name='meetingtype', unique_together=set([('agenda', 'slug')]),),
|
||||
]
|
||||
|
|
|
@ -15,10 +15,9 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='slug',
|
||||
field=models.SlugField(default=None, null=True, blank=True, max_length=160, verbose_name='Identifier')
|
||||
field=models.SlugField(
|
||||
default=None, null=True, blank=True, max_length=160, verbose_name='Identifier'
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='event',
|
||||
unique_together=set([('agenda', 'slug')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(name='event', unique_together=set([('agenda', 'slug')]),),
|
||||
]
|
||||
|
|
|
@ -71,16 +71,28 @@ class Agenda(models.Model):
|
|||
label = models.CharField(_('Label'), max_length=150)
|
||||
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
|
||||
kind = models.CharField(_('Kind'), max_length=20, choices=AGENDA_KINDS, default='events')
|
||||
minimal_booking_delay = models.PositiveIntegerField(
|
||||
_('Minimal booking delay (in days)'), default=1)
|
||||
minimal_booking_delay = models.PositiveIntegerField(_('Minimal booking delay (in days)'), default=1)
|
||||
maximal_booking_delay = models.PositiveIntegerField(
|
||||
_('Maximal booking delay (in days)'), default=56) # eight weeks
|
||||
edit_role = models.ForeignKey(Group, blank=True, null=True, default=None,
|
||||
related_name='+', verbose_name=_('Edit Role'),
|
||||
on_delete=models.SET_NULL)
|
||||
view_role = models.ForeignKey(Group, blank=True, null=True, default=None,
|
||||
related_name='+', verbose_name=_('View Role'),
|
||||
on_delete=models.SET_NULL)
|
||||
_('Maximal booking delay (in days)'), default=56
|
||||
) # eight weeks
|
||||
edit_role = models.ForeignKey(
|
||||
Group,
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
related_name='+',
|
||||
verbose_name=_('Edit Role'),
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
view_role = models.ForeignKey(
|
||||
Group,
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
related_name='+',
|
||||
verbose_name=_('View Role'),
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['label']
|
||||
|
@ -124,7 +136,7 @@ class Agenda(models.Model):
|
|||
'permissions': {
|
||||
'view': self.view_role.name if self.view_role else None,
|
||||
'edit': self.edit_role.name if self.edit_role else None,
|
||||
}
|
||||
},
|
||||
}
|
||||
if self.kind == 'events':
|
||||
agenda['events'] = [x.export_json() for x in self.event_set.all()]
|
||||
|
@ -170,6 +182,7 @@ class Agenda(models.Model):
|
|||
Desk.import_json(desk).save()
|
||||
return created
|
||||
|
||||
|
||||
WEEKDAYS_LIST = sorted(WEEKDAYS.items(), key=lambda x: x[0])
|
||||
|
||||
|
||||
|
@ -200,7 +213,8 @@ class TimePeriod(models.Model):
|
|||
return u'%s / %s → %s' % (
|
||||
force_text(WEEKDAYS[self.weekday]),
|
||||
date_format(self.start_time, 'TIME_FORMAT'),
|
||||
date_format(self.end_time, 'TIME_FORMAT'))
|
||||
date_format(self.end_time, 'TIME_FORMAT'),
|
||||
)
|
||||
|
||||
@property
|
||||
def weekday_str(self):
|
||||
|
@ -223,19 +237,21 @@ class TimePeriod(models.Model):
|
|||
min_datetime = make_naive(min_datetime)
|
||||
max_datetime = make_naive(max_datetime)
|
||||
|
||||
real_min_datetime = min_datetime + datetime.timedelta(
|
||||
days=self.weekday - min_datetime.weekday())
|
||||
real_min_datetime = min_datetime + datetime.timedelta(days=self.weekday - min_datetime.weekday())
|
||||
if real_min_datetime < min_datetime:
|
||||
real_min_datetime += datetime.timedelta(days=7)
|
||||
|
||||
event_datetime = real_min_datetime.replace(hour=self.start_time.hour,
|
||||
minute=self.start_time.minute, second=0, microsecond=0)
|
||||
event_datetime = real_min_datetime.replace(
|
||||
hour=self.start_time.hour, minute=self.start_time.minute, second=0, microsecond=0
|
||||
)
|
||||
while event_datetime < max_datetime:
|
||||
end_time = event_datetime + meeting_duration
|
||||
next_time = event_datetime + duration
|
||||
if end_time.time() > self.end_time or event_datetime.date() != next_time.date():
|
||||
# back to morning
|
||||
event_datetime = event_datetime.replace(hour=self.start_time.hour, minute=self.start_time.minute)
|
||||
event_datetime = event_datetime.replace(
|
||||
hour=self.start_time.hour, minute=self.start_time.minute
|
||||
)
|
||||
# but next week
|
||||
event_datetime += datetime.timedelta(days=7)
|
||||
next_time = event_datetime + duration
|
||||
|
@ -243,7 +259,9 @@ class TimePeriod(models.Model):
|
|||
if event_datetime > max_datetime:
|
||||
break
|
||||
|
||||
yield TimeSlot(start_datetime=make_aware(event_datetime), meeting_type=meeting_type, desk=self.desk)
|
||||
yield TimeSlot(
|
||||
start_datetime=make_aware(event_datetime), meeting_type=meeting_type, desk=self.desk
|
||||
)
|
||||
event_datetime = next_time
|
||||
|
||||
|
||||
|
@ -265,7 +283,8 @@ class MeetingType(models.Model):
|
|||
@classmethod
|
||||
def import_json(cls, data):
|
||||
meeting_type, created = cls.objects.get_or_create(
|
||||
slug=data['slug'], agenda=data['agenda'], defaults=data)
|
||||
slug=data['slug'], agenda=data['agenda'], defaults=data
|
||||
)
|
||||
if not created:
|
||||
for k, v in data.items():
|
||||
setattr(meeting_type, k, v)
|
||||
|
@ -284,13 +303,18 @@ class Event(models.Model):
|
|||
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE)
|
||||
start_datetime = models.DateTimeField(_('Date/time'))
|
||||
places = models.PositiveIntegerField(_('Places'))
|
||||
waiting_list_places = models.PositiveIntegerField(
|
||||
_('Places in waiting list'), default=0)
|
||||
label = models.CharField(_('Label'), max_length=150, null=True, blank=True,
|
||||
help_text=_('Optional label to identify this date.'))
|
||||
waiting_list_places = models.PositiveIntegerField(_('Places in waiting list'), default=0)
|
||||
label = models.CharField(
|
||||
_('Label'),
|
||||
max_length=150,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text=_('Optional label to identify this date.'),
|
||||
)
|
||||
slug = models.SlugField(_('Identifier'), max_length=160, null=True, blank=True, default=None)
|
||||
description = models.TextField(_('Description'), null=True, blank=True,
|
||||
help_text=_('Optional event description.'))
|
||||
description = models.TextField(
|
||||
_('Description'), null=True, blank=True, help_text=_('Optional event description.')
|
||||
)
|
||||
full = models.BooleanField(default=False)
|
||||
meeting_type = models.ForeignKey(MeetingType, null=True, on_delete=models.CASCADE)
|
||||
desk = models.ForeignKey('Desk', null=True, on_delete=models.CASCADE)
|
||||
|
@ -310,14 +334,24 @@ class Event(models.Model):
|
|||
|
||||
def check_full(self):
|
||||
self.full = bool(
|
||||
(self.booked_places >= self.places and self.waiting_list_places == 0) or
|
||||
(self.waiting_list_places and self.waiting_list >= self.waiting_list_places))
|
||||
(self.booked_places >= self.places and self.waiting_list_places == 0)
|
||||
or (self.waiting_list_places and self.waiting_list >= self.waiting_list_places)
|
||||
)
|
||||
|
||||
def in_bookable_period(self):
|
||||
if localtime(now()).date() > localtime(self.start_datetime - datetime.timedelta(days=self.agenda.minimal_booking_delay)).date():
|
||||
if (
|
||||
localtime(now()).date()
|
||||
> localtime(
|
||||
self.start_datetime - datetime.timedelta(days=self.agenda.minimal_booking_delay)
|
||||
).date()
|
||||
):
|
||||
return False
|
||||
if self.agenda.maximal_booking_delay and (
|
||||
localtime(now()).date() <= localtime(self.start_datetime - datetime.timedelta(days=self.agenda.maximal_booking_delay)).date()):
|
||||
localtime(now()).date()
|
||||
<= localtime(
|
||||
self.start_datetime - datetime.timedelta(days=self.agenda.maximal_booking_delay)
|
||||
).date()
|
||||
):
|
||||
return False
|
||||
if self.start_datetime < now():
|
||||
# past the event date, we may want in the future to allow for some
|
||||
|
@ -327,13 +361,11 @@ class Event(models.Model):
|
|||
|
||||
@property
|
||||
def booked_places(self):
|
||||
return self.booking_set.filter(cancellation_datetime__isnull=True,
|
||||
in_waiting_list=False).count()
|
||||
return self.booking_set.filter(cancellation_datetime__isnull=True, in_waiting_list=False).count()
|
||||
|
||||
@property
|
||||
def waiting_list(self):
|
||||
return self.booking_set.filter(cancellation_datetime__isnull=True,
|
||||
in_waiting_list=True).count()
|
||||
return self.booking_set.filter(cancellation_datetime__isnull=True, in_waiting_list=True).count()
|
||||
|
||||
@property
|
||||
def end_datetime(self):
|
||||
|
@ -344,8 +376,9 @@ class Event(models.Model):
|
|||
|
||||
@classmethod
|
||||
def import_json(cls, data):
|
||||
data['start_datetime'] = make_aware(datetime.datetime.strptime(
|
||||
data['start_datetime'], '%Y-%m-%d %H:%M:%S'))
|
||||
data['start_datetime'] = make_aware(
|
||||
datetime.datetime.strptime(data['start_datetime'], '%Y-%m-%d %H:%M:%S')
|
||||
)
|
||||
if data.get('slug'):
|
||||
event, created = cls.objects.get_or_create(slug=data['slug'], defaults=data)
|
||||
if not created:
|
||||
|
@ -372,11 +405,14 @@ class Booking(models.Model):
|
|||
in_waiting_list = models.BooleanField(default=False)
|
||||
creation_datetime = models.DateTimeField(auto_now_add=True)
|
||||
# primary booking is used to group multiple bookings together
|
||||
primary_booking = models.ForeignKey('self', null=True,
|
||||
on_delete=models.CASCADE, related_name='secondary_booking_set')
|
||||
primary_booking = models.ForeignKey(
|
||||
'self', null=True, on_delete=models.CASCADE, related_name='secondary_booking_set'
|
||||
)
|
||||
|
||||
label = models.CharField(max_length=250, blank=True)
|
||||
user_display_label = models.CharField(verbose_name=_('Label displayed to user'), max_length=250, blank=True)
|
||||
user_display_label = models.CharField(
|
||||
verbose_name=_('Label displayed to user'), max_length=250, blank=True
|
||||
)
|
||||
user_name = models.CharField(max_length=250, blank=True)
|
||||
backoffice_url = models.URLField(blank=True)
|
||||
|
||||
|
@ -405,14 +441,20 @@ class Booking(models.Model):
|
|||
ics = vobject.iCalendar()
|
||||
ics.add('prodid').value = '-//Entr\'ouvert//NON SGML Publik'
|
||||
vevent = vobject.newFromBehavior('vevent')
|
||||
vevent.add('uid').value = '%s-%s-%s' % (self.event.start_datetime.isoformat(), self.event.agenda.pk, self.pk)
|
||||
vevent.add('uid').value = '%s-%s-%s' % (
|
||||
self.event.start_datetime.isoformat(),
|
||||
self.event.agenda.pk,
|
||||
self.pk,
|
||||
)
|
||||
|
||||
vevent.add('summary').value = self.user_display_label or self.label
|
||||
vevent.add('dtstart').value = self.event.start_datetime
|
||||
if self.user_name:
|
||||
vevent.add('attendee').value = self.user_name
|
||||
if self.event.meeting_type:
|
||||
vevent.add('dtend').value = self.event.start_datetime + datetime.timedelta(minutes=self.event.meeting_type.duration)
|
||||
vevent.add('dtend').value = self.event.start_datetime + datetime.timedelta(
|
||||
minutes=self.event.meeting_type.duration
|
||||
)
|
||||
|
||||
for field in ('description', 'location', 'comment', 'url'):
|
||||
field_value = request and request.GET.get(field) or self.extra_data.get(field)
|
||||
|
@ -422,15 +464,14 @@ class Booking(models.Model):
|
|||
return ics.serialize()
|
||||
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Desk(models.Model):
|
||||
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE)
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
slug = models.SlugField(_('Identifier'), max_length=160)
|
||||
timeperiod_exceptions_remote_url = models.URLField(
|
||||
_('URL to fetch time period exceptions from'),
|
||||
blank=True, max_length=500)
|
||||
_('URL to fetch time period exceptions from'), blank=True, max_length=500
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
@ -448,8 +489,7 @@ class Desk(models.Model):
|
|||
def import_json(cls, data):
|
||||
timeperiods = data.pop('timeperiods')
|
||||
exceptions = data.pop('exceptions')
|
||||
instance, created = cls.objects.get_or_create(
|
||||
slug=data['slug'], agenda=data['agenda'], defaults=data)
|
||||
instance, created = cls.objects.get_or_create(slug=data['slug'], agenda=data['agenda'], defaults=data)
|
||||
if not created:
|
||||
for k, v in data.items():
|
||||
setattr(instance, k, v)
|
||||
|
@ -462,21 +502,24 @@ class Desk(models.Model):
|
|||
return instance
|
||||
|
||||
def export_json(self):
|
||||
return {'label': self.label,
|
||||
return {
|
||||
'label': self.label,
|
||||
'slug': self.slug,
|
||||
'timeperiods': [time_period.export_json() for time_period in self.timeperiod_set.all()],
|
||||
'exceptions': [exception.export_json() for exception in self.timeperiodexception_set.all()]
|
||||
'exceptions': [exception.export_json() for exception in self.timeperiodexception_set.all()],
|
||||
}
|
||||
|
||||
def get_exceptions_within_two_weeks(self):
|
||||
in_two_weeks = make_aware(datetime.datetime.today() + datetime.timedelta(days=14))
|
||||
exceptions = self.timeperiodexception_set.filter(end_datetime__gte=now()).filter(
|
||||
Q(end_datetime__lte=in_two_weeks) | Q(start_datetime__lt=now()))
|
||||
Q(end_datetime__lte=in_two_weeks) | Q(start_datetime__lt=now())
|
||||
)
|
||||
if exceptions.exists():
|
||||
return exceptions
|
||||
# if none found within the 2 coming weeks, return the next one
|
||||
next_exception = self.timeperiodexception_set.filter(
|
||||
start_datetime__gte=now()).order_by('start_datetime').first()
|
||||
next_exception = (
|
||||
self.timeperiodexception_set.filter(start_datetime__gte=now()).order_by('start_datetime').first()
|
||||
)
|
||||
if next_exception:
|
||||
return [next_exception]
|
||||
return []
|
||||
|
@ -491,12 +534,14 @@ class Desk(models.Model):
|
|||
response.raise_for_status()
|
||||
except requests.HTTPError as e:
|
||||
raise ICSError(
|
||||
_('Failed to retrieve remote calendar (%(url)s, HTTP error %(status_code)s).') %
|
||||
{'url': url, 'status_code': e.response.status_code})
|
||||
_('Failed to retrieve remote calendar (%(url)s, HTTP error %(status_code)s).')
|
||||
% {'url': url, 'status_code': e.response.status_code}
|
||||
)
|
||||
except requests.RequestException as e:
|
||||
raise ICSError(
|
||||
_('Failed to retrieve remote calendar (%(url)s, %(exception)s).') %
|
||||
{'url': url, 'exception': e})
|
||||
_('Failed to retrieve remote calendar (%(url)s, %(exception)s).')
|
||||
% {'url': url, 'exception': e}
|
||||
)
|
||||
|
||||
return self.create_timeperiod_exceptions_from_ics(response.text, keep_synced_by_uid=True)
|
||||
|
||||
|
@ -582,7 +627,9 @@ class Desk(models.Model):
|
|||
if end_dt < update_datetime:
|
||||
TimePeriodException.objects.filter(**kwargs).update(**event)
|
||||
else:
|
||||
obj, created = TimePeriodException.objects.update_or_create(defaults=event, **kwargs)
|
||||
obj, created = TimePeriodException.objects.update_or_create(
|
||||
defaults=event, **kwargs
|
||||
)
|
||||
if created:
|
||||
total_created += 1
|
||||
# delete unseen occurrences
|
||||
|
@ -591,8 +638,9 @@ class Desk(models.Model):
|
|||
|
||||
if keep_synced_by_uid:
|
||||
# delete all outdated exceptions from remote calendar
|
||||
TimePeriodException.objects.filter(update_datetime__lt=update_datetime,
|
||||
desk=self).exclude(external_id='').delete()
|
||||
TimePeriodException.objects.filter(update_datetime__lt=update_datetime, desk=self).exclude(
|
||||
external_id=''
|
||||
).delete()
|
||||
|
||||
return total_created
|
||||
|
||||
|
@ -606,8 +654,8 @@ class Desk(models.Model):
|
|||
aware_date = make_aware(datetime.datetime(date.year, date.month, date.day))
|
||||
aware_next_date = aware_date + datetime.timedelta(days=1)
|
||||
for exception in self.timeperiodexception_set.filter(
|
||||
start_datetime__lt=aware_next_date,
|
||||
end_datetime__gt=aware_date):
|
||||
start_datetime__lt=aware_next_date, end_datetime__gt=aware_date
|
||||
):
|
||||
openslots.remove(exception.start_datetime, exception.end_datetime)
|
||||
|
||||
return openslots.search(aware_date, aware_next_date)
|
||||
|
@ -635,17 +683,20 @@ class TimePeriodException(models.Model):
|
|||
else:
|
||||
exc_repr = u'%s → %s' % (
|
||||
date_format(localtime(self.start_datetime), 'SHORT_DATE_FORMAT'),
|
||||
date_format(localtime(self.end_datetime), 'SHORT_DATE_FORMAT'))
|
||||
date_format(localtime(self.end_datetime), 'SHORT_DATE_FORMAT'),
|
||||
)
|
||||
else:
|
||||
if localtime(self.start_datetime).date() == localtime(self.end_datetime).date():
|
||||
# same day
|
||||
exc_repr = u'%s → %s' % (
|
||||
date_format(localtime(self.start_datetime), 'SHORT_DATETIME_FORMAT'),
|
||||
date_format(localtime(self.end_datetime), 'TIME_FORMAT'))
|
||||
date_format(localtime(self.end_datetime), 'TIME_FORMAT'),
|
||||
)
|
||||
else:
|
||||
exc_repr = u'%s → %s' % (
|
||||
date_format(localtime(self.start_datetime), 'SHORT_DATETIME_FORMAT'),
|
||||
date_format(localtime(self.end_datetime), 'SHORT_DATETIME_FORMAT'))
|
||||
date_format(localtime(self.end_datetime), 'SHORT_DATETIME_FORMAT'),
|
||||
)
|
||||
|
||||
if self.label:
|
||||
exc_repr = u'%s (%s)' % (self.label, exc_repr)
|
||||
|
@ -662,9 +713,8 @@ class TimePeriodException(models.Model):
|
|||
# incomplete time period, can't tell
|
||||
return False
|
||||
for event in Event.objects.filter(
|
||||
desk=self.desk,
|
||||
booking__isnull=False,
|
||||
booking__cancellation_datetime__isnull=True):
|
||||
desk=self.desk, booking__isnull=False, booking__cancellation_datetime__isnull=True
|
||||
):
|
||||
if self.start_datetime <= event.start_datetime < self.end_datetime:
|
||||
return True
|
||||
return False
|
||||
|
|
|
@ -21,29 +21,32 @@ from . import views
|
|||
urlpatterns = [
|
||||
url(r'agenda/$', views.agendas),
|
||||
url(r'agenda/(?P<agenda_identifier>[\w-]+)/$', views.agenda_detail),
|
||||
|
||||
url(r'agenda/(?P<agenda_identifier>[\w-]+)/datetimes/$', views.datetimes, name='api-agenda-datetimes'),
|
||||
url(r'agenda/(?P<agenda_identifier>[\w-]+)/fillslot/(?P<event_identifier>[\w:-]+)/$',
|
||||
views.fillslot, name='api-fillslot'),
|
||||
url(r'agenda/(?P<agenda_identifier>[\w-]+)/fillslots/$',
|
||||
views.fillslots, name='api-agenda-fillslots'),
|
||||
url(r'agenda/(?P<agenda_identifier>[\w-]+)/status/(?P<event_identifier>[\w-]+)/$', views.slot_status,
|
||||
name='api-event-status'),
|
||||
|
||||
url(r'agenda/meetings/(?P<meeting_identifier>[\w-]+)/datetimes/$',
|
||||
views.meeting_datetimes, name='api-agenda-meeting-datetimes-legacy'),
|
||||
url(r'agenda/(?P<agenda_identifier>[\w-]+)/meetings/$',
|
||||
views.meeting_list, name='api-agenda-meetings'),
|
||||
url(r'agenda/(?P<agenda_identifier>[\w-]+)/desks/$',
|
||||
views.agenda_desk_list, name='api-agenda-desks'),
|
||||
url(r'agenda/(?P<agenda_identifier>[\w-]+)/meetings/(?P<meeting_identifier>[\w-]+)/datetimes/$',
|
||||
views.meeting_datetimes, name='api-agenda-meeting-datetimes'),
|
||||
|
||||
url(
|
||||
r'agenda/(?P<agenda_identifier>[\w-]+)/fillslot/(?P<event_identifier>[\w:-]+)/$',
|
||||
views.fillslot,
|
||||
name='api-fillslot',
|
||||
),
|
||||
url(r'agenda/(?P<agenda_identifier>[\w-]+)/fillslots/$', views.fillslots, name='api-agenda-fillslots'),
|
||||
url(
|
||||
r'agenda/(?P<agenda_identifier>[\w-]+)/status/(?P<event_identifier>[\w-]+)/$',
|
||||
views.slot_status,
|
||||
name='api-event-status',
|
||||
),
|
||||
url(
|
||||
r'agenda/meetings/(?P<meeting_identifier>[\w-]+)/datetimes/$',
|
||||
views.meeting_datetimes,
|
||||
name='api-agenda-meeting-datetimes-legacy',
|
||||
),
|
||||
url(r'agenda/(?P<agenda_identifier>[\w-]+)/meetings/$', views.meeting_list, name='api-agenda-meetings'),
|
||||
url(r'agenda/(?P<agenda_identifier>[\w-]+)/desks/$', views.agenda_desk_list, name='api-agenda-desks'),
|
||||
url(
|
||||
r'agenda/(?P<agenda_identifier>[\w-]+)/meetings/(?P<meeting_identifier>[\w-]+)/datetimes/$',
|
||||
views.meeting_datetimes,
|
||||
name='api-agenda-meeting-datetimes',
|
||||
),
|
||||
url(r'booking/(?P<booking_pk>\w+)/$', views.booking),
|
||||
url(r'booking/(?P<booking_pk>\w+)/cancel/$', views.cancel_booking,
|
||||
name='api-cancel-booking'),
|
||||
url(r'booking/(?P<booking_pk>\w+)/accept/$', views.accept_booking,
|
||||
name='api-accept-booking'),
|
||||
url(r'booking/(?P<booking_pk>\w+)/ics/$', views.booking_ics,
|
||||
name='api-booking-ics'),
|
||||
url(r'booking/(?P<booking_pk>\w+)/cancel/$', views.cancel_booking, name='api-cancel-booking'),
|
||||
url(r'booking/(?P<booking_pk>\w+)/accept/$', views.accept_booking, name='api-accept-booking'),
|
||||
url(r'booking/(?P<booking_pk>\w+)/ics/$', views.booking_ics, name='api-booking-ics'),
|
||||
]
|
||||
|
|
|
@ -32,8 +32,7 @@ from rest_framework import permissions, serializers, status
|
|||
from rest_framework.views import APIView
|
||||
|
||||
from chrono.api.utils import Response
|
||||
from ..agendas.models import (Agenda, Event, Booking, MeetingType,
|
||||
TimePeriod, Desk)
|
||||
from ..agendas.models import Agenda, Event, Booking, MeetingType, TimePeriod, Desk
|
||||
from ..interval import Intervals
|
||||
|
||||
|
||||
|
@ -44,7 +43,9 @@ def format_response_datetime(dt):
|
|||
def get_exceptions_by_desk(agenda):
|
||||
exceptions_by_desk = {}
|
||||
for desk in Desk.objects.filter(agenda=agenda).prefetch_related('timeperiodexception_set'):
|
||||
exceptions_by_desk[desk.id] = [(exc.start_datetime, exc.end_datetime) for exc in desk.timeperiodexception_set.all()]
|
||||
exceptions_by_desk[desk.id] = [
|
||||
(exc.start_datetime, exc.end_datetime) for exc in desk.timeperiodexception_set.all()
|
||||
]
|
||||
return exceptions_by_desk
|
||||
|
||||
|
||||
|
@ -58,14 +59,16 @@ def get_all_slots(agenda, meeting_type):
|
|||
time_period_filters = {
|
||||
'min_datetime': min_datetime,
|
||||
'max_datetime': max_datetime,
|
||||
'meeting_type': meeting_type
|
||||
'meeting_type': meeting_type,
|
||||
}
|
||||
|
||||
base_date = now().date()
|
||||
open_slots_by_desk = defaultdict(lambda: Intervals())
|
||||
for time_period in TimePeriod.objects.filter(desk__agenda=agenda):
|
||||
duration = (datetime.datetime.combine(base_date, time_period.end_time) -
|
||||
datetime.datetime.combine(base_date, time_period.start_time)).seconds / 60
|
||||
duration = (
|
||||
datetime.datetime.combine(base_date, time_period.end_time)
|
||||
- datetime.datetime.combine(base_date, time_period.start_time)
|
||||
).seconds / 60
|
||||
if duration < meeting_type.duration:
|
||||
# skip time period that can't even hold a single meeting
|
||||
continue
|
||||
|
@ -80,11 +83,15 @@ def get_all_slots(agenda, meeting_type):
|
|||
begin, end = interval
|
||||
open_slots_by_desk[desk].remove_overlap(localtime(begin), localtime(end))
|
||||
|
||||
for event in agenda.event_set.filter(
|
||||
agenda=agenda, start_datetime__gte=min_datetime,
|
||||
start_datetime__lte=max_datetime + datetime.timedelta(meeting_type.duration)).select_related(
|
||||
'meeting_type').exclude(
|
||||
booking__cancellation_datetime__isnull=False):
|
||||
for event in (
|
||||
agenda.event_set.filter(
|
||||
agenda=agenda,
|
||||
start_datetime__gte=min_datetime,
|
||||
start_datetime__lte=max_datetime + datetime.timedelta(meeting_type.duration),
|
||||
)
|
||||
.select_related('meeting_type')
|
||||
.exclude(booking__cancellation_datetime__isnull=False)
|
||||
):
|
||||
for slot in open_slots_by_desk[event.desk_id].search_data(event.start_datetime, event.end_datetime):
|
||||
slot.full = True
|
||||
|
||||
|
@ -108,21 +115,21 @@ def get_agenda_detail(request, agenda):
|
|||
if agenda.kind == 'events':
|
||||
agenda_detail['api'] = {
|
||||
'datetimes_url': request.build_absolute_uri(
|
||||
reverse('api-agenda-datetimes',
|
||||
kwargs={'agenda_identifier': agenda.slug}))
|
||||
reverse('api-agenda-datetimes', kwargs={'agenda_identifier': agenda.slug})
|
||||
)
|
||||
}
|
||||
elif agenda.kind == 'meetings':
|
||||
agenda_detail['api'] = {
|
||||
'meetings_url': request.build_absolute_uri(
|
||||
reverse('api-agenda-meetings',
|
||||
kwargs={'agenda_identifier': agenda.slug})),
|
||||
reverse('api-agenda-meetings', kwargs={'agenda_identifier': agenda.slug})
|
||||
),
|
||||
'desks_url': request.build_absolute_uri(
|
||||
reverse('api-agenda-desks',
|
||||
kwargs={'agenda_identifier': agenda.slug}))
|
||||
reverse('api-agenda-desks', kwargs={'agenda_identifier': agenda.slug})
|
||||
),
|
||||
}
|
||||
agenda_detail['api']['fillslots_url'] = request.build_absolute_uri(
|
||||
reverse('api-agenda-fillslots',
|
||||
kwargs={'agenda_identifier': agenda.slug}))
|
||||
reverse('api-agenda-fillslots', kwargs={'agenda_identifier': agenda.slug})
|
||||
)
|
||||
|
||||
return agenda_detail
|
||||
|
||||
|
@ -136,7 +143,7 @@ def get_event_places(event):
|
|||
if event.waiting_list_places:
|
||||
places['waiting_list_total'] = event.waiting_list_places
|
||||
places['waiting_list_reserved'] = event.waiting_list
|
||||
places['waiting_list_available'] = (event.waiting_list_places - event.waiting_list)
|
||||
places['waiting_list_available'] = event.waiting_list_places - event.waiting_list
|
||||
return places
|
||||
|
||||
|
||||
|
@ -144,10 +151,10 @@ class Agendas(APIView):
|
|||
permission_classes = ()
|
||||
|
||||
def get(self, request, format=None):
|
||||
agendas = [get_agenda_detail(request, agenda)
|
||||
for agenda in Agenda.objects.all().order_by('label')]
|
||||
agendas = [get_agenda_detail(request, agenda) for agenda in Agenda.objects.all().order_by('label')]
|
||||
return Response({'data': agendas})
|
||||
|
||||
|
||||
agendas = Agendas.as_view()
|
||||
|
||||
|
||||
|
@ -155,12 +162,14 @@ class AgendaDetail(APIView):
|
|||
'''
|
||||
Retrieve an agenda instance.
|
||||
'''
|
||||
|
||||
permission_classes = ()
|
||||
|
||||
def get(self, request, agenda_identifier):
|
||||
agenda = get_object_or_404(Agenda, slug=agenda_identifier)
|
||||
return Response({'data': get_agenda_detail(request, agenda)})
|
||||
|
||||
|
||||
agenda_detail = AgendaDetail.as_view()
|
||||
|
||||
|
||||
|
@ -186,21 +195,36 @@ class Datetimes(APIView):
|
|||
|
||||
if agenda.minimal_booking_delay:
|
||||
entries = entries.filter(
|
||||
start_datetime__gte=localtime(now() + datetime.timedelta(days=agenda.minimal_booking_delay)).replace(hour=0, minute=0))
|
||||
start_datetime__gte=localtime(
|
||||
now() + datetime.timedelta(days=agenda.minimal_booking_delay)
|
||||
).replace(hour=0, minute=0)
|
||||
)
|
||||
|
||||
if agenda.maximal_booking_delay:
|
||||
entries = entries.filter(
|
||||
start_datetime__lt=localtime(now() + datetime.timedelta(days=agenda.maximal_booking_delay)).replace(hour=0, minute=0))
|
||||
start_datetime__lt=localtime(
|
||||
now() + datetime.timedelta(days=agenda.maximal_booking_delay)
|
||||
).replace(hour=0, minute=0)
|
||||
)
|
||||
|
||||
if 'date_start' in request.GET:
|
||||
entries = entries.filter(start_datetime__gte=make_aware(
|
||||
datetime.datetime.combine(parse_date(request.GET['date_start']), datetime.time(0, 0))))
|
||||
entries = entries.filter(
|
||||
start_datetime__gte=make_aware(
|
||||
datetime.datetime.combine(parse_date(request.GET['date_start']), datetime.time(0, 0))
|
||||
)
|
||||
)
|
||||
|
||||
if 'date_end' in request.GET:
|
||||
entries = entries.filter(start_datetime__lt=make_aware(
|
||||
datetime.datetime.combine(parse_date(request.GET['date_end']), datetime.time(0, 0))))
|
||||
entries = entries.filter(
|
||||
start_datetime__lt=make_aware(
|
||||
datetime.datetime.combine(parse_date(request.GET['date_end']), datetime.time(0, 0))
|
||||
)
|
||||
)
|
||||
|
||||
response = {'data': [{'id': x.id,
|
||||
response = {
|
||||
'data': [
|
||||
{
|
||||
'id': x.id,
|
||||
'slug': x.slug,
|
||||
'text': force_text(x),
|
||||
'datetime': format_response_datetime(x.start_datetime),
|
||||
|
@ -208,21 +232,31 @@ class Datetimes(APIView):
|
|||
'disabled': bool(x.full),
|
||||
'api': {
|
||||
'fillslot_url': request.build_absolute_uri(
|
||||
reverse('api-fillslot',
|
||||
reverse(
|
||||
'api-fillslot',
|
||||
kwargs={
|
||||
'agenda_identifier': agenda.slug,
|
||||
'event_identifier': x.slug or x.id,
|
||||
})),
|
||||
'status_url': request.build_absolute_uri(
|
||||
reverse('api-event-status',
|
||||
kwargs={
|
||||
'agenda_identifier': agenda.slug,
|
||||
'event_identifier': x.slug or x.id,
|
||||
}))
|
||||
},
|
||||
} for x in entries]}
|
||||
)
|
||||
),
|
||||
'status_url': request.build_absolute_uri(
|
||||
reverse(
|
||||
'api-event-status',
|
||||
kwargs={
|
||||
'agenda_identifier': agenda.slug,
|
||||
'event_identifier': x.slug or x.id,
|
||||
},
|
||||
)
|
||||
),
|
||||
},
|
||||
}
|
||||
for x in entries
|
||||
]
|
||||
}
|
||||
return Response(response)
|
||||
|
||||
|
||||
datetimes = Datetimes.as_view()
|
||||
|
||||
|
||||
|
@ -235,8 +269,9 @@ class MeetingDatetimes(APIView):
|
|||
# legacy access by meeting id
|
||||
meeting_type = MeetingType.objects.get(id=meeting_identifier)
|
||||
else:
|
||||
meeting_type = MeetingType.objects.get(slug=meeting_identifier,
|
||||
agenda__slug=agenda_identifier)
|
||||
meeting_type = MeetingType.objects.get(
|
||||
slug=meeting_identifier, agenda__slug=agenda_identifier
|
||||
)
|
||||
except (ValueError, MeetingType.DoesNotExist):
|
||||
raise Http404()
|
||||
|
||||
|
@ -259,22 +294,27 @@ class MeetingDatetimes(APIView):
|
|||
# to request.build_absolute_uri()
|
||||
fake_event_identifier = '__event_identifier__'
|
||||
fillslot_url = request.build_absolute_uri(
|
||||
reverse('api-fillslot',
|
||||
kwargs={
|
||||
'agenda_identifier': agenda.slug,
|
||||
'event_identifier': fake_event_identifier,
|
||||
}))
|
||||
reverse(
|
||||
'api-fillslot',
|
||||
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': fake_event_identifier,},
|
||||
)
|
||||
)
|
||||
|
||||
response = {'data': [{'id': x.id,
|
||||
response = {
|
||||
'data': [
|
||||
{
|
||||
'id': x.id,
|
||||
'datetime': format_response_datetime(x.start_datetime),
|
||||
'text': force_text(x),
|
||||
'disabled': bool(x.full),
|
||||
'api': {
|
||||
'fillslot_url': fillslot_url.replace(fake_event_identifier, str(x.id)),
|
||||
},
|
||||
} for x in slots]}
|
||||
'api': {'fillslot_url': fillslot_url.replace(fake_event_identifier, str(x.id)),},
|
||||
}
|
||||
for x in slots
|
||||
]
|
||||
}
|
||||
return Response(response)
|
||||
|
||||
|
||||
meeting_datetimes = MeetingDatetimes.as_view()
|
||||
|
||||
|
||||
|
@ -291,20 +331,28 @@ class MeetingList(APIView):
|
|||
|
||||
meeting_types = []
|
||||
for meeting_type in agenda.meetingtype_set.all():
|
||||
meeting_types.append({
|
||||
meeting_types.append(
|
||||
{
|
||||
'text': meeting_type.label,
|
||||
'id': meeting_type.slug,
|
||||
'duration': meeting_type.duration,
|
||||
'api': {
|
||||
'datetimes_url': request.build_absolute_uri(
|
||||
reverse('api-agenda-meeting-datetimes',
|
||||
kwargs={'agenda_identifier': agenda.slug,
|
||||
'meeting_identifier': meeting_type.slug})),
|
||||
reverse(
|
||||
'api-agenda-meeting-datetimes',
|
||||
kwargs={
|
||||
'agenda_identifier': agenda.slug,
|
||||
'meeting_identifier': meeting_type.slug,
|
||||
},
|
||||
)
|
||||
),
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return Response({'data': meeting_types})
|
||||
|
||||
|
||||
meeting_list = MeetingList.as_view()
|
||||
|
||||
|
||||
|
@ -322,6 +370,7 @@ class AgendaDeskList(APIView):
|
|||
desks = [{'id': x.slug, 'text': x.label} for x in agenda.desk_set.all()]
|
||||
return Response({'data': desks})
|
||||
|
||||
|
||||
agenda_desk_list = AgendaDeskList.as_view()
|
||||
|
||||
|
||||
|
@ -329,6 +378,7 @@ class SlotSerializer(serializers.Serializer):
|
|||
'''
|
||||
payload to fill one slot. The slot (event id) is in the URL.
|
||||
'''
|
||||
|
||||
label = serializers.CharField(max_length=250, allow_blank=True)
|
||||
user_name = serializers.CharField(max_length=250, allow_blank=True)
|
||||
user_display_label = serializers.CharField(max_length=250, allow_blank=True)
|
||||
|
@ -342,8 +392,10 @@ class SlotsSerializer(SlotSerializer):
|
|||
payload to fill multiple slots: same as SlotSerializer, but the
|
||||
slots list is in the payload.
|
||||
'''
|
||||
slots = serializers.ListField(required=True,
|
||||
child=serializers.CharField(max_length=64, allow_blank=False))
|
||||
|
||||
slots = serializers.ListField(
|
||||
required=True, child=serializers.CharField(max_length=64, allow_blank=False)
|
||||
)
|
||||
|
||||
|
||||
class Fillslots(APIView):
|
||||
|
@ -351,8 +403,7 @@ class Fillslots(APIView):
|
|||
serializer_class = SlotsSerializer
|
||||
|
||||
def post(self, request, agenda_identifier=None, event_identifier=None, format=None):
|
||||
return self.fillslot(request=request, agenda_identifier=agenda_identifier,
|
||||
format=format)
|
||||
return self.fillslot(request=request, agenda_identifier=agenda_identifier, format=format)
|
||||
|
||||
def fillslot(self, request, agenda_identifier=None, slots=[], format=None):
|
||||
multiple_booking = bool(not slots)
|
||||
|
@ -367,22 +418,28 @@ class Fillslots(APIView):
|
|||
|
||||
serializer = self.serializer_class(data=request.data, partial=True)
|
||||
if not serializer.is_valid():
|
||||
return Response({
|
||||
return Response(
|
||||
{
|
||||
'err': 1,
|
||||
'err_class': 'invalid payload',
|
||||
'err_desc': _('invalid payload'),
|
||||
'errors': serializer.errors
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
'errors': serializer.errors,
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
payload = serializer.validated_data
|
||||
|
||||
if 'slots' in payload:
|
||||
slots = payload['slots']
|
||||
if not slots:
|
||||
return Response({
|
||||
return Response(
|
||||
{
|
||||
'err': 1,
|
||||
'err_class': 'slots list cannot be empty',
|
||||
'err_desc': _('slots list cannot be empty'),
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if 'count' in payload:
|
||||
places_count = payload['count']
|
||||
|
@ -391,20 +448,26 @@ class Fillslots(APIView):
|
|||
try:
|
||||
places_count = int(request.query_params['count'])
|
||||
except ValueError:
|
||||
return Response({
|
||||
return Response(
|
||||
{
|
||||
'err': 1,
|
||||
'err_class': 'invalid value for count (%s)' % request.query_params['count'],
|
||||
'err_desc': _('invalid value for count (%s)') % request.query_params['count'],
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
else:
|
||||
places_count = 1
|
||||
|
||||
if places_count <= 0:
|
||||
return Response({
|
||||
return Response(
|
||||
{
|
||||
'err': 1,
|
||||
'err_class': 'count cannot be less than or equal to zero',
|
||||
'err_desc': _('count cannot be less than or equal to zero'),
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
to_cancel_booking = None
|
||||
cancel_booking_id = None
|
||||
|
@ -412,11 +475,14 @@ class Fillslots(APIView):
|
|||
try:
|
||||
cancel_booking_id = int(payload.get('cancel_booking_id'))
|
||||
except (ValueError, TypeError):
|
||||
return Response({
|
||||
return Response(
|
||||
{
|
||||
'err': 1,
|
||||
'err_class': 'cancel_booking_id is not an integer',
|
||||
'err_desc': _('cancel_booking_id is not an integer'),
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if cancel_booking_id is not None:
|
||||
cancel_error = None
|
||||
|
@ -432,11 +498,7 @@ class Fillslots(APIView):
|
|||
cancel_error = gettext_noop('cancel booking: booking does no exist')
|
||||
|
||||
if cancel_error:
|
||||
return Response({
|
||||
'err': 1,
|
||||
'err_class': cancel_error,
|
||||
'err_desc': _(cancel_error),
|
||||
})
|
||||
return Response({'err': 1, 'err_class': cancel_error, 'err_desc': _(cancel_error),})
|
||||
|
||||
extra_data = {}
|
||||
for k, v in request.data.items():
|
||||
|
@ -454,17 +516,25 @@ class Fillslots(APIView):
|
|||
try:
|
||||
meeting_type_id_, datetime_str = slot.split(':')
|
||||
except ValueError:
|
||||
return Response({
|
||||
return Response(
|
||||
{
|
||||
'err': 1,
|
||||
'err_class': 'invalid slot: %s' % slot,
|
||||
'err_desc': _('invalid slot: %s') % slot,
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
if meeting_type_id_ != meeting_type_id:
|
||||
return Response({
|
||||
return Response(
|
||||
{
|
||||
'err': 1,
|
||||
'err_class': 'all slots must have the same meeting type id (%s)' % meeting_type_id,
|
||||
'err_desc': _('all slots must have the same meeting type id (%s)') % meeting_type_id,
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
'err_class': 'all slots must have the same meeting type id (%s)'
|
||||
% meeting_type_id,
|
||||
'err_desc': _('all slots must have the same meeting type id (%s)')
|
||||
% meeting_type_id,
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')))
|
||||
|
||||
# get all free slots and separate them by desk
|
||||
|
@ -480,11 +550,13 @@ class Fillslots(APIView):
|
|||
available_desk = Desk.objects.get(id=available_desk_id)
|
||||
break
|
||||
else:
|
||||
return Response({
|
||||
return Response(
|
||||
{
|
||||
'err': 1,
|
||||
'err_class': 'no more desk available',
|
||||
'err_desc': _('no more desk available'),
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
# all datetimes are free, book them in order
|
||||
datetimes = list(datetimes)
|
||||
|
@ -494,11 +566,16 @@ class Fillslots(APIView):
|
|||
# create them now, with data from the slots and the desk we found.
|
||||
events = []
|
||||
for start_datetime in datetimes:
|
||||
events.append(Event.objects.create(agenda=agenda,
|
||||
events.append(
|
||||
Event.objects.create(
|
||||
agenda=agenda,
|
||||
meeting_type_id=meeting_type_id,
|
||||
start_datetime=start_datetime,
|
||||
full=False, places=1,
|
||||
desk=available_desk))
|
||||
full=False,
|
||||
places=1,
|
||||
desk=available_desk,
|
||||
)
|
||||
)
|
||||
else:
|
||||
try:
|
||||
events = Event.objects.filter(id__in=[int(s) for s in slots]).order_by('start_datetime')
|
||||
|
@ -514,18 +591,10 @@ class Fillslots(APIView):
|
|||
# in the waiting list.
|
||||
in_waiting_list = True
|
||||
if (event.waiting_list + places_count) > event.waiting_list_places:
|
||||
return Response({
|
||||
'err': 1,
|
||||
'err_class': 'sold out',
|
||||
'err_desc': _('sold out'),
|
||||
})
|
||||
return Response({'err': 1, 'err_class': 'sold out', 'err_desc': _('sold out'),})
|
||||
else:
|
||||
if (event.booked_places + places_count) > event.places:
|
||||
return Response({
|
||||
'err': 1,
|
||||
'err_class': 'sold out',
|
||||
'err_desc': _('sold out')
|
||||
})
|
||||
return Response({'err': 1, 'err_class': 'sold out', 'err_desc': _('sold out')})
|
||||
|
||||
with transaction.atomic():
|
||||
if to_cancel_booking:
|
||||
|
@ -536,13 +605,15 @@ class Fillslots(APIView):
|
|||
primary_booking = None
|
||||
for event in events:
|
||||
for i in range(places_count):
|
||||
new_booking = Booking(event_id=event.id,
|
||||
new_booking = Booking(
|
||||
event_id=event.id,
|
||||
in_waiting_list=in_waiting_list,
|
||||
label=payload.get('label', ''),
|
||||
user_name=payload.get('user_name', ''),
|
||||
backoffice_url=payload.get('backoffice_url', ''),
|
||||
user_display_label=payload.get('user_display_label', ''),
|
||||
extra_data=extra_data)
|
||||
extra_data=extra_data,
|
||||
)
|
||||
if primary_booking is not None:
|
||||
new_booking.primary_booking = primary_booking
|
||||
new_booking.save()
|
||||
|
@ -556,21 +627,22 @@ class Fillslots(APIView):
|
|||
'datetime': format_response_datetime(events[0].start_datetime),
|
||||
'api': {
|
||||
'cancel_url': request.build_absolute_uri(
|
||||
reverse('api-cancel-booking', kwargs={'booking_pk': primary_booking.id})),
|
||||
reverse('api-cancel-booking', kwargs={'booking_pk': primary_booking.id})
|
||||
),
|
||||
'ics_url': request.build_absolute_uri(
|
||||
reverse('api-booking-ics', kwargs={'booking_pk': primary_booking.id})),
|
||||
}
|
||||
reverse('api-booking-ics', kwargs={'booking_pk': primary_booking.id})
|
||||
),
|
||||
},
|
||||
}
|
||||
if in_waiting_list:
|
||||
response['api']['accept_url'] = request.build_absolute_uri(
|
||||
reverse('api-accept-booking', kwargs={'booking_pk': primary_booking.id}))
|
||||
reverse('api-accept-booking', kwargs={'booking_pk': primary_booking.id})
|
||||
)
|
||||
if agenda.kind == 'meetings':
|
||||
response['end_datetime'] = format_response_datetime(events[-1].end_datetime)
|
||||
response['duration'] = (events[-1].end_datetime - events[-1].start_datetime).seconds // 60
|
||||
if available_desk:
|
||||
response['desk'] = {
|
||||
'label': available_desk.label,
|
||||
'slug': available_desk.slug}
|
||||
response['desk'] = {'label': available_desk.label, 'slug': available_desk.slug}
|
||||
if to_cancel_booking:
|
||||
response['cancelled_booking_id'] = cancelled_booking_id
|
||||
if agenda.kind == 'events' and not multiple_booking:
|
||||
|
@ -579,6 +651,7 @@ class Fillslots(APIView):
|
|||
|
||||
return Response(response)
|
||||
|
||||
|
||||
fillslots = Fillslots.as_view()
|
||||
|
||||
|
||||
|
@ -586,10 +659,13 @@ class Fillslot(Fillslots):
|
|||
serializer_class = SlotSerializer
|
||||
|
||||
def post(self, request, agenda_identifier=None, event_identifier=None, format=None):
|
||||
return self.fillslot(request=request,
|
||||
return self.fillslot(
|
||||
request=request,
|
||||
agenda_identifier=agenda_identifier,
|
||||
slots=[event_identifier], # fill a "list on one slot"
|
||||
format=format)
|
||||
format=format,
|
||||
)
|
||||
|
||||
|
||||
fillslot = Fillslot.as_view()
|
||||
|
||||
|
@ -599,14 +675,14 @@ class BookingAPI(APIView):
|
|||
|
||||
def initial(self, request, *args, **kwargs):
|
||||
super(BookingAPI, self).initial(request, *args, **kwargs)
|
||||
self.booking = Booking.objects.get(id=kwargs.get('booking_pk'),
|
||||
cancellation_datetime__isnull=True)
|
||||
self.booking = Booking.objects.get(id=kwargs.get('booking_pk'), cancellation_datetime__isnull=True)
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.booking.cancel()
|
||||
response = {'err': 0, 'booking_id': self.booking.id}
|
||||
return Response(response)
|
||||
|
||||
|
||||
booking = BookingAPI.as_view()
|
||||
|
||||
|
||||
|
@ -616,6 +692,7 @@ class CancelBooking(APIView):
|
|||
|
||||
It will return an error (code 1) if the booking was already cancelled.
|
||||
'''
|
||||
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
def post(self, request, booking_pk=None, format=None):
|
||||
|
@ -631,6 +708,7 @@ class CancelBooking(APIView):
|
|||
response = {'err': 0, 'booking_id': booking.id}
|
||||
return Response(response)
|
||||
|
||||
|
||||
cancel_booking = CancelBooking.as_view()
|
||||
|
||||
|
||||
|
@ -641,6 +719,7 @@ class AcceptBooking(APIView):
|
|||
It will return error codes if the booking was cancelled before (code 1) and
|
||||
if the booking was not in waiting list (code 2).
|
||||
'''
|
||||
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
def post(self, request, booking_pk=None, format=None):
|
||||
|
@ -663,6 +742,7 @@ class AcceptBooking(APIView):
|
|||
response = {'err': 0, 'booking_id': booking.id}
|
||||
return Response(response)
|
||||
|
||||
|
||||
accept_booking = AcceptBooking.as_view()
|
||||
|
||||
|
||||
|
@ -699,4 +779,5 @@ class BookingICS(APIView):
|
|||
response = HttpResponse(booking.get_ics(request), content_type='text/calendar')
|
||||
return response
|
||||
|
||||
|
||||
booking_ics = BookingICS.as_view()
|
||||
|
|
|
@ -63,6 +63,7 @@ class Intervals(object):
|
|||
10: [a],
|
||||
}
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
self.points = []
|
||||
self.container = []
|
||||
|
|
|
@ -26,8 +26,15 @@ from django.utils.encoding import force_text
|
|||
from django.utils.timezone import make_aware
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from chrono.agendas.models import (Agenda, Event, MeetingType, TimePeriod, Desk,
|
||||
TimePeriodException, WEEKDAYS_LIST)
|
||||
from chrono.agendas.models import (
|
||||
Agenda,
|
||||
Event,
|
||||
MeetingType,
|
||||
TimePeriod,
|
||||
Desk,
|
||||
TimePeriodException,
|
||||
WEEKDAYS_LIST,
|
||||
)
|
||||
|
||||
from . import widgets
|
||||
|
||||
|
@ -35,7 +42,7 @@ from . import widgets
|
|||
DATETIME_OPTIONS = {
|
||||
'weekStart': 1,
|
||||
'autoclose': True,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DateTimeWidget(widgets.DateTimeWidget):
|
||||
|
@ -49,11 +56,11 @@ class AgendaAddForm(forms.ModelForm):
|
|||
fields = ['label', 'kind', 'edit_role', 'view_role']
|
||||
|
||||
edit_role = forms.ModelChoiceField(
|
||||
label=_('Edit Role'), required=False,
|
||||
queryset=Group.objects.all().order_by('name'))
|
||||
label=_('Edit Role'), required=False, queryset=Group.objects.all().order_by('name')
|
||||
)
|
||||
view_role = forms.ModelChoiceField(
|
||||
label=_('View Role'), required=False,
|
||||
queryset=Group.objects.all().order_by('name'))
|
||||
label=_('View Role'), required=False, queryset=Group.objects.all().order_by('name')
|
||||
)
|
||||
|
||||
|
||||
class AgendaEditForm(AgendaAddForm):
|
||||
|
@ -102,9 +109,8 @@ class MeetingTypeForm(forms.ModelForm):
|
|||
|
||||
class TimePeriodAddForm(forms.Form):
|
||||
weekdays = forms.MultipleChoiceField(
|
||||
label=_('Days'),
|
||||
widget=widgets.WeekdaysWidget(),
|
||||
choices=WEEKDAYS_LIST)
|
||||
label=_('Days'), widget=widgets.WeekdaysWidget(), choices=WEEKDAYS_LIST
|
||||
)
|
||||
start_time = forms.TimeField(label=_('Start Time'), widget=widgets.TimeWidget())
|
||||
end_time = forms.TimeField(label=_('End Time'), widget=widgets.TimeWidget())
|
||||
|
||||
|
@ -168,9 +174,12 @@ class ImportEventsForm(forms.Form):
|
|||
events_csv_file = forms.FileField(
|
||||
label=_('Events File'),
|
||||
required=True,
|
||||
help_text=_('CSV file with date, time, number of places, '
|
||||
help_text=_(
|
||||
'CSV file with date, time, number of places, '
|
||||
'number of places in waiting list, and label '
|
||||
'as columns.'))
|
||||
'as columns.'
|
||||
),
|
||||
)
|
||||
events = None
|
||||
|
||||
def __init__(self, agenda_pk, **kwargs):
|
||||
|
@ -201,31 +210,37 @@ class ImportEventsForm(forms.Form):
|
|||
if not csvline:
|
||||
continue
|
||||
if len(csvline) < 3:
|
||||
raise ValidationError(_('Invalid file format. (line %d)') % (i+1))
|
||||
raise ValidationError(_('Invalid file format. (line %d)') % (i + 1))
|
||||
if i == 0 and csvline[0].strip('#') in ('date', 'Date', _('date'), _('Date')):
|
||||
continue
|
||||
event = Event()
|
||||
event.agenda_id = self.agenda_pk
|
||||
for datetime_fmt in ('%Y-%m-%d %H:%M', '%d/%m/%Y %H:%M',
|
||||
'%d/%m/%Y %Hh%M', '%Y-%m-%d %H:%M:%S', '%d/%m/%Y %H:%M:%S'):
|
||||
for datetime_fmt in (
|
||||
'%Y-%m-%d %H:%M',
|
||||
'%d/%m/%Y %H:%M',
|
||||
'%d/%m/%Y %Hh%M',
|
||||
'%Y-%m-%d %H:%M:%S',
|
||||
'%d/%m/%Y %H:%M:%S',
|
||||
):
|
||||
try:
|
||||
event_datetime = datetime.datetime.strptime(
|
||||
'%s %s' % tuple(csvline[:2]), datetime_fmt)
|
||||
event_datetime = datetime.datetime.strptime('%s %s' % tuple(csvline[:2]), datetime_fmt)
|
||||
except ValueError:
|
||||
continue
|
||||
event.start_datetime = make_aware(event_datetime)
|
||||
break
|
||||
else:
|
||||
raise ValidationError(_('Invalid file format. (date/time format, line %d)') % (i+1))
|
||||
raise ValidationError(_('Invalid file format. (date/time format, line %d)') % (i + 1))
|
||||
try:
|
||||
event.places = int(csvline[2])
|
||||
except ValueError:
|
||||
raise ValidationError(_('Invalid file format. (number of places, line %d)') % (i+1))
|
||||
raise ValidationError(_('Invalid file format. (number of places, line %d)') % (i + 1))
|
||||
if len(csvline) >= 4:
|
||||
try:
|
||||
event.waiting_list_places = int(csvline[3])
|
||||
except ValueError:
|
||||
raise ValidationError(_('Invalid file format. (number of places in waiting list, line %d)') % (i+1))
|
||||
raise ValidationError(
|
||||
_('Invalid file format. (number of places in waiting list, line %d)') % (i + 1)
|
||||
)
|
||||
if len(csvline) >= 5:
|
||||
event.label = force_text(csvline[4])
|
||||
exclude = ['desk', 'meeting_type']
|
||||
|
@ -237,11 +252,10 @@ class ImportEventsForm(forms.Form):
|
|||
event.full_clean(exclude=exclude)
|
||||
except ValidationError as e:
|
||||
errors = [
|
||||
_('Invalid file format. (%(label)s: %(errors)s, line %(line)d)') % {
|
||||
'label': label,
|
||||
'errors': u', '.join(field_errors),
|
||||
'line': i + 1
|
||||
} for label, field_errors in e.message_dict.items()]
|
||||
_('Invalid file format. (%(label)s: %(errors)s, line %(line)d)')
|
||||
% {'label': label, 'errors': u', '.join(field_errors), 'line': i + 1}
|
||||
for label, field_errors in e.message_dict.items()
|
||||
]
|
||||
raise ValidationError(errors)
|
||||
events.append(event)
|
||||
self.events = events
|
||||
|
@ -252,10 +266,16 @@ class ExceptionsImportForm(forms.ModelForm):
|
|||
model = Desk
|
||||
fields = []
|
||||
|
||||
ics_file = forms.FileField(label=_('ICS File'), required=False,
|
||||
help_text=_('ICS file containing events which will be considered as exceptions.'))
|
||||
ics_url = forms.URLField(label=_('URL'), required=False,
|
||||
help_text=_('URL to remote calendar which will be synchronised hourly.'))
|
||||
ics_file = forms.FileField(
|
||||
label=_('ICS File'),
|
||||
required=False,
|
||||
help_text=_('ICS file containing events which will be considered as exceptions.'),
|
||||
)
|
||||
ics_url = forms.URLField(
|
||||
label=_('URL'),
|
||||
required=False,
|
||||
help_text=_('URL to remote calendar which will be synchronised hourly.'),
|
||||
)
|
||||
|
||||
|
||||
class AgendasImportForm(forms.Form):
|
||||
|
|
|
@ -27,8 +27,8 @@ class Command(BaseCommand):
|
|||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--output', metavar='FILE', default=None,
|
||||
help='name of a file to write output to')
|
||||
'--output', metavar='FILE', default=None, help='name of a file to write output to'
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if options['output']:
|
||||
|
|
|
@ -27,17 +27,12 @@ class Command(BaseCommand):
|
|||
help = 'Import an exported site'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('filename', metavar='FILENAME', type=str,
|
||||
help='name of file to import')
|
||||
parser.add_argument('filename', metavar='FILENAME', type=str, help='name of file to import')
|
||||
parser.add_argument('--clean', action='store_true', default=False, help='Clean site before importing')
|
||||
parser.add_argument(
|
||||
'--clean', action='store_true', default=False,
|
||||
help='Clean site before importing')
|
||||
parser.add_argument(
|
||||
'--if-empty', action='store_true', default=False,
|
||||
help='Import only if site is empty')
|
||||
parser.add_argument(
|
||||
'--overwrite', action='store_true', default=False,
|
||||
help='Overwrite existing data')
|
||||
'--if-empty', action='store_true', default=False, help='Import only if site is empty'
|
||||
)
|
||||
parser.add_argument('--overwrite', action='store_true', default=False, help='Overwrite existing data')
|
||||
|
||||
def handle(self, filename, **options):
|
||||
if filename == '-':
|
||||
|
@ -45,9 +40,11 @@ class Command(BaseCommand):
|
|||
else:
|
||||
fd = open(filename)
|
||||
try:
|
||||
import_site(json.load(fd),
|
||||
import_site(
|
||||
json.load(fd),
|
||||
if_empty=options['if_empty'],
|
||||
clean=options['clean'],
|
||||
overwrite=options['overwrite'])
|
||||
overwrite=options['overwrite'],
|
||||
)
|
||||
except AgendaImportError as exc:
|
||||
raise CommandError(u'%s' % exc)
|
||||
|
|
|
@ -20,69 +20,90 @@ from . import views
|
|||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.homepage, name='chrono-manager-homepage'),
|
||||
url(r'^agendas/add/$', views.agenda_add,
|
||||
name='chrono-manager-agenda-add'),
|
||||
url(r'^agendas/import/$', views.agendas_import,
|
||||
name='chrono-manager-agendas-import'),
|
||||
url(r'^agendas/(?P<pk>\d+)/$', views.agenda_view,
|
||||
name='chrono-manager-agenda-view'),
|
||||
url(r'^agendas/(?P<pk>\d+)/(?P<year>[0-9]{4})/(?P<month>[0-9]+)/$', views.agenda_monthly_view,
|
||||
name='chrono-manager-agenda-month-view'),
|
||||
url(r'^agendas/(?P<pk>\d+)/(?P<year>[0-9]{4})/(?P<month>[0-9]+)/(?P<day>[0-9]+)/$', views.agenda_day_view,
|
||||
name='chrono-manager-agenda-day-view'),
|
||||
url(r'^agendas/(?P<pk>\d+)/settings$', views.agenda_settings,
|
||||
name='chrono-manager-agenda-settings'),
|
||||
url(r'^agendas/(?P<pk>\d+)/edit$', views.agenda_edit,
|
||||
name='chrono-manager-agenda-edit'),
|
||||
url(r'^agendas/(?P<pk>\d+)/delete$', views.agenda_delete,
|
||||
name='chrono-manager-agenda-delete'),
|
||||
url(r'^agendas/(?P<pk>\d+)/export$', views.agenda_export,
|
||||
name='chrono-manager-agenda-export'),
|
||||
url(r'^agendas/(?P<pk>\d+)/add-event$', views.agenda_add_event,
|
||||
name='chrono-manager-agenda-add-event'),
|
||||
url(r'^agendas/(?P<pk>\d+)/import-events$', views.agenda_import_events,
|
||||
name='chrono-manager-agenda-import-events'),
|
||||
url(r'^events/(?P<pk>\d+)/$', views.event_edit,
|
||||
name='chrono-manager-event-edit'),
|
||||
url(r'^events/(?P<pk>\d+)/delete$', views.event_delete,
|
||||
name='chrono-manager-event-delete'),
|
||||
|
||||
url(r'^agendas/(?P<pk>\d+)/add-meeting-type$', views.agenda_add_meeting_type,
|
||||
name='chrono-manager-agenda-add-meeting-type'),
|
||||
url(r'^meetingtypes/(?P<pk>\d+)/edit$', views.meeting_type_edit,
|
||||
name='chrono-manager-meeting-type-edit'),
|
||||
url(r'^meetingtypes/(?P<pk>\d+)/delete$', views.meeting_type_delete,
|
||||
name='chrono-manager-meeting-type-delete'),
|
||||
|
||||
url(r'^agendas/(?P<agenda_pk>\d+)/desk/(?P<pk>\d+)/add-time-period$', views.agenda_add_time_period,
|
||||
name='chrono-manager-agenda-add-time-period'),
|
||||
url(r'^timeperiods/(?P<pk>\d+)/edit$', views.time_period_edit,
|
||||
name='chrono-manager-time-period-edit'),
|
||||
url(r'^timeperiods/(?P<pk>\d+)/delete$', views.time_period_delete,
|
||||
name='chrono-manager-time-period-delete'),
|
||||
|
||||
url(r'^agendas/(?P<pk>\d+)/add-desk$', views.agenda_add_desk,
|
||||
name='chrono-manager-agenda-add-desk'),
|
||||
url(r'^desks/(?P<pk>\d+)/edit$', views.desk_edit,
|
||||
name='chrono-manager-desk-edit'),
|
||||
url(r'^desks/(?P<pk>\d+)/delete$', views.desk_delete,
|
||||
name='chrono-manager-desk-delete'),
|
||||
|
||||
url(r'^agendas/(?P<agenda_pk>\d+)/desk/(?P<pk>\d+)/add-time-period-exception$', views.agenda_add_time_period_exception,
|
||||
name='chrono-manager-agenda-add-time-period-exception'),
|
||||
url(r'^agendas/desk/(?P<pk>\d+)/import-exceptions-from-ics/$', views.desk_import_time_period_exceptions,
|
||||
name='chrono-manager-desk-add-import-time-period-exceptions'),
|
||||
url(r'^time-period-exceptions/(?P<pk>\d+)/edit$', views.time_period_exception_edit,
|
||||
name='chrono-manager-time-period-exception-edit'),
|
||||
url(r'^time-period-exceptions/(?P<pk>\d+)/delete$', views.time_period_exception_delete,
|
||||
name='chrono-manager-time-period-exception-delete'),
|
||||
url(r'^time-period-exceptions/(?P<pk>\d+)/exception-extract-list$', views.time_period_exception_extract_list,
|
||||
name='chrono-manager-time-period-exception-extract-list'),
|
||||
url(r'^time-period-exceptions/(?P<pk>\d+)/exception-list$', views.time_period_exception_list,
|
||||
name='chrono-manager-time-period-exception-list'),
|
||||
|
||||
url(r'^agendas/events.csv$', views.agenda_import_events_sample_csv,
|
||||
name='chrono-manager-sample-events-csv'),
|
||||
|
||||
url(r'^agendas/add/$', views.agenda_add, name='chrono-manager-agenda-add'),
|
||||
url(r'^agendas/import/$', views.agendas_import, name='chrono-manager-agendas-import'),
|
||||
url(r'^agendas/(?P<pk>\d+)/$', views.agenda_view, name='chrono-manager-agenda-view'),
|
||||
url(
|
||||
r'^agendas/(?P<pk>\d+)/(?P<year>[0-9]{4})/(?P<month>[0-9]+)/$',
|
||||
views.agenda_monthly_view,
|
||||
name='chrono-manager-agenda-month-view',
|
||||
),
|
||||
url(
|
||||
r'^agendas/(?P<pk>\d+)/(?P<year>[0-9]{4})/(?P<month>[0-9]+)/(?P<day>[0-9]+)/$',
|
||||
views.agenda_day_view,
|
||||
name='chrono-manager-agenda-day-view',
|
||||
),
|
||||
url(r'^agendas/(?P<pk>\d+)/settings$', views.agenda_settings, name='chrono-manager-agenda-settings'),
|
||||
url(r'^agendas/(?P<pk>\d+)/edit$', views.agenda_edit, name='chrono-manager-agenda-edit'),
|
||||
url(r'^agendas/(?P<pk>\d+)/delete$', views.agenda_delete, name='chrono-manager-agenda-delete'),
|
||||
url(r'^agendas/(?P<pk>\d+)/export$', views.agenda_export, name='chrono-manager-agenda-export'),
|
||||
url(r'^agendas/(?P<pk>\d+)/add-event$', views.agenda_add_event, name='chrono-manager-agenda-add-event'),
|
||||
url(
|
||||
r'^agendas/(?P<pk>\d+)/import-events$',
|
||||
views.agenda_import_events,
|
||||
name='chrono-manager-agenda-import-events',
|
||||
),
|
||||
url(r'^events/(?P<pk>\d+)/$', views.event_edit, name='chrono-manager-event-edit'),
|
||||
url(r'^events/(?P<pk>\d+)/delete$', views.event_delete, name='chrono-manager-event-delete'),
|
||||
url(
|
||||
r'^agendas/(?P<pk>\d+)/add-meeting-type$',
|
||||
views.agenda_add_meeting_type,
|
||||
name='chrono-manager-agenda-add-meeting-type',
|
||||
),
|
||||
url(r'^meetingtypes/(?P<pk>\d+)/edit$', views.meeting_type_edit, name='chrono-manager-meeting-type-edit'),
|
||||
url(
|
||||
r'^meetingtypes/(?P<pk>\d+)/delete$',
|
||||
views.meeting_type_delete,
|
||||
name='chrono-manager-meeting-type-delete',
|
||||
),
|
||||
url(
|
||||
r'^agendas/(?P<agenda_pk>\d+)/desk/(?P<pk>\d+)/add-time-period$',
|
||||
views.agenda_add_time_period,
|
||||
name='chrono-manager-agenda-add-time-period',
|
||||
),
|
||||
url(r'^timeperiods/(?P<pk>\d+)/edit$', views.time_period_edit, name='chrono-manager-time-period-edit'),
|
||||
url(
|
||||
r'^timeperiods/(?P<pk>\d+)/delete$',
|
||||
views.time_period_delete,
|
||||
name='chrono-manager-time-period-delete',
|
||||
),
|
||||
url(r'^agendas/(?P<pk>\d+)/add-desk$', views.agenda_add_desk, name='chrono-manager-agenda-add-desk'),
|
||||
url(r'^desks/(?P<pk>\d+)/edit$', views.desk_edit, name='chrono-manager-desk-edit'),
|
||||
url(r'^desks/(?P<pk>\d+)/delete$', views.desk_delete, name='chrono-manager-desk-delete'),
|
||||
url(
|
||||
r'^agendas/(?P<agenda_pk>\d+)/desk/(?P<pk>\d+)/add-time-period-exception$',
|
||||
views.agenda_add_time_period_exception,
|
||||
name='chrono-manager-agenda-add-time-period-exception',
|
||||
),
|
||||
url(
|
||||
r'^agendas/desk/(?P<pk>\d+)/import-exceptions-from-ics/$',
|
||||
views.desk_import_time_period_exceptions,
|
||||
name='chrono-manager-desk-add-import-time-period-exceptions',
|
||||
),
|
||||
url(
|
||||
r'^time-period-exceptions/(?P<pk>\d+)/edit$',
|
||||
views.time_period_exception_edit,
|
||||
name='chrono-manager-time-period-exception-edit',
|
||||
),
|
||||
url(
|
||||
r'^time-period-exceptions/(?P<pk>\d+)/delete$',
|
||||
views.time_period_exception_delete,
|
||||
name='chrono-manager-time-period-exception-delete',
|
||||
),
|
||||
url(
|
||||
r'^time-period-exceptions/(?P<pk>\d+)/exception-extract-list$',
|
||||
views.time_period_exception_extract_list,
|
||||
name='chrono-manager-time-period-exception-extract-list',
|
||||
),
|
||||
url(
|
||||
r'^time-period-exceptions/(?P<pk>\d+)/exception-list$',
|
||||
views.time_period_exception_list,
|
||||
name='chrono-manager-time-period-exception-list',
|
||||
),
|
||||
url(
|
||||
r'^agendas/events.csv$',
|
||||
views.agenda_import_events_sample_csv,
|
||||
name='chrono-manager-sample-events-csv',
|
||||
),
|
||||
url(r'^menu.json$', views.menu_json),
|
||||
]
|
||||
|
|
|
@ -28,17 +28,46 @@ from django.utils.timezone import now, make_aware, make_naive
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext
|
||||
from django.utils.encoding import force_text
|
||||
from django.views.generic import (DetailView, CreateView, UpdateView,
|
||||
ListView, DeleteView, FormView, TemplateView, DayArchiveView,
|
||||
MonthArchiveView)
|
||||
from django.views.generic import (
|
||||
DetailView,
|
||||
CreateView,
|
||||
UpdateView,
|
||||
ListView,
|
||||
DeleteView,
|
||||
FormView,
|
||||
TemplateView,
|
||||
DayArchiveView,
|
||||
MonthArchiveView,
|
||||
)
|
||||
|
||||
from chrono.agendas.models import (Agenda, Event, MeetingType, TimePeriod,
|
||||
Booking, Desk, TimePeriodException,
|
||||
ICSError, AgendaImportError)
|
||||
from chrono.agendas.models import (
|
||||
Agenda,
|
||||
Event,
|
||||
MeetingType,
|
||||
TimePeriod,
|
||||
Booking,
|
||||
Desk,
|
||||
TimePeriodException,
|
||||
ICSError,
|
||||
AgendaImportError,
|
||||
)
|
||||
|
||||
from .forms import (AgendaAddForm, AgendaEditForm, NewEventForm, EventForm, NewMeetingTypeForm, MeetingTypeForm,
|
||||
TimePeriodForm, ImportEventsForm, NewDeskForm, DeskForm, TimePeriodExceptionForm,
|
||||
ExceptionsImportForm, AgendasImportForm, TimePeriodAddForm)
|
||||
from .forms import (
|
||||
AgendaAddForm,
|
||||
AgendaEditForm,
|
||||
NewEventForm,
|
||||
EventForm,
|
||||
NewMeetingTypeForm,
|
||||
MeetingTypeForm,
|
||||
TimePeriodForm,
|
||||
ImportEventsForm,
|
||||
NewDeskForm,
|
||||
DeskForm,
|
||||
TimePeriodExceptionForm,
|
||||
ExceptionsImportForm,
|
||||
AgendasImportForm,
|
||||
TimePeriodAddForm,
|
||||
)
|
||||
from .utils import import_site
|
||||
|
||||
|
||||
|
@ -53,6 +82,7 @@ class HomepageView(ListView):
|
|||
queryset = queryset.filter(Q(view_role_id__in=group_ids) | Q(edit_role_id__in=group_ids))
|
||||
return queryset
|
||||
|
||||
|
||||
homepage = HomepageView.as_view()
|
||||
|
||||
|
||||
|
@ -76,6 +106,7 @@ class AgendaAddView(CreateView):
|
|||
def get_success_url(self):
|
||||
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.object.id})
|
||||
|
||||
|
||||
agenda_add = AgendaAddView.as_view()
|
||||
|
||||
|
||||
|
@ -108,20 +139,21 @@ class AgendasImportView(FormView):
|
|||
if results.get('created') == 0:
|
||||
message1 = _('No agenda created.')
|
||||
else:
|
||||
message1 = ungettext('An agenda has been created.',
|
||||
'%(count)d agendas have been created.', results['created']) % {
|
||||
'count': results['created']}
|
||||
message1 = ungettext(
|
||||
'An agenda has been created.', '%(count)d agendas have been created.', results['created']
|
||||
) % {'count': results['created']}
|
||||
|
||||
if results.get('updated') == 0:
|
||||
message2 = _('No agenda updated.')
|
||||
else:
|
||||
message2 = ungettext('An agenda has been updated.',
|
||||
'%(count)d agendas have been updated.', results['updated']) % {
|
||||
'count': results['updated']}
|
||||
message2 = ungettext(
|
||||
'An agenda has been updated.', '%(count)d agendas have been updated.', results['updated']
|
||||
) % {'count': results['updated']}
|
||||
messages.info(self.request, u'%s %s' % (message1, message2))
|
||||
|
||||
return super(AgendasImportView, self).form_valid(form)
|
||||
|
||||
|
||||
agendas_import = AgendasImportView.as_view()
|
||||
|
||||
|
||||
|
@ -139,6 +171,7 @@ class AgendaEditView(UpdateView):
|
|||
def get_success_url(self):
|
||||
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.object.id})
|
||||
|
||||
|
||||
agenda_edit = AgendaEditView.as_view()
|
||||
|
||||
|
||||
|
@ -157,7 +190,8 @@ class AgendaDeleteView(DeleteView):
|
|||
context['cannot_delete'] = Booking.objects.filter(
|
||||
event__agenda=self.get_object(),
|
||||
event__start_datetime__gt=now(),
|
||||
cancellation_datetime__isnull=True).exists()
|
||||
cancellation_datetime__isnull=True,
|
||||
).exists()
|
||||
return context
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
|
@ -167,6 +201,7 @@ class AgendaDeleteView(DeleteView):
|
|||
raise PermissionDenied()
|
||||
return super(AgendaDeleteView, self).delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
agenda_delete = AgendaDeleteView.as_view()
|
||||
|
||||
|
||||
|
@ -184,15 +219,16 @@ class AgendaView(DetailView):
|
|||
if agenda.kind == 'meetings':
|
||||
# redirect to today view
|
||||
today = datetime.date.today()
|
||||
return HttpResponseRedirect(reverse('chrono-manager-agenda-day-view',
|
||||
kwargs={'pk': agenda.id,
|
||||
'year': today.year,
|
||||
'month': today.month,
|
||||
'day': today.day}))
|
||||
return HttpResponseRedirect(
|
||||
reverse(
|
||||
'chrono-manager-agenda-day-view',
|
||||
kwargs={'pk': agenda.id, 'year': today.year, 'month': today.month, 'day': today.day},
|
||||
)
|
||||
)
|
||||
|
||||
# redirect to settings
|
||||
return HttpResponseRedirect(
|
||||
reverse('chrono-manager-agenda-settings', kwargs={'pk': agenda.id}))
|
||||
return HttpResponseRedirect(reverse('chrono-manager-agenda-settings', kwargs={'pk': agenda.id}))
|
||||
|
||||
|
||||
agenda_view = AgendaView.as_view()
|
||||
|
||||
|
@ -214,20 +250,23 @@ class AgendaDateView(object):
|
|||
# specify 6am time to get the expected timezone on daylight saving time
|
||||
# days.
|
||||
try:
|
||||
self.date = make_aware(datetime.datetime.strptime(
|
||||
'%s-%s-%s 06:00' % (self.get_year(), self.get_month(), self.get_day()),
|
||||
'%Y-%m-%d %H:%M'))
|
||||
self.date = make_aware(
|
||||
datetime.datetime.strptime(
|
||||
'%s-%s-%s 06:00' % (self.get_year(), self.get_month(), self.get_day()), '%Y-%m-%d %H:%M'
|
||||
)
|
||||
)
|
||||
except ValueError: # day is out of range for month
|
||||
# redirect to last day of month
|
||||
date = datetime.date(int(self.get_year()), int(self.get_month()), 1)
|
||||
date += datetime.timedelta(days=40)
|
||||
date = date.replace(day=1)
|
||||
date -= datetime.timedelta(days=1)
|
||||
return HttpResponseRedirect(reverse('chrono-manager-agenda-day-view',
|
||||
kwargs={'pk': self.agenda.id,
|
||||
'year': date.year,
|
||||
'month': date.month,
|
||||
'day': date.day}))
|
||||
return HttpResponseRedirect(
|
||||
reverse(
|
||||
'chrono-manager-agenda-day-view',
|
||||
kwargs={'pk': self.agenda.id, 'year': date.year, 'month': date.month, 'day': date.day},
|
||||
)
|
||||
)
|
||||
return super(AgendaDateView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -253,7 +292,7 @@ class AgendaDateView(object):
|
|||
|
||||
def get_years(self):
|
||||
year = now().year
|
||||
return [str(x) for x in range(year-1, year+5)]
|
||||
return [str(x) for x in range(year - 1, year + 5)]
|
||||
|
||||
|
||||
class AgendaDayView(AgendaDateView, DayArchiveView):
|
||||
|
@ -261,25 +300,30 @@ class AgendaDayView(AgendaDateView, DayArchiveView):
|
|||
|
||||
def get_previous_day_url(self):
|
||||
previous_day = self.date.date() - datetime.timedelta(days=1)
|
||||
return reverse('chrono-manager-agenda-day-view',
|
||||
kwargs={'pk': self.agenda.id,
|
||||
return reverse(
|
||||
'chrono-manager-agenda-day-view',
|
||||
kwargs={
|
||||
'pk': self.agenda.id,
|
||||
'year': previous_day.year,
|
||||
'month': previous_day.month,
|
||||
'day': previous_day.day})
|
||||
'day': previous_day.day,
|
||||
},
|
||||
)
|
||||
|
||||
def get_next_day_url(self):
|
||||
next_day = self.date.date() + datetime.timedelta(days=1)
|
||||
return reverse('chrono-manager-agenda-day-view',
|
||||
kwargs={'pk': self.agenda.id,
|
||||
return reverse(
|
||||
'chrono-manager-agenda-day-view',
|
||||
kwargs={
|
||||
'pk': self.agenda.id,
|
||||
'year': next_day.year,
|
||||
'month': next_day.month,
|
||||
'day': next_day.day})
|
||||
'day': next_day.day,
|
||||
},
|
||||
)
|
||||
|
||||
def get_timetable_infos(self):
|
||||
timeperiods = TimePeriod.objects.filter(
|
||||
desk__agenda=self.agenda,
|
||||
weekday=self.date.weekday(),
|
||||
)
|
||||
timeperiods = TimePeriod.objects.filter(desk__agenda=self.agenda, weekday=self.date.weekday(),)
|
||||
if not timeperiods:
|
||||
return
|
||||
|
||||
|
@ -307,15 +351,22 @@ class AgendaDayView(AgendaDateView, DayArchiveView):
|
|||
# use first row to include opening hours
|
||||
info['opening_hours'] = opening_hours = []
|
||||
for opening_hour in desk.get_opening_hours(current_date.date()):
|
||||
opening_hours.append({
|
||||
opening_hours.append(
|
||||
{
|
||||
'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
|
||||
finish_datetime = current_date + interval
|
||||
for event in [x for x in self.object_list if x.desk_id == desk.id and
|
||||
x.start_datetime >= current_date and x.start_datetime < finish_datetime]:
|
||||
for event in [
|
||||
x
|
||||
for x in self.object_list
|
||||
if x.desk_id == desk.id
|
||||
and x.start_datetime >= current_date
|
||||
and x.start_datetime < finish_datetime
|
||||
]:
|
||||
# don't consider cancelled bookings
|
||||
for booking in [x for x in event.booking_set.all() if not x.cancellation_datetime]:
|
||||
booking.css_top = int(100 * event.start_datetime.minute / 60)
|
||||
|
@ -326,6 +377,7 @@ class AgendaDayView(AgendaDateView, DayArchiveView):
|
|||
current_date += interval
|
||||
first = False
|
||||
|
||||
|
||||
agenda_day_view = AgendaDayView.as_view()
|
||||
|
||||
|
||||
|
@ -334,17 +386,17 @@ class AgendaMonthView(AgendaDateView, MonthArchiveView):
|
|||
|
||||
def get_previous_month_url(self):
|
||||
previous_month = self.get_previous_month(self.date.date())
|
||||
return reverse('chrono-manager-agenda-month-view',
|
||||
kwargs={'pk': self.agenda.id,
|
||||
'year': previous_month.year,
|
||||
'month': previous_month.month})
|
||||
return reverse(
|
||||
'chrono-manager-agenda-month-view',
|
||||
kwargs={'pk': self.agenda.id, 'year': previous_month.year, 'month': previous_month.month},
|
||||
)
|
||||
|
||||
def get_next_month_url(self):
|
||||
next_month = self.get_next_month(self.date.date())
|
||||
return reverse('chrono-manager-agenda-month-view',
|
||||
kwargs={'pk': self.agenda.id,
|
||||
'year': next_month.year,
|
||||
'month': next_month.month})
|
||||
return reverse(
|
||||
'chrono-manager-agenda-month-view',
|
||||
kwargs={'pk': self.agenda.id, 'year': next_month.year, 'month': next_month.month},
|
||||
)
|
||||
|
||||
def get_day(self):
|
||||
return '1'
|
||||
|
@ -362,11 +414,11 @@ class AgendaMonthView(AgendaDateView, MonthArchiveView):
|
|||
last_week_number = 53
|
||||
|
||||
for week_number in range(first_week_number, last_week_number + 1):
|
||||
yield self.get_week_timetable_infos(week_number-first_week_number, timeperiods)
|
||||
yield self.get_week_timetable_infos(week_number - first_week_number, timeperiods)
|
||||
|
||||
def get_week_timetable_infos(self, week_index, timeperiods):
|
||||
|
||||
date = self.date + datetime.timedelta(week_index*7)
|
||||
date = self.date + datetime.timedelta(week_index * 7)
|
||||
year, week_number, dow = date.isocalendar()
|
||||
start_date = date - datetime.timedelta(dow)
|
||||
|
||||
|
@ -382,16 +434,23 @@ class AgendaMonthView(AgendaDateView, MonthArchiveView):
|
|||
periods.append(period)
|
||||
period = period + interval
|
||||
|
||||
return {'days': [self.get_day_timetable_infos(start_date + datetime.timedelta(i), interval) for i in range(1, 8)],
|
||||
'periods': periods}
|
||||
return {
|
||||
'days': [
|
||||
self.get_day_timetable_infos(start_date + datetime.timedelta(i), interval)
|
||||
for i in range(1, 8)
|
||||
],
|
||||
'periods': periods,
|
||||
}
|
||||
|
||||
def get_day_timetable_infos(self, day, interval):
|
||||
day = make_aware(make_naive(day)) # give day correct timezone
|
||||
period = current_date = day.replace(hour=self.min_timeperiod.hour, minute=0)
|
||||
timetable = {'date': current_date,
|
||||
timetable = {
|
||||
'date': current_date,
|
||||
'today': day.date() == datetime.date.today(),
|
||||
'other_month': day.month != self.date.month,
|
||||
'infos': {'opening_hours': [], 'booked_slots': []}}
|
||||
'infos': {'opening_hours': [], 'booked_slots': []},
|
||||
}
|
||||
|
||||
desks = self.agenda.desk_set.all()
|
||||
desks_len = len(desks)
|
||||
|
@ -406,35 +465,42 @@ class AgendaMonthView(AgendaDateView, MonthArchiveView):
|
|||
period_end = period + interval
|
||||
for desk_index, desk in enumerate(desks):
|
||||
width = (98.0 / desks_len) - 1
|
||||
for event in [x for x in self.object_list if x.desk_id == desk.id and
|
||||
x.start_datetime >= period and x.start_datetime < period_end]:
|
||||
for event in [
|
||||
x
|
||||
for x in self.object_list
|
||||
if x.desk_id == desk.id and x.start_datetime >= period and x.start_datetime < period_end
|
||||
]:
|
||||
# don't consider cancelled bookings
|
||||
bookings = [x for x in event.booking_set.all() if not x.cancellation_datetime]
|
||||
if not bookings:
|
||||
continue
|
||||
booking = {'css_top': 100 * (event.start_datetime - current_date).seconds // 3600,
|
||||
booking = {
|
||||
'css_top': 100 * (event.start_datetime - current_date).seconds // 3600,
|
||||
'css_height': 100 * event.meeting_type.duration // 60,
|
||||
'css_width': width,
|
||||
'css_left': left,
|
||||
'desk': desk,
|
||||
'booking': bookings[0]
|
||||
'booking': bookings[0],
|
||||
}
|
||||
timetable['infos']['booked_slots'].append(booking)
|
||||
|
||||
# get desks opening hours on last period iteration
|
||||
if period == max_date:
|
||||
for hour in desk.get_opening_hours(current_date):
|
||||
timetable['infos']['opening_hours'].append({
|
||||
timetable['infos']['opening_hours'].append(
|
||||
{
|
||||
'css_top': 100 * (hour.begin - current_date).seconds // 3600,
|
||||
'css_height': 100 * (hour.end - hour.begin).seconds // 3600,
|
||||
'css_width': width,
|
||||
'css_left': left,
|
||||
})
|
||||
}
|
||||
)
|
||||
left += width + 1
|
||||
period += interval
|
||||
|
||||
return timetable
|
||||
|
||||
|
||||
agenda_monthly_view = AgendaMonthView.as_view()
|
||||
|
||||
|
||||
|
@ -550,6 +616,7 @@ class AgendaSettings(ManagedAgendaMixin, DetailView):
|
|||
context['user_can_manage'] = self.get_object().can_be_managed(self.request.user)
|
||||
return context
|
||||
|
||||
|
||||
agenda_settings = AgendaSettings.as_view()
|
||||
|
||||
|
||||
|
@ -561,6 +628,7 @@ class AgendaExport(ManagedAgendaMixin, DetailView):
|
|||
json.dump({'agendas': [self.get_object().export_json()]}, response, indent=2)
|
||||
return response
|
||||
|
||||
|
||||
agenda_export = AgendaExport.as_view()
|
||||
|
||||
|
||||
|
@ -569,6 +637,7 @@ class AgendaAddEventView(ManagedAgendaMixin, CreateView):
|
|||
model = Event
|
||||
form_class = NewEventForm
|
||||
|
||||
|
||||
agenda_add_event = AgendaAddEventView.as_view()
|
||||
|
||||
|
||||
|
@ -583,6 +652,7 @@ class AgendaImportEventsSampleView(TemplateView):
|
|||
context['some_future_date'] = some_future_date
|
||||
return context
|
||||
|
||||
|
||||
agenda_import_events_sample_csv = AgendaImportEventsSampleView.as_view()
|
||||
|
||||
|
||||
|
@ -600,14 +670,13 @@ class AgendaImportEventsView(ManagedAgendaMixin, FormView):
|
|||
if form.events:
|
||||
for event in form.events:
|
||||
event.agenda_id = self.kwargs['pk']
|
||||
if event.slug and Event.objects.filter(
|
||||
agenda_id=event.agenda_id,
|
||||
slug=event.slug).exists():
|
||||
if event.slug and Event.objects.filter(agenda_id=event.agenda_id, slug=event.slug).exists():
|
||||
raise ValidationError(_('Duplicated event identifier'))
|
||||
event.save()
|
||||
messages.info(self.request, _('%d events have been imported.') % len(form.events))
|
||||
return super(AgendaImportEventsView, self).form_valid(form)
|
||||
|
||||
|
||||
agenda_import_events = AgendaImportEventsView.as_view()
|
||||
|
||||
|
||||
|
@ -616,6 +685,7 @@ class EventEditView(ManagedAgendaSubobjectMixin, UpdateView):
|
|||
model = Event
|
||||
form_class = EventForm
|
||||
|
||||
|
||||
event_edit = EventEditView.as_view()
|
||||
|
||||
|
||||
|
@ -626,8 +696,9 @@ class EventDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super(EventDeleteView, self).get_context_data(**kwargs)
|
||||
context['cannot_delete'] = bool(
|
||||
self.object.booking_set.filter(cancellation_datetime__isnull=True).exists() and
|
||||
self.object.start_datetime > now())
|
||||
self.object.booking_set.filter(cancellation_datetime__isnull=True).exists()
|
||||
and self.object.start_datetime > now()
|
||||
)
|
||||
return context
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
|
@ -637,6 +708,7 @@ class EventDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
|
|||
raise PermissionDenied()
|
||||
return super(EventDeleteView, self).delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
event_delete = EventDeleteView.as_view()
|
||||
|
||||
|
||||
|
@ -645,13 +717,16 @@ class AgendaAddMeetingTypeView(ManagedAgendaMixin, CreateView):
|
|||
model = Event
|
||||
form_class = NewMeetingTypeForm
|
||||
|
||||
|
||||
agenda_add_meeting_type = AgendaAddMeetingTypeView.as_view()
|
||||
|
||||
|
||||
class MeetingTypeEditView(ManagedAgendaSubobjectMixin, UpdateView):
|
||||
template_name = 'chrono/manager_meeting_type_form.html'
|
||||
model = MeetingType
|
||||
form_class = MeetingTypeForm
|
||||
|
||||
|
||||
meeting_type_edit = MeetingTypeEditView.as_view()
|
||||
|
||||
|
||||
|
@ -659,6 +734,7 @@ class MeetingTypeDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
|
|||
template_name = 'chrono/manager_confirm_delete.html'
|
||||
model = MeetingType
|
||||
|
||||
|
||||
meeting_type_delete = MeetingTypeDeleteView.as_view()
|
||||
|
||||
|
||||
|
@ -672,10 +748,12 @@ class AgendaAddTimePeriodView(ManagedDeskMixin, FormView):
|
|||
weekday=weekday,
|
||||
start_time=form.cleaned_data['start_time'],
|
||||
end_time=form.cleaned_data['end_time'],
|
||||
desk=self.desk)
|
||||
desk=self.desk,
|
||||
)
|
||||
period.save()
|
||||
return super(AgendaAddTimePeriodView, self).form_valid(form)
|
||||
|
||||
|
||||
agenda_add_time_period = AgendaAddTimePeriodView.as_view()
|
||||
|
||||
|
||||
|
@ -684,6 +762,7 @@ class TimePeriodEditView(ManagedDeskSubobjectMixin, UpdateView):
|
|||
model = TimePeriod
|
||||
form_class = TimePeriodForm
|
||||
|
||||
|
||||
time_period_edit = TimePeriodEditView.as_view()
|
||||
|
||||
|
||||
|
@ -691,6 +770,7 @@ class TimePeriodDeleteView(ManagedDeskSubobjectMixin, DeleteView):
|
|||
template_name = 'chrono/manager_confirm_delete.html'
|
||||
model = TimePeriod
|
||||
|
||||
|
||||
time_period_delete = TimePeriodDeleteView.as_view()
|
||||
|
||||
|
||||
|
@ -719,9 +799,8 @@ class DeskDeleteView(ManagedAgendaSubobjectMixin, DeleteView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super(DeskDeleteView, self).get_context_data(**kwargs)
|
||||
context['cannot_delete'] = Booking.objects.filter(
|
||||
event__desk=self.get_object(),
|
||||
event__start_datetime__gt=now(),
|
||||
cancellation_datetime__isnull=True).exists()
|
||||
event__desk=self.get_object(), event__start_datetime__gt=now(), cancellation_datetime__isnull=True
|
||||
).exists()
|
||||
return context
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
|
@ -812,7 +891,9 @@ class DeskImportTimePeriodExceptionsView(ManagedAgendaSubobjectMixin, UpdateView
|
|||
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'])
|
||||
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:
|
||||
|
@ -821,21 +902,28 @@ class DeskImportTimePeriodExceptionsView(ManagedAgendaSubobjectMixin, UpdateView
|
|||
form.instance.timeperiod_exceptions_remote_url = form.cleaned_data['ics_url']
|
||||
form.instance.save()
|
||||
if exceptions is not None:
|
||||
message = ungettext('An exception has been imported.',
|
||||
'%(count)d exceptions have been imported.', exceptions)
|
||||
message = ungettext(
|
||||
'An exception has been imported.', '%(count)d exceptions have been imported.', exceptions
|
||||
)
|
||||
message = message % {'count': exceptions}
|
||||
messages.info(self.request, message)
|
||||
return super(DeskImportTimePeriodExceptionsView, self).form_valid(form)
|
||||
|
||||
|
||||
desk_import_time_period_exceptions = DeskImportTimePeriodExceptionsView.as_view()
|
||||
|
||||
|
||||
def menu_json(request):
|
||||
label = _('Agendas')
|
||||
json_str = json.dumps([{'label': force_text(label),
|
||||
json_str = json.dumps(
|
||||
[
|
||||
{
|
||||
'label': force_text(label),
|
||||
'slug': 'calendar',
|
||||
'url': request.build_absolute_uri(reverse('chrono-manager-homepage'))
|
||||
}])
|
||||
'url': request.build_absolute_uri(reverse('chrono-manager-homepage')),
|
||||
}
|
||||
]
|
||||
)
|
||||
content_type = 'application/json'
|
||||
for variable in ('jsonpCallback', 'callback'):
|
||||
if variable in request.GET:
|
||||
|
|
|
@ -39,7 +39,7 @@ DATE_FORMAT_PY_JS_MAPPING = {
|
|||
'%Y': 'yyyy',
|
||||
'%y': 'yy',
|
||||
'%p': 'P',
|
||||
'%S': 'ss'
|
||||
'%S': 'ss',
|
||||
}
|
||||
|
||||
DATE_FORMAT_TO_JS_REGEX = re.compile(r'(?<!\w)(' + '|'.join(DATE_FORMAT_PY_JS_MAPPING.keys()) + r')\b')
|
||||
|
@ -77,8 +77,7 @@ class PickerWidgetMixin(object):
|
|||
# with a default, and convert it to a Python data format for later string parsing
|
||||
date_format = self.options['format']
|
||||
self.format = DATE_FORMAT_TO_PYTHON_REGEX.sub(
|
||||
lambda x: DATE_FORMAT_JS_PY_MAPPING[x.group()],
|
||||
date_format
|
||||
lambda x: DATE_FORMAT_JS_PY_MAPPING[x.group()], date_format
|
||||
)
|
||||
|
||||
super(PickerWidgetMixin, self).__init__(attrs, format=self.format)
|
||||
|
@ -87,7 +86,7 @@ class PickerWidgetMixin(object):
|
|||
final_attrs = self.build_attrs(attrs)
|
||||
rendered_widget = super(PickerWidgetMixin, self).render(name, value, final_attrs, renderer=renderer)
|
||||
|
||||
#if not set, autoclose have to be true.
|
||||
# if not set, autoclose have to be true.
|
||||
self.options.setdefault('autoclose', True)
|
||||
|
||||
# Build javascript options out of python dictionary
|
||||
|
@ -100,12 +99,14 @@ class PickerWidgetMixin(object):
|
|||
# Use provided id or generate hex to avoid collisions in document
|
||||
id = final_attrs.get('id', uuid.uuid4().hex)
|
||||
|
||||
return mark_safe(BOOTSTRAP_INPUT_TEMPLATE % dict(
|
||||
return mark_safe(
|
||||
BOOTSTRAP_INPUT_TEMPLATE
|
||||
% dict(
|
||||
id=id,
|
||||
rendered_widget=rendered_widget,
|
||||
clear_button=CLEAR_BTN_TEMPLATE if self.options.get('clearBtn') else '',
|
||||
glyphicon=self.glyphicon,
|
||||
options=js_options
|
||||
options=js_options,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -136,6 +137,7 @@ class TimeWidget(TimeInput):
|
|||
input type and has a bit of a fallback mechanism with the presence
|
||||
of the pattern attribute in case a standard text input is used.
|
||||
"""
|
||||
|
||||
input_type = 'time'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
@ -149,13 +151,17 @@ class WeekdaysWidget(SelectMultiple):
|
|||
s = []
|
||||
value = value or []
|
||||
for choice_id, choice_label in self.choices:
|
||||
s.append('<li><label><input type="checkbox" '
|
||||
s.append(
|
||||
'<li><label><input type="checkbox" '
|
||||
' name="%(name)s-%(choice_id)s" %(checked)s>'
|
||||
'<span>%(choice_label)s</span></label></li>' % {
|
||||
'<span>%(choice_label)s</span></label></li>'
|
||||
% {
|
||||
'name': name,
|
||||
'checked': 'checked' if choice_id in value else '',
|
||||
'choice_id': choice_id,
|
||||
'choice_label': choice_label})
|
||||
'choice_label': choice_label,
|
||||
}
|
||||
)
|
||||
return mark_safe('<ul id="%(id)s">' % attrs + '\n'.join(s) + '</ul>')
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
|
|
|
@ -77,10 +77,7 @@ WSGI_APPLICATION = 'chrono.wsgi.application'
|
|||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
}
|
||||
'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),}
|
||||
}
|
||||
|
||||
# Internationalization
|
||||
|
@ -96,7 +93,7 @@ USE_L10N = True
|
|||
|
||||
USE_TZ = True
|
||||
|
||||
LOCALE_PATHS = (os.path.join(BASE_DIR, 'chrono', 'locale'), )
|
||||
LOCALE_PATHS = (os.path.join(BASE_DIR, 'chrono', 'locale'),)
|
||||
|
||||
FORMAT_MODULE_PATH = 'chrono.formats'
|
||||
|
||||
|
@ -104,8 +101,7 @@ FORMAT_MODULE_PATH = 'chrono.formats'
|
|||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [
|
||||
],
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
|
@ -165,7 +161,8 @@ MELLON_IDENTITY_PROVIDERS = []
|
|||
# (see http://docs.python-requests.org/en/master/user/advanced/#proxies)
|
||||
REQUESTS_PROXIES = None
|
||||
|
||||
local_settings_file = os.environ.get('CHRONO_SETTINGS_FILE',
|
||||
os.path.join(os.path.dirname(__file__), 'local_settings.py'))
|
||||
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):
|
||||
exec(open(local_settings_file).read())
|
||||
|
|
|
@ -28,8 +28,7 @@ from .manager.urls import urlpatterns as chrono_manager_urls
|
|||
|
||||
urlpatterns = [
|
||||
url(r'^$', homepage, name='home'),
|
||||
url(r'^manage/', decorated_includes(manager_required,
|
||||
include(chrono_manager_urls))),
|
||||
url(r'^manage/', decorated_includes(manager_required, include(chrono_manager_urls))),
|
||||
url(r'^api/', include(chrono_api_urls)),
|
||||
url(r'^logout/$', LogoutView.as_view(), name='auth_logout'),
|
||||
url(r'^login/$', LoginView.as_view(), name='auth_login'),
|
||||
|
|
|
@ -37,6 +37,7 @@ class DecoratedURLPattern(URLPattern):
|
|||
result.func = self._decorate_with(result.func)
|
||||
return result
|
||||
|
||||
|
||||
def decorated_includes(func, includes, *args, **kwargs):
|
||||
urlconf_module, app_name, namespace = includes
|
||||
|
||||
|
@ -59,6 +60,7 @@ def manager_required(function=None, login_url=None):
|
|||
raise PermissionDenied()
|
||||
# As the last resort, show the login form
|
||||
return False
|
||||
|
||||
actual_decorator = user_passes_test(check_manager, login_url=login_url)
|
||||
if function:
|
||||
return actual_decorator(function)
|
||||
|
|
|
@ -35,8 +35,9 @@ class LoginView(auth_views.LoginView):
|
|||
if any(get_idps()):
|
||||
if not 'next' in request.GET:
|
||||
return HttpResponseRedirect(resolve_url('mellon_login'))
|
||||
return HttpResponseRedirect(resolve_url('mellon_login') + '?next='
|
||||
+ quote(request.GET.get('next')))
|
||||
return HttpResponseRedirect(
|
||||
resolve_url('mellon_login') + '?next=' + quote(request.GET.get('next'))
|
||||
)
|
||||
return super(LoginView, self).get(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,9 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
|
|||
"""
|
||||
|
||||
import os
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chrono.settings")
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = False
|
||||
|
||||
#ADMINS = (
|
||||
# ADMINS = (
|
||||
# # ('User 1', 'watchdog@example.net'),
|
||||
# # ('User 2', 'janitor@example.net'),
|
||||
#)
|
||||
# )
|
||||
|
||||
# ALLOWED_HOSTS must be correct in production!
|
||||
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||
|
|
37
setup.py
37
setup.py
|
@ -15,6 +15,7 @@ from distutils.errors import CompileError
|
|||
from distutils.spawn import find_executable
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
class eo_sdist(sdist):
|
||||
def run(self):
|
||||
if os.path.exists('VERSION'):
|
||||
|
@ -27,6 +28,7 @@ class eo_sdist(sdist):
|
|||
if os.path.exists('VERSION'):
|
||||
os.remove('VERSION')
|
||||
|
||||
|
||||
def get_version():
|
||||
'''Use the VERSION, if absent generates a version with git describe, if not
|
||||
tag exists, take 0.0- and add the length of the commit log.
|
||||
|
@ -35,8 +37,11 @@ def get_version():
|
|||
with open('VERSION', 'r') as v:
|
||||
return v.read()
|
||||
if os.path.exists('.git'):
|
||||
p = subprocess.Popen(['git','describe','--dirty=.dirty','--match=v*'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
p = subprocess.Popen(
|
||||
['git', 'describe', '--dirty=.dirty', '--match=v*'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
result = p.communicate()[0]
|
||||
if p.returncode == 0:
|
||||
result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v
|
||||
|
@ -47,11 +52,10 @@ def get_version():
|
|||
version = result
|
||||
return version
|
||||
else:
|
||||
return '0.0.post%s' % len(
|
||||
subprocess.check_output(
|
||||
['git', 'rev-list', 'HEAD']).splitlines())
|
||||
return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines())
|
||||
return '0.0'
|
||||
|
||||
|
||||
def data_tree(destdir, sourcedir):
|
||||
extensions = ['.css', '.png', '.jpeg', '.jpg', '.gif', '.xml', '.html', '.js']
|
||||
r = []
|
||||
|
@ -60,6 +64,7 @@ def data_tree(destdir, sourcedir):
|
|||
r.append((root.replace(sourcedir, destdir, 1), l))
|
||||
return r
|
||||
|
||||
|
||||
class compile_translations(Command):
|
||||
description = 'compile message catalogs to MO files via django compilemessages'
|
||||
user_options = []
|
||||
|
@ -74,6 +79,7 @@ class compile_translations(Command):
|
|||
curdir = os.getcwd()
|
||||
try:
|
||||
from django.core.management import call_command
|
||||
|
||||
for path, dirs, files in os.walk('chrono'):
|
||||
if 'locale' not in dirs:
|
||||
continue
|
||||
|
@ -102,7 +108,9 @@ class compile_scss(Command):
|
|||
if sass_bin:
|
||||
break
|
||||
if not sass_bin:
|
||||
raise CompileError('A sass compiler is required but none was found. See sass-lang.com for choices.')
|
||||
raise CompileError(
|
||||
'A sass compiler is required but none was found. See sass-lang.com for choices.'
|
||||
)
|
||||
|
||||
for package in self.distribution.packages:
|
||||
for package_path in __import__(package).__path__:
|
||||
|
@ -112,13 +120,17 @@ class compile_scss(Command):
|
|||
continue
|
||||
if filename.startswith('_'):
|
||||
continue
|
||||
subprocess.check_call([sass_bin, '%s/%s' % (path, filename),
|
||||
'%s/%s' % (path, filename.replace('.scss', '.css'))])
|
||||
subprocess.check_call(
|
||||
[
|
||||
sass_bin,
|
||||
'%s/%s' % (path, filename),
|
||||
'%s/%s' % (path, filename.replace('.scss', '.css')),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class build(_build):
|
||||
sub_commands = [('compile_translations', None),
|
||||
('compile_scss', None) ] + _build.sub_commands
|
||||
sub_commands = [('compile_translations', None), ('compile_scss', None)] + _build.sub_commands
|
||||
|
||||
|
||||
class install_lib(_install_lib):
|
||||
|
@ -148,13 +160,14 @@ setup(
|
|||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 3',
|
||||
],
|
||||
install_requires=['django>=1.11, <2.3',
|
||||
install_requires=[
|
||||
'django>=1.11, <2.3',
|
||||
'gadjo',
|
||||
'djangorestframework>=3.4',
|
||||
'django-jsonfield >= 0.9.3',
|
||||
'vobject',
|
||||
'python-dateutil',
|
||||
'requests'
|
||||
'requests',
|
||||
],
|
||||
zip_safe=False,
|
||||
cmdclass={
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.contrib.auth.models import User
|
|||
|
||||
import django_webtest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app(request):
|
||||
wtm = django_webtest.WebTestMixin()
|
||||
|
|
|
@ -10,8 +10,16 @@ from django.contrib.auth.models import Group
|
|||
from django.core.management import call_command
|
||||
from django.core.management.base import CommandError
|
||||
|
||||
from chrono.agendas.models import (Agenda, Event, Booking, MeetingType,
|
||||
Desk, TimePeriod, TimePeriodException, ICSError)
|
||||
from chrono.agendas.models import (
|
||||
Agenda,
|
||||
Event,
|
||||
Booking,
|
||||
MeetingType,
|
||||
Desk,
|
||||
TimePeriod,
|
||||
TimePeriodException,
|
||||
ICSError,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -154,6 +162,7 @@ def test_event_manager():
|
|||
booking.save()
|
||||
assert Event.objects.all()[0].booked_places == 0
|
||||
|
||||
|
||||
def test_event_bookable_period():
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.save()
|
||||
|
@ -179,6 +188,7 @@ def test_event_bookable_period():
|
|||
event.save()
|
||||
assert event.in_bookable_period() is False
|
||||
|
||||
|
||||
def test_meeting_type_slugs():
|
||||
agenda1 = Agenda(label=u'Foo bar')
|
||||
agenda1.save()
|
||||
|
@ -197,6 +207,7 @@ def test_meeting_type_slugs():
|
|||
meeting_type3.save()
|
||||
assert meeting_type3.slug == 'baz'
|
||||
|
||||
|
||||
def test_timeperiodexception_creation_from_ics():
|
||||
agenda = Agenda(label=u'Test 1 agenda')
|
||||
agenda.save()
|
||||
|
@ -206,6 +217,7 @@ def test_timeperiodexception_creation_from_ics():
|
|||
assert exceptions_count == 2
|
||||
assert TimePeriodException.objects.filter(desk=desk).count() == 2
|
||||
|
||||
|
||||
def test_timeperiodexception_creation_from_ics_without_startdt():
|
||||
agenda = Agenda(label=u'Test 2 agenda')
|
||||
agenda.save()
|
||||
|
@ -222,6 +234,7 @@ def test_timeperiodexception_creation_from_ics_without_startdt():
|
|||
exceptions_count = desk.create_timeperiod_exceptions_from_ics(ics_sample)
|
||||
assert 'Event "Event 1" has no start date.' == str(e.value)
|
||||
|
||||
|
||||
def test_timeperiodexception_creation_from_ics_without_enddt():
|
||||
agenda = Agenda(label=u'Test 3 agenda')
|
||||
agenda.save()
|
||||
|
@ -248,14 +261,17 @@ def test_timeperiodexception_creation_from_ics_with_recurrences():
|
|||
desk.save()
|
||||
assert desk.create_timeperiod_exceptions_from_ics(ICS_SAMPLE_WITH_RECURRENT_EVENT) == 3
|
||||
assert TimePeriodException.objects.filter(desk=desk).count() == 3
|
||||
assert set(TimePeriodException.objects.values_list('start_datetime', flat=True)) == set([
|
||||
make_aware(datetime.datetime(2018, 1, 1)), make_aware(datetime.datetime(2019, 1, 1))])
|
||||
assert set(TimePeriodException.objects.values_list('start_datetime', flat=True)) == set(
|
||||
[make_aware(datetime.datetime(2018, 1, 1)), make_aware(datetime.datetime(2019, 1, 1))]
|
||||
)
|
||||
assert desk.create_timeperiod_exceptions_from_ics(ICS_SAMPLE_WITH_RECURRENT_EVENT) == 0
|
||||
# verify occurences are cleaned when count changed
|
||||
assert desk.create_timeperiod_exceptions_from_ics(ICS_SAMPLE_WITH_RECURRENT_EVENT_2) == 0
|
||||
assert TimePeriodException.objects.filter(desk=desk).count() == 2
|
||||
assert set(TimePeriodException.objects.values_list('start_datetime', flat=True)) == set([
|
||||
make_aware(datetime.datetime(2018, 1, 1)), make_aware(datetime.datetime(2018, 1, 2))])
|
||||
assert set(TimePeriodException.objects.values_list('start_datetime', flat=True)) == set(
|
||||
[make_aware(datetime.datetime(2018, 1, 1)), make_aware(datetime.datetime(2018, 1, 2))]
|
||||
)
|
||||
|
||||
|
||||
def test_timeexception_creation_from_ics_with_dates():
|
||||
agenda = Agenda(label=u'Test 5 agenda')
|
||||
|
@ -275,6 +291,7 @@ def test_timeexception_creation_from_ics_with_dates():
|
|||
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')
|
||||
agenda.save()
|
||||
|
@ -284,6 +301,7 @@ def test_timeexception_create_from_invalid_ics():
|
|||
exceptions_count = desk.create_timeperiod_exceptions_from_ics(INVALID_ICS_SAMPLE)
|
||||
assert str(e.value) == 'File format is invalid.'
|
||||
|
||||
|
||||
def test_timeexception_create_from_ics_with_no_events():
|
||||
agenda = Agenda(label=u'Test 7 agenda')
|
||||
agenda.save()
|
||||
|
@ -293,6 +311,7 @@ def test_timeexception_create_from_ics_with_no_events():
|
|||
exceptions_count = desk.create_timeperiod_exceptions_from_ics(ICS_SAMPLE_WITH_NO_EVENTS)
|
||||
assert str(e.value) == "The file doesn't contain any events."
|
||||
|
||||
|
||||
@mock.patch('chrono.agendas.models.requests.get')
|
||||
def test_timeperiodexception_creation_from_remote_ics(mocked_get):
|
||||
agenda = Agenda(label=u'Test 8 agenda')
|
||||
|
@ -316,6 +335,7 @@ def test_timeperiodexception_creation_from_remote_ics(mocked_get):
|
|||
assert exceptions_count == 0
|
||||
TimePeriodException.objects.filter(external_id='desk-%s:' % desk.id).count() == 0
|
||||
|
||||
|
||||
@mock.patch('chrono.agendas.models.requests.get')
|
||||
def test_timeperiodexception_creation_from_unreachable_remote_ics(mocked_get):
|
||||
agenda = Agenda(label=u'Test 9 agenda')
|
||||
|
@ -325,13 +345,16 @@ def test_timeperiodexception_creation_from_unreachable_remote_ics(mocked_get):
|
|||
mocked_response = mock.Mock()
|
||||
mocked_response.text = ICS_SAMPLE
|
||||
mocked_get.return_value = mocked_response
|
||||
|
||||
def mocked_requests_connection_error(*args, **kwargs):
|
||||
raise requests.ConnectionError('unreachable')
|
||||
|
||||
mocked_get.side_effect = mocked_requests_connection_error
|
||||
with pytest.raises(ICSError) as e:
|
||||
exceptions_count = desk.create_timeperiod_exceptions_from_remote_ics('http://example.com/sample.ics')
|
||||
assert str(e.value) == "Failed to retrieve remote calendar (http://example.com/sample.ics, unreachable)."
|
||||
|
||||
|
||||
@mock.patch('chrono.agendas.models.requests.get')
|
||||
def test_timeperiodexception_creation_from_forbidden_remote_ics(mocked_get):
|
||||
agenda = Agenda(label=u'Test 10 agenda')
|
||||
|
@ -341,35 +364,50 @@ def test_timeperiodexception_creation_from_forbidden_remote_ics(mocked_get):
|
|||
mocked_response = mock.Mock()
|
||||
mocked_response.status_code = 403
|
||||
mocked_get.return_value = mocked_response
|
||||
|
||||
def mocked_requests_http_forbidden_error(*args, **kwargs):
|
||||
raise requests.HTTPError(response=mocked_response)
|
||||
|
||||
mocked_get.side_effect = mocked_requests_http_forbidden_error
|
||||
|
||||
with pytest.raises(ICSError) as e:
|
||||
exceptions_count = desk.create_timeperiod_exceptions_from_remote_ics('http://example.com/sample.ics')
|
||||
assert str(e.value) == "Failed to retrieve remote calendar (http://example.com/sample.ics, HTTP error 403)."
|
||||
assert (
|
||||
str(e.value) == "Failed to retrieve remote calendar (http://example.com/sample.ics, HTTP error 403)."
|
||||
)
|
||||
|
||||
|
||||
@mock.patch('chrono.agendas.models.requests.get')
|
||||
def test_sync_desks_timeperiod_exceptions_from_ics(mocked_get, capsys):
|
||||
agenda = Agenda(label=u'Test 11 agenda')
|
||||
agenda.save()
|
||||
desk = Desk(label='Test 11 desk', agenda=agenda, timeperiod_exceptions_remote_url='http://example.com/sample.ics')
|
||||
desk = Desk(
|
||||
label='Test 11 desk', agenda=agenda, timeperiod_exceptions_remote_url='http://example.com/sample.ics'
|
||||
)
|
||||
desk.save()
|
||||
mocked_response = mock.Mock()
|
||||
mocked_response.status_code = 403
|
||||
mocked_get.return_value = mocked_response
|
||||
|
||||
def mocked_requests_http_forbidden_error(*args, **kwargs):
|
||||
raise requests.HTTPError(response=mocked_response)
|
||||
|
||||
mocked_get.side_effect = mocked_requests_http_forbidden_error
|
||||
call_command('sync_desks_timeperiod_exceptions')
|
||||
out, err = capsys.readouterr()
|
||||
assert err == 'unable to create timeperiod exceptions for "Test 11 desk": Failed to retrieve remote calendar (http://example.com/sample.ics, HTTP error 403).\n'
|
||||
assert (
|
||||
err
|
||||
== 'unable to create timeperiod exceptions for "Test 11 desk": Failed to retrieve remote calendar (http://example.com/sample.ics, HTTP error 403).\n'
|
||||
)
|
||||
|
||||
|
||||
@mock.patch('chrono.agendas.models.requests.get')
|
||||
def test_sync_desks_timeperiod_exceptions_from_changing_ics(mocked_get, caplog):
|
||||
agenda = Agenda(label=u'Test 11 agenda')
|
||||
agenda.save()
|
||||
desk = Desk(label='Test 11 desk', agenda=agenda, timeperiod_exceptions_remote_url='http:example.com/sample.ics')
|
||||
desk = Desk(
|
||||
label='Test 11 desk', agenda=agenda, timeperiod_exceptions_remote_url='http:example.com/sample.ics'
|
||||
)
|
||||
desk.save()
|
||||
mocked_response = mock.Mock()
|
||||
mocked_response.text = ICS_SAMPLE
|
||||
|
@ -397,6 +435,7 @@ END:VCALENDAR"""
|
|||
call_command('sync_desks_timeperiod_exceptions')
|
||||
assert not TimePeriodException.objects.filter(desk=desk).exists()
|
||||
|
||||
|
||||
def test_base_meeting_duration():
|
||||
agenda = Agenda(label='Meeting', kind='meetings')
|
||||
agenda.save()
|
||||
|
@ -427,14 +466,18 @@ def test_timeperiodexception_creation_from_ics_with_duration():
|
|||
exceptions_count = desk.create_timeperiod_exceptions_from_ics(ICS_SAMPLE_WITH_DURATION)
|
||||
assert exceptions_count == 2
|
||||
assert TimePeriodException.objects.filter(desk=desk).count() == 2
|
||||
assert set(TimePeriodException.objects.values_list('start_datetime', flat=True)) == set([
|
||||
assert set(TimePeriodException.objects.values_list('start_datetime', flat=True)) == set(
|
||||
[
|
||||
make_aware(datetime.datetime(2017, 8, 31, 19, 8, 0)),
|
||||
make_aware(datetime.datetime(2017, 8, 30, 20, 8, 0)),
|
||||
])
|
||||
assert set(TimePeriodException.objects.values_list('end_datetime', flat=True)) == set([
|
||||
]
|
||||
)
|
||||
assert set(TimePeriodException.objects.values_list('end_datetime', flat=True)) == set(
|
||||
[
|
||||
make_aware(datetime.datetime(2017, 8, 31, 22, 34, 0)),
|
||||
make_aware(datetime.datetime(2017, 9, 1, 0, 34, 0)),
|
||||
])
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2017-12-01')
|
||||
|
@ -447,8 +490,9 @@ def test_timeperiodexception_creation_from_ics_with_recurrences_in_the_past():
|
|||
desk.save()
|
||||
assert desk.create_timeperiod_exceptions_from_ics(ICS_SAMPLE_WITH_RECURRENT_EVENT_IN_THE_PAST) == 2
|
||||
assert TimePeriodException.objects.filter(desk=desk).count() == 2
|
||||
assert set(TimePeriodException.objects.values_list('start_datetime', flat=True)) == set([
|
||||
make_aware(datetime.datetime(2018, 1, 1)), make_aware(datetime.datetime(2019, 1, 1))])
|
||||
assert set(TimePeriodException.objects.values_list('start_datetime', flat=True)) == set(
|
||||
[make_aware(datetime.datetime(2018, 1, 1)), make_aware(datetime.datetime(2019, 1, 1))]
|
||||
)
|
||||
assert desk.create_timeperiod_exceptions_from_ics(ICS_SAMPLE_WITH_RECURRENT_EVENT_IN_THE_PAST) == 0
|
||||
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,13 +3,16 @@ import pytest
|
|||
from chrono.api.utils import Response
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data, expected', [
|
||||
@pytest.mark.parametrize(
|
||||
'data, expected',
|
||||
[
|
||||
(None, None),
|
||||
({}, {}),
|
||||
({'reason': 'foo'}, {'reason': 'foo'}),
|
||||
({'err_class': 'foo'}, {'err_class': 'foo', 'reason': 'foo'}),
|
||||
({'bar': 'foo'}, {'bar': 'foo'}),
|
||||
])
|
||||
],
|
||||
)
|
||||
def test_response_data(data, expected):
|
||||
resp = Response(data=data)
|
||||
assert resp.data == expected
|
||||
|
|
|
@ -52,23 +52,31 @@ def test_timeperiod_data_migrations():
|
|||
Event = old_apps.get_model(app, 'Event')
|
||||
agenda = Agenda.objects.create(label='foo', slug='foo', kind='meetings')
|
||||
agenda2 = Agenda.objects.create(label='bar', slug='bar', kind='events')
|
||||
TimePeriod.objects.create(agenda=agenda, weekday=1,
|
||||
start_time=datetime.time(8, 0),
|
||||
end_time=datetime.time(12, 0))
|
||||
TimePeriod.objects.create(agenda=agenda, weekday=2,
|
||||
start_time=datetime.time(8, 0),
|
||||
end_time=datetime.time(10, 0))
|
||||
TimePeriod.objects.create(agenda=agenda, weekday=3,
|
||||
start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(12, 0))
|
||||
meeting_type = MeetingType.objects.create(agenda=agenda, label='foo',
|
||||
slug='foo', duration=60)
|
||||
Event.objects.create(agenda=agenda, places=1, meeting_type=meeting_type,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 9, 30)))
|
||||
Event.objects.create(agenda=agenda, places=1, meeting_type=meeting_type,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)))
|
||||
Event.objects.create(agenda=agenda2, places=5,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)))
|
||||
TimePeriod.objects.create(
|
||||
agenda=agenda, weekday=1, start_time=datetime.time(8, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
agenda=agenda, weekday=2, start_time=datetime.time(8, 0), end_time=datetime.time(10, 0)
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
agenda=agenda, weekday=3, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
meeting_type = MeetingType.objects.create(agenda=agenda, label='foo', slug='foo', duration=60)
|
||||
Event.objects.create(
|
||||
agenda=agenda,
|
||||
places=1,
|
||||
meeting_type=meeting_type,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 9, 30)),
|
||||
)
|
||||
Event.objects.create(
|
||||
agenda=agenda,
|
||||
places=1,
|
||||
meeting_type=meeting_type,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)),
|
||||
)
|
||||
Event.objects.create(
|
||||
agenda=agenda2, places=5, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0))
|
||||
)
|
||||
executor.loader.build_graph()
|
||||
executor.migrate(migrate_to)
|
||||
new_apps = executor.loader.project_state(migrate_to).apps
|
||||
|
|
|
@ -16,8 +16,7 @@ from django.core.management import call_command, CommandError
|
|||
from django.utils.encoding import force_bytes
|
||||
from django.utils.timezone import make_aware
|
||||
|
||||
from chrono.agendas.models import (Agenda, Event, TimePeriod, Desk,
|
||||
TimePeriodException, AgendaImportError)
|
||||
from chrono.agendas.models import Agenda, Event, TimePeriod, Desk, TimePeriodException, AgendaImportError
|
||||
from chrono.manager.utils import import_site
|
||||
|
||||
from test_api import some_data, meetings_agenda, time_zone, mock_now
|
||||
|
@ -40,10 +39,7 @@ def test_import_export(app, some_data, meetings_agenda):
|
|||
desk = meetings_agenda.desk_set.first()
|
||||
tpx_start = make_aware(datetime.datetime(2017, 5, 22, 8, 0))
|
||||
tpx_end = make_aware(datetime.datetime(2017, 5, 22, 12, 30))
|
||||
TimePeriodException.objects.create(
|
||||
desk=desk,
|
||||
start_datetime=tpx_start,
|
||||
end_datetime=tpx_end)
|
||||
TimePeriodException.objects.create(desk=desk, start_datetime=tpx_start, end_datetime=tpx_end)
|
||||
output = get_output_of_command('export_site')
|
||||
assert len(json.loads(output)['agendas']) == 3
|
||||
import_site(data={}, clean=True)
|
||||
|
@ -77,15 +73,14 @@ def test_import_export(app, some_data, meetings_agenda):
|
|||
event.save()
|
||||
desk, _ = Desk.objects.get_or_create(agenda=agenda2, label='Desk A', slug='desk-a')
|
||||
timeperiod = TimePeriod(
|
||||
desk=desk,
|
||||
weekday=2,
|
||||
start_time=datetime.time(10, 0),
|
||||
end_time=datetime.time(11, 0))
|
||||
desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0)
|
||||
)
|
||||
timeperiod.save()
|
||||
exception = TimePeriodException(
|
||||
desk=desk,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 8, 0)),
|
||||
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)))
|
||||
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)),
|
||||
)
|
||||
exception.save()
|
||||
|
||||
import_site(json.loads(output), overwrite=True)
|
||||
|
@ -97,15 +92,14 @@ def test_import_export(app, some_data, meetings_agenda):
|
|||
event = Event(agenda=agenda1, start_datetime=make_aware(datetime.datetime.now()), places=10)
|
||||
event.save()
|
||||
timeperiod = TimePeriod(
|
||||
weekday=2,
|
||||
desk=desk,
|
||||
start_time=datetime.time(10, 0),
|
||||
end_time=datetime.time(11, 0))
|
||||
weekday=2, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0)
|
||||
)
|
||||
timeperiod.save()
|
||||
exception = TimePeriodException(
|
||||
desk=desk,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 8, 0)),
|
||||
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)))
|
||||
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 30)),
|
||||
)
|
||||
exception.save()
|
||||
import_site(json.loads(output), overwrite=False)
|
||||
assert Event.objects.filter(id=event.id).count() == 1
|
||||
|
|
|
@ -16,12 +16,12 @@ from webtest import Upload
|
|||
|
||||
from chrono.wsgi import application
|
||||
|
||||
from chrono.agendas.models import (Agenda, Event, Booking, MeetingType,
|
||||
TimePeriod, Desk, TimePeriodException)
|
||||
from chrono.agendas.models import Agenda, Event, Booking, MeetingType, TimePeriod, Desk, TimePeriodException
|
||||
from chrono.manager.utils import export_site
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def simple_user():
|
||||
try:
|
||||
|
@ -30,6 +30,7 @@ def simple_user():
|
|||
user = User.objects.create_user('user', password='user')
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def manager_user():
|
||||
try:
|
||||
|
@ -42,6 +43,7 @@ def manager_user():
|
|||
user.groups.set([group])
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def admin_user():
|
||||
try:
|
||||
|
@ -50,17 +52,20 @@ def admin_user():
|
|||
user = User.objects.create_superuser('admin', email=None, password='admin')
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def api_user():
|
||||
try:
|
||||
user = User.objects.get(username='api-user')
|
||||
except User.DoesNotExist:
|
||||
user = User.objects.create(username='john.doe',
|
||||
first_name=u'John', last_name=u'Doe', email='john.doe@example.net')
|
||||
user = User.objects.create(
|
||||
username='john.doe', first_name=u'John', last_name=u'Doe', email='john.doe@example.net'
|
||||
)
|
||||
user.set_password('password')
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
def login(app, username='admin', password='admin'):
|
||||
login_page = app.get('/login/')
|
||||
login_form = login_page.forms[0]
|
||||
|
@ -70,15 +75,18 @@ def login(app, username='admin', password='admin'):
|
|||
assert resp.status_int == 302
|
||||
return app
|
||||
|
||||
|
||||
def test_unlogged_access(app):
|
||||
# connect while not being logged in
|
||||
assert app.get('/manage/', status=302).location.endswith('/login/?next=/manage/')
|
||||
|
||||
|
||||
def test_simple_user_access(app, simple_user):
|
||||
# connect while being logged as a simple user, access should be forbidden
|
||||
app = login(app, username='user', password='user')
|
||||
assert app.get('/manage/', status=403)
|
||||
|
||||
|
||||
def test_manager_user_access(app, manager_user):
|
||||
# connect while being logged as a manager user, access should be granted if
|
||||
# there's at least an agenda that is viewable or editable.
|
||||
|
@ -99,20 +107,24 @@ def test_manager_user_access(app, manager_user):
|
|||
agenda.save()
|
||||
assert app.get('/manage/', status=200)
|
||||
|
||||
|
||||
def test_home_redirect(app):
|
||||
assert app.get('/', status=302).location.endswith('/manage/')
|
||||
|
||||
|
||||
def test_access(app, admin_user):
|
||||
app = login(app)
|
||||
resp = app.get('/manage/', status=200)
|
||||
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)
|
||||
app.get('/logout/')
|
||||
assert app.get('/manage/', status=302).location.endswith('/login/?next=/manage/')
|
||||
|
||||
|
||||
def test_menu_json(app, admin_user):
|
||||
app = login(app)
|
||||
resp = app.get('/manage/menu.json', status=200)
|
||||
|
@ -123,6 +135,7 @@ def test_menu_json(app, admin_user):
|
|||
assert resp2.text == 'Q(%s);' % resp.text
|
||||
assert resp2.content_type == 'application/javascript'
|
||||
|
||||
|
||||
def test_view_agendas_as_manager(app, manager_user):
|
||||
agenda = Agenda(label=u'Foo Bar')
|
||||
agenda.view_role = manager_user.groups.all()[0]
|
||||
|
@ -156,6 +169,7 @@ def test_view_agendas_as_manager(app, manager_user):
|
|||
# check it gives a 404 on unknown agendas
|
||||
resp = app.get('/manage/agendas/%s/settings' % '9999', status=404)
|
||||
|
||||
|
||||
def test_add_agenda(app, admin_user):
|
||||
app = login(app)
|
||||
resp = app.get('/manage/', status=200)
|
||||
|
@ -169,6 +183,7 @@ def test_add_agenda(app, admin_user):
|
|||
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
|
||||
# allowed.
|
||||
|
@ -179,6 +194,7 @@ def test_add_agenda_as_manager(app, manager_user):
|
|||
resp = app.get('/manage/', status=200)
|
||||
resp = app.get('/manage/agendas/add/', status=403)
|
||||
|
||||
|
||||
def test_options_agenda(app, admin_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.save()
|
||||
|
@ -194,6 +210,7 @@ def test_options_agenda(app, admin_user):
|
|||
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')
|
||||
agenda.view_role = manager_user.groups.all()[0]
|
||||
|
@ -227,6 +244,7 @@ def test_options_agenda_as_manager(app, manager_user):
|
|||
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')
|
||||
agenda.save()
|
||||
|
@ -239,11 +257,11 @@ def test_delete_agenda(app, admin_user):
|
|||
resp = resp.follow()
|
||||
assert not 'Foo bar' in resp.text
|
||||
|
||||
|
||||
def test_delete_busy_agenda(app, admin_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.save()
|
||||
event = Event(start_datetime=now() + datetime.timedelta(days=10),
|
||||
places=10, agenda=agenda)
|
||||
event = Event(start_datetime=now() + datetime.timedelta(days=10), places=10, agenda=agenda)
|
||||
event.save()
|
||||
|
||||
app = login(app)
|
||||
|
@ -272,6 +290,7 @@ def test_delete_busy_agenda(app, admin_user):
|
|||
booking.save()
|
||||
resp = resp.form.submit(status=403)
|
||||
|
||||
|
||||
def test_delete_agenda_as_manager(app, manager_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.edit_role = manager_user.groups.all()[0]
|
||||
|
@ -290,8 +309,7 @@ def test_delete_busy_desk(app, admin_user):
|
|||
desk_a = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
desk_b = Desk.objects.create(agenda=agenda, label='Desk B')
|
||||
|
||||
event = Event(start_datetime=now() + datetime.timedelta(days=10),
|
||||
places=10, agenda=agenda, desk=desk_a)
|
||||
event = Event(start_datetime=now() + datetime.timedelta(days=10), places=10, agenda=agenda, desk=desk_a)
|
||||
event.save()
|
||||
|
||||
app = login(app)
|
||||
|
@ -354,6 +372,7 @@ def test_add_event_on_missing_agenda(app, admin_user):
|
|||
app = login(app)
|
||||
app.get('/manage/agendas/%s/add-event' % '999', status=404)
|
||||
|
||||
|
||||
def test_add_event_as_manager(app, manager_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.view_role = manager_user.groups.all()[0]
|
||||
|
@ -378,12 +397,11 @@ def test_add_event_as_manager(app, manager_user):
|
|||
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')
|
||||
agenda.save()
|
||||
event = Event(
|
||||
start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)),
|
||||
places=20, agenda=agenda)
|
||||
event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=20, agenda=agenda)
|
||||
event.save()
|
||||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
|
||||
|
@ -397,16 +415,17 @@ def test_edit_event(app, admin_user):
|
|||
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)
|
||||
app.get('/manage/agendas/999/', status=404)
|
||||
|
||||
|
||||
def test_edit_event_as_manager(app, manager_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.view_role = manager_user.groups.all()[0]
|
||||
agenda.save()
|
||||
event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)),
|
||||
places=20, agenda=agenda)
|
||||
event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=20, agenda=agenda)
|
||||
event.save()
|
||||
app = login(app, username='manager', password='manager')
|
||||
resp = app.get('/manage/events/%s/' % event.id, status=403)
|
||||
|
@ -424,11 +443,11 @@ def test_edit_event_as_manager(app, manager_user):
|
|||
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')
|
||||
agenda.save()
|
||||
event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)),
|
||||
places=10, agenda=agenda)
|
||||
event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda)
|
||||
event.save()
|
||||
Booking(event=event).save()
|
||||
Booking(event=event).save()
|
||||
|
@ -437,11 +456,11 @@ def test_booked_places(app, admin_user):
|
|||
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')
|
||||
agenda.save()
|
||||
event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)),
|
||||
places=10, agenda=agenda)
|
||||
event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda)
|
||||
event.save()
|
||||
for i in range(2):
|
||||
Booking(event=event).save()
|
||||
|
@ -463,11 +482,11 @@ def test_event_classes(app, admin_user):
|
|||
assert 'full' in resp.text
|
||||
assert 'overbooking' in resp.text
|
||||
|
||||
|
||||
def test_delete_event(app, admin_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.save()
|
||||
event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)),
|
||||
places=10, agenda=agenda)
|
||||
event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda)
|
||||
event.save()
|
||||
|
||||
app = login(app)
|
||||
|
@ -478,11 +497,11 @@ def test_delete_event(app, admin_user):
|
|||
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
|
||||
assert Event.objects.count() == 0
|
||||
|
||||
|
||||
def test_delete_busy_event(app, admin_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.save()
|
||||
event = Event(start_datetime=now() + datetime.timedelta(days=10),
|
||||
places=10, agenda=agenda)
|
||||
event = Event(start_datetime=now() + datetime.timedelta(days=10), places=10, agenda=agenda)
|
||||
event.save()
|
||||
|
||||
app = login(app)
|
||||
|
@ -511,12 +530,12 @@ def test_delete_busy_event(app, admin_user):
|
|||
booking.save()
|
||||
resp = resp.form.submit(status=403)
|
||||
|
||||
|
||||
def test_delete_event_as_manager(app, manager_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.edit_role = manager_user.groups.all()[0]
|
||||
agenda.save()
|
||||
event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)),
|
||||
places=10, agenda=agenda)
|
||||
event = Event(start_datetime=make_aware(datetime.datetime(2016, 2, 15, 17, 0)), places=10, agenda=agenda)
|
||||
event.save()
|
||||
|
||||
app = login(app, username='manager', password='manager')
|
||||
|
@ -527,6 +546,7 @@ def test_delete_event_as_manager(app, manager_user):
|
|||
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
|
||||
assert Event.objects.count() == 0
|
||||
|
||||
|
||||
def test_import_events(app, admin_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.save()
|
||||
|
@ -590,8 +610,9 @@ 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',
|
||||
u'2016-09-16,18:00,10,5,éléphant'.encode('utf-8'), '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))
|
||||
|
@ -601,8 +622,9 @@ 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',
|
||||
u'2016-09-16,18:00,10,5,éléphant'.encode('iso-8859-15'), 'text/csv')
|
||||
resp.form['events_csv_file'] = Upload(
|
||||
't.csv', u'2016-09-16,18:00,10,5,éléphant'.encode('iso-8859-15'), '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))
|
||||
|
@ -612,8 +634,9 @@ 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',
|
||||
u'2016-09-16,18:00,10,5,éléphant'.encode('eucjp'), 'text/csv')
|
||||
resp.form['events_csv_file'] = Upload(
|
||||
't.csv', u'2016-09-16,18:00,10,5,éléphant'.encode('eucjp'), '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))
|
||||
|
@ -623,18 +646,23 @@ 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', 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.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()
|
||||
|
||||
resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200)
|
||||
resp.form['events_csv_file'] = Upload('t.csv', '"date"\t"time"\t"etc."\n'
|
||||
resp.form['events_csv_file'] = Upload(
|
||||
't.csv',
|
||||
'"date"\t"time"\t"etc."\n'
|
||||
'"2016-09-16"\t"18:00"\t"10"\t"5"\t"éléphant"\n'
|
||||
'"2016-09-19"\t"18:00"\t"10"'.encode('iso-8859-15'), 'text/csv')
|
||||
'"2016-09-19"\t"18:00"\t"10"'.encode('iso-8859-15'),
|
||||
'text/csv',
|
||||
)
|
||||
resp = resp.form.submit(status=302)
|
||||
assert Event.objects.count() == 2
|
||||
Event.objects.all().delete()
|
||||
|
@ -671,6 +699,7 @@ def test_add_meetings_agenda(app, admin_user):
|
|||
agenda = Agenda.objects.get(label='Foo bar')
|
||||
assert agenda.kind == 'meetings'
|
||||
|
||||
|
||||
def test_meetings_agenda_add_meeting_type(app, admin_user):
|
||||
agenda = Agenda(label=u'Foo bar', kind='meetings')
|
||||
agenda.save()
|
||||
|
@ -693,6 +722,7 @@ def test_meetings_agenda_add_meeting_type(app, admin_user):
|
|||
resp = resp.form.submit()
|
||||
assert MeetingType.objects.get(agenda=agenda).duration == 30
|
||||
|
||||
|
||||
def test_meetings_agenda_delete_meeting_type(app, admin_user):
|
||||
agenda = Agenda(label=u'Foo bar', kind='meetings')
|
||||
agenda.save()
|
||||
|
@ -709,6 +739,7 @@ def test_meetings_agenda_delete_meeting_type(app, admin_user):
|
|||
assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
|
||||
assert MeetingType.objects.count() == 0
|
||||
|
||||
|
||||
def test_meetings_agenda_add_time_period(app, admin_user):
|
||||
agenda = Agenda(label=u'Foo bar', kind='meetings')
|
||||
agenda.save()
|
||||
|
@ -771,6 +802,7 @@ def test_meetings_agenda_add_time_period(app, admin_user):
|
|||
resp = resp.form.submit()
|
||||
assert TimePeriod.objects.filter(desk=desk).count() == 4
|
||||
|
||||
|
||||
def test_meetings_agenda_delete_time_period(app, admin_user):
|
||||
agenda = Agenda(label=u'Foo bar', kind='meetings')
|
||||
agenda.save()
|
||||
|
@ -778,9 +810,9 @@ def test_meetings_agenda_delete_time_period(app, admin_user):
|
|||
MeetingType(agenda=agenda, label='Blah').save()
|
||||
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
time_period = TimePeriod(desk=desk, weekday=2,
|
||||
start_time=datetime.time(10, 0),
|
||||
end_time=datetime.time(18, 0))
|
||||
time_period = TimePeriod(
|
||||
desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
|
||||
)
|
||||
time_period.save()
|
||||
|
||||
app = login(app)
|
||||
|
@ -811,8 +843,9 @@ def test_meetings_agenda_add_time_period_as_manager(app, manager_user):
|
|||
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)
|
||||
time_period = TimePeriod(desk=desk, weekday=0, start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(12, 0))
|
||||
time_period = TimePeriod(
|
||||
desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
time_period.save()
|
||||
resp = app.get('/manage/agendas/%d/' % agenda.id)
|
||||
app.get('/manage/timeperiods/%d/edit' % time_period.id, status=403)
|
||||
|
@ -915,8 +948,12 @@ def test_meetings_agenda_add_time_period_exception(app, admin_user):
|
|||
resp = resp.form.submit().follow()
|
||||
assert TimePeriodException.objects.count() == 1
|
||||
time_period_exception = TimePeriodException.objects.first()
|
||||
assert localtime(time_period_exception.start_datetime).strftime(dt_format) == tomorrow.replace(hour=8).strftime(dt_format)
|
||||
assert localtime(time_period_exception.end_datetime).strftime(dt_format) == tomorrow.replace(hour=16).strftime(dt_format)
|
||||
assert localtime(time_period_exception.start_datetime).strftime(dt_format) == tomorrow.replace(
|
||||
hour=8
|
||||
).strftime(dt_format)
|
||||
assert localtime(time_period_exception.end_datetime).strftime(dt_format) == tomorrow.replace(
|
||||
hour=16
|
||||
).strftime(dt_format)
|
||||
# add an exception beyond 2 weeks and make sure it isn't listed
|
||||
resp = resp.click('Add a time period exception', index=1)
|
||||
future = tomorrow + datetime.timedelta(days=15)
|
||||
|
@ -927,7 +964,9 @@ def test_meetings_agenda_add_time_period_exception(app, admin_user):
|
|||
assert TimePeriodException.objects.count() == 2
|
||||
assert 'Exception 1' in resp.text
|
||||
assert 'Exception 2' not in resp.text
|
||||
resp = resp.click(href="/manage/time-period-exceptions/%d/exception-extract-list" % agenda.desk_set.first().pk)
|
||||
resp = resp.click(
|
||||
href="/manage/time-period-exceptions/%d/exception-extract-list" % agenda.desk_set.first().pk
|
||||
)
|
||||
assert 'Exception 1' in resp.text
|
||||
assert 'Exception 2' in resp.text
|
||||
|
||||
|
@ -936,10 +975,12 @@ def test_meetings_agenda_add_time_period_exception_when_booking_exists(app, admi
|
|||
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
MeetingType(agenda=agenda, label='Blah').save()
|
||||
TimePeriod.objects.create(weekday=1, desk=desk,
|
||||
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
|
||||
event = Event.objects.create(agenda=agenda, places=1, desk=desk,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30)))
|
||||
TimePeriod.objects.create(
|
||||
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
event = Event.objects.create(
|
||||
agenda=agenda, places=1, desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
|
||||
)
|
||||
Booking.objects.create(event=event)
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
|
||||
|
@ -957,8 +998,9 @@ def test_meetings_agenda_add_time_period_exception_when_booking_exists(app, admi
|
|||
|
||||
# check it's possible to add an exception on another desk
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk B')
|
||||
TimePeriod.objects.create(weekday=1, desk=desk,
|
||||
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
|
||||
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')
|
||||
resp = resp.click('Add a time period exception', href='desk/%s/' % desk.id)
|
||||
|
@ -967,16 +1009,20 @@ def test_meetings_agenda_add_time_period_exception_when_booking_exists(app, admi
|
|||
resp = resp.form.submit()
|
||||
assert TimePeriodException.objects.count() == 1
|
||||
|
||||
|
||||
def test_meetings_agenda_add_time_period_exception_when_cancelled_booking_exists(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
MeetingType(agenda=agenda, label='Blah').save()
|
||||
TimePeriod.objects.create(weekday=1, desk=desk,
|
||||
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
|
||||
event = Event.objects.create(agenda=agenda, places=1,
|
||||
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30)))
|
||||
Booking.objects.create(event=event,
|
||||
cancellation_datetime=make_aware(datetime.datetime(2017, 5, 20, 10, 30)))
|
||||
TimePeriod.objects.create(
|
||||
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
event = Event.objects.create(
|
||||
agenda=agenda, places=1, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
|
||||
)
|
||||
Booking.objects.create(
|
||||
event=event, cancellation_datetime=make_aware(datetime.datetime(2017, 5, 20, 10, 30))
|
||||
)
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
|
||||
resp = resp.click('Settings')
|
||||
|
@ -987,12 +1033,14 @@ def test_meetings_agenda_add_time_period_exception_when_cancelled_booking_exists
|
|||
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):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
MeetingType(agenda=agenda, label='Blah').save()
|
||||
TimePeriod.objects.create(weekday=1, desk=desk,
|
||||
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
|
||||
TimePeriod.objects.create(
|
||||
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
|
||||
resp = resp.click('Settings')
|
||||
|
@ -1007,8 +1055,9 @@ def test_meetings_agenda_delete_time_period_exception(app, admin_user):
|
|||
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
MeetingType(agenda=agenda, label='Blah').save()
|
||||
TimePeriod.objects.create(weekday=1, desk=desk,
|
||||
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
|
||||
TimePeriod.objects.create(
|
||||
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
|
||||
resp = resp.click('Settings')
|
||||
|
@ -1022,8 +1071,12 @@ def test_meetings_agenda_delete_time_period_exception(app, admin_user):
|
|||
resp = resp.form.submit().follow()
|
||||
assert TimePeriodException.objects.count() == 1
|
||||
time_period_exception = TimePeriodException.objects.first()
|
||||
assert localtime(time_period_exception.start_datetime).strftime(dt_format) == tomorrow.replace(hour=8).strftime(dt_format)
|
||||
assert localtime(time_period_exception.end_datetime).strftime(dt_format) == tomorrow.replace(hour=16).strftime(dt_format)
|
||||
assert localtime(time_period_exception.start_datetime).strftime(dt_format) == tomorrow.replace(
|
||||
hour=8
|
||||
).strftime(dt_format)
|
||||
assert localtime(time_period_exception.end_datetime).strftime(dt_format) == tomorrow.replace(
|
||||
hour=16
|
||||
).strftime(dt_format)
|
||||
resp = resp.click(href='/manage/time-period-exceptions/%d/edit' % time_period_exception.id)
|
||||
resp = resp.click('Delete')
|
||||
resp = resp.form.submit().follow()
|
||||
|
@ -1035,7 +1088,8 @@ def test_meetings_agenda_delete_time_period_exception(app, admin_user):
|
|||
label='Future Exception',
|
||||
desk=desk,
|
||||
start_datetime=now() + datetime.timedelta(days=1),
|
||||
end_datetime=now() + datetime.timedelta(days=2))
|
||||
end_datetime=now() + datetime.timedelta(days=2),
|
||||
)
|
||||
resp = app.get('/manage/time-period-exceptions/%d/exception-list' % desk.pk)
|
||||
resp = resp.click(href='/manage/time-period-exceptions/%d/delete' % time_period_exception.pk)
|
||||
resp = resp.form.submit(
|
||||
|
@ -1048,23 +1102,27 @@ def test_exception_list(app, admin_user):
|
|||
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
MeetingType(agenda=agenda, label='Blah').save()
|
||||
TimePeriod.objects.create(weekday=1, desk=desk,
|
||||
start_time=datetime.time(10, 0), end_time=datetime.time(12, 0))
|
||||
TimePeriod.objects.create(
|
||||
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
past_exception = TimePeriodException.objects.create(
|
||||
label='Past Exception',
|
||||
desk=desk,
|
||||
start_datetime=now() - datetime.timedelta(days=2),
|
||||
end_datetime=now() - datetime.timedelta(days=1))
|
||||
end_datetime=now() - datetime.timedelta(days=1),
|
||||
)
|
||||
current_exception = TimePeriodException.objects.create(
|
||||
label='Current Exception',
|
||||
desk=desk,
|
||||
start_datetime=now() - datetime.timedelta(days=1),
|
||||
end_datetime=now() + datetime.timedelta(days=1))
|
||||
end_datetime=now() + datetime.timedelta(days=1),
|
||||
)
|
||||
future_exception = TimePeriodException.objects.create(
|
||||
label='Future Exception',
|
||||
desk=desk,
|
||||
start_datetime=now() + datetime.timedelta(days=1),
|
||||
end_datetime=now() + datetime.timedelta(days=2))
|
||||
end_datetime=now() + datetime.timedelta(days=2),
|
||||
)
|
||||
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
|
||||
|
@ -1092,8 +1150,9 @@ def test_agenda_import_time_period_exception_from_ics(app, admin_user):
|
|||
resp = resp.click('Settings')
|
||||
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))
|
||||
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')
|
||||
|
@ -1150,9 +1209,9 @@ def test_agenda_import_time_period_exception_from_ics_recurrent(app, admin_user)
|
|||
agenda = Agenda.objects.create(label='Example', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='Test Desk')
|
||||
MeetingType(agenda=agenda, label='Foo').save()
|
||||
TimePeriod.objects.create(weekday=1, desk=desk,
|
||||
start_time=datetime.time(10, 0),
|
||||
end_time=datetime.time(12, 0))
|
||||
TimePeriod.objects.create(
|
||||
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
|
||||
resp = resp.click('Settings')
|
||||
|
@ -1182,8 +1241,9 @@ def test_agenda_import_time_period_exception_with_remote_ics(mocked_get, app, ad
|
|||
resp = resp.click('Settings')
|
||||
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))
|
||||
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')
|
||||
|
@ -1213,8 +1273,10 @@ END:VCALENDAR"""
|
|||
resp = resp.click('upload')
|
||||
resp.form['ics_url'] = ''
|
||||
resp = resp.form.submit(status=302)
|
||||
assert not TimePeriodException.objects.filter(desk=desk,
|
||||
external_id='desk-%s:random-event-id' % desk.id).exists()
|
||||
assert not TimePeriodException.objects.filter(
|
||||
desk=desk, external_id='desk-%s:random-event-id' % desk.id
|
||||
).exists()
|
||||
|
||||
|
||||
@mock.patch('chrono.agendas.models.requests.get')
|
||||
def test_agenda_import_time_period_exception_with_remote_ics_no_events(mocked_get, app, admin_user):
|
||||
|
@ -1226,8 +1288,9 @@ def test_agenda_import_time_period_exception_with_remote_ics_no_events(mocked_ge
|
|||
resp = resp.click('Settings')
|
||||
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))
|
||||
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')
|
||||
|
@ -1257,8 +1320,7 @@ END:VCALENDAR"""
|
|||
resp = resp.click('Settings')
|
||||
resp = resp.click('upload')
|
||||
resp = resp.form.submit(status=302)
|
||||
assert not TimePeriodException.objects.filter(desk=desk,
|
||||
external_id='random-event-id').exists()
|
||||
assert not TimePeriodException.objects.filter(desk=desk, external_id='random-event-id').exists()
|
||||
|
||||
|
||||
@mock.patch('chrono.agendas.models.requests.get')
|
||||
|
@ -1271,8 +1333,9 @@ def test_agenda_update_time_period_exception_from_remote_ics(mocked_get, app, ad
|
|||
resp = resp.click('Settings')
|
||||
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))
|
||||
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')
|
||||
|
@ -1316,8 +1379,11 @@ END:VCALENDAR"""
|
|||
resp = resp.form.submit(status=302)
|
||||
assert TimePeriodException.objects.filter(desk=desk).count() == 1
|
||||
|
||||
|
||||
@mock.patch('chrono.agendas.models.requests.get')
|
||||
def test_agenda_import_time_period_exception_from_remote_ics_with_connection_error(mocked_get, app, admin_user):
|
||||
def test_agenda_import_time_period_exception_from_remote_ics_with_connection_error(
|
||||
mocked_get, app, admin_user
|
||||
):
|
||||
agenda = Agenda.objects.create(label='New Example', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='New Desk')
|
||||
MeetingType(agenda=agenda, label='Bar').save()
|
||||
|
@ -1326,8 +1392,9 @@ def test_agenda_import_time_period_exception_from_remote_ics_with_connection_err
|
|||
resp = resp.click('Settings')
|
||||
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))
|
||||
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')
|
||||
|
@ -1338,12 +1405,15 @@ def test_agenda_import_time_period_exception_from_remote_ics_with_connection_err
|
|||
resp.form['ics_url'] = 'http://example.com/foo.ics'
|
||||
mocked_response = mock.Mock()
|
||||
mocked_get.return_value = mocked_response
|
||||
|
||||
def mocked_requests_connection_error(*args, **kwargs):
|
||||
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 (http://example.com/foo.ics, 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):
|
||||
agenda = Agenda.objects.create(label='New Example', kind='meetings')
|
||||
|
@ -1354,8 +1424,9 @@ def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_ge
|
|||
resp = resp.click('Settings')
|
||||
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))
|
||||
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')
|
||||
|
@ -1364,12 +1435,15 @@ def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_ge
|
|||
mocked_response = mock.Mock()
|
||||
mocked_response.status_code = 403
|
||||
mocked_get.return_value = mocked_response
|
||||
|
||||
def mocked_requests_http_forbidden_error(*args, **kwargs):
|
||||
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://example.com/foo.ics, 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):
|
||||
agenda = Agenda.objects.create(label='New Example', kind='meetings')
|
||||
|
@ -1379,8 +1453,9 @@ def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mock
|
|||
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
|
||||
resp = resp.click('Settings')
|
||||
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))
|
||||
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')
|
||||
|
@ -1388,12 +1463,15 @@ def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mock
|
|||
resp.form['ics_url'] = 'https://example.com/foo.ics'
|
||||
mocked_response = mock.Mock()
|
||||
mocked_get.return_value = mocked_response
|
||||
|
||||
def mocked_requests_http_ssl_error(*args, **kwargs):
|
||||
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 (https://example.com/foo.ics, 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')
|
||||
desk = Desk.objects.create(agenda=agenda, label='New Desk')
|
||||
|
@ -1409,9 +1487,9 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
|
|||
resp = resp.follow()
|
||||
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 = 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.text
|
||||
|
@ -1434,14 +1512,12 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
|
|||
booking_url = resp.json['data'][0]['api']['fillslot_url']
|
||||
booking_url2 = resp.json['data'][2]['api']['fillslot_url']
|
||||
resp = app.post(booking_url)
|
||||
resp = app.post_json(booking_url2,
|
||||
params={'label': 'foo', 'user': 'bar', 'url': 'http://baz/'})
|
||||
resp = app.post_json(booking_url2, params={'label': 'foo', 'user': 'bar', 'url': 'http://baz/'})
|
||||
|
||||
app.reset()
|
||||
login(app)
|
||||
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))
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
|
||||
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
|
||||
|
@ -1450,8 +1526,7 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
|
|||
# (and visually this will give more room for events)
|
||||
meetingtype = MeetingType(agenda=agenda, label='Baz', duration=15)
|
||||
meetingtype.save()
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
|
||||
agenda.id, date.year, date.month, date.day))
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
|
||||
assert resp.text.count('div class="booking') == 2
|
||||
assert 'hourspan-4' in resp.text # table CSS class
|
||||
|
||||
|
@ -1464,29 +1539,26 @@ def test_agenda_day_view(app, admin_user, manager_user, api_user):
|
|||
|
||||
app.reset()
|
||||
login(app)
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
|
||||
agenda.id, date.year, date.month, date.day))
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day))
|
||||
assert resp.text.count('div class="booking') == 1
|
||||
|
||||
# wrong type
|
||||
agenda2 = Agenda(label=u'Foo bar')
|
||||
agenda2.save()
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
|
||||
agenda2.id, date.year, date.month, date.day), status=404)
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda2.id, date.year, date.month, date.day), status=404)
|
||||
|
||||
# not enough permissions
|
||||
agenda2.view_role = manager_user.groups.all()[0]
|
||||
agenda2.save()
|
||||
app.reset()
|
||||
app = login(app, username='manager', password='manager')
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
|
||||
agenda.id, date.year, date.month, date.day), status=403)
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=403)
|
||||
|
||||
# just enough permissions
|
||||
agenda.view_role = manager_user.groups.all()[0]
|
||||
agenda.save()
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (
|
||||
agenda.id, date.year, date.month, date.day), status=200)
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, date.year, date.month, date.day), status=200)
|
||||
|
||||
|
||||
def test_agenda_day_view_late_meeting(app, admin_user, manager_user, api_user):
|
||||
agenda = Agenda.objects.create(label='New Example', kind='meetings')
|
||||
|
@ -1498,9 +1570,9 @@ def test_agenda_day_view_late_meeting(app, admin_user, manager_user, api_user):
|
|||
|
||||
today = datetime.date.today()
|
||||
|
||||
timeperiod = TimePeriod(desk=desk, weekday=today.weekday(),
|
||||
start_time=datetime.time(10, 0),
|
||||
end_time=datetime.time(23, 30))
|
||||
timeperiod = TimePeriod(
|
||||
desk=desk, weekday=today.weekday(), start_time=datetime.time(10, 0), end_time=datetime.time(23, 30)
|
||||
)
|
||||
timeperiod.save()
|
||||
|
||||
login(app)
|
||||
|
@ -1508,6 +1580,7 @@ def test_agenda_day_view_late_meeting(app, admin_user, manager_user, api_user):
|
|||
assert resp.text.count('<tr') == 15
|
||||
assert '<th class="hour">11 p.m.</th>' in resp.text
|
||||
|
||||
|
||||
def test_agenda_invalid_day_view(app, admin_user, manager_user, api_user):
|
||||
agenda = Agenda.objects.create(label='New Example', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='New Desk')
|
||||
|
@ -1520,6 +1593,7 @@ def test_agenda_invalid_day_view(app, admin_user, manager_user, api_user):
|
|||
resp = app.get('/manage/agendas/%s/%d/%d/%d/' % (agenda.id, 2018, 11, 31), status=302)
|
||||
assert resp.location.endswith('2018/11/30/')
|
||||
|
||||
|
||||
def test_agenda_month_view(app, admin_user, manager_user, api_user):
|
||||
agenda = Agenda.objects.create(label='Passeports', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
|
@ -1540,15 +1614,15 @@ def test_agenda_month_view(app, admin_user, manager_user, api_user):
|
|||
|
||||
today = datetime.date(2018, 11, 10) # fixed day
|
||||
timeperiod_weekday = today.weekday()
|
||||
timeperiod = TimePeriod(desk=desk, weekday=timeperiod_weekday,
|
||||
start_time=datetime.time(10, 0),
|
||||
end_time=datetime.time(18, 0))
|
||||
timeperiod = TimePeriod(
|
||||
desk=desk, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
|
||||
)
|
||||
timeperiod.save()
|
||||
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
|
||||
assert not 'No opening hours this month.' in resp.text
|
||||
assert not '<div class="booking' in resp.text
|
||||
first_month_day = today.replace(day=1)
|
||||
last_month_day = today.replace(day=1, month=today.month+1) - datetime.timedelta(days=1)
|
||||
last_month_day = today.replace(day=1, month=today.month + 1) - datetime.timedelta(days=1)
|
||||
start_week_number = first_month_day.isocalendar()[1]
|
||||
end_week_number = last_month_day.isocalendar()[1]
|
||||
weeks_number = end_week_number - start_week_number + 1
|
||||
|
@ -1564,19 +1638,17 @@ def test_agenda_month_view(app, admin_user, manager_user, api_user):
|
|||
booking_url = resp.json['data'][0]['api']['fillslot_url']
|
||||
booking_url2 = resp.json['data'][2]['api']['fillslot_url']
|
||||
booking = app.post(booking_url)
|
||||
booking_2 = app.post_json(booking_url2,
|
||||
params={'label': 'foo', 'user': 'bar', 'url': 'http://baz/'})
|
||||
booking_2 = app.post_json(booking_url2, params={'label': 'foo', 'user': 'bar', 'url': 'http://baz/'})
|
||||
|
||||
app.reset()
|
||||
login(app)
|
||||
date = Booking.objects.all()[0].event.start_datetime
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/' % (
|
||||
agenda.id, date.year, date.month))
|
||||
resp = app.get('/manage/agendas/%s/%d/%d/' % (agenda.id, date.year, date.month))
|
||||
assert resp.text.count('<div class="booking" style="left:1.0%;height:33.0%;') == 2 # booking cells
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk B')
|
||||
timeperiod = TimePeriod(desk=desk, weekday=timeperiod_weekday,
|
||||
start_time=datetime.time(10, 0),
|
||||
end_time=datetime.time(18, 0))
|
||||
timeperiod = TimePeriod(
|
||||
desk=desk, weekday=timeperiod_weekday, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
|
||||
)
|
||||
timeperiod.save()
|
||||
|
||||
app.reset()
|
||||
|
@ -1613,6 +1685,7 @@ def test_agenda_month_view(app, admin_user, manager_user, api_user):
|
|||
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, today.year, today.month))
|
||||
assert not 'No opening hours this month.' in resp.text
|
||||
|
||||
|
||||
def test_agenda_month_view_dst_change(app, admin_user, manager_user, api_user):
|
||||
agenda = Agenda.objects.create(label='Passeports', kind='meetings')
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
|
@ -1621,9 +1694,9 @@ def test_agenda_month_view_dst_change(app, admin_user, manager_user, api_user):
|
|||
meetingtype.save()
|
||||
|
||||
for weekday in range(0, 7): # open all mornings
|
||||
TimePeriod(desk=desk, weekday=weekday,
|
||||
start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(12, 0)).save()
|
||||
TimePeriod(
|
||||
desk=desk, weekday=weekday, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
|
||||
).save()
|
||||
|
||||
login(app)
|
||||
for date in ('2019-10-01', '2019-10-31'):
|
||||
|
@ -1658,6 +1731,7 @@ def test_import_agenda_as_manager(app, manager_user):
|
|||
resp = app.get('/manage/', status=200)
|
||||
resp = app.get('/manage/agendas/import/', status=403)
|
||||
|
||||
|
||||
def test_import_agenda(app, admin_user):
|
||||
agenda = Agenda(label=u'Foo bar')
|
||||
agenda.save()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from chrono.manager.widgets import DateTimeWidget, TimeWidget
|
||||
|
||||
|
||||
def test_widgets_init():
|
||||
DateTimeWidget()
|
||||
TimeWidget()
|
||||
|
|
|
@ -6,6 +6,7 @@ from chrono.wsgi import application
|
|||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_sso(app):
|
||||
with override_settings(MELLON_IDENTITY_PROVIDERS=[{'METADATA': 'x', 'ENTITY_ID': 'x'}]):
|
||||
resp = app.get('/login/')
|
||||
|
@ -14,6 +15,7 @@ def test_sso(app):
|
|||
resp = app.get('/login/?next=/manage/')
|
||||
assert resp.location.endswith('/accounts/mellon/login/?next=/manage/')
|
||||
|
||||
|
||||
def test_slo(app):
|
||||
with override_settings(MELLON_IDENTITY_PROVIDERS=[{'METADATA': 'x', 'ENTITY_ID': 'x'}]):
|
||||
resp = app.get('/logout/')
|
||||
|
|
|
@ -18,13 +18,14 @@ def test_timeperiod_time_slots():
|
|||
desk = Desk.objects.create(label='Desk 1', agenda=agenda)
|
||||
meeting_type = MeetingType(duration=60, agenda=agenda)
|
||||
meeting_type.save()
|
||||
timeperiod = TimePeriod(desk=desk, weekday=0,
|
||||
start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(12, 0))
|
||||
timeperiod = TimePeriod(
|
||||
desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
events = timeperiod.get_time_slots(
|
||||
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
|
||||
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
|
||||
meeting_type=meeting_type)
|
||||
meeting_type=meeting_type,
|
||||
)
|
||||
events = list(sorted(events, key=lambda x: x.start_datetime))
|
||||
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 5, 9, 0)
|
||||
assert events[1].start_datetime.timetuple()[:5] == (2016, 9, 5, 10, 0)
|
||||
|
@ -35,52 +36,56 @@ def test_timeperiod_time_slots():
|
|||
assert len(events) == 12
|
||||
|
||||
# another start before the timeperiod
|
||||
timeperiod = TimePeriod(desk=desk, weekday=1,
|
||||
start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(12, 0))
|
||||
timeperiod = TimePeriod(
|
||||
desk=desk, weekday=1, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
events = timeperiod.get_time_slots(
|
||||
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
|
||||
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
|
||||
meeting_type=meeting_type)
|
||||
meeting_type=meeting_type,
|
||||
)
|
||||
events = list(sorted(events, key=lambda x: x.start_datetime))
|
||||
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 6, 9, 0)
|
||||
assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 27, 11, 0)
|
||||
assert len(events) == 12
|
||||
|
||||
# a start on the day of the timeperiod
|
||||
timeperiod = TimePeriod(desk=desk, weekday=3,
|
||||
start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(12, 0))
|
||||
timeperiod = TimePeriod(
|
||||
desk=desk, weekday=3, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
events = timeperiod.get_time_slots(
|
||||
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
|
||||
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
|
||||
meeting_type=meeting_type)
|
||||
meeting_type=meeting_type,
|
||||
)
|
||||
events = list(sorted(events, key=lambda x: x.start_datetime))
|
||||
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 1, 9, 0)
|
||||
assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 29, 11, 0)
|
||||
assert len(events) == 15
|
||||
|
||||
# a start after the day of the timeperiod
|
||||
timeperiod = TimePeriod(desk=desk, weekday=4,
|
||||
start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(12, 0))
|
||||
timeperiod = TimePeriod(
|
||||
desk=desk, weekday=4, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
events = timeperiod.get_time_slots(
|
||||
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
|
||||
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
|
||||
meeting_type=meeting_type)
|
||||
meeting_type=meeting_type,
|
||||
)
|
||||
events = list(sorted(events, key=lambda x: x.start_datetime))
|
||||
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 2, 9, 0)
|
||||
assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 30, 11, 0)
|
||||
assert len(events) == 15
|
||||
|
||||
# another start after the day of the timeperiod
|
||||
timeperiod = TimePeriod(desk=desk, weekday=5,
|
||||
start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(12, 0))
|
||||
timeperiod = TimePeriod(
|
||||
desk=desk, weekday=5, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
events = timeperiod.get_time_slots(
|
||||
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
|
||||
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
|
||||
meeting_type=meeting_type)
|
||||
meeting_type=meeting_type,
|
||||
)
|
||||
events = list(sorted(events, key=lambda x: x.start_datetime))
|
||||
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 3, 9, 0)
|
||||
assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 24, 11, 0)
|
||||
|
@ -89,13 +94,14 @@ def test_timeperiod_time_slots():
|
|||
# shorter duration -> double the events
|
||||
meeting_type.duration = 30
|
||||
meeting_type.save()
|
||||
timeperiod = TimePeriod(desk=desk, weekday=5,
|
||||
start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(12, 0))
|
||||
timeperiod = TimePeriod(
|
||||
desk=desk, weekday=5, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
events = timeperiod.get_time_slots(
|
||||
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
|
||||
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
|
||||
meeting_type=meeting_type)
|
||||
meeting_type=meeting_type,
|
||||
)
|
||||
events = list(sorted(events, key=lambda x: x.start_datetime))
|
||||
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 3, 9, 0)
|
||||
assert events[-1].start_datetime.timetuple()[:5] == (2016, 9, 24, 11, 30)
|
||||
|
@ -105,28 +111,48 @@ def test_timeperiod_time_slots():
|
|||
@override_settings(LANGUAGE_CODE='fr-fr')
|
||||
def test_time_period_exception_as_string():
|
||||
# single day
|
||||
assert force_text(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'
|
||||
end_datetime=make_aware(datetime.datetime(2018, 1, 19)),
|
||||
)
|
||||
)
|
||||
== u'18 jan. 2018'
|
||||
)
|
||||
|
||||
# multiple full days
|
||||
assert force_text(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'
|
||||
end_datetime=make_aware(datetime.datetime(2018, 1, 20)),
|
||||
)
|
||||
)
|
||||
== u'18 jan. 2018 → 20 jan. 2018'
|
||||
)
|
||||
|
||||
# a few hours in a day
|
||||
assert force_text(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'
|
||||
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 force_text(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'
|
||||
end_datetime=make_aware(datetime.datetime(2018, 1, 20, 12, 0)),
|
||||
)
|
||||
)
|
||||
== u'18 jan. 2018 10:00 → 20 jan. 2018 12:00'
|
||||
)
|
||||
|
||||
|
||||
def test_desk_opening_hours():
|
||||
|
@ -139,18 +165,14 @@ def test_desk_opening_hours():
|
|||
assert len(hours) == 0
|
||||
|
||||
# morning
|
||||
TimePeriod(desk=desk, weekday=0,
|
||||
start_time=datetime.time(9, 0),
|
||||
end_time=datetime.time(12, 0)).save()
|
||||
TimePeriod(desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)).save()
|
||||
hours = list(desk.get_opening_hours(datetime.date(2018, 1, 22)))
|
||||
assert len(hours) == 1
|
||||
assert hours[0].begin.time() == datetime.time(9, 0)
|
||||
assert hours[0].end.time() == datetime.time(12, 0)
|
||||
|
||||
# and afternoon
|
||||
TimePeriod(desk=desk, weekday=0,
|
||||
start_time=datetime.time(14, 0),
|
||||
end_time=datetime.time(17, 0)).save()
|
||||
TimePeriod(desk=desk, weekday=0, start_time=datetime.time(14, 0), end_time=datetime.time(17, 0)).save()
|
||||
hours = list(desk.get_opening_hours(datetime.date(2018, 1, 22)))
|
||||
assert len(hours) == 2
|
||||
assert hours[0].begin.time() == datetime.time(9, 0)
|
||||
|
@ -163,7 +185,8 @@ def test_desk_opening_hours():
|
|||
exception = TimePeriodException(
|
||||
desk=desk,
|
||||
start_datetime=make_aware(datetime.datetime(2018, 1, 22)),
|
||||
end_datetime=make_aware(datetime.datetime(2018, 1, 23)))
|
||||
end_datetime=make_aware(datetime.datetime(2018, 1, 23)),
|
||||
)
|
||||
exception.save()
|
||||
|
||||
hours = list(desk.get_opening_hours(datetime.date(2018, 1, 22)))
|
||||
|
@ -200,13 +223,14 @@ def test_timeperiod_midnight_overlap_time_slots():
|
|||
desk = Desk.objects.create(label='Desk 1', agenda=agenda)
|
||||
meeting_type = MeetingType(duration=120, agenda=agenda)
|
||||
meeting_type.save()
|
||||
timeperiod = TimePeriod(desk=desk, weekday=0,
|
||||
start_time=datetime.time(21, 0),
|
||||
end_time=datetime.time(23, 0))
|
||||
timeperiod = TimePeriod(
|
||||
desk=desk, weekday=0, start_time=datetime.time(21, 0), end_time=datetime.time(23, 0)
|
||||
)
|
||||
events = timeperiod.get_time_slots(
|
||||
min_datetime=make_aware(datetime.datetime(2016, 9, 1)),
|
||||
max_datetime=make_aware(datetime.datetime(2016, 10, 1)),
|
||||
meeting_type=meeting_type)
|
||||
meeting_type=meeting_type,
|
||||
)
|
||||
events = list(sorted(events, key=lambda x: x.start_datetime))
|
||||
assert events[0].start_datetime.timetuple()[:5] == (2016, 9, 5, 21, 0)
|
||||
assert events[1].start_datetime.timetuple()[:5] == (2016, 9, 12, 21, 0)
|
||||
|
|
Loading…
Reference in New Issue