0005-agendas-remove-CheckType-CheckTypeGroup-models-66015.patch
chrono/agendas/migrations/0131_remove_check_type.py | ||
---|---|---|
1 |
from django.db import migrations |
|
2 | ||
3 | ||
4 |
class Migration(migrations.Migration): |
|
5 | ||
6 |
dependencies = [ |
|
7 |
('agendas', '0130_remove_check_type'), |
|
8 |
] |
|
9 | ||
10 |
operations = [ |
|
11 |
migrations.RemoveField( |
|
12 |
model_name='agenda', |
|
13 |
name='check_type_group', |
|
14 |
), |
|
15 |
migrations.DeleteModel( |
|
16 |
name='CheckType', |
|
17 |
), |
|
18 |
migrations.DeleteModel( |
|
19 |
name='CheckTypeGroup', |
|
20 |
), |
|
21 |
] |
chrono/agendas/models.py | ||
---|---|---|
200 | 200 |
category = models.ForeignKey( |
201 | 201 |
'Category', verbose_name=_('Category'), blank=True, null=True, on_delete=models.SET_NULL |
202 | 202 |
) |
203 |
check_type_group = models.ForeignKey( |
|
204 |
'CheckTypeGroup', |
|
205 |
verbose_name=_('Check type group'), |
|
206 |
blank=True, |
|
207 |
null=True, |
|
208 |
on_delete=models.SET_NULL, |
|
209 |
) |
|
210 | 203 |
default_view = models.CharField(_('Default view'), max_length=20, choices=AGENDA_VIEWS) |
211 | 204 |
booking_form_url = models.CharField( |
212 | 205 |
_('Booking form URL'), max_length=200, blank=True, validators=[django_template_validator] |
... | ... | |
391 | 384 |
agenda['events'] = [x.export_json() for x in self.event_set.filter(primary_event__isnull=True)] |
392 | 385 |
if hasattr(self, 'notifications_settings'): |
393 | 386 |
agenda['notifications_settings'] = self.notifications_settings.export_json() |
394 |
agenda['check_type_group'] = self.check_type_group.slug if self.check_type_group else None |
|
395 | 387 |
agenda['exceptions_desk'] = self.desk_set.get().export_json() |
396 | 388 |
agenda['minimal_booking_delay_in_working_days'] = self.minimal_booking_delay_in_working_days |
397 | 389 |
agenda['booking_user_block_template'] = self.booking_user_block_template |
... | ... | |
438 | 430 |
data['category'] = Category.objects.get(slug=data['category']) |
439 | 431 |
except Category.DoesNotExist: |
440 | 432 |
del data['category'] |
441 |
if data.get('check_type_group'): |
|
442 |
try: |
|
443 |
data['check_type_group'] = CheckTypeGroup.objects.get(slug=data['check_type_group']) |
|
444 |
except CheckTypeGroup.DoesNotExist: |
|
445 |
raise AgendaImportError(_('Missing "%s" check type group') % data['check_type_group']) |
|
446 | 433 |
if data.get('events_type'): |
447 | 434 |
try: |
448 | 435 |
data['events_type'] = EventsType.objects.get(slug=data['events_type']) |
... | ... | |
3051 | 3038 |
return new_settings |
3052 | 3039 | |
3053 | 3040 | |
3054 |
class CheckTypeGroup(models.Model): |
|
3055 |
slug = models.SlugField(_('Identifier'), max_length=160, unique=True) |
|
3056 |
label = models.CharField(_('Label'), max_length=150) |
|
3057 | ||
3058 |
class Meta: |
|
3059 |
ordering = ['label'] |
|
3060 | ||
3061 |
def __str__(self): |
|
3062 |
return self.label |
|
3063 | ||
3064 |
def save(self, *args, **kwargs): |
|
3065 |
if not self.slug: |
|
3066 |
self.slug = generate_slug(self) |
|
3067 |
super().save(*args, **kwargs) |
|
3068 | ||
3069 |
@property |
|
3070 |
def base_slug(self): |
|
3071 |
return slugify(self.label) |
|
3072 | ||
3073 |
@classmethod |
|
3074 |
def import_json(cls, data, overwrite=False): |
|
3075 |
check_types = data.pop('check_types', []) |
|
3076 |
data = clean_import_data(cls, data) |
|
3077 |
group, created = cls.objects.update_or_create(slug=data['slug'], defaults=data) |
|
3078 | ||
3079 |
if overwrite: |
|
3080 |
CheckType.objects.filter(group=group).delete() |
|
3081 | ||
3082 |
for check_type in check_types: |
|
3083 |
check_type['group'] = group |
|
3084 |
CheckType.import_json(check_type) |
|
3085 | ||
3086 |
return created, group |
|
3087 | ||
3088 |
def export_json(self): |
|
3089 |
return { |
|
3090 |
'label': self.label, |
|
3091 |
'slug': self.slug, |
|
3092 |
'check_types': [a.export_json() for a in self.check_types.all()], |
|
3093 |
} |
|
3094 | ||
3095 | ||
3096 |
class CheckTypeManager(models.Manager): |
|
3097 |
def absences(self): |
|
3098 |
return self.filter(kind='absence', disabled=False) |
|
3099 | ||
3100 |
def presences(self): |
|
3101 |
return self.filter(kind='presence', disabled=False) |
|
3102 | ||
3103 | ||
3104 |
class CheckType(models.Model): |
|
3105 |
group = models.ForeignKey(CheckTypeGroup, on_delete=models.CASCADE, related_name='check_types') |
|
3106 |
slug = models.SlugField(_('Identifier'), max_length=160) |
|
3107 |
label = models.CharField(_('Label'), max_length=150) |
|
3108 |
kind = models.CharField( |
|
3109 |
_('Kind'), |
|
3110 |
max_length=8, |
|
3111 |
choices=[('absence', _('Absence')), ('presence', _('Presence'))], |
|
3112 |
default='absence', |
|
3113 |
) |
|
3114 |
disabled = models.BooleanField(_('Disabled'), default=False) |
|
3115 |
objects = CheckTypeManager() |
|
3116 | ||
3117 |
class Meta: |
|
3118 |
ordering = ['label'] |
|
3119 |
unique_together = ['group', 'slug'] |
|
3120 | ||
3121 |
def __str__(self): |
|
3122 |
return self.label |
|
3123 | ||
3124 |
def save(self, *args, **kwargs): |
|
3125 |
if not self.slug: |
|
3126 |
self.slug = generate_slug(self, group=self.group) |
|
3127 |
super().save(*args, **kwargs) |
|
3128 | ||
3129 |
@property |
|
3130 |
def base_slug(self): |
|
3131 |
return slugify(self.label) |
|
3132 | ||
3133 |
@classmethod |
|
3134 |
def import_json(cls, data): |
|
3135 |
data = clean_import_data(cls, data) |
|
3136 |
cls.objects.update_or_create(slug=data['slug'], group=data['group'], defaults=data) |
|
3137 | ||
3138 |
def export_json(self): |
|
3139 |
return { |
|
3140 |
'label': self.label, |
|
3141 |
'slug': self.slug, |
|
3142 |
'kind': self.kind, |
|
3143 |
} |
|
3144 | ||
3145 | ||
3146 | 3041 |
class Subscription(models.Model): |
3147 | 3042 |
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE, related_name='subscriptions') |
3148 | 3043 |
user_external_id = models.CharField(max_length=250) |
chrono/api/views.py | ||
---|---|---|
380 | 380 |
} |
381 | 381 |
if check_events: |
382 | 382 |
agenda_detail['opened_events_available'] = bool(agenda.get_open_events().filter(full=False)) |
383 |
if agenda.check_type_group: |
|
384 |
agenda_detail['absence_reasons'] = [ |
|
385 |
{'id': r.slug, 'slug': r.slug, 'text': r.label, 'label': r.label} |
|
386 |
for r in agenda.check_type_group.check_types.all() |
|
387 |
if r.kind == 'absence' |
|
388 |
] |
|
389 |
agenda_detail['presence_reasons'] = [ |
|
390 |
{'id': r.slug, 'slug': r.slug, 'text': r.label, 'label': r.label} |
|
391 |
for r in agenda.check_type_group.check_types.all() |
|
392 |
if r.kind == 'presence' |
|
393 |
] |
|
394 | 383 |
elif agenda.accept_meetings(): |
395 | 384 |
agenda_detail['api'] = { |
396 | 385 |
'meetings_url': request.build_absolute_uri( |
... | ... | |
742 | 731 |
def get(self, request, format=None): |
743 | 732 |
agendas_queryset = ( |
744 | 733 |
Agenda.objects.all() |
745 |
.select_related('check_type_group', 'category', 'edit_role', 'view_role', 'events_type')
|
|
746 |
.prefetch_related('resources', 'check_type_group__check_types')
|
|
734 |
.select_related('category', 'edit_role', 'view_role', 'events_type') |
|
735 |
.prefetch_related('resources') |
|
747 | 736 |
.order_by('label') |
748 | 737 |
) |
749 | 738 |
chrono/manager/forms.py | ||
---|---|---|
44 | 44 |
AgendaNotificationsSettings, |
45 | 45 |
AgendaReminderSettings, |
46 | 46 |
Booking, |
47 |
CheckType, |
|
48 |
CheckTypeGroup, |
|
49 | 47 |
Desk, |
50 | 48 |
Event, |
51 | 49 |
EventsType, |
... | ... | |
70 | 68 |
from .widgets import SplitDateTimeField, WeekdaysWidget |
71 | 69 | |
72 | 70 | |
73 |
class CheckTypeForm(forms.ModelForm): |
|
74 |
class Meta: |
|
75 |
model = CheckType |
|
76 |
fields = ['label', 'slug', 'disabled'] |
|
77 | ||
78 |
def clean_slug(self): |
|
79 |
slug = self.cleaned_data['slug'] |
|
80 | ||
81 |
if self.instance.group.check_types.filter(slug=slug).exclude(pk=self.instance.pk).exists(): |
|
82 |
raise ValidationError(_('Another check type exists with the same identifier.')) |
|
83 | ||
84 |
return slug |
|
85 | ||
86 | ||
87 | 71 |
class AgendaAddForm(forms.ModelForm): |
88 | 72 |
edit_role = forms.ModelChoiceField( |
89 | 73 |
label=_('Edit Role'), required=False, queryset=Group.objects.all().order_by('name') |
... | ... | |
1288 | 1272 |
class Meta: |
1289 | 1273 |
model = Agenda |
1290 | 1274 |
fields = [ |
1291 |
'check_type_group', |
|
1292 | 1275 |
'booking_check_filters', |
1293 | 1276 |
'booking_user_block_template', |
1294 | 1277 |
'mark_event_checked_auto', |
... | ... | |
1296 | 1279 |
] |
1297 | 1280 |
widgets = {'booking_user_block_template': forms.Textarea(attrs={'rows': 3})} |
1298 | 1281 | |
1299 |
def __init__(self, *args, **kwargs): |
|
1300 |
super().__init__(*args, **kwargs) |
|
1301 |
if not CheckTypeGroup.objects.exists(): |
|
1302 |
del self.fields['check_type_group'] |
|
1303 |
elif Booking.objects.filter(event__agenda=self.instance, user_check_type_slug__isnull=False).exists(): |
|
1304 |
# not possible to update check_type_group if bookings with non null check_type exist |
|
1305 |
del self.fields['check_type_group'] |
|
1306 | ||
1307 | 1282 | |
1308 | 1283 |
class AgendaNotificationsForm(forms.ModelForm): |
1309 | 1284 |
class Meta: |
... | ... | |
1402 | 1377 |
label=_('Unavailability calendars'), required=False, initial=True |
1403 | 1378 |
) |
1404 | 1379 |
categories = forms.BooleanField(label=_('Categories'), required=False, initial=True) |
1405 |
check_type_groups = forms.BooleanField(label=_('Check type groups'), required=False, initial=True) |
|
1406 | 1380 |
events_types = forms.BooleanField(label=_('Events types'), required=False, initial=True) |
1407 | 1381 | |
1408 | 1382 |
chrono/manager/static/css/style.scss | ||
---|---|---|
74 | 74 |
margin-top: 0; |
75 | 75 |
} |
76 | 76 | |
77 |
.timeperiods .timeperiod a.add::before, |
|
78 |
.check-type-group h3 a.delete::before, |
|
79 |
.check-type-group a.add::before { |
|
77 |
.timeperiods .timeperiod a.add::before { |
|
80 | 78 |
content: "\f055"; /* plus-circle */ |
81 | 79 |
font-family: FontAwesome; |
82 | 80 |
padding-right: 1ex; |
... | ... | |
90 | 88 |
content: "\f0ad"; /* wrench */ |
91 | 89 |
} |
92 | 90 | |
93 |
.check-type-group h3 a.delete { |
|
94 |
width: 1em; |
|
95 |
overflow: hidden; |
|
96 |
&::before { |
|
97 |
content: "\f057"; /* remove-sign */ |
|
98 |
padding-right: 3em; |
|
99 |
} |
|
100 |
} |
|
101 | ||
102 | 91 |
.dayview h2 a, |
103 | 92 |
.monthview h2 a { |
104 | 93 |
padding: 0 1ex; |
chrono/manager/templates/chrono/manager_check_type_form.html | ||
---|---|---|
1 |
{% extends "chrono/manager_check_type_list.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
{% if form.instance.pk %} |
|
7 |
<a href="{% url 'chrono-manager-check-type-edit' form.instance.group_id form.instance.pk %}">{{ form.instance }}</a> |
|
8 |
{% else %} |
|
9 |
<a href="{% url 'chrono-manager-check-type-add' form.instance.group_id %}">{% trans "New check type" %}</a> |
|
10 |
{% endif %} |
|
11 |
{% endblock %} |
|
12 | ||
13 |
{% block appbar %} |
|
14 |
{% if form.instance.pk %} |
|
15 |
<h2>{{ form.instance.group }} - {% trans "Edit check type" %}</h2> |
|
16 |
{% else %} |
|
17 |
<h2>{{ form.instance.group }} - {% trans "New check type" %}</h2> |
|
18 |
{% endif %} |
|
19 |
{% endblock %} |
|
20 | ||
21 |
{% block content %} |
|
22 | ||
23 |
<form method="post" enctype="multipart/form-data"> |
|
24 |
{% csrf_token %} |
|
25 |
{% if is_used %} |
|
26 |
<p> |
|
27 |
{% trans "This check type is set on some existing bookings, modify it with caution." %} |
|
28 |
</p> |
|
29 |
{% endif %} |
|
30 |
{{ form.as_p }} |
|
31 |
<div class="buttons"> |
|
32 |
<button class="submit-button">{% trans "Save" %}</button> |
|
33 |
<a class="cancel" href="{% url 'chrono-manager-check-type-list' %}">{% trans 'Cancel' %}</a> |
|
34 |
</div> |
|
35 |
</form> |
|
36 |
{% endblock %} |
chrono/manager/templates/chrono/manager_check_type_group_form.html | ||
---|---|---|
1 |
{% extends "chrono/manager_check_type_list.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
{% if object.pk %} |
|
7 |
<a href="{% url 'chrono-manager-check-type-group-edit' object.pk %}">{{ object }}</a> |
|
8 |
{% else %} |
|
9 |
<a href="{% url 'chrono-manager-check-type-group-add' %}">{% trans "New check type group" %}</a> |
|
10 |
{% endif %} |
|
11 |
{% endblock %} |
|
12 | ||
13 |
{% block appbar %} |
|
14 |
{% if object.pk %} |
|
15 |
<h2>{% trans "Edit check type group" %}</h2> |
|
16 |
{% else %} |
|
17 |
<h2>{% trans "New check type group" %}</h2> |
|
18 |
{% endif %} |
|
19 |
{% endblock %} |
|
20 | ||
21 |
{% block content %} |
|
22 | ||
23 |
<form method="post" enctype="multipart/form-data"> |
|
24 |
{% csrf_token %} |
|
25 |
{{ form.as_p }} |
|
26 |
<div class="buttons"> |
|
27 |
<button class="submit-button">{% trans "Save" %}</button> |
|
28 |
<a class="cancel" href="{% url 'chrono-manager-check-type-list' %}">{% trans 'Cancel' %}</a> |
|
29 |
</div> |
|
30 |
</form> |
|
31 |
{% endblock %} |
chrono/manager/templates/chrono/manager_check_type_list.html | ||
---|---|---|
1 |
{% extends "chrono/manager_base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block breadcrumb %} |
|
5 |
{{ block.super }} |
|
6 |
<a href="{% url 'chrono-manager-check-type-list' %}">{% trans "Check types" %}</a> |
|
7 |
{% endblock %} |
|
8 | ||
9 |
{% block appbar %} |
|
10 |
<h2>{% trans 'Check types' %}</h2> |
|
11 |
<span class="actions"> |
|
12 |
<a rel="popup" href="{% url 'chrono-manager-check-type-group-add' %}">{% trans 'New group' %}</a> |
|
13 |
</span> |
|
14 |
{% endblock %} |
|
15 | ||
16 |
{% block content %} |
|
17 |
<div class="pk-information"> |
|
18 |
<p>{% trans "Define here check types used in events agendas to check bookings." %}</p> |
|
19 |
</div> |
|
20 |
{% for object in object_list %} |
|
21 |
<div class="section check-type-group"> |
|
22 |
<h3> |
|
23 |
<a rel="popup" href="{% url 'chrono-manager-check-type-group-edit' object.pk %}">{{ object }}</a> |
|
24 |
<span> |
|
25 |
<a class="button" href="{% url 'chrono-manager-check-type-group-export' object.pk %}">{% trans "Export"%}</a> |
|
26 |
<a class="button" rel="popup" href="{% url 'chrono-manager-check-type-group-delete' object.pk %}">{% trans "Delete"%}</a> |
|
27 |
</span> |
|
28 |
</h3> |
|
29 |
<div> |
|
30 |
<ul class="objects-list single-links"> |
|
31 |
{% for check_type in object.check_types.all %} |
|
32 |
{% if check_type.kind == 'absence' %} |
|
33 |
<li> |
|
34 |
<a rel="popup" href="{% url 'chrono-manager-check-type-edit' object.pk check_type.pk %}">{% trans "Absence" %} - {{ check_type }}{% if check_type.disabled %}<span class="extra-info"> ({% trans "disabled" %})</span>{% endif %}</a> |
|
35 |
<a class="delete" rel="popup" href="{% url 'chrono-manager-check-type-delete' object.pk check_type.pk %}">{% trans "delete"%}</a> |
|
36 |
</li> |
|
37 |
{% endif %} |
|
38 |
{% endfor %} |
|
39 |
{% for check_type in object.check_types.all %} |
|
40 |
{% if check_type.kind == 'presence' %} |
|
41 |
<li> |
|
42 |
<a rel="popup" href="{% url 'chrono-manager-check-type-edit' object.pk check_type.pk %}">{% trans "Presence" %} - {{ check_type }}{% if check_type.disabled %}<span class="extra-info"> ({% trans "disabled" %})</span>{% endif %}</a> |
|
43 |
<a class="delete" rel="popup" href="{% url 'chrono-manager-check-type-delete' object.pk check_type.pk %}">{% trans "delete"%}</a> |
|
44 |
</li> |
|
45 |
{% endif %} |
|
46 |
{% endfor %} |
|
47 |
<li><a class="add" rel="popup" href="{% url 'chrono-manager-check-type-add' object.pk %}">{% trans "Add a check type" %}</a></li> |
|
48 |
</ul> |
|
49 |
</div> |
|
50 |
</div> |
|
51 |
{% empty %} |
|
52 |
<div class="big-msg-info"> |
|
53 |
{% blocktrans %} |
|
54 |
This site doesn't have any check type group yet. Click on the "New group" button in the top |
|
55 |
right of the page to add a first one. |
|
56 |
{% endblocktrans %} |
|
57 |
</div> |
|
58 |
{% endfor %} |
|
59 |
{% endblock %} |
chrono/manager/templates/chrono/manager_events_agenda_settings.html | ||
---|---|---|
36 | 36 |
</h3> |
37 | 37 |
<div> |
38 | 38 |
<ul> |
39 |
{% if has_check_types %} |
|
40 |
{% if agenda.check_type_group %} |
|
41 |
<li>{% trans "Check type group:" %} {{ agenda.check_type_group }} |
|
42 |
<ul> |
|
43 |
<li>{% trans "Absences:" %} |
|
44 |
<ul> |
|
45 |
{% for check_type in agenda.check_type_group.check_types.absences %} |
|
46 |
<li>{{ check_type }}</li> |
|
47 |
{% empty %} |
|
48 |
<li>({% trans "No absence check type defined" %})</li> |
|
49 |
{% endfor %} |
|
50 |
</ul> |
|
51 |
</li> |
|
52 |
<li>{% trans "Presences:" %} |
|
53 |
<ul> |
|
54 |
{% for check_type in agenda.check_type_group.check_types.presences %} |
|
55 |
<li>{{ check_type }}</li> |
|
56 |
{% empty %} |
|
57 |
<li>({% trans "No presence check type defined" %})</li> |
|
58 |
{% endfor %} |
|
59 |
</ul> |
|
60 |
</li> |
|
61 |
</ul> |
|
62 |
</li> |
|
63 |
{% else %} |
|
64 |
<li>{% trans "No check types configured for this agenda." %}</li> |
|
65 |
{% endif %} |
|
66 |
{% endif %} |
|
67 | ||
68 | 39 |
{% with agenda.get_booking_check_filters as check_filters %} |
69 | 40 |
{% if check_filters %} |
70 | 41 |
<li>{% trans "Filters:" %} |
chrono/manager/templates/chrono/manager_home.html | ||
---|---|---|
11 | 11 |
<li><a rel="popup" href="{% url 'chrono-manager-agendas-import' %}">{% trans 'Import' %}</a></li> |
12 | 12 |
<li><a rel="popup" href="{% url 'chrono-manager-agendas-export' %}" data-autoclose-dialog="true">{% trans 'Export' %}</a></li> |
13 | 13 |
<li><a href="{% url 'chrono-manager-events-type-list' %}">{% trans 'Events types' %}</a></li> |
14 |
<li><a href="{% url 'chrono-manager-check-type-list' %}">{% trans 'Check types' %}</a></li> |
|
15 | 14 |
{% endif %} |
16 | 15 |
{% if has_access_to_unavailability_calendars %} |
17 | 16 |
<li><a href="{% url 'chrono-manager-unavailability-calendar-list' %}">{% trans 'Unavailability calendars' %}</a></li> |
chrono/manager/urls.py | ||
---|---|---|
84 | 84 |
url(r'^category/add/$', views.category_add, name='chrono-manager-category-add'), |
85 | 85 |
url(r'^category/(?P<pk>\d+)/edit/$', views.category_edit, name='chrono-manager-category-edit'), |
86 | 86 |
url(r'^category/(?P<pk>\d+)/delete/$', views.category_delete, name='chrono-manager-category-delete'), |
87 |
url(r'^check-types/$', views.check_type_list, name='chrono-manager-check-type-list'), |
|
88 |
url( |
|
89 |
r'^check-type/group/add/$', |
|
90 |
views.check_type_group_add, |
|
91 |
name='chrono-manager-check-type-group-add', |
|
92 |
), |
|
93 |
url( |
|
94 |
r'^check-type/group/(?P<pk>\d+)/edit/$', |
|
95 |
views.check_type_group_edit, |
|
96 |
name='chrono-manager-check-type-group-edit', |
|
97 |
), |
|
98 |
url( |
|
99 |
r'^check-type/group/(?P<pk>\d+)/delete/$', |
|
100 |
views.check_type_group_delete, |
|
101 |
name='chrono-manager-check-type-group-delete', |
|
102 |
), |
|
103 |
url( |
|
104 |
r'^check-type/group/(?P<pk>\d+)/export/$', |
|
105 |
views.check_type_group_export, |
|
106 |
name='chrono-manager-check-type-group-export', |
|
107 |
), |
|
108 |
url( |
|
109 |
r'^check-type/group/(?P<group_pk>\d+)/add/$', |
|
110 |
views.check_type_add, |
|
111 |
name='chrono-manager-check-type-add', |
|
112 |
), |
|
113 |
url( |
|
114 |
r'^check-type/group/(?P<group_pk>\d+)/(?P<pk>\d+)/edit/$', |
|
115 |
views.check_type_edit, |
|
116 |
name='chrono-manager-check-type-edit', |
|
117 |
), |
|
118 |
url( |
|
119 |
r'^check-type/group/(?P<group_pk>\d+)/(?P<pk>\d+)/delete/$', |
|
120 |
views.check_type_delete, |
|
121 |
name='chrono-manager-check-type-delete', |
|
122 |
), |
|
123 | 87 |
url(r'^events-types/$', views.events_type_list, name='chrono-manager-events-type-list'), |
124 | 88 |
url(r'^events-type/add/$', views.events_type_add, name='chrono-manager-events-type-add'), |
125 | 89 |
url(r'^events-type/(?P<pk>\d+)/edit/$', views.events_type_edit, name='chrono-manager-events-type-edit'), |
chrono/manager/utils.py | ||
---|---|---|
25 | 25 |
Agenda, |
26 | 26 |
AgendaImportError, |
27 | 27 |
Category, |
28 |
CheckTypeGroup, |
|
29 | 28 |
EventsType, |
30 | 29 |
Resource, |
31 | 30 |
UnavailabilityCalendar, |
... | ... | |
35 | 34 |
def export_site( |
36 | 35 |
agendas=True, |
37 | 36 |
unavailability_calendars=True, |
38 |
check_type_groups=True, |
|
39 | 37 |
events_types=True, |
40 | 38 |
resources=True, |
41 | 39 |
categories=True, |
... | ... | |
48 | 46 |
data['resources'] = [x.export_json() for x in Resource.objects.all()] |
49 | 47 |
if events_types: |
50 | 48 |
data['events_types'] = [x.export_json() for x in EventsType.objects.all()] |
51 |
if check_type_groups: |
|
52 |
data['check_type_groups'] = [x.export_json() for x in CheckTypeGroup.objects.all()] |
|
53 | 49 |
if unavailability_calendars: |
54 | 50 |
data['unavailability_calendars'] = [x.export_json() for x in UnavailabilityCalendar.objects.all()] |
55 | 51 |
if agendas: |
... | ... | |
64 | 60 |
if if_empty and ( |
65 | 61 |
Agenda.objects.exists() |
66 | 62 |
or UnavailabilityCalendar.objects.exists() |
67 |
or CheckTypeGroup.objects.exists() |
|
68 | 63 |
or EventsType.objects.exists() |
69 | 64 |
or Resource.objects.exists() |
70 | 65 |
or Category.objects.exists() |
... | ... | |
74 | 69 |
if clean: |
75 | 70 |
Agenda.objects.all().delete() |
76 | 71 |
UnavailabilityCalendar.objects.all().delete() |
77 |
CheckTypeGroup.objects.all().delete() |
|
78 | 72 |
EventsType.objects.all().delete() |
79 | 73 |
Resource.objects.all().delete() |
80 | 74 |
Category.objects.all().delete() |
... | ... | |
84 | 78 |
for key in [ |
85 | 79 |
'agendas', |
86 | 80 |
'unavailability_calendars', |
87 |
'check_type_groups', |
|
88 | 81 |
'events_types', |
89 | 82 |
'resources', |
90 | 83 |
'categories', |
... | ... | |
108 | 101 |
(Category, 'categories'), |
109 | 102 |
(Resource, 'resources'), |
110 | 103 |
(EventsType, 'events_types'), |
111 |
(CheckTypeGroup, 'check_type_groups'), |
|
112 | 104 |
(UnavailabilityCalendar, 'unavailability_calendars'), |
113 | 105 |
(Agenda, 'agendas'), |
114 | 106 |
): |
chrono/manager/views.py | ||
---|---|---|
67 | 67 |
Booking, |
68 | 68 |
BookingColor, |
69 | 69 |
Category, |
70 |
CheckType, |
|
71 |
CheckTypeGroup, |
|
72 | 70 |
Desk, |
73 | 71 |
Event, |
74 | 72 |
EventCancellationReport, |
... | ... | |
105 | 103 |
BookingCheckAbsenceForm, |
106 | 104 |
BookingCheckFilterSet, |
107 | 105 |
BookingCheckPresenceForm, |
108 |
CheckTypeForm, |
|
109 | 106 |
CustomFieldFormSet, |
110 | 107 |
DeskExceptionsImportForm, |
111 | 108 |
DeskForm, |
... | ... | |
655 | 652 |
category_delete = CategoryDeleteView.as_view() |
656 | 653 | |
657 | 654 | |
658 |
class CheckTypeListView(ListView): |
|
659 |
template_name = 'chrono/manager_check_type_list.html' |
|
660 |
model = CheckTypeGroup |
|
661 | ||
662 |
def dispatch(self, request, *args, **kwargs): |
|
663 |
if not request.user.is_staff: |
|
664 |
raise PermissionDenied() |
|
665 |
return super().dispatch(request, *args, **kwargs) |
|
666 | ||
667 |
def get_queryset(self): |
|
668 |
return CheckTypeGroup.objects.prefetch_related('check_types') |
|
669 | ||
670 | ||
671 |
check_type_list = CheckTypeListView.as_view() |
|
672 | ||
673 | ||
674 |
class CheckTypeGroupAddView(CreateView): |
|
675 |
template_name = 'chrono/manager_check_type_group_form.html' |
|
676 |
model = CheckTypeGroup |
|
677 |
fields = ['label'] |
|
678 | ||
679 |
def dispatch(self, request, *args, **kwargs): |
|
680 |
if not request.user.is_staff: |
|
681 |
raise PermissionDenied() |
|
682 |
return super().dispatch(request, *args, **kwargs) |
|
683 | ||
684 |
def get_success_url(self): |
|
685 |
return reverse('chrono-manager-check-type-list') |
|
686 | ||
687 | ||
688 |
check_type_group_add = CheckTypeGroupAddView.as_view() |
|
689 | ||
690 | ||
691 |
class CheckTypeGroupEditView(UpdateView): |
|
692 |
template_name = 'chrono/manager_check_type_group_form.html' |
|
693 |
model = CheckTypeGroup |
|
694 |
fields = ['label', 'slug'] |
|
695 | ||
696 |
def dispatch(self, request, *args, **kwargs): |
|
697 |
if not request.user.is_staff: |
|
698 |
raise PermissionDenied() |
|
699 |
return super().dispatch(request, *args, **kwargs) |
|
700 | ||
701 |
def get_success_url(self): |
|
702 |
return reverse('chrono-manager-check-type-list') |
|
703 | ||
704 | ||
705 |
check_type_group_edit = CheckTypeGroupEditView.as_view() |
|
706 | ||
707 | ||
708 |
class CheckTypeGroupDeleteView(DeleteView): |
|
709 |
template_name = 'chrono/manager_confirm_delete.html' |
|
710 |
model = CheckTypeGroup |
|
711 | ||
712 |
def dispatch(self, request, *args, **kwargs): |
|
713 |
if not request.user.is_staff: |
|
714 |
raise PermissionDenied() |
|
715 |
return super().dispatch(request, *args, **kwargs) |
|
716 | ||
717 |
def get_success_url(self): |
|
718 |
return reverse('chrono-manager-check-type-list') |
|
719 | ||
720 | ||
721 |
check_type_group_delete = CheckTypeGroupDeleteView.as_view() |
|
722 | ||
723 | ||
724 |
class CheckTypeGroupExport(DetailView): |
|
725 |
model = CheckTypeGroup |
|
726 | ||
727 |
def dispatch(self, request, *args, **kwargs): |
|
728 |
if not request.user.is_staff: |
|
729 |
raise PermissionDenied() |
|
730 |
return super().dispatch(request, *args, **kwargs) |
|
731 | ||
732 |
def get(self, request, *args, **kwargs): |
|
733 |
response = HttpResponse(content_type='application/json') |
|
734 |
today = datetime.date.today() |
|
735 |
attachment = 'attachment; filename="export_check_type_group_{}_{}.json"'.format( |
|
736 |
self.get_object().slug, today.strftime('%Y%m%d') |
|
737 |
) |
|
738 |
response['Content-Disposition'] = attachment |
|
739 |
json.dump({'check_type_groups': [self.get_object().export_json()]}, response, indent=2) |
|
740 |
return response |
|
741 | ||
742 | ||
743 |
check_type_group_export = CheckTypeGroupExport.as_view() |
|
744 | ||
745 | ||
746 |
class CheckTypeAddView(CreateView): |
|
747 |
template_name = 'chrono/manager_check_type_form.html' |
|
748 |
model = CheckType |
|
749 |
fields = ['label', 'kind'] |
|
750 | ||
751 |
def dispatch(self, request, *args, **kwargs): |
|
752 |
self.group_pk = kwargs.pop('group_pk') |
|
753 |
if not request.user.is_staff: |
|
754 |
raise PermissionDenied() |
|
755 |
return super().dispatch(request, *args, **kwargs) |
|
756 | ||
757 |
def get_form_kwargs(self): |
|
758 |
kwargs = super().get_form_kwargs() |
|
759 |
if not kwargs.get('instance'): |
|
760 |
kwargs['instance'] = self.model() |
|
761 |
kwargs['instance'].group_id = self.group_pk |
|
762 |
return kwargs |
|
763 | ||
764 |
def get_success_url(self): |
|
765 |
return reverse('chrono-manager-check-type-list') |
|
766 | ||
767 | ||
768 |
check_type_add = CheckTypeAddView.as_view() |
|
769 | ||
770 | ||
771 |
class CheckTypeEditView(UpdateView): |
|
772 |
template_name = 'chrono/manager_check_type_form.html' |
|
773 |
model = CheckType |
|
774 |
form_class = CheckTypeForm |
|
775 | ||
776 |
def dispatch(self, request, *args, **kwargs): |
|
777 |
self.group_pk = kwargs.pop('group_pk') |
|
778 |
if not request.user.is_staff: |
|
779 |
raise PermissionDenied() |
|
780 |
return super().dispatch(request, *args, **kwargs) |
|
781 | ||
782 |
def get_queryset(self): |
|
783 |
return CheckType.objects.filter(group=self.group_pk) |
|
784 | ||
785 |
def get_context_data(self, **kwargs): |
|
786 |
context = super().get_context_data(**kwargs) |
|
787 |
context['is_used'] = Booking.objects.filter(user_check_type_slug=self.get_object().slug).exists() |
|
788 |
return context |
|
789 | ||
790 |
def get_success_url(self): |
|
791 |
return reverse('chrono-manager-check-type-list') |
|
792 | ||
793 | ||
794 |
check_type_edit = CheckTypeEditView.as_view() |
|
795 | ||
796 | ||
797 |
class CheckTypeDeleteView(DeleteView): |
|
798 |
template_name = 'chrono/manager_confirm_delete.html' |
|
799 |
model = CheckType |
|
800 | ||
801 |
def dispatch(self, request, *args, **kwargs): |
|
802 |
self.group_pk = kwargs.pop('group_pk') |
|
803 |
if not request.user.is_staff: |
|
804 |
raise PermissionDenied() |
|
805 |
return super().dispatch(request, *args, **kwargs) |
|
806 | ||
807 |
def get_queryset(self): |
|
808 |
return CheckType.objects.filter(group=self.group_pk) |
|
809 | ||
810 |
def get_context_data(self, **kwargs): |
|
811 |
context = super().get_context_data(**kwargs) |
|
812 |
context['cannot_delete'] = Booking.objects.filter( |
|
813 |
user_check_type_slug=self.get_object().slug |
|
814 |
).exists() |
|
815 |
context['cannot_delete_msg'] = _( |
|
816 |
'Can not delete this check type: it is set on some existing bookings.' |
|
817 |
) |
|
818 |
return context |
|
819 | ||
820 |
def delete(self, request, *args, **kwargs): |
|
821 |
if Booking.objects.filter(user_check_type_slug=self.get_object().slug).exists(): |
|
822 |
raise Http404 |
|
823 |
return super().delete(request, *args, **kwargs) |
|
824 | ||
825 |
def get_success_url(self): |
|
826 |
return reverse('chrono-manager-check-type-list') |
|
827 | ||
828 | ||
829 |
check_type_delete = CheckTypeDeleteView.as_view() |
|
830 | ||
831 | ||
832 | 655 |
class EventsTypeListView(ListView): |
833 | 656 |
template_name = 'chrono/manager_events_type_list.html' |
834 | 657 |
model = EventsType |
... | ... | |
1002 | 825 |
x, |
1003 | 826 |
), |
1004 | 827 |
}, |
1005 |
'check_type_groups': { |
|
1006 |
'create_noop': _('No check type group created.'), |
|
1007 |
'create': lambda x: ungettext( |
|
1008 |
'A check type group has been created.', |
|
1009 |
'%(count)d check type groups have been created.', |
|
1010 |
x, |
|
1011 |
), |
|
1012 |
'update_noop': _('No check type group updated.'), |
|
1013 |
'update': lambda x: ungettext( |
|
1014 |
'A check type group has been updated.', |
|
1015 |
'%(count)d check type groups have been updated.', |
|
1016 |
x, |
|
1017 |
), |
|
1018 |
}, |
|
1019 | 828 |
'events_types': { |
1020 | 829 |
'create_noop': _('No events type created.'), |
1021 | 830 |
'create': lambda x: ungettext( |
... | ... | |
1078 | 887 | |
1079 | 888 |
obj_results['messages'] = "%s %s" % (message1, message2) |
1080 | 889 | |
1081 |
a_count, uc_count, arg_count = (
|
|
890 |
a_count, uc_count = ( |
|
1082 | 891 |
len(results['agendas']['all']), |
1083 | 892 |
len(results['unavailability_calendars']['all']), |
1084 |
len(results['check_type_groups']['all']), |
|
1085 | 893 |
) |
1086 |
if (a_count, uc_count, arg_count) == (1, 0, 0):
|
|
894 |
if (a_count, uc_count) == (1, 0):
|
|
1087 | 895 |
# only one agenda imported, redirect to settings page |
1088 | 896 |
return HttpResponseRedirect( |
1089 | 897 |
reverse('chrono-manager-agenda-settings', kwargs={'pk': results['agendas']['all'][0].pk}) |
1090 | 898 |
) |
1091 |
if (a_count, uc_count, arg_count) == (0, 1, 0):
|
|
899 |
if (a_count, uc_count) == (0, 1):
|
|
1092 | 900 |
# only one unavailability calendar imported, redirect to settings page |
1093 | 901 |
return HttpResponseRedirect( |
1094 | 902 |
reverse( |
... | ... | |
1096 | 904 |
kwargs={'pk': results['unavailability_calendars']['all'][0].pk}, |
1097 | 905 |
) |
1098 | 906 |
) |
1099 |
if (a_count, uc_count, arg_count) == (0, 0, 1): |
|
1100 |
# only one check type group imported, redirect to check type page |
|
1101 |
return HttpResponseRedirect(reverse('chrono-manager-check-type-list')) |
|
1102 | 907 | |
1103 | 908 |
if global_noop: |
1104 | 909 |
messages.info(self.request, _('No data found.')) |
1105 | 910 |
else: |
1106 | 911 |
messages.info(self.request, results['agendas']['messages']) |
1107 | 912 |
messages.info(self.request, results['unavailability_calendars']['messages']) |
1108 |
messages.info(self.request, results['check_type_groups']['messages']) |
|
1109 | 913 |
messages.info(self.request, results['events_types']['messages']) |
1110 | 914 |
messages.info(self.request, results['resources']['messages']) |
1111 | 915 |
messages.info(self.request, results['categories']['messages']) |
... | ... | |
1828 | 1632 | |
1829 | 1633 |
def set_agenda(self, **kwargs): |
1830 | 1634 |
self.agenda = get_object_or_404( |
1831 |
Agenda.objects.select_related('edit_role', 'view_role', 'check_type_group'),
|
|
1635 |
Agenda.objects.select_related('edit_role', 'view_role'), |
|
1832 | 1636 |
pk=kwargs.get('pk'), |
1833 | 1637 |
) |
1834 | 1638 | |
... | ... | |
1855 | 1659 |
else False |
1856 | 1660 |
) |
1857 | 1661 |
if self.agenda.kind == 'events': |
1858 |
context['has_check_types'] = CheckTypeGroup.objects.exists() |
|
1859 | 1662 |
context['has_recurring_events'] = self.agenda.event_set.filter( |
1860 | 1663 |
recurrence_days__isnull=False |
1861 | 1664 |
).exists() |
... | ... | |
2299 | 2102 | |
2300 | 2103 |
def set_agenda(self, **kwargs): |
2301 | 2104 |
self.agenda = get_object_or_404( |
2302 |
Agenda.objects.prefetch_related('check_type_group__check_types'),
|
|
2105 |
Agenda, |
|
2303 | 2106 |
pk=kwargs.get('pk'), |
2304 | 2107 |
kind='events', |
2305 | 2108 |
) |
tests/api/test_agenda.py | ||
---|---|---|
10 | 10 |
Agenda, |
11 | 11 |
Booking, |
12 | 12 |
Category, |
13 |
CheckType, |
|
14 |
CheckTypeGroup, |
|
15 | 13 |
Desk, |
16 | 14 |
Event, |
17 | 15 |
EventsType, |
... | ... | |
27 | 25 |
view_group = Group.objects.create(name='View') |
28 | 26 |
category_a = Category.objects.create(label='Category A') |
29 | 27 |
category_b = Category.objects.create(label='Category B') |
30 |
group = CheckTypeGroup.objects.create(label='Foo') |
|
31 |
reason = CheckType.objects.create(group=group, label='Foo bar') |
|
32 |
reason2 = CheckType.objects.create(group=group, label='Foo bar baz') |
|
33 |
reason3 = CheckType.objects.create(group=group, label='Foo bar baz presence', kind='presence') |
|
34 | 28 |
events_type = EventsType.objects.create(label='Type A') |
35 | 29 |
events_type2 = EventsType.objects.create(label='Type B') |
36 | 30 |
event_agenda = Agenda.objects.create( |
37 | 31 |
label='Foo bar', |
38 | 32 |
category=category_a, |
39 |
check_type_group=group, |
|
40 | 33 |
events_type=events_type, |
41 | 34 |
edit_role=edit_group, |
42 | 35 |
) |
43 | 36 |
Desk.objects.create(agenda=event_agenda, slug='_exceptions_holder') |
44 | 37 |
event_agenda2 = Agenda.objects.create(label='Foo bar 2', category=category_a, events_type=events_type2) |
45 | 38 |
Desk.objects.create(agenda=event_agenda2, slug='_exceptions_holder') |
46 |
event_agenda3 = Agenda.objects.create(label='Foo bar 3', check_type_group=group)
|
|
39 |
event_agenda3 = Agenda.objects.create(label='Foo bar 3') |
|
47 | 40 |
Desk.objects.create(agenda=event_agenda3, slug='_exceptions_holder') |
48 | 41 |
meetings_agenda1 = Agenda.objects.create( |
49 | 42 |
label='Foo bar Meeting', kind='meetings', category=category_b, view_role=view_group |
... | ... | |
78 | 71 |
'category': 'category-a', |
79 | 72 |
'category_label': 'Category A', |
80 | 73 |
'events_type': 'type-a', |
81 |
'absence_reasons': [ |
|
82 |
{'id': reason.slug, 'slug': reason.slug, 'text': reason.label, 'label': reason.label}, |
|
83 |
{'id': reason2.slug, 'slug': reason2.slug, 'text': reason2.label, 'label': reason2.label}, |
|
84 |
], |
|
85 |
'presence_reasons': [ |
|
86 |
{'id': reason3.slug, 'slug': reason3.slug, 'text': reason3.label, 'label': reason3.label}, |
|
87 |
], |
|
88 | 74 |
'api': { |
89 | 75 |
'datetimes_url': 'http://testserver/api/agenda/foo-bar/datetimes/', |
90 | 76 |
'fillslots_url': 'http://testserver/api/agenda/foo-bar/fillslots/', |
... | ... | |
123 | 109 |
'category': None, |
124 | 110 |
'category_label': None, |
125 | 111 |
'events_type': None, |
126 |
'absence_reasons': [ |
|
127 |
{'id': reason.slug, 'slug': reason.slug, 'text': reason.label, 'label': reason.label}, |
|
128 |
{'id': reason2.slug, 'slug': reason2.slug, 'text': reason2.label, 'label': reason2.label}, |
|
129 |
], |
|
130 |
'presence_reasons': [ |
|
131 |
{'id': reason3.slug, 'slug': reason3.slug, 'text': reason3.label, 'label': reason3.label}, |
|
132 |
], |
|
133 | 112 |
'api': { |
134 | 113 |
'datetimes_url': 'http://testserver/api/agenda/foo-bar-3/datetimes/', |
135 | 114 |
'fillslots_url': 'http://testserver/api/agenda/foo-bar-3/fillslots/', |
... | ... | |
209 | 188 | |
210 | 189 |
with CaptureQueriesContext(connection) as ctx: |
211 | 190 |
resp = app.get('/api/agenda/') |
212 |
assert len(ctx.captured_queries) == 4
|
|
191 |
assert len(ctx.captured_queries) == 3
|
|
213 | 192 |
with CaptureQueriesContext(connection) as ctx: |
214 | 193 |
resp = app.get('/api/agenda/', params={'q': 'MEET'}) |
215 | 194 |
assert len(ctx.captured_queries) == 2 |
... | ... | |
328 | 307 | |
329 | 308 |
with CaptureQueriesContext(connection) as ctx: |
330 | 309 |
resp = app.get('/api/agenda/', params={'with_open_events': '1'}) |
331 |
assert len(ctx.captured_queries) == 4
|
|
310 |
assert len(ctx.captured_queries) == 3
|
|
332 | 311 | |
333 | 312 | |
334 | 313 |
def test_agenda_detail_api(app): |
tests/manager/test_check_type.py | ||
---|---|---|
1 |
import pytest |
|
2 |
from django.utils.timezone import now |
|
3 | ||
4 |
from chrono.agendas.models import Agenda, Booking, CheckType, CheckTypeGroup, Desk, Event |
|
5 |
from tests.utils import login |
|
6 | ||
7 |
pytestmark = pytest.mark.django_db |
|
8 | ||
9 | ||
10 |
@pytest.fixture |
|
11 |
def agenda_with_restrictions(manager_user): |
|
12 |
agenda = Agenda(label='Foo Bar') |
|
13 |
agenda.view_role = manager_user.groups.all()[0] |
|
14 |
agenda.save() |
|
15 |
return agenda |
|
16 | ||
17 | ||
18 |
def test_list_types_as_manager(app, manager_user, agenda_with_restrictions): |
|
19 |
app = login(app, username='manager', password='manager') |
|
20 |
app.get('/manage/check-types/', status=403) |
|
21 | ||
22 |
resp = app.get('/manage/') |
|
23 |
assert 'Check types' not in resp.text |
|
24 | ||
25 | ||
26 |
def test_add_group(app, admin_user): |
|
27 |
app = login(app) |
|
28 |
resp = app.get('/manage/') |
|
29 |
resp = resp.click('Check types') |
|
30 |
resp = resp.click('New group') |
|
31 |
resp.form['label'] = 'Foo bar' |
|
32 |
resp = resp.form.submit() |
|
33 |
group = CheckTypeGroup.objects.latest('pk') |
|
34 |
assert resp.location.endswith('/manage/check-types/') |
|
35 |
assert group.label == 'Foo bar' |
|
36 |
assert group.slug == 'foo-bar' |
|
37 | ||
38 | ||
39 |
def test_add_group_as_manager(app, manager_user, agenda_with_restrictions): |
|
40 |
app = login(app, username='manager', password='manager') |
|
41 |
app.get('/manage/check-type/group/add/', status=403) |
|
42 | ||
43 | ||
44 |
def test_edit_group(app, admin_user): |
|
45 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
46 |
group2 = CheckTypeGroup.objects.create(label='baz') |
|
47 | ||
48 |
app = login(app) |
|
49 |
resp = app.get('/manage/check-types/') |
|
50 |
resp = resp.click(href='/manage/check-type/group/%s/edit/' % group.pk) |
|
51 |
resp.form['label'] = 'Foo bar baz' |
|
52 |
resp.form['slug'] = group2.slug |
|
53 |
resp = resp.form.submit() |
|
54 |
assert resp.context['form'].errors['slug'] == ['Check type group with this Identifier already exists.'] |
|
55 | ||
56 |
resp.form['slug'] = 'baz2' |
|
57 |
resp = resp.form.submit() |
|
58 |
assert resp.location.endswith('/manage/check-types/') |
|
59 |
group.refresh_from_db() |
|
60 |
assert group.label == 'Foo bar baz' |
|
61 |
assert group.slug == 'baz2' |
|
62 | ||
63 | ||
64 |
def test_edit_group_as_manager(app, manager_user, agenda_with_restrictions): |
|
65 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
66 | ||
67 |
app = login(app, username='manager', password='manager') |
|
68 |
app.get('/manage/check-type/group/%s/edit/' % group.pk, status=403) |
|
69 | ||
70 | ||
71 |
def test_delete_group(app, admin_user): |
|
72 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
73 |
CheckType.objects.create(label='Foo reason', group=group) |
|
74 |
agenda = Agenda.objects.create(label='Foo bar') |
|
75 |
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10) |
|
76 |
Booking.objects.create(event=event) |
|
77 | ||
78 |
app = login(app) |
|
79 |
resp = app.get('/manage/check-types/') |
|
80 |
resp = resp.click(href='/manage/check-type/group/%s/delete/' % group.pk) |
|
81 |
resp = resp.form.submit() |
|
82 |
assert resp.location.endswith('/manage/check-types/') |
|
83 |
assert CheckTypeGroup.objects.exists() is False |
|
84 |
assert CheckType.objects.exists() is False |
|
85 | ||
86 | ||
87 |
def test_delete_group_as_manager(app, manager_user, agenda_with_restrictions): |
|
88 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
89 | ||
90 |
app = login(app, username='manager', password='manager') |
|
91 |
app.get('/manage/check-type/group/%s/delete/' % group.pk, status=403) |
|
92 | ||
93 | ||
94 |
def test_add_check_type(app, admin_user): |
|
95 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
96 | ||
97 |
app = login(app) |
|
98 |
resp = app.get('/manage/') |
|
99 |
resp = resp.click('Check types') |
|
100 |
resp = resp.click('Add a check type') |
|
101 |
resp.form['label'] = 'Foo reason' |
|
102 |
assert 'slug' not in resp.context['form'].fields |
|
103 |
assert 'disabled' not in resp.context['form'].fields |
|
104 |
resp = resp.form.submit() |
|
105 |
check_type = CheckType.objects.latest('pk') |
|
106 |
assert resp.location.endswith('/manage/check-types/') |
|
107 |
assert check_type.label == 'Foo reason' |
|
108 |
assert check_type.group == group |
|
109 |
assert check_type.slug == 'foo-reason' |
|
110 |
assert check_type.kind == 'absence' |
|
111 | ||
112 |
resp = app.get('/manage/check-type/group/%s/add/' % group.pk) |
|
113 |
resp.form['label'] = 'Foo reason' |
|
114 |
resp.form['kind'] = 'presence' |
|
115 |
resp = resp.form.submit() |
|
116 |
assert resp.location.endswith('/manage/check-types/') |
|
117 |
check_type = CheckType.objects.latest('pk') |
|
118 |
assert check_type.label == 'Foo reason' |
|
119 |
assert check_type.slug == 'foo-reason-1' |
|
120 |
assert check_type.kind == 'presence' |
|
121 | ||
122 | ||
123 |
def test_add_check_type_as_manager(app, manager_user, agenda_with_restrictions): |
|
124 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
125 | ||
126 |
app = login(app, username='manager', password='manager') |
|
127 |
app.get('/manage/check-type/group/%s/add/' % group.pk, status=403) |
|
128 | ||
129 | ||
130 |
def test_edit_check_type(app, admin_user): |
|
131 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
132 |
check_type = CheckType.objects.create(label='Foo reason', group=group, kind='presence') |
|
133 |
check_type2 = CheckType.objects.create(label='Baz', group=group) |
|
134 |
group2 = CheckTypeGroup.objects.create(label='Foo bar') |
|
135 |
check_type3 = CheckType.objects.create(label='Foo bar reason', group=group2) |
|
136 |
agenda = Agenda.objects.create(label='Foo bar') |
|
137 |
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10) |
|
138 |
Booking.objects.create(event=event) |
|
139 | ||
140 |
app = login(app) |
|
141 |
resp = app.get('/manage/check-types/') |
|
142 |
resp = resp.click(href='/manage/check-type/group/%s/%s/edit/' % (group.pk, check_type.pk)) |
|
143 |
assert 'This check type is set on some existing bookings, modify it with caution.' not in resp |
|
144 |
resp.form['label'] = 'Foo bar reason' |
|
145 |
resp.form['slug'] = check_type2.slug |
|
146 |
resp.form['disabled'] = True |
|
147 |
assert 'kind' not in resp.context['form'].fields |
|
148 |
resp = resp.form.submit() |
|
149 |
assert resp.context['form'].errors['slug'] == ['Another check type exists with the same identifier.'] |
|
150 | ||
151 |
resp.form['slug'] = check_type3.slug |
|
152 |
resp = resp.form.submit() |
|
153 |
assert resp.location.endswith('/manage/check-types/') |
|
154 |
check_type.refresh_from_db() |
|
155 |
assert check_type.label == 'Foo bar reason' |
|
156 |
assert check_type.slug == 'foo-bar-reason' |
|
157 |
assert check_type.kind == 'presence' |
|
158 |
assert check_type.disabled is True |
|
159 | ||
160 |
# check_type is used |
|
161 |
Booking.objects.update(user_check_type_slug=check_type.slug) |
|
162 |
resp = app.get('/manage/check-type/group/%s/%s/edit/' % (group.pk, check_type.pk)) |
|
163 |
assert 'This check type is set on some existing bookings, modify it with caution.' in resp |
|
164 | ||
165 |
app.get('/manage/check-type/group/%s/%s/edit/' % (group2.pk, check_type.pk), status=404) |
|
166 | ||
167 | ||
168 |
def test_edit_check_type_as_manager(app, manager_user, agenda_with_restrictions): |
|
169 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
170 |
check_type = CheckType.objects.create(label='Foo reason', group=group) |
|
171 | ||
172 |
app = login(app, username='manager', password='manager') |
|
173 |
app.get('/manage/check-type/group/%s/%s/edit/' % (group.pk, check_type.pk), status=403) |
|
174 | ||
175 | ||
176 |
def test_delete_check_type(app, admin_user): |
|
177 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
178 |
check_type = CheckType.objects.create(label='Foo reason', group=group) |
|
179 |
agenda = Agenda.objects.create(label='Foo bar') |
|
180 |
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10) |
|
181 |
Booking.objects.create(event=event) |
|
182 | ||
183 |
app = login(app) |
|
184 |
resp = app.get('/manage/check-types/') |
|
185 |
resp = resp.click(href='/manage/check-type/group/%s/%s/delete/' % (group.pk, check_type.pk)) |
|
186 |
resp = resp.form.submit() |
|
187 |
assert resp.location.endswith('/manage/check-types/') |
|
188 |
assert CheckTypeGroup.objects.exists() is True |
|
189 |
assert CheckType.objects.exists() is False |
|
190 | ||
191 |
# check_type is used |
|
192 |
check_type = CheckType.objects.create(label='Foo reason', group=group) |
|
193 |
Booking.objects.update(user_check_type_slug=check_type.slug) |
|
194 |
resp = app.get('/manage/check-type/group/%s/%s/delete/' % (group.pk, check_type.pk)) |
|
195 |
assert 'Can not delete this check type: it is set on some existing bookings.' in resp |
|
196 |
resp.form.submit(status=404) |
|
197 | ||
198 |
group2 = CheckTypeGroup.objects.create(label='Foo bar baz') |
|
199 |
app.get('/manage/check-type/group/%s/%s/delete/' % (group2.pk, check_type.pk), status=404) |
|
200 | ||
201 | ||
202 |
def test_delete_check_type_as_manager(app, manager_user, agenda_with_restrictions): |
|
203 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
204 |
check_type = CheckType.objects.create(label='Foo reason', group=group) |
|
205 | ||
206 |
app = login(app, username='manager', password='manager') |
|
207 |
app.get('/manage/check-type/group/%s/%s/delete/' % (group.pk, check_type.pk), status=403) |
|
208 | ||
209 | ||
210 |
def test_meetings_agenda_group(app, admin_user): |
|
211 |
agenda = Agenda.objects.create(label='Foo bar', kind='meetings') |
|
212 |
CheckTypeGroup.objects.create(label='Foo bar') |
|
213 | ||
214 |
app = login(app) |
|
215 |
resp = app.get('/manage/agendas/%s/settings' % agenda.pk) |
|
216 |
assert 'has_check_types' not in resp.context |
|
217 | ||
218 |
# not for meetings agenda |
|
219 |
app.get('/manage/agendas/%s/check-types' % agenda.pk, status=404) |
|
220 | ||
221 | ||
222 |
def test_agenda_group(app, admin_user): |
|
223 |
agenda = Agenda.objects.create(label='Foo bar', kind='events') |
|
224 |
Desk.objects.create(agenda=agenda, slug='_exceptions_holder') |
|
225 |
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10) |
|
226 |
booking = Booking.objects.create(event=event) |
|
227 | ||
228 |
app = login(app) |
|
229 |
resp = app.get('/manage/agendas/%s/settings' % agenda.pk) |
|
230 |
assert 'has_check_types' in resp.context |
|
231 |
assert resp.context['has_check_types'] is False |
|
232 |
assert 'No check types configured for this agenda.' not in resp |
|
233 |
assert '/manage/agendas/%s/check-types' % agenda.pk not in resp |
|
234 |
resp = resp.click(href='/manage/agendas/%s/check-options' % agenda.pk) |
|
235 |
assert 'check_type_group' not in resp.context['form'].fields |
|
236 | ||
237 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
238 |
check_type = CheckType.objects.create(label='Foo reason', group=group) |
|
239 |
resp = app.get('/manage/agendas/%s/settings' % agenda.pk) |
|
240 |
assert 'has_check_types' in resp.context |
|
241 |
assert resp.context['has_check_types'] is True |
|
242 |
assert 'No check types configured for this agenda.' in resp |
|
243 |
resp = resp.click(href='/manage/agendas/%s/check-options' % agenda.pk) |
|
244 |
resp.form['check_type_group'] = group.pk |
|
245 |
resp = resp.form.submit().follow() |
|
246 |
agenda.refresh_from_db() |
|
247 |
assert agenda.check_type_group == group |
|
248 |
assert 'Check type group: Foo bar' in resp |
|
249 | ||
250 |
# cannot change check_type group booking with non null check_type exists |
|
251 |
booking.user_check_type_slug = check_type.slug |
|
252 |
booking.save() |
|
253 |
resp = app.get('/manage/agendas/%s/check-options' % agenda.pk) |
|
254 |
assert 'check_type_group' not in resp.context['form'].fields |
tests/manager/test_event.py | ||
---|---|---|
1782 | 1782 |
resp = app.get( |
1783 | 1783 |
'/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk), params={'extra-data-foo': 'val1'} |
1784 | 1784 |
) |
1785 |
assert len(ctx.captured_queries) == 11
|
|
1785 |
assert len(ctx.captured_queries) == 10
|
|
1786 | 1786 |
assert 'User none' not in resp |
1787 | 1787 |
assert 'User empty' not in resp |
1788 | 1788 |
assert 'User foo-val1 bar-none presence' in resp |
tests/manager/test_import_export.py | ||
---|---|---|
7 | 7 |
from django.utils.timezone import now |
8 | 8 |
from webtest import Upload |
9 | 9 | |
10 |
from chrono.agendas.models import ( |
|
11 |
Agenda, |
|
12 |
Booking, |
|
13 |
CheckType, |
|
14 |
CheckTypeGroup, |
|
15 |
Desk, |
|
16 |
Event, |
|
17 |
MeetingType, |
|
18 |
UnavailabilityCalendar, |
|
19 |
) |
|
10 |
from chrono.agendas.models import Agenda, Booking, Desk, Event, MeetingType, UnavailabilityCalendar |
|
20 | 11 |
from tests.utils import login |
21 | 12 | |
22 | 13 |
pytestmark = pytest.mark.django_db |
... | ... | |
36 | 27 |
assert site_json == { |
37 | 28 |
'unavailability_calendars': [], |
38 | 29 |
'agendas': [], |
39 |
'check_type_groups': [], |
|
40 | 30 |
'events_types': [], |
41 | 31 |
'resources': [], |
42 | 32 |
'categories': [], |
... | ... | |
51 | 41 |
site_json = json.loads(resp.text) |
52 | 42 |
assert len(site_json['agendas']) == 1 |
53 | 43 |
assert len(site_json['unavailability_calendars']) == 1 |
54 |
assert len(site_json['check_type_groups']) == 0 |
|
55 | 44 |
assert len(site_json['events_types']) == 0 |
56 | 45 |
assert len(site_json['resources']) == 0 |
57 | 46 |
assert len(site_json['categories']) == 0 |
58 | 47 | |
59 | 48 |
resp = app.get('/manage/agendas/export/') |
60 | 49 |
resp.form['agendas'] = False |
61 |
resp.form['check_type_groups'] = False |
|
62 | 50 |
resp.form['events_types'] = False |
63 | 51 |
resp.form['resources'] = False |
64 | 52 |
resp.form['categories'] = False |
... | ... | |
67 | 55 |
site_json = json.loads(resp.text) |
68 | 56 |
assert 'agendas' not in site_json |
69 | 57 |
assert 'unavailability_calendars' in site_json |
70 |
assert 'check_type_groups' not in site_json |
|
71 | 58 |
assert 'events_types' not in site_json |
72 | 59 |
assert 'resources' not in site_json |
73 | 60 |
assert 'categories' not in site_json |
... | ... | |
291 | 278 |
resp = resp.form.submit() |
292 | 279 |
assert 'Missing roles: "gé1"' in resp.text |
293 | 280 |
del calendar_export_dict['unavailability_calendars'][0]['permissions']['view'] |
294 | ||
295 | ||
296 |
@pytest.mark.freeze_time('2021-07-08') |
|
297 |
def test_import_check_type_group(app, admin_user): |
|
298 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
299 |
CheckType.objects.create(label='Foo reason', group=group) |
|
300 |
CheckType.objects.create(label='Baz', group=group) |
|
301 | ||
302 |
app = login(app) |
|
303 |
resp = app.get('/manage/check-type/group/%s/export/' % group.id) |
|
304 |
assert resp.headers['content-type'] == 'application/json' |
|
305 |
assert ( |
|
306 |
resp.headers['content-disposition'] |
|
307 |
== 'attachment; filename="export_check_type_group_foo-bar_20210708.json"' |
|
308 |
) |
|
309 |
group_export = resp.text |
|
310 | ||
311 |
# existing group |
|
312 |
resp = app.get('/manage/', status=200) |
|
313 |
resp = resp.click('Import') |
|
314 |
resp.form['agendas_json'] = Upload('export.json', group_export.encode('utf-8'), 'application/json') |
|
315 |
resp = resp.form.submit() |
|
316 |
assert resp.location.endswith('/manage/check-types/') |
|
317 |
resp = resp.follow() |
|
318 |
assert 'No check type group created. A check type group has been updated.' not in resp.text |
|
319 |
assert CheckTypeGroup.objects.count() == 1 |
|
320 |
assert CheckType.objects.count() == 2 |
|
321 | ||
322 |
# new group |
|
323 |
CheckTypeGroup.objects.all().delete() |
|
324 |
resp = app.get('/manage/', status=200) |
|
325 |
resp = resp.click('Import') |
|
326 |
resp.form['agendas_json'] = Upload('export.json', group_export.encode('utf-8'), 'application/json') |
|
327 |
resp = resp.form.submit() |
|
328 |
assert resp.location.endswith('/manage/check-types/') |
|
329 |
resp = resp.follow() |
|
330 |
assert 'A check type group has been created. No check type group updated.' not in resp.text |
|
331 |
assert CheckTypeGroup.objects.count() == 1 |
|
332 |
assert CheckType.objects.count() == 2 |
|
333 | ||
334 |
# multiple groups |
|
335 |
groups = json.loads(group_export) |
|
336 |
groups['check_type_groups'].append(copy.copy(groups['check_type_groups'][0])) |
|
337 |
groups['check_type_groups'].append(copy.copy(groups['check_type_groups'][0])) |
|
338 |
groups['check_type_groups'][1]['label'] = 'Foo bar 2' |
|
339 |
groups['check_type_groups'][1]['slug'] = 'foo-bar-2' |
|
340 |
groups['check_type_groups'][2]['label'] = 'Foo bar 3' |
|
341 |
groups['check_type_groups'][2]['slug'] = 'foo-bar-3' |
|
342 | ||
343 |
resp = app.get('/manage/', status=200) |
|
344 |
resp = resp.click('Import') |
|
345 |
resp.form['agendas_json'] = Upload('export.json', json.dumps(groups).encode('utf-8'), 'application/json') |
|
346 |
resp = resp.form.submit() |
|
347 |
assert resp.location.endswith('/manage/') |
|
348 |
resp = resp.follow() |
|
349 |
assert '2 check type groups have been created. A check type group has been updated.' in resp.text |
|
350 |
assert CheckTypeGroup.objects.count() == 3 |
|
351 |
assert CheckType.objects.count() == 6 |
|
352 | ||
353 |
CheckTypeGroup.objects.all().delete() |
|
354 |
resp = app.get('/manage/', status=200) |
|
355 |
resp = resp.click('Import') |
|
356 |
resp.form['agendas_json'] = Upload('export.json', json.dumps(groups).encode('utf-8'), 'application/json') |
|
357 |
resp = resp.form.submit().follow() |
|
358 |
assert '3 check type groups have been created. No check type group updated.' in resp.text |
|
359 |
assert CheckTypeGroup.objects.count() == 3 |
|
360 |
assert CheckType.objects.count() == 6 |
tests/test_agendas.py | ||
---|---|---|
18 | 18 |
AgendaReminderSettings, |
19 | 19 |
Booking, |
20 | 20 |
Category, |
21 |
CheckTypeGroup, |
|
22 | 21 |
Desk, |
23 | 22 |
Event, |
24 | 23 |
EventCancellationReport, |
... | ... | |
187 | 186 |
assert category.slug == 'foo-baz-2' |
188 | 187 | |
189 | 188 | |
190 |
def test_check_type_group_slug(): |
|
191 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
192 |
assert group.slug == 'foo-bar' |
|
193 | ||
194 | ||
195 |
def test_check_type_group_existing_slug(): |
|
196 |
group = CheckTypeGroup.objects.create(label='Foo bar', slug='bar') |
|
197 |
assert group.slug == 'bar' |
|
198 | ||
199 | ||
200 |
def test_check_type_group_duplicate_slugs(): |
|
201 |
group = CheckTypeGroup.objects.create(label='Foo baz') |
|
202 |
assert group.slug == 'foo-baz' |
|
203 |
group = CheckTypeGroup.objects.create(label='Foo baz') |
|
204 |
assert group.slug == 'foo-baz-1' |
|
205 |
group = CheckTypeGroup.objects.create(label='Foo baz') |
|
206 |
assert group.slug == 'foo-baz-2' |
|
207 | ||
208 | ||
209 | 189 |
def test_agenda_minimal_booking_delay(freezer): |
210 | 190 |
freezer.move_to('2021-07-09') |
211 | 191 |
agenda = Agenda.objects.create(label='Agenda', minimal_booking_delay=4) |
tests/test_import_export.py | ||
---|---|---|
21 | 21 |
AgendaNotificationsSettings, |
22 | 22 |
AgendaReminderSettings, |
23 | 23 |
Category, |
24 |
CheckType, |
|
25 |
CheckTypeGroup, |
|
26 | 24 |
Desk, |
27 | 25 |
Event, |
28 | 26 |
EventsType, |
... | ... | |
431 | 429 |
assert agenda.category == category |
432 | 430 | |
433 | 431 | |
434 |
def test_import_export_agenda_with_check_types(app): |
|
435 |
group = CheckTypeGroup.objects.create(label='foo') |
|
436 |
agenda = Agenda.objects.create(label='Foo Bar', kind='events', check_type_group=group) |
|
437 |
Desk.objects.create(agenda=agenda, slug='_exceptions_holder') |
|
438 |
output = get_output_of_command('export_site') |
|
439 | ||
440 |
import_site(data={}, clean=True) |
|
441 |
assert Agenda.objects.count() == 0 |
|
442 |
assert CheckTypeGroup.objects.count() == 0 |
|
443 |
data = json.loads(output) |
|
444 |
del data['check_type_groups'] |
|
445 | ||
446 |
with pytest.raises(AgendaImportError) as excinfo: |
|
447 |
import_site(data, overwrite=True) |
|
448 |
assert str(excinfo.value) == 'Missing "foo" check type group' |
|
449 | ||
450 |
CheckTypeGroup.objects.create(label='foobar') |
|
451 |
with pytest.raises(AgendaImportError) as excinfo: |
|
452 |
import_site(data, overwrite=True) |
|
453 |
assert str(excinfo.value) == 'Missing "foo" check type group' |
|
454 | ||
455 |
group = CheckTypeGroup.objects.create(label='foo') |
|
456 |
import_site(data, overwrite=True) |
|
457 |
agenda = Agenda.objects.get(slug=agenda.slug) |
|
458 |
assert agenda.check_type_group == group |
|
459 | ||
460 | ||
461 | 432 |
def test_import_export_agenda_with_events_type(app): |
462 | 433 |
events_type = EventsType.objects.create(label='foo') |
463 | 434 |
agenda = Agenda.objects.create(label='Foo Bar', kind='events', events_type=events_type) |
... | ... | |
932 | 903 |
assert calendar.label == 'Calendar Updated' |
933 | 904 | |
934 | 905 | |
935 |
def test_import_export_check_type_group(app): |
|
936 |
output = get_output_of_command('export_site') |
|
937 |
payload = json.loads(output) |
|
938 |
assert len(payload['check_type_groups']) == 0 |
|
939 | ||
940 |
group = CheckTypeGroup.objects.create(label='Foo bar') |
|
941 |
CheckType.objects.create(label='Foo reason', group=group) |
|
942 |
CheckType.objects.create(label='Baz', group=group) |
|
943 | ||
944 |
output = get_output_of_command('export_site') |
|
945 |
payload = json.loads(output) |
|
946 |
assert len(payload['check_type_groups']) == 1 |
|
947 | ||
948 |
group.delete() |
|
949 |
assert not CheckTypeGroup.objects.exists() |
|
950 |
assert not CheckType.objects.exists() |
|
951 | ||
952 |
import_site(copy.deepcopy(payload)) |
|
953 |
assert CheckTypeGroup.objects.count() == 1 |
|
954 |
group = CheckTypeGroup.objects.first() |
|
955 |
assert group.label == 'Foo bar' |
|
956 |
assert group.slug == 'foo-bar' |
|
957 |
assert group.check_types.count() == 2 |
|
958 |
assert CheckType.objects.get(group=group, label='Foo reason', slug='foo-reason') |
|
959 |
assert CheckType.objects.get(group=group, label='Baz', slug='baz') |
|
960 | ||
961 |
# update |
|
962 |
update_payload = copy.deepcopy(payload) |
|
963 |
update_payload['check_type_groups'][0]['label'] = 'Foo bar Updated' |
|
964 |
import_site(update_payload) |
|
965 |
group.refresh_from_db() |
|
966 |
assert group.label == 'Foo bar Updated' |
|
967 | ||
968 |
# insert another group |
|
969 |
group.slug = 'foo-bar-updated' |
|
970 |
group.save() |
|
971 |
import_site(copy.deepcopy(payload)) |
|
972 |
assert CheckTypeGroup.objects.count() == 2 |
|
973 |
group = CheckTypeGroup.objects.latest('pk') |
|
974 |
assert group.label == 'Foo bar' |
|
975 |
assert group.slug == 'foo-bar' |
|
976 |
assert group.check_types.count() == 2 |
|
977 |
assert CheckType.objects.get(group=group, label='Foo reason', slug='foo-reason') |
|
978 |
assert CheckType.objects.get(group=group, label='Baz', slug='baz') |
|
979 | ||
980 |
# with overwrite |
|
981 |
CheckType.objects.create(group=group, label='Baz2') |
|
982 |
import_site(copy.deepcopy(payload), overwrite=True) |
|
983 |
assert CheckTypeGroup.objects.count() == 2 |
|
984 |
group = CheckTypeGroup.objects.latest('pk') |
|
985 |
assert group.label == 'Foo bar' |
|
986 |
assert group.slug == 'foo-bar' |
|
987 |
assert group.check_types.count() == 2 |
|
988 |
assert CheckType.objects.get(group=group, label='Foo reason', slug='foo-reason') |
|
989 |
assert CheckType.objects.get(group=group, label='Baz', slug='baz') |
|
990 | ||
991 | ||
992 | 906 |
def test_import_export_category(app): |
993 | 907 |
output = get_output_of_command('export_site') |
994 | 908 |
payload = json.loads(output) |
995 |
- |