Compare commits
19 Commits
b1378c5925
...
86e25f5fd2
Author | SHA1 | Date |
---|---|---|
Yann Weber | 86e25f5fd2 | |
Frédéric Péters | 07512150e8 | |
Lauréline Guérin | 2576350aae | |
Lauréline Guérin | 2187bf3dde | |
Lauréline Guérin | 4add868dd9 | |
Valentin Deniaud | 9c19321fb9 | |
Lauréline Guérin | a88db00e04 | |
Lauréline Guérin | eecbb80809 | |
Lauréline Guérin | e0f1d9541d | |
Lauréline Guérin | 701733da57 | |
Lauréline Guérin | d709fa9bc7 | |
Lauréline Guérin | 1d00c5fce8 | |
Lauréline Guérin | bd06f2b82f | |
Lauréline Guérin | ecf0ffd96e | |
Lauréline Guérin | 1b1bc13c82 | |
Lauréline Guérin | 06ab6f12b7 | |
Lauréline Guérin | 024b34b34f | |
Lauréline Guérin | 0ea056dcd5 | |
Lauréline Guérin | 3cef873ce4 |
|
@ -10,6 +10,6 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='agenda',
|
||||
name='desk_simple_management',
|
||||
field=models.BooleanField(default=False),
|
||||
field=models.BooleanField(default=False, verbose_name='Global desk management'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -62,13 +62,14 @@ from django.template import (
|
|||
VariableDoesNotExist,
|
||||
engines,
|
||||
)
|
||||
from django.template.defaultfilters import yesno
|
||||
from django.urls import reverse
|
||||
from django.utils import functional
|
||||
from django.utils.dates import WEEKDAYS
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import escape
|
||||
from django.utils.html import escape, linebreaks
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import slugify
|
||||
|
@ -177,12 +178,35 @@ def booking_template_validator(value):
|
|||
pass
|
||||
|
||||
|
||||
class WithInspectMixin:
|
||||
def get_inspect_fields(self, keys=None):
|
||||
keys = keys or self.get_inspect_keys()
|
||||
for key in keys:
|
||||
field = self._meta.get_field(key)
|
||||
get_value_method = 'get_%s_inspect_value' % key
|
||||
get_display_method = 'get_%s_display' % key
|
||||
if hasattr(self, get_value_method):
|
||||
value = getattr(self, get_value_method)()
|
||||
elif hasattr(self, get_display_method):
|
||||
value = getattr(self, get_display_method)()
|
||||
else:
|
||||
value = getattr(self, key)
|
||||
if value in [None, '']:
|
||||
continue
|
||||
if isinstance(value, bool):
|
||||
value = yesno(value)
|
||||
if isinstance(field, models.TextField):
|
||||
value = mark_safe(linebreaks(value))
|
||||
yield (field.verbose_name, value)
|
||||
|
||||
|
||||
TimeSlot = collections.namedtuple(
|
||||
'TimeSlot', ['start_datetime', 'end_datetime', 'full', 'desk', 'booked_for_external_user']
|
||||
)
|
||||
|
||||
|
||||
class Agenda(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
||||
# pylint: disable=too-many-public-methods
|
||||
class Agenda(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.Model):
|
||||
# mark temporarily restored snapshots
|
||||
snapshot = models.ForeignKey(
|
||||
AgendaSnapshot, on_delete=models.CASCADE, null=True, related_name='temporary_instance'
|
||||
|
@ -250,7 +274,7 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
booking_form_url = models.CharField(
|
||||
_('Booking form URL'), max_length=200, blank=True, validators=[django_template_validator]
|
||||
)
|
||||
desk_simple_management = models.BooleanField(default=False)
|
||||
desk_simple_management = models.BooleanField(_('Global desk management'), default=False)
|
||||
mark_event_checked_auto = models.BooleanField(
|
||||
_('Automatically mark event as checked when all bookings have been checked'), default=False
|
||||
)
|
||||
|
@ -475,6 +499,7 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
yield from desk.get_dependencies()
|
||||
if self.kind == 'events':
|
||||
yield self.events_type
|
||||
yield from self.desk_set.get().get_dependencies()
|
||||
|
||||
def export_json(self):
|
||||
agenda = {
|
||||
|
@ -484,11 +509,11 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
'category': self.category.slug if self.category else None,
|
||||
'minimal_booking_delay': self.minimal_booking_delay,
|
||||
'maximal_booking_delay': self.maximal_booking_delay,
|
||||
'anonymize_delay': self.anonymize_delay,
|
||||
'permissions': {
|
||||
'view': self.view_role.name if self.view_role else None,
|
||||
'edit': self.edit_role.name if self.edit_role else None,
|
||||
},
|
||||
'resources': [x.slug for x in self.resources.all()],
|
||||
'default_view': self.default_view,
|
||||
}
|
||||
if hasattr(self, 'reminder_settings'):
|
||||
|
@ -516,6 +541,7 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
agenda['meetingtypes'] = [x.export_json() for x in self.meetingtype_set.filter(deleted=False)]
|
||||
agenda['desks'] = [desk.export_json() for desk in self.desk_set.all()]
|
||||
agenda['desk_simple_management'] = self.desk_simple_management
|
||||
agenda['resources'] = [x.slug for x in self.resources.all()]
|
||||
elif self.kind == 'virtual':
|
||||
agenda['excluded_timeperiods'] = [x.export_json() for x in self.excluded_timeperiods.all()]
|
||||
agenda['real_agendas'] = [{'slug': x.slug, 'kind': x.kind} for x in self.real_agendas.all()]
|
||||
|
@ -559,7 +585,8 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
slug = data.pop('slug')
|
||||
qs_kwargs = {}
|
||||
if snapshot:
|
||||
qs_kwargs = {'snapshot': snapshot}
|
||||
qs_kwargs = {'snapshot': snapshot} # don't take slug from snapshot: it has to be unique !
|
||||
data['slug'] = str(uuid.uuid4()) # random slug
|
||||
else:
|
||||
qs_kwargs = {'slug': slug}
|
||||
agenda, created = cls.objects.update_or_create(defaults=data, **qs_kwargs)
|
||||
|
@ -623,6 +650,62 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
|
||||
return created, agenda
|
||||
|
||||
def get_inspect_keys(self):
|
||||
keys = ['label', 'slug', 'kind', 'category', 'anonymize_delay', 'default_view']
|
||||
if self.kind == 'events':
|
||||
keys += ['booking_form_url', 'events_type']
|
||||
elif self.kind == 'meetings':
|
||||
keys += ['desk_simple_management']
|
||||
return keys
|
||||
|
||||
def get_permissions_inspect_fields(self):
|
||||
yield from self.get_inspect_fields(keys=['edit_role', 'view_role'])
|
||||
|
||||
def get_display_inspect_fields(self):
|
||||
keys = []
|
||||
if self.kind == 'events':
|
||||
keys += ['event_display_template']
|
||||
keys += [
|
||||
'booking_user_block_template',
|
||||
]
|
||||
yield from self.get_inspect_fields(keys=keys)
|
||||
|
||||
def get_booking_check_inspect_fields(self):
|
||||
keys = [
|
||||
'booking_check_filters',
|
||||
'mark_event_checked_auto',
|
||||
'disable_check_update',
|
||||
'enable_check_for_future_events',
|
||||
'booking_extra_user_block_template',
|
||||
]
|
||||
yield from self.get_inspect_fields(keys=keys)
|
||||
|
||||
def get_invoicing_inspect_fields(self):
|
||||
keys = ['invoicing_unit', 'invoicing_tolerance']
|
||||
yield from self.get_inspect_fields(keys=keys)
|
||||
|
||||
def get_notifications_inspect_fields(self):
|
||||
if hasattr(self, 'notifications_settings'):
|
||||
yield from self.notifications_settings.get_inspect_fields()
|
||||
return []
|
||||
|
||||
def get_reminder_inspect_fields(self):
|
||||
if hasattr(self, 'reminder_settings'):
|
||||
yield from self.reminder_settings.get_inspect_fields()
|
||||
return []
|
||||
|
||||
def get_booking_delays_inspect_fields(self):
|
||||
keys = [
|
||||
'minimal_booking_delay',
|
||||
'minimal_booking_delay_in_working_days',
|
||||
'maximal_booking_delay',
|
||||
'minimal_booking_time',
|
||||
]
|
||||
yield from self.get_inspect_fields(keys=keys)
|
||||
|
||||
def get_kind_inspect_value(self):
|
||||
return self.get_real_kind_display()
|
||||
|
||||
def duplicate(self, label=None):
|
||||
# clone current agenda
|
||||
new_agenda = copy.deepcopy(self)
|
||||
|
@ -1759,7 +1842,7 @@ WEEK_CHOICES = [
|
|||
]
|
||||
|
||||
|
||||
class TimePeriod(models.Model):
|
||||
class TimePeriod(WithInspectMixin, models.Model):
|
||||
weekday = models.IntegerField(_('Week day'), choices=WEEKDAYS_LIST, null=True)
|
||||
weekday_indexes = ArrayField(
|
||||
models.IntegerField(choices=WEEK_CHOICES),
|
||||
|
@ -1826,6 +1909,15 @@ class TimePeriod(models.Model):
|
|||
'end_time': self.end_time.strftime('%H:%M'),
|
||||
}
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return [
|
||||
'weekday',
|
||||
'weekday_indexes',
|
||||
'date',
|
||||
'start_time',
|
||||
'end_time',
|
||||
]
|
||||
|
||||
def duplicate(self, desk_target=None, agenda_target=None):
|
||||
# clone current period
|
||||
new_period = copy.deepcopy(self)
|
||||
|
@ -2033,7 +2125,7 @@ class SharedTimePeriod:
|
|||
start_datetime += datetime.timedelta(days=7)
|
||||
|
||||
|
||||
class MeetingType(models.Model):
|
||||
class MeetingType(WithInspectMixin, models.Model):
|
||||
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE)
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
slug = models.SlugField(_('Identifier'), max_length=160)
|
||||
|
@ -2070,6 +2162,13 @@ class MeetingType(models.Model):
|
|||
'duration': self.duration,
|
||||
}
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return [
|
||||
'label',
|
||||
'slug',
|
||||
'duration',
|
||||
]
|
||||
|
||||
def duplicate(self, agenda_target=None):
|
||||
new_meeting_type = copy.deepcopy(self)
|
||||
new_meeting_type.pk = None
|
||||
|
@ -2091,7 +2190,7 @@ class MeetingType(models.Model):
|
|||
)
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
class Event(WithInspectMixin, models.Model):
|
||||
id = models.BigAutoField(primary_key=True)
|
||||
INTERVAL_CHOICES = [
|
||||
(1, _('Every week')),
|
||||
|
@ -2659,6 +2758,23 @@ class Event(models.Model):
|
|||
'duration': self.duration,
|
||||
}
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return [
|
||||
'label',
|
||||
'slug',
|
||||
'description',
|
||||
'start_datetime',
|
||||
'duration',
|
||||
'recurrence_days',
|
||||
'recurrence_week_interval',
|
||||
'recurrence_end_date',
|
||||
'publication_datetime',
|
||||
'places',
|
||||
'waiting_list_places',
|
||||
'url',
|
||||
'pricing',
|
||||
]
|
||||
|
||||
def duplicate(self, agenda_target=None, primary_event=None, label=None, start_datetime=None):
|
||||
new_event = copy.deepcopy(self)
|
||||
new_event.pk = None
|
||||
|
@ -2843,7 +2959,7 @@ class Event(models.Model):
|
|||
return custom_fields
|
||||
|
||||
|
||||
class EventsType(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
||||
class EventsType(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.Model):
|
||||
# mark temporarily restored snapshots
|
||||
snapshot = models.ForeignKey(
|
||||
EventsTypeSnapshot, on_delete=models.CASCADE, null=True, related_name='temporary_instance'
|
||||
|
@ -2903,7 +3019,8 @@ class EventsType(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
slug = data.pop('slug')
|
||||
qs_kwargs = {}
|
||||
if snapshot:
|
||||
qs_kwargs = {'snapshot': snapshot}
|
||||
qs_kwargs = {'snapshot': snapshot} # don't take slug from snapshot: it has to be unique !
|
||||
data['slug'] = str(uuid.uuid4()) # random slug
|
||||
else:
|
||||
qs_kwargs = {'slug': slug}
|
||||
events_type, created = cls.objects.update_or_create(defaults=data, **qs_kwargs)
|
||||
|
@ -2916,6 +3033,9 @@ class EventsType(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
'custom_fields': self.custom_fields,
|
||||
}
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return ['label', 'slug']
|
||||
|
||||
|
||||
class BookingColor(models.Model):
|
||||
COLOR_COUNT = 8
|
||||
|
@ -3310,7 +3430,7 @@ class BookingCheck(models.Model):
|
|||
OpeningHour = collections.namedtuple('OpeningHour', ['begin', 'end'])
|
||||
|
||||
|
||||
class Desk(models.Model):
|
||||
class Desk(WithInspectMixin, models.Model):
|
||||
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE)
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
slug = models.SlugField(_('Identifier'), max_length=160)
|
||||
|
@ -3373,6 +3493,12 @@ class Desk(models.Model):
|
|||
'unavailability_calendars': [{'slug': x.slug} for x in self.unavailability_calendars.all()],
|
||||
}
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return [
|
||||
'label',
|
||||
'slug',
|
||||
]
|
||||
|
||||
def duplicate(self, label=None, agenda_target=None, reset_slug=True):
|
||||
# clone current desk
|
||||
new_desk = copy.deepcopy(self)
|
||||
|
@ -3474,7 +3600,7 @@ class Desk(models.Model):
|
|||
).delete() # source was not in settings anymore
|
||||
|
||||
|
||||
class Resource(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
||||
class Resource(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.Model):
|
||||
# mark temporarily restored snapshots
|
||||
snapshot = models.ForeignKey(
|
||||
ResourceSnapshot, on_delete=models.CASCADE, null=True, related_name='temporary_instance'
|
||||
|
@ -3524,7 +3650,8 @@ class Resource(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
slug = data.pop('slug')
|
||||
qs_kwargs = {}
|
||||
if snapshot:
|
||||
qs_kwargs = {'snapshot': snapshot}
|
||||
qs_kwargs = {'snapshot': snapshot} # don't take slug from snapshot: it has to be unique !
|
||||
data['slug'] = str(uuid.uuid4()) # random slug
|
||||
else:
|
||||
qs_kwargs = {'slug': slug}
|
||||
resource, created = cls.objects.update_or_create(defaults=data, **qs_kwargs)
|
||||
|
@ -3537,8 +3664,11 @@ class Resource(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
'description': self.description,
|
||||
}
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return ['label', 'slug', 'description']
|
||||
|
||||
class Category(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
||||
|
||||
class Category(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.Model):
|
||||
# mark temporarily restored snapshots
|
||||
snapshot = models.ForeignKey(
|
||||
CategorySnapshot, on_delete=models.CASCADE, null=True, related_name='temporary_instance'
|
||||
|
@ -3581,7 +3711,8 @@ class Category(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
slug = data.pop('slug')
|
||||
qs_kwargs = {}
|
||||
if snapshot:
|
||||
qs_kwargs = {'snapshot': snapshot}
|
||||
qs_kwargs = {'snapshot': snapshot} # don't take slug from snapshot: it has to be unique !
|
||||
data['slug'] = str(uuid.uuid4()) # random slug
|
||||
else:
|
||||
qs_kwargs = {'slug': slug}
|
||||
category, created = cls.objects.update_or_create(defaults=data, **qs_kwargs)
|
||||
|
@ -3593,12 +3724,15 @@ class Category(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
|||
'slug': self.slug,
|
||||
}
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return ['label', 'slug']
|
||||
|
||||
|
||||
def ics_directory_path(instance, filename):
|
||||
return f'ics/{str(uuid.uuid4())}/{filename}'
|
||||
|
||||
|
||||
class TimePeriodExceptionSource(models.Model):
|
||||
class TimePeriodExceptionSource(WithInspectMixin, models.Model):
|
||||
desk = models.ForeignKey(Desk, on_delete=models.CASCADE, null=True)
|
||||
unavailability_calendar = models.ForeignKey('UnavailabilityCalendar', on_delete=models.CASCADE, null=True)
|
||||
ics_filename = models.CharField(null=True, max_length=256)
|
||||
|
@ -3841,15 +3975,18 @@ class TimePeriodExceptionSource(models.Model):
|
|||
data = clean_import_data(cls, data)
|
||||
|
||||
if data.get('ics_file'):
|
||||
data['ics_file'] = ContentFile(base64.b64decode(data['ics_file']), name=data['ics_filename'])
|
||||
try:
|
||||
data['ics_file'] = ContentFile(base64.b64decode(data['ics_file']), name=data['ics_filename'])
|
||||
except base64.binascii.Error:
|
||||
raise AgendaImportError(_('Bad ics file'))
|
||||
|
||||
desk = data.pop('desk')
|
||||
settings_slug = data.pop('settings_slug')
|
||||
ics_url = data.pop('ics_url', None)
|
||||
ics_filename = data.pop('ics_filename', None)
|
||||
source, _ = cls.objects.update_or_create(
|
||||
source = cls.objects.update_or_create(
|
||||
desk=desk, settings_slug=settings_slug, ics_filename=ics_filename, ics_url=ics_url, defaults=data
|
||||
)
|
||||
)[0]
|
||||
if settings_slug:
|
||||
if source.enabled:
|
||||
source.enable()
|
||||
|
@ -3869,8 +4006,18 @@ class TimePeriodExceptionSource(models.Model):
|
|||
'enabled': self.enabled,
|
||||
}
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return [
|
||||
'ics_filename',
|
||||
'ics_file',
|
||||
'ics_url',
|
||||
'settings_slug',
|
||||
'settings_label',
|
||||
'enabled',
|
||||
]
|
||||
|
||||
class UnavailabilityCalendar(WithSnapshotMixin, WithApplicationMixin, models.Model):
|
||||
|
||||
class UnavailabilityCalendar(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.Model):
|
||||
# mark temporarily restored snapshots
|
||||
snapshot = models.ForeignKey(
|
||||
UnavailabilityCalendarSnapshot, on_delete=models.CASCADE, null=True, related_name='temporary_instance'
|
||||
|
@ -3966,7 +4113,8 @@ class UnavailabilityCalendar(WithSnapshotMixin, WithApplicationMixin, models.Mod
|
|||
slug = data.pop('slug')
|
||||
qs_kwargs = {}
|
||||
if snapshot:
|
||||
qs_kwargs = {'snapshot': snapshot}
|
||||
qs_kwargs = {'snapshot': snapshot} # don't take slug from snapshot: it has to be unique !
|
||||
data['slug'] = str(uuid.uuid4()) # random slug
|
||||
else:
|
||||
qs_kwargs = {'slug': slug}
|
||||
unavailability_calendar, created = cls.objects.update_or_create(defaults=data, **qs_kwargs)
|
||||
|
@ -3978,6 +4126,12 @@ class UnavailabilityCalendar(WithSnapshotMixin, WithApplicationMixin, models.Mod
|
|||
|
||||
return created, unavailability_calendar
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return ['label', 'slug']
|
||||
|
||||
def get_permissions_inspect_fields(self):
|
||||
yield from self.get_inspect_fields(keys=['edit_role', 'view_role'])
|
||||
|
||||
|
||||
class TimePeriodExceptionGroup(models.Model):
|
||||
unavailability_calendar = models.ForeignKey(UnavailabilityCalendar, on_delete=models.CASCADE)
|
||||
|
@ -3992,7 +4146,7 @@ class TimePeriodExceptionGroup(models.Model):
|
|||
return self.label
|
||||
|
||||
|
||||
class TimePeriodException(models.Model):
|
||||
class TimePeriodException(WithInspectMixin, models.Model):
|
||||
desk = models.ForeignKey(Desk, on_delete=models.CASCADE, null=True)
|
||||
unavailability_calendar = models.ForeignKey(UnavailabilityCalendar, on_delete=models.CASCADE, null=True)
|
||||
source = models.ForeignKey(TimePeriodExceptionSource, on_delete=models.CASCADE, null=True)
|
||||
|
@ -4106,6 +4260,13 @@ class TimePeriodException(models.Model):
|
|||
'update_datetime': export_datetime(self.update_datetime),
|
||||
}
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return [
|
||||
'label',
|
||||
'start_datetime',
|
||||
'end_datetime',
|
||||
]
|
||||
|
||||
def duplicate(self, desk_target=None, source_target=None):
|
||||
# clone current exception
|
||||
new_exception = copy.deepcopy(self)
|
||||
|
@ -4197,7 +4358,7 @@ class NotificationType:
|
|||
return self.settings._meta.get_field(self.name).verbose_name
|
||||
|
||||
|
||||
class AgendaNotificationsSettings(models.Model):
|
||||
class AgendaNotificationsSettings(WithInspectMixin, models.Model):
|
||||
EMAIL_FIELD = 'use-email-field'
|
||||
VIEW_ROLE = 'view-role'
|
||||
EDIT_ROLE = 'edit-role'
|
||||
|
@ -4261,6 +4422,13 @@ class AgendaNotificationsSettings(models.Model):
|
|||
'cancelled_event_emails': self.cancelled_event_emails,
|
||||
}
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return [
|
||||
'almost_full_event',
|
||||
'full_event',
|
||||
'cancelled_event',
|
||||
]
|
||||
|
||||
def duplicate(self, agenda_target):
|
||||
new_settings = copy.deepcopy(self)
|
||||
new_settings.pk = None
|
||||
|
@ -4269,7 +4437,7 @@ class AgendaNotificationsSettings(models.Model):
|
|||
return new_settings
|
||||
|
||||
|
||||
class AgendaReminderSettings(models.Model):
|
||||
class AgendaReminderSettings(WithInspectMixin, models.Model):
|
||||
ONE_DAY_BEFORE = 1
|
||||
TWO_DAYS_BEFORE = 2
|
||||
THREE_DAYS_BEFORE = 3
|
||||
|
@ -4358,6 +4526,14 @@ class AgendaReminderSettings(models.Model):
|
|||
'sms_extra_info': self.sms_extra_info,
|
||||
}
|
||||
|
||||
def get_inspect_keys(self):
|
||||
return [
|
||||
'days_before_email',
|
||||
'days_before_sms',
|
||||
'email_extra_info',
|
||||
'sms_extra_info',
|
||||
]
|
||||
|
||||
def duplicate(self, agenda_target):
|
||||
new_settings = copy.deepcopy(self)
|
||||
new_settings.pk = None
|
||||
|
|
|
@ -43,7 +43,7 @@ klasses_translation_reverse = {v: k for k, v in klasses_translation.items()}
|
|||
|
||||
|
||||
class Index(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
data = []
|
||||
|
@ -137,7 +137,7 @@ def get_component_bundle_entry(request, component):
|
|||
|
||||
|
||||
class ListComponents(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
klass = klasses[kwargs['component_type']]
|
||||
|
@ -152,11 +152,11 @@ list_components = ListComponents.as_view()
|
|||
|
||||
|
||||
class ExportComponent(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request, slug, *args, **kwargs):
|
||||
klass = klasses[kwargs['component_type']]
|
||||
serialisation = klass.objects.get(slug=slug).export_json()
|
||||
serialisation = get_object_or_404(klass, slug=slug).export_json()
|
||||
return Response({'data': serialisation})
|
||||
|
||||
|
||||
|
@ -164,11 +164,11 @@ export_component = ExportComponent.as_view()
|
|||
|
||||
|
||||
class ComponentDependencies(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request, slug, *args, **kwargs):
|
||||
klass = klasses[kwargs['component_type']]
|
||||
component = klass.objects.get(slug=slug)
|
||||
component = get_object_or_404(klass, slug=slug)
|
||||
|
||||
def dependency_dict(element):
|
||||
return get_component_bundle_entry(request, element)
|
||||
|
@ -197,7 +197,7 @@ def component_redirect(request, component_type, slug):
|
|||
|
||||
|
||||
class BundleCheck(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
return Response({'err': 0, 'data': {}})
|
||||
|
@ -207,7 +207,7 @@ bundle_check = BundleCheck.as_view()
|
|||
|
||||
|
||||
class BundleImport(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
install = True
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
|
@ -283,7 +283,7 @@ bundle_declare = BundleDeclare.as_view()
|
|||
|
||||
|
||||
class BundleUnlink(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if request.POST.get('application'):
|
||||
|
|
|
@ -40,7 +40,7 @@ class WithSnapshotMixin:
|
|||
return cls._meta.get_field('snapshot').related_model
|
||||
|
||||
def take_snapshot(self, *args, **kwargs):
|
||||
self.get_snapshot_model().take(self, *args, **kwargs)
|
||||
return self.get_snapshot_model().take(self, *args, **kwargs)
|
||||
|
||||
|
||||
class AbstractSnapshot(models.Model):
|
||||
|
@ -74,17 +74,68 @@ class AbstractSnapshot(models.Model):
|
|||
snapshot.application_slug = application.slug
|
||||
snapshot.application_version = application.version_number
|
||||
snapshot.save()
|
||||
return snapshot
|
||||
|
||||
def get_instance(self):
|
||||
try:
|
||||
# try reusing existing instance
|
||||
return self.get_instance_model().snapshots.get(snapshot=self)
|
||||
instance = self.get_instance_model().snapshots.get(snapshot=self)
|
||||
except self.get_instance_model().DoesNotExist:
|
||||
return self.load_instance(self.serialization, snapshot=self)
|
||||
instance = self.load_instance(self.serialization, snapshot=self)
|
||||
instance.slug = self.serialization['slug'] # restore slug
|
||||
return instance
|
||||
|
||||
def load_instance(self, json_instance, snapshot=None):
|
||||
return self.get_instance_model().import_json(json_instance, snapshot=snapshot)[1]
|
||||
|
||||
def load_history(self):
|
||||
if self.instance is None:
|
||||
self._history = []
|
||||
return
|
||||
history = type(self).objects.filter(instance=self.instance)
|
||||
self._history = [s.id for s in history]
|
||||
|
||||
@property
|
||||
def previous(self):
|
||||
if not hasattr(self, '_history'):
|
||||
self.load_history()
|
||||
|
||||
try:
|
||||
idx = self._history.index(self.id)
|
||||
except ValueError:
|
||||
return None
|
||||
if idx == 0:
|
||||
return None
|
||||
return self._history[idx - 1]
|
||||
|
||||
@property
|
||||
def next(self):
|
||||
if not hasattr(self, '_history'):
|
||||
self.load_history()
|
||||
|
||||
try:
|
||||
idx = self._history.index(self.id)
|
||||
except ValueError:
|
||||
return None
|
||||
try:
|
||||
return self._history[idx + 1]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def first(self):
|
||||
if not hasattr(self, '_history'):
|
||||
self.load_history()
|
||||
|
||||
return self._history[0]
|
||||
|
||||
@property
|
||||
def last(self):
|
||||
if not hasattr(self, '_history'):
|
||||
self.load_history()
|
||||
|
||||
return self._history[-1]
|
||||
|
||||
|
||||
class AgendaSnapshot(AbstractSnapshot):
|
||||
instance = models.ForeignKey(
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
# chrono - agendas system
|
||||
# Copyright (C) 2016-2024 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import difflib
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template import loader
|
||||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import DetailView, ListView
|
||||
from lxml.html.diff import htmldiff
|
||||
from pyquery import PyQuery as pq
|
||||
|
||||
from chrono.utils.timezone import localtime
|
||||
|
||||
|
||||
class InstanceWithSnapshotHistoryView(ListView):
|
||||
def get_queryset(self):
|
||||
self.instance = get_object_or_404(self.model.get_instance_model(), pk=self.kwargs['pk'])
|
||||
return self.instance.instance_snapshots.all().select_related('user')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs[self.instance_context_key] = self.instance
|
||||
kwargs['object'] = self.instance
|
||||
current_date = None
|
||||
context = super().get_context_data(**kwargs)
|
||||
day_snapshot = None
|
||||
for snapshot in context['object_list']:
|
||||
if snapshot.timestamp.date() != current_date:
|
||||
current_date = snapshot.timestamp.date()
|
||||
snapshot.new_day = True
|
||||
snapshot.day_other_count = 0
|
||||
day_snapshot = snapshot
|
||||
else:
|
||||
day_snapshot.day_other_count += 1
|
||||
return context
|
||||
|
||||
|
||||
class InstanceWithSnapshotHistoryCompareView(DetailView):
|
||||
def get_snapshots(self):
|
||||
id1 = self.request.GET.get('version1')
|
||||
id2 = self.request.GET.get('version2')
|
||||
if not id1 or not id2:
|
||||
raise Http404
|
||||
|
||||
snapshot1 = get_object_or_404(self.model.get_snapshot_model(), pk=id1, instance=self.object)
|
||||
snapshot2 = get_object_or_404(self.model.get_snapshot_model(), pk=id2, instance=self.object)
|
||||
|
||||
return snapshot1, snapshot2
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs[self.instance_context_key] = self.object
|
||||
|
||||
mode = self.request.GET.get('mode') or 'json'
|
||||
if mode not in ['json', 'inspect']:
|
||||
raise Http404
|
||||
|
||||
snapshot1, snapshot2 = self.get_snapshots()
|
||||
if not snapshot1 or not snapshot2:
|
||||
return redirect(reverse(self.history_view, args=[self.object.pk]))
|
||||
if snapshot1.timestamp > snapshot2.timestamp:
|
||||
snapshot1, snapshot2 = snapshot2, snapshot1
|
||||
|
||||
kwargs['mode'] = mode
|
||||
kwargs['snapshot1'] = snapshot1
|
||||
kwargs['snapshot2'] = snapshot2
|
||||
kwargs['fromdesc'] = self.get_snapshot_desc(snapshot1)
|
||||
kwargs['todesc'] = self.get_snapshot_desc(snapshot2)
|
||||
kwargs.update(getattr(self, 'get_compare_%s_context' % mode)(snapshot1, snapshot2))
|
||||
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
context = self.get_context_data(object=self.object)
|
||||
if isinstance(context, HttpResponseRedirect):
|
||||
return context
|
||||
return self.render_to_response(context)
|
||||
|
||||
def get_compare_inspect_context(self, snapshot1, snapshot2):
|
||||
instance1 = snapshot1.get_instance()
|
||||
instance2 = snapshot2.get_instance()
|
||||
|
||||
def get_context(instance):
|
||||
return {
|
||||
'object': instance,
|
||||
}
|
||||
|
||||
def fix_result(panel_diff):
|
||||
if not panel_diff:
|
||||
return panel_diff
|
||||
panel = pq(panel_diff)
|
||||
# remove "Link" added by htmldiff
|
||||
for link in panel.find('a'):
|
||||
d = pq(link)
|
||||
text = d.html()
|
||||
new_text = re.sub(r' Link: .*$', '', text)
|
||||
d.html(new_text)
|
||||
# remove empty ins and del tags
|
||||
for elem in panel.find('ins, del'):
|
||||
d = pq(elem)
|
||||
if not (d.html() or '').strip():
|
||||
d.remove()
|
||||
# prevent auto-closing behaviour of pyquery .html() method
|
||||
for elem in panel.find('span, ul, div'):
|
||||
d = pq(elem)
|
||||
if not d.html():
|
||||
d.html(' ')
|
||||
return panel.html()
|
||||
|
||||
inspect1 = loader.render_to_string(self.inspect_template_name, get_context(instance1), self.request)
|
||||
d1 = pq(str(inspect1))
|
||||
inspect2 = loader.render_to_string(self.inspect_template_name, get_context(instance2), self.request)
|
||||
d2 = pq(str(inspect2))
|
||||
panels_attrs = [tab.attrib for tab in d1('[role="tabpanel"]')]
|
||||
panels1 = list(d1('[role="tabpanel"]'))
|
||||
panels2 = list(d2('[role="tabpanel"]'))
|
||||
|
||||
# build tab list (merge version 1 and version2)
|
||||
tabs1 = d1.find('[role="tab"]')
|
||||
tabs2 = d2.find('[role="tab"]')
|
||||
tabs_order = [t.get('id') for t in panels_attrs]
|
||||
tabs = {}
|
||||
for tab in tabs1 + tabs2:
|
||||
tab_id = pq(tab).attr('aria-controls')
|
||||
tabs[tab_id] = pq(tab).outer_html()
|
||||
tabs = [tabs[k] for k in tabs_order if k in tabs]
|
||||
|
||||
# build diff of each panel
|
||||
panels_diff = list(map(htmldiff, panels1, panels2))
|
||||
panels_diff = [fix_result(t) for t in panels_diff]
|
||||
|
||||
return {
|
||||
'tabs': tabs,
|
||||
'panels': zip(panels_attrs, panels_diff),
|
||||
'tab_class_names': d1('.pk-tabs').attr('class'),
|
||||
}
|
||||
|
||||
def get_compare_json_context(self, snapshot1, snapshot2):
|
||||
s1 = json.dumps(snapshot1.serialization, sort_keys=True, indent=2)
|
||||
s2 = json.dumps(snapshot2.serialization, sort_keys=True, indent=2)
|
||||
diff_serialization = difflib.HtmlDiff(wrapcolumn=160).make_table(
|
||||
fromlines=s1.splitlines(True),
|
||||
tolines=s2.splitlines(True),
|
||||
)
|
||||
|
||||
return {
|
||||
'diff_serialization': diff_serialization,
|
||||
}
|
||||
|
||||
def get_snapshot_desc(self, snapshot):
|
||||
label_or_comment = ''
|
||||
if snapshot.label:
|
||||
label_or_comment = snapshot.label
|
||||
elif snapshot.comment:
|
||||
label_or_comment = snapshot.comment
|
||||
if snapshot.application_version:
|
||||
label_or_comment += ' (%s)' % _('Version %s') % snapshot.application_version
|
||||
return '{name} ({pk}) - {label_or_comment} ({user}{timestamp})'.format(
|
||||
name=_('Snapshot'),
|
||||
pk=snapshot.id,
|
||||
label_or_comment=label_or_comment,
|
||||
user='%s ' % snapshot.user if snapshot.user_id else '',
|
||||
timestamp=date_format(localtime(snapshot.timestamp), format='DATETIME_FORMAT'),
|
||||
)
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: chrono 0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-02-27 16:39+0100\n"
|
||||
"POT-Creation-Date: 2024-03-21 08:36+0100\n"
|
||||
"PO-Revision-Date: 2024-02-01 09:50+0100\n"
|
||||
"Last-Translator: Frederic Peters <fpeters@entrouvert.com>\n"
|
||||
"Language: French\n"
|
||||
|
@ -41,6 +41,7 @@ msgid "in %s days"
|
|||
msgstr "dans %s jours"
|
||||
|
||||
#: agendas/models.py
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_events_agenda_day_view.html
|
||||
#: manager/templates/chrono/manager_events_agenda_month_view.html
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
|
@ -142,6 +143,7 @@ msgid "Label"
|
|||
msgstr "Libellé"
|
||||
|
||||
#: agendas/models.py
|
||||
#: manager/templates/chrono/includes/snapshot_history_fragment.html
|
||||
msgid "Identifier"
|
||||
msgstr "Identifiant"
|
||||
|
||||
|
@ -192,6 +194,10 @@ msgstr "Vue par défaut"
|
|||
msgid "Booking form URL"
|
||||
msgstr "Adresse de la démarche de réservation"
|
||||
|
||||
#: agendas/models.py
|
||||
msgid "Global desk management"
|
||||
msgstr "Gestion globale des guichets"
|
||||
|
||||
#: agendas/models.py
|
||||
msgid "Automatically mark event as checked when all bookings have been checked"
|
||||
msgstr ""
|
||||
|
@ -380,6 +386,7 @@ msgid "Repeat"
|
|||
msgstr "Répéter"
|
||||
|
||||
#: agendas/models.py manager/forms.py
|
||||
#: manager/templates/chrono/includes/snapshot_history_fragment.html
|
||||
msgid "Date"
|
||||
msgstr "Date"
|
||||
|
||||
|
@ -461,6 +468,7 @@ msgid "Optional label to identify this date."
|
|||
msgstr "Libellé optionnel pour identifier la date."
|
||||
|
||||
#: agendas/models.py
|
||||
#: manager/templates/chrono/includes/snapshot_history_fragment.html
|
||||
msgid "Description"
|
||||
msgstr "Description"
|
||||
|
||||
|
@ -567,6 +575,7 @@ msgid "Resource"
|
|||
msgstr "Ressource"
|
||||
|
||||
#: agendas/models.py manager/forms.py
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_home.html
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_resource_list.html
|
||||
|
@ -612,11 +621,16 @@ msgstr "L’évènement « %s » n’a pas de date de début."
|
|||
msgid "Exception"
|
||||
msgstr "Exception"
|
||||
|
||||
#: agendas/models.py
|
||||
msgid "Bad ics file"
|
||||
msgstr "Mauvais format de fichier ICS"
|
||||
|
||||
#: agendas/models.py
|
||||
msgid "Unavailability calendar"
|
||||
msgstr "Calendrier d’indisponibilités"
|
||||
|
||||
#: agendas/models.py manager/forms.py
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_home.html
|
||||
msgid "Unavailability calendars"
|
||||
msgstr "Calendrier d’indisponibilités"
|
||||
|
@ -1938,6 +1952,15 @@ msgstr "Rôle"
|
|||
msgid "deletion"
|
||||
msgstr "suppression"
|
||||
|
||||
#: apps/snapshot/views.py
|
||||
#, python-format
|
||||
msgid "Version %s"
|
||||
msgstr "Version %s"
|
||||
|
||||
#: apps/snapshot/views.py
|
||||
msgid "Snapshot"
|
||||
msgstr "Sauvegarde"
|
||||
|
||||
#: manager/forms.py
|
||||
msgid "Desk 1"
|
||||
msgstr "Guichet 1"
|
||||
|
@ -2011,14 +2034,17 @@ msgid "Field type"
|
|||
msgstr "Type du champ"
|
||||
|
||||
#: manager/forms.py
|
||||
#: manager/templates/chrono/manager_events_type_inspect_fragment.html
|
||||
msgid "Text"
|
||||
msgstr "Texte"
|
||||
|
||||
#: manager/forms.py
|
||||
#: manager/templates/chrono/manager_events_type_inspect_fragment.html
|
||||
msgid "Textarea"
|
||||
msgstr "Zone de texte"
|
||||
|
||||
#: manager/forms.py
|
||||
#: manager/templates/chrono/manager_events_type_inspect_fragment.html
|
||||
msgid "Boolean"
|
||||
msgstr "Booléen"
|
||||
|
||||
|
@ -2441,12 +2467,49 @@ msgstr "Couleurs des rendez-vous :"
|
|||
msgid "Applications"
|
||||
msgstr "Applications"
|
||||
|
||||
#: manager/templates/chrono/includes/snapshot_history_fragment.html
|
||||
msgid "Show differences"
|
||||
msgstr "Afficher les différences"
|
||||
|
||||
#: manager/templates/chrono/includes/snapshot_history_fragment.html
|
||||
msgid "Compare"
|
||||
msgstr "Comparer"
|
||||
|
||||
#: manager/templates/chrono/includes/snapshot_history_fragment.html
|
||||
msgid "User"
|
||||
msgstr "Usager"
|
||||
|
||||
#: manager/templates/chrono/includes/snapshot_history_fragment.html
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_category_list.html
|
||||
#: manager/templates/chrono/manager_events_type_list.html
|
||||
#: manager/templates/chrono/manager_home.html
|
||||
#: manager/templates/chrono/manager_resource_detail.html
|
||||
#: manager/templates/chrono/manager_resource_list.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_list.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_settings.html
|
||||
msgid "Actions"
|
||||
msgstr "Actions"
|
||||
|
||||
#: manager/templates/chrono/includes/snapshot_history_fragment.html
|
||||
#, python-format
|
||||
msgid "1 other this day"
|
||||
msgid_plural "%(counter)s others"
|
||||
msgstr[0] "1 autre ce jour"
|
||||
msgstr[1] "%(counter)s autres ce jour"
|
||||
|
||||
#: manager/templates/chrono/includes/snapshot_history_fragment.html
|
||||
#, python-format
|
||||
msgid "Version %(version)s"
|
||||
msgstr "Version %(version)s"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_add_form.html
|
||||
msgid "New Agenda"
|
||||
msgstr "Nouvel agenda"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_date_view.html
|
||||
#: manager/templates/chrono/manager_agenda_form.html
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_agenda_notifications_form.html
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_agenda_view.html
|
||||
|
@ -2578,6 +2641,169 @@ msgstr "hors de la période d’inscription"
|
|||
msgid "Booking form"
|
||||
msgstr "Démarche de réservation"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_history.html
|
||||
msgid "Agenda history"
|
||||
msgstr "Historique de l’agenda"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_history.html
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_category_form.html
|
||||
#: manager/templates/chrono/manager_category_history.html
|
||||
#: manager/templates/chrono/manager_events_type_form.html
|
||||
#: manager/templates/chrono/manager_events_type_history.html
|
||||
#: manager/templates/chrono/manager_resource_detail.html
|
||||
#: manager/templates/chrono/manager_resource_history.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_history.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_settings.html
|
||||
msgid "History"
|
||||
msgstr "Historique"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_history_compare.html
|
||||
#: manager/templates/chrono/manager_category_history_compare.html
|
||||
#: manager/templates/chrono/manager_events_type_history_compare.html
|
||||
#: manager/templates/chrono/manager_resource_history_compare.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_history_compare.html
|
||||
msgid "Compare snapshots"
|
||||
msgstr "Comparaison des sauvergardes"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_history_compare.html
|
||||
#: manager/templates/chrono/manager_category_history_compare.html
|
||||
#: manager/templates/chrono/manager_events_type_history_compare.html
|
||||
#: manager/templates/chrono/manager_resource_history_compare.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_history_compare.html
|
||||
msgid "JSON"
|
||||
msgstr "JSON"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_history_compare.html
|
||||
#: manager/templates/chrono/manager_agenda_inspect.html
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_category_form.html
|
||||
#: manager/templates/chrono/manager_category_history_compare.html
|
||||
#: manager/templates/chrono/manager_category_inspect.html
|
||||
#: manager/templates/chrono/manager_events_type_form.html
|
||||
#: manager/templates/chrono/manager_events_type_history_compare.html
|
||||
#: manager/templates/chrono/manager_events_type_inspect.html
|
||||
#: manager/templates/chrono/manager_resource_detail.html
|
||||
#: manager/templates/chrono/manager_resource_history_compare.html
|
||||
#: manager/templates/chrono/manager_resource_inspect.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_history_compare.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_inspect.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_settings.html
|
||||
msgid "Inspect"
|
||||
msgstr "Inspecteur"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_history_compare.html
|
||||
#: manager/templates/chrono/manager_category_history_compare.html
|
||||
#: manager/templates/chrono/manager_events_type_history_compare.html
|
||||
#: manager/templates/chrono/manager_resource_history_compare.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_history_compare.html
|
||||
msgid "Compare inspect"
|
||||
msgstr "Comparaison des inspecteurs"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_history_compare.html
|
||||
#: manager/templates/chrono/manager_category_history_compare.html
|
||||
#: manager/templates/chrono/manager_events_type_history_compare.html
|
||||
#: manager/templates/chrono/manager_resource_history_compare.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_history_compare.html
|
||||
msgid "Compare JSON"
|
||||
msgstr "Comparaison JSON"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_category_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_events_type_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_resource_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_inspect_fragment.html
|
||||
msgid "Information"
|
||||
msgstr "Informations"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_inspect_fragment.html
|
||||
msgid "Permissions"
|
||||
msgstr "Permissions"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
msgid "Recurrence exceptions"
|
||||
msgstr "Exceptions aux récurrences"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_virtual_agenda_settings.html
|
||||
msgid "Meeting Types"
|
||||
msgstr "Types de rendez-vous"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
msgid "Desks"
|
||||
msgstr "Guichets"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_virtual_agenda_settings.html
|
||||
msgid "Included Agendas"
|
||||
msgstr "Agendas inclus"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_virtual_agenda_settings.html
|
||||
msgid "Excluded Periods"
|
||||
msgstr "Périodes exclues"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_category_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_events_type_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_resource_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_inspect_fragment.html
|
||||
#, python-format
|
||||
msgid "%(label)s:"
|
||||
msgstr "%(label)s :"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
msgid "Display options"
|
||||
msgstr "Paramètres d’affichage"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
msgid "Booking check options"
|
||||
msgstr "Paramètres du pointage"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
msgid "Invoicing options"
|
||||
msgstr "Paramètres de facturation"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
msgid "Management notifications"
|
||||
msgstr "Notifications d’administration"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
msgid "Booking reminders"
|
||||
msgstr "Rappels de réservation"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
msgid "Booking Delays"
|
||||
msgstr "Délais de réservation"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
msgid "Exception sources"
|
||||
msgstr "Sources d’exceptions"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_time_period_exception_list.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_inspect_fragment.html
|
||||
msgid "Exceptions"
|
||||
msgstr "Exceptions"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_inspect_fragment.html
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
msgid "Opening hours"
|
||||
msgstr "Plages horaires"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_month_view.html
|
||||
#: manager/templates/chrono/manager_resource_month_view.html
|
||||
msgid "Previous month"
|
||||
|
@ -2662,18 +2888,6 @@ msgstr "Exporter les évènements (CSV)"
|
|||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
msgid "Booking reminders"
|
||||
msgstr "Rappels de réservation"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
msgid "Booking Delays"
|
||||
msgstr "Délais de réservation"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
msgid "Permissions"
|
||||
msgstr "Permissions"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
msgid "Reminders are disabled for this agenda."
|
||||
msgstr "Les rappels sont désactivés pour cet agenda."
|
||||
|
@ -2735,23 +2949,19 @@ msgstr "Rôle d’édition :"
|
|||
msgid "View Role:"
|
||||
msgstr "Rôle de visualisation :"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_category_list.html
|
||||
#: manager/templates/chrono/manager_events_type_list.html
|
||||
#: manager/templates/chrono/manager_home.html
|
||||
#: manager/templates/chrono/manager_resource_detail.html
|
||||
#: manager/templates/chrono/manager_resource_list.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_list.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_settings.html
|
||||
msgid "Actions"
|
||||
msgstr "Actions"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_event_detail.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_settings.html
|
||||
msgid "Options"
|
||||
msgstr "Options"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_home.html
|
||||
#: manager/templates/chrono/manager_resource_detail.html
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_settings.html
|
||||
msgid "Navigation"
|
||||
msgstr "Navigation"
|
||||
|
||||
#: manager/templates/chrono/manager_agenda_unavailability_calendar_form.html
|
||||
msgid "Add unavailability calendar"
|
||||
msgstr "Ajouter un calendrier d’indisponibilités"
|
||||
|
@ -2795,6 +3005,10 @@ msgstr "Nouvelle catégorie"
|
|||
msgid "Edit Category"
|
||||
msgstr "Modification de la catégorie"
|
||||
|
||||
#: manager/templates/chrono/manager_category_history.html
|
||||
msgid "Category history"
|
||||
msgstr "Historique de la catégorie"
|
||||
|
||||
#: manager/templates/chrono/manager_category_list.html
|
||||
msgid "Categories outside applications"
|
||||
msgstr "Catégories hors applications"
|
||||
|
@ -3116,37 +3330,11 @@ msgstr "Ce mois n’a pas d’évènements configurés."
|
|||
msgid "Import Events"
|
||||
msgstr "Importer des évènements"
|
||||
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_home.html
|
||||
msgid "Navigation"
|
||||
msgstr "Navigation"
|
||||
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
msgctxt "pricing"
|
||||
msgid "Pricing"
|
||||
msgstr "Tarification"
|
||||
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
msgid "Recurrence exceptions"
|
||||
msgstr "Exceptions aux récurrences"
|
||||
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
msgid "Display options"
|
||||
msgstr "Paramètres d’affichage"
|
||||
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
msgid "Booking check options"
|
||||
msgstr "Paramètres du pointage"
|
||||
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
msgid "Invoicing options"
|
||||
msgstr "Paramètres de facturation"
|
||||
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
msgid "Management notifications"
|
||||
msgstr "Notifications d’administration"
|
||||
|
||||
#: manager/templates/chrono/manager_events_agenda_settings.html
|
||||
msgid ""
|
||||
"This agenda doesn't have any event yet. Click on the \"New Event\" button in "
|
||||
|
@ -3329,6 +3517,7 @@ msgid "Edit events type"
|
|||
msgstr "Modification du type d’évènement"
|
||||
|
||||
#: manager/templates/chrono/manager_events_type_form.html
|
||||
#: manager/templates/chrono/manager_events_type_inspect_fragment.html
|
||||
msgid "Custom fields"
|
||||
msgstr "Champs personnalisés"
|
||||
|
||||
|
@ -3336,6 +3525,22 @@ msgstr "Champs personnalisés"
|
|||
msgid "Add another custom field"
|
||||
msgstr "Ajouter un autre champ"
|
||||
|
||||
#: manager/templates/chrono/manager_events_type_history.html
|
||||
msgid "Events type history"
|
||||
msgstr "Historique du type d’évènement"
|
||||
|
||||
#: manager/templates/chrono/manager_events_type_inspect_fragment.html
|
||||
msgid "Field slug:"
|
||||
msgstr "Identifiant du champ :"
|
||||
|
||||
#: manager/templates/chrono/manager_events_type_inspect_fragment.html
|
||||
msgid "Field label:"
|
||||
msgstr "Libellé du champ :"
|
||||
|
||||
#: manager/templates/chrono/manager_events_type_inspect_fragment.html
|
||||
msgid "Field type:"
|
||||
msgstr "Type du champ :"
|
||||
|
||||
#: manager/templates/chrono/manager_events_type_list.html
|
||||
msgid "Events types outside applications"
|
||||
msgstr "Types d’évènements hors applications"
|
||||
|
@ -3430,19 +3635,6 @@ msgstr "Basculer en gestion unitaire des guichets"
|
|||
msgid "Switch to global desk management"
|
||||
msgstr "Basculer en gestion globale des guichets"
|
||||
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_virtual_agenda_settings.html
|
||||
msgid "Meeting Types"
|
||||
msgstr "Types de rendez-vous"
|
||||
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
msgid "Desks"
|
||||
msgstr "Guichets"
|
||||
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
msgid "Opening hours"
|
||||
msgstr "Plages horaires"
|
||||
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_virtual_agenda_settings.html
|
||||
msgid "minutes"
|
||||
|
@ -3468,11 +3660,6 @@ msgstr "Ajouter une plage horaire régulière"
|
|||
msgid "Add a unique period"
|
||||
msgstr "Ajouter une plage horaire unique"
|
||||
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
#: manager/templates/chrono/manager_time_period_exception_list.html
|
||||
msgid "Exceptions"
|
||||
msgstr "Exceptions"
|
||||
|
||||
#: manager/templates/chrono/manager_meetings_agenda_settings.html
|
||||
msgid "manage exceptions"
|
||||
msgstr "gérer les exceptions"
|
||||
|
@ -3602,6 +3789,10 @@ msgstr "Nouvelle ressource"
|
|||
msgid "Edit Resource"
|
||||
msgstr "Modification de la ressource"
|
||||
|
||||
#: manager/templates/chrono/manager_resource_history.html
|
||||
msgid "Resource history"
|
||||
msgstr "Historique de la ressource"
|
||||
|
||||
#: manager/templates/chrono/manager_resource_list.html
|
||||
msgid "Resources outside applications"
|
||||
msgstr "Ressources hors applications"
|
||||
|
@ -3786,6 +3977,10 @@ msgstr "Modification du calendrier d’indisponibilités"
|
|||
msgid "New Unavailability Calendar"
|
||||
msgstr "Nouveau calendrier d’indisponibilités"
|
||||
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_history.html
|
||||
msgid "UnavailabilityCalendarSnapshot calendar history"
|
||||
msgstr "Historique du calendrier d’indisponibilités"
|
||||
|
||||
#: manager/templates/chrono/manager_unavailability_calendar_list.html
|
||||
msgid "Unavailability Calendars"
|
||||
msgstr "Calendriers d’indisponibilités"
|
||||
|
@ -3837,14 +4032,6 @@ msgstr "Ajouter une période d’exclusion"
|
|||
msgid "Include Agenda"
|
||||
msgstr "Inclure un agenda"
|
||||
|
||||
#: manager/templates/chrono/manager_virtual_agenda_settings.html
|
||||
msgid "Included Agendas"
|
||||
msgstr "Agendas inclus"
|
||||
|
||||
#: manager/templates/chrono/manager_virtual_agenda_settings.html
|
||||
msgid "Excluded Periods"
|
||||
msgstr "Périodes exclues"
|
||||
|
||||
#: manager/templates/chrono/manager_virtual_agenda_settings.html
|
||||
msgid ""
|
||||
"This virtual agenda doesn't include any agenda yet. Click on the \"Include "
|
||||
|
|
|
@ -589,12 +589,17 @@ class BookingCheckPresenceForm(forms.Form):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
agenda = kwargs.pop('agenda')
|
||||
subscription = kwargs.pop('subscription', False)
|
||||
super().__init__(*args, **kwargs)
|
||||
check_types = get_agenda_check_types(agenda)
|
||||
self.presence_check_types = [ct for ct in check_types if ct.kind == 'presence']
|
||||
self.fields['check_type'].choices = [('', '---------')] + [
|
||||
(ct.slug, ct.label) for ct in self.presence_check_types
|
||||
]
|
||||
if not self.initial and subscription:
|
||||
unexpected_presences = [ct for ct in check_types if ct.unexpected_presence]
|
||||
if unexpected_presences:
|
||||
self.initial['check_type'] = unexpected_presences[0].slug
|
||||
|
||||
|
||||
class PartialBookingCheckForm(forms.ModelForm):
|
||||
|
|
|
@ -956,3 +956,81 @@ a.button.button-paragraph {
|
|||
.application-logo, .application-icon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.snapshots-list .collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
p.snapshot-description {
|
||||
font-size: 80%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.diff {
|
||||
margin: 1em 0;
|
||||
h3 {
|
||||
del, ins {
|
||||
font-weight: bold;
|
||||
background-color: transparent;
|
||||
}
|
||||
del {
|
||||
color: #fbb6c2 !important;
|
||||
}
|
||||
ins {
|
||||
color: #d4fcbc !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ins {
|
||||
text-decoration: none;
|
||||
background-color: #d4fcbc;
|
||||
}
|
||||
|
||||
del {
|
||||
text-decoration: line-through;
|
||||
background-color: #fbb6c2;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
table.diff {
|
||||
background: white;
|
||||
border: 1px solid #f3f3f3;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
colgroup, thead, tbody, td {
|
||||
border: 1px solid #f3f3f3;
|
||||
}
|
||||
tbody tr:nth-child(even) {
|
||||
background: #fdfdfd;
|
||||
}
|
||||
th, td {
|
||||
max-width: 30vw;
|
||||
/* it will not actually limit width as the table is set to
|
||||
* expand to 100% but it will prevent one side getting wider
|
||||
*/
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
}
|
||||
.diff_header {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
td.diff_header {
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
color: #606060;
|
||||
}
|
||||
.diff_next {
|
||||
display: none;
|
||||
}
|
||||
.diff_add {
|
||||
background-color: #aaffaa;
|
||||
}
|
||||
.diff_chg {
|
||||
background-color: #ffff77;
|
||||
}
|
||||
.diff_sub {
|
||||
background-color: #ffaaaa;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<p class="snapshot-description">{{ fromdesc|safe }} ➔ {{ todesc|safe }}</p>
|
||||
<div class="diff">
|
||||
{% if mode == 'json' %}
|
||||
{{ diff_serialization|safe }}
|
||||
{% else %}
|
||||
<div class="{{ tab_class_names }}">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
{% for tab in tabs %}{{ tab|safe }}{% endfor %}
|
||||
{{ tab_list|safe }}
|
||||
</div>
|
||||
<div class="pk-tabs--container">
|
||||
{% for attrs, panel in panels %}
|
||||
<div{% for k, v in attrs.items %} {{ k }}="{{ v }}"{% endfor %}>
|
||||
{{ panel|safe }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
|
@ -0,0 +1,63 @@
|
|||
{% load i18n %}
|
||||
<div>
|
||||
<form action="{{ compare_url }}" method="get">
|
||||
{% if object_list|length > 1 %}
|
||||
<p><button>{% trans "Show differences" %}</button></p>
|
||||
{% endif %}
|
||||
<table class="main">
|
||||
<thead>
|
||||
<th>{% trans 'Identifier' %}</th>
|
||||
<th>{% trans 'Compare' %}</th>
|
||||
<th>{% trans 'Date' %}</th>
|
||||
<th>{% trans 'Description' %}</th>
|
||||
<th>{% trans 'User' %}</th>
|
||||
<th>{% trans 'Actions' %}</th>
|
||||
</thead>
|
||||
<tbody class="snapshots-list">
|
||||
{% for snapshot in object_list %}
|
||||
<tr data-day="{{ snapshot.timestamp|date:"Y-m-d" }}" class="{% if snapshot.new_day %}new-day{% else %}collapsed{% endif %}">
|
||||
<td><span class="counter">#{{ snapshot.pk }}</span></td>
|
||||
<td>
|
||||
{% if object_list|length > 1 %}
|
||||
{% if not forloop.last %}<input type="radio" name="version1" value="{{ snapshot.pk }}" {% if forloop.first %}checked="checked"{% endif %} />{% else %} {% endif %}
|
||||
{% if not forloop.first %}<input type="radio" name="version2" value="{{ snapshot.pk }}" {% if forloop.counter == 2 %}checked="checked"{% endif %}/>{% else %} {% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ snapshot.timestamp }}
|
||||
{% if snapshot.new_day and snapshot.day_other_count %} — <a class="reveal" href="#day-{{ snapshot.timestamp|date:"Y-m-d"}}">
|
||||
{% if snapshot.day_other_count >= 50 %}<strong>{% endif %}
|
||||
{% blocktrans trimmed count counter=snapshot.day_other_count %}
|
||||
1 other this day
|
||||
{% plural %}
|
||||
{{ counter }} others
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if snapshot.label %}
|
||||
<strong>{{ snapshot.label }}</strong>
|
||||
{% elif snapshot.comment %}
|
||||
{{ snapshot.comment }}
|
||||
{% endif %}
|
||||
{% if snapshot.application_version %}({% blocktrans with version=snapshot.application_version %}Version {{ version }}{% endblocktrans %}){% endif %}
|
||||
</td>
|
||||
<td>{% if snapshot.user %} {{ snapshot.user.get_full_name }}{% endif %}</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
$('tr.new-day a.reveal').on('click', function() {
|
||||
var day = $(this).parents('tr.new-day').data('day');
|
||||
$('.snapshots-list tr[data-day="' + day + '"]:not(.new-day)').toggleClass('collapsed');
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_agenda_settings.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Agenda history' %} - {{ agenda }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-agenda-history' pk=agenda.pk %}">{% trans "History" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'chrono-manager-agenda-history-compare' pk=agenda.pk as compare_url %}
|
||||
{% include 'chrono/includes/snapshot_history_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_agenda_history.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})</h2>
|
||||
<span class="actions">
|
||||
<a href="?version1={{ snapshot1.pk }}&version2={{ snapshot2.pk }}&mode=inspect">{% trans "Compare inspect" %}</a>
|
||||
<a href="?version1={{ snapshot1.pk }}&version2={{ snapshot2.pk }}&mode=json">{% trans "Compare JSON" %}</a>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-agenda-history-compare' pk=agenda.pk %}">{% trans "Compare snapshots" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/includes/snapshot_compare_fragment.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_agenda_settings.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Inspect' %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-agenda-inspect' pk=agenda.pk %}">{% trans "Inspect" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/manager_agenda_inspect_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,309 @@
|
|||
{% load i18n %}
|
||||
<div class="pk-tabs">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
<button aria-controls="panel-information" aria-selected="true" id="tab-information" role="tab" tabindex="0">{% trans "Information" %}</button>
|
||||
<button aria-controls="panel-settings" aria-selected="false" id="tab-settings" role="tab" tabindex="-1">{% trans "Settings" %}</button>
|
||||
<button aria-controls="panel-permissions" aria-selected="false" id="tab-permissions" role="tab" tabindex="-1">{% trans "Permissions" %}</button>
|
||||
{% if object.kind == 'events' %}
|
||||
<button aria-controls="panel-events" aria-selected="false" id="tab-events" role="tab" tabindex="-1">{% trans "Events" %}</button>
|
||||
<button aria-controls="panel-exceptions" aria-selected="false" id="tab-exceptions" role="tab" tabindex="-1">{% trans "Recurrence exceptions" %}</button>
|
||||
{% elif object.kind == 'meetings' %}
|
||||
<button aria-controls="panel-meeting-types" aria-selected="false" id="tab-meeting-types" role="tab" tabindex="-1">{% trans "Meeting Types" %}</button>
|
||||
<button aria-controls="panel-desks" aria-selected="false" id="tab-desks" role="tab" tabindex="-1">{% trans "Desks" %}</button>
|
||||
<button aria-controls="panel-resources" aria-selected="false" id="tab-resources" role="tab" tabindex="-1">{% trans "Resources" %}</button>
|
||||
{% elif object.kind == 'virtual' %}
|
||||
<button aria-controls="panel-agendas" aria-selected="false" id="tab-agendas" role="tab" tabindex="-1">{% trans "Included Agendas" %}</button>
|
||||
<button aria-controls="panel-time-periods" aria-selected="false" id="tab-time-periods" role="tab" tabindex="-1">{% trans "Excluded Periods" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="pk-tabs--container">
|
||||
|
||||
<div aria-labelledby="tab-information" id="panel-information" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
<ul>
|
||||
{% for label, value in object.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-settings" hidden id="panel-settings" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
|
||||
{% if object.kind != 'virtual' %}
|
||||
<h4>{% trans "Display options" %}</h4>
|
||||
<ul>
|
||||
{% for label, value in object.get_display_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if object.kind == 'events' %}
|
||||
<h4>{% trans "Booking check options" %}</h4>
|
||||
<ul>
|
||||
{% for label, value in object.get_booking_check_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if object.kind == 'events' %}
|
||||
{% if agenda.partial_bookings %}
|
||||
<h4>{% trans "Invoicing options" %}</h4>
|
||||
<ul>
|
||||
{% for label, value in object.get_invoicing_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<h4>{% trans "Management notifications" %}</h4>
|
||||
<ul>
|
||||
{% for label, value in object.get_notifications_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if object.kind != 'virtual' and not object.partial_bookings %}
|
||||
<h4>{% trans "Booking reminders" %}</h4>
|
||||
<ul>
|
||||
{% for label, value in object.get_reminder_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<h4>{% trans "Booking Delays" %}</h4>
|
||||
<ul>
|
||||
{% for label, value in object.get_booking_delays_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-permissions" hidden id="panel-permissions" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
<ul>
|
||||
{% for label, value in object.get_permissions_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if object.kind == 'events' %}
|
||||
|
||||
<div aria-labelledby="tab-events" hidden id="panel-events" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
{% for event in object.event_set.all %}
|
||||
<h4>{{ event }}</h4>
|
||||
<ul>
|
||||
{% for label, value in event.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-exceptions" hidden id="panel-exceptions" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
{% for desk in object.desk_set.all %}{% if desk.slug == '_exceptions_holder' %}
|
||||
<h4>{% trans "Unavailability calendars" %}</h4>
|
||||
<ul>
|
||||
{% for unavailability_calendar in desk.unavailability_calendars.all %}
|
||||
<li class="parameter-unavailability-calendar }}">
|
||||
{{ unavailability_calendar }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h4>{% trans "Exception sources" %}</h4>
|
||||
{% for source in desk.timeperiodexceptionsource_set.all %}
|
||||
<h5>{{ source }}</h5>
|
||||
<ul>
|
||||
{% for label, value in source.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
|
||||
<h4>{% trans "Exceptions" %}</h4>
|
||||
{% for exception in desk.timeperiodexception_set.all %}
|
||||
<h5>{{ exception }}</h5>
|
||||
<ul>
|
||||
{% for label, value in exception.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
{% endif %}{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% elif object.kind == 'meetings' %}
|
||||
|
||||
<div aria-labelledby="tab-meeting-types" hidden id="panel-meeting-types" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
{% for meeting_type in object.meetingtype_set.all %}
|
||||
<h4>{{ meeting_type }}</h4>
|
||||
<ul>
|
||||
{% for label, value in meeting_type.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-desks" hidden id="panel-desks" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
{% for desk in object.desk_set.all %}
|
||||
<h4>{{ desk }}</h4>
|
||||
<ul>
|
||||
{% for label, value in desk.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h5>{% trans "Opening hours" %}</h5>
|
||||
{% for time_period in desk.timeperiod_set.all %}
|
||||
<h6>{{ time_period }}</h6>
|
||||
<ul>
|
||||
{% for label, value in time_period.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
|
||||
<h5>{% trans "Unavailability calendars" %}</h5>
|
||||
<ul>
|
||||
{% for unavailability_calendar in desk.unavailability_calendars.all %}
|
||||
<li class="parameter-unavailability-calendar }}">
|
||||
{{ unavailability_calendar }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h5>{% trans "Exception sources" %}</h5>
|
||||
{% for source in desk.timeperiodexceptionsource_set.all %}
|
||||
<h6>{{ source }}</h6>
|
||||
<ul>
|
||||
{% for label, value in source.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
|
||||
<h5>{% trans "Exceptions" %}</h5>
|
||||
{% for exception in desk.timeperiodexception_set.all %}
|
||||
<h6>{{ exception }}</h6>
|
||||
<ul>
|
||||
{% for label, value in exception.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-resources" hidden id="panel-resources" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
<ul>
|
||||
{% for resource in object.resources.all %}
|
||||
<li class="parameter-resource }}">
|
||||
{{ resource }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% elif object.kind == "virtual" %}
|
||||
|
||||
<div aria-labelledby="tab-agendas" hidden id="panel-agendas" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
<ul>
|
||||
{% for agenda in object.real_agendas.all %}
|
||||
<li class="parameter-agenda }}">
|
||||
{{ agenda }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-time-periods" hidden id="panel-time-periods" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
{% for time_period in object.excluded_timeperiods.all %}
|
||||
<h4>{{ time_period }}</h4>
|
||||
<ul>
|
||||
{% for label, value in time_period.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -120,6 +120,11 @@
|
|||
<a class="button button-paragraph" rel="popup" class="action-duplicate" href="{% url 'chrono-manager-agenda-duplicate' pk=object.pk %}">{% trans 'Duplicate' %}</a>
|
||||
{% endif %}
|
||||
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
{% if show_history %}
|
||||
<a class="button button-paragraph" href="{% url 'chrono-manager-agenda-history' pk=agenda.pk %}">{% trans 'History' %}</a>
|
||||
{% endif %}
|
||||
<a class="button button-paragraph" href="{% url 'chrono-manager-agenda-inspect' pk=agenda.pk %}">{% trans 'Inspect' %}</a>
|
||||
{% block agenda-extra-navigation-actions %}{% endblock %}
|
||||
|
||||
{% url 'chrono-manager-homepage' as object_list_url %}
|
||||
|
|
|
@ -20,6 +20,14 @@
|
|||
{% else %}
|
||||
<h2>{% trans "New Category" %}</h2>
|
||||
{% endif %}
|
||||
{% if category %}
|
||||
<span class="actions">
|
||||
<a href="{% url 'chrono-manager-category-inspect' pk=category.pk %}">{% trans 'Inspect' %}</a>
|
||||
{% if show_history %}
|
||||
<a href="{% url 'chrono-manager-category-history' pk=category.pk %}">{% trans 'History' %}</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_category_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Category history' %} - {{ category }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-category-history' pk=category.pk %}">{% trans "History" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'chrono-manager-category-history-compare' pk=category.pk as compare_url %}
|
||||
{% include 'chrono/includes/snapshot_history_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_category_history.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})</h2>
|
||||
<span class="actions">
|
||||
<a href="?version1={{ snapshot1.pk }}&version2={{ snapshot2.pk }}&mode=inspect">{% trans "Compare inspect" %}</a>
|
||||
<a href="?version1={{ snapshot1.pk }}&version2={{ snapshot2.pk }}&mode=json">{% trans "Compare JSON" %}</a>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-category-history-compare' pk=category.pk %}">{% trans "Compare snapshots" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/includes/snapshot_compare_fragment.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "chrono/manager_category_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Inspect' %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-category-inspect' pk=category.pk %}">{% trans "Inspect" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/manager_category_inspect_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,21 @@
|
|||
{% load i18n %}
|
||||
<div class="pk-tabs">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
<button aria-controls="panel-information" aria-selected="true" id="tab-information" role="tab" tabindex="0">{% trans "Information" %}</button>
|
||||
</div>
|
||||
<div class="pk-tabs--container">
|
||||
|
||||
<div aria-labelledby="tab-information" id="panel-information" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
<ul>
|
||||
{% for label, value in object.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
{% block agenda-extra-navigation-actions %}
|
||||
{% with lingo_url=object.get_lingo_url %}{% if lingo_url %}
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
<a class="button button-paragraph" href="{{ lingo_url }}">{% trans 'Pricing' context 'pricing' %}</a>
|
||||
{% endif %}{% endwith %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -20,6 +20,14 @@
|
|||
{% else %}
|
||||
<h2>{% trans "New events type" %}</h2>
|
||||
{% endif %}
|
||||
{% if object.pk %}
|
||||
<span class="actions">
|
||||
<a href="{% url 'chrono-manager-events-type-inspect' pk=object.pk %}">{% trans 'Inspect' %}</a>
|
||||
{% if show_history %}
|
||||
<a href="{% url 'chrono-manager-events-type-history' pk=object.pk %}">{% trans 'History' %}</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_events_type_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Events type history' %} - {{ events_type }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-events-type-history' pk=events_type.pk %}">{% trans "History" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'chrono-manager-events-type-history-compare' pk=events_type.pk as compare_url %}
|
||||
{% include 'chrono/includes/snapshot_history_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_events_type_history.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})</h2>
|
||||
<span class="actions">
|
||||
<a href="?version1={{ snapshot1.pk }}&version2={{ snapshot2.pk }}&mode=inspect">{% trans "Compare inspect" %}</a>
|
||||
<a href="?version1={{ snapshot1.pk }}&version2={{ snapshot2.pk }}&mode=json">{% trans "Compare JSON" %}</a>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-events-type-history-compare' pk=events_type.pk %}">{% trans "Compare snapshots" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/includes/snapshot_compare_fragment.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "chrono/manager_events_type_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Inspect' %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-events-type-inspect' pk=events_type.pk %}">{% trans "Inspect" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/manager_events_type_inspect_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,47 @@
|
|||
{% load i18n %}
|
||||
<div class="pk-tabs">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
<button aria-controls="panel-information" aria-selected="true" id="tab-information" role="tab" tabindex="0">{% trans "Information" %}</button>
|
||||
<button aria-controls="panel-custom-fields" aria-selected="false" id="tab-custom-fields" role="tab" tabindex="-1">{% trans "Custom fields" %}</button>
|
||||
</div>
|
||||
<div class="pk-tabs--container">
|
||||
|
||||
<div aria-labelledby="tab-information" id="panel-information" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
<ul>
|
||||
{% for label, value in object.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-custom-fields" hidden id="panel-custom-fields" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
{% for value in object.get_custom_fields %}
|
||||
<h4>{{ value.label }}</h4>
|
||||
<ul>
|
||||
<li class="parameter-varname">
|
||||
<span class="parameter">{% trans "Field slug:" %}</span>
|
||||
{{ value.varname }}
|
||||
</li>
|
||||
<li class="parameter-label">
|
||||
<span class="parameter">{% trans "Field label:" %}</span>
|
||||
{{ value.label }}
|
||||
</li>
|
||||
<li class="parameter-field-type">
|
||||
<span class="parameter">{% trans "Field type:" %}</span>
|
||||
{% if value.field_type == 'text' %}{% trans "Text" %}{% endif %}
|
||||
{% if value.field_type == 'textarea' %}{% trans "Textarea" %}{% endif %}
|
||||
{% if value.field_type == 'textbool' %}{% trans "Boolean" %}{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -63,6 +63,12 @@
|
|||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-resource-edit' pk=resource.pk %}">{% trans 'Edit' %}</a>
|
||||
{% endif %}
|
||||
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
<a class="button button-paragraph" href="{% url 'chrono-manager-resource-inspect' pk=resource.pk %}">{% trans 'Inspect' %}</a>
|
||||
{% if show_history %}
|
||||
<a class="button button-paragraph" href="{% url 'chrono-manager-resource-history' pk=resource.pk %}">{% trans 'History' %}</a>
|
||||
{% endif %}
|
||||
|
||||
{% url 'chrono-manager-resource-list' as object_list_url %}
|
||||
{% include 'chrono/includes/application_detail_fragment.html' %}
|
||||
</aside>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_resource_detail.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Resource history' %} - {{ resource }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-resource-history' pk=resource.pk %}">{% trans "History" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'chrono-manager-resource-history-compare' pk=resource.pk as compare_url %}
|
||||
{% include 'chrono/includes/snapshot_history_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_resource_history.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})</h2>
|
||||
<span class="actions">
|
||||
<a href="?version1={{ snapshot1.pk }}&version2={{ snapshot2.pk }}&mode=inspect">{% trans "Compare inspect" %}</a>
|
||||
<a href="?version1={{ snapshot1.pk }}&version2={{ snapshot2.pk }}&mode=json">{% trans "Compare JSON" %}</a>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-resource-history-compare' pk=resource.pk %}">{% trans "Compare snapshots" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/includes/snapshot_compare_fragment.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "chrono/manager_resource_detail.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Inspect' %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-resource-inspect' pk=resource.pk %}">{% trans "Inspect" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/manager_resource_inspect_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,22 @@
|
|||
{% load i18n %}
|
||||
<div class="pk-tabs">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
<button aria-controls="panel-information" aria-selected="true" id="tab-information" role="tab" tabindex="0">{% trans "Information" %}</button>
|
||||
</div>
|
||||
<div class="pk-tabs--container">
|
||||
|
||||
<div aria-labelledby="tab-information" id="panel-information" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
<ul>
|
||||
{% for label, value in object.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_unavailability_calendar_settings.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'UnavailabilityCalendarSnapshot calendar history' %} - {{ unavailability_calendar }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-history' pk=unavailability_calendar.pk %}">{% trans "History" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'chrono-manager-unavailability-calendar-history-compare' pk=unavailability_calendar.pk as compare_url %}
|
||||
{% include 'chrono/includes/snapshot_history_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,19 @@
|
|||
{% extends "chrono/manager_unavailability_calendar_history.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Compare snapshots' %} ({% if mode == 'json' %}{% trans "JSON" %}{% else %}{% trans "Inspect" %}{% endif %})</h2>
|
||||
<span class="actions">
|
||||
<a href="?version1={{ snapshot1.pk }}&version2={{ snapshot2.pk }}&mode=inspect">{% trans "Compare inspect" %}</a>
|
||||
<a href="?version1={{ snapshot1.pk }}&version2={{ snapshot2.pk }}&mode=json">{% trans "Compare JSON" %}</a>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-history-compare' pk=unavailability_calendar.pk %}">{% trans "Compare snapshots" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/includes/snapshot_compare_fragment.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "chrono/manager_unavailability_calendar_settings.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Inspect' %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'chrono-manager-unavailability-calendar-inspect' pk=unavailability_calendar.pk %}">{% trans "Inspect" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'chrono/manager_unavailability_calendar_inspect_fragment.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,53 @@
|
|||
{% load i18n %}
|
||||
<div class="pk-tabs">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
<button aria-controls="panel-information" aria-selected="true" id="tab-information" role="tab" tabindex="0">{% trans "Information" %}</button>
|
||||
<button aria-controls="panel-permissions" aria-selected="false" id="tab-permissions" role="tab" tabindex="-1">{% trans "Permissions" %}</button>
|
||||
<button aria-controls="panel-exceptions" aria-selected="false" id="tab-exceptions" role="tab" tabindex="-1">{% trans "Exceptions" %}</button>
|
||||
</div>
|
||||
<div class="pk-tabs--container">
|
||||
|
||||
<div aria-labelledby="tab-information" id="panel-information" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
<ul>
|
||||
{% for label, value in object.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-permissions" hidden id="panel-permissions" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
<ul>
|
||||
{% for label, value in object.get_permissions_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-exceptions" hidden id="panel-exceptions" role="tabpanel" tabindex="0">
|
||||
<div class="section">
|
||||
{% for exception in object.timeperiodexception_set.all %}
|
||||
<h4>{{ exception }}</h4>
|
||||
<ul>
|
||||
{% for label, value in exception.get_inspect_fields %}
|
||||
<li class="parameter-{{ label|slugify }}">
|
||||
<span class="parameter">{% blocktrans %}{{ label }}:{% endblocktrans %}</span>
|
||||
{{ value }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -54,6 +54,12 @@
|
|||
<a class="button button-paragraph" rel="popup" href="{% url 'chrono-manager-unavailability-calendar-delete' pk=unavailability_calendar.id %}">{% trans 'Delete' %}</a>
|
||||
{% endif %}
|
||||
|
||||
<h3>{% trans "Navigation" %}</h3>
|
||||
{% if show_history %}
|
||||
<a class="button button-paragraph" href="{% url 'chrono-manager-unavailability-calendar-history' pk=unavailability_calendar.pk %}">{% trans 'History' %}</a>
|
||||
{% endif %}
|
||||
<a class="button button-paragraph" href="{% url 'chrono-manager-unavailability-calendar-inspect' pk=unavailability_calendar.pk %}">{% trans 'Inspect' %}</a>
|
||||
|
||||
{% url 'chrono-manager-unavailability-calendar-list' as object_list_url %}
|
||||
{% include 'chrono/includes/application_detail_fragment.html' %}
|
||||
</aside>
|
||||
|
|
|
@ -65,6 +65,21 @@ urlpatterns = [
|
|||
views.unavailability_calendar_import_unavailabilities,
|
||||
name='chrono-manager-unavailability-calendar-import-unavailabilities',
|
||||
),
|
||||
path(
|
||||
'unavailability-calendar/<int:pk>/inspect/',
|
||||
views.unavailability_calendar_inspect,
|
||||
name='chrono-manager-unavailability-calendar-inspect',
|
||||
),
|
||||
path(
|
||||
'unavailability-calendar/<int:pk>/history/',
|
||||
views.unavailability_calendar_history,
|
||||
name='chrono-manager-unavailability-calendar-history',
|
||||
),
|
||||
path(
|
||||
'unavailability-calendar/<int:pk>/history/compare/',
|
||||
views.unavailability_calendar_history_compare,
|
||||
name='chrono-manager-unavailability-calendar-history-compare',
|
||||
),
|
||||
path('resources/', views.resource_list, name='chrono-manager-resource-list'),
|
||||
path('resource/add/', views.resource_add, name='chrono-manager-resource-add'),
|
||||
path('resource/<int:pk>/', views.resource_view, name='chrono-manager-resource-view'),
|
||||
|
@ -90,10 +105,24 @@ urlpatterns = [
|
|||
),
|
||||
path('resource/<int:pk>/edit/', views.resource_edit, name='chrono-manager-resource-edit'),
|
||||
path('resource/<int:pk>/delete/', views.resource_delete, name='chrono-manager-resource-delete'),
|
||||
path('resource/<int:pk>/inspect/', views.resource_inspect, name='chrono-manager-resource-inspect'),
|
||||
path('resource/<int:pk>/history/', views.resource_history, name='chrono-manager-resource-history'),
|
||||
path(
|
||||
'resource/<int:pk>/history/compare/',
|
||||
views.resource_history_compare,
|
||||
name='chrono-manager-resource-history-compare',
|
||||
),
|
||||
path('categories/', views.category_list, name='chrono-manager-category-list'),
|
||||
path('category/add/', views.category_add, name='chrono-manager-category-add'),
|
||||
path('category/<int:pk>/edit/', views.category_edit, name='chrono-manager-category-edit'),
|
||||
path('category/<int:pk>/delete/', views.category_delete, name='chrono-manager-category-delete'),
|
||||
path('category/<int:pk>/inspect/', views.category_inspect, name='chrono-manager-category-inspect'),
|
||||
path('category/<int:pk>/history/', views.category_history, name='chrono-manager-category-history'),
|
||||
path(
|
||||
'category/<int:pk>/history/compare/',
|
||||
views.category_history_compare,
|
||||
name='chrono-manager-category-history-compare',
|
||||
),
|
||||
path('events-types/', views.events_type_list, name='chrono-manager-events-type-list'),
|
||||
path('events-type/add/', views.events_type_add, name='chrono-manager-events-type-add'),
|
||||
path('events-type/<int:pk>/edit/', views.events_type_edit, name='chrono-manager-events-type-edit'),
|
||||
|
@ -102,6 +131,17 @@ urlpatterns = [
|
|||
views.events_type_delete,
|
||||
name='chrono-manager-events-type-delete',
|
||||
),
|
||||
path(
|
||||
'events-type/<int:pk>/inspect/', views.events_type_inspect, name='chrono-manager-events-type-inspect'
|
||||
),
|
||||
path(
|
||||
'events-type/<int:pk>/history/', views.events_type_history, name='chrono-manager-events-type-history'
|
||||
),
|
||||
path(
|
||||
'events-type/<int:pk>/history/compare/',
|
||||
views.events_type_history_compare,
|
||||
name='chrono-manager-events-type-history-compare',
|
||||
),
|
||||
path('agendas/add/', views.agenda_add, name='chrono-manager-agenda-add'),
|
||||
path('agendas/import/', views.agendas_import, name='chrono-manager-agendas-import'),
|
||||
path('agendas/export/', views.agendas_export, name='chrono-manager-agendas-export'),
|
||||
|
@ -449,6 +489,13 @@ urlpatterns = [
|
|||
views.agenda_import_events_sample_csv,
|
||||
name='chrono-manager-sample-events-csv',
|
||||
),
|
||||
path('agendas/<int:pk>/inspect/', views.agenda_inspect, name='chrono-manager-agenda-inspect'),
|
||||
path('agendas/<int:pk>/history/', views.agenda_history, name='chrono-manager-agenda-history'),
|
||||
path(
|
||||
'agendas/<int:pk>/history/compare/',
|
||||
views.agenda_history_compare,
|
||||
name='chrono-manager-agenda-history-compare',
|
||||
),
|
||||
path(
|
||||
'shared-custody/settings/',
|
||||
views.shared_custody_settings,
|
||||
|
|
|
@ -30,7 +30,7 @@ from django.conf import settings
|
|||
from django.contrib import messages
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import IntegrityError, models, transaction
|
||||
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Func, Max, Min, Q, Value
|
||||
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Func, Max, Min, Prefetch, Q, Value
|
||||
from django.db.models.deletion import ProtectedError
|
||||
from django.db.models.functions import Cast
|
||||
from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseRedirect
|
||||
|
@ -92,6 +92,14 @@ from chrono.agendas.models import (
|
|||
VirtualMember,
|
||||
)
|
||||
from chrono.apps.export_import.models import Application
|
||||
from chrono.apps.snapshot.models import (
|
||||
AgendaSnapshot,
|
||||
CategorySnapshot,
|
||||
EventsTypeSnapshot,
|
||||
ResourceSnapshot,
|
||||
UnavailabilityCalendarSnapshot,
|
||||
)
|
||||
from chrono.apps.snapshot.views import InstanceWithSnapshotHistoryCompareView, InstanceWithSnapshotHistoryView
|
||||
from chrono.utils.date import get_weekday_index
|
||||
from chrono.utils.timezone import localtime, make_aware, make_naive, now
|
||||
|
||||
|
@ -288,6 +296,7 @@ class ResourceDetailView(DetailView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['resource'] = self.object
|
||||
context['show_history'] = settings.SNAPSHOTS_ENABLED
|
||||
return context
|
||||
|
||||
|
||||
|
@ -791,6 +800,49 @@ class ResourceDeleteView(DeleteView):
|
|||
resource_delete = ResourceDeleteView.as_view()
|
||||
|
||||
|
||||
class ResourceInspectView(DetailView):
|
||||
template_name = 'chrono/manager_resource_inspect.html'
|
||||
model = Resource
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
resource_inspect = ResourceInspectView.as_view()
|
||||
|
||||
|
||||
class ResourceHistoryView(InstanceWithSnapshotHistoryView):
|
||||
template_name = 'chrono/manager_resource_history.html'
|
||||
model = ResourceSnapshot
|
||||
instance_context_key = 'resource'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
resource_history = ResourceHistoryView.as_view()
|
||||
|
||||
|
||||
class ResourceHistoryCompareView(InstanceWithSnapshotHistoryCompareView):
|
||||
template_name = 'chrono/manager_resource_history_compare.html'
|
||||
inspect_template_name = 'chrono/manager_resource_inspect_fragment.html'
|
||||
model = Resource
|
||||
instance_context_key = 'resource'
|
||||
history_view = 'chrono-manager-resource-history'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
resource_history_compare = ResourceHistoryCompareView.as_view()
|
||||
|
||||
|
||||
class CategoryListView(WithApplicationsMixin, ListView):
|
||||
template_name = 'chrono/manager_category_list.html'
|
||||
model = Category
|
||||
|
@ -852,6 +904,10 @@ class CategoryEditView(UpdateView):
|
|||
self.object.take_snapshot(request=self.request)
|
||||
return response
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['show_history'] = settings.SNAPSHOTS_ENABLED
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
category_edit = CategoryEditView.as_view()
|
||||
|
||||
|
@ -876,6 +932,49 @@ class CategoryDeleteView(DeleteView):
|
|||
category_delete = CategoryDeleteView.as_view()
|
||||
|
||||
|
||||
class CategoryInspectView(DetailView):
|
||||
template_name = 'chrono/manager_category_inspect.html'
|
||||
model = Category
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
category_inspect = CategoryInspectView.as_view()
|
||||
|
||||
|
||||
class CategoryHistoryView(InstanceWithSnapshotHistoryView):
|
||||
template_name = 'chrono/manager_category_history.html'
|
||||
model = CategorySnapshot
|
||||
instance_context_key = 'category'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
category_history = CategoryHistoryView.as_view()
|
||||
|
||||
|
||||
class CategoryHistoryCompareView(InstanceWithSnapshotHistoryCompareView):
|
||||
template_name = 'chrono/manager_category_history_compare.html'
|
||||
inspect_template_name = 'chrono/manager_category_inspect_fragment.html'
|
||||
model = Category
|
||||
instance_context_key = 'category'
|
||||
history_view = 'chrono-manager-category-history'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
category_history_compare = CategoryHistoryCompareView.as_view()
|
||||
|
||||
|
||||
class EventsTypeListView(WithApplicationsMixin, ListView):
|
||||
template_name = 'chrono/manager_events_type_list.html'
|
||||
model = EventsType
|
||||
|
@ -934,6 +1033,7 @@ class EventsTypeEditView(UpdateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['show_history'] = settings.SNAPSHOTS_ENABLED
|
||||
data = None
|
||||
if self.request.method == 'POST':
|
||||
data = self.request.POST
|
||||
|
@ -1020,6 +1120,50 @@ class EventsTypeDeleteView(DeleteView):
|
|||
events_type_delete = EventsTypeDeleteView.as_view()
|
||||
|
||||
|
||||
class EventsTypeInspectView(DetailView):
|
||||
template_name = 'chrono/manager_events_type_inspect.html'
|
||||
model = EventsType
|
||||
context_object_name = 'events_type'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
events_type_inspect = EventsTypeInspectView.as_view()
|
||||
|
||||
|
||||
class EventsTypeHistoryView(InstanceWithSnapshotHistoryView):
|
||||
template_name = 'chrono/manager_events_type_history.html'
|
||||
model = EventsTypeSnapshot
|
||||
instance_context_key = 'events_type'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
events_type_history = EventsTypeHistoryView.as_view()
|
||||
|
||||
|
||||
class EventsTypeHistoryCompareView(InstanceWithSnapshotHistoryCompareView):
|
||||
template_name = 'chrono/manager_events_type_history_compare.html'
|
||||
inspect_template_name = 'chrono/manager_events_type_inspect_fragment.html'
|
||||
model = EventsType
|
||||
instance_context_key = 'events_type'
|
||||
history_view = 'chrono-manager-events-type-history'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_staff:
|
||||
raise PermissionDenied()
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
events_type_history_compare = EventsTypeHistoryCompareView.as_view()
|
||||
|
||||
|
||||
class AgendaAddView(CreateView):
|
||||
template_name = 'chrono/manager_agenda_add_form.html'
|
||||
model = Agenda
|
||||
|
@ -1252,6 +1396,7 @@ class AgendaEditView(ManagedAgendaMixin, UpdateView):
|
|||
|
||||
def form_valid(self, *args, **kwargs):
|
||||
response = super().form_valid(*args, **kwargs)
|
||||
self.agenda = Agenda.objects.get(pk=self.agenda.pk) # refresh object, for M2M
|
||||
self.agenda.take_snapshot(request=self.request, comment=self.comment)
|
||||
return response
|
||||
|
||||
|
@ -1614,6 +1759,7 @@ class EventChecksMixin:
|
|||
)
|
||||
subscription.presence_form = BookingCheckPresenceForm(
|
||||
agenda=self.agenda,
|
||||
subscription=True,
|
||||
)
|
||||
# sort results
|
||||
if (
|
||||
|
@ -2430,6 +2576,7 @@ class AgendaSettings(ManagedAgendaMixin, DetailView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['show_history'] = settings.SNAPSHOTS_ENABLED
|
||||
if self.agenda.accept_meetings():
|
||||
context['meeting_types'] = self.object.iter_meetingtypes()
|
||||
if self.agenda.kind == 'virtual':
|
||||
|
@ -4052,6 +4199,60 @@ class TimePeriodExceptionSourceRefreshView(ManagedTimePeriodExceptionMixin, Deta
|
|||
time_period_exception_source_refresh = TimePeriodExceptionSourceRefreshView.as_view()
|
||||
|
||||
|
||||
class AgendaInspectView(ManagedAgendaMixin, DetailView):
|
||||
template_name = 'chrono/manager_agenda_inspect.html'
|
||||
model = Agenda
|
||||
|
||||
def set_agenda(self, **kwargs):
|
||||
self.agenda = get_object_or_404(
|
||||
Agenda.objects.select_related(
|
||||
'category', 'events_type', 'edit_role', 'view_role'
|
||||
).prefetch_related(
|
||||
'resources',
|
||||
Prefetch(
|
||||
'desk_set',
|
||||
queryset=Desk.objects.prefetch_related(
|
||||
'timeperiod_set',
|
||||
'timeperiodexceptionsource_set',
|
||||
'unavailability_calendars',
|
||||
Prefetch(
|
||||
'timeperiodexception_set',
|
||||
queryset=TimePeriodException.objects.filter(source__isnull=True),
|
||||
),
|
||||
),
|
||||
),
|
||||
Prefetch('event_set', queryset=Event.objects.filter(primary_event__isnull=True)),
|
||||
),
|
||||
id=kwargs.get('pk'),
|
||||
)
|
||||
|
||||
def get_object(self):
|
||||
return self.agenda
|
||||
|
||||
|
||||
agenda_inspect = AgendaInspectView.as_view()
|
||||
|
||||
|
||||
class AgendaHistoryView(ManagedAgendaMixin, InstanceWithSnapshotHistoryView):
|
||||
template_name = 'chrono/manager_agenda_history.html'
|
||||
model = AgendaSnapshot
|
||||
instance_context_key = 'agenda'
|
||||
|
||||
|
||||
agenda_history = AgendaHistoryView.as_view()
|
||||
|
||||
|
||||
class AgendaHistoryCompareView(ManagedAgendaMixin, InstanceWithSnapshotHistoryCompareView):
|
||||
template_name = 'chrono/manager_agenda_history_compare.html'
|
||||
inspect_template_name = 'chrono/manager_agenda_inspect_fragment.html'
|
||||
model = Agenda
|
||||
instance_context_key = 'agenda'
|
||||
history_view = 'chrono-manager-agenda-history'
|
||||
|
||||
|
||||
agenda_history_compare = AgendaHistoryCompareView.as_view()
|
||||
|
||||
|
||||
class BookingCancelView(ViewableAgendaMixin, UpdateView):
|
||||
template_name = 'chrono/manager_confirm_booking_cancellation.html'
|
||||
model = Booking
|
||||
|
@ -4560,6 +4761,7 @@ class UnavailabilityCalendarSettings(ManagedUnavailabilityCalendarMixin, DetailV
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['unavailability_calendar'] = self.object
|
||||
context['show_history'] = settings.SNAPSHOTS_ENABLED
|
||||
return context
|
||||
|
||||
|
||||
|
@ -4669,6 +4871,45 @@ class UnavailabilityCalendarImportUnavailabilitiesView(ManagedUnavailabilityCale
|
|||
unavailability_calendar_import_unavailabilities = UnavailabilityCalendarImportUnavailabilitiesView.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarInspectView(ManagedUnavailabilityCalendarMixin, DetailView):
|
||||
template_name = 'chrono/manager_unavailability_calendar_inspect.html'
|
||||
model = UnavailabilityCalendar
|
||||
context_object_name = 'unavailability_calendar'
|
||||
|
||||
def set_unavailability_calendar(self, **kwargs):
|
||||
self.unavailability_calendar = get_object_or_404(
|
||||
UnavailabilityCalendar.objects.select_related('edit_role', 'view_role'), pk=kwargs.get('pk')
|
||||
)
|
||||
|
||||
def get_object(self):
|
||||
return self.unavailability_calendar
|
||||
|
||||
|
||||
unavailability_calendar_inspect = UnavailabilityCalendarInspectView.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarHistoryView(ManagedUnavailabilityCalendarMixin, InstanceWithSnapshotHistoryView):
|
||||
template_name = 'chrono/manager_unavailability_calendar_history.html'
|
||||
model = UnavailabilityCalendarSnapshot
|
||||
instance_context_key = 'unavailability_calendar'
|
||||
|
||||
|
||||
unavailability_calendar_history = UnavailabilityCalendarHistoryView.as_view()
|
||||
|
||||
|
||||
class UnavailabilityCalendarHistoryCompareView(
|
||||
ManagedUnavailabilityCalendarMixin, InstanceWithSnapshotHistoryCompareView
|
||||
):
|
||||
template_name = 'chrono/manager_unavailability_calendar_history_compare.html'
|
||||
inspect_template_name = 'chrono/manager_unavailability_calendar_inspect_fragment.html'
|
||||
model = UnavailabilityCalendar
|
||||
instance_context_key = 'unavailability_calendar'
|
||||
history_view = 'chrono-manager-unavailability-calendar-history'
|
||||
|
||||
|
||||
unavailability_calendar_history_compare = UnavailabilityCalendarHistoryCompareView.as_view()
|
||||
|
||||
|
||||
class SharedCustodyAgendaMixin:
|
||||
agenda = None
|
||||
tab_anchor = None
|
||||
|
|
|
@ -206,6 +206,7 @@ REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'chrono.api.utils.exception_handler'}
|
|||
|
||||
SHARED_CUSTODY_ENABLED = False
|
||||
PARTIAL_BOOKINGS_ENABLED = False
|
||||
SNAPSHOTS_ENABLED = False
|
||||
|
||||
CHRONO_ANTS_HUB_URL = None
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ class CheckType:
|
|||
slug: str
|
||||
label: str
|
||||
kind: str
|
||||
unexpected_presence: bool = False
|
||||
|
||||
|
||||
def get_agenda_check_types(agenda):
|
||||
|
@ -73,5 +74,12 @@ def get_agenda_check_types(agenda):
|
|||
|
||||
check_types = []
|
||||
for ct in result['data']:
|
||||
check_types.append(CheckType(slug=ct['id'], label=ct['text'], kind=ct['kind']))
|
||||
check_types.append(
|
||||
CheckType(
|
||||
slug=ct['id'],
|
||||
label=ct['text'],
|
||||
kind=ct['kind'],
|
||||
unexpected_presence=ct.get('unexpected_presence') or False,
|
||||
)
|
||||
)
|
||||
return check_types
|
||||
|
|
|
@ -14,7 +14,9 @@ Package: python3-chrono
|
|||
Architecture: all
|
||||
Depends: python3-django (>= 2:3.2),
|
||||
python3-gadjo,
|
||||
python3-lxml,
|
||||
python3-publik-django-templatetags,
|
||||
python3-pyquery,
|
||||
python3-requests,
|
||||
python3-uwsgidecorators,
|
||||
${misc:Depends},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[MASTER]
|
||||
persistent=yes
|
||||
ignore=vendor,Bouncers,ezt.py
|
||||
extension-pkg-allow-list=lxml
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=
|
||||
|
|
2
setup.py
2
setup.py
|
@ -165,10 +165,12 @@ setup(
|
|||
'django-filter<23.2',
|
||||
'vobject',
|
||||
'python-dateutil',
|
||||
'pyquery',
|
||||
'requests',
|
||||
'workalendar',
|
||||
'weasyprint',
|
||||
'sorl-thumbnail',
|
||||
'lxml',
|
||||
],
|
||||
zip_safe=False,
|
||||
cmdclass={
|
||||
|
|
|
@ -13,8 +13,11 @@ from chrono.apps.export_import.models import Application, ApplicationElement
|
|||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_object_types(app, user):
|
||||
def test_object_types(app, user, admin_user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
app.get('/api/export-import/', status=403)
|
||||
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
resp = app.get('/api/export-import/')
|
||||
assert resp.json == {
|
||||
'data': [
|
||||
|
@ -63,8 +66,8 @@ def test_object_types(app, user):
|
|||
}
|
||||
|
||||
|
||||
def test_list(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_list(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
|
||||
Agenda.objects.create(label='Event', slug='event', kind='events')
|
||||
Category.objects.create(slug='cat', label='Category')
|
||||
|
@ -163,8 +166,8 @@ def test_list(app, user):
|
|||
}
|
||||
|
||||
|
||||
def test_export_agenda(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_export_agenda(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
group1 = Group.objects.create(name='group1')
|
||||
group2 = Group.objects.create(name='group2')
|
||||
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings', edit_role=group1, view_role=group2)
|
||||
|
@ -173,8 +176,8 @@ def test_export_agenda(app, user):
|
|||
assert resp.json['data']['permissions'] == {'view': 'group2', 'edit': 'group1'}
|
||||
|
||||
|
||||
def test_export_minor_components(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_export_minor_components(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
Category.objects.create(slug='cat', label='Category')
|
||||
Resource.objects.create(slug='foo', label='Foo')
|
||||
EventsType.objects.create(slug='foo', label='Foo')
|
||||
|
@ -189,9 +192,12 @@ def test_export_minor_components(app, user):
|
|||
resp = app.get('/api/export-import/unavailability_calendars/foo/')
|
||||
assert resp.json['data']['label'] == 'Foo'
|
||||
|
||||
# unknown component
|
||||
app.get('/api/export-import/agendas/foo/', status=404)
|
||||
|
||||
def test_agenda_dependencies_category(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
def test_agenda_dependencies_category(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
category = Category.objects.create(slug='cat', label='Category')
|
||||
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings', category=category)
|
||||
resp = app.get('/api/export-import/agendas/rdv/dependencies/')
|
||||
|
@ -212,8 +218,8 @@ def test_agenda_dependencies_category(app, user):
|
|||
}
|
||||
|
||||
|
||||
def test_agenda_dependencies_resources(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_agenda_dependencies_resources(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
meetings_agenda = Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
|
||||
meetings_agenda.resources.add(Resource.objects.create(slug='foo', label='Foo'))
|
||||
resp = app.get('/api/export-import/agendas/rdv/dependencies/')
|
||||
|
@ -234,11 +240,12 @@ def test_agenda_dependencies_resources(app, user):
|
|||
}
|
||||
|
||||
|
||||
def test_agenda_dependencies_unavailability_calendars(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_agenda_dependencies_unavailability_calendars(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
meetings_agenda = Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
|
||||
desk = Desk.objects.create(slug='foo', label='Foo', agenda=meetings_agenda)
|
||||
desk.unavailability_calendars.add(UnavailabilityCalendar.objects.create(slug='foo', label='Foo'))
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(slug='foo', label='Foo')
|
||||
desk.unavailability_calendars.add(unavailability_calendar)
|
||||
resp = app.get('/api/export-import/agendas/rdv/dependencies/')
|
||||
assert resp.json == {
|
||||
'data': [
|
||||
|
@ -255,10 +262,29 @@ def test_agenda_dependencies_unavailability_calendars(app, user):
|
|||
],
|
||||
'err': 0,
|
||||
}
|
||||
events_agenda = Agenda.objects.create(label='Evt', slug='evt', kind='events')
|
||||
desk = Desk.objects.create(agenda=events_agenda, slug='_exceptions_holder')
|
||||
desk.unavailability_calendars.add(unavailability_calendar)
|
||||
resp = app.get('/api/export-import/agendas/evt/dependencies/')
|
||||
assert resp.json == {
|
||||
'data': [
|
||||
{
|
||||
'id': 'foo',
|
||||
'text': 'Foo',
|
||||
'type': 'unavailability_calendars',
|
||||
'urls': {
|
||||
'dependencies': 'http://testserver/api/export-import/unavailability_calendars/foo/dependencies/',
|
||||
'export': 'http://testserver/api/export-import/unavailability_calendars/foo/',
|
||||
'redirect': 'http://testserver/api/export-import/unavailability_calendars/foo/redirect/',
|
||||
},
|
||||
}
|
||||
],
|
||||
'err': 0,
|
||||
}
|
||||
|
||||
|
||||
def test_agenda_dependencies_groups(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_agenda_dependencies_groups(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
group1 = Group.objects.create(name='group1')
|
||||
group2 = Group.objects.create(name='group2')
|
||||
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings', edit_role=group1, view_role=group2)
|
||||
|
@ -274,8 +300,8 @@ def test_agenda_dependencies_groups(app, user):
|
|||
}
|
||||
|
||||
|
||||
def test_agenda_dependencies_virtual_agendas(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_agenda_dependencies_virtual_agendas(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
rdv1 = Agenda.objects.create(label='Rdv1', slug='rdv1', kind='meetings')
|
||||
rdv2 = Agenda.objects.create(label='Rdv2', slug='rdv2', kind='meetings')
|
||||
virt = Agenda.objects.create(label='Virt', slug='virt', kind='virtual')
|
||||
|
@ -309,10 +335,11 @@ def test_agenda_dependencies_virtual_agendas(app, user):
|
|||
}
|
||||
|
||||
|
||||
def test_agenda_dependencies_events_type(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_agenda_dependencies_events_type(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
events_type = EventsType.objects.create(slug='foo', label='Foo')
|
||||
Agenda.objects.create(label='Evt', slug='evt', kind='events', events_type=events_type)
|
||||
events_agenda = Agenda.objects.create(label='Evt', slug='evt', kind='events', events_type=events_type)
|
||||
Desk.objects.create(agenda=events_agenda, slug='_exceptions_holder')
|
||||
resp = app.get('/api/export-import/agendas/evt/dependencies/')
|
||||
assert resp.json == {
|
||||
'data': [
|
||||
|
@ -331,8 +358,13 @@ def test_agenda_dependencies_events_type(app, user):
|
|||
}
|
||||
|
||||
|
||||
def test_unknown_compoment_dependencies(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
app.get('/api/export-import/agendas/foo/dependencies/', status=404)
|
||||
|
||||
|
||||
def test_redirect(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
app.authorization = ('Basic', ('john', 'doe'))
|
||||
agenda = Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
|
||||
category = Category.objects.create(slug='cat', label='Category')
|
||||
resource = Resource.objects.create(slug='foo', label='Foo')
|
||||
|
@ -360,8 +392,8 @@ def test_redirect(app, user):
|
|||
assert resp.location == f'/manage/unavailability-calendar/{unavailability_calendar.pk}/'
|
||||
|
||||
|
||||
def create_bundle(app, user, visible=True, version_number='42.0'):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def create_bundle(app, admin_user, visible=True, version_number='42.0'):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
|
||||
group, _ = Group.objects.get_or_create(name='plop')
|
||||
category, _ = Category.objects.get_or_create(slug='foo', label='Foo')
|
||||
|
@ -446,12 +478,12 @@ def bundle(app, user):
|
|||
return create_bundle(app, user)
|
||||
|
||||
|
||||
def test_bundle_import(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_bundle_import(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
|
||||
bundles = []
|
||||
for version_number in ['42.0', '42.1']:
|
||||
bundles.append(create_bundle(app, user, version_number=version_number))
|
||||
bundles.append(create_bundle(app, admin_user, version_number=version_number))
|
||||
|
||||
Agenda.objects.all().delete()
|
||||
Category.objects.all().delete()
|
||||
|
@ -505,10 +537,10 @@ def test_bundle_import(app, user):
|
|||
)
|
||||
|
||||
|
||||
def test_bundle_declare(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_bundle_declare(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
|
||||
bundle = create_bundle(app, user, visible=False)
|
||||
bundle = create_bundle(app, admin_user, visible=False)
|
||||
resp = app.put('/api/export-import/bundle-declare/', bundle)
|
||||
assert Agenda.objects.all().count() == 4
|
||||
assert resp.json['err'] == 0
|
||||
|
@ -525,7 +557,7 @@ def test_bundle_declare(app, user):
|
|||
assert application.visible is False
|
||||
assert ApplicationElement.objects.count() == 8
|
||||
|
||||
bundle = create_bundle(app, user, visible=True)
|
||||
bundle = create_bundle(app, admin_user, visible=True)
|
||||
# create link to element not present in manifest: it should be unlinked
|
||||
last_page = Agenda.objects.latest('pk')
|
||||
ApplicationElement.objects.create(
|
||||
|
@ -543,8 +575,8 @@ def test_bundle_declare(app, user):
|
|||
assert ApplicationElement.objects.count() == 4 # category, events_type, unavailability_calendar, resource
|
||||
|
||||
|
||||
def test_bundle_unlink(app, user, bundle):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_bundle_unlink(app, admin_user, bundle):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
|
||||
application = Application.objects.create(
|
||||
name='Test',
|
||||
|
@ -598,6 +630,6 @@ def test_bundle_unlink(app, user, bundle):
|
|||
assert ApplicationElement.objects.count() == 2
|
||||
|
||||
|
||||
def test_bundle_check(app, user):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
def test_bundle_check(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
assert app.put('/api/export-import/bundle-check/').json == {'err': 0, 'data': {}}
|
||||
|
|
|
@ -13,14 +13,17 @@ from django.test.utils import CaptureQueriesContext
|
|||
|
||||
from chrono.agendas.models import (
|
||||
Agenda,
|
||||
AgendaNotificationsSettings,
|
||||
AgendaReminderSettings,
|
||||
Booking,
|
||||
Desk,
|
||||
Event,
|
||||
EventsType,
|
||||
MeetingType,
|
||||
Resource,
|
||||
TimePeriod,
|
||||
TimePeriodException,
|
||||
TimePeriodExceptionSource,
|
||||
UnavailabilityCalendar,
|
||||
VirtualMember,
|
||||
)
|
||||
|
@ -478,13 +481,15 @@ def test_add_agenda_and_set_role(app, admin_user, manager_user):
|
|||
resp = resp.form.submit().follow()
|
||||
assert 'Edit Role: Managers' in resp.text
|
||||
assert AgendaSnapshot.objects.count() == 2
|
||||
snapshot = AgendaSnapshot.objects.latest('pk')
|
||||
assert snapshot.serialization['permissions'] == {'edit': 'Managers', 'view': None}
|
||||
|
||||
# still only one desk
|
||||
assert agenda.desk_set.count() == 1
|
||||
|
||||
|
||||
def test_agenda_set_role_with_partial_booking(settings, app, admin_user):
|
||||
settings.PARTIAL_BOOKING_ENABLED = True
|
||||
settings.PARTIAL_BOOKINGS_ENABLED = True
|
||||
|
||||
group = Group.objects.create(name='testgroup')
|
||||
agenda = Agenda.objects.create(label='Foobar')
|
||||
|
@ -798,6 +803,78 @@ def test_options_agenda_as_manager(app, manager_user):
|
|||
assert '<h2>Settings' in resp.text
|
||||
|
||||
|
||||
def test_inspect_agenda(app, admin_user):
|
||||
meetings_agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
||||
meetings_agenda.resources.add(Resource.objects.create(slug='foo', label='Foo'))
|
||||
desk = Desk.objects.create(slug='foo', label='Foo', agenda=meetings_agenda)
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(slug='foo', label='Foo')
|
||||
desk.unavailability_calendars.add(unavailability_calendar)
|
||||
MeetingType.objects.create(agenda=meetings_agenda, label='Meeting Type', duration=30)
|
||||
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)
|
||||
TimePeriod.objects.create(
|
||||
desk=desk, weekday=2, start_time=tpx_start.time(), end_time=tpx_end.time(), weekday_indexes=[1, 3]
|
||||
)
|
||||
TimePeriod.objects.create(
|
||||
desk=desk, date=datetime.date(2022, 10, 24), start_time=tpx_start.time(), end_time=tpx_end.time()
|
||||
)
|
||||
TimePeriodExceptionSource.objects.create(desk=desk, ics_url='http://example.com/sample.ics')
|
||||
AgendaNotificationsSettings.objects.create(
|
||||
agenda=meetings_agenda,
|
||||
full_event=AgendaNotificationsSettings.EMAIL_FIELD,
|
||||
full_event_emails=['hop@entrouvert.com', 'top@entrouvert.com'],
|
||||
)
|
||||
AgendaReminderSettings.objects.create(agenda=meetings_agenda, days_before_email=1, email_extra_info='top')
|
||||
|
||||
events_agenda = Agenda.objects.create(label='Events', kind='events')
|
||||
Event.objects.create(
|
||||
agenda=events_agenda, start_datetime=make_aware(datetime.datetime(2020, 7, 21, 16, 42, 35)), places=10
|
||||
)
|
||||
exceptions_desk = Desk.objects.create(agenda=events_agenda, slug='_exceptions_holder')
|
||||
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=exceptions_desk, start_datetime=tpx_start, end_datetime=tpx_end)
|
||||
exceptions_desk.unavailability_calendars.add(unavailability_calendar)
|
||||
|
||||
virtual_agenda = Agenda.objects.create(label='Virtual', kind='virtual')
|
||||
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=meetings_agenda)
|
||||
TimePeriod.objects.create(
|
||||
agenda=virtual_agenda, weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(11, 0)
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
|
||||
resp = app.get('/manage/agendas/%s/settings' % meetings_agenda.pk)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.click('Inspect')
|
||||
assert len(ctx.captured_queries) == 12
|
||||
|
||||
resp = app.get('/manage/agendas/%s/settings' % events_agenda.pk)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.click('Inspect')
|
||||
assert len(ctx.captured_queries) == 12
|
||||
|
||||
resp = app.get('/manage/agendas/%s/settings' % virtual_agenda.pk)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.click('Inspect')
|
||||
assert len(ctx.captured_queries) == 8
|
||||
|
||||
|
||||
def test_inspect_agenda_as_manager(app, manager_user):
|
||||
agenda = Agenda.objects.create(slug='foo', label='Foo')
|
||||
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
||||
|
||||
app = login(app, username='manager', password='manager')
|
||||
agenda.view_role = manager_user.groups.all()[0]
|
||||
agenda.save()
|
||||
app.get('/manage/agendas/%s/inspect/' % agenda.pk, status=403)
|
||||
|
||||
agenda.edit_role = manager_user.groups.all()[0]
|
||||
agenda.save()
|
||||
app.get('/manage/agendas/%s/inspect/' % agenda.pk, status=200)
|
||||
|
||||
|
||||
@mock.patch('chrono.agendas.models.Agenda.is_available_for_simple_management')
|
||||
def test_agenda_options_desk_simple_management(available_mock, app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import pytest
|
||||
from django.db import connection
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
|
||||
from chrono.agendas.models import Agenda, Category
|
||||
from chrono.apps.snapshot.models import CategorySnapshot
|
||||
|
@ -84,3 +86,14 @@ def test_delete_category_as_manager(app, manager_user):
|
|||
category = Category.objects.create(label='Foo bar')
|
||||
app = login(app, username='manager', password='manager')
|
||||
app.get('/manage/category/%s/delete/' % category.pk, status=403)
|
||||
|
||||
|
||||
def test_inspect_category(app, admin_user):
|
||||
category = Category.objects.create(label='Foo bar')
|
||||
|
||||
app = login(app)
|
||||
|
||||
resp = app.get('/manage/category/%s/edit/' % category.pk)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.click('Inspect')
|
||||
assert len(ctx.captured_queries) == 3
|
||||
|
|
|
@ -1026,7 +1026,7 @@ def test_import_events(app, admin_user):
|
|||
)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.form.submit(status=302)
|
||||
assert len(ctx.captured_queries) == 32
|
||||
assert len(ctx.captured_queries) == 31
|
||||
assert Event.objects.count() == 5
|
||||
assert set(Event.objects.values_list('slug', flat=True)) == {
|
||||
'labelb',
|
||||
|
@ -2482,11 +2482,12 @@ def test_event_check_booking(check_types, app, admin_user):
|
|||
|
||||
check_types.return_value = [
|
||||
CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
|
||||
CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
|
||||
CheckType(slug='bar-reason', label='Bar reason', kind='presence', unexpected_presence=True),
|
||||
]
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert len(resp.pyquery.find('td.booking-actions form.absence select')) == 1
|
||||
assert len(resp.pyquery.find('td.booking-actions form.presence select')) == 1
|
||||
assert resp.pyquery.find('td.booking-actions form.presence option:selected').text() == '---------'
|
||||
|
||||
# reset
|
||||
_test_reset()
|
||||
|
@ -2833,6 +2834,14 @@ def test_event_check_subscription(check_types, app, admin_user):
|
|||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert '/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk) in resp
|
||||
assert '/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk) in resp
|
||||
assert resp.pyquery.find('td.booking-actions form.presence option:selected').text() == '---------'
|
||||
check_types.return_value = [
|
||||
CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
|
||||
CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
|
||||
CheckType(slug='baz-reason', label='Baz reason', kind='presence', unexpected_presence=True),
|
||||
]
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
assert resp.pyquery.find('td.booking-actions form.presence option:selected').text() == 'Baz reason'
|
||||
app.post(
|
||||
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': token, 'check_type': 'bar-reason'},
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import pytest
|
||||
from django.db import connection
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
|
||||
from chrono.agendas.models import Agenda, EventsType
|
||||
from chrono.apps.snapshot.models import EventsTypeSnapshot
|
||||
|
@ -212,3 +214,14 @@ def test_delete_events_type_as_manager(app, manager_user):
|
|||
events_type = EventsType.objects.create(label='Foo bar')
|
||||
app = login(app, username='manager', password='manager')
|
||||
app.get('/manage/events-type/%s/delete/' % events_type.pk, status=403)
|
||||
|
||||
|
||||
def test_inspect_events_type(app, admin_user):
|
||||
events_type = EventsType.objects.create(label='Foo bar')
|
||||
|
||||
app = login(app)
|
||||
|
||||
resp = app.get('/manage/events-type/%s/edit/' % events_type.pk)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.click('Inspect')
|
||||
assert len(ctx.captured_queries) == 3
|
||||
|
|
|
@ -1043,3 +1043,14 @@ def test_resource_today_button(app, admin_user):
|
|||
|
||||
resp = app.get('/manage/resource/%s/week/%s/%s/%s/' % (resource.pk, today.year, today.month, today.day))
|
||||
assert 'Today' not in resp.pyquery('a.active').text()
|
||||
|
||||
|
||||
def test_inspect_resource(app, admin_user):
|
||||
resource = Resource.objects.create(label='Foo bar')
|
||||
|
||||
app = login(app)
|
||||
|
||||
resp = app.get('/manage/resource/%s/' % resource.pk)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.click('Inspect')
|
||||
assert len(ctx.captured_queries) == 3
|
||||
|
|
|
@ -0,0 +1,308 @@
|
|||
import datetime
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import now
|
||||
|
||||
from chrono.agendas.models import Agenda, Category, Desk, Event, EventsType, Resource, UnavailabilityCalendar
|
||||
from chrono.apps.snapshot.models import (
|
||||
AgendaSnapshot,
|
||||
CategorySnapshot,
|
||||
EventsTypeSnapshot,
|
||||
ResourceSnapshot,
|
||||
UnavailabilityCalendarSnapshot,
|
||||
)
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_agenda_history(settings, app, admin_user):
|
||||
agenda = Agenda.objects.create(slug='foo', label='Foo')
|
||||
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
||||
snapshot1 = agenda.take_snapshot()
|
||||
Event.objects.create(
|
||||
agenda=agenda,
|
||||
places=1,
|
||||
start_datetime=now() - datetime.timedelta(days=60),
|
||||
)
|
||||
agenda.description = 'Foo Bar'
|
||||
agenda.save()
|
||||
snapshot2 = agenda.take_snapshot()
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert AgendaSnapshot.objects.count() == 2
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
assert 'History' not in resp
|
||||
settings.SNAPSHOTS_ENABLED = True
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
resp = resp.click('History')
|
||||
assert [x.attrib['class'] for x in resp.pyquery.find('.snapshots-list tr')] == [
|
||||
'new-day',
|
||||
'collapsed',
|
||||
]
|
||||
assert '(Version 42.0)' in resp.pyquery('tr:nth-child(1)').text()
|
||||
|
||||
for mode in ['json', 'inspect', '']:
|
||||
resp = app.get(
|
||||
'/manage/agendas/%s/history/compare/?version1=%s&version2=%s&mode=%s'
|
||||
% (agenda.pk, snapshot1.pk, snapshot2.pk, mode)
|
||||
)
|
||||
assert 'Snapshot (%s)' % (snapshot1.pk) in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
|
||||
if mode == 'inspect':
|
||||
assert resp.text.count('<ins>') == 6
|
||||
assert resp.text.count('<del>') == 0
|
||||
else:
|
||||
assert resp.text.count('diff_sub') == 1
|
||||
assert resp.text.count('diff_add') == 16
|
||||
assert resp.text.count('diff_chg') == 0
|
||||
resp = app.get(
|
||||
'/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (agenda.pk, snapshot2.pk, snapshot1.pk)
|
||||
)
|
||||
assert 'Snapshot (%s)' % (snapshot1.pk) in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
|
||||
assert resp.text.count('diff_sub') == 1
|
||||
assert resp.text.count('diff_add') == 16
|
||||
assert resp.text.count('diff_chg') == 0
|
||||
|
||||
|
||||
def test_agenda_history_as_manager(app, manager_user):
|
||||
agenda = Agenda.objects.create(slug='foo', label='Foo')
|
||||
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
|
||||
snapshot1 = agenda.take_snapshot()
|
||||
snapshot2 = agenda.take_snapshot()
|
||||
|
||||
app = login(app, username='manager', password='manager')
|
||||
agenda.view_role = manager_user.groups.all()[0]
|
||||
agenda.save()
|
||||
app.get('/manage/agendas/%s/history/' % agenda.pk, status=403)
|
||||
app.get(
|
||||
'/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (agenda.pk, snapshot2.pk, snapshot1.pk),
|
||||
status=403,
|
||||
)
|
||||
|
||||
agenda.edit_role = manager_user.groups.all()[0]
|
||||
agenda.save()
|
||||
app.get('/manage/agendas/%s/history/' % agenda.pk, status=200)
|
||||
app.get(
|
||||
'/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (agenda.pk, snapshot2.pk, snapshot1.pk),
|
||||
status=200,
|
||||
)
|
||||
|
||||
|
||||
def test_category_history(settings, app, admin_user):
|
||||
category = Category.objects.create(slug='foo', label='Foo')
|
||||
snapshot1 = category.take_snapshot()
|
||||
category.label = 'Bar'
|
||||
category.save()
|
||||
snapshot2 = category.take_snapshot()
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert CategorySnapshot.objects.count() == 2
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/category/%s/edit/' % category.pk)
|
||||
assert 'History' not in resp
|
||||
settings.SNAPSHOTS_ENABLED = True
|
||||
resp = app.get('/manage/category/%s/edit/' % category.pk)
|
||||
resp = resp.click('History')
|
||||
assert [x.attrib['class'] for x in resp.pyquery.find('.snapshots-list tr')] == [
|
||||
'new-day',
|
||||
'collapsed',
|
||||
]
|
||||
assert '(Version 42.0)' in resp.pyquery('tr:nth-child(1)').text()
|
||||
|
||||
for mode in ['json', 'inspect', '']:
|
||||
resp = app.get(
|
||||
'/manage/category/%s/history/compare/?version1=%s&version2=%s&mode=%s'
|
||||
% (category.pk, snapshot1.pk, snapshot2.pk, mode)
|
||||
)
|
||||
assert 'Snapshot (%s)' % (snapshot1.pk) in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
|
||||
if mode == 'inspect':
|
||||
assert resp.text.count('<ins>') == 1
|
||||
assert resp.text.count('<del>') == 1
|
||||
else:
|
||||
assert resp.text.count('diff_sub') == 0
|
||||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
resp = app.get(
|
||||
'/manage/category/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (category.pk, snapshot2.pk, snapshot1.pk)
|
||||
)
|
||||
assert 'Snapshot (%s)' % (snapshot1.pk) in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
|
||||
assert resp.text.count('diff_sub') == 0
|
||||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
|
||||
|
||||
def test_events_type_history(settings, app, admin_user):
|
||||
events_type = EventsType.objects.create(slug='foo', label='Foo')
|
||||
snapshot1 = events_type.take_snapshot()
|
||||
events_type.label = 'Bar'
|
||||
events_type.save()
|
||||
snapshot2 = events_type.take_snapshot()
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert EventsTypeSnapshot.objects.count() == 2
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/events-type/%s/edit/' % events_type.pk)
|
||||
assert 'History' not in resp
|
||||
settings.SNAPSHOTS_ENABLED = True
|
||||
resp = app.get('/manage/events-type/%s/edit/' % events_type.pk)
|
||||
resp = resp.click('History')
|
||||
assert [x.attrib['class'] for x in resp.pyquery.find('.snapshots-list tr')] == [
|
||||
'new-day',
|
||||
'collapsed',
|
||||
]
|
||||
assert '(Version 42.0)' in resp.pyquery('tr:nth-child(1)').text()
|
||||
|
||||
for mode in ['json', 'inspect', '']:
|
||||
resp = app.get(
|
||||
'/manage/events-type/%s/history/compare/?version1=%s&version2=%s&mode=%s'
|
||||
% (events_type.pk, snapshot1.pk, snapshot2.pk, mode)
|
||||
)
|
||||
assert 'Snapshot (%s)' % (snapshot1.pk) in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
|
||||
if mode == 'inspect':
|
||||
assert resp.text.count('<ins>') == 1
|
||||
assert resp.text.count('<del>') == 1
|
||||
else:
|
||||
assert resp.text.count('diff_sub') == 0
|
||||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
resp = app.get(
|
||||
'/manage/events-type/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (events_type.pk, snapshot2.pk, snapshot1.pk)
|
||||
)
|
||||
assert 'Snapshot (%s)' % (snapshot1.pk) in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
|
||||
assert resp.text.count('diff_sub') == 0
|
||||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
|
||||
|
||||
def test_resource_history(settings, app, admin_user):
|
||||
resource = Resource.objects.create(slug='foo', label='Foo')
|
||||
snapshot1 = resource.take_snapshot()
|
||||
resource.label = 'Bar'
|
||||
resource.save()
|
||||
snapshot2 = resource.take_snapshot()
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert ResourceSnapshot.objects.count() == 2
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/resource/%s/' % resource.pk)
|
||||
assert 'History' not in resp
|
||||
settings.SNAPSHOTS_ENABLED = True
|
||||
resp = app.get('/manage/resource/%s/' % resource.pk)
|
||||
resp = resp.click('History')
|
||||
assert [x.attrib['class'] for x in resp.pyquery.find('.snapshots-list tr')] == [
|
||||
'new-day',
|
||||
'collapsed',
|
||||
]
|
||||
assert '(Version 42.0)' in resp.pyquery('tr:nth-child(1)').text()
|
||||
|
||||
for mode in ['json', 'inspect', '']:
|
||||
resp = app.get(
|
||||
'/manage/resource/%s/history/compare/?version1=%s&version2=%s&mode=%s'
|
||||
% (resource.pk, snapshot1.pk, snapshot2.pk, mode)
|
||||
)
|
||||
assert 'Snapshot (%s)' % (snapshot1.pk) in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
|
||||
if mode == 'inspect':
|
||||
assert resp.text.count('<ins>') == 1
|
||||
assert resp.text.count('<del>') == 1
|
||||
else:
|
||||
assert resp.text.count('diff_sub') == 0
|
||||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
resp = app.get(
|
||||
'/manage/resource/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (resource.pk, snapshot2.pk, snapshot1.pk)
|
||||
)
|
||||
assert 'Snapshot (%s)' % (snapshot1.pk) in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
|
||||
assert resp.text.count('diff_sub') == 0
|
||||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
|
||||
|
||||
def test_unavailability_calendar_history(settings, app, admin_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(slug='foo', label='Foo')
|
||||
snapshot1 = unavailability_calendar.take_snapshot()
|
||||
unavailability_calendar.label = 'Bar'
|
||||
unavailability_calendar.save()
|
||||
snapshot2 = unavailability_calendar.take_snapshot()
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert UnavailabilityCalendarSnapshot.objects.count() == 2
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar.pk)
|
||||
assert 'History' not in resp
|
||||
settings.SNAPSHOTS_ENABLED = True
|
||||
resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar.pk)
|
||||
resp = resp.click('History')
|
||||
assert [x.attrib['class'] for x in resp.pyquery.find('.snapshots-list tr')] == [
|
||||
'new-day',
|
||||
'collapsed',
|
||||
]
|
||||
assert '(Version 42.0)' in resp.pyquery('tr:nth-child(1)').text()
|
||||
|
||||
for mode in ['json', 'inspect', '']:
|
||||
resp = app.get(
|
||||
'/manage/unavailability-calendar/%s/history/compare/?version1=%s&version2=%s&mode=%s'
|
||||
% (unavailability_calendar.pk, snapshot1.pk, snapshot2.pk, mode)
|
||||
)
|
||||
assert 'Snapshot (%s)' % (snapshot1.pk) in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
|
||||
if mode == 'inspect':
|
||||
assert resp.text.count('<ins>') == 1
|
||||
assert resp.text.count('<del>') == 1
|
||||
else:
|
||||
assert resp.text.count('diff_sub') == 0
|
||||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
resp = app.get(
|
||||
'/manage/unavailability-calendar/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (unavailability_calendar.pk, snapshot2.pk, snapshot1.pk)
|
||||
)
|
||||
assert 'Snapshot (%s)' % (snapshot1.pk) in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % (snapshot2.pk) in resp
|
||||
assert resp.text.count('diff_sub') == 0
|
||||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
|
||||
|
||||
def test_unavailability_calendar_history_as_manager(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(slug='foo', label='Foo')
|
||||
snapshot1 = unavailability_calendar.take_snapshot()
|
||||
snapshot2 = unavailability_calendar.take_snapshot()
|
||||
|
||||
app = login(app, username='manager', password='manager')
|
||||
unavailability_calendar.view_role = manager_user.groups.all()[0]
|
||||
unavailability_calendar.save()
|
||||
app.get('/manage/unavailability-calendar/%s/history/' % unavailability_calendar.pk, status=403)
|
||||
app.get(
|
||||
'/manage/unavailability-calendar/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (unavailability_calendar.pk, snapshot2.pk, snapshot1.pk),
|
||||
status=403,
|
||||
)
|
||||
|
||||
unavailability_calendar.edit_role = manager_user.groups.all()[0]
|
||||
unavailability_calendar.save()
|
||||
app.get('/manage/unavailability-calendar/%s/history/' % unavailability_calendar.pk, status=200)
|
||||
app.get(
|
||||
'/manage/unavailability-calendar/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (unavailability_calendar.pk, snapshot2.pk, snapshot1.pk),
|
||||
status=200,
|
||||
)
|
|
@ -3,6 +3,8 @@ import os
|
|||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from django.db import connection
|
||||
from django.test.utils import CaptureQueriesContext
|
||||
from webtest import Upload
|
||||
|
||||
from chrono.agendas.models import (
|
||||
|
@ -709,3 +711,32 @@ def test_unavailability_calendar_delete_unavailability_permissions(app, manager_
|
|||
unavailability_calendar.edit_role = group
|
||||
unavailability_calendar.save()
|
||||
app.get(url)
|
||||
|
||||
|
||||
def test_inspect_unavailability_calendar(app, admin_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
|
||||
TimePeriodException.objects.create(
|
||||
unavailability_calendar=unavailability_calendar,
|
||||
start_datetime=now() - datetime.timedelta(days=2),
|
||||
end_datetime=now() - datetime.timedelta(days=1),
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
|
||||
resp = app.get('/manage/unavailability-calendar/%s/settings' % unavailability_calendar.pk)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = resp.click('Inspect')
|
||||
assert len(ctx.captured_queries) == 4
|
||||
|
||||
|
||||
def test_inspect_unavailability_calendar_as_manager(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar 1')
|
||||
|
||||
app = login(app, username='manager', password='manager')
|
||||
unavailability_calendar.view_role = manager_user.groups.all()[0]
|
||||
unavailability_calendar.save()
|
||||
app.get('/manage/unavailability-calendar/%s/inspect/' % unavailability_calendar.pk, status=403)
|
||||
|
||||
unavailability_calendar.edit_role = manager_user.groups.all()[0]
|
||||
unavailability_calendar.save()
|
||||
app.get('/manage/unavailability-calendar/%s/inspect/' % unavailability_calendar.pk, status=200)
|
||||
|
|
|
@ -186,6 +186,7 @@ def test_import_export_events_agenda_options(app):
|
|||
label='Foo Bar',
|
||||
kind='events',
|
||||
default_view='open_events',
|
||||
anonymize_delay=42,
|
||||
booking_form_url='{{ eservices_url }}backoffice/submission/inscription-aux-activites/',
|
||||
minimal_booking_delay_in_working_days=True,
|
||||
booking_user_block_template='foo bar',
|
||||
|
@ -210,6 +211,7 @@ def test_import_export_events_agenda_options(app):
|
|||
assert Agenda.objects.count() == 1
|
||||
agenda = Agenda.objects.first()
|
||||
assert agenda.default_view == 'open_events'
|
||||
assert agenda.anonymize_delay == 42
|
||||
assert agenda.booking_form_url == '{{ eservices_url }}backoffice/submission/inscription-aux-activites/'
|
||||
assert agenda.minimal_booking_delay_in_working_days is True
|
||||
assert agenda.booking_user_block_template == 'foo bar'
|
||||
|
@ -897,6 +899,11 @@ def test_import_export_time_period_exception_source_ics_file(mocked_get):
|
|||
assert TimePeriodExceptionSource.objects.count() == 1
|
||||
assert TimePeriodException.objects.count() == 2
|
||||
|
||||
payload['agendas'][0]['desks'][0]['exception_sources'][0]['ics_file'] = 'garbage'
|
||||
with pytest.raises(AgendaImportError) as excinfo:
|
||||
import_site(payload)
|
||||
assert '%s' % excinfo.value == 'Bad ics file'
|
||||
|
||||
|
||||
@override_settings(
|
||||
EXCEPTIONS_SOURCES={
|
||||
|
|
|
@ -36,6 +36,7 @@ def test_get_weekday_index():
|
|||
|
||||
CHECK_TYPES_DATA = [
|
||||
{'id': 'bar-reason', 'kind': 'presence', 'text': 'Bar reason'},
|
||||
{'id': 'baz-reason', 'kind': 'presence', 'text': 'Baz reason', 'unexpected_presence': True},
|
||||
{'id': 'foo-reason', 'kind': 'absence', 'text': 'Foo reason'},
|
||||
]
|
||||
|
||||
|
@ -92,6 +93,7 @@ def test_get_agenda_check_types():
|
|||
requests_get.return_value = MockedRequestResponse(content=json.dumps(data))
|
||||
assert get_agenda_check_types(agenda) == [
|
||||
CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
|
||||
CheckType(slug='baz-reason', label='Baz reason', kind='presence', unexpected_presence=True),
|
||||
CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
|
||||
]
|
||||
|
||||
|
|
3
tox.ini
3
tox.ini
|
@ -44,7 +44,7 @@ allowlist_externals =
|
|||
commands =
|
||||
./getlasso3.sh
|
||||
python3 setup.py compile_translations
|
||||
py.test {env:COVERAGE:} {posargs:tests/}
|
||||
py.test -v --dist loadfile {env:COVERAGE:} {posargs:tests/}
|
||||
codestyle: pre-commit run --all-files --show-diff-on-failure
|
||||
|
||||
[testenv:pylint]
|
||||
|
@ -67,6 +67,7 @@ deps =
|
|||
psycopg2-binary<2.9
|
||||
git+https://git.entrouvert.org/publik-django-templatetags.git
|
||||
responses
|
||||
lxml
|
||||
commands =
|
||||
./getlasso3.sh
|
||||
pylint: ./pylint.sh chrono/ tests/
|
||||
|
|
Loading…
Reference in New Issue