Create tournaments

This commit is contained in:
Yohann D'ANELLO 2020-12-31 12:13:42 +01:00
parent 03144ae58e
commit 4e29b4830a
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
13 changed files with 140 additions and 97 deletions

View File

@ -3,11 +3,13 @@
import re import re
from bootstrap_datepicker_plus import DatePickerInput, DateTimePickerInput
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils import formats
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .models import Participation, Team from .models import Participation, Team, Tournament
class TeamForm(forms.ModelForm): class TeamForm(forms.ModelForm):
@ -85,3 +87,25 @@ class ValidateParticipationForm(forms.Form):
label=_("Message to address to the team:"), label=_("Message to address to the team:"),
widget=forms.Textarea(), widget=forms.Textarea(),
) )
class TournamentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["date_start"].widget = DatePickerInput(
format=formats.get_format_lazy(format_type="DATE_INPUT_FORMATS", use_l10n=True)[0])
self.fields["date_end"].widget = DatePickerInput(
format=formats.get_format_lazy(format_type="DATE_INPUT_FORMATS", use_l10n=True)[0])
self.fields["inscription_limit"].widget = DateTimePickerInput(
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
self.fields["solution_limit"].widget = DateTimePickerInput(
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
self.fields["syntheses_first_phase_limit"].widget = DateTimePickerInput(
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
self.fields["syntheses_second_phase_limit"].widget = DateTimePickerInput(
format=formats.get_format_lazy(format_type="DATETIME_INPUT_FORMATS", use_l10n=True)[0])
class Meta:
model = Tournament
fields = '__all__'

View File

@ -1,4 +1,4 @@
# Generated by Django 3.1.4 on 2020-12-28 17:16 # Generated by Django 3.0.11 on 2020-12-30 12:02
import django.core.validators import django.core.validators
from django.db import migrations, models from django.db import migrations, models
@ -79,7 +79,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True, verbose_name='name')), ('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
('date_start', models.DateField(default=django.utils.timezone.now, verbose_name='start')), ('date_start', models.DateField(default=django.utils.timezone.now, verbose_name='start')),
('date_end', models.DateField(default=django.utils.timezone.now, verbose_name='start')), ('date_end', models.DateField(default=django.utils.timezone.now, verbose_name='end')),
('inscription_limit', models.DateTimeField(default=django.utils.timezone.now, verbose_name='limit date for registrations')), ('inscription_limit', models.DateTimeField(default=django.utils.timezone.now, verbose_name='limit date for registrations')),
('solution_limit', models.DateTimeField(default=django.utils.timezone.now, verbose_name='limit date to upload solutions')), ('solution_limit', models.DateTimeField(default=django.utils.timezone.now, verbose_name='limit date to upload solutions')),
('syntheses_first_phase_limit', models.DateTimeField(default=django.utils.timezone.now, verbose_name='limit date to upload the syntheses for the first phase')), ('syntheses_first_phase_limit', models.DateTimeField(default=django.utils.timezone.now, verbose_name='limit date to upload the syntheses for the first phase')),

View File

@ -1,5 +1,6 @@
# Generated by Django 3.1.4 on 2020-12-28 17:16 # Generated by Django 3.0.11 on 2020-12-30 12:02
import address.models
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -9,8 +10,9 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('participation', '0001_initial'),
('registration', '0001_initial'), ('registration', '0001_initial'),
('address', '0003_auto_20200830_1851'),
('participation', '0001_initial'),
] ]
operations = [ operations = [
@ -19,6 +21,11 @@ class Migration(migrations.Migration):
name='organizers', name='organizers',
field=models.ManyToManyField(related_name='organized_tournaments', to='registration.VolunteerRegistration', verbose_name='organizers'), field=models.ManyToManyField(related_name='organized_tournaments', to='registration.VolunteerRegistration', verbose_name='organizers'),
), ),
migrations.AddField(
model_name='tournament',
name='place',
field=address.models.AddressField(on_delete=django.db.models.deletion.CASCADE, to='address.Address', verbose_name='place'),
),
migrations.AddIndex( migrations.AddIndex(
model_name='team', model_name='team',
index=models.Index(fields=['trigram'], name='participati_trigram_239255_idx'), index=models.Index(fields=['trigram'], name='participati_trigram_239255_idx'),
@ -26,17 +33,17 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='synthesis', model_name='synthesis',
name='participation', name='participation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='participation.participation', verbose_name='participation'), field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='participation.Participation', verbose_name='participation'),
), ),
migrations.AddField( migrations.AddField(
model_name='synthesis', model_name='synthesis',
name='pool', name='pool',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='syntheses', to='participation.pool', verbose_name='pool'), field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='syntheses', to='participation.Pool', verbose_name='pool'),
), ),
migrations.AddField( migrations.AddField(
model_name='solution', model_name='solution',
name='participation', name='participation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='participation.participation', verbose_name='participation'), field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='participation.Participation', verbose_name='participation'),
), ),
migrations.AddField( migrations.AddField(
model_name='pool', model_name='pool',
@ -51,17 +58,17 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='pool', model_name='pool',
name='tournament', name='tournament',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pools', to='participation.tournament', verbose_name='tournament'), field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pools', to='participation.Tournament', verbose_name='tournament'),
), ),
migrations.AddField( migrations.AddField(
model_name='participation', model_name='participation',
name='team', name='team',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='participation.team', verbose_name='team'), field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='participation.Team', verbose_name='team'),
), ),
migrations.AddField( migrations.AddField(
model_name='participation', model_name='participation',
name='tournament', name='tournament',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='participation.tournament', verbose_name='tournament'), field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='participation.Tournament', verbose_name='tournament'),
), ),
migrations.AddIndex( migrations.AddIndex(
model_name='tournament', model_name='tournament',

View File

@ -3,6 +3,7 @@
import os import os
from address.models import AddressField
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.db import models from django.db import models
from django.db.models import Index from django.db.models import Index
@ -133,6 +134,10 @@ class Tournament(models.Model):
default=timezone.now, default=timezone.now,
) )
place = AddressField(
verbose_name=_("place"),
)
inscription_limit = models.DateTimeField( inscription_limit = models.DateTimeField(
verbose_name=_("limit date for registrations"), verbose_name=_("limit date for registrations"),
default=timezone.now, default=timezone.now,

View File

@ -0,0 +1,13 @@
{% extends "base.html" %}
{% load crispy_forms_filters i18n %}
{% block content %}
<form method="post">
<div id="form-content">
{% csrf_token %}
{{ form|crispy }}
</div>
<button class="btn btn-success" type="submit">{% trans "Create" %}</button>
</form>
{% endblock content %}

View File

@ -10,7 +10,7 @@
<div id="form-content"> <div id="form-content">
{% render_table table %} {% render_table table %}
{% if user.registration.is_admin %} {% if user.registration.is_admin %}
<a class="btn btn-block btn-success" href="#">{% trans "Add tournament" %}</a> <a class="btn btn-block btn-success" href="{% url "participation:tournament_create" %}">{% trans "Add tournament" %}</a>
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -6,7 +6,8 @@ from django.views.generic import TemplateView
from .views import CreateTeamView, JoinTeamView, \ from .views import CreateTeamView, JoinTeamView, \
MyParticipationDetailView, MyTeamDetailView, ParticipationDetailView, TeamAuthorizationsView, \ MyParticipationDetailView, MyTeamDetailView, ParticipationDetailView, TeamAuthorizationsView, \
TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, TournamentListView TeamDetailView, TeamLeaveView, TeamListView, TeamUpdateView, TournamentCreateView, TournamentDetailView, \
TournamentListView, TournamentUpdateView
app_name = "participation" app_name = "participation"
@ -23,5 +24,8 @@ urlpatterns = [
path("detail/", MyParticipationDetailView.as_view(), name="my_participation_detail"), path("detail/", MyParticipationDetailView.as_view(), name="my_participation_detail"),
path("detail/<int:pk>/", ParticipationDetailView.as_view(), name="participation_detail"), path("detail/<int:pk>/", ParticipationDetailView.as_view(), name="participation_detail"),
path("tournament/", TournamentListView.as_view(), name="tournament_list"), path("tournament/", TournamentListView.as_view(), name="tournament_list"),
path("tournament/create/", TournamentCreateView.as_view(), name="tournament_create"),
path("tournament/<int:pk>/", TournamentDetailView.as_view(), name="tournament_detail"),
path("tournament/<int:pk>/update/", TournamentUpdateView.as_view(), name="tournament_update"),
path("chat/", TemplateView.as_view(template_name="participation/chat.html"), name="chat") path("chat/", TemplateView.as_view(template_name="participation/chat.html"), name="chat")
] ]

View File

@ -23,7 +23,8 @@ from tfjm.lists import get_sympa_client
from tfjm.matrix import Matrix from tfjm.matrix import Matrix
from tfjm.views import AdminMixin from tfjm.views import AdminMixin
from .forms import JoinTeamForm, ParticipationForm, RequestValidationForm, TeamForm, ValidateParticipationForm from .forms import JoinTeamForm, ParticipationForm, RequestValidationForm, TeamForm, ValidateParticipationForm, \
TournamentForm
from .models import Participation, Team, Tournament from .models import Participation, Team, Tournament
from .tables import TeamTable, TournamentTable from .tables import TeamTable, TournamentTable
@ -404,5 +405,37 @@ class ParticipationDetailView(LoginRequiredMixin, DetailView):
class TournamentListView(SingleTableView): class TournamentListView(SingleTableView):
"""
Display the list of all tournaments.
"""
model = Tournament model = Tournament
table_class = TournamentTable table_class = TournamentTable
class TournamentCreateView(AdminMixin, CreateView):
"""
Create a new tournament.
"""
model = Tournament
form_class = TournamentForm
def get_success_url(self):
return reverse_lazy("participation:tournament_detail", args=(self.object.pk,))
class TournamentUpdateView(AdminMixin, UpdateView):
"""
Update tournament detail.
"""
model = Tournament
form_class = TournamentForm
def get_success_url(self):
return reverse_lazy("participation:tournament_detail", args=(self.object.pk,))
class TournamentDetailView(DetailView):
"""
Display tournament detail.
"""
model = Tournament

View File

@ -1,8 +1,11 @@
# Generated by Django 3.1.4 on 2020-12-28 17:16 # Generated by Django 3.0.11 on 2020-12-30 12:02
import address.models
import datetime
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import phonenumber_field.modelfields
import registration.models import registration.models
@ -11,9 +14,10 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('participation', '0001_initial'),
('contenttypes', '0002_remove_content_type_name'), ('contenttypes', '0002_remove_content_type_name'),
('address', '0003_auto_20200830_1851'),
('participation', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
@ -23,7 +27,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('give_contact_to_animath', models.BooleanField(default=False, verbose_name='Grant Animath to contact me in the future about other actions')), ('give_contact_to_animath', models.BooleanField(default=False, verbose_name='Grant Animath to contact me in the future about other actions')),
('email_confirmed', models.BooleanField(default=False, verbose_name='email confirmed')), ('email_confirmed', models.BooleanField(default=False, verbose_name='email confirmed')),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_registration.registration_set+', to='contenttypes.contenttype')), ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_registration.registration_set+', to='contenttypes.ContentType')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
], ],
options={ options={
@ -31,25 +35,16 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'registrations', 'verbose_name_plural': 'registrations',
}, },
), ),
migrations.CreateModel(
name='AdminRegistration',
fields=[
('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.registration')),
('role', models.TextField(verbose_name='role of the administrator')),
],
options={
'verbose_name': 'admin registration',
'verbose_name_plural': 'admin registrations',
},
bases=('registration.registration',),
),
migrations.CreateModel( migrations.CreateModel(
name='ParticipantRegistration', name='ParticipantRegistration',
fields=[ fields=[
('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.registration')), ('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.Registration')),
('birth_date', models.DateField(default=datetime.date.today, verbose_name='birth date')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, region=None, verbose_name='phone number')),
('photo_authorization', models.FileField(blank=True, default='', upload_to=registration.models.get_random_photo_filename, verbose_name='photo authorization')), ('photo_authorization', models.FileField(blank=True, default='', upload_to=registration.models.get_random_photo_filename, verbose_name='photo authorization')),
('health_sheet', models.FileField(blank=True, default='', upload_to=registration.models.get_random_health_filename, verbose_name='health sheet')), ('health_sheet', models.FileField(blank=True, default='', upload_to=registration.models.get_random_health_filename, verbose_name='health sheet')),
('team', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='participants', to='participation.team', verbose_name='team')), ('address', address.models.AddressField(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='address.Address', verbose_name='address')),
('team', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='participants', to='participation.Team', verbose_name='team')),
], ],
options={ options={
'abstract': False, 'abstract': False,
@ -60,7 +55,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='VolunteerRegistration', name='VolunteerRegistration',
fields=[ fields=[
('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.registration')), ('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.Registration')),
('professional_activity', models.TextField(verbose_name='professional activity')), ('professional_activity', models.TextField(verbose_name='professional activity')),
], ],
options={ options={
@ -69,10 +64,22 @@ class Migration(migrations.Migration):
}, },
bases=('registration.registration',), bases=('registration.registration',),
), ),
migrations.CreateModel(
name='AdminRegistration',
fields=[
('volunteerregistration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.VolunteerRegistration')),
('role', models.TextField(verbose_name='role of the administrator')),
],
options={
'verbose_name': 'admin registration',
'verbose_name_plural': 'admin registrations',
},
bases=('registration.volunteerregistration',),
),
migrations.CreateModel( migrations.CreateModel(
name='CoachRegistration', name='CoachRegistration',
fields=[ fields=[
('participantregistration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.participantregistration')), ('participantregistration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.ParticipantRegistration')),
('professional_activity', models.TextField(verbose_name='professional activity')), ('professional_activity', models.TextField(verbose_name='professional activity')),
], ],
options={ options={
@ -84,9 +91,12 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='StudentRegistration', name='StudentRegistration',
fields=[ fields=[
('participantregistration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.participantregistration')), ('participantregistration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.ParticipantRegistration')),
('student_class', models.IntegerField(choices=[(12, '12th grade'), (11, '11th grade'), (10, '10th grade or lower')], verbose_name='student class')), ('student_class', models.IntegerField(choices=[(12, '12th grade'), (11, '11th grade'), (10, '10th grade or lower')], verbose_name='student class')),
('school', models.CharField(max_length=255, verbose_name='school')), ('school', models.CharField(max_length=255, verbose_name='school')),
('responsible_name', models.CharField(default='', max_length=255, verbose_name='responsible name')),
('responsible_phone', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, region=None, verbose_name='responsible phone number')),
('responsible_email', models.EmailField(default='', max_length=254, verbose_name='responsible email address')),
('parental_authorization', models.FileField(blank=True, default='', upload_to=registration.models.get_random_parental_filename, verbose_name='parental authorization')), ('parental_authorization', models.FileField(blank=True, default='', upload_to=registration.models.get_random_parental_filename, verbose_name='parental authorization')),
], ],
options={ options={

View File

@ -1,48 +0,0 @@
# Generated by Django 3.0.11 on 2020-12-28 21:19
import address.models
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import phonenumber_field.modelfields
class Migration(migrations.Migration):
dependencies = [
('address', '0003_auto_20200830_1851'),
('registration', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='participantregistration',
name='address',
field=address.models.AddressField(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='address.Address', verbose_name='address'),
),
migrations.AddField(
model_name='participantregistration',
name='birth_date',
field=models.DateField(default=django.utils.timezone.now, verbose_name='birth date'),
),
migrations.AddField(
model_name='participantregistration',
name='phone_number',
field=phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, region=None, verbose_name='phone number'),
),
migrations.AddField(
model_name='studentregistration',
name='responsible_email',
field=models.EmailField(default='', max_length=254, verbose_name='responsible email address'),
),
migrations.AddField(
model_name='studentregistration',
name='responsible_name',
field=models.CharField(default='', max_length=255, verbose_name='responsible name'),
),
migrations.AddField(
model_name='studentregistration',
name='responsible_phone',
field=phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, region=None, verbose_name='responsible phone number'),
),
]

View File

@ -259,7 +259,7 @@ class VolunteerRegistration(Registration):
return VolunteerRegistrationForm return VolunteerRegistrationForm
class AdminRegistration(Registration): class AdminRegistration(VolunteerRegistration):
""" """
Specific registration for admins. Specific registration for admins.
They have a field to justify they status. They have a field to justify they status.

View File

@ -233,3 +233,6 @@ PHONENUMBER_DB_FORMAT = 'NATIONAL'
PHONENUMBER_DEFAULT_REGION = 'FR' PHONENUMBER_DEFAULT_REGION = 'FR'
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
# Use local Jquery
JQUERY_URL = False

View File

@ -19,23 +19,15 @@
{% endif %} {% endif %}
{# Bootstrap CSS #} {# Bootstrap CSS #}
<link rel="stylesheet" <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/all.css"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/all.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/v4-shims.css"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/v4-shims.css">
{# JQuery, Bootstrap and Turbolinks JavaScript #} {# JQuery, Bootstrap and Turbolinks JavaScript #}
<script src="https://code.jquery.com/jquery-3.4.1.min.js" <script src="https://code.jquery.com/jquery-3.5.1.min.js" crossorigin="anonymous"></script>
integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh" <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"
crossorigin="anonymous"></script> integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.2.0/turbolinks.js" <script src="https://cdnjs.cloudflare.com/ajax/libs/turbolinks/5.2.0/turbolinks.js"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>