0001-general-make-event-slugs-optional-37987.patch
chrono/agendas/migrations/0028_event_slug.py | ||
---|---|---|
5 | 5 |
from django.utils.text import slugify |
6 | 6 | |
7 | 7 | |
8 |
def set_slug_on_events(apps, schema_editor): |
|
9 |
Event = apps.get_model('agendas', 'Event') |
|
10 |
for event in Event.objects.filter(slug=''): |
|
11 |
base_slug = slugify(event.label) |
|
12 |
slug = base_slug |
|
13 |
i = 1 |
|
14 |
while True: |
|
15 |
if not Event.objects.filter(slug=slug).exists(): |
|
16 |
break |
|
17 |
slug = '%s-%s' % (base_slug, i) |
|
18 |
i += 1 |
|
19 |
event.slug = slug |
|
20 |
event.save(update_fields=['slug']) |
|
21 | ||
22 | ||
23 | 8 |
class Migration(migrations.Migration): |
24 | 9 | |
25 | 10 |
dependencies = [ |
... | ... | |
30 | 15 |
migrations.AddField( |
31 | 16 |
model_name='event', |
32 | 17 |
name='slug', |
33 |
field=models.SlugField(default='', max_length=160, verbose_name='Identifier'),
|
|
18 |
field=models.SlugField(default=None, null=True, blank=True, max_length=160, verbose_name='Identifier'),
|
|
34 | 19 |
preserve_default=False, |
35 | 20 |
), |
36 |
migrations.RunPython(set_slug_on_events, lambda x, y: None), |
|
37 | 21 |
] |
chrono/agendas/migrations/0029_auto_20191106_1320.py | ||
---|---|---|
15 | 15 |
migrations.AlterField( |
16 | 16 |
model_name='event', |
17 | 17 |
name='slug', |
18 |
field=models.SlugField(max_length=160, unique=True, verbose_name='Identifier'),
|
|
18 |
field=models.SlugField(default=None, null=True, blank=True, max_length=160, verbose_name='Identifier'),
|
|
19 | 19 |
), |
20 | 20 |
] |
chrono/agendas/models.py | ||
---|---|---|
288 | 288 |
_('Places in waiting list'), default=0) |
289 | 289 |
label = models.CharField(_('Label'), max_length=150, null=True, blank=True, |
290 | 290 |
help_text=_('Optional label to identify this date.')) |
291 |
slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
|
|
291 |
slug = models.SlugField(_('Identifier'), max_length=160, null=True, blank=True, default=None)
|
|
292 | 292 |
description = models.TextField(_('Description'), null=True, blank=True, |
293 | 293 |
help_text=_('Optional event description.')) |
294 | 294 |
full = models.BooleanField(default=False) |
... | ... | |
297 | 297 | |
298 | 298 |
class Meta: |
299 | 299 |
ordering = ['agenda', 'start_datetime', 'label'] |
300 |
unique_together = ('agenda', 'slug') |
|
300 | 301 | |
301 | 302 |
def __str__(self): |
302 | 303 |
if self.label: |
... | ... | |
305 | 306 | |
306 | 307 |
def save(self, *args, **kwargs): |
307 | 308 |
self.check_full() |
308 |
if not self.slug: |
|
309 |
self.slug = generate_slug(self) |
|
310 | 309 |
return super(Event, self).save(*args, **kwargs) |
311 | 310 | |
312 | 311 |
def check_full(self): |
... | ... | |
347 | 346 |
def import_json(cls, data): |
348 | 347 |
data['start_datetime'] = make_aware(datetime.datetime.strptime( |
349 | 348 |
data['start_datetime'], '%Y-%m-%d %H:%M:%S')) |
350 |
if 'slug' in data:
|
|
349 |
if data.get('slug'):
|
|
351 | 350 |
event, created = cls.objects.get_or_create(slug=data['slug'], defaults=data) |
352 | 351 |
if not created: |
353 | 352 |
for k, v in data.items(): |
chrono/manager/forms.py | ||
---|---|---|
174 | 174 |
'as columns.')) |
175 | 175 |
events = None |
176 | 176 | |
177 |
def __init__(self, agenda_pk, **kwargs): |
|
178 |
self.agenda_pk = agenda_pk |
|
179 |
super(ImportEventsForm, self).__init__(**kwargs) |
|
180 | ||
177 | 181 |
def clean_events_csv_file(self): |
178 | 182 |
content = self.cleaned_data['events_csv_file'].read() |
179 | 183 |
if b'\0' in content: |
... | ... | |
209 | 213 |
if i == 0 and csvline[0].strip('#') in ('date', 'Date', _('date'), _('Date')): |
210 | 214 |
continue |
211 | 215 |
event = Event() |
216 |
event.agenda_id = self.agenda_pk |
|
212 | 217 |
for datetime_fmt in ('%Y-%m-%d %H:%M', '%d/%m/%Y %H:%M', |
213 | 218 |
'%d/%m/%Y %Hh%M', '%Y-%m-%d %H:%M:%S', '%d/%m/%Y %H:%M:%S'): |
214 | 219 |
try: |
... | ... | |
231 | 236 |
raise ValidationError(_('Invalid file format. (number of places in waiting list, line %d)') % (i+1)) |
232 | 237 |
if len(csvline) >= 5: |
233 | 238 |
event.label = force_text(csvline[4]) |
234 |
exclude = ['agenda', 'desk', 'meeting_type']
|
|
239 |
exclude = ['desk', 'meeting_type'] |
|
235 | 240 |
if len(csvline) >= 6: |
236 | 241 |
event.slug = ' '.join([force_text(x) for x in csvline[5:]]) |
237 | 242 |
else: |
chrono/manager/templates/chrono/manager_agenda_settings.html | ||
---|---|---|
50 | 50 |
data-total="{{event.waiting_list_places}}" data-booked="{{event.waiting_list}}" |
51 | 51 |
{% endif %} |
52 | 52 |
><a rel="popup" href="{% if user_can_manage %}{% url 'chrono-manager-event-edit' pk=event.id %}{% else %}#{% endif %}"> |
53 |
{% if event.label %}{{event.label}} {% endif %}[{% trans "identifier:" %} {{ event.slug }}] /
|
|
53 |
{% if event.label %}{{event.label}} / {% endif %}
|
|
54 | 54 |
{{ event.start_datetime }} |
55 | 55 |
{% if event.full %}/ <span class="full">{% trans "full" %}</span>{% endif %} |
56 | 56 |
( |
chrono/manager/views.py | ||
---|---|---|
591 | 591 |
template_name = 'chrono/manager_import_events.html' |
592 | 592 |
agenda = None |
593 | 593 | |
594 |
def get_form_kwargs(self): |
|
595 |
kwargs = super(AgendaImportEventsView, self).get_form_kwargs() |
|
596 |
kwargs['agenda_pk'] = self.kwargs['pk'] |
|
597 |
return kwargs |
|
598 | ||
594 | 599 |
def form_valid(self, form): |
595 | 600 |
if form.events: |
596 | 601 |
for event in form.events: |
597 | 602 |
event.agenda_id = self.kwargs['pk'] |
603 |
if event.slug and Event.objects.filter( |
|
604 |
agenda_id=event.agenda_id, |
|
605 |
slug=event.slug).exists(): |
|
606 |
raise ValidationError(_('Duplicated event identifier')) |
|
598 | 607 |
event.save() |
599 | 608 |
messages.info(self.request, _('%d events have been imported.') % len(form.events)) |
600 | 609 |
return super(AgendaImportEventsView, self).form_valid(form) |
tests/test_agendas.py | ||
---|---|---|
123 | 123 |
agenda.save() |
124 | 124 |
assert agenda.slug == 'foo-bar' |
125 | 125 | |
126 |
event = Event.objects.create(start_datetime=now(), places=42, agenda=agenda, label='Foo bar') |
|
127 |
assert event.slug == 'foo-bar' |
|
128 | ||
129 | 126 | |
130 | 127 |
def test_existing_slug(): |
131 | 128 |
agenda = Agenda(label=u'Foo bar', slug='bar') |
132 | 129 |
agenda.save() |
133 | 130 |
assert agenda.slug == 'bar' |
134 | 131 | |
135 |
event = Event.objects.create(start_datetime=now(), places=42, agenda=agenda, label='Foo bar', slug='bar') |
|
136 |
assert event.slug == 'bar' |
|
137 | ||
138 | 132 | |
139 | 133 |
def test_duplicate_slugs(): |
140 | 134 |
agenda = Agenda(label=u'Foo baz') |
... | ... | |
147 | 141 |
agenda.save() |
148 | 142 |
assert agenda.slug == 'foo-baz-2' |
149 | 143 | |
150 |
event = Event.objects.create(start_datetime=now(), places=42, agenda=agenda, label='Foo bar') |
|
151 |
assert event.slug == 'foo-bar' |
|
152 |
event = Event.objects.create(start_datetime=now(), places=42, agenda=agenda, label='Foo bar') |
|
153 |
assert event.slug == 'foo-bar-1' |
|
154 |
event = Event.objects.create(start_datetime=now(), places=42, agenda=agenda, label='Foo bar') |
|
155 |
assert event.slug == 'foo-bar-2' |
|
156 | ||
157 | 144 | |
158 | 145 |
def test_event_manager(): |
159 | 146 |
agenda = Agenda(label=u'Foo baz') |
tests/test_api.py | ||
---|---|---|
351 | 351 |
assert Booking.objects.count() == 1 |
352 | 352 | |
353 | 353 |
# access by slug |
354 |
event.slug = 'bar' |
|
355 |
event.save() |
|
354 | 356 |
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.slug)) |
355 | 357 |
assert Booking.objects.count() == 2 |
356 | 358 |
assert Booking.objects.filter(event__agenda=agenda).count() == 2 |
... | ... | |
474 | 476 |
def test_booking_api_fillslots(app, some_data, user): |
475 | 477 |
agenda = Agenda.objects.filter(label=u'Foo bar')[0] |
476 | 478 |
events_ids = [x.id for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()] |
479 |
for i, event in enumerate([x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()]): |
|
480 |
event.slug = 'event-%s' % i |
|
481 |
event.save() |
|
477 | 482 |
events_slugs = [x.slug for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()] |
478 | 483 |
assert len(events_ids) == 3 |
479 | 484 |
event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0] # first event |
... | ... | |
992 | 997 |
assert resp.json['places']['waiting_list_reserved'] == 1 |
993 | 998 | |
994 | 999 |
# access by slug |
1000 |
event.slug = 'bar' |
|
1001 |
event.save() |
|
995 | 1002 |
resp = app.get('/api/agenda/%s/status/%s/' % (agenda_id, event.slug)) |
996 | 1003 |
# not found event |
997 | 1004 |
resp = app.get('/api/agenda/%s/status/%s/' % (agenda_id, 'unknown'), status=404) |
... | ... | |
1453 | 1460 |
booking_url = event_data['api']['fillslot_url'] |
1454 | 1461 |
with CaptureQueriesContext(connection) as ctx: |
1455 | 1462 |
app.post(booking_url) |
1456 |
# 2 + idx: because of slug unicity |
|
1457 |
assert len(ctx.captured_queries) == queries_count_fillslot1 + 2 + idx |
|
1463 |
assert len(ctx.captured_queries) == queries_count_fillslot1 |
|
1458 | 1464 | |
1459 | 1465 |
with CaptureQueriesContext(connection) as ctx: |
1460 | 1466 |
app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id) |
tests/test_manager.py | ||
---|---|---|
328 | 328 |
resp = resp.form.submit() |
329 | 329 |
resp = resp.follow() |
330 | 330 |
event = Event.objects.get(places=10) |
331 |
assert event.slug is None |
|
331 | 332 |
assert not "This agenda doesn't have any event yet." in resp.text |
332 | 333 |
assert '/manage/events/%s/' % event.id in resp.text |
333 | 334 |
assert ('Feb. 15, %s, 5 p.m.' % year) in resp.text |
... | ... | |
651 | 652 |
resp = app.get('/manage/agendas/%s/import-events' % agenda.id, status=200) |
652 | 653 |
resp.form['events_csv_file'] = Upload('t.csv', b'2016-09-16,18:00,10,5,label,slug', 'text/csv') |
653 | 654 |
resp = resp.form.submit(status=200) |
654 |
assert 'Invalid file format. (slug: Event with this Identifier already exists.' in resp.text
|
|
655 |
assert 'Invalid file format. (__all__: Event with this Agenda and Identifier already exists.' in resp.text
|
|
655 | 656 | |
656 | 657 | |
657 | 658 |
def test_add_meetings_agenda(app, admin_user): |
658 |
- |