0001-agenda-generate-event-slug-if-not-provided-44375.patch
chrono/agendas/migrations/0049_event_slug.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
from django.db import migrations |
|
5 |
from django.utils.text import slugify |
|
6 | ||
7 | ||
8 |
def generate_slug(instance, **query_filters): |
|
9 |
base_slug = slugify(instance.label or ('%s-event' % instance.agenda.label)) |
|
10 |
slug = base_slug |
|
11 |
i = 1 |
|
12 |
while True: |
|
13 |
queryset = instance._meta.model.objects.filter(slug=slug, **query_filters).exclude(pk=instance.pk) |
|
14 |
if not queryset.exists(): |
|
15 |
break |
|
16 |
slug = '%s-%s' % (base_slug, i) |
|
17 |
i += 1 |
|
18 |
return slug |
|
19 | ||
20 | ||
21 |
def set_slug_on_events(apps, schema_editor): |
|
22 |
Event = apps.get_model('agendas', 'Event') |
|
23 |
for event in Event.objects.all().order_by('-pk'): |
|
24 |
if event.slug and not Event.objects.filter(slug=event.slug).exclude(pk=event.pk).exists(): |
|
25 |
continue |
|
26 |
event.slug = generate_slug(event) |
|
27 |
event.save(update_fields=['slug']) |
|
28 | ||
29 | ||
30 |
class Migration(migrations.Migration): |
|
31 | ||
32 |
dependencies = [ |
|
33 |
('agendas', '0048_meeting_type_deleted_flag'), |
|
34 |
] |
|
35 | ||
36 |
operations = [ |
|
37 |
migrations.RunPython(set_slug_on_events, lambda x, y: None), |
|
38 |
] |
chrono/agendas/migrations/0050_event_slug.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
from __future__ import unicode_literals |
|
3 | ||
4 |
import chrono.agendas.models |
|
5 |
from django.db import migrations, models |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('agendas', '0049_event_slug'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AlterField( |
|
16 |
model_name='event', |
|
17 |
name='slug', |
|
18 |
field=models.SlugField( |
|
19 |
blank=True, |
|
20 |
max_length=160, |
|
21 |
validators=[chrono.agendas.models.validate_not_digit], |
|
22 |
verbose_name='Identifier', |
|
23 |
), |
|
24 |
preserve_default=False, |
|
25 |
), |
|
26 |
] |
chrono/agendas/models.py | ||
---|---|---|
60 | 60 | |
61 | 61 | |
62 | 62 |
def generate_slug(instance, **query_filters): |
63 |
base_slug = slugify(instance.label)
|
|
63 |
base_slug = instance.base_slug
|
|
64 | 64 |
slug = base_slug |
65 | 65 |
i = 1 |
66 | 66 |
while instance._meta.model.objects.filter(slug=slug, **query_filters).exists(): |
... | ... | |
154 | 154 |
self.maximal_booking_delay = 8 * 7 |
155 | 155 |
super(Agenda, self).save(*args, **kwargs) |
156 | 156 | |
157 |
@property |
|
158 |
def base_slug(self): |
|
159 |
return slugify(self.label) |
|
160 | ||
157 | 161 |
def get_absolute_url(self): |
158 | 162 |
return reverse('chrono-manager-agenda-view', kwargs={'pk': self.id}) |
159 | 163 | |
... | ... | |
691 | 695 |
self.slug = generate_slug(self, agenda=self.agenda) |
692 | 696 |
super(MeetingType, self).save(*args, **kwargs) |
693 | 697 | |
698 |
@property |
|
699 |
def base_slug(self): |
|
700 |
return slugify(self.label) |
|
701 | ||
694 | 702 |
@classmethod |
695 | 703 |
def import_json(cls, data): |
696 | 704 |
data = clean_import_data(cls, data) |
... | ... | |
735 | 743 |
blank=True, |
736 | 744 |
help_text=_('Optional label to identify this date.'), |
737 | 745 |
) |
738 |
slug = models.SlugField( |
|
739 |
_('Identifier'), max_length=160, null=True, blank=True, default=None, validators=[validate_not_digit] |
|
740 |
) |
|
746 |
slug = models.SlugField(_('Identifier'), max_length=160, blank=True, validators=[validate_not_digit]) |
|
741 | 747 |
description = models.TextField( |
742 | 748 |
_('Description'), null=True, blank=True, help_text=_('Optional event description.') |
743 | 749 |
) |
... | ... | |
761 | 767 |
assert self.agenda.kind != 'virtual', "an event can't reference a virtual agenda" |
762 | 768 |
assert not (self.slug and self.slug.isdigit()), 'slug cannot be a number' |
763 | 769 |
self.check_full() |
770 |
if not self.slug: |
|
771 |
self.slug = generate_slug(self, agenda=self.agenda) |
|
764 | 772 |
return super(Event, self).save(*args, **kwargs) |
765 | 773 | |
774 |
@property |
|
775 |
def base_slug(self): |
|
776 |
# label can be empty |
|
777 |
return slugify(self.label or ('%s-event' % self.agenda.label)) |
|
778 | ||
766 | 779 |
def check_full(self): |
767 | 780 |
self.full = bool( |
768 | 781 |
(self.booked_places >= self.places and self.waiting_list_places == 0) |
... | ... | |
982 | 995 |
self.slug = generate_slug(self, agenda=self.agenda) |
983 | 996 |
super(Desk, self).save(*args, **kwargs) |
984 | 997 | |
998 |
@property |
|
999 |
def base_slug(self): |
|
1000 |
return slugify(self.label) |
|
1001 | ||
985 | 1002 |
@classmethod |
986 | 1003 |
def import_json(cls, data): |
987 | 1004 |
timeperiods = data.pop('timeperiods', []) |
... | ... | |
1214 | 1231 |
self.slug = generate_slug(self) |
1215 | 1232 |
super().save(*args, **kwargs) |
1216 | 1233 | |
1234 |
@property |
|
1235 |
def base_slug(self): |
|
1236 |
return slugify(self.label) |
|
1237 | ||
1217 | 1238 | |
1218 | 1239 |
def ics_directory_path(instance, filename): |
1219 | 1240 |
return 'ics/{0}/{1}'.format(str(uuid.uuid4()), filename) |
chrono/api/views.py | ||
---|---|---|
17 | 17 |
import collections |
18 | 18 |
import datetime |
19 | 19 |
import itertools |
20 |
import uuid |
|
20 | 21 | |
21 | 22 | |
22 | 23 |
from django.db import transaction |
23 |
from django.db.models import Q |
|
24 | 24 |
from django.http import Http404, HttpResponse |
25 | 25 |
from django.shortcuts import get_object_or_404 |
26 | 26 |
from django.urls import reverse |
... | ... | |
424 | 424 |
'fillslot_url': request.build_absolute_uri( |
425 | 425 |
reverse( |
426 | 426 |
'api-fillslot', |
427 |
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': x.id,},
|
|
427 |
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': x.slug},
|
|
428 | 428 |
) |
429 | 429 |
), |
430 | 430 |
'status_url': request.build_absolute_uri( |
431 | 431 |
reverse( |
432 | 432 |
'api-event-status', |
433 |
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': x.id,},
|
|
433 |
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': x.slug},
|
|
434 | 434 |
) |
435 | 435 |
), |
436 | 436 |
}, |
... | ... | |
497 | 497 |
fillslot_url = request.build_absolute_uri( |
498 | 498 |
reverse( |
499 | 499 |
'api-fillslot', |
500 |
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': fake_event_identifier,},
|
|
500 |
kwargs={'agenda_identifier': agenda.slug, 'event_identifier': fake_event_identifier}, |
|
501 | 501 |
) |
502 | 502 |
) |
503 | 503 |
if resources: |
... | ... | |
520 | 520 |
'datetime': format_response_datetime(slot.start_datetime), |
521 | 521 |
'text': date_format(slot.start_datetime, format='DATETIME_FORMAT'), |
522 | 522 |
'disabled': bool(slot.full), |
523 |
'api': {'fillslot_url': fillslot_url.replace(fake_event_identifier, slot_id),},
|
|
523 |
'api': {'fillslot_url': fillslot_url.replace(fake_event_identifier, slot_id)}, |
|
524 | 524 |
} |
525 | 525 |
for slot in generator_of_unique_slots |
526 | 526 |
# we do not have the := operator, so we do that |
... | ... | |
723 | 723 |
cancel_error = gettext_noop('cancel booking: booking does no exist') |
724 | 724 | |
725 | 725 |
if cancel_error: |
726 |
return Response({'err': 1, 'err_class': cancel_error, 'err_desc': _(cancel_error),})
|
|
726 |
return Response({'err': 1, 'err_class': cancel_error, 'err_desc': _(cancel_error)}) |
|
727 | 727 | |
728 | 728 |
extra_data = {} |
729 | 729 |
for k, v in request.data.items(): |
... | ... | |
860 | 860 |
for start_datetime in datetimes: |
861 | 861 |
event = Event.objects.create( |
862 | 862 |
agenda=available_desk.agenda, |
863 |
slug=str(uuid.uuid4()), # set slug to avoid queries during slug generation |
|
863 | 864 |
meeting_type_id=meeting_type_id, |
864 | 865 |
start_datetime=start_datetime, |
865 | 866 |
full=False, |
... | ... | |
871 | 872 |
events.append(event) |
872 | 873 |
else: |
873 | 874 |
try: |
874 |
events = Event.objects.filter(id__in=[int(s) for s in slots]).order_by('start_datetime')
|
|
875 |
events = agenda.event_set.filter(id__in=[int(s) for s in slots]).order_by('start_datetime')
|
|
875 | 876 |
except ValueError: |
876 |
events = Event.objects.filter(slug__in=slots).order_by('start_datetime')
|
|
877 |
events = agenda.event_set.filter(slug__in=slots).order_by('start_datetime')
|
|
877 | 878 | |
878 | 879 |
for event in events: |
879 | 880 |
if not event.in_bookable_period(): |
chrono/manager/templates/chrono/manager_events_agenda_settings.html | ||
---|---|---|
24 | 24 |
data-total="{{event.waiting_list_places}}" data-booked="{{event.waiting_list_count}}" |
25 | 25 |
{% endif %} |
26 | 26 |
><a rel="popup" href="{% url 'chrono-manager-event-edit' pk=agenda.id event_pk=event.id %}?next=settings"> |
27 |
{% if event.label %}{{event.label}} / {% endif %} |
|
27 |
{% if event.label %}{{event.label}} / {% endif %}[{% trans "identifier:" %} {{ event.slug }}]
|
|
28 | 28 |
{{ event.start_datetime }} |
29 | 29 |
{% if event.full %}/ <span class="full">{% trans "full" %}</span>{% endif %} |
30 | 30 |
( |
tests/test_agendas.py | ||
---|---|---|
149 | 149 |
assert resource.slug == 'foo-baz-2' |
150 | 150 | |
151 | 151 | |
152 |
def test_event_slug(): |
|
153 |
other_agenda = Agenda.objects.create(label='Foo bar') |
|
154 |
Event.objects.create(agenda=other_agenda, places=42, start_datetime=now(), slug='foo-bar') |
|
155 | ||
156 |
agenda = Agenda.objects.create(label='Foo baz') |
|
157 |
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now(), label='Foo bar') |
|
158 |
assert event.slug == 'foo-bar' |
|
159 |
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now(), label='Foo bar') |
|
160 |
assert event.slug == 'foo-bar-1' |
|
161 |
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now(), label='Foo bar') |
|
162 |
assert event.slug == 'foo-bar-2' |
|
163 | ||
164 |
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now()) |
|
165 |
assert event.slug == 'foo-baz-event' |
|
166 |
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now()) |
|
167 |
assert event.slug == 'foo-baz-event-1' |
|
168 |
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now()) |
|
169 |
assert event.slug == 'foo-baz-event-2' |
|
170 | ||
171 | ||
172 |
def test_event_existing_slug(): |
|
173 |
other_agenda = Agenda.objects.create(label='Foo bar') |
|
174 |
Event.objects.create(agenda=other_agenda, places=42, start_datetime=now(), slug='bar') |
|
175 | ||
176 |
agenda = Agenda.objects.create(label='Foo baz') |
|
177 |
event = Event.objects.create(agenda=agenda, places=42, start_datetime=now(), label='Foo bar', slug='bar') |
|
178 |
assert event.slug == 'bar' |
|
179 | ||
180 | ||
152 | 181 |
def test_event_manager(): |
153 | 182 |
agenda = Agenda(label=u'Foo baz') |
154 | 183 |
agenda.save() |
tests/test_api.py | ||
---|---|---|
360 | 360 |
for datum in resp.json['data']: |
361 | 361 |
assert urlparse.urlparse(datum['api']['status_url']).path == '/api/agenda/%s/status/%s/' % ( |
362 | 362 |
agenda.slug, |
363 |
datum['slug'] or datum['id'],
|
|
363 |
datum['slug'], |
|
364 | 364 |
) |
365 | 365 | |
366 | 366 | |
... | ... | |
706 | 706 |
] |
707 | 707 |
assert urlparse.urlparse(event_fillslot_url).path == '/api/agenda/%s/fillslot/%s/' % ( |
708 | 708 |
agenda.slug, |
709 |
event.slug or event.id,
|
|
709 |
event.slug, |
|
710 | 710 |
) |
711 | 711 | |
712 | 712 |
app.authorization = ('Basic', ('john.doe', 'password')) |
... | ... | |
889 | 889 |
def test_booking_api_fillslots(app, some_data, user): |
890 | 890 |
agenda = Agenda.objects.filter(label=u'Foo bar')[0] |
891 | 891 |
events_ids = [x.id for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()] |
892 |
for i, event in enumerate([x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()]): |
|
893 |
event.slug = 'event-%s' % i |
|
894 |
event.save() |
|
895 | 892 |
events_slugs = [x.slug for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()] |
896 | 893 |
assert len(events_ids) == 3 |
897 | 894 |
event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0] # first event |
tests/test_manager.py | ||
---|---|---|
907 | 907 |
resp = resp.form.submit() |
908 | 908 |
resp = resp.follow() |
909 | 909 |
event = Event.objects.get(places=10) |
910 |
assert event.slug is None |
|
911 | 910 |
assert event.publication_date is None |
912 | 911 |
assert "This agenda doesn't have any event yet." not in resp.text |
913 | 912 |
assert '/manage/agendas/%s/events/%s/' % (agenda.id, event.id) in resp.text |
914 |
- |