diff --git a/chrono/agendas/migrations/0001_initial.py b/chrono/agendas/migrations/0001_initial.py index f6a07cb9..61c3b9e6 100644 --- a/chrono/agendas/migrations/0001_initial.py +++ b/chrono/agendas/migrations/0001_initial.py @@ -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,), ), ] diff --git a/chrono/agendas/migrations/0002_event.py b/chrono/agendas/migrations/0002_event.py index 368b50ae..62e332a2 100644 --- a/chrono/agendas/migrations/0002_event.py +++ b/chrono/agendas/migrations/0002_event.py @@ -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,), ), ] diff --git a/chrono/agendas/migrations/0003_booking.py b/chrono/agendas/migrations/0003_booking.py index c890aba4..d7a1996e 100644 --- a/chrono/agendas/migrations/0003_booking.py +++ b/chrono/agendas/migrations/0003_booking.py @@ -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,), ), ] diff --git a/chrono/agendas/migrations/0005_event_label.py b/chrono/agendas/migrations/0005_event_label.py index 56dfe498..b6f80b41 100644 --- a/chrono/agendas/migrations/0005_event_label.py +++ b/chrono/agendas/migrations/0005_event_label.py @@ -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', + ), ), ] diff --git a/chrono/agendas/migrations/0006_auto_20160707_1357.py b/chrono/agendas/migrations/0006_auto_20160707_1357.py index 87597e77..f2f23582 100644 --- a/chrono/agendas/migrations/0006_auto_20160707_1357.py +++ b/chrono/agendas/migrations/0006_auto_20160707_1357.py @@ -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', diff --git a/chrono/agendas/migrations/0007_auto_20160722_1135.py b/chrono/agendas/migrations/0007_auto_20160722_1135.py index eadaf23a..5329a801 100644 --- a/chrono/agendas/migrations/0007_auto_20160722_1135.py +++ b/chrono/agendas/migrations/0007_auto_20160722_1135.py @@ -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'), ), ] diff --git a/chrono/agendas/migrations/0008_auto_20160910_1319.py b/chrono/agendas/migrations/0008_auto_20160910_1319.py index 3ea66b31..0aa24e7a 100644 --- a/chrono/agendas/migrations/0008_auto_20160910_1319.py +++ b/chrono/agendas/migrations/0008_auto_20160910_1319.py @@ -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', diff --git a/chrono/agendas/migrations/0010_auto_20160918_1250.py b/chrono/agendas/migrations/0010_auto_20160918_1250.py index d92977e0..449bdba9 100644 --- a/chrono/agendas/migrations/0010_auto_20160918_1250.py +++ b/chrono/agendas/migrations/0010_auto_20160918_1250.py @@ -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, + ), ), ] diff --git a/chrono/agendas/migrations/0013_auto_20161028_1603.py b/chrono/agendas/migrations/0013_auto_20161028_1603.py index 456af267..8586a24e 100644 --- a/chrono/agendas/migrations/0013_auto_20161028_1603.py +++ b/chrono/agendas/migrations/0013_auto_20161028_1603.py @@ -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'), ), ] diff --git a/chrono/agendas/migrations/0014_booking_primary_booking.py b/chrono/agendas/migrations/0014_booking_primary_booking.py index 4940bb60..23fd6e26 100644 --- a/chrono/agendas/migrations/0014_booking_primary_booking.py +++ b/chrono/agendas/migrations/0014_booking_primary_booking.py @@ -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, + ), ), ] diff --git a/chrono/agendas/migrations/0015_auto_20170628_1137.py b/chrono/agendas/migrations/0015_auto_20170628_1137.py index fe3c4101..01ba0e7d 100644 --- a/chrono/agendas/migrations/0015_auto_20170628_1137.py +++ b/chrono/agendas/migrations/0015_auto_20170628_1137.py @@ -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', diff --git a/chrono/agendas/migrations/0016_desk.py b/chrono/agendas/migrations/0016_desk.py index 914011b7..6e5fbb4a 100644 --- a/chrono/agendas/migrations/0016_desk.py +++ b/chrono/agendas/migrations/0016_desk.py @@ -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'],}, ), ] diff --git a/chrono/agendas/migrations/0017_timeperiod_desk.py b/chrono/agendas/migrations/0017_timeperiod_desk.py index ecb47a05..77caf7a0 100644 --- a/chrono/agendas/migrations/0017_timeperiod_desk.py +++ b/chrono/agendas/migrations/0017_timeperiod_desk.py @@ -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',), ] diff --git a/chrono/agendas/migrations/0018_event_desk.py b/chrono/agendas/migrations/0018_event_desk.py index db08d39a..86a9d77e 100644 --- a/chrono/agendas/migrations/0018_event_desk.py +++ b/chrono/agendas/migrations/0018_event_desk.py @@ -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), ] diff --git a/chrono/agendas/migrations/0019_timeperiodexception.py b/chrono/agendas/migrations/0019_timeperiodexception.py index 55e8d476..578d8f17 100644 --- a/chrono/agendas/migrations/0019_timeperiodexception.py +++ b/chrono/agendas/migrations/0019_timeperiodexception.py @@ -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'],}, ), ] diff --git a/chrono/agendas/migrations/0020_auto_20171102_1021.py b/chrono/agendas/migrations/0020_auto_20171102_1021.py index 40bb940f..49d7aa68 100644 --- a/chrono/agendas/migrations/0020_auto_20171102_1021.py +++ b/chrono/agendas/migrations/0020_auto_20171102_1021.py @@ -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, ), ] diff --git a/chrono/agendas/migrations/0021_auto_20171126_1330.py b/chrono/agendas/migrations/0021_auto_20171126_1330.py index 47b5576f..439204e2 100644 --- a/chrono/agendas/migrations/0021_auto_20171126_1330.py +++ b/chrono/agendas/migrations/0021_auto_20171126_1330.py @@ -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), ), ] diff --git a/chrono/agendas/migrations/0022_auto_20171202_1828.py b/chrono/agendas/migrations/0022_auto_20171202_1828.py index d9878801..0773d489 100644 --- a/chrono/agendas/migrations/0022_auto_20171202_1828.py +++ b/chrono/agendas/migrations/0022_auto_20171202_1828.py @@ -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', diff --git a/chrono/agendas/migrations/0023_auto_20171202_1835.py b/chrono/agendas/migrations/0023_auto_20171202_1835.py index f8d7a023..b567be46 100644 --- a/chrono/agendas/migrations/0023_auto_20171202_1835.py +++ b/chrono/agendas/migrations/0023_auto_20171202_1835.py @@ -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 + ), ), ] diff --git a/chrono/agendas/migrations/0024_auto_20180426_1127.py b/chrono/agendas/migrations/0024_auto_20180426_1127.py index 2ac2a19b..56389436 100644 --- a/chrono/agendas/migrations/0024_auto_20180426_1127.py +++ b/chrono/agendas/migrations/0024_auto_20180426_1127.py @@ -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', diff --git a/chrono/agendas/migrations/0025_auto_20181206_1252.py b/chrono/agendas/migrations/0025_auto_20181206_1252.py index 637f6973..e733eca7 100644 --- a/chrono/agendas/migrations/0025_auto_20181206_1252.py +++ b/chrono/agendas/migrations/0025_auto_20181206_1252.py @@ -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', + ), ), ] diff --git a/chrono/agendas/migrations/0027_event_description.py b/chrono/agendas/migrations/0027_event_description.py index 6a1169ff..8c44789e 100644 --- a/chrono/agendas/migrations/0027_event_description.py +++ b/chrono/agendas/migrations/0027_event_description.py @@ -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' + ), ), ] diff --git a/chrono/agendas/migrations/0028_event_slug.py b/chrono/agendas/migrations/0028_event_slug.py index 912b2299..d9bc4c22 100644 --- a/chrono/agendas/migrations/0028_event_slug.py +++ b/chrono/agendas/migrations/0028_event_slug.py @@ -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, ), ] diff --git a/chrono/agendas/migrations/0029_auto_20191106_1320.py b/chrono/agendas/migrations/0029_auto_20191106_1320.py index 78a6971f..010c1593 100644 --- a/chrono/agendas/migrations/0029_auto_20191106_1320.py +++ b/chrono/agendas/migrations/0029_auto_20191106_1320.py @@ -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' + ), ), ] diff --git a/chrono/agendas/migrations/0030_auto_20191107_1200.py b/chrono/agendas/migrations/0030_auto_20191107_1200.py index 8e551bdc..3348250c 100644 --- a/chrono/agendas/migrations/0030_auto_20191107_1200.py +++ b/chrono/agendas/migrations/0030_auto_20191107_1200.py @@ -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']) diff --git a/chrono/agendas/migrations/0031_auto_20191107_1225.py b/chrono/agendas/migrations/0031_auto_20191107_1225.py index e0c900c1..e957216f 100644 --- a/chrono/agendas/migrations/0031_auto_20191107_1225.py +++ b/chrono/agendas/migrations/0031_auto_20191107_1225.py @@ -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')]),), ] diff --git a/chrono/agendas/migrations/0032_auto_20191127_0919.py b/chrono/agendas/migrations/0032_auto_20191127_0919.py index ab555033..0e0e1122 100644 --- a/chrono/agendas/migrations/0032_auto_20191127_0919.py +++ b/chrono/agendas/migrations/0032_auto_20191127_0919.py @@ -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') - ), - migrations.AlterUniqueTogether( - name='event', - unique_together=set([('agenda', 'slug')]), + field=models.SlugField( + default=None, null=True, blank=True, max_length=160, verbose_name='Identifier' + ), ), + migrations.AlterUniqueTogether(name='event', unique_together=set([('agenda', 'slug')]),), ] diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index c5c8ae17..3374a50e 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -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]) @@ -198,9 +211,10 @@ class TimePeriod(models.Model): def __str__(self): return u'%s / %s → %s' % ( - force_text(WEEKDAYS[self.weekday]), - date_format(self.start_time, 'TIME_FORMAT'), - date_format(self.end_time, 'TIME_FORMAT')) + force_text(WEEKDAYS[self.weekday]), + date_format(self.start_time, 'TIME_FORMAT'), + date_format(self.end_time, 'TIME_FORMAT'), + ) @property def weekday_str(self): @@ -212,9 +226,9 @@ class TimePeriod(models.Model): def export_json(self): return { - 'weekday': self.weekday, - 'start_time': self.start_time.strftime('%H:%M'), - 'end_time': self.end_time.strftime('%H:%M'), + 'weekday': self.weekday, + 'start_time': self.start_time.strftime('%H:%M'), + 'end_time': self.end_time.strftime('%H:%M'), } def get_time_slots(self, min_datetime, max_datetime, meeting_type): @@ -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) @@ -273,9 +292,9 @@ class MeetingType(models.Model): def export_json(self): return { - 'label': self.label, - 'slug': self.slug, - 'duration': self.duration, + 'label': self.label, + 'slug': self.slug, + 'duration': self.duration, } @@ -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, - '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()] - } + 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()], + } 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) @@ -634,18 +682,21 @@ class TimePeriodException(models.Model): exc_repr = u'%s' % date_format(localtime(self.start_datetime), 'SHORT_DATE_FORMAT') 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.start_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.start_datetime), 'SHORT_DATETIME_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.start_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 diff --git a/chrono/api/urls.py b/chrono/api/urls.py index 79ee179f..d9c51217 100644 --- a/chrono/api/urls.py +++ b/chrono/api/urls.py @@ -21,29 +21,32 @@ from . import views urlpatterns = [ url(r'agenda/$', views.agendas), url(r'agenda/(?P[\w-]+)/$', views.agenda_detail), - url(r'agenda/(?P[\w-]+)/datetimes/$', views.datetimes, name='api-agenda-datetimes'), - url(r'agenda/(?P[\w-]+)/fillslot/(?P[\w:-]+)/$', - views.fillslot, name='api-fillslot'), - url(r'agenda/(?P[\w-]+)/fillslots/$', - views.fillslots, name='api-agenda-fillslots'), - url(r'agenda/(?P[\w-]+)/status/(?P[\w-]+)/$', views.slot_status, - name='api-event-status'), - - url(r'agenda/meetings/(?P[\w-]+)/datetimes/$', - views.meeting_datetimes, name='api-agenda-meeting-datetimes-legacy'), - url(r'agenda/(?P[\w-]+)/meetings/$', - views.meeting_list, name='api-agenda-meetings'), - url(r'agenda/(?P[\w-]+)/desks/$', - views.agenda_desk_list, name='api-agenda-desks'), - url(r'agenda/(?P[\w-]+)/meetings/(?P[\w-]+)/datetimes/$', - views.meeting_datetimes, name='api-agenda-meeting-datetimes'), - + url( + r'agenda/(?P[\w-]+)/fillslot/(?P[\w:-]+)/$', + views.fillslot, + name='api-fillslot', + ), + url(r'agenda/(?P[\w-]+)/fillslots/$', views.fillslots, name='api-agenda-fillslots'), + url( + r'agenda/(?P[\w-]+)/status/(?P[\w-]+)/$', + views.slot_status, + name='api-event-status', + ), + url( + r'agenda/meetings/(?P[\w-]+)/datetimes/$', + views.meeting_datetimes, + name='api-agenda-meeting-datetimes-legacy', + ), + url(r'agenda/(?P[\w-]+)/meetings/$', views.meeting_list, name='api-agenda-meetings'), + url(r'agenda/(?P[\w-]+)/desks/$', views.agenda_desk_list, name='api-agenda-desks'), + url( + r'agenda/(?P[\w-]+)/meetings/(?P[\w-]+)/datetimes/$', + views.meeting_datetimes, + name='api-agenda-meeting-datetimes', + ), url(r'booking/(?P\w+)/$', views.booking), - url(r'booking/(?P\w+)/cancel/$', views.cancel_booking, - name='api-cancel-booking'), - url(r'booking/(?P\w+)/accept/$', views.accept_booking, - name='api-accept-booking'), - url(r'booking/(?P\w+)/ics/$', views.booking_ics, - name='api-booking-ics'), + url(r'booking/(?P\w+)/cancel/$', views.cancel_booking, name='api-cancel-booking'), + url(r'booking/(?P\w+)/accept/$', views.accept_booking, name='api-accept-booking'), + url(r'booking/(?P\w+)/ics/$', views.booking_ics, name='api-booking-ics'), ] diff --git a/chrono/api/views.py b/chrono/api/views.py index d34f7293..118c5024 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -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 @@ -98,7 +105,7 @@ def get_all_slots(agenda, meeting_type): def get_agenda_detail(request, agenda): agenda_detail = { 'id': agenda.slug, - 'slug': agenda.slug, # kept for compatibility + 'slug': agenda.slug, # kept for compatibility 'text': agenda.label, 'kind': agenda.kind, 'minimal_booking_delay': agenda.minimal_booking_delay, @@ -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,43 +195,68 @@ 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, - 'slug': x.slug, - 'text': force_text(x), - 'datetime': format_response_datetime(x.start_datetime), - 'description': x.description, - 'disabled': bool(x.full), - 'api': { - 'fillslot_url': request.build_absolute_uri( - 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]} + response = { + 'data': [ + { + 'id': x.id, + 'slug': x.slug, + 'text': force_text(x), + 'datetime': format_response_datetime(x.start_datetime), + 'description': x.description, + 'disabled': bool(x.full), + 'api': { + 'fillslot_url': request.build_absolute_uri( + 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 + ] + } 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, - '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]} + 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 + ] + } 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({ - '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})), - } - }) + 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, + }, + ) + ), + }, + } + ) 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({ - 'err': 1, - 'err_class': 'invalid payload', - 'err_desc': _('invalid payload'), - 'errors': serializer.errors - }, status=status.HTTP_400_BAD_REQUEST) + return Response( + { + 'err': 1, + 'err_class': 'invalid payload', + 'err_desc': _('invalid payload'), + '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({ - 'err': 1, - 'err_class': 'slots list cannot be empty', - 'err_desc': _('slots list cannot be empty'), - }, status=status.HTTP_400_BAD_REQUEST) + return Response( + { + 'err': 1, + 'err_class': 'slots list cannot be empty', + 'err_desc': _('slots list cannot be empty'), + }, + 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({ - '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) + 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, + ) else: places_count = 1 if places_count <= 0: - 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) + 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, + ) 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({ - '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) + 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, + ) 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({ - 'err': 1, - 'err_class': 'invalid slot: %s' % slot, - 'err_desc': _('invalid slot: %s') % slot, - }, status=status.HTTP_400_BAD_REQUEST) + return Response( + { + 'err': 1, + 'err_class': 'invalid slot: %s' % slot, + 'err_desc': _('invalid slot: %s') % slot, + }, + status=status.HTTP_400_BAD_REQUEST, + ) if meeting_type_id_ != meeting_type_id: - 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) + 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, + ) 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({ - 'err': 1, - 'err_class': 'no more desk available', - 'err_desc': _('no more desk available'), - }) + 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, - 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) + 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, + ) 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, - agenda_identifier=agenda_identifier, - slots=[event_identifier], # fill a "list on one slot" - format=format) + return self.fillslot( + request=request, + agenda_identifier=agenda_identifier, + slots=[event_identifier], # fill a "list on one slot" + 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() diff --git a/chrono/interval.py b/chrono/interval.py index 8ff5ddfc..5b990bc8 100644 --- a/chrono/interval.py +++ b/chrono/interval.py @@ -63,6 +63,7 @@ class Intervals(object): 10: [a], } ''' + def __init__(self): self.points = [] self.container = [] diff --git a/chrono/manager/forms.py b/chrono/manager/forms.py index 3f049d88..2ec2217a 100644 --- a/chrono/manager/forms.py +++ b/chrono/manager/forms.py @@ -26,16 +26,23 @@ 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 DATETIME_OPTIONS = { - 'weekStart': 1, - 'autoclose': True, - } + '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): @@ -66,8 +73,8 @@ class NewEventForm(forms.ModelForm): class Meta: model = Event widgets = { - 'agenda': forms.HiddenInput(), - 'start_datetime': DateTimeWidget(), + 'agenda': forms.HiddenInput(), + 'start_datetime': DateTimeWidget(), } exclude = ['full', 'meeting_type', 'desk', 'slug'] @@ -76,8 +83,8 @@ class EventForm(forms.ModelForm): class Meta: model = Event widgets = { - 'agenda': forms.HiddenInput(), - 'start_datetime': DateTimeWidget(), + 'agenda': forms.HiddenInput(), + 'start_datetime': DateTimeWidget(), } exclude = ['full', 'meeting_type', 'desk'] @@ -86,7 +93,7 @@ class NewMeetingTypeForm(forms.ModelForm): class Meta: model = MeetingType widgets = { - 'agenda': forms.HiddenInput(), + 'agenda': forms.HiddenInput(), } exclude = ['slug'] @@ -95,16 +102,15 @@ class MeetingTypeForm(forms.ModelForm): class Meta: model = MeetingType widgets = { - 'agenda': forms.HiddenInput(), + 'agenda': forms.HiddenInput(), } exclude = [] 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()) @@ -118,9 +124,9 @@ class TimePeriodForm(forms.ModelForm): class Meta: model = TimePeriod widgets = { - 'start_time': widgets.TimeWidget(), - 'end_time': widgets.TimeWidget(), - 'desk': forms.HiddenInput(), + 'start_time': widgets.TimeWidget(), + 'end_time': widgets.TimeWidget(), + 'desk': forms.HiddenInput(), } exclude = [] @@ -166,11 +172,14 @@ class TimePeriodExceptionForm(forms.ModelForm): class ImportEventsForm(forms.Form): events_csv_file = forms.FileField( - label=_('Events File'), - required=True, - help_text=_('CSV file with date, time, number of places, ' - 'number of places in waiting list, and label ' - 'as columns.')) + label=_('Events File'), + required=True, + help_text=_( + 'CSV file with date, time, number of places, ' + 'number of places in waiting list, and label ' + '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): diff --git a/chrono/manager/management/commands/export_site.py b/chrono/manager/management/commands/export_site.py index 9ac15cb3..1751b789 100644 --- a/chrono/manager/management/commands/export_site.py +++ b/chrono/manager/management/commands/export_site.py @@ -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']: diff --git a/chrono/manager/management/commands/import_site.py b/chrono/manager/management/commands/import_site.py index f32a1dd5..e56e06c3 100644 --- a/chrono/manager/management/commands/import_site.py +++ b/chrono/manager/management/commands/import_site.py @@ -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), - if_empty=options['if_empty'], - clean=options['clean'], - overwrite=options['overwrite']) + import_site( + json.load(fd), + if_empty=options['if_empty'], + clean=options['clean'], + overwrite=options['overwrite'], + ) except AgendaImportError as exc: raise CommandError(u'%s' % exc) diff --git a/chrono/manager/urls.py b/chrono/manager/urls.py index eba7a170..39866c0b 100644 --- a/chrono/manager/urls.py +++ b/chrono/manager/urls.py @@ -19,70 +19,91 @@ from django.conf.urls import url 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\d+)/$', views.agenda_view, - name='chrono-manager-agenda-view'), - url(r'^agendas/(?P\d+)/(?P[0-9]{4})/(?P[0-9]+)/$', views.agenda_monthly_view, - name='chrono-manager-agenda-month-view'), - url(r'^agendas/(?P\d+)/(?P[0-9]{4})/(?P[0-9]+)/(?P[0-9]+)/$', views.agenda_day_view, - name='chrono-manager-agenda-day-view'), - url(r'^agendas/(?P\d+)/settings$', views.agenda_settings, - name='chrono-manager-agenda-settings'), - url(r'^agendas/(?P\d+)/edit$', views.agenda_edit, - name='chrono-manager-agenda-edit'), - url(r'^agendas/(?P\d+)/delete$', views.agenda_delete, - name='chrono-manager-agenda-delete'), - url(r'^agendas/(?P\d+)/export$', views.agenda_export, - name='chrono-manager-agenda-export'), - url(r'^agendas/(?P\d+)/add-event$', views.agenda_add_event, - name='chrono-manager-agenda-add-event'), - url(r'^agendas/(?P\d+)/import-events$', views.agenda_import_events, - name='chrono-manager-agenda-import-events'), - url(r'^events/(?P\d+)/$', views.event_edit, - name='chrono-manager-event-edit'), - url(r'^events/(?P\d+)/delete$', views.event_delete, - name='chrono-manager-event-delete'), - - url(r'^agendas/(?P\d+)/add-meeting-type$', views.agenda_add_meeting_type, - name='chrono-manager-agenda-add-meeting-type'), - url(r'^meetingtypes/(?P\d+)/edit$', views.meeting_type_edit, - name='chrono-manager-meeting-type-edit'), - url(r'^meetingtypes/(?P\d+)/delete$', views.meeting_type_delete, - name='chrono-manager-meeting-type-delete'), - - url(r'^agendas/(?P\d+)/desk/(?P\d+)/add-time-period$', views.agenda_add_time_period, - name='chrono-manager-agenda-add-time-period'), - url(r'^timeperiods/(?P\d+)/edit$', views.time_period_edit, - name='chrono-manager-time-period-edit'), - url(r'^timeperiods/(?P\d+)/delete$', views.time_period_delete, - name='chrono-manager-time-period-delete'), - - url(r'^agendas/(?P\d+)/add-desk$', views.agenda_add_desk, - name='chrono-manager-agenda-add-desk'), - url(r'^desks/(?P\d+)/edit$', views.desk_edit, - name='chrono-manager-desk-edit'), - url(r'^desks/(?P\d+)/delete$', views.desk_delete, - name='chrono-manager-desk-delete'), - - url(r'^agendas/(?P\d+)/desk/(?P\d+)/add-time-period-exception$', views.agenda_add_time_period_exception, - name='chrono-manager-agenda-add-time-period-exception'), - url(r'^agendas/desk/(?P\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\d+)/edit$', views.time_period_exception_edit, - name='chrono-manager-time-period-exception-edit'), - url(r'^time-period-exceptions/(?P\d+)/delete$', views.time_period_exception_delete, - name='chrono-manager-time-period-exception-delete'), - url(r'^time-period-exceptions/(?P\d+)/exception-extract-list$', views.time_period_exception_extract_list, - name='chrono-manager-time-period-exception-extract-list'), - url(r'^time-period-exceptions/(?P\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), + 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\d+)/$', views.agenda_view, name='chrono-manager-agenda-view'), + url( + r'^agendas/(?P\d+)/(?P[0-9]{4})/(?P[0-9]+)/$', + views.agenda_monthly_view, + name='chrono-manager-agenda-month-view', + ), + url( + r'^agendas/(?P\d+)/(?P[0-9]{4})/(?P[0-9]+)/(?P[0-9]+)/$', + views.agenda_day_view, + name='chrono-manager-agenda-day-view', + ), + url(r'^agendas/(?P\d+)/settings$', views.agenda_settings, name='chrono-manager-agenda-settings'), + url(r'^agendas/(?P\d+)/edit$', views.agenda_edit, name='chrono-manager-agenda-edit'), + url(r'^agendas/(?P\d+)/delete$', views.agenda_delete, name='chrono-manager-agenda-delete'), + url(r'^agendas/(?P\d+)/export$', views.agenda_export, name='chrono-manager-agenda-export'), + url(r'^agendas/(?P\d+)/add-event$', views.agenda_add_event, name='chrono-manager-agenda-add-event'), + url( + r'^agendas/(?P\d+)/import-events$', + views.agenda_import_events, + name='chrono-manager-agenda-import-events', + ), + url(r'^events/(?P\d+)/$', views.event_edit, name='chrono-manager-event-edit'), + url(r'^events/(?P\d+)/delete$', views.event_delete, name='chrono-manager-event-delete'), + url( + r'^agendas/(?P\d+)/add-meeting-type$', + views.agenda_add_meeting_type, + name='chrono-manager-agenda-add-meeting-type', + ), + url(r'^meetingtypes/(?P\d+)/edit$', views.meeting_type_edit, name='chrono-manager-meeting-type-edit'), + url( + r'^meetingtypes/(?P\d+)/delete$', + views.meeting_type_delete, + name='chrono-manager-meeting-type-delete', + ), + url( + r'^agendas/(?P\d+)/desk/(?P\d+)/add-time-period$', + views.agenda_add_time_period, + name='chrono-manager-agenda-add-time-period', + ), + url(r'^timeperiods/(?P\d+)/edit$', views.time_period_edit, name='chrono-manager-time-period-edit'), + url( + r'^timeperiods/(?P\d+)/delete$', + views.time_period_delete, + name='chrono-manager-time-period-delete', + ), + url(r'^agendas/(?P\d+)/add-desk$', views.agenda_add_desk, name='chrono-manager-agenda-add-desk'), + url(r'^desks/(?P\d+)/edit$', views.desk_edit, name='chrono-manager-desk-edit'), + url(r'^desks/(?P\d+)/delete$', views.desk_delete, name='chrono-manager-desk-delete'), + url( + r'^agendas/(?P\d+)/desk/(?P\d+)/add-time-period-exception$', + views.agenda_add_time_period_exception, + name='chrono-manager-agenda-add-time-period-exception', + ), + url( + r'^agendas/desk/(?P\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\d+)/edit$', + views.time_period_exception_edit, + name='chrono-manager-time-period-exception-edit', + ), + url( + r'^time-period-exceptions/(?P\d+)/delete$', + views.time_period_exception_delete, + name='chrono-manager-time-period-exception-delete', + ), + url( + r'^time-period-exceptions/(?P\d+)/exception-extract-list$', + views.time_period_exception_extract_list, + name='chrono-manager-time-period-exception-extract-list', + ), + url( + r'^time-period-exceptions/(?P\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), ] diff --git a/chrono/manager/views.py b/chrono/manager/views.py index 33a0be95..19a66328 100644 --- a/chrono/manager/views.py +++ b/chrono/manager/views.py @@ -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() @@ -155,9 +188,10 @@ class AgendaDeleteView(DeleteView): def get_context_data(self, **kwargs): context = super(AgendaDeleteView, self).get_context_data(**kwargs) context['cannot_delete'] = Booking.objects.filter( - event__agenda=self.get_object(), - event__start_datetime__gt=now(), - cancellation_datetime__isnull=True).exists() + event__agenda=self.get_object(), + event__start_datetime__gt=now(), + 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, - 'year': previous_day.year, - 'month': previous_day.month, - 'day': previous_day.day}) + return reverse( + 'chrono-manager-agenda-day-view', + kwargs={ + 'pk': self.agenda.id, + 'year': previous_day.year, + 'month': previous_day.month, + '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, - 'year': next_day.year, - 'month': next_day.month, - 'day': next_day.day}) + return reverse( + 'chrono-manager-agenda-day-view', + kwargs={ + 'pk': self.agenda.id, + 'year': next_day.year, + 'month': next_day.month, + '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({ - 'css_top': 100 * (opening_hour.begin - start_date).seconds // 3600, - 'css_height': 100 * (opening_hour.end - opening_hour.begin).seconds // 3600, - }) + 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, - 'today': day.date() == datetime.date.today(), - 'other_month': day.month != self.date.month, - 'infos': {'opening_hours': [], 'booked_slots': []}} + timetable = { + 'date': current_date, + 'today': day.date() == datetime.date.today(), + 'other_month': day.month != self.date.month, + '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, - 'css_height': 100 * event.meeting_type.duration // 60, - 'css_width': width, - 'css_left': left, - 'desk': desk, - 'booking': bookings[0] + 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], } 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({ - 'css_top': 100 * (hour.begin - current_date).seconds // 3600, - 'css_height': 100 * (hour.end - hour.begin).seconds // 3600, - 'css_width': width, - 'css_left': left, - }) + 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() @@ -669,13 +745,15 @@ class AgendaAddTimePeriodView(ManagedDeskMixin, FormView): def form_valid(self, form): for weekday in form.cleaned_data.get('weekdays'): period = TimePeriod( - weekday=weekday, - start_time=form.cleaned_data['start_time'], - end_time=form.cleaned_data['end_time'], - desk=self.desk) + weekday=weekday, + start_time=form.cleaned_data['start_time'], + end_time=form.cleaned_data['end_time'], + 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), - 'slug': 'calendar', - 'url': request.build_absolute_uri(reverse('chrono-manager-homepage')) - }]) + json_str = json.dumps( + [ + { + 'label': force_text(label), + 'slug': 'calendar', + 'url': request.build_absolute_uri(reverse('chrono-manager-homepage')), + } + ] + ) content_type = 'application/json' for variable in ('jsonpCallback', 'callback'): if variable in request.GET: diff --git a/chrono/manager/widgets.py b/chrono/manager/widgets.py index f3387c01..c12cf338 100644 --- a/chrono/manager/widgets.py +++ b/chrono/manager/widgets.py @@ -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'(?' % { - 'name': name, - 'checked': 'checked' if choice_id in value else '', - 'choice_id': choice_id, - 'choice_label': choice_label}) + s.append( + '
  • ' + % { + 'name': name, + 'checked': 'checked' if choice_id in value else '', + 'choice_id': choice_id, + 'choice_label': choice_label, + } + ) return mark_safe('
      ' % attrs + '\n'.join(s) + '
    ') def value_from_datadict(self, data, files, name): diff --git a/chrono/settings.py b/chrono/settings.py index b5163b3e..37cadf37 100644 --- a/chrono/settings.py +++ b/chrono/settings.py @@ -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()) diff --git a/chrono/urls.py b/chrono/urls.py index 9f441177..86c06dd9 100644 --- a/chrono/urls.py +++ b/chrono/urls.py @@ -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'), diff --git a/chrono/urls_utils.py b/chrono/urls_utils.py index b4f38b2f..980ad645 100644 --- a/chrono/urls_utils.py +++ b/chrono/urls_utils.py @@ -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) diff --git a/chrono/views.py b/chrono/views.py index 56333b58..f26423a4 100644 --- a/chrono/views.py +++ b/chrono/views.py @@ -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) diff --git a/chrono/wsgi.py b/chrono/wsgi.py index e7d8e421..72268bfb 100644 --- a/chrono/wsgi.py +++ b/chrono/wsgi.py @@ -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() diff --git a/debian/settings.py b/debian/settings.py index 5e354c76..c1977b74 100644 --- a/debian/settings.py +++ b/debian/settings.py @@ -14,15 +14,15 @@ # 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 ALLOWED_HOSTS = [ - '*', + '*', ] # Databases diff --git a/setup.py b/setup.py index 7ffef544..5de2fc1f 100644 --- a/setup.py +++ b/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,23 +37,25 @@ 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 - if '-' in result: # not a tagged version + result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v + if '-' in result: # not a tagged version real_number, commit_count, commit_hash = result.split('-', 2) version = '%s.post%s+%s' % (real_number, commit_count, commit_hash) else: 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,14 +160,15 @@ 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={ 'build': build, diff --git a/tests/conftest.py b/tests/conftest.py index 5eebfd43..57f8e465 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ from django.contrib.auth.models import User import django_webtest + @pytest.fixture def app(request): wtm = django_webtest.WebTestMixin() diff --git a/tests/test_agendas.py b/tests/test_agendas.py index 820d8a27..c919219e 100644 --- a/tests/test_agendas.py +++ b/tests/test_agendas.py @@ -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([ - 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([ - make_aware(datetime.datetime(2017, 8, 31, 22, 34, 0)), - make_aware(datetime.datetime(2017, 9, 1, 0, 34, 0)), - ]) + 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( + [ + 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 diff --git a/tests/test_api.py b/tests/test_api.py index ce5d1abe..add2c749 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -9,9 +9,7 @@ from django.test import override_settings from django.test.utils import CaptureQueriesContext from django.utils.timezone import now, make_aware, localtime -from chrono.agendas.models import (Agenda, Event, Booking, - MeetingType, TimePeriod, Desk, - TimePeriodException) +from chrono.agendas.models import Agenda, Event, Booking, MeetingType, TimePeriod, Desk, TimePeriodException import chrono.api.views @@ -25,29 +23,36 @@ def datetime_from_str(dt_str): @pytest.fixture def user(): User = get_user_model() - 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 + @pytest.fixture(params=['Europe/Brussels', 'Asia/Kolkata', 'Brazil/East']) def time_zone(request, settings): settings.TIME_ZONE = request.param -@pytest.fixture(params=[ - datetime.datetime(2017, 5, 20, 1, 12), - datetime.datetime(2017, 5, 20, 11, 42), - datetime.datetime(2017, 5, 20, 23, 17)]) +@pytest.fixture( + params=[ + datetime.datetime(2017, 5, 20, 1, 12), + datetime.datetime(2017, 5, 20, 11, 42), + datetime.datetime(2017, 5, 20, 23, 17), + ] +) def mock_now(request, monkeypatch): def mockreturn(): return make_aware(request.param) + monkeypatch.setattr(chrono.api.views, 'now', mockreturn) monkeypatch.setattr(chrono.agendas.models, 'now', mockreturn) monkeypatch.setattr(sys.modules[__name__], 'now', mockreturn) return mockreturn() + @pytest.fixture def some_data(time_zone, mock_now): agenda = Agenda(label=u'Foo bar') @@ -55,8 +60,7 @@ def some_data(time_zone, mock_now): first_date = localtime(now()).replace(hour=17, minute=0, second=0, microsecond=0) first_date += datetime.timedelta(days=1) for i in range(3): - event = Event(start_datetime=first_date + datetime.timedelta(days=i), - places=20, agenda=agenda) + event = Event(start_datetime=first_date + datetime.timedelta(days=i), places=20, agenda=agenda) event.save() agenda2 = Agenda(label=u'Foo bar2') @@ -64,19 +68,19 @@ def some_data(time_zone, mock_now): first_date = localtime(now()).replace(hour=20, minute=0, second=0, microsecond=0) first_date += datetime.timedelta(days=1) for i in range(2): - event = Event(start_datetime=first_date + datetime.timedelta(days=i), - places=20, agenda=agenda2) + event = Event(start_datetime=first_date + datetime.timedelta(days=i), places=20, agenda=agenda2) event.save() # a date in the past - event = Event(start_datetime=first_date - datetime.timedelta(days=10), - places=10, agenda=agenda) + event = Event(start_datetime=first_date - datetime.timedelta(days=10), places=10, agenda=agenda) event.save() + @pytest.fixture def meetings_agenda(time_zone, mock_now): - agenda = Agenda(label=u'Foo bar Meeting', kind='meetings', - minimal_booking_delay=1, maximal_booking_delay=56) + agenda = Agenda( + label=u'Foo bar Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=56 + ) agenda.save() meeting_type = MeetingType(agenda=agenda, label='Blah', duration=30) meeting_type.save() @@ -86,48 +90,84 @@ def meetings_agenda(time_zone, mock_now): default_desk, created = Desk.objects.get_or_create(agenda=agenda, label='Desk 1') - time_period = TimePeriod(weekday=test_1st_weekday, - start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=default_desk) + time_period = TimePeriod( + weekday=test_1st_weekday, + start_time=datetime.time(10, 0), + end_time=datetime.time(12, 0), + desk=default_desk, + ) time_period.save() - time_period = TimePeriod(weekday=test_2nd_weekday, - start_time=datetime.time(10, 0), end_time=datetime.time(17, 0), desk=default_desk) + time_period = TimePeriod( + weekday=test_2nd_weekday, + start_time=datetime.time(10, 0), + end_time=datetime.time(17, 0), + desk=default_desk, + ) time_period.save() return agenda + def test_agendas_api(app, some_data, meetings_agenda): agenda1 = Agenda.objects.filter(label=u'Foo bar')[0] agenda2 = Agenda.objects.filter(label=u'Foo bar2')[0] resp = app.get('/api/agenda/') - assert resp.json == {'data': [ - {'text': 'Foo bar', 'id': u'foo-bar', 'slug': 'foo-bar', 'kind': 'events', - 'minimal_booking_delay': 1, 'maximal_booking_delay': 56, - 'api': {'datetimes_url': 'http://testserver/api/agenda/%s/datetimes/' % agenda1.slug, - 'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % agenda1.slug}}, - {'text': 'Foo bar Meeting', 'id': u'foo-bar-meeting', 'slug': 'foo-bar-meeting', - 'minimal_booking_delay': 1, 'maximal_booking_delay': 56, - 'kind': 'meetings', - 'api': {'meetings_url': 'http://testserver/api/agenda/%s/meetings/' % meetings_agenda.slug, - 'desks_url': 'http://testserver/api/agenda/%s/desks/' % meetings_agenda.slug, - 'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % meetings_agenda.slug, + assert resp.json == { + 'data': [ + { + 'text': 'Foo bar', + 'id': u'foo-bar', + 'slug': 'foo-bar', + 'kind': 'events', + 'minimal_booking_delay': 1, + 'maximal_booking_delay': 56, + 'api': { + 'datetimes_url': 'http://testserver/api/agenda/%s/datetimes/' % agenda1.slug, + 'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % agenda1.slug, }, - }, - {'text': 'Foo bar2', 'id': u'foo-bar2', 'kind': 'events', 'slug': 'foo-bar2', - 'minimal_booking_delay': 1, 'maximal_booking_delay': 56, - 'api': {'datetimes_url': 'http://testserver/api/agenda/%s/datetimes/' % agenda2.slug, - 'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % agenda2.slug}} - ]} + }, + { + 'text': 'Foo bar Meeting', + 'id': u'foo-bar-meeting', + 'slug': 'foo-bar-meeting', + 'minimal_booking_delay': 1, + 'maximal_booking_delay': 56, + 'kind': 'meetings', + 'api': { + 'meetings_url': 'http://testserver/api/agenda/%s/meetings/' % meetings_agenda.slug, + 'desks_url': 'http://testserver/api/agenda/%s/desks/' % meetings_agenda.slug, + 'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % meetings_agenda.slug, + }, + }, + { + 'text': 'Foo bar2', + 'id': u'foo-bar2', + 'kind': 'events', + 'slug': 'foo-bar2', + 'minimal_booking_delay': 1, + 'maximal_booking_delay': 56, + 'api': { + 'datetimes_url': 'http://testserver/api/agenda/%s/datetimes/' % agenda2.slug, + 'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % agenda2.slug, + }, + }, + ] + } + def test_agendas_meetingtypes_api(app, some_data, meetings_agenda): resp = app.get('/api/agenda/%s/meetings/' % meetings_agenda.slug) - assert resp.json == {'data': [ - {'text': 'Blah', - 'id': 'blah', - 'duration': 30, - 'api': { - 'datetimes_url': 'http://testserver/api/agenda/foo-bar-meeting/meetings/blah/datetimes/', - } - } - ]} + assert resp.json == { + 'data': [ + { + 'text': 'Blah', + 'id': 'blah', + 'duration': 30, + 'api': { + 'datetimes_url': 'http://testserver/api/agenda/foo-bar-meeting/meetings/blah/datetimes/', + }, + } + ] + } # wrong kind agenda1 = Agenda.objects.filter(label=u'Foo bar')[0] @@ -139,11 +179,7 @@ def test_agendas_meetingtypes_api(app, some_data, meetings_agenda): def test_agendas_desks_api(app, some_data, meetings_agenda): resp = app.get('/api/agenda/%s/desks/' % meetings_agenda.slug) - assert resp.json == {'data': [ - {'text': 'Desk 1', - 'id': 'desk-1', - } - ]} + assert resp.json == {'data': [{'text': 'Desk 1', 'id': 'desk-1',}]} # wrong kind agenda1 = Agenda.objects.filter(label=u'Foo bar')[0] @@ -198,12 +234,14 @@ def test_datetimes_api(app, some_data): resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug) assert resp.json['data'][0]['description'] + def test_datetimes_api_wrong_kind(app, some_data): agenda = Agenda.objects.filter(label=u'Foo bar')[0] agenda.kind = 'meetings' agenda.save() resp = app.get('/api/agenda/%s/datetimes/' % agenda.id, status=404) + def test_datetime_api_fr(app, some_data): agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id with override_settings(LANGUAGE_CODE='fr-fr'): @@ -213,6 +251,7 @@ def test_datetime_api_fr(app, some_data): assert resp.json['data'][0]['datetime'].endswith(' 17:00:00') assert 'data' in resp.json + def test_datetime_api_label(app, some_data): agenda_id = Agenda.objects.filter(label=u'Foo bar2')[0].id event = Event.objects.filter(agenda=agenda_id)[0] @@ -221,17 +260,20 @@ def test_datetime_api_label(app, some_data): resp = app.get('/api/agenda/%s/datetimes/' % agenda_id) assert 'Hello world' in [x['text'] for x in resp.json['data']] + def test_datetime_api_status_url(app, some_data): agenda = Agenda.objects.get(label=u'Foo bar2') resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug) for datum in resp.json['data']: assert urlparse.urlparse(datum['api']['status_url']).path == '/api/agenda/%s/status/%s/' % ( - agenda.slug, datum['slug'] or datum['id']) + agenda.slug, + datum['slug'] or datum['id'], + ) + def test_datetimes_api_meetings_agenda(app, meetings_agenda): meeting_type = MeetingType.objects.get(agenda=meetings_agenda) - api_url = '/api/agenda/%s/meetings/%s/datetimes/' % ( - meeting_type.agenda.slug, meeting_type.slug) + api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug) resp = app.get('/api/agenda/%s/meetings/xxx/datetimes/' % meeting_type.agenda.slug, status=404) @@ -253,8 +295,14 @@ def test_datetimes_api_meetings_agenda(app, meetings_agenda): resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M') - ev = Event(agenda=meetings_agenda, meeting_type=meeting_type, - places=1, full=False, start_datetime=make_aware(dt), desk=Desk.objects.first()) + ev = Event( + agenda=meetings_agenda, + meeting_type=meeting_type, + places=1, + full=False, + start_datetime=make_aware(dt), + desk=Desk.objects.first(), + ) ev.save() booking = Booking(event=ev) booking.save() @@ -277,9 +325,12 @@ def test_datetimes_api_meetings_agenda(app, meetings_agenda): default_desk, _ = Desk.objects.get_or_create(agenda=meetings_agenda, slug='desk-1') TimePeriod.objects.filter(desk=default_desk).delete() start_time = localtime(now()) - datetime.timedelta(minutes=10) - time_period = TimePeriod(weekday=localtime(now()).weekday(), - start_time=start_time, - end_time=start_time + datetime.timedelta(hours=1), desk=default_desk) + time_period = TimePeriod( + weekday=localtime(now()).weekday(), + start_time=start_time, + end_time=start_time + datetime.timedelta(hours=1), + desk=default_desk, + ) time_period.save() meetings_agenda.minimal_booking_delay = 0 meetings_agenda.maximal_booking_delay = 10 @@ -287,6 +338,7 @@ def test_datetimes_api_meetings_agenda(app, meetings_agenda): resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert len(resp.json['data']) == 3 + def test_datetimes_api_meetings_agenda_short_time_periods(app, meetings_agenda, user): meetings_agenda.minimal_booking_delay = 0 meetings_agenda.maximal_booking_delay = 10 @@ -294,14 +346,17 @@ def test_datetimes_api_meetings_agenda_short_time_periods(app, meetings_agenda, default_desk, _ = Desk.objects.get_or_create(agenda=meetings_agenda, slug='desk-1') meeting_type = MeetingType.objects.get(agenda=meetings_agenda) - api_url = '/api/agenda/%s/meetings/%s/datetimes/' % ( - meeting_type.agenda.slug, meeting_type.slug) + api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug) # test with short time periods TimePeriod.objects.filter(desk=default_desk).delete() test_1st_weekday = (localtime(now()).weekday() + 2) % 7 - time_period = TimePeriod(weekday=test_1st_weekday, - start_time=datetime.time(10, 0), end_time=datetime.time(10, 30), desk=default_desk) + time_period = TimePeriod( + weekday=test_1st_weekday, + start_time=datetime.time(10, 0), + end_time=datetime.time(10, 30), + desk=default_desk, + ) time_period.save() resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert len(resp.json['data']) == 2 @@ -328,6 +383,7 @@ def test_datetimes_api_meetings_agenda_short_time_periods(app, meetings_agenda, assert resp.json['err_class'] == 'no more desk available' assert resp.json['err_desc'] == 'no more desk available' + def test_booking_api(app, some_data, user): agenda = Agenda.objects.filter(label=u'Foo bar')[0] event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0] @@ -337,8 +393,13 @@ def test_booking_api(app, some_data, user): for agenda_key in (agenda.slug, agenda.id): # acces datetimes via agenda slug or id (legacy) resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda_key) - event_fillslot_url = [x for x in resp_datetimes.json['data'] if x['id'] == event.id][0]['api']['fillslot_url'] - assert urlparse.urlparse(event_fillslot_url).path == '/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug or event.id) + event_fillslot_url = [x for x in resp_datetimes.json['data'] if x['id'] == event.id][0]['api'][ + 'fillslot_url' + ] + assert urlparse.urlparse(event_fillslot_url).path == '/api/agenda/%s/fillslot/%s/' % ( + agenda.slug, + event.slug or event.id, + ) app.authorization = ('Basic', ('john.doe', 'password')) resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id)) @@ -359,30 +420,39 @@ def test_booking_api(app, some_data, user): assert Booking.objects.filter(event__agenda=agenda).count() == 2 # test with additional data - resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), - params={'label': 'foo', 'user_name': 'bar', 'backoffice_url': 'http://example.net/'}) + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), + params={'label': 'foo', 'user_name': 'bar', 'backoffice_url': 'http://example.net/'}, + ) assert Booking.objects.get(id=resp.json['booking_id']).label == 'foo' assert Booking.objects.get(id=resp.json['booking_id']).user_name == 'bar' assert Booking.objects.get(id=resp.json['booking_id']).backoffice_url == 'http://example.net/' # blank data are OK - resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), - params={'label': '', 'user_name': '', 'backoffice_url': ''}) + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), + params={'label': '', 'user_name': '', 'backoffice_url': ''}, + ) assert Booking.objects.get(id=resp.json['booking_id']).label == '' assert Booking.objects.get(id=resp.json['booking_id']).user_name == '' assert Booking.objects.get(id=resp.json['booking_id']).backoffice_url == '' # extra data stored in extra_data field - resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), - params={'label': 'l', 'user_name': 'u', 'backoffice_url': '', 'foo': 'bar'}) + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), + params={'label': 'l', 'user_name': 'u', 'backoffice_url': '', 'foo': 'bar'}, + ) assert Booking.objects.get(id=resp.json['booking_id']).label == 'l' assert Booking.objects.get(id=resp.json['booking_id']).user_name == 'u' assert Booking.objects.get(id=resp.json['booking_id']).backoffice_url == '' assert Booking.objects.get(id=resp.json['booking_id']).extra_data == {'foo': 'bar'} # test invalid data are refused - resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), - params={'user_name': {'foo': 'bar'}}, status=400) + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), + params={'user_name': {'foo': 'bar'}}, + status=400, + ) assert resp.json['err'] == 1 assert resp.json['reason'] == 'invalid payload' # legacy assert resp.json['err_class'] == 'invalid payload' @@ -394,6 +464,7 @@ def test_booking_api(app, some_data, user): resp = app.post('/api/agenda/233/fillslot/%s/' % event.id, status=404) + def test_booking_ics(app, some_data, meetings_agenda, user): agenda = Agenda.objects.filter(label=u'Foo bar')[0] event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0] @@ -407,15 +478,24 @@ def test_booking_ics(app, some_data, meetings_agenda, user): formatted_start_date = event.start_datetime.strftime('%Y%m%dT%H%M%S') booking_ics = Booking.objects.get(id=resp.json['booking_id']).get_ics() - assert 'UID:%s-%s-%s\r\n' % (event.start_datetime.isoformat(), agenda.pk, resp.json['booking_id']) in booking_ics + assert ( + 'UID:%s-%s-%s\r\n' % (event.start_datetime.isoformat(), agenda.pk, resp.json['booking_id']) + in booking_ics + ) assert 'SUMMARY:\r\n' in booking_ics assert 'DTSTART:%sZ\r\n' % formatted_start_date in booking_ics assert 'DTEDND:' not in booking_ics # test with additional data - resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), - params={'label': 'foo', 'user_name': 'bar', 'backoffice_url': 'http://example.net/', - 'url': 'http://example.com/booking'}) + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), + params={ + 'label': 'foo', + 'user_name': 'bar', + 'backoffice_url': 'http://example.net/', + 'url': 'http://example.com/booking', + }, + ) assert Booking.objects.count() == 2 booking_ics = Booking.objects.get(id=resp.json['booking_id']).get_ics() assert 'SUMMARY:foo\r\n' in booking_ics @@ -423,9 +503,16 @@ def test_booking_ics(app, some_data, meetings_agenda, user): assert 'URL:http://example.com/booking\r\n' in booking_ics # test with user_label in additionnal data - resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), - params={'label': 'foo', 'user_name': 'bar', 'backoffice_url': 'http://example.net/', - 'url': 'http://example.com/booking', 'user_display_label': 'your booking'}) + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), + params={ + 'label': 'foo', + 'user_name': 'bar', + 'backoffice_url': 'http://example.net/', + 'url': 'http://example.com/booking', + 'user_display_label': 'your booking', + }, + ) assert Booking.objects.count() == 3 booking_ics = Booking.objects.get(id=resp.json['booking_id']).get_ics() assert 'SUMMARY:your booking\r\n' in booking_ics @@ -433,9 +520,17 @@ def test_booking_ics(app, some_data, meetings_agenda, user): assert 'URL:http://example.com/booking\r\n' in booking_ics # extra data stored in extra_data field - resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), - params={'label': 'l', 'user_name': 'u', 'backoffice_url': '', 'location': 'bar', - 'comment': 'booking comment', 'description': 'booking description'}) + resp = app.post_json( + '/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), + params={ + 'label': 'l', + 'user_name': 'u', + 'backoffice_url': '', + 'location': 'bar', + 'comment': 'booking comment', + 'description': 'booking description', + }, + ) assert Booking.objects.count() == 4 booking_id = resp.json['booking_id'] booking = Booking.objects.get(id=booking_id) @@ -452,8 +547,12 @@ def test_booking_ics(app, some_data, meetings_agenda, user): resp = app.get('/api/booking/%s/ics/' % resp.json['booking_id']) assert resp.headers['Content-Type'] == 'text/calendar' - params = {'description': 'custom booking description', 'location': 'custom booking location', - 'comment': 'custom comment', 'url': 'http://example.com/custom'} + params = { + 'description': 'custom booking description', + 'location': 'custom booking location', + 'comment': 'custom comment', + 'url': 'http://example.com/custom', + } resp = app.get('/api/booking/%s/ics/' % booking_id, params=params) assert 'DESCRIPTION:custom booking description\r\n' in resp.text assert 'LOCATION:custom booking location\r\n' in resp.text @@ -470,10 +569,13 @@ def test_booking_ics(app, some_data, meetings_agenda, user): booking = Booking.objects.get(id=resp.json['booking_id']) booking_ics = booking.get_ics() start = booking.event.start_datetime.strftime('%Y%m%dT%H%M%S') - end = (booking.event.start_datetime + datetime.timedelta(minutes=booking.event.meeting_type.duration)).strftime('%Y%m%dT%H%M%S') + end = ( + booking.event.start_datetime + datetime.timedelta(minutes=booking.event.meeting_type.duration) + ).strftime('%Y%m%dT%H%M%S') assert "DTSTART:%sZ\r\n" % start in booking_ics assert "DTEND:%sZ\r\n" % end in booking_ics + def test_booking_api_fillslots(app, some_data, user): agenda = Agenda.objects.filter(label=u'Foo bar')[0] events_ids = [x.id for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()] @@ -520,9 +622,15 @@ def test_booking_api_fillslots(app, some_data, user): assert Booking.objects.filter(event__agenda=agenda, primary_booking=primary_booking_id_2).count() == 2 # test with additional data - resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.id, - params={'slots': events_ids, - 'label': 'foo', 'user_name': 'bar', 'backoffice_url': 'http://example.net/'}) + resp = app.post_json( + '/api/agenda/%s/fillslots/' % agenda.id, + params={ + 'slots': events_ids, + 'label': 'foo', + 'user_name': 'bar', + 'backoffice_url': 'http://example.net/', + }, + ) booking_id = resp.json['booking_id'] assert Booking.objects.get(id=booking_id).label == 'foo' assert Booking.objects.get(id=booking_id).user_name == 'bar' @@ -538,9 +646,10 @@ def test_booking_api_fillslots(app, some_data, user): assert Booking.objects.get(id=booking_id).cancellation_datetime is not None # extra data stored in extra_data field - resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.id, - params={'slots': events_ids, - 'label': 'l', 'user_name': 'u', 'backoffice_url': '', 'foo': 'bar'}) + resp = app.post_json( + '/api/agenda/%s/fillslots/' % agenda.id, + params={'slots': events_ids, 'label': 'l', 'user_name': 'u', 'backoffice_url': '', 'foo': 'bar'}, + ) assert Booking.objects.get(id=resp.json['booking_id']).label == 'l' assert Booking.objects.get(id=resp.json['booking_id']).user_name == 'u' assert Booking.objects.get(id=resp.json['booking_id']).backoffice_url == '' @@ -549,9 +658,11 @@ def test_booking_api_fillslots(app, some_data, user): assert booking.extra_data == {'foo': 'bar'} # test invalid data are refused - resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.id, - params={'slots': events_ids, - 'user_name': {'foo': 'bar'}}, status=400) + resp = app.post_json( + '/api/agenda/%s/fillslots/' % agenda.id, + params={'slots': events_ids, 'user_name': {'foo': 'bar'}}, + status=400, + ) assert resp.json['err'] == 1 assert resp.json['reason'] == 'invalid payload' # legacy assert resp.json['err_class'] == 'invalid payload' @@ -583,13 +694,15 @@ def test_booking_api_fillslots(app, some_data, user): resp = app.post('/api/agenda/foobar/fillslots/', status=404) resp = app.post('/api/agenda/233/fillslots/', status=404) + def test_booking_api_meeting(app, meetings_agenda, user): agenda_id = meetings_agenda.slug meeting_type = MeetingType.objects.get(agenda=meetings_agenda) resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) event_id = resp.json['data'][2]['id'] - assert urlparse.urlparse(resp.json['data'][2]['api']['fillslot_url'] - ).path == '/api/agenda/%s/fillslot/%s/' % (meetings_agenda.slug, event_id) + assert urlparse.urlparse( + resp.json['data'][2]['api']['fillslot_url'] + ).path == '/api/agenda/%s/fillslot/%s/' % (meetings_agenda.slug, event_id) app.authorization = ('Basic', ('john.doe', 'password')) @@ -600,10 +713,12 @@ def test_booking_api_meeting(app, meetings_agenda, user): # make a booking resp_booking = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id)) assert Booking.objects.count() == 1 - assert resp_booking.json['datetime'] == localtime(Booking.objects.all()[0].event.start_datetime - ).strftime('%Y-%m-%d %H:%M:%S') - assert resp_booking.json['end_datetime'] == localtime(Booking.objects.all()[0].event.end_datetime - ).strftime('%Y-%m-%d %H:%M:%S') + assert resp_booking.json['datetime'] == localtime(Booking.objects.all()[0].event.start_datetime).strftime( + '%Y-%m-%d %H:%M:%S' + ) + assert resp_booking.json['end_datetime'] == localtime( + Booking.objects.all()[0].event.end_datetime + ).strftime('%Y-%m-%d %H:%M:%S') assert resp_booking.json['duration'] == 30 resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) @@ -622,6 +737,7 @@ def test_booking_api_meeting(app, meetings_agenda, user): assert resp.json['err'] == 0 assert Booking.objects.count() == 2 + def test_booking_api_meeting_fillslots(app, meetings_agenda, user): agenda_id = meetings_agenda.slug meeting_type = MeetingType.objects.get(agenda=meetings_agenda) @@ -633,10 +749,12 @@ def test_booking_api_meeting_fillslots(app, meetings_agenda, user): assert Booking.objects.count() == 2 primary_booking = Booking.objects.filter(primary_booking__isnull=True).first() secondary_booking = Booking.objects.filter(primary_booking=primary_booking.id).first() - assert resp_booking.json['datetime'] == localtime(primary_booking.event.start_datetime - ).strftime('%Y-%m-%d %H:%M:%S') - assert resp_booking.json['end_datetime'] == localtime(secondary_booking.event.end_datetime - ).strftime('%Y-%m-%d %H:%M:%S') + assert resp_booking.json['datetime'] == localtime(primary_booking.event.start_datetime).strftime( + '%Y-%m-%d %H:%M:%S' + ) + assert resp_booking.json['end_datetime'] == localtime(secondary_booking.event.end_datetime).strftime( + '%Y-%m-%d %H:%M:%S' + ) resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert len(resp.json['data']) == len([x for x in resp2.json['data'] if not x.get('disabled')]) + 2 @@ -672,14 +790,13 @@ def test_booking_api_meeting_fillslots(app, meetings_agenda, user): assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 2 impossible_slots = ['1:2017-05-22-1130', '2:2017-05-22-1100'] - resp = app.post('/api/agenda/%s/fillslots/' % agenda_id, - params={'slots': impossible_slots}, - status=400) + resp = app.post('/api/agenda/%s/fillslots/' % agenda_id, params={'slots': impossible_slots}, status=400) assert resp.json['err'] == 1 assert resp.json['reason'] == 'all slots must have the same meeting type id (1)' # legacy assert resp.json['err_class'] == 'all slots must have the same meeting type id (1)' assert resp.json['err_desc'] == 'all slots must have the same meeting type id (1)' + def test_booking_api_meeting_across_daylight_saving_time(app, meetings_agenda, user): meetings_agenda.maximal_booking_delay = 365 meetings_agenda.save() @@ -689,19 +806,22 @@ def test_booking_api_meeting_across_daylight_saving_time(app, meetings_agenda, u resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) event_index = 26 * 18 event_id = resp.json['data'][event_index]['id'] - assert event_id[-4:] == resp.json['data'][2*18]['id'][-4:] - assert urlparse.urlparse(resp.json['data'][event_index]['api']['fillslot_url'] - ).path == '/api/agenda/%s/fillslot/%s/' % (meetings_agenda.slug, event_id) + assert event_id[-4:] == resp.json['data'][2 * 18]['id'][-4:] + assert urlparse.urlparse( + resp.json['data'][event_index]['api']['fillslot_url'] + ).path == '/api/agenda/%s/fillslot/%s/' % (meetings_agenda.slug, event_id) app.authorization = ('Basic', ('john.doe', 'password')) resp_booking = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id)) assert Booking.objects.count() == 1 - assert resp_booking.json['datetime'] == localtime(Booking.objects.all()[0].event.start_datetime - ).strftime('%Y-%m-%d %H:%M:%S') + assert resp_booking.json['datetime'] == localtime(Booking.objects.all()[0].event.start_datetime).strftime( + '%Y-%m-%d %H:%M:%S' + ) resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert resp.json['data'][event_index]['disabled'] + def test_booking_api_meeting_different_durations_book_short(app, meetings_agenda, user): agenda_id = meetings_agenda.id meeting_type = MeetingType.objects.get(agenda=meetings_agenda) @@ -722,9 +842,12 @@ def test_booking_api_meeting_different_durations_book_short(app, meetings_agenda # the longer event at the same time shouldn't be available anymore resp_long2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) - assert len(resp_long.json['data']) == len([x for x in resp_long2.json['data'] if not x.get('disabled')]) + 1 + assert ( + len(resp_long.json['data']) == len([x for x in resp_long2.json['data'] if not x.get('disabled')]) + 1 + ) assert resp_long.json['data'][1:] == [x for x in resp_long2.json['data'] if not x.get('disabled')] + def test_booking_api_meeting_different_durations_book_long(app, meetings_agenda, user): agenda_id = meetings_agenda.id meeting_type = MeetingType.objects.get(agenda=meetings_agenda) @@ -745,7 +868,10 @@ def test_booking_api_meeting_different_durations_book_long(app, meetings_agenda, # this should have removed two short events resp_short2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_2.id) - assert len(resp_short.json['data']) == len([x for x in resp_short2.json['data'] if not x.get('disabled')]) + 2 + assert ( + len(resp_short.json['data']) + == len([x for x in resp_short2.json['data'] if not x.get('disabled')]) + 2 + ) # book another long event event_id = resp.json['data'][10]['id'] @@ -754,15 +880,18 @@ def test_booking_api_meeting_different_durations_book_long(app, meetings_agenda, assert Booking.objects.count() == 2 resp_short2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_2.id) - assert len(resp_short.json['data']) == len([x for x in resp_short2.json['data'] if not x.get('disabled')]) + 4 + assert ( + len(resp_short.json['data']) + == len([x for x in resp_short2.json['data'] if not x.get('disabled')]) + 4 + ) + def test_booking_api_with_data(app, some_data, user): agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id event = Event.objects.filter(agenda_id=agenda_id)[0] app.authorization = ('Basic', ('john.doe', 'password')) - resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda_id, event.id), - params={'hello': 'world'}) + resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda_id, event.id), params={'hello': 'world'}) assert Booking.objects.count() == 1 assert Booking.objects.all()[0].extra_data == {'hello': 'world'} @@ -801,11 +930,12 @@ def test_booking_api_available(app, some_data, meetings_agenda, user): assert 'places' not in resp.json # not for multiple booking - events = [x for x in Event.objects.filter(agenda=agenda).order_by('start_datetime') if x.in_bookable_period()][:2] + events = [ + x for x in Event.objects.filter(agenda=agenda).order_by('start_datetime') if x.in_bookable_period() + ][:2] slots = [x.pk for x in events] - resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, - params={'slots': slots, 'count': '3'}) + resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': '3'}) assert resp.json['err'] == 0 assert 'places' not in resp.json @@ -822,7 +952,7 @@ def test_booking_api_with_cancel_booking(app, some_data, user): # Book a new event and cancel previous booking resp = app.post_json( '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_1.id), - params={'cancel_booking_id': first_booking.pk} + params={'cancel_booking_id': first_booking.pk}, ) assert resp.json['err'] == 0 assert resp.json['cancelled_booking_id'] == first_booking.pk @@ -833,7 +963,7 @@ def test_booking_api_with_cancel_booking(app, some_data, user): # Cancelling an already cancelled booking returns an error resp = app.post_json( '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_1.id), - params={'cancel_booking_id': first_booking.pk} + params={'cancel_booking_id': first_booking.pk}, ) assert resp.json['err'] == 1 assert resp.json['reason'] == 'cancel booking: booking already cancelled' # legacy @@ -843,8 +973,7 @@ def test_booking_api_with_cancel_booking(app, some_data, user): # Cancelling a non existent booking returns an error resp = app.post_json( - '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_1.id), - params={'cancel_booking_id': '-1'} + '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_1.id), params={'cancel_booking_id': '-1'} ) assert resp.json['err'] == 1 assert resp.json['reason'] == 'cancel booking: booking does no exist' # legacy @@ -853,17 +982,14 @@ def test_booking_api_with_cancel_booking(app, some_data, user): assert Booking.objects.count() == 2 # Cancelling booking with different count than new booking - resp = app.post_json( - '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_2.id), - params={'count': 2} - ) + resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_2.id), params={'count': 2}) assert resp.json['err'] == 0 assert Booking.objects.count() == 4 booking_id = resp.json['booking_id'] resp = app.post_json( '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_3.id), - params={'cancel_booking_id': booking_id, 'count': 1} + params={'cancel_booking_id': booking_id, 'count': 1}, ) assert resp.json['err'] == 1 assert resp.json['reason'] == 'cancel booking: count is different' # legacy @@ -875,23 +1001,25 @@ def test_booking_api_with_cancel_booking(app, some_data, user): app.post_json( '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_0.id), params={'cancel_booking_id': 'no an integer'}, - status=400) + status=400, + ) # cancel_booking_id can be empty or null resp = app.post_json( - '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_0.id), - params={'cancel_booking_id': ''}) + '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_0.id), params={'cancel_booking_id': ''} + ) assert resp.json['err'] == 0 assert 'cancelled_booking_id' not in resp.json assert Booking.objects.count() == 5 resp = app.post_json( - '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_0.id), - params={'cancel_booking_id': None}) + '/api/agenda/%s/fillslot/%s/' % (agenda_id, event_0.id), params={'cancel_booking_id': None} + ) assert resp.json['err'] == 0 assert 'cancelled_booking_id' not in resp.json assert Booking.objects.count() == 6 + def test_booking_cancellation_api(app, some_data, user): agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id event = Event.objects.filter(agenda_id=agenda_id)[0] @@ -904,6 +1032,7 @@ def test_booking_cancellation_api(app, some_data, user): resp = app.delete('/api/booking/%s/' % booking_id) assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 1 + def test_booking_cancellation_post_api(app, some_data, user): agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id event = Event.objects.filter(agenda_id=agenda_id)[0] @@ -924,6 +1053,7 @@ def test_booking_cancellation_post_api(app, some_data, user): resp = app.post('/api/booking/%s/cancel/' % booking_id, status=200) assert resp.json['err'] == 1 + def test_booking_cancellation_post_meeting_api(app, meetings_agenda, user): agenda_id = Agenda.objects.filter(label=u'Foo bar Meeting')[0].id meeting_type = MeetingType.objects.get(agenda=meetings_agenda) @@ -948,6 +1078,7 @@ def test_booking_cancellation_post_meeting_api(app, meetings_agenda, user): resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert len([x for x in resp.json['data'] if not x.get('disabled')]) == nb_events - 1 + def test_soldout(app, some_data, user): agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0] @@ -971,6 +1102,7 @@ def test_soldout(app, some_data, user): assert resp.json['err_class'] == 'sold out' assert resp.json['err_desc'] == 'sold out' + def test_status(app, some_data, user): agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id event = Event.objects.filter(agenda_id=agenda_id)[0] @@ -1033,6 +1165,7 @@ def test_waiting_list_datetimes(app, some_data, user): assert not event.id in [x['id'] for x in resp.json['data'] if not x.get('disabled')] assert event.id in [x['id'] for x in resp.json['data'] if x.get('disabled')] + def test_waiting_list_booking(app, some_data, user): agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0] @@ -1074,6 +1207,7 @@ def test_waiting_list_booking(app, some_data, user): assert resp.json['err_class'] == 'sold out' assert resp.json['err_desc'] == 'sold out' + def test_accept_booking(app, some_data, user): agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0] @@ -1107,12 +1241,15 @@ def test_accept_booking(app, some_data, user): assert Booking.objects.filter(in_waiting_list=True).count() == 1 assert Booking.objects.filter(in_waiting_list=False).count() == 0 + def test_multiple_booking_api(app, some_data, user): agenda = Agenda.objects.filter(label=u'Foo bar')[0] event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0] resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id) - event_fillslot_url = [x for x in resp_datetimes.json['data'] if x['id'] == event.id][0]['api']['fillslot_url'] + event_fillslot_url = [x for x in resp_datetimes.json['data'] if x['id'] == event.id][0]['api'][ + 'fillslot_url' + ] app.authorization = ('Basic', ('john.doe', 'password')) resp = app.post('/api/agenda/%s/fillslot/%s/?count=NaN' % (agenda.slug, event.id), status=400) @@ -1199,10 +1336,13 @@ def test_multiple_booking_api(app, some_data, user): assert Event.objects.get(id=event.id).booked_places == 3 assert Event.objects.get(id=event.id).waiting_list == 2 + def test_multiple_booking_api_fillslots(app, some_data, user): agenda = Agenda.objects.filter(label=u'Foo bar')[0] # get slots of first 2 events - events = [x for x in Event.objects.filter(agenda=agenda).order_by('start_datetime') if x.in_bookable_period()][:2] + events = [ + x for x in Event.objects.filter(agenda=agenda).order_by('start_datetime') if x.in_bookable_period() + ][:2] events_ids = [x.id for x in events] resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id) slots = [x['id'] for x in resp_datetimes.json['data'] if x['id'] in events_ids] @@ -1214,8 +1354,9 @@ def test_multiple_booking_api_fillslots(app, some_data, user): assert resp.json['err_class'] == "invalid value for count (NaN)" assert resp.json['err_desc'] == "invalid value for count (NaN)" - resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, - params={'slots': slots, 'count': 'NaN'}, status=400) + resp = app.post( + '/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 'NaN'}, status=400 + ) assert resp.json['err'] == 1 assert resp.json['reason'] == "invalid payload" # legacy assert resp.json['err_class'] == "invalid payload" @@ -1223,8 +1364,7 @@ def test_multiple_booking_api_fillslots(app, some_data, user): assert 'count' in resp.json['errors'] # get 3 places on 2 slots - resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, - params={'slots': slots, 'count': '3'}) + resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': '3'}) # one booking with 5 children booking = Booking.objects.get(id=resp.json['booking_id']) cancel_url = resp.json['api']['cancel_url'] @@ -1236,8 +1376,7 @@ def test_multiple_booking_api_fillslots(app, some_data, user): for event in events: assert Event.objects.get(id=event.id).booked_places == 3 - resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, - params={'slots': slots, 'count': 2}) + resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 2}) for event in events: assert Event.objects.get(id=event.id).booked_places == 5 @@ -1251,8 +1390,7 @@ def test_multiple_booking_api_fillslots(app, some_data, user): events[0].waiting_list_places = 8 events[0].save() - resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, - params={'slots': slots, 'count': 5}) + resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 5}) for event in events: assert Event.objects.get(id=event.id).booked_places == 2 assert Event.objects.get(id=event.id).waiting_list == 5 @@ -1260,8 +1398,7 @@ def test_multiple_booking_api_fillslots(app, some_data, user): return - resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, - params={'slots': slots, 'count': 5}) + resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 5}) assert resp.json['err'] == 1 assert resp.json['reason'] == 'sold out' # legacy assert resp.json['err_class'] == 'sold out' @@ -1283,34 +1420,31 @@ def test_multiple_booking_api_fillslots(app, some_data, user): events[0].waiting_list_places = 2 events[0].save() - resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, - params={'slots': slots, 'count': 5}) + resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 5}) assert resp.json['err'] == 1 assert resp.json['reason'] == 'sold out' # legacy assert resp.json['err_class'] == 'sold out' assert resp.json['err_desc'] == 'sold out' - resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, - params={'slots': slots, 'count': 3}) + resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 3}) assert resp.json['err'] == 0 for event in events: assert Event.objects.get(id=event.id).booked_places == 3 assert Event.objects.get(id=event.id).waiting_list == 0 - resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, - params={'slots': slots, 'count': 3}) + resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 3}) assert resp.json['err'] == 1 assert resp.json['reason'] == 'sold out' # legacy assert resp.json['err_class'] == 'sold out' assert resp.json['err_desc'] == 'sold out' - resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, - params={'slots': slots, 'count': '2'}) + resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': '2'}) assert resp.json['err'] == 0 for event in events: assert Event.objects.get(id=event.id).booked_places == 3 assert Event.objects.get(id=event.id).waiting_list == 2 + def test_agenda_detail_api(app, some_data): agenda = Agenda.objects.get(slug='foo-bar') resp = app.get('/api/agenda/%s/' % agenda.slug) @@ -1324,6 +1458,7 @@ def test_agenda_detail_api(app, some_data): # unknown app.get('/api/agenda/whatever/', status=404) + def test_agenda_api_date_range(app, some_data): # test range limitation agenda2 = Agenda.objects.get(slug='foo-bar2') @@ -1339,8 +1474,7 @@ def test_agenda_api_date_range(app, some_data): day_events = ['8:00'] day = base_date + datetime.timedelta(days=idx) for event in day_events: - event_dt = datetime.datetime.combine( - day, datetime.datetime.strptime(event, '%H:%M').time()) + event_dt = datetime.datetime.combine(day, datetime.datetime.strptime(event, '%H:%M').time()) Event.objects.create(agenda=agenda2, start_datetime=make_aware(event_dt), places=2) params = {'date_start': base_date.isoformat()} @@ -1380,6 +1514,7 @@ def test_agenda_api_date_range(app, some_data): assert resp.json['data'][0]['datetime'] == '2017-05-30 09:00:00' assert resp.json['data'][-1]['datetime'] == '2017-05-30 11:00:00' + def test_agenda_meeting_api_multiple_desk(app, meetings_agenda, user): app.authorization = ('Basic', ('john.doe', 'password')) agenda_id = meetings_agenda.slug @@ -1396,15 +1531,19 @@ def test_agenda_meeting_api_multiple_desk(app, meetings_agenda, user): time_period = meetings_agenda.desk_set.first().timeperiod_set.first() desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda) TimePeriod.objects.create( - start_time=time_period.start_time, end_time=time_period.end_time, - weekday=time_period.weekday, desk=desk2) + start_time=time_period.start_time, + end_time=time_period.end_time, + weekday=time_period.weekday, + desk=desk2, + ) resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) event_id = resp.json['data'][1]['id'] resp_booking = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id)) assert Booking.objects.count() == 2 - assert resp_booking.json['datetime'] == localtime(Booking.objects.last().event.start_datetime - ).strftime('%Y-%m-%d %H:%M:%S') + assert resp_booking.json['datetime'] == localtime(Booking.objects.last().event.start_datetime).strftime( + '%Y-%m-%d %H:%M:%S' + ) resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert len(resp.json['data']) == len([x for x in resp2.json['data'] if not x['disabled']]) + 1 @@ -1430,8 +1569,9 @@ def test_agenda_meeting_api_multiple_desk(app, meetings_agenda, user): resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id)) queries_count_fillslot1 = len(ctx.captured_queries) - assert resp_booking.json['datetime'] == localtime(Booking.objects.last().event.start_datetime - ).strftime('%Y-%m-%d %H:%M:%S') + assert resp_booking.json['datetime'] == localtime(Booking.objects.last().event.start_datetime).strftime( + '%Y-%m-%d %H:%M:%S' + ) cancel_url = resp.json['api']['cancel_url'] resp3 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) @@ -1446,8 +1586,9 @@ def test_agenda_meeting_api_multiple_desk(app, meetings_agenda, user): resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id)) assert Booking.objects.count() == 4 assert Booking.objects.exclude(cancellation_datetime__isnull=True).count() == 2 - assert resp_booking.json['datetime'] == localtime(Booking.objects.last().event.start_datetime - ).strftime('%Y-%m-%d %H:%M:%S') + assert resp_booking.json['datetime'] == localtime(Booking.objects.last().event.start_datetime).strftime( + '%Y-%m-%d %H:%M:%S' + ) # try booking the same timeslot again and fail resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id)) @@ -1467,6 +1608,7 @@ def test_agenda_meeting_api_multiple_desk(app, meetings_agenda, user): app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert queries_count_datetime1 == len(ctx.captured_queries) + def test_agenda_meeting_api_fillslots_multiple_desks(app, meetings_agenda, user): app.authorization = ('Basic', ('john.doe', 'password')) agenda_id = meetings_agenda.slug @@ -1476,8 +1618,11 @@ def test_agenda_meeting_api_fillslots_multiple_desks(app, meetings_agenda, user) time_period = meetings_agenda.desk_set.first().timeperiod_set.first() desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda) TimePeriod.objects.create( - start_time=time_period.start_time, end_time=time_period.end_time, - weekday=time_period.weekday, desk=desk2) + start_time=time_period.start_time, + end_time=time_period.end_time, + weekday=time_period.weekday, + desk=desk2, + ) resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) slots = [x['id'] for x in resp.json['data'][:3]] @@ -1485,6 +1630,7 @@ def test_agenda_meeting_api_fillslots_multiple_desks(app, meetings_agenda, user) def get_free_places(): resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) return len([x for x in resp.json['data'] if not x['disabled']]) + start_free_places = get_free_places() # booking 3 slots on desk 1 @@ -1541,6 +1687,7 @@ def test_agenda_meeting_api_fillslots_multiple_desks(app, meetings_agenda, user) assert resp.json['desk']['slug'] == desk1 assert get_free_places() == start_free_places - len(slots) + def test_agenda_meeting_same_day(app, meetings_agenda, mock_now, user): app.authorization = ('Basic', ('john.doe', 'password')) agenda = Agenda(label='Foo', kind='meetings') @@ -1553,20 +1700,19 @@ def test_agenda_meeting_same_day(app, meetings_agenda, mock_now, user): desk2 = Desk.objects.create(label='bar', agenda=agenda) for weekday in range(7): TimePeriod.objects.create( - weekday=weekday, start_time=datetime.time(11, 0), end_time=datetime.time(12, 30), - desk=desk1) + weekday=weekday, start_time=datetime.time(11, 0), end_time=datetime.time(12, 30), desk=desk1 + ) TimePeriod.objects.create( - weekday=weekday, start_time=datetime.time(11, 0), end_time=datetime.time(12, 30), - desk=desk2) + weekday=weekday, start_time=datetime.time(11, 0), end_time=datetime.time(12, 30), desk=desk2 + ) resp = app.get(datetime_url) event_data = resp.json['data'][0] # check first proposed date is on the same day unless we're past the last # open timeperiod. event_datetime = datetime.datetime.strptime(event_data['datetime'], '%Y-%m-%d %H:%M:%S').timetuple() - assert (event_datetime[:3] == mock_now.timetuple()[:3] and - event_datetime[3:5] >= mock_now.timetuple()[3:5]) or ( - event_datetime[:3] > mock_now.timetuple()[:3] and - event_datetime[3:5] < mock_now.timetuple()[3:5]) + assert ( + event_datetime[:3] == mock_now.timetuple()[:3] and event_datetime[3:5] >= mock_now.timetuple()[3:5] + ) or (event_datetime[:3] > mock_now.timetuple()[:3] and event_datetime[3:5] < mock_now.timetuple()[3:5]) # check booking works first_booking_url = resp.json['data'][0]['api']['fillslot_url'] @@ -1598,8 +1744,8 @@ def test_agenda_meeting_next_day(app, meetings_agenda, mock_now, user): desk = Desk.objects.create(label='foo', agenda=agenda) for weekday in range(7): time_period = TimePeriod.objects.create( - weekday=weekday, start_time=datetime.time(11, 0), end_time=datetime.time(12, 30), - desk=desk) + weekday=weekday, start_time=datetime.time(11, 0), end_time=datetime.time(12, 30), desk=desk + ) resp = app.get(datetime_url) event_data = resp.json['data'][0] # check all proposed dates are on the next day @@ -1631,8 +1777,10 @@ def test_agenda_meeting_api_exception(app, meetings_agenda, user): desk = meetings_agenda.desk_set.first() # test exception at the lowest limit excp1 = TimePeriodException.objects.create( - desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)), - end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0))) + desk=desk, + start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)), + end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)), + ) resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert len(resp.json['data']) == len(resp2.json['data']) + 4 @@ -1650,7 +1798,8 @@ def test_agenda_meeting_api_exception(app, meetings_agenda, user): TimePeriodException.objects.create( desk=excp1.desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 15, 0)), - end_datetime=make_aware(datetime.datetime(2017, 5, 23, 9, 0))) + end_datetime=make_aware(datetime.datetime(2017, 5, 23, 9, 0)), + ) resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert len(resp.json['data']) == len(resp2.json['data']) + 6 @@ -1659,8 +1808,11 @@ def test_agenda_meeting_api_exception(app, meetings_agenda, user): desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda) time_period = desk.timeperiod_set.first() TimePeriod.objects.create( - desk=desk2, start_time=time_period.start_time, end_time=time_period.end_time, - weekday=time_period.weekday) + desk=desk2, + start_time=time_period.start_time, + end_time=time_period.end_time, + weekday=time_period.weekday, + ) resp3 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert len(resp.json['data']) == len(resp3.json['data']) + 2 # +2 because excp1 changed @@ -1668,7 +1820,8 @@ def test_agenda_meeting_api_exception(app, meetings_agenda, user): TimePeriodException.objects.create( desk=desk2, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 9, 0)), - end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0))) + end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)), + ) booking_url = resp3.json['data'][0]['api']['fillslot_url'] resp = app.post(booking_url) assert resp.json['err'] == 1 @@ -1683,34 +1836,68 @@ def test_agenda_meeting_api_in_between_exceptions(app, meetings_agenda, user): TimePeriodException.objects.create( desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)), - end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0))) + end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)), + ) resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert len(resp.json['data']) == len(resp2.json['data']) + 4 # exclude slots on 2017-05-30 and 2017-07-10 date_2017_05_30 = datetime.datetime(2017, 5, 30).date() date_2017_07_10 = datetime.datetime(2017, 7, 10).date() - count_on_2017_05_30 = len([ - datum for datum in resp.json['data'] if datetime_from_str(datum['datetime']).date() == date_2017_05_30]) - count_on_2017_07_10 = len([ - datum for datum in resp.json['data'] if datetime_from_str(datum['datetime']).date() == date_2017_07_10]) + count_on_2017_05_30 = len( + [ + datum + for datum in resp.json['data'] + if datetime_from_str(datum['datetime']).date() == date_2017_05_30 + ] + ) + count_on_2017_07_10 = len( + [ + datum + for datum in resp.json['data'] + if datetime_from_str(datum['datetime']).date() == date_2017_07_10 + ] + ) TimePeriodException.objects.create( desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 30, 8, 0)), - end_datetime=make_aware(datetime.datetime(2017, 5, 30, 18, 0))) + end_datetime=make_aware(datetime.datetime(2017, 5, 30, 18, 0)), + ) TimePeriodException.objects.create( desk=desk, start_datetime=make_aware(datetime.datetime(2017, 7, 10, 8, 0)), - end_datetime=make_aware(datetime.datetime(2017, 7, 10, 18, 0))) + end_datetime=make_aware(datetime.datetime(2017, 7, 10, 18, 0)), + ) resp3 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert len(resp2.json['data']) == len(resp3.json['data']) + count_on_2017_05_30 + count_on_2017_07_10 - assert len([datum for datum in resp3.json['data'] if datetime_from_str(datum['datetime']).date() == date_2017_05_30]) == 0 - assert len([datum for datum in resp3.json['data'] if datetime_from_str(datum['datetime']).date() == date_2017_07_10]) == 0 + assert ( + len( + [ + datum + for datum in resp3.json['data'] + if datetime_from_str(datum['datetime']).date() == date_2017_05_30 + ] + ) + == 0 + ) + assert ( + len( + [ + datum + for datum in resp3.json['data'] + if datetime_from_str(datum['datetime']).date() == date_2017_07_10 + ] + ) + == 0 + ) # with a second desk with the same time periods desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda) for time_period in desk.timeperiod_set.all(): TimePeriod.objects.create( - desk=desk2, start_time=time_period.start_time, end_time=time_period.end_time, - weekday=time_period.weekday) + desk=desk2, + start_time=time_period.start_time, + end_time=time_period.end_time, + weekday=time_period.weekday, + ) resp4 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) assert len(resp.json['data']) == len(resp4.json['data']) @@ -1722,8 +1909,11 @@ def test_agenda_meeting_api_desk_info(app, meetings_agenda, user): desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda) for time_period in desk.timeperiod_set.all(): TimePeriod.objects.create( - desk=desk2, start_time=time_period.start_time, end_time=time_period.end_time, - weekday=time_period.weekday) + desk=desk2, + start_time=time_period.start_time, + end_time=time_period.end_time, + weekday=time_period.weekday, + ) resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) booking_url = resp.json['data'][0]['api']['fillslot_url'] booking_url2 = resp.json['data'][3]['api']['fillslot_url'] @@ -1792,9 +1982,13 @@ def test_agenda_meeting_gcd_durations(app, meetings_agenda, user): assert Booking.objects.count() == 4 resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id) - assert [x for x in resp.json['data'] if not x.get('disabled')][0]['datetime'].startswith('2017-05-22 12:00:00') + assert [x for x in resp.json['data'] if not x.get('disabled')][0]['datetime'].startswith( + '2017-05-22 12:00:00' + ) resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id) - assert [x for x in resp.json['data'] if not x.get('disabled')][0]['datetime'].startswith('2017-05-22 12:00:00') + assert [x for x in resp.json['data'] if not x.get('disabled')][0]['datetime'].startswith( + '2017-05-22 12:00:00' + ) def test_agenda_meeting_gcd_durations_and_exceptions(app, meetings_agenda, user): @@ -1820,7 +2014,8 @@ def test_agenda_meeting_gcd_durations_and_exceptions(app, meetings_agenda, user) TimePeriodException.objects.create( desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 20)), - end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0))) + end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)), + ) resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id) assert len(resp.json['data']) == 1 @@ -1830,13 +2025,18 @@ def test_agenda_meeting_gcd_durations_and_exceptions(app, meetings_agenda, user) def test_datetimes_api_meetings_agenda_start_hour_change(app, meetings_agenda): meeting_type = MeetingType.objects.get(agenda=meetings_agenda) - api_url = '/api/agenda/%s/meetings/%s/datetimes/' % ( - meeting_type.agenda.slug, meeting_type.slug) + api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug) resp = app.get(api_url) dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M') - ev = Event(agenda=meetings_agenda, meeting_type=meeting_type, - places=1, full=False, start_datetime=make_aware(dt), desk=Desk.objects.first()) + ev = Event( + agenda=meetings_agenda, + meeting_type=meeting_type, + places=1, + full=False, + start_datetime=make_aware(dt), + desk=Desk.objects.first(), + ) ev.save() booking = Booking(event=ev) booking.save() diff --git a/tests/test_api_utils.py b/tests/test_api_utils.py index 365fa094..07ce4a90 100644 --- a/tests/test_api_utils.py +++ b/tests/test_api_utils.py @@ -3,13 +3,16 @@ import pytest from chrono.api.utils import Response -@pytest.mark.parametrize('data, expected', [ - (None, None), - ({}, {}), - ({'reason': 'foo'}, {'reason': 'foo'}), - ({'err_class': 'foo'}, {'err_class': 'foo', 'reason': 'foo'}), - ({'bar': 'foo'}, {'bar': 'foo'}), -]) +@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 diff --git a/tests/test_data_migrations.py b/tests/test_data_migrations.py index 16244ea4..afb1698d 100644 --- a/tests/test_data_migrations.py +++ b/tests/test_data_migrations.py @@ -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 diff --git a/tests/test_import_export.py b/tests/test_import_export.py index c3fa948f..f49c6720 100644 --- a/tests/test_import_export.py +++ b/tests/test_import_export.py @@ -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 diff --git a/tests/test_manager.py b/tests/test_manager.py index 8913345c..ca1377e6 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -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 '

    Agendas

    ' 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 '

    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 '

    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 '

    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' - '"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') + 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', + ) 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,16 +975,18 @@ 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() resp = resp.click('Settings') resp = resp.click('Add a time period exception') - resp = resp.form.submit() # submit empty form + resp = resp.form.submit() # submit empty form # fields should be marked with errors assert resp.text.count('This field is required.') == 2 # try again with data in fields @@ -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') @@ -1193,7 +1253,7 @@ def test_agenda_import_time_period_exception_with_remote_ics(mocked_get, app, ad assert 'ics_url' in resp.form.fields resp.form['ics_url'] = 'http://example.com/foo.ics' mocked_response = mock.Mock() - mocked_response.text = """BEGIN:VCALENDAR + mocked_response.text = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//foo.bar//EN BEGIN:VEVENT @@ -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,15 +1288,16 @@ 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') resp = resp.click('upload') resp.form['ics_url'] = 'http://example.com/foo.ics' mocked_response = mock.Mock() - mocked_response.text = """BEGIN:VCALENDAR + mocked_response.text = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//foo.bar//EN BEGIN:VEVENT @@ -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,15 +1333,16 @@ 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') resp = resp.click('upload') resp.form['ics_url'] = 'http://example.com/foo.ics' mocked_response = mock.Mock() - mocked_response.text = """BEGIN:VCALENDAR + mocked_response.text = """BEGIN:VCALENDAR VERSION:2.0 PRODID:-//foo.bar//EN BEGIN:VEVENT @@ -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('11 p.m.' 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') @@ -1535,20 +1609,20 @@ def test_agenda_month_view(app, admin_user, manager_user, api_user): today = datetime.date.today() assert resp.request.url.endswith('%s/%s/' % (today.year, today.month)) - assert 'Day view' in resp.text # date view link should be present + assert 'Day view' in resp.text # date view link should be present assert 'No opening hours this month.' in resp.text 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 '