Projet

Général

Profil

0001-agenda-generate-event-slug-if-not-provided-44375.patch

Lauréline Guérin, 23 juin 2020 16:33

Télécharger (14,4 ko)

Voir les différences:

Subject: [PATCH] agenda: generate event slug if not provided (#44375)

 chrono/agendas/migrations/0049_event_slug.py  | 38 +++++++++++++++++++
 chrono/agendas/migrations/0050_event_slug.py  | 26 +++++++++++++
 chrono/agendas/models.py                      | 29 ++++++++++++--
 chrono/api/views.py                           | 17 +++++----
 .../manager_events_agenda_settings.html       |  2 +-
 tests/test_agendas.py                         | 29 ++++++++++++++
 tests/test_api.py                             |  7 +---
 tests/test_manager.py                         |  1 -
 8 files changed, 130 insertions(+), 19 deletions(-)
 create mode 100644 chrono/agendas/migrations/0049_event_slug.py
 create mode 100644 chrono/agendas/migrations/0050_event_slug.py
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
-