Compare commits
16 Commits
f81dfa9ca6
...
758ef73ae8
Author | SHA1 | Date |
---|---|---|
Yann Weber | 758ef73ae8 | |
Valentin Deniaud | 3576928b2c | |
Frédéric Péters | ae55827939 | |
Lauréline Guérin | d733e91135 | |
Lauréline Guérin | 4b8c3412e4 | |
Valentin Deniaud | be975cfa29 | |
Lauréline Guérin | f7e224ba9b | |
Thomas Jund | 41cadbcfa9 | |
Lauréline Guérin | a34d55879e | |
Lauréline Guérin | 43c42c507c | |
Lauréline Guérin | 886afb206e | |
Lauréline Guérin | 2c30eec6ac | |
Lauréline Guérin | 1896c33f29 | |
Lauréline Guérin | 1f23f85b3d | |
Lauréline Guérin | 393a20b87b | |
Lauréline Guérin | df0e356e75 |
|
@ -6,7 +6,7 @@ pipeline {
|
|||
stages {
|
||||
stage('Unit Tests') {
|
||||
steps {
|
||||
sh 'tox -rv -- --numprocesses 3'
|
||||
sh 'NUMPROCESSES=3 tox -rv'
|
||||
}
|
||||
post {
|
||||
always {
|
||||
|
|
|
@ -1171,6 +1171,9 @@ class Agenda(WithSnapshotMixin, WithApplicationMixin, WithInspectMixin, models.M
|
|||
event_queryset = Agenda.filter_for_guardian(
|
||||
event_queryset, guardian_external_id, user_external_id
|
||||
)
|
||||
event_queryset = event_queryset.prefetch_related(
|
||||
Prefetch('primary_event', queryset=Event.objects.all().order_by())
|
||||
)
|
||||
|
||||
return qs.filter(kind='events').prefetch_related(
|
||||
Prefetch(
|
||||
|
@ -2698,6 +2701,12 @@ class Event(WithInspectMixin, models.Model):
|
|||
except ValueError:
|
||||
raise AgendaImportError(_('Bad datetime format "%s"') % data['start_datetime'])
|
||||
|
||||
if data.get('end_time'):
|
||||
try:
|
||||
data['end_time'] = datetime.datetime.strptime(data['end_time'], '%H:%M').time()
|
||||
except ValueError:
|
||||
raise AgendaImportError(_('Bad time format "%s"') % data['end_time'])
|
||||
|
||||
if data.get('recurrence_days'):
|
||||
# keep stable weekday numbering after switch to ISO in db
|
||||
data['recurrence_days'] = [i + 1 for i in data['recurrence_days']]
|
||||
|
@ -2717,6 +2726,7 @@ class Event(WithInspectMixin, models.Model):
|
|||
update_fields = {
|
||||
field: getattr(event, field)
|
||||
for field in [
|
||||
'end_time',
|
||||
'label',
|
||||
'duration',
|
||||
'publication_datetime',
|
||||
|
@ -2736,6 +2746,7 @@ class Event(WithInspectMixin, models.Model):
|
|||
)
|
||||
return {
|
||||
'start_datetime': make_naive(self.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'end_time': self.end_time.strftime('%H:%M') if self.end_time else None,
|
||||
'publication_datetime': make_naive(self.publication_datetime).strftime('%Y-%m-%d %H:%M:%S')
|
||||
if self.publication_datetime
|
||||
else None,
|
||||
|
|
|
@ -331,9 +331,11 @@ def get_short_event_detail(
|
|||
details = {
|
||||
'id': '%s@%s' % (agenda.slug, event.slug) if multiple_agendas else event.slug,
|
||||
'slug': event.slug, # kept for compatibility
|
||||
'primary_event': None,
|
||||
'text': get_event_text(event, agenda),
|
||||
'label': event.label or '',
|
||||
'agenda_label': agenda.label,
|
||||
'agenda_slug': agenda.slug,
|
||||
'date': format_response_date(event.start_datetime),
|
||||
'datetime': format_response_datetime(event.start_datetime),
|
||||
'end_datetime': format_response_datetime(event.end_datetime) if event.end_datetime else '',
|
||||
|
@ -345,6 +347,12 @@ def get_short_event_detail(
|
|||
'check_locked': event.check_locked,
|
||||
'invoiced': event.invoiced,
|
||||
}
|
||||
if event.primary_event:
|
||||
details['primary_event'] = (
|
||||
'%s@%s' % (agenda.slug, event.primary_event.slug)
|
||||
if multiple_agendas
|
||||
else event.primary_event.slug
|
||||
)
|
||||
for key, value in event.get_custom_fields().items():
|
||||
details['custom_field_%s' % key] = value
|
||||
return details
|
||||
|
@ -663,6 +671,7 @@ class Datetimes(APIView):
|
|||
entries = Event.annotate_queryset_for_user(entries, user_external_id)
|
||||
if lock_code:
|
||||
entries = Event.annotate_queryset_for_lock_code(entries, lock_code=lock_code)
|
||||
entries = entries.prefetch_related(Prefetch('primary_event', queryset=Event.objects.all().order_by()))
|
||||
entries = entries.order_by('start_datetime', 'duration', 'label')
|
||||
|
||||
if payload['hide_disabled']:
|
||||
|
@ -743,6 +752,9 @@ class MultipleAgendasDatetimes(APIView):
|
|||
entries = Event.annotate_queryset_for_user(entries, user_external_id, with_status=with_status)
|
||||
if lock_code:
|
||||
Event.annotate_queryset_for_lock_code(entries, lock_code)
|
||||
entries = entries.prefetch_related(
|
||||
Prefetch('primary_event', queryset=Event.objects.all().order_by())
|
||||
)
|
||||
|
||||
if check_overlaps:
|
||||
entries = Event.annotate_queryset_with_overlaps(entries)
|
||||
|
@ -1845,6 +1857,7 @@ class RecurringFillslots(APIView):
|
|||
min_start=start_datetime,
|
||||
max_start=end_datetime,
|
||||
)
|
||||
events = events.prefetch_related(Prefetch('primary_event', queryset=Event.objects.all().order_by()))
|
||||
|
||||
return events
|
||||
|
||||
|
@ -2003,6 +2016,7 @@ class EventsFillslots(APIView):
|
|||
output_field=BooleanField(),
|
||||
)
|
||||
)
|
||||
events = events.prefetch_related(Prefetch('primary_event', queryset=Event.objects.all().order_by()))
|
||||
waiting_list_event_ids = [event.pk for event in events if event.in_waiting_list]
|
||||
|
||||
extra_data = get_extra_data(request, payload)
|
||||
|
@ -2227,9 +2241,13 @@ class MultipleAgendasEventsFillslotsRevert(APIView):
|
|||
if booking.previous_state == 'cancelled':
|
||||
bookings_to_cancel.append(booking)
|
||||
|
||||
events = Event.objects.filter(
|
||||
pk__in=[b.event_id for b in bookings_to_cancel + bookings_to_book + bookings_to_delete]
|
||||
).prefetch_related('agenda')
|
||||
events = (
|
||||
Event.objects.filter(
|
||||
pk__in=[b.event_id for b in bookings_to_cancel + bookings_to_book + bookings_to_delete]
|
||||
)
|
||||
.prefetch_related('agenda')
|
||||
.prefetch_related(Prefetch('primary_event', queryset=Event.objects.all().order_by()))
|
||||
)
|
||||
events_by_id = {x.id: x for x in events}
|
||||
with transaction.atomic():
|
||||
cancellation_datetime = now()
|
||||
|
@ -2753,10 +2771,12 @@ class BookingsAPI(ListAPIView):
|
|||
return Response({'err': 0, 'data': data})
|
||||
|
||||
def get_queryset(self):
|
||||
event_queryset = Event.objects.all().prefetch_related(
|
||||
'agenda', 'desk', Prefetch('primary_event', queryset=Event.objects.all().order_by())
|
||||
)
|
||||
return (
|
||||
Booking.objects.filter(primary_booking__isnull=True, cancellation_datetime__isnull=True)
|
||||
.select_related('event', 'event__agenda', 'event__desk')
|
||||
.prefetch_related('user_checks')
|
||||
.prefetch_related('user_checks', Prefetch('event', queryset=event_queryset))
|
||||
.order_by('event__start_datetime', 'event__slug', 'event__agenda__slug', 'pk')
|
||||
)
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import json
|
|||
import tarfile
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
|
@ -28,6 +29,7 @@ from rest_framework.generics import GenericAPIView
|
|||
from rest_framework.response import Response
|
||||
|
||||
from chrono.agendas.models import Agenda, Category, EventsType, Resource, UnavailabilityCalendar
|
||||
from chrono.api.utils import APIError
|
||||
from chrono.apps.export_import.models import Application, ApplicationElement
|
||||
from chrono.manager.utils import import_site
|
||||
|
||||
|
@ -41,6 +43,21 @@ klasses_translation = {
|
|||
}
|
||||
klasses_translation_reverse = {v: k for k, v in klasses_translation.items()}
|
||||
|
||||
compare_urls = {
|
||||
'agendas': 'chrono-manager-agenda-history-compare',
|
||||
'categories': 'chrono-manager-category-history-compare',
|
||||
'events_types': 'chrono-manager-events-type-history-compare',
|
||||
'resources': 'chrono-manager-resource-history-compare',
|
||||
'unavailability_calendars': 'chrono-manager-unavailability-calendar-history-compare',
|
||||
}
|
||||
|
||||
|
||||
def get_klass_from_component_type(component_type):
|
||||
try:
|
||||
return klasses[component_type]
|
||||
except KeyError:
|
||||
raise Http404
|
||||
|
||||
|
||||
class Index(GenericAPIView):
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
@ -140,7 +157,7 @@ class ListComponents(GenericAPIView):
|
|||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
klass = klasses[kwargs['component_type']]
|
||||
klass = get_klass_from_component_type(kwargs['component_type'])
|
||||
order_by = 'slug'
|
||||
if klass == Group:
|
||||
order_by = 'name'
|
||||
|
@ -155,7 +172,7 @@ class ExportComponent(GenericAPIView):
|
|||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request, slug, *args, **kwargs):
|
||||
klass = klasses[kwargs['component_type']]
|
||||
klass = get_klass_from_component_type(kwargs['component_type'])
|
||||
serialisation = get_object_or_404(klass, slug=slug).export_json()
|
||||
return Response({'data': serialisation})
|
||||
|
||||
|
@ -167,7 +184,7 @@ class ComponentDependencies(GenericAPIView):
|
|||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request, slug, *args, **kwargs):
|
||||
klass = klasses[kwargs['component_type']]
|
||||
klass = get_klass_from_component_type(kwargs['component_type'])
|
||||
component = get_object_or_404(klass, slug=slug)
|
||||
|
||||
def dependency_dict(element):
|
||||
|
@ -181,8 +198,29 @@ component_dependencies = ComponentDependencies.as_view()
|
|||
|
||||
|
||||
def component_redirect(request, component_type, slug):
|
||||
klass = klasses[component_type]
|
||||
klass = get_klass_from_component_type(component_type)
|
||||
component = get_object_or_404(klass, slug=slug)
|
||||
|
||||
if component_type not in klasses or component_type == 'roles':
|
||||
raise Http404
|
||||
|
||||
if (
|
||||
'compare' in request.GET
|
||||
and request.GET.get('application')
|
||||
and request.GET.get('version1')
|
||||
and request.GET.get('version2')
|
||||
):
|
||||
component_type = klasses_translation.get(component_type, component_type)
|
||||
return redirect(
|
||||
'%s?version1=%s&version2=%s&application=%s'
|
||||
% (
|
||||
reverse(compare_urls[component_type], args=[component.pk]),
|
||||
request.GET['version1'],
|
||||
request.GET['version2'],
|
||||
request.GET['application'],
|
||||
)
|
||||
)
|
||||
|
||||
if klass == Agenda:
|
||||
return redirect(reverse('chrono-manager-agenda-view', kwargs={'pk': component.pk}))
|
||||
if klass == Category:
|
||||
|
@ -200,7 +238,114 @@ class BundleCheck(GenericAPIView):
|
|||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
return Response({'err': 0, 'data': {}})
|
||||
tar_io = io.BytesIO(request.read())
|
||||
try:
|
||||
with tarfile.open(fileobj=tar_io) as tar:
|
||||
try:
|
||||
manifest = json.loads(tar.extractfile('manifest.json').read().decode())
|
||||
except KeyError:
|
||||
raise APIError(_('Invalid tar file, missing manifest'))
|
||||
application_slug = manifest.get('slug')
|
||||
application_version = manifest.get('version_number')
|
||||
if not application_slug or not application_version:
|
||||
return Response({'data': {}})
|
||||
|
||||
differences = []
|
||||
unknown_elements = []
|
||||
no_history_elements = []
|
||||
legacy_elements = []
|
||||
content_types = ContentType.objects.get_for_models(
|
||||
*[v for k, v in klasses.items() if k != 'roles']
|
||||
)
|
||||
for element in manifest.get('elements'):
|
||||
component_type = element['type']
|
||||
if component_type not in klasses or component_type == 'roles':
|
||||
continue
|
||||
klass = klasses[component_type]
|
||||
component_type = klasses_translation.get(component_type, component_type)
|
||||
try:
|
||||
component = klass.objects.get(slug=element['slug'])
|
||||
except klass.DoesNotExist:
|
||||
unknown_elements.append(
|
||||
{
|
||||
'type': component_type,
|
||||
'slug': element['slug'],
|
||||
}
|
||||
)
|
||||
continue
|
||||
elements_qs = ApplicationElement.objects.filter(
|
||||
application__slug=application_slug,
|
||||
content_type=content_types[klass],
|
||||
object_id=component.pk,
|
||||
)
|
||||
if not elements_qs.exists():
|
||||
# object exists, but not linked to the application
|
||||
legacy_elements.append(
|
||||
{
|
||||
'type': component.application_component_type,
|
||||
'slug': str(component.slug),
|
||||
# information needed here, Relation objects may not exist yet in hobo
|
||||
'text': component.label,
|
||||
'url': reverse(
|
||||
'api-export-import-component-redirect',
|
||||
kwargs={
|
||||
'slug': str(component.slug),
|
||||
'component_type': component.application_component_type,
|
||||
},
|
||||
),
|
||||
}
|
||||
)
|
||||
continue
|
||||
snapshot_for_app = (
|
||||
klass.get_snapshot_model()
|
||||
.objects.filter(
|
||||
instance=component,
|
||||
application_slug=application_slug,
|
||||
application_version=application_version,
|
||||
)
|
||||
.order_by('timestamp')
|
||||
.last()
|
||||
)
|
||||
if not snapshot_for_app:
|
||||
# no snapshot for this bundle
|
||||
no_history_elements.append(
|
||||
{
|
||||
'type': element['type'],
|
||||
'slug': element['slug'],
|
||||
}
|
||||
)
|
||||
continue
|
||||
last_snapshot = (
|
||||
klass.get_snapshot_model().objects.filter(instance=component).latest('timestamp')
|
||||
)
|
||||
if snapshot_for_app.pk != last_snapshot.pk:
|
||||
differences.append(
|
||||
{
|
||||
'type': element['type'],
|
||||
'slug': element['slug'],
|
||||
'url': '%s?version1=%s&version2=%s'
|
||||
% (
|
||||
request.build_absolute_uri(
|
||||
reverse(compare_urls[component_type], args=[component.pk])
|
||||
),
|
||||
snapshot_for_app.pk,
|
||||
last_snapshot.pk,
|
||||
),
|
||||
}
|
||||
)
|
||||
except tarfile.TarError:
|
||||
raise APIError(_('Invalid tar file'))
|
||||
|
||||
return Response(
|
||||
{
|
||||
'data': {
|
||||
'differences': differences,
|
||||
'unknown_elements': unknown_elements,
|
||||
'no_history_elements': no_history_elements,
|
||||
'legacy_elements': legacy_elements,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
bundle_check = BundleCheck.as_view()
|
||||
|
@ -213,25 +358,40 @@ class BundleImport(GenericAPIView):
|
|||
def put(self, request, *args, **kwargs):
|
||||
tar_io = io.BytesIO(request.read())
|
||||
components = {}
|
||||
with tarfile.open(fileobj=tar_io) as tar:
|
||||
manifest = json.loads(tar.extractfile('manifest.json').read().decode())
|
||||
self.application = Application.update_or_create_from_manifest(
|
||||
manifest,
|
||||
tar,
|
||||
editable=not self.install,
|
||||
)
|
||||
|
||||
for element in manifest.get('elements'):
|
||||
component_type = element['type']
|
||||
if component_type not in klasses or element['type'] == 'roles':
|
||||
continue
|
||||
component_type = klasses_translation.get(component_type, component_type)
|
||||
if component_type not in components:
|
||||
components[component_type] = []
|
||||
component_content = (
|
||||
tar.extractfile('%s/%s' % (element['type'], element['slug'])).read().decode()
|
||||
try:
|
||||
with tarfile.open(fileobj=tar_io) as tar:
|
||||
try:
|
||||
manifest = json.loads(tar.extractfile('manifest.json').read().decode())
|
||||
except KeyError:
|
||||
raise APIError(_('Invalid tar file, missing manifest'))
|
||||
self.application = Application.update_or_create_from_manifest(
|
||||
manifest,
|
||||
tar,
|
||||
editable=not self.install,
|
||||
)
|
||||
components[component_type].append(json.loads(component_content).get('data'))
|
||||
|
||||
for element in manifest.get('elements'):
|
||||
component_type = element['type']
|
||||
if component_type not in klasses or element['type'] == 'roles':
|
||||
continue
|
||||
component_type = klasses_translation.get(component_type, component_type)
|
||||
if component_type not in components:
|
||||
components[component_type] = []
|
||||
try:
|
||||
component_content = (
|
||||
tar.extractfile('%s/%s' % (element['type'], element['slug'])).read().decode()
|
||||
)
|
||||
except KeyError:
|
||||
raise APIError(
|
||||
_(
|
||||
'Invalid tar file, missing component %s/%s'
|
||||
% (element['type'], element['slug'])
|
||||
)
|
||||
)
|
||||
components[component_type].append(json.loads(component_content).get('data'))
|
||||
except tarfile.TarError:
|
||||
raise APIError(_('Invalid tar file'))
|
||||
|
||||
# init cache of application elements, from manifest
|
||||
self.application_elements = set()
|
||||
# import agendas
|
||||
|
@ -260,6 +420,11 @@ class BundleImport(GenericAPIView):
|
|||
self.application, existing_component
|
||||
)
|
||||
self.application_elements.add(element.content_object)
|
||||
if self.install is True:
|
||||
existing_component.take_snapshot(
|
||||
comment=_('Application (%s)') % self.application,
|
||||
application=self.application,
|
||||
)
|
||||
|
||||
def unlink_obsolete_objects(self):
|
||||
known_elements = ApplicationElement.objects.filter(application=self.application)
|
||||
|
|
|
@ -55,10 +55,10 @@ class Application(models.Model):
|
|||
slug=manifest.get('slug'), defaults={'editable': editable}
|
||||
)
|
||||
application.name = manifest.get('application')
|
||||
application.description = manifest.get('description')
|
||||
application.documentation_url = manifest.get('documentation_url')
|
||||
application.description = manifest.get('description') or ''
|
||||
application.documentation_url = manifest.get('documentation_url') or ''
|
||||
application.version_number = manifest.get('version_number') or 'unknown'
|
||||
application.version_notes = manifest.get('version_notes')
|
||||
application.version_notes = manifest.get('version_notes') or ''
|
||||
if not editable:
|
||||
application.editable = editable
|
||||
application.visible = manifest.get('visible', True)
|
||||
|
|
|
@ -38,7 +38,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -72,7 +72,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -106,7 +106,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -140,7 +140,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -174,7 +174,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'user',
|
||||
models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
|
||||
null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -45,7 +45,7 @@ class WithSnapshotMixin:
|
|||
|
||||
class AbstractSnapshot(models.Model):
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True)
|
||||
comment = models.TextField(blank=True, null=True)
|
||||
serialization = models.JSONField(blank=True, default=dict)
|
||||
label = models.CharField(_('Label'), max_length=150, blank=True)
|
||||
|
|
|
@ -54,7 +54,38 @@ class InstanceWithSnapshotHistoryView(ListView):
|
|||
|
||||
|
||||
class InstanceWithSnapshotHistoryCompareView(DetailView):
|
||||
def get_snapshots_from_application(self):
|
||||
version1 = self.request.GET.get('version1')
|
||||
version2 = self.request.GET.get('version2')
|
||||
if not version1 or not version2:
|
||||
raise Http404
|
||||
|
||||
snapshot_for_app1 = (
|
||||
self.model.get_snapshot_model()
|
||||
.objects.filter(
|
||||
instance=self.object,
|
||||
application_slug=self.request.GET['application'],
|
||||
application_version=self.request.GET['version1'],
|
||||
)
|
||||
.order_by('timestamp')
|
||||
.last()
|
||||
)
|
||||
snapshot_for_app2 = (
|
||||
self.model.get_snapshot_model()
|
||||
.objects.filter(
|
||||
instance=self.object,
|
||||
application_slug=self.request.GET['application'],
|
||||
application_version=self.request.GET['version2'],
|
||||
)
|
||||
.order_by('timestamp')
|
||||
.last()
|
||||
)
|
||||
return snapshot_for_app1, snapshot_for_app2
|
||||
|
||||
def get_snapshots(self):
|
||||
if 'application' in self.request.GET:
|
||||
return self.get_snapshots_from_application()
|
||||
|
||||
id1 = self.request.GET.get('version1')
|
||||
id2 = self.request.GET.get('version2')
|
||||
if not id1 or not id2:
|
||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: chrono 0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-03-21 08:36+0100\n"
|
||||
"POT-Creation-Date: 2024-03-21 13:40+0100\n"
|
||||
"PO-Revision-Date: 2024-02-01 09:50+0100\n"
|
||||
"Last-Translator: Frederic Peters <fpeters@entrouvert.com>\n"
|
||||
"Language: French\n"
|
||||
|
@ -1948,6 +1948,24 @@ msgstr "Rôles"
|
|||
msgid "Role"
|
||||
msgstr "Rôle"
|
||||
|
||||
#: apps/export_import/api_views.py
|
||||
msgid "Invalid tar file, missing manifest"
|
||||
msgstr "Mauvais format de fichier tar, manifest manquant"
|
||||
|
||||
#: apps/export_import/api_views.py
|
||||
msgid "Invalid tar file"
|
||||
msgstr "Mauvais format de fichier tar"
|
||||
|
||||
#: apps/export_import/api_views.py
|
||||
#, python-format
|
||||
msgid "Invalid tar file, missing component %s/%s"
|
||||
msgstr "Mauvais format de fichier tar, composant %s/%s manquant"
|
||||
|
||||
#: apps/export_import/api_views.py
|
||||
#, python-format
|
||||
msgid "Application (%s)"
|
||||
msgstr "Application (%s)"
|
||||
|
||||
#: apps/snapshot/models.py
|
||||
msgid "deletion"
|
||||
msgstr "suppression"
|
||||
|
|
|
@ -870,20 +870,73 @@ div#main-content.partial-booking-dayview {
|
|||
background-color: var(--red);
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.agenda-table.partial-bookings .booking {
|
||||
height: 70%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 15%;
|
||||
background: #1066bc;
|
||||
&.present {
|
||||
background: hsl(120, 57%, 35%);
|
||||
}
|
||||
&.absent {
|
||||
background: hsl(355, 80%, 45%);
|
||||
// Month view, table element
|
||||
&-month {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
& col.we {
|
||||
background-color: var(--zebra-color);
|
||||
}
|
||||
& col.today {
|
||||
background-image: linear-gradient(
|
||||
135deg,
|
||||
hsl(65, 65%, 94%) 20%,
|
||||
hsl(65, 55%, 92%) 70%,
|
||||
hsl(65, 50%, 90%) 90%);
|
||||
}
|
||||
&--day {
|
||||
padding: .33em;
|
||||
a {
|
||||
color: var(--font-color);
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
}
|
||||
&.today a {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
& .registrant {
|
||||
&--name {
|
||||
box-sizing: border-box;
|
||||
text-align: right;
|
||||
padding: .66rem;
|
||||
font-size: 130%;
|
||||
color: #505050;
|
||||
font-weight: normal;
|
||||
width: var(--registrant-name-width);
|
||||
}
|
||||
&--day-cell {
|
||||
border-left: var(--separator-size) solid var(--separator-color);
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
padding: .33em;
|
||||
line-height: 0;
|
||||
& .booking {
|
||||
display: inline-block;
|
||||
width: Min(100%, 1.75em);
|
||||
height: 1.75em;
|
||||
--booking-color: #1066bc;
|
||||
background-color: var(--booking-color);
|
||||
&.present {
|
||||
background: var(--green);
|
||||
}
|
||||
&.absent {
|
||||
background: var(--red);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&--registrant:nth-child(odd) {
|
||||
& th, & td {
|
||||
background-color: var(--zebra-color);
|
||||
}
|
||||
}
|
||||
&--registrant:nth-child(even) {
|
||||
& th, & td {
|
||||
--separator-color: var(--zebra-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,33 +3,54 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
<table class="agenda-table partial-bookings">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<div class="pk-table-wrapper">
|
||||
<table class="partial-booking partial-booking-month">
|
||||
<colgroup>
|
||||
<col class="name" />
|
||||
{% for day in days %}
|
||||
<th>
|
||||
<a href="{% url 'chrono-manager-agenda-day-view' pk=agenda.pk year=day.date|date:"Y" month=day.date|date:"m" day=day.date|date:"d" %}">{{ day|date:"d" }}</a>
|
||||
</th>
|
||||
<col class="{% if day|date:"w" == "0" or day|date:"w" == "6" %}we{% endif %}
|
||||
{% if today == day.date %}today{% endif %}
|
||||
" />
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
</colgroup>
|
||||
|
||||
<tbody>
|
||||
{% for booking_info in user_booking_info %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
<th>{{ booking_info.user_name }}</th>
|
||||
{% for booking in booking_info.bookings %}
|
||||
<td class="day-cell">
|
||||
{% if booking %}
|
||||
<span class="booking {{ booking.check_css_class }}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<thead>
|
||||
<tr class="partial-booking-month--day-list">
|
||||
<td></td>
|
||||
{% for day in days %}
|
||||
<th scope="col" class="partial-booking-month--day{% if today == day.date %} today{% endif %}">
|
||||
<a href="{% url 'chrono-manager-agenda-day-view' pk=agenda.pk year=day.date|date:"Y" month=day.date|date:"m" day=day.date|date:"d" %}">
|
||||
<time datetime="{{ day|date:"Y-m-d" }}">{{ day|date:"d" }}</time>
|
||||
</a>
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</thead>
|
||||
|
||||
</table>
|
||||
<tbody class="partial-booking-month--registrant-items">
|
||||
{% for booking_info in user_booking_info %}
|
||||
<tr class="partial-booking-month--registrant">
|
||||
<th class="registrant--name" scope="row">{{ booking_info.user_name }}</th>
|
||||
{% for booking in booking_info.bookings %}
|
||||
<td class="registrant--day-cell">
|
||||
{% if booking %}
|
||||
{% if booking.check_css_class == 'present' %}
|
||||
{% trans "Present" as booking_status %}
|
||||
{% elif booking.check_css_class == 'absent' %}
|
||||
{% trans "Absent" as booking_status %}
|
||||
{% else %}
|
||||
{% trans "Not checked" as booking_status %}
|
||||
{% endif %}
|
||||
<span title="{{ booking_status }}" class="booking {{ booking.check_css_class }}">
|
||||
<span class="sr-only">{{ booking_status }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -2268,6 +2268,7 @@ class AgendaWeekMonthMixin:
|
|||
self.first_day + datetime.timedelta(days=i)
|
||||
for i in range((first_day_next_month - self.first_day).days)
|
||||
]
|
||||
context['today'] = localtime().date()
|
||||
|
||||
booking_info_by_user = {}
|
||||
bookings = Booking.objects.filter(event__in=self.events).prefetch_related('user_checks')
|
||||
|
|
|
@ -129,6 +129,7 @@ def test_datetime_api_label(app):
|
|||
agenda=agenda,
|
||||
)
|
||||
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
||||
assert resp.json['data'][0]['primary_event'] is None
|
||||
assert resp.json['data'][0]['text'] == 'Hello world'
|
||||
assert resp.json['data'][0]['label'] == 'Hello world'
|
||||
|
||||
|
@ -692,6 +693,8 @@ def test_recurring_events_api(app, user, freezer):
|
|||
assert data[0]['id'] == 'abc--2021-01-19-1305'
|
||||
assert data[0]['datetime'] == '2021-01-19 13:05:00'
|
||||
assert data[0]['text'] == "Rock'n roll (Jan. 19, 2021, 1:05 p.m.)"
|
||||
assert data[0]['label'] == "Rock'n roll"
|
||||
assert data[0]['primary_event'] == 'abc'
|
||||
assert data[3]['id'] == 'abc--2021-02-09-1305'
|
||||
assert Event.objects.count() == 6
|
||||
|
||||
|
@ -713,7 +716,7 @@ def test_recurring_events_api(app, user, freezer):
|
|||
# check querysets
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
||||
assert len(ctx.captured_queries) == 3
|
||||
assert len(ctx.captured_queries) == 4
|
||||
|
||||
# events follow agenda display template
|
||||
agenda.event_display_template = '{{ event.label }} - {{ event.start_datetime }}'
|
||||
|
|
|
@ -34,12 +34,14 @@ def test_datetimes_multiple_agendas(app):
|
|||
Desk.objects.create(agenda=first_agenda, slug='_exceptions_holder')
|
||||
Event.objects.create(
|
||||
slug='event',
|
||||
label='Event',
|
||||
start_datetime=now() + datetime.timedelta(days=5),
|
||||
places=5,
|
||||
agenda=first_agenda,
|
||||
)
|
||||
event = Event.objects.create( # base recurring event not visible in datetimes api
|
||||
slug='recurring',
|
||||
label='Recurring',
|
||||
start_datetime=now() + datetime.timedelta(hours=1),
|
||||
recurrence_days=[localtime().isoweekday()],
|
||||
recurrence_end_date=now() + datetime.timedelta(days=15),
|
||||
|
@ -60,11 +62,18 @@ def test_datetimes_multiple_agendas(app):
|
|||
Booking.objects.create(event=event)
|
||||
|
||||
agenda_slugs = '%s,%s' % (first_agenda.slug, second_agenda.slug)
|
||||
resp = app.get('/api/agendas/datetimes/', params={'agendas': agenda_slugs})
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.get('/api/agendas/datetimes/', params={'agendas': agenda_slugs})
|
||||
assert len(ctx.captured_queries) == 3
|
||||
assert len(resp.json['data']) == 5
|
||||
assert resp.json['data'][0]['id'] == 'first-agenda@recurring--2021-05-06-1700'
|
||||
assert resp.json['data'][0]['text'] == 'Recurring (May 6, 2021, 5 p.m.)'
|
||||
assert resp.json['data'][0]['label'] == 'Recurring'
|
||||
assert resp.json['data'][0]['primary_event'] == 'first-agenda@recurring'
|
||||
assert resp.json['data'][1]['id'] == 'first-agenda@event'
|
||||
assert resp.json['data'][1]['text'] == 'May 11, 2021, 4 p.m.'
|
||||
assert resp.json['data'][1]['text'] == 'Event'
|
||||
assert resp.json['data'][1]['label'] == 'Event'
|
||||
assert resp.json['data'][1]['primary_event'] is None
|
||||
assert resp.json['data'][1]['places']['available'] == 5
|
||||
|
||||
assert resp.json['data'][2]['id'] == 'second-agenda@event'
|
||||
|
|
|
@ -434,7 +434,7 @@ def test_recurring_events_api_list_multiple_agendas_queries(app):
|
|||
'/api/agendas/recurring-events/?subscribed=category-a&user_external_id=xxx&guardian_external_id=father_id'
|
||||
)
|
||||
assert len(resp.json['data']) == 40
|
||||
assert len(ctx.captured_queries) == 5
|
||||
assert len(ctx.captured_queries) == 6
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2021-09-06 12:00')
|
||||
|
|
|
@ -936,6 +936,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'cancelled_events': [
|
||||
{
|
||||
'agenda_label': 'Foo bar',
|
||||
'agenda_slug': 'foo-bar',
|
||||
'check_locked': False,
|
||||
'checked': False,
|
||||
'date': '2021-02-28',
|
||||
|
@ -950,6 +951,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'slug': 'event',
|
||||
'text': 'Event',
|
||||
'url': None,
|
||||
'primary_event': None,
|
||||
}
|
||||
],
|
||||
'deleted_booking_count': 0,
|
||||
|
@ -972,6 +974,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'cancelled_events': [
|
||||
{
|
||||
'agenda_label': 'Foo bar',
|
||||
'agenda_slug': 'foo-bar',
|
||||
'check_locked': False,
|
||||
'checked': False,
|
||||
'date': '2021-02-28',
|
||||
|
@ -986,6 +989,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'slug': 'event',
|
||||
'text': 'Event',
|
||||
'url': None,
|
||||
'primary_event': None,
|
||||
}
|
||||
],
|
||||
'deleted_booking_count': 0,
|
||||
|
@ -1014,6 +1018,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'booked_events': [
|
||||
{
|
||||
'agenda_label': 'Foo bar',
|
||||
'agenda_slug': 'foo-bar',
|
||||
'check_locked': False,
|
||||
'checked': False,
|
||||
'date': '2021-02-28',
|
||||
|
@ -1028,6 +1033,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'slug': 'event',
|
||||
'text': 'Event',
|
||||
'url': None,
|
||||
'primary_event': None,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
@ -1050,6 +1056,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'booked_events': [
|
||||
{
|
||||
'agenda_label': 'Foo bar',
|
||||
'agenda_slug': 'foo-bar',
|
||||
'check_locked': False,
|
||||
'checked': False,
|
||||
'date': '2021-02-28',
|
||||
|
@ -1064,6 +1071,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'slug': 'event',
|
||||
'text': 'Event',
|
||||
'url': None,
|
||||
'primary_event': None,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
@ -1086,6 +1094,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'deleted_events': [
|
||||
{
|
||||
'agenda_label': 'Foo bar',
|
||||
'agenda_slug': 'foo-bar',
|
||||
'check_locked': False,
|
||||
'checked': False,
|
||||
'date': '2021-02-28',
|
||||
|
@ -1100,6 +1109,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'slug': 'event',
|
||||
'text': 'Event',
|
||||
'url': None,
|
||||
'primary_event': None,
|
||||
}
|
||||
],
|
||||
'booked_booking_count': 0,
|
||||
|
@ -1124,6 +1134,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'deleted_events': [
|
||||
{
|
||||
'agenda_label': 'Foo bar',
|
||||
'agenda_slug': 'foo-bar',
|
||||
'check_locked': False,
|
||||
'checked': False,
|
||||
'date': '2021-02-28',
|
||||
|
@ -1138,6 +1149,7 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
'slug': 'event',
|
||||
'text': 'Event',
|
||||
'url': None,
|
||||
'primary_event': None,
|
||||
}
|
||||
],
|
||||
'booked_booking_count': 0,
|
||||
|
@ -1169,10 +1181,14 @@ def test_api_events_fillslots_multiple_agendas_revert(app, user):
|
|||
duration=120,
|
||||
places=1,
|
||||
agenda=agenda,
|
||||
recurrence_days=[7],
|
||||
recurrence_end_date=now() + datetime.timedelta(days=14), # 2 weeks
|
||||
)
|
||||
event.create_all_recurrences()
|
||||
event = event.recurrences.first()
|
||||
Booking.objects.create(
|
||||
event=event, request_uuid=request_uuid, previous_state='unbooked', cancellation_datetime=now()
|
||||
)
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.post(revert_url)
|
||||
assert len(ctx.captured_queries) == 14
|
||||
assert len(ctx.captured_queries) == 15
|
||||
|
|
|
@ -107,7 +107,7 @@ def test_recurring_events_api_fillslots(app, user, freezer, action):
|
|||
params['user_external_id'] = 'user_id_3'
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.post_json(fillslots_url, params=params)
|
||||
assert len(ctx.captured_queries) in [12, 13]
|
||||
assert len(ctx.captured_queries) in [15, 16]
|
||||
# everything goes in waiting list
|
||||
assert events.filter(booked_waiting_list_places=1).count() == 6
|
||||
# but an event was full
|
||||
|
@ -1368,7 +1368,7 @@ def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user):
|
|||
)
|
||||
assert resp.json['booking_count'] == 180
|
||||
assert resp.json['cancelled_booking_count'] == 0
|
||||
assert len(ctx.captured_queries) == 15
|
||||
assert len(ctx.captured_queries) == 17
|
||||
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.post_json(
|
||||
|
@ -1382,7 +1382,7 @@ def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user):
|
|||
)
|
||||
assert resp.json['booking_count'] == 0
|
||||
assert resp.json['cancelled_booking_count'] == 5
|
||||
assert len(ctx.captured_queries) == 17
|
||||
assert len(ctx.captured_queries) == 18
|
||||
|
||||
father = Person.objects.create(user_external_id='father_id', first_name='John', last_name='Doe')
|
||||
mother = Person.objects.create(user_external_id='mother_id', first_name='Jane', last_name='Doe')
|
||||
|
@ -1401,7 +1401,7 @@ def test_recurring_events_api_fillslots_multiple_agendas_queries(app, user):
|
|||
params={'slots': events_to_book, 'user_external_id': 'xxx'},
|
||||
)
|
||||
assert resp.json['booking_count'] == 100
|
||||
assert len(ctx.captured_queries) == 14
|
||||
assert len(ctx.captured_queries) == 16
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2022-03-07 14:00') # Monday of 10th week
|
||||
|
|
|
@ -320,7 +320,7 @@ def test_agendas_api(settings, app):
|
|||
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
||||
assert len(ctx.captured_queries) == 3
|
||||
assert len(ctx.captured_queries) == 4
|
||||
|
||||
|
||||
def test_agenda_detail_api(app):
|
||||
|
|
|
@ -9,6 +9,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||
|
||||
from chrono.agendas.models import Agenda, Category, Desk, EventsType, Resource, UnavailabilityCalendar
|
||||
from chrono.apps.export_import.models import Application, ApplicationElement
|
||||
from chrono.apps.snapshot.models import AgendaSnapshot
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -165,6 +166,9 @@ def test_list(app, admin_user):
|
|||
'data': [{'id': group.pk, 'text': 'group1', 'type': 'roles', 'urls': {}, 'uuid': None}]
|
||||
}
|
||||
|
||||
# unknown component type
|
||||
app.get('/api/export-import/unknown/', status=404)
|
||||
|
||||
|
||||
def test_export_agenda(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
|
@ -195,6 +199,9 @@ def test_export_minor_components(app, admin_user):
|
|||
# unknown component
|
||||
app.get('/api/export-import/agendas/foo/', status=404)
|
||||
|
||||
# unknown component type
|
||||
app.get('/api/export-import/unknown/foo/', status=404)
|
||||
|
||||
|
||||
def test_agenda_dependencies_category(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
|
@ -363,6 +370,11 @@ def test_unknown_compoment_dependencies(app, admin_user):
|
|||
app.get('/api/export-import/agendas/foo/dependencies/', status=404)
|
||||
|
||||
|
||||
def test_unknown_compoment_type_dependencies(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
app.get('/api/export-import/unknown/foo/dependencies/', status=404)
|
||||
|
||||
|
||||
def test_redirect(app, user):
|
||||
app.authorization = ('Basic', ('john', 'doe'))
|
||||
agenda = Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
|
||||
|
@ -374,22 +386,60 @@ def test_redirect(app, user):
|
|||
redirect_url = f'/api/export-import/agendas/{agenda.slug}/redirect/'
|
||||
resp = app.get(redirect_url, status=302)
|
||||
assert resp.location == f'/manage/agendas/{agenda.pk}/'
|
||||
resp = app.get(redirect_url + '?compare', status=302)
|
||||
assert resp.location == f'/manage/agendas/{agenda.pk}/'
|
||||
resp = app.get(redirect_url + '?compare&version1=bar&version2=bar&application=foo', status=302)
|
||||
assert (
|
||||
resp.location
|
||||
== f'/manage/agendas/{agenda.pk}/history/compare/?version1=bar&version2=bar&application=foo'
|
||||
)
|
||||
|
||||
redirect_url = f'/api/export-import/agendas_categories/{category.slug}/redirect/'
|
||||
resp = app.get(redirect_url, status=302)
|
||||
assert resp.location == '/manage/categories/'
|
||||
resp = app.get(redirect_url + '?compare', status=302)
|
||||
assert resp.location == '/manage/categories/'
|
||||
resp = app.get(redirect_url + '?compare&version1=bar&version2=bar&application=foo', status=302)
|
||||
assert (
|
||||
resp.location
|
||||
== f'/manage/category/{category.pk}/history/compare/?version1=bar&version2=bar&application=foo'
|
||||
)
|
||||
|
||||
redirect_url = f'/api/export-import/resources/{resource.slug}/redirect/'
|
||||
resp = app.get(redirect_url, status=302)
|
||||
assert resp.location == f'/manage/resource/{resource.pk}/'
|
||||
resp = app.get(redirect_url + '?compare', status=302)
|
||||
assert resp.location == f'/manage/resource/{resource.pk}/'
|
||||
resp = app.get(redirect_url + '?compare&version1=bar&version2=bar&application=foo', status=302)
|
||||
assert (
|
||||
resp.location
|
||||
== f'/manage/resource/{resource.pk}/history/compare/?version1=bar&version2=bar&application=foo'
|
||||
)
|
||||
|
||||
redirect_url = f'/api/export-import/events_types/{events_type.slug}/redirect/'
|
||||
resp = app.get(redirect_url, status=302)
|
||||
assert resp.location == '/manage/events-types/'
|
||||
resp = app.get(redirect_url + '?compare', status=302)
|
||||
assert resp.location == '/manage/events-types/'
|
||||
resp = app.get(redirect_url + '?compare&version1=bar&version2=bar&application=foo', status=302)
|
||||
assert (
|
||||
resp.location
|
||||
== f'/manage/events-type/{events_type.pk}/history/compare/?version1=bar&version2=bar&application=foo'
|
||||
)
|
||||
|
||||
redirect_url = f'/api/export-import/unavailability_calendars/{unavailability_calendar.slug}/redirect/'
|
||||
resp = app.get(redirect_url, status=302)
|
||||
assert resp.location == f'/manage/unavailability-calendar/{unavailability_calendar.pk}/'
|
||||
resp = app.get(redirect_url + '?compare', status=302)
|
||||
assert resp.location == f'/manage/unavailability-calendar/{unavailability_calendar.pk}/'
|
||||
resp = app.get(redirect_url + '?compare&version1=bar&version2=bar&application=foo', status=302)
|
||||
assert (
|
||||
resp.location
|
||||
== f'/manage/unavailability-calendar/{unavailability_calendar.pk}/history/compare/?version1=bar&version2=bar&application=foo'
|
||||
)
|
||||
|
||||
# unknown component type
|
||||
app.get('/api/export-import/unknown/foo/redirect/', status=404)
|
||||
|
||||
|
||||
def create_bundle(app, admin_user, visible=True, version_number='42.0'):
|
||||
|
@ -506,6 +556,12 @@ def test_bundle_import(app, admin_user):
|
|||
assert application.editable is False
|
||||
assert application.visible is True
|
||||
assert ApplicationElement.objects.count() == 8
|
||||
for model in [Agenda, Category, EventsType, Resource, UnavailabilityCalendar]:
|
||||
for instance in model.objects.all():
|
||||
last_snapshot = model.get_snapshot_model().objects.filter(instance=instance).latest('pk')
|
||||
assert last_snapshot.comment == 'Application (Test)'
|
||||
assert last_snapshot.application_slug == 'test'
|
||||
assert last_snapshot.application_version == '42.0'
|
||||
|
||||
# check editable flag is kept on install
|
||||
application.editable = True
|
||||
|
@ -535,6 +591,44 @@ def test_bundle_import(app, admin_user):
|
|||
).exists()
|
||||
is False
|
||||
)
|
||||
for model in [Agenda, Category, EventsType, Resource, UnavailabilityCalendar]:
|
||||
for instance in model.objects.all():
|
||||
last_snapshot = model.get_snapshot_model().objects.filter(instance=instance).latest('pk')
|
||||
assert last_snapshot.comment == 'Application (Test)'
|
||||
assert last_snapshot.application_slug == 'test'
|
||||
assert last_snapshot.application_version == '42.1'
|
||||
|
||||
# bad file format
|
||||
resp = app.put('/api/export-import/bundle-import/', b'garbage')
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file'
|
||||
|
||||
# missing manifest
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
foo_fd = io.BytesIO(json.dumps({'foo': 'bar'}, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('foo.json')
|
||||
tarinfo.size = len(foo_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=foo_fd)
|
||||
resp = app.put('/api/export-import/bundle-import/', tar_io.getvalue())
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file, missing manifest'
|
||||
|
||||
# missing component
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
manifest_json = {
|
||||
'application': 'Test',
|
||||
'slug': 'test',
|
||||
'elements': [{'type': 'agendas', 'slug': 'foo', 'name': 'foo'}],
|
||||
}
|
||||
manifest_fd = io.BytesIO(json.dumps(manifest_json, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('manifest.json')
|
||||
tarinfo.size = len(manifest_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=manifest_fd)
|
||||
resp = app.put('/api/export-import/bundle-import/', tar_io.getvalue())
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file, missing component agendas/foo'
|
||||
|
||||
|
||||
def test_bundle_declare(app, admin_user):
|
||||
|
@ -565,7 +659,7 @@ def test_bundle_declare(app, admin_user):
|
|||
content_type=ContentType.objects.get_for_model(Agenda),
|
||||
object_id=last_page.pk + 1,
|
||||
)
|
||||
# and remove agendas to have unkown references in manifest
|
||||
# and remove agendas to have unknown references in manifest
|
||||
Agenda.objects.all().delete()
|
||||
|
||||
resp = app.put('/api/export-import/bundle-declare/', bundle)
|
||||
|
@ -574,6 +668,38 @@ def test_bundle_declare(app, admin_user):
|
|||
assert application.visible is True
|
||||
assert ApplicationElement.objects.count() == 4 # category, events_type, unavailability_calendar, resource
|
||||
|
||||
# bad file format
|
||||
resp = app.put('/api/export-import/bundle-declare/', b'garbage')
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file'
|
||||
|
||||
# missing manifest
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
foo_fd = io.BytesIO(json.dumps({'foo': 'bar'}, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('foo.json')
|
||||
tarinfo.size = len(foo_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=foo_fd)
|
||||
resp = app.put('/api/export-import/bundle-declare/', tar_io.getvalue())
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file, missing manifest'
|
||||
|
||||
# missing component
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
manifest_json = {
|
||||
'application': 'Test',
|
||||
'slug': 'test',
|
||||
'elements': [{'type': 'agendas', 'slug': 'foo', 'name': 'foo'}],
|
||||
}
|
||||
manifest_fd = io.BytesIO(json.dumps(manifest_json, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('manifest.json')
|
||||
tarinfo.size = len(manifest_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=manifest_fd)
|
||||
resp = app.put('/api/export-import/bundle-declare/', tar_io.getvalue())
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file, missing component agendas/foo'
|
||||
|
||||
|
||||
def test_bundle_unlink(app, admin_user, bundle):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
|
@ -632,4 +758,283 @@ def test_bundle_unlink(app, admin_user, bundle):
|
|||
|
||||
def test_bundle_check(app, admin_user):
|
||||
app.authorization = ('Basic', ('admin', 'admin'))
|
||||
assert app.put('/api/export-import/bundle-check/').json == {'err': 0, 'data': {}}
|
||||
|
||||
bundles = []
|
||||
for version_number in ['42.0', '42.1']:
|
||||
bundles.append(create_bundle(app, admin_user, version_number=version_number))
|
||||
Agenda.objects.all().delete()
|
||||
Category.objects.all().delete()
|
||||
Resource.objects.all().delete()
|
||||
EventsType.objects.all().delete()
|
||||
UnavailabilityCalendar.objects.all().delete()
|
||||
|
||||
incomplete_bundles = []
|
||||
for manifest_json in [{'slug': 'test'}, {'version_number': '1.0'}]:
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
manifest_fd = io.BytesIO(json.dumps(manifest_json, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('manifest.json')
|
||||
tarinfo.size = len(manifest_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=manifest_fd)
|
||||
incomplete_bundles.append(tar_io.getvalue())
|
||||
|
||||
# incorrect bundles, missing information
|
||||
resp = app.put('/api/export-import/bundle-check/', incomplete_bundles[0])
|
||||
assert resp.json == {'data': {}}
|
||||
resp = app.put('/api/export-import/bundle-check/', incomplete_bundles[1])
|
||||
assert resp.json == {'data': {}}
|
||||
|
||||
# not yet imported
|
||||
resp = app.put('/api/export-import/bundle-check/', bundles[0])
|
||||
assert resp.json == {
|
||||
'data': {
|
||||
'differences': [],
|
||||
'no_history_elements': [],
|
||||
'unknown_elements': [
|
||||
{'slug': 'rdv', 'type': 'agendas'},
|
||||
{'slug': 'foo', 'type': 'categories'},
|
||||
{'slug': 'foo', 'type': 'resources'},
|
||||
{'slug': 'foo', 'type': 'unavailability_calendars'},
|
||||
{'slug': 'evt', 'type': 'agendas'},
|
||||
{'slug': 'foo', 'type': 'events_types'},
|
||||
{'slug': 'virt', 'type': 'agendas'},
|
||||
{'slug': 'sub', 'type': 'agendas'},
|
||||
],
|
||||
'legacy_elements': [],
|
||||
}
|
||||
}
|
||||
|
||||
# import bundle
|
||||
resp = app.put('/api/export-import/bundle-import/', bundles[0])
|
||||
assert Application.objects.count() == 1
|
||||
assert ApplicationElement.objects.count() == 8
|
||||
|
||||
# remove application links
|
||||
Application.objects.all().delete()
|
||||
resp = app.put('/api/export-import/bundle-check/', bundles[0])
|
||||
assert resp.json == {
|
||||
'data': {
|
||||
'differences': [],
|
||||
'no_history_elements': [],
|
||||
'unknown_elements': [],
|
||||
'legacy_elements': [
|
||||
{
|
||||
'slug': 'rdv',
|
||||
'text': 'Rdv',
|
||||
'type': 'agendas',
|
||||
'url': '/api/export-import/agendas/rdv/redirect/',
|
||||
},
|
||||
{
|
||||
'slug': 'foo',
|
||||
'text': 'Foo',
|
||||
'type': 'agendas_categories',
|
||||
'url': '/api/export-import/agendas_categories/foo/redirect/',
|
||||
},
|
||||
{
|
||||
'slug': 'foo',
|
||||
'text': 'Foo',
|
||||
'type': 'resources',
|
||||
'url': '/api/export-import/resources/foo/redirect/',
|
||||
},
|
||||
{
|
||||
'slug': 'foo',
|
||||
'text': 'Foo',
|
||||
'type': 'unavailability_calendars',
|
||||
'url': '/api/export-import/unavailability_calendars/foo/redirect/',
|
||||
},
|
||||
{
|
||||
'slug': 'evt',
|
||||
'text': 'Evt',
|
||||
'type': 'agendas',
|
||||
'url': '/api/export-import/agendas/evt/redirect/',
|
||||
},
|
||||
{
|
||||
'slug': 'foo',
|
||||
'text': 'Foo',
|
||||
'type': 'events_types',
|
||||
'url': '/api/export-import/events_types/foo/redirect/',
|
||||
},
|
||||
{
|
||||
'slug': 'virt',
|
||||
'text': 'Virt',
|
||||
'type': 'agendas',
|
||||
'url': '/api/export-import/agendas/virt/redirect/',
|
||||
},
|
||||
{
|
||||
'slug': 'sub',
|
||||
'text': 'Sub',
|
||||
'type': 'agendas',
|
||||
'url': '/api/export-import/agendas/sub/redirect/',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
# import bundle again, recreate links
|
||||
resp = app.put('/api/export-import/bundle-import/', bundles[0])
|
||||
assert Application.objects.count() == 1
|
||||
assert ApplicationElement.objects.count() == 8
|
||||
|
||||
# no changes since last import
|
||||
resp = app.put('/api/export-import/bundle-check/', bundles[0])
|
||||
assert resp.json == {
|
||||
'data': {
|
||||
'differences': [],
|
||||
'unknown_elements': [],
|
||||
'no_history_elements': [],
|
||||
'legacy_elements': [],
|
||||
}
|
||||
}
|
||||
|
||||
# add local changes
|
||||
snapshots = {}
|
||||
for model in [Agenda, Category, EventsType, Resource, UnavailabilityCalendar]:
|
||||
for instance in model.objects.all():
|
||||
old_snapshot = model.get_snapshot_model().objects.filter(instance=instance).latest('pk')
|
||||
instance.take_snapshot(comment='local changes')
|
||||
new_snapshot = model.get_snapshot_model().objects.filter(instance=instance).latest('pk')
|
||||
assert new_snapshot.pk > old_snapshot.pk
|
||||
snapshots[f'{instance.application_component_type}:{instance.slug}'] = (
|
||||
instance.pk,
|
||||
old_snapshot.pk,
|
||||
new_snapshot.pk,
|
||||
)
|
||||
|
||||
# and check
|
||||
resp = app.put('/api/export-import/bundle-check/', bundles[0])
|
||||
assert resp.json == {
|
||||
'data': {
|
||||
'differences': [
|
||||
{
|
||||
'slug': 'rdv',
|
||||
'type': 'agendas',
|
||||
'url': 'http://testserver/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (
|
||||
snapshots['agendas:rdv'][0],
|
||||
snapshots['agendas:rdv'][1],
|
||||
snapshots['agendas:rdv'][2],
|
||||
),
|
||||
},
|
||||
{
|
||||
'slug': 'foo',
|
||||
'type': 'agendas_categories',
|
||||
'url': 'http://testserver/manage/category/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (
|
||||
snapshots['agendas_categories:foo'][0],
|
||||
snapshots['agendas_categories:foo'][1],
|
||||
snapshots['agendas_categories:foo'][2],
|
||||
),
|
||||
},
|
||||
{
|
||||
'slug': 'foo',
|
||||
'type': 'resources',
|
||||
'url': 'http://testserver/manage/resource/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (
|
||||
snapshots['resources:foo'][0],
|
||||
snapshots['resources:foo'][1],
|
||||
snapshots['resources:foo'][2],
|
||||
),
|
||||
},
|
||||
{
|
||||
'slug': 'foo',
|
||||
'type': 'unavailability_calendars',
|
||||
'url': 'http://testserver/manage/unavailability-calendar/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (
|
||||
snapshots['unavailability_calendars:foo'][0],
|
||||
snapshots['unavailability_calendars:foo'][1],
|
||||
snapshots['unavailability_calendars:foo'][2],
|
||||
),
|
||||
},
|
||||
{
|
||||
'slug': 'evt',
|
||||
'type': 'agendas',
|
||||
'url': 'http://testserver/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (
|
||||
snapshots['agendas:evt'][0],
|
||||
snapshots['agendas:evt'][1],
|
||||
snapshots['agendas:evt'][2],
|
||||
),
|
||||
},
|
||||
{
|
||||
'slug': 'foo',
|
||||
'type': 'events_types',
|
||||
'url': 'http://testserver/manage/events-type/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (
|
||||
snapshots['events_types:foo'][0],
|
||||
snapshots['events_types:foo'][1],
|
||||
snapshots['events_types:foo'][2],
|
||||
),
|
||||
},
|
||||
{
|
||||
'slug': 'virt',
|
||||
'type': 'agendas',
|
||||
'url': 'http://testserver/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (
|
||||
snapshots['agendas:virt'][0],
|
||||
snapshots['agendas:virt'][1],
|
||||
snapshots['agendas:virt'][2],
|
||||
),
|
||||
},
|
||||
{
|
||||
'slug': 'sub',
|
||||
'type': 'agendas',
|
||||
'url': 'http://testserver/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
|
||||
% (
|
||||
snapshots['agendas:sub'][0],
|
||||
snapshots['agendas:sub'][1],
|
||||
snapshots['agendas:sub'][2],
|
||||
),
|
||||
},
|
||||
],
|
||||
'unknown_elements': [],
|
||||
'no_history_elements': [],
|
||||
'legacy_elements': [],
|
||||
}
|
||||
}
|
||||
|
||||
# update bundle
|
||||
resp = app.put('/api/export-import/bundle-import/', bundles[1])
|
||||
|
||||
# and check
|
||||
resp = app.put('/api/export-import/bundle-check/', bundles[1])
|
||||
assert resp.json == {
|
||||
'data': {
|
||||
'differences': [],
|
||||
'unknown_elements': [],
|
||||
'no_history_elements': [],
|
||||
'legacy_elements': [],
|
||||
}
|
||||
}
|
||||
|
||||
# snapshots without application info
|
||||
AgendaSnapshot.objects.update(application_slug=None, application_version=None)
|
||||
resp = app.put('/api/export-import/bundle-check/', bundles[1])
|
||||
assert resp.json == {
|
||||
'data': {
|
||||
'differences': [],
|
||||
'unknown_elements': [],
|
||||
'no_history_elements': [
|
||||
{'slug': 'rdv', 'type': 'agendas'},
|
||||
{'slug': 'evt', 'type': 'agendas'},
|
||||
{'slug': 'virt', 'type': 'agendas'},
|
||||
{'slug': 'sub', 'type': 'agendas'},
|
||||
],
|
||||
'legacy_elements': [],
|
||||
}
|
||||
}
|
||||
|
||||
# bad file format
|
||||
resp = app.put('/api/export-import/bundle-check/', b'garbage')
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file'
|
||||
|
||||
# missing manifest
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
foo_fd = io.BytesIO(json.dumps({'foo': 'bar'}, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('foo.json')
|
||||
tarinfo.size = len(foo_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=foo_fd)
|
||||
resp = app.put('/api/export-import/bundle-check/', tar_io.getvalue())
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file, missing manifest'
|
||||
|
|
|
@ -188,7 +188,7 @@ def test_bookings_api(app, user):
|
|||
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
resp = app.get('/api/bookings/', params={'user_external_id': 'enfant-1234'})
|
||||
assert len(ctx.captured_queries) == 3
|
||||
assert len(ctx.captured_queries) == 6
|
||||
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['data'] == [
|
||||
|
|
|
@ -44,9 +44,11 @@ def test_status(app, user):
|
|||
'err': 0,
|
||||
'id': 'event-slug',
|
||||
'slug': 'event-slug',
|
||||
'primary_event': None,
|
||||
'text': str(event),
|
||||
'label': '',
|
||||
'agenda_label': 'Foo bar',
|
||||
'agenda_slug': 'foo-bar',
|
||||
'date': localtime(event.start_datetime).strftime('%Y-%m-%d'),
|
||||
'datetime': localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'end_datetime': '',
|
||||
|
@ -84,9 +86,11 @@ def test_status(app, user):
|
|||
'err': 0,
|
||||
'id': 'event-slug',
|
||||
'slug': 'event-slug',
|
||||
'primary_event': None,
|
||||
'text': str(event),
|
||||
'label': '',
|
||||
'agenda_label': 'Foo bar',
|
||||
'agenda_slug': 'foo-bar',
|
||||
'date': localtime(event.start_datetime).strftime('%Y-%m-%d'),
|
||||
'datetime': localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'end_datetime': '',
|
||||
|
|
|
@ -1138,7 +1138,7 @@ def test_manager_partial_bookings_month_view(app, admin_user, freezer):
|
|||
today = start_datetime.date()
|
||||
resp = app.get('/manage/agendas/%s/month/%d/%d/%d/' % (agenda.pk, today.year, today.month, today.day))
|
||||
|
||||
assert [int(x.text) for x in resp.pyquery('thead th a')] == list(range(1, 32))
|
||||
assert [int(x.text) for x in resp.pyquery('thead th time')] == list(range(1, 32))
|
||||
|
||||
assert [x.text for x in resp.pyquery('tbody tr th')] == [
|
||||
'User Absent',
|
||||
|
@ -1151,21 +1151,24 @@ def test_manager_partial_bookings_month_view(app, admin_user, freezer):
|
|||
|
||||
user_absent_row = resp.pyquery('tbody tr')[0]
|
||||
assert len(resp.pyquery(user_absent_row)('td')) == 31
|
||||
assert len(resp.pyquery(user_absent_row)('td span')) == 1
|
||||
assert len(resp.pyquery(user_absent_row)('td span.booking')) == 1
|
||||
assert len(resp.pyquery(user_absent_row)('td span.booking.absent')) == 1
|
||||
assert resp.pyquery(user_absent_row)('td span.booking.absent').text() == 'Absent'
|
||||
|
||||
subscription_not_booked_row = resp.pyquery('tbody tr')[1]
|
||||
assert len(resp.pyquery(subscription_not_booked_row)('td')) == 31
|
||||
assert len(resp.pyquery(subscription_not_booked_row)('td span')) == 0
|
||||
assert len(resp.pyquery(subscription_not_booked_row)('td span.booking')) == 0
|
||||
|
||||
user_not_checked_row = resp.pyquery('tbody tr')[2]
|
||||
assert len(resp.pyquery(user_not_checked_row)('td')) == 31
|
||||
assert len(resp.pyquery(user_not_checked_row)('td span.booking')) == 2
|
||||
assert resp.pyquery(user_not_checked_row)('td span.booking').text() == 'Not checked Not checked'
|
||||
|
||||
user_present_row = resp.pyquery('tbody tr')[3]
|
||||
assert len(resp.pyquery(user_present_row)('td')) == 31
|
||||
assert len(resp.pyquery(user_present_row)('td span')) == 1
|
||||
assert len(resp.pyquery(user_present_row)('td span.booking')) == 1
|
||||
assert len(resp.pyquery(user_present_row)('td span.booking.present')) == 1
|
||||
assert resp.pyquery(user_present_row)('td span.booking.present').text() == 'Present'
|
||||
|
||||
user_present_mixed_row = resp.pyquery('tbody tr')[4]
|
||||
assert len(resp.pyquery(user_present_mixed_row)('td')) == 31
|
||||
|
@ -1178,10 +1181,20 @@ def test_manager_partial_bookings_month_view(app, admin_user, freezer):
|
|||
assert len(resp.pyquery(user_present_incomplete_row)('td span.booking.present')) == 0
|
||||
|
||||
resp = resp.click('Next month')
|
||||
assert [int(x.text) for x in resp.pyquery('thead th a')] == list(range(1, 31))
|
||||
assert [int(x.text) for x in resp.pyquery('thead th time')] == list(range(1, 31))
|
||||
assert [x.text for x in resp.pyquery('tbody tr th')] == ['Subscription Next Month']
|
||||
assert len(resp.pyquery('tbody tr td')) == 30
|
||||
|
||||
freezer.move_to('2023-05-10 14:00')
|
||||
resp = app.get(resp.request.url)
|
||||
assert len(resp.pyquery('th.today')) == 0
|
||||
assert len(resp.pyquery('col.today')) == 0
|
||||
|
||||
freezer.move_to('2023-06-10 14:00')
|
||||
resp = app.get(resp.request.url)
|
||||
assert resp.pyquery('th.today').text() == '10'
|
||||
assert len(resp.pyquery('col.today')) == 1
|
||||
|
||||
|
||||
def test_manager_partial_bookings_occupation_rates(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Foo bar', kind='events', partial_bookings=True)
|
||||
|
|
|
@ -28,6 +28,7 @@ def test_agenda_history(settings, app, admin_user):
|
|||
agenda.description = 'Foo Bar'
|
||||
agenda.save()
|
||||
snapshot2 = agenda.take_snapshot()
|
||||
snapshot2.application_slug = 'foobar'
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert AgendaSnapshot.objects.count() == 2
|
||||
|
@ -56,7 +57,7 @@ def test_agenda_history(settings, app, admin_user):
|
|||
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_add') == 17
|
||||
assert resp.text.count('diff_chg') == 0
|
||||
resp = app.get(
|
||||
'/manage/agendas/%s/history/compare/?version1=%s&version2=%s'
|
||||
|
@ -65,9 +66,40 @@ def test_agenda_history(settings, app, admin_user):
|
|||
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_add') == 17
|
||||
assert resp.text.count('diff_chg') == 0
|
||||
|
||||
# check compare on application version number
|
||||
snapshot1.application_slug = 'foobar'
|
||||
snapshot1.application_version = '41.0'
|
||||
snapshot1.save()
|
||||
# application not found
|
||||
resp = app.get(
|
||||
'/manage/agendas/%s/history/compare/?application=foobaz&version1=41.0&version2=42.0' % agenda.pk
|
||||
)
|
||||
assert resp.location == '/manage/agendas/%s/history/' % agenda.pk
|
||||
# version1 not found
|
||||
resp = app.get(
|
||||
'/manage/agendas/%s/history/compare/?application=foobar&version1=40.0&version2=42.0' % agenda.pk
|
||||
)
|
||||
assert resp.location == '/manage/agendas/%s/history/' % agenda.pk
|
||||
# version2 not found
|
||||
resp = app.get(
|
||||
'/manage/agendas/%s/history/compare/?application=foobar&version1=41.0&version2=43.0' % agenda.pk
|
||||
)
|
||||
assert resp.location == '/manage/agendas/%s/history/' % agenda.pk
|
||||
# ok
|
||||
resp = app.get(
|
||||
'/manage/agendas/%s/history/compare/?application=foobar&version1=41.0&version2=42.0' % agenda.pk
|
||||
)
|
||||
assert 'Snapshot (%s) - (Version 41.0)' % snapshot1.pk in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % snapshot2.pk in resp
|
||||
|
||||
assert AgendaSnapshot.objects.update(user=admin_user)
|
||||
admin_user.delete()
|
||||
assert AgendaSnapshot.objects.count() == 2
|
||||
assert AgendaSnapshot.objects.filter(user__isnull=True).count() == 2
|
||||
|
||||
|
||||
def test_agenda_history_as_manager(app, manager_user):
|
||||
agenda = Agenda.objects.create(slug='foo', label='Foo')
|
||||
|
@ -101,6 +133,7 @@ def test_category_history(settings, app, admin_user):
|
|||
category.label = 'Bar'
|
||||
category.save()
|
||||
snapshot2 = category.take_snapshot()
|
||||
snapshot2.application_slug = 'foobar'
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert CategorySnapshot.objects.count() == 2
|
||||
|
@ -141,6 +174,37 @@ def test_category_history(settings, app, admin_user):
|
|||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
|
||||
# check compare on application version number
|
||||
snapshot1.application_slug = 'foobar'
|
||||
snapshot1.application_version = '41.0'
|
||||
snapshot1.save()
|
||||
# application not found
|
||||
resp = app.get(
|
||||
'/manage/category/%s/history/compare/?application=foobaz&version1=41.0&version2=42.0' % category.pk
|
||||
)
|
||||
assert resp.location == '/manage/category/%s/history/' % category.pk
|
||||
# version1 not found
|
||||
resp = app.get(
|
||||
'/manage/category/%s/history/compare/?application=foobar&version1=40.0&version2=42.0' % category.pk
|
||||
)
|
||||
assert resp.location == '/manage/category/%s/history/' % category.pk
|
||||
# version2 not found
|
||||
resp = app.get(
|
||||
'/manage/category/%s/history/compare/?application=foobar&version1=41.0&version2=43.0' % category.pk
|
||||
)
|
||||
assert resp.location == '/manage/category/%s/history/' % category.pk
|
||||
# ok
|
||||
resp = app.get(
|
||||
'/manage/category/%s/history/compare/?application=foobar&version1=41.0&version2=42.0' % category.pk
|
||||
)
|
||||
assert 'Snapshot (%s) - (Version 41.0)' % snapshot1.pk in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % snapshot2.pk in resp
|
||||
|
||||
assert CategorySnapshot.objects.update(user=admin_user)
|
||||
admin_user.delete()
|
||||
assert CategorySnapshot.objects.count() == 2
|
||||
assert CategorySnapshot.objects.filter(user__isnull=True).count() == 2
|
||||
|
||||
|
||||
def test_events_type_history(settings, app, admin_user):
|
||||
events_type = EventsType.objects.create(slug='foo', label='Foo')
|
||||
|
@ -148,6 +212,7 @@ def test_events_type_history(settings, app, admin_user):
|
|||
events_type.label = 'Bar'
|
||||
events_type.save()
|
||||
snapshot2 = events_type.take_snapshot()
|
||||
snapshot2.application_slug = 'foobar'
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert EventsTypeSnapshot.objects.count() == 2
|
||||
|
@ -188,6 +253,41 @@ def test_events_type_history(settings, app, admin_user):
|
|||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
|
||||
# check compare on application version number
|
||||
snapshot1.application_slug = 'foobar'
|
||||
snapshot1.application_version = '41.0'
|
||||
snapshot1.save()
|
||||
# application not found
|
||||
resp = app.get(
|
||||
'/manage/events-type/%s/history/compare/?application=foobaz&version1=41.0&version2=42.0'
|
||||
% events_type.pk
|
||||
)
|
||||
assert resp.location == '/manage/events-type/%s/history/' % events_type.pk
|
||||
# version1 not found
|
||||
resp = app.get(
|
||||
'/manage/events-type/%s/history/compare/?application=foobar&version1=40.0&version2=42.0'
|
||||
% events_type.pk
|
||||
)
|
||||
assert resp.location == '/manage/events-type/%s/history/' % events_type.pk
|
||||
# version2 not found
|
||||
resp = app.get(
|
||||
'/manage/events-type/%s/history/compare/?application=foobar&version1=41.0&version2=43.0'
|
||||
% events_type.pk
|
||||
)
|
||||
assert resp.location == '/manage/events-type/%s/history/' % events_type.pk
|
||||
# ok
|
||||
resp = app.get(
|
||||
'/manage/events-type/%s/history/compare/?application=foobar&version1=41.0&version2=42.0'
|
||||
% events_type.pk
|
||||
)
|
||||
assert 'Snapshot (%s) - (Version 41.0)' % snapshot1.pk in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % snapshot2.pk in resp
|
||||
|
||||
assert EventsTypeSnapshot.objects.update(user=admin_user)
|
||||
admin_user.delete()
|
||||
assert EventsTypeSnapshot.objects.count() == 2
|
||||
assert EventsTypeSnapshot.objects.filter(user__isnull=True).count() == 2
|
||||
|
||||
|
||||
def test_resource_history(settings, app, admin_user):
|
||||
resource = Resource.objects.create(slug='foo', label='Foo')
|
||||
|
@ -195,6 +295,7 @@ def test_resource_history(settings, app, admin_user):
|
|||
resource.label = 'Bar'
|
||||
resource.save()
|
||||
snapshot2 = resource.take_snapshot()
|
||||
snapshot2.application_slug = 'foobar'
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert ResourceSnapshot.objects.count() == 2
|
||||
|
@ -235,6 +336,37 @@ def test_resource_history(settings, app, admin_user):
|
|||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
|
||||
# check compare on application version number
|
||||
snapshot1.application_slug = 'foobar'
|
||||
snapshot1.application_version = '41.0'
|
||||
snapshot1.save()
|
||||
# application not found
|
||||
resp = app.get(
|
||||
'/manage/resource/%s/history/compare/?application=foobaz&version1=41.0&version2=42.0' % resource.pk
|
||||
)
|
||||
assert resp.location == '/manage/resource/%s/history/' % resource.pk
|
||||
# version1 not found
|
||||
resp = app.get(
|
||||
'/manage/resource/%s/history/compare/?application=foobar&version1=40.0&version2=42.0' % resource.pk
|
||||
)
|
||||
assert resp.location == '/manage/resource/%s/history/' % resource.pk
|
||||
# version2 not found
|
||||
resp = app.get(
|
||||
'/manage/resource/%s/history/compare/?application=foobar&version1=41.0&version2=43.0' % resource.pk
|
||||
)
|
||||
assert resp.location == '/manage/resource/%s/history/' % resource.pk
|
||||
# ok
|
||||
resp = app.get(
|
||||
'/manage/resource/%s/history/compare/?application=foobar&version1=41.0&version2=42.0' % resource.pk
|
||||
)
|
||||
assert 'Snapshot (%s) - (Version 41.0)' % snapshot1.pk in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % snapshot2.pk in resp
|
||||
|
||||
assert ResourceSnapshot.objects.update(user=admin_user)
|
||||
admin_user.delete()
|
||||
assert ResourceSnapshot.objects.count() == 2
|
||||
assert ResourceSnapshot.objects.filter(user__isnull=True).count() == 2
|
||||
|
||||
|
||||
def test_unavailability_calendar_history(settings, app, admin_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(slug='foo', label='Foo')
|
||||
|
@ -242,6 +374,7 @@ def test_unavailability_calendar_history(settings, app, admin_user):
|
|||
unavailability_calendar.label = 'Bar'
|
||||
unavailability_calendar.save()
|
||||
snapshot2 = unavailability_calendar.take_snapshot()
|
||||
snapshot2.application_slug = 'foobar'
|
||||
snapshot2.application_version = '42.0'
|
||||
snapshot2.save()
|
||||
assert UnavailabilityCalendarSnapshot.objects.count() == 2
|
||||
|
@ -282,6 +415,41 @@ def test_unavailability_calendar_history(settings, app, admin_user):
|
|||
assert resp.text.count('diff_add') == 0
|
||||
assert resp.text.count('diff_chg') == 2
|
||||
|
||||
# check compare on application version number
|
||||
snapshot1.application_slug = 'foobar'
|
||||
snapshot1.application_version = '41.0'
|
||||
snapshot1.save()
|
||||
# application not found
|
||||
resp = app.get(
|
||||
'/manage/unavailability-calendar/%s/history/compare/?application=foobaz&version1=41.0&version2=42.0'
|
||||
% unavailability_calendar.pk
|
||||
)
|
||||
assert resp.location == '/manage/unavailability-calendar/%s/history/' % unavailability_calendar.pk
|
||||
# version1 not found
|
||||
resp = app.get(
|
||||
'/manage/unavailability-calendar/%s/history/compare/?application=foobar&version1=40.0&version2=42.0'
|
||||
% unavailability_calendar.pk
|
||||
)
|
||||
assert resp.location == '/manage/unavailability-calendar/%s/history/' % unavailability_calendar.pk
|
||||
# version2 not found
|
||||
resp = app.get(
|
||||
'/manage/unavailability-calendar/%s/history/compare/?application=foobar&version1=41.0&version2=43.0'
|
||||
% unavailability_calendar.pk
|
||||
)
|
||||
assert resp.location == '/manage/unavailability-calendar/%s/history/' % unavailability_calendar.pk
|
||||
# ok
|
||||
resp = app.get(
|
||||
'/manage/unavailability-calendar/%s/history/compare/?application=foobar&version1=41.0&version2=42.0'
|
||||
% unavailability_calendar.pk
|
||||
)
|
||||
assert 'Snapshot (%s) - (Version 41.0)' % snapshot1.pk in resp
|
||||
assert 'Snapshot (%s) - (Version 42.0)' % snapshot2.pk in resp
|
||||
|
||||
assert UnavailabilityCalendarSnapshot.objects.update(user=admin_user)
|
||||
admin_user.delete()
|
||||
assert UnavailabilityCalendarSnapshot.objects.count() == 2
|
||||
assert UnavailabilityCalendarSnapshot.objects.filter(user__isnull=True).count() == 2
|
||||
|
||||
|
||||
def test_unavailability_calendar_history_as_manager(app, manager_user):
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(slug='foo', label='Foo')
|
||||
|
|
|
@ -181,6 +181,25 @@ def test_import_export_bad_date_format(app):
|
|||
assert '%s' % excinfo.value == 'Bad datetime format "17-05-22 08:00:00"'
|
||||
|
||||
|
||||
def test_import_export_bad_end_time_format(app):
|
||||
agenda_events = Agenda.objects.create(label='Events Agenda', kind='events')
|
||||
Desk.objects.create(agenda=agenda_events, slug='_exceptions_holder')
|
||||
Event.objects.create(
|
||||
agenda=agenda_events,
|
||||
start_datetime=make_aware(datetime.datetime(2020, 7, 21, 16, 42, 35)),
|
||||
places=10,
|
||||
end_time=datetime.time(20, 00),
|
||||
)
|
||||
|
||||
output = get_output_of_command('export_site')
|
||||
payload = json.loads(output)
|
||||
assert len(payload['agendas']) == 1
|
||||
payload['agendas'][0]['events'][0]['end_time'] = 'xxx20:00'
|
||||
with pytest.raises(AgendaImportError) as excinfo:
|
||||
import_site(payload)
|
||||
assert '%s' % excinfo.value == 'Bad time format "xxx20:00"'
|
||||
|
||||
|
||||
def test_import_export_events_agenda_options(app):
|
||||
agenda = Agenda.objects.create(
|
||||
label='Foo Bar',
|
||||
|
@ -256,6 +275,7 @@ def test_import_export_event_details(app):
|
|||
publication_datetime=make_aware(datetime.datetime(2020, 5, 11)),
|
||||
places=42,
|
||||
start_datetime=now(),
|
||||
end_time=datetime.time(20, 00),
|
||||
duration=30,
|
||||
)
|
||||
# check event (agenda, slug) unicity
|
||||
|
@ -287,6 +307,7 @@ def test_import_export_event_details(app):
|
|||
assert str(first_imported_event.publication_datetime) == '2020-05-10 22:00:00+00:00'
|
||||
assert str(first_imported_event.publication_datetime.tzinfo) == 'UTC'
|
||||
assert first_imported_event.duration == 30
|
||||
assert first_imported_event.end_time == datetime.time(20, 00)
|
||||
assert Agenda.objects.get(label='Foo Bar 2').event_set.first().slug == 'event'
|
||||
|
||||
|
||||
|
@ -297,6 +318,7 @@ def test_import_export_recurring_event(app, freezer):
|
|||
event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
start_datetime=now(),
|
||||
end_time=datetime.time(20, 00),
|
||||
recurrence_days=[now().isoweekday()],
|
||||
recurrence_week_interval=2,
|
||||
places=10,
|
||||
|
@ -353,6 +375,7 @@ def test_import_export_recurring_event(app, freezer):
|
|||
|
||||
event = Event.objects.get(slug='test')
|
||||
assert event.places == 42
|
||||
assert event.end_time == datetime.time(20, 00)
|
||||
assert Event.objects.filter(primary_event=event, places=42).count() == 1
|
||||
|
||||
|
||||
|
|
|
@ -275,7 +275,7 @@ def test_build_event_agenda(db):
|
|||
start = now()
|
||||
events = {
|
||||
f'Event {i}': {
|
||||
'start_datetime': start + datetime.timedelta(days=i),
|
||||
'start_datetime': localtime(start) + datetime.timedelta(days=i),
|
||||
'places': 10,
|
||||
}
|
||||
for i in range(10)
|
||||
|
|
3
tox.ini
3
tox.ini
|
@ -13,6 +13,7 @@ setenv =
|
|||
CHRONO_SETTINGS_FILE=tests/settings.py
|
||||
BRANCH_NAME={env:BRANCH_NAME:}
|
||||
SETUPTOOLS_USE_DISTUTILS=stdlib
|
||||
NUMPROCESSES={env:NUMPROCESSES:1}
|
||||
coverage: COVERAGE=--junitxml=junit-{envname}.xml --cov-report xml --cov-report html --cov=chrono/ --cov-config .coveragerc
|
||||
|
||||
deps =
|
||||
|
@ -44,7 +45,7 @@ allowlist_externals =
|
|||
commands =
|
||||
./getlasso3.sh
|
||||
python3 setup.py compile_translations
|
||||
py.test -v --dist loadfile {env:COVERAGE:} {posargs:tests/}
|
||||
py.test {posargs:-v --dist loadfile {env:COVERAGE:} --numprocesses={env:NUMPROCESSES:1} tests/}
|
||||
codestyle: pre-commit run --all-files --show-diff-on-failure
|
||||
|
||||
[testenv:pylint]
|
||||
|
|
Loading…
Reference in New Issue