0001-agendas-add-slug-form-absence-reason-model-53147.patch
chrono/agendas/migrations/0079_reason_slug.py | ||
---|---|---|
1 |
from django.db import migrations, models |
|
2 | ||
3 | ||
4 |
class Migration(migrations.Migration): |
|
5 | ||
6 |
dependencies = [ |
|
7 |
('agendas', '0078_absence_reasons'), |
|
8 |
] |
|
9 | ||
10 |
operations = [ |
|
11 |
migrations.AddField( |
|
12 |
model_name='absencereason', |
|
13 |
name='slug', |
|
14 |
field=models.SlugField(max_length=160, null=True, verbose_name='Identifier'), |
|
15 |
), |
|
16 |
migrations.AlterUniqueTogether( |
|
17 |
name='absencereason', |
|
18 |
unique_together=['group', 'slug'], |
|
19 |
), |
|
20 |
] |
chrono/agendas/migrations/0080_reason_slug.py | ||
---|---|---|
1 |
from django.db import migrations |
|
2 |
from django.utils.text import slugify |
|
3 | ||
4 | ||
5 |
def generate_slug(instance, **query_filters): |
|
6 |
base_slug = slugify(instance.label) |
|
7 |
slug = base_slug |
|
8 |
i = 1 |
|
9 |
while True: |
|
10 |
queryset = instance._meta.model.objects.filter(slug=slug, **query_filters).exclude(pk=instance.pk) |
|
11 |
if not queryset.exists(): |
|
12 |
break |
|
13 |
slug = '%s-%s' % (base_slug, i) |
|
14 |
i += 1 |
|
15 |
return slug |
|
16 | ||
17 | ||
18 |
def set_slug_on_absence_reasons(apps, schema_editor): |
|
19 |
AbsenceReason = apps.get_model('agendas', 'AbsenceReason') |
|
20 |
for reason in AbsenceReason.objects.all().order_by('-pk'): |
|
21 |
if ( |
|
22 |
reason.slug |
|
23 |
and not AbsenceReason.objects.filter(slug=reason.slug, group=reason.group) |
|
24 |
.exclude(pk=reason.pk) |
|
25 |
.exists() |
|
26 |
): |
|
27 |
continue |
|
28 |
reason.slug = generate_slug(reason, group=reason.group) |
|
29 |
reason.save(update_fields=['slug']) |
|
30 | ||
31 | ||
32 |
class Migration(migrations.Migration): |
|
33 | ||
34 |
dependencies = [ |
|
35 |
('agendas', '0079_reason_slug'), |
|
36 |
] |
|
37 | ||
38 |
operations = [ |
|
39 |
migrations.RunPython(set_slug_on_absence_reasons, migrations.RunPython.noop), |
|
40 |
] |
chrono/agendas/migrations/0081_reason_slug.py | ||
---|---|---|
1 |
from django.db import migrations, models |
|
2 | ||
3 | ||
4 |
class Migration(migrations.Migration): |
|
5 | ||
6 |
dependencies = [ |
|
7 |
('agendas', '0080_reason_slug'), |
|
8 |
] |
|
9 | ||
10 |
operations = [ |
|
11 |
migrations.AlterField( |
|
12 |
model_name='absencereason', |
|
13 |
name='slug', |
|
14 |
field=models.SlugField(max_length=160, verbose_name='Identifier'), |
|
15 |
), |
|
16 |
] |
chrono/agendas/models.py | ||
---|---|---|
2455 | 2455 | |
2456 | 2456 |
class AbsenceReason(models.Model): |
2457 | 2457 |
group = models.ForeignKey(AbsenceReasonGroup, on_delete=models.CASCADE, related_name='absence_reasons') |
2458 |
slug = models.SlugField(_('Identifier'), max_length=160) |
|
2458 | 2459 |
label = models.CharField(_('Label'), max_length=150) |
2459 | 2460 | |
2460 | 2461 |
class Meta: |
2461 | 2462 |
ordering = ['label'] |
2463 |
unique_together = ['group', 'slug'] |
|
2462 | 2464 | |
2463 | 2465 |
def __str__(self): |
2464 | 2466 |
return self.label |
2467 | ||
2468 |
def save(self, *args, **kwargs): |
|
2469 |
if not self.slug: |
|
2470 |
self.slug = generate_slug(self, group=self.group) |
|
2471 |
super().save(*args, **kwargs) |
|
2472 | ||
2473 |
@property |
|
2474 |
def base_slug(self): |
|
2475 |
return slugify(self.label) |
chrono/manager/forms.py | ||
---|---|---|
53 | 53 |
from .widgets import SplitDateTimeField |
54 | 54 | |
55 | 55 | |
56 |
class AbsenceReasonForm(forms.ModelForm): |
|
57 |
class Meta: |
|
58 |
model = AbsenceReason |
|
59 |
fields = ['label', 'slug'] |
|
60 | ||
61 |
def clean_slug(self): |
|
62 |
slug = self.cleaned_data['slug'] |
|
63 | ||
64 |
if self.instance.group.absence_reasons.filter(slug=slug).exclude(pk=self.instance.pk).exists(): |
|
65 |
raise ValidationError(_('Another absence reason exists with the same identifier.')) |
|
66 | ||
67 |
return slug |
|
68 | ||
69 | ||
56 | 70 |
class AgendaAddForm(forms.ModelForm): |
57 | 71 |
edit_role = forms.ModelChoiceField( |
58 | 72 |
label=_('Edit Role'), required=False, queryset=Group.objects.all().order_by('name') |
chrono/manager/views.py | ||
---|---|---|
79 | 79 |
) |
80 | 80 | |
81 | 81 |
from .forms import ( |
82 |
AbsenceReasonForm, |
|
82 | 83 |
AgendaAddForm, |
83 | 84 |
AgendaEditForm, |
84 | 85 |
AgendaBookingDelaysForm, |
... | ... | |
703 | 704 |
class AbsenceReasonEditView(UpdateView): |
704 | 705 |
template_name = 'chrono/manager_absence_reason_form.html' |
705 | 706 |
model = AbsenceReason |
706 |
fields = ['label']
|
|
707 |
form_class = AbsenceReasonForm
|
|
707 | 708 | |
708 | 709 |
def dispatch(self, request, *args, **kwargs): |
709 | 710 |
self.group_pk = kwargs.pop('group_pk') |
tests/manager/test_absence_reason.py | ||
---|---|---|
68 | 68 | |
69 | 69 |
def test_edit_group(app, admin_user): |
70 | 70 |
group = AbsenceReasonGroup.objects.create(label='Foo bar') |
71 |
group2 = AbsenceReasonGroup.objects.create(label='baz') |
|
71 | 72 | |
72 | 73 |
app = login(app) |
73 | 74 |
resp = app.get('/manage/absence-reasons/') |
74 | 75 |
resp = resp.click(href='/manage/absence-reason/group/%s/edit/' % group.pk) |
75 | 76 |
resp.form['label'] = 'Foo bar baz' |
76 |
resp.form['slug'] = 'baz' |
|
77 |
resp.form['slug'] = group2.slug |
|
78 |
resp = resp.form.submit() |
|
79 |
assert resp.context['form'].errors['slug'] == [ |
|
80 |
'Absence reason group with this Identifier already exists.' |
|
81 |
] |
|
82 | ||
83 |
resp.form['slug'] = 'baz2' |
|
77 | 84 |
resp = resp.form.submit() |
78 | 85 |
assert resp.location.endswith('/manage/absence-reasons/') |
79 | 86 |
group.refresh_from_db() |
80 | 87 |
assert group.label == 'Foo bar baz' |
81 |
assert group.slug == 'baz' |
|
88 |
assert group.slug == 'baz2'
|
|
82 | 89 | |
83 | 90 | |
84 | 91 |
def test_edit_group_as_manager(app, manager_user, agenda_with_restrictions): |
... | ... | |
121 | 128 |
assert resp.location.endswith('/manage/absence-reasons/') |
122 | 129 |
assert reason.label == 'Foo reason' |
123 | 130 |
assert reason.group == group |
131 |
assert reason.slug == 'foo-reason' |
|
124 | 132 | |
125 | 133 | |
126 | 134 |
def test_add_reason_as_manager(app, manager_user, agenda_with_restrictions): |
... | ... | |
133 | 141 |
def test_edit_reason(app, admin_user): |
134 | 142 |
group = AbsenceReasonGroup.objects.create(label='Foo bar') |
135 | 143 |
reason = AbsenceReason.objects.create(label='Foo reason', group=group) |
144 |
reason2 = AbsenceReason.objects.create(label='Baz', group=group) |
|
145 |
group2 = AbsenceReasonGroup.objects.create(label='Foo bar') |
|
146 |
reason3 = AbsenceReason.objects.create(label='Foo bar reason', group=group2) |
|
136 | 147 | |
137 | 148 |
app = login(app) |
138 | 149 |
resp = app.get('/manage/absence-reasons/') |
139 | 150 |
resp = resp.click(href='/manage/absence-reason/group/%s/%s/edit/' % (group.pk, reason.pk)) |
140 | 151 |
resp.form['label'] = 'Foo bar reason' |
152 |
resp.form['slug'] = reason2.slug |
|
153 |
resp = resp.form.submit() |
|
154 |
assert resp.context['form'].errors['slug'] == ['Another absence reason exists with the same identifier.'] |
|
155 | ||
156 |
resp.form['slug'] = reason3.slug |
|
141 | 157 |
resp = resp.form.submit() |
142 | 158 |
assert resp.location.endswith('/manage/absence-reasons/') |
143 | 159 |
reason.refresh_from_db() |
144 | 160 |
assert reason.label == 'Foo bar reason' |
161 |
assert reason.slug == 'foo-bar-reason' |
|
145 | 162 | |
146 |
group2 = AbsenceReasonGroup.objects.create(label='Foo bar baz') |
|
147 | 163 |
app.get('/manage/absence-reason/group/%s/%s/edit/' % (group2.pk, reason.pk), status=404) |
148 | 164 | |
149 | 165 |
tests/test_agendas.py | ||
---|---|---|
14 | 14 |
from django.utils.timezone import localtime, make_aware, now |
15 | 15 | |
16 | 16 |
from chrono.agendas.models import ( |
17 |
AbsenceReasonGroup, |
|
17 | 18 |
Agenda, |
18 | 19 |
Booking, |
19 | 20 |
Category, |
... | ... | |
177 | 178 |
assert category.slug == 'foo-baz-2' |
178 | 179 | |
179 | 180 | |
181 |
def test_absence_reason_group_slug(): |
|
182 |
group = AbsenceReasonGroup.objects.create(label=u'Foo bar') |
|
183 |
assert group.slug == 'foo-bar' |
|
184 | ||
185 | ||
186 |
def test_absence_reason_group_existing_slug(): |
|
187 |
group = AbsenceReasonGroup.objects.create(label=u'Foo bar', slug='bar') |
|
188 |
assert group.slug == 'bar' |
|
189 | ||
190 | ||
191 |
def test_absence_reason_group_duplicate_slugs(): |
|
192 |
group = AbsenceReasonGroup.objects.create(label=u'Foo baz') |
|
193 |
assert group.slug == 'foo-baz' |
|
194 |
group = AbsenceReasonGroup.objects.create(label=u'Foo baz') |
|
195 |
assert group.slug == 'foo-baz-1' |
|
196 |
group = AbsenceReasonGroup.objects.create(label=u'Foo baz') |
|
197 |
assert group.slug == 'foo-baz-2' |
|
198 | ||
199 | ||
180 | 200 |
@pytest.mark.parametrize('with_prefetch', [True, False]) |
181 | 201 |
def test_agenda_is_available_for_simple_management(settings, with_prefetch): |
182 | 202 |
settings.EXCEPTIONS_SOURCES = { |
183 |
- |