From 8a56e7a28042da77c50a1b73848d98d0d893e75d Mon Sep 17 00:00:00 2001 From: Constantin Gierczak--Galle Date: Mon, 12 Feb 2024 10:30:15 +0100 Subject: [PATCH] Remove many things --- bda/__init__.py | 0 bda/admin.py | 350 ----- bda/algorithm.py | 102 -- bda/forms.py | 184 --- bda/management/__init__.py | 0 bda/management/commands/__init__.py | 0 bda/management/commands/loadbdadevdata.py | 101 -- bda/management/commands/manage_reventes.py | 49 - bda/management/commands/sendrappels.py | 33 - bda/management/data/locations.json | 26 - bda/management/data/shows.json | 100 -- bda/migrations/0001_initial.py | 205 --- bda/migrations/0002_add_tirage.py | 111 -- .../0003_update_tirage_and_spectacle.py | 21 - bda/migrations/0004_mails-rappel.py | 26 - bda/migrations/0005_encoding.py | 28 - bda/migrations/0006_add_tirage_switch.py | 33 - bda/migrations/0007_extends_spectacle.py | 99 -- bda/migrations/0008_py3.py | 109 -- bda/migrations/0009_revente.py | 86 -- .../0010_spectaclerevente_shotgun.py | 35 - .../0011_tirage_appear_catalogue.py | 18 - bda/migrations/0012_notif_time.py | 30 - bda/migrations/0012_swap_double_choice.py | 50 - bda/migrations/0013_merge_20180524_2123.py | 11 - bda/migrations/0014_attribution_paid_field.py | 30 - bda/migrations/0015_move_bda_payment.py | 36 - .../0016_delete_participant_paid.py | 12 - .../0017_participant_accepte_charte.py | 17 - bda/migrations/0018_auto_20201021_1818.py | 37 - bda/migrations/__init__.py | 0 bda/models.py | 490 ------- bda/static/bda/css/bda.css | 125 -- bda/templates/bda-attrib-extra.html | 28 - bda/templates/bda-attrib.html | 49 - bda/templates/bda-emails.html | 7 - bda/templates/bda-token.html | 13 - bda/templates/bda-unpaid.html | 8 - bda/templates/bda/etat-places.html | 50 - .../bda/forms/attribution_label_table.html | 1 - bda/templates/bda/forms/checkbox_table.html | 4 - bda/templates/bda/forms/date_tirage.html | 1 - .../bda/forms/revente_other_label_table.html | 3 - .../bda/forms/revente_self_label_table.html | 2 - .../bda/forms/revente_sold_label_table.html | 4 - .../bda/forms/spectacle_label_table.html | 4 - bda/templates/bda/inscription-formset.html | 41 - bda/templates/bda/inscription-tirage.html | 169 --- bda/templates/bda/mails-rappel.html | 41 - .../bda/mails/attributions-decus.txt | 10 - bda/templates/bda/mails/attributions.txt | 31 - bda/templates/bda/mails/rappel.txt | 23 - bda/templates/bda/mails/revente-new.txt | 12 - bda/templates/bda/mails/revente-seller.txt | 13 - .../bda/mails/revente-shotgun-seller.txt | 6 - bda/templates/bda/mails/revente-shotgun.txt | 11 - .../bda/mails/revente-tirage-loser.txt | 9 - .../bda/mails/revente-tirage-seller.txt | 7 - .../bda/mails/revente-tirage-winner.txt | 7 - bda/templates/bda/participants.html | 71 - .../bda/resume-inscription-tirage.html | 14 - bda/templates/bda/resume_places.html | 25 - .../bda/revente/confirm-shotgun.html | 20 - bda/templates/bda/revente/confirmed.html | 9 - bda/templates/bda/revente/mail-success.html | 8 - bda/templates/bda/revente/manage.html | 121 -- bda/templates/bda/revente/none.html | 6 - bda/templates/bda/revente/shotgun.html | 29 - bda/templates/bda/revente/subscribe.html | 64 - bda/templates/bda/revente/tirages.html | 99 -- bda/templates/bda/revente/wrongtime.html | 13 - bda/templates/spectacle_list.html | 53 - bda/templates/tirage-failed.html | 10 - bda/tests/__init__.py | 0 bda/tests/mixins.py | 65 - bda/tests/test_models.py | 100 -- bda/tests/test_revente.py | 79 -- bda/tests/test_views.py | 368 ----- bda/tests/utils.py | 36 - bda/urls.py | 74 - bda/views.py | 911 ------------- bds/__init__.py | 1 - bds/admin.py | 5 - bds/apps.py | 29 - bds/autocomplete.py | 63 - bds/forms.py | 41 - bds/migrations/0001_initial.py | 141 -- bds/migrations/0002_bds_group.py | 16 - bds/migrations/0003_staff_permission.py | 24 - bds/migrations/0004_is_member_cotiz_type.py | 33 - ...0005_remove_bdsprofile_certificate_file.py | 16 - bds/migrations/0006_bdsprofile_comments.py | 22 - bds/migrations/__init__.py | 0 bds/mixins.py | 122 -- bds/models.py | 113 -- bds/static/bds/css/bds.css | 1 - bds/static/bds/css/bds.css.map | 1 - bds/static/bds/images/logo.svg | 15 - bds/static/bds/images/logo_square.svg | 42 - bds/static/bds/images/logout.svg | 80 -- bds/static/bds/js/bds.js | 7 - bds/static/src/sass/bds.scss | 152 --- bds/templates/bds/base.html | 50 - bds/templates/bds/expired_members.html | 22 - bds/templates/bds/forms/checkbox.html | 16 - bds/templates/bds/forms/field.html | 33 - bds/templates/bds/forms/file.html | 31 - bds/templates/bds/forms/form.html | 22 - bds/templates/bds/forms/input.html | 19 - bds/templates/bds/forms/other.html | 19 - bds/templates/bds/forms/radio.html | 24 - bds/templates/bds/forms/select.html | 21 - bds/templates/bds/forms/textarea.html | 19 - bds/templates/bds/home.html | 62 - bds/templates/bds/nav.html | 57 - bds/templates/bds/search_results.html | 21 - bds/templates/bds/user_create.html | 33 - bds/templates/bds/user_update.html | 104 -- bds/templatetags/bulma_utils.py | 74 - bds/tests/__init__.py | 0 bds/tests/test_views.py | 76 -- bds/urls.py | 24 - bds/views.py | 184 --- clubs/__init__.py | 0 clubs/admin.py | 5 - clubs/apps.py | 5 - clubs/migrations/0001_initial.py | 45 - clubs/migrations/__init__.py | 0 clubs/models.py | 16 - clubs/views.py | 0 events/__init__.py | 0 events/admin.py | 5 - events/apps.py | 5 - events/migrations/0001_event.py | 66 - events/migrations/0002_event_subscribers.py | 21 - .../0003_options_and_extra_fields.py | 198 --- events/migrations/0004_unique_constraints.py | 34 - events/migrations/__init__.py | 0 events/models.py | 171 --- events/tests/__init__.py | 0 events/tests/test_views.py | 161 --- events/urls.py | 12 - events/views.py | 55 - gestioasso/__init__.py | 0 gestioasso/apps.py | 5 - gestioasso/asgi.py | 8 - gestioasso/locale/__init__.py | 0 gestioasso/locale/en/__init__.py | 0 gestioasso/locale/en/formats.py | 11 - gestioasso/locale/fr/__init__.py | 0 gestioasso/locale/fr/formats.py | 7 - gestioasso/routing.py | 3 - gestioasso/settings/.gitignore | 1 - gestioasso/settings/__init__.py | 0 gestioasso/settings/bds_prod.py | 38 - gestioasso/settings/cof_prod.py | 234 ---- gestioasso/settings/common.py | 140 -- gestioasso/settings/dev.py | 64 - gestioasso/settings/local.py | 80 -- gestioasso/settings/secret_example.py | 25 - gestioasso/urls.py | 73 - gestioasso/wsgi.py | 6 - gestioncof/__init__.py | 1 - gestioncof/admin.py | 310 ----- gestioncof/apps.py | 18 - gestioncof/autocomplete.py | 58 - gestioncof/cms/__init__.py | 1 - gestioncof/cms/apps.py | 7 - gestioncof/cms/fixtures/examplesite.json | 696 ---------- gestioncof/cms/fixtures/examplesite0.json | 641 --------- gestioncof/cms/forms.py | 15 - .../cms/locale/en/LC_MESSAGES/django.mo | Bin 1740 -> 0 bytes .../cms/locale/en/LC_MESSAGES/django.po | 118 -- gestioncof/cms/migrations/0001_initial.py | 472 ------- .../cms/migrations/0002_auto_20190523_1521.py | 31 - .../0003_directory_entry_optional_links.py | 106 -- .../cms/migrations/0004_auto_20200829_2314.py | 133 -- gestioncof/cms/migrations/__init__.py | 0 gestioncof/cms/models.py | 229 ---- gestioncof/cms/static/cofcms/config.rb | 25 - gestioncof/cms/static/cofcms/css/screen.css | 781 ----------- gestioncof/cms/static/cofcms/images/en.png | Bin 3372 -> 0 bytes gestioncof/cms/static/cofcms/images/fr.png | Bin 300 -> 0 bytes .../cms/static/cofcms/images/minimenu.svg | 11 - gestioncof/cms/static/cofcms/js/calendar.js | 32 - gestioncof/cms/static/cofcms/js/script.js | 13 - .../cms/static/cofcms/sass/_colors.scss | 11 - .../cms/static/cofcms/sass/_responsive.scss | 128 -- gestioncof/cms/static/cofcms/sass/screen.scss | 564 -------- gestioncof/cms/templates/cofcms/base.html | 62 - .../cms/templates/cofcms/base_aside.html | 12 - gestioncof/cms/templates/cofcms/base_nav.html | 20 - gestioncof/cms/templates/cofcms/calendar.html | 28 - .../cms/templates/cofcms/calendar_raw.html | 2 - .../templates/cofcms/cof_actu_index_page.html | 53 - .../cms/templates/cofcms/cof_actu_page.html | 17 - .../cofcms/cof_directory_entry_page.html | 11 - .../templates/cofcms/cof_directory_page.html | 50 - gestioncof/cms/templates/cofcms/cof_page.html | 29 - .../cms/templates/cofcms/cof_root_page.html | 41 - .../cms/templates/cofcms/mini_calendar.html | 11 - gestioncof/cms/templates/cofcms/sympa.html | 32 - gestioncof/cms/templatetags/__init__.py | 0 gestioncof/cms/templatetags/cofcms_tags.py | 167 --- gestioncof/cms/translation.py | 41 - gestioncof/cms/urls.py | 0 gestioncof/cms/views.py | 22 - gestioncof/csv_views.py | 76 -- gestioncof/decorators.py | 81 -- gestioncof/fixtures/gestion.json | 199 --- gestioncof/fixtures/sites.json | 10 - gestioncof/forms.py | 460 ------- gestioncof/management/__init__.py | 0 gestioncof/management/base.py | 41 - gestioncof/management/commands/__init__.py | 0 gestioncof/management/commands/loaddevdata.py | 116 -- gestioncof/management/data/gaulois.json | 368 ----- gestioncof/management/data/romains.json | 614 --------- gestioncof/migrations/0001_initial.py | 855 ------------ .../0002_enable_unprocessed_demandes.py | 18 - gestioncof/migrations/0003_event_image.py | 18 - .../migrations/0004_registration_mail.py | 34 - gestioncof/migrations/0005_encoding.py | 75 - gestioncof/migrations/0006_add_calendar.py | 65 - gestioncof/migrations/0007_alter_club.py | 43 - gestioncof/migrations/0008_py3.py | 275 ---- gestioncof/migrations/0009_delete_clipper.py | 10 - .../migrations/0010_delete_custommail.py | 9 - gestioncof/migrations/0011_longer_clippers.py | 18 - .../migrations/0011_remove_cofprofile_num.py | 10 - gestioncof/migrations/0012_merge.py | 13 - gestioncof/migrations/0013_pei.py | 46 - .../0014_cofprofile_mailing_unernestaparis.py | 19 - .../migrations/0015_psql_choices_niveaux.py | 47 - gestioncof/migrations/0016_unique_clippers.py | 44 - .../migrations/0017_petitscours_uniqueness.py | 18 - .../migrations/0018_petitscours_email.py | 17 - .../0019_cofprofile_date_adhesion.py | 19 - gestioncof/migrations/__init__.py | 0 gestioncof/models.py | 268 ---- gestioncof/shared.py | 25 - gestioncof/signals.py | 22 - gestioncof/static/gestioncof/css/cof.css | 1151 ---------------- .../gestioncof/src/jquery.ui.touch-punch.js | 180 --- .../static/gestioncof/src/stupidtable.js | 158 --- .../vendor/jquery.ui.touch-punch.min.js | 11 - .../gestioncof/vendor/stupidtable.min.js | 3 - gestioncof/templates/404.html | 65 - gestioncof/templates/500.html | 5 - gestioncof/templates/base.html | 27 - gestioncof/templates/base_title.html | 20 - gestioncof/templates/buro-denied.html | 5 - gestioncof/templates/cof-denied.html | 5 - gestioncof/templates/event_status.html | 51 - .../templates/gestioncof/banner_update.html | 23 - .../templates/gestioncof/base_header.html | 44 - .../gestioncof/calendar_subscription.html | 49 - gestioncof/templates/gestioncof/event.html | 13 - gestioncof/templates/gestioncof/home.html | 150 -- .../templates/gestioncof/mails/welcome.txt | 11 - gestioncof/templates/gestioncof/profile.html | 26 - .../gestioncof/registration_form.html | 32 - .../gestioncof/registration_post.html | 8 - .../templates/gestioncof/reset_comptes.html | 14 - .../templates/gestioncof/search_results.html | 21 - gestioncof/templates/gestioncof/survey.html | 22 - .../templates/gestioncof/utile_cof.html | 24 - gestioncof/templates/liste_clubs.html | 25 - gestioncof/templates/liste_mails.html | 7 - gestioncof/templates/login.html | 36 - gestioncof/templates/login_error.html | 11 - gestioncof/templates/login_switch.html | 32 - gestioncof/templates/logout.html | 7 - gestioncof/templates/membres_clubs.html | 41 - gestioncof/templates/registration.html | 41 - .../registration/password_change_done.html | 9 - .../registration/password_change_form.html | 13 - gestioncof/templates/survey_status.html | 52 - gestioncof/templates/tristate_js.html | 70 - gestioncof/templates/utile_bda.html | 13 - gestioncof/templatetags/utils.py | 14 - gestioncof/tests/__init__.py | 0 gestioncof/tests/mixins.py | 74 - gestioncof/tests/test_legacy.py | 28 - gestioncof/tests/test_views.py | 1209 ----------------- gestioncof/tests/utils.py | 68 - gestioncof/urls.py | 165 --- gestioncof/views.py | 980 ------------- gestioncof/widgets.py | 20 - petitscours/__init__.py | 0 petitscours/forms.py | 52 - petitscours/models.py | 241 ---- .../templates/petitscours/base_title.html | 2 - .../templates/petitscours/demande.html | 16 - .../templates/petitscours/demande_detail.html | 50 - .../templates/petitscours/demande_list.html | 47 - .../templates/petitscours/demande_raw.html | 19 - .../petitscours/details_demande_infos.html | 14 - .../templates/petitscours/inscription.html | 117 -- .../petitscours/inscription_formset.html | 40 - .../templates/petitscours/mails/demandeur.txt | 17 - .../templates/petitscours/mails/eleve.txt | 28 - .../petitscours/traitement_demande.html | 59 - .../traitement_demande_autre_niveau.html | 66 - .../traitement_demande_success.html | 7 - petitscours/tests/__init__.py | 0 petitscours/tests/test_views.py | 326 ----- petitscours/tests/utils.py | 27 - petitscours/urls.py | 37 - petitscours/views.py | 315 ----- requirements.txt | 5 +- shell.nix | 39 +- 312 files changed, 13 insertions(+), 24088 deletions(-) delete mode 100644 bda/__init__.py delete mode 100644 bda/admin.py delete mode 100644 bda/algorithm.py delete mode 100644 bda/forms.py delete mode 100644 bda/management/__init__.py delete mode 100644 bda/management/commands/__init__.py delete mode 100644 bda/management/commands/loadbdadevdata.py delete mode 100644 bda/management/commands/manage_reventes.py delete mode 100644 bda/management/commands/sendrappels.py delete mode 100644 bda/management/data/locations.json delete mode 100644 bda/management/data/shows.json delete mode 100644 bda/migrations/0001_initial.py delete mode 100644 bda/migrations/0002_add_tirage.py delete mode 100644 bda/migrations/0003_update_tirage_and_spectacle.py delete mode 100644 bda/migrations/0004_mails-rappel.py delete mode 100644 bda/migrations/0005_encoding.py delete mode 100644 bda/migrations/0006_add_tirage_switch.py delete mode 100644 bda/migrations/0007_extends_spectacle.py delete mode 100644 bda/migrations/0008_py3.py delete mode 100644 bda/migrations/0009_revente.py delete mode 100644 bda/migrations/0010_spectaclerevente_shotgun.py delete mode 100644 bda/migrations/0011_tirage_appear_catalogue.py delete mode 100644 bda/migrations/0012_notif_time.py delete mode 100644 bda/migrations/0012_swap_double_choice.py delete mode 100644 bda/migrations/0013_merge_20180524_2123.py delete mode 100644 bda/migrations/0014_attribution_paid_field.py delete mode 100644 bda/migrations/0015_move_bda_payment.py delete mode 100644 bda/migrations/0016_delete_participant_paid.py delete mode 100644 bda/migrations/0017_participant_accepte_charte.py delete mode 100644 bda/migrations/0018_auto_20201021_1818.py delete mode 100644 bda/migrations/__init__.py delete mode 100644 bda/models.py delete mode 100644 bda/static/bda/css/bda.css delete mode 100644 bda/templates/bda-attrib-extra.html delete mode 100644 bda/templates/bda-attrib.html delete mode 100644 bda/templates/bda-emails.html delete mode 100644 bda/templates/bda-token.html delete mode 100644 bda/templates/bda-unpaid.html delete mode 100644 bda/templates/bda/etat-places.html delete mode 100644 bda/templates/bda/forms/attribution_label_table.html delete mode 100644 bda/templates/bda/forms/checkbox_table.html delete mode 100644 bda/templates/bda/forms/date_tirage.html delete mode 100644 bda/templates/bda/forms/revente_other_label_table.html delete mode 100644 bda/templates/bda/forms/revente_self_label_table.html delete mode 100644 bda/templates/bda/forms/revente_sold_label_table.html delete mode 100644 bda/templates/bda/forms/spectacle_label_table.html delete mode 100644 bda/templates/bda/inscription-formset.html delete mode 100644 bda/templates/bda/inscription-tirage.html delete mode 100644 bda/templates/bda/mails-rappel.html delete mode 100644 bda/templates/bda/mails/attributions-decus.txt delete mode 100644 bda/templates/bda/mails/attributions.txt delete mode 100644 bda/templates/bda/mails/rappel.txt delete mode 100644 bda/templates/bda/mails/revente-new.txt delete mode 100644 bda/templates/bda/mails/revente-seller.txt delete mode 100644 bda/templates/bda/mails/revente-shotgun-seller.txt delete mode 100644 bda/templates/bda/mails/revente-shotgun.txt delete mode 100644 bda/templates/bda/mails/revente-tirage-loser.txt delete mode 100644 bda/templates/bda/mails/revente-tirage-seller.txt delete mode 100644 bda/templates/bda/mails/revente-tirage-winner.txt delete mode 100644 bda/templates/bda/participants.html delete mode 100644 bda/templates/bda/resume-inscription-tirage.html delete mode 100644 bda/templates/bda/resume_places.html delete mode 100644 bda/templates/bda/revente/confirm-shotgun.html delete mode 100644 bda/templates/bda/revente/confirmed.html delete mode 100644 bda/templates/bda/revente/mail-success.html delete mode 100644 bda/templates/bda/revente/manage.html delete mode 100644 bda/templates/bda/revente/none.html delete mode 100644 bda/templates/bda/revente/shotgun.html delete mode 100644 bda/templates/bda/revente/subscribe.html delete mode 100644 bda/templates/bda/revente/tirages.html delete mode 100644 bda/templates/bda/revente/wrongtime.html delete mode 100644 bda/templates/spectacle_list.html delete mode 100644 bda/templates/tirage-failed.html delete mode 100644 bda/tests/__init__.py delete mode 100644 bda/tests/mixins.py delete mode 100644 bda/tests/test_models.py delete mode 100644 bda/tests/test_revente.py delete mode 100644 bda/tests/test_views.py delete mode 100644 bda/tests/utils.py delete mode 100644 bda/urls.py delete mode 100644 bda/views.py delete mode 100644 bds/__init__.py delete mode 100644 bds/admin.py delete mode 100644 bds/apps.py delete mode 100644 bds/autocomplete.py delete mode 100644 bds/forms.py delete mode 100644 bds/migrations/0001_initial.py delete mode 100644 bds/migrations/0002_bds_group.py delete mode 100644 bds/migrations/0003_staff_permission.py delete mode 100644 bds/migrations/0004_is_member_cotiz_type.py delete mode 100644 bds/migrations/0005_remove_bdsprofile_certificate_file.py delete mode 100644 bds/migrations/0006_bdsprofile_comments.py delete mode 100644 bds/migrations/__init__.py delete mode 100644 bds/mixins.py delete mode 100644 bds/models.py delete mode 100644 bds/static/bds/css/bds.css delete mode 100644 bds/static/bds/css/bds.css.map delete mode 100644 bds/static/bds/images/logo.svg delete mode 100644 bds/static/bds/images/logo_square.svg delete mode 100644 bds/static/bds/images/logout.svg delete mode 100644 bds/static/bds/js/bds.js delete mode 100644 bds/static/src/sass/bds.scss delete mode 100644 bds/templates/bds/base.html delete mode 100644 bds/templates/bds/expired_members.html delete mode 100644 bds/templates/bds/forms/checkbox.html delete mode 100644 bds/templates/bds/forms/field.html delete mode 100644 bds/templates/bds/forms/file.html delete mode 100644 bds/templates/bds/forms/form.html delete mode 100644 bds/templates/bds/forms/input.html delete mode 100644 bds/templates/bds/forms/other.html delete mode 100644 bds/templates/bds/forms/radio.html delete mode 100644 bds/templates/bds/forms/select.html delete mode 100644 bds/templates/bds/forms/textarea.html delete mode 100644 bds/templates/bds/home.html delete mode 100644 bds/templates/bds/nav.html delete mode 100644 bds/templates/bds/search_results.html delete mode 100644 bds/templates/bds/user_create.html delete mode 100644 bds/templates/bds/user_update.html delete mode 100644 bds/templatetags/bulma_utils.py delete mode 100644 bds/tests/__init__.py delete mode 100644 bds/tests/test_views.py delete mode 100644 bds/urls.py delete mode 100644 bds/views.py delete mode 100644 clubs/__init__.py delete mode 100644 clubs/admin.py delete mode 100644 clubs/apps.py delete mode 100644 clubs/migrations/0001_initial.py delete mode 100644 clubs/migrations/__init__.py delete mode 100644 clubs/models.py delete mode 100644 clubs/views.py delete mode 100644 events/__init__.py delete mode 100644 events/admin.py delete mode 100644 events/apps.py delete mode 100644 events/migrations/0001_event.py delete mode 100644 events/migrations/0002_event_subscribers.py delete mode 100644 events/migrations/0003_options_and_extra_fields.py delete mode 100644 events/migrations/0004_unique_constraints.py delete mode 100644 events/migrations/__init__.py delete mode 100644 events/models.py delete mode 100644 events/tests/__init__.py delete mode 100644 events/tests/test_views.py delete mode 100644 events/urls.py delete mode 100644 events/views.py delete mode 100644 gestioasso/__init__.py delete mode 100644 gestioasso/apps.py delete mode 100644 gestioasso/asgi.py delete mode 100644 gestioasso/locale/__init__.py delete mode 100644 gestioasso/locale/en/__init__.py delete mode 100644 gestioasso/locale/en/formats.py delete mode 100644 gestioasso/locale/fr/__init__.py delete mode 100644 gestioasso/locale/fr/formats.py delete mode 100644 gestioasso/routing.py delete mode 100644 gestioasso/settings/.gitignore delete mode 100644 gestioasso/settings/__init__.py delete mode 100644 gestioasso/settings/bds_prod.py delete mode 100644 gestioasso/settings/cof_prod.py delete mode 100644 gestioasso/settings/common.py delete mode 100644 gestioasso/settings/dev.py delete mode 100644 gestioasso/settings/local.py delete mode 100644 gestioasso/settings/secret_example.py delete mode 100644 gestioasso/urls.py delete mode 100644 gestioasso/wsgi.py delete mode 100644 gestioncof/__init__.py delete mode 100644 gestioncof/admin.py delete mode 100644 gestioncof/apps.py delete mode 100644 gestioncof/autocomplete.py delete mode 100644 gestioncof/cms/__init__.py delete mode 100644 gestioncof/cms/apps.py delete mode 100644 gestioncof/cms/fixtures/examplesite.json delete mode 100644 gestioncof/cms/fixtures/examplesite0.json delete mode 100644 gestioncof/cms/forms.py delete mode 100644 gestioncof/cms/locale/en/LC_MESSAGES/django.mo delete mode 100644 gestioncof/cms/locale/en/LC_MESSAGES/django.po delete mode 100644 gestioncof/cms/migrations/0001_initial.py delete mode 100644 gestioncof/cms/migrations/0002_auto_20190523_1521.py delete mode 100644 gestioncof/cms/migrations/0003_directory_entry_optional_links.py delete mode 100644 gestioncof/cms/migrations/0004_auto_20200829_2314.py delete mode 100644 gestioncof/cms/migrations/__init__.py delete mode 100644 gestioncof/cms/models.py delete mode 100644 gestioncof/cms/static/cofcms/config.rb delete mode 100644 gestioncof/cms/static/cofcms/css/screen.css delete mode 100644 gestioncof/cms/static/cofcms/images/en.png delete mode 100644 gestioncof/cms/static/cofcms/images/fr.png delete mode 100644 gestioncof/cms/static/cofcms/images/minimenu.svg delete mode 100644 gestioncof/cms/static/cofcms/js/calendar.js delete mode 100644 gestioncof/cms/static/cofcms/js/script.js delete mode 100644 gestioncof/cms/static/cofcms/sass/_colors.scss delete mode 100644 gestioncof/cms/static/cofcms/sass/_responsive.scss delete mode 100644 gestioncof/cms/static/cofcms/sass/screen.scss delete mode 100644 gestioncof/cms/templates/cofcms/base.html delete mode 100644 gestioncof/cms/templates/cofcms/base_aside.html delete mode 100644 gestioncof/cms/templates/cofcms/base_nav.html delete mode 100644 gestioncof/cms/templates/cofcms/calendar.html delete mode 100644 gestioncof/cms/templates/cofcms/calendar_raw.html delete mode 100644 gestioncof/cms/templates/cofcms/cof_actu_index_page.html delete mode 100644 gestioncof/cms/templates/cofcms/cof_actu_page.html delete mode 100644 gestioncof/cms/templates/cofcms/cof_directory_entry_page.html delete mode 100644 gestioncof/cms/templates/cofcms/cof_directory_page.html delete mode 100644 gestioncof/cms/templates/cofcms/cof_page.html delete mode 100644 gestioncof/cms/templates/cofcms/cof_root_page.html delete mode 100644 gestioncof/cms/templates/cofcms/mini_calendar.html delete mode 100644 gestioncof/cms/templates/cofcms/sympa.html delete mode 100644 gestioncof/cms/templatetags/__init__.py delete mode 100644 gestioncof/cms/templatetags/cofcms_tags.py delete mode 100644 gestioncof/cms/translation.py delete mode 100644 gestioncof/cms/urls.py delete mode 100644 gestioncof/cms/views.py delete mode 100644 gestioncof/csv_views.py delete mode 100644 gestioncof/decorators.py delete mode 100644 gestioncof/fixtures/gestion.json delete mode 100644 gestioncof/fixtures/sites.json delete mode 100644 gestioncof/forms.py delete mode 100644 gestioncof/management/__init__.py delete mode 100644 gestioncof/management/base.py delete mode 100644 gestioncof/management/commands/__init__.py delete mode 100644 gestioncof/management/commands/loaddevdata.py delete mode 100644 gestioncof/management/data/gaulois.json delete mode 100644 gestioncof/management/data/romains.json delete mode 100644 gestioncof/migrations/0001_initial.py delete mode 100644 gestioncof/migrations/0002_enable_unprocessed_demandes.py delete mode 100644 gestioncof/migrations/0003_event_image.py delete mode 100644 gestioncof/migrations/0004_registration_mail.py delete mode 100644 gestioncof/migrations/0005_encoding.py delete mode 100644 gestioncof/migrations/0006_add_calendar.py delete mode 100644 gestioncof/migrations/0007_alter_club.py delete mode 100644 gestioncof/migrations/0008_py3.py delete mode 100644 gestioncof/migrations/0009_delete_clipper.py delete mode 100644 gestioncof/migrations/0010_delete_custommail.py delete mode 100644 gestioncof/migrations/0011_longer_clippers.py delete mode 100644 gestioncof/migrations/0011_remove_cofprofile_num.py delete mode 100644 gestioncof/migrations/0012_merge.py delete mode 100644 gestioncof/migrations/0013_pei.py delete mode 100644 gestioncof/migrations/0014_cofprofile_mailing_unernestaparis.py delete mode 100644 gestioncof/migrations/0015_psql_choices_niveaux.py delete mode 100644 gestioncof/migrations/0016_unique_clippers.py delete mode 100644 gestioncof/migrations/0017_petitscours_uniqueness.py delete mode 100644 gestioncof/migrations/0018_petitscours_email.py delete mode 100644 gestioncof/migrations/0019_cofprofile_date_adhesion.py delete mode 100644 gestioncof/migrations/__init__.py delete mode 100644 gestioncof/models.py delete mode 100644 gestioncof/shared.py delete mode 100644 gestioncof/signals.py delete mode 100644 gestioncof/static/gestioncof/css/cof.css delete mode 100644 gestioncof/static/gestioncof/src/jquery.ui.touch-punch.js delete mode 100644 gestioncof/static/gestioncof/src/stupidtable.js delete mode 100644 gestioncof/static/gestioncof/vendor/jquery.ui.touch-punch.min.js delete mode 100644 gestioncof/static/gestioncof/vendor/stupidtable.min.js delete mode 100644 gestioncof/templates/404.html delete mode 100644 gestioncof/templates/500.html delete mode 100644 gestioncof/templates/base.html delete mode 100644 gestioncof/templates/base_title.html delete mode 100644 gestioncof/templates/buro-denied.html delete mode 100644 gestioncof/templates/cof-denied.html delete mode 100644 gestioncof/templates/event_status.html delete mode 100644 gestioncof/templates/gestioncof/banner_update.html delete mode 100644 gestioncof/templates/gestioncof/base_header.html delete mode 100644 gestioncof/templates/gestioncof/calendar_subscription.html delete mode 100644 gestioncof/templates/gestioncof/event.html delete mode 100644 gestioncof/templates/gestioncof/home.html delete mode 100644 gestioncof/templates/gestioncof/mails/welcome.txt delete mode 100644 gestioncof/templates/gestioncof/profile.html delete mode 100644 gestioncof/templates/gestioncof/registration_form.html delete mode 100644 gestioncof/templates/gestioncof/registration_post.html delete mode 100644 gestioncof/templates/gestioncof/reset_comptes.html delete mode 100644 gestioncof/templates/gestioncof/search_results.html delete mode 100644 gestioncof/templates/gestioncof/survey.html delete mode 100644 gestioncof/templates/gestioncof/utile_cof.html delete mode 100644 gestioncof/templates/liste_clubs.html delete mode 100644 gestioncof/templates/liste_mails.html delete mode 100644 gestioncof/templates/login.html delete mode 100644 gestioncof/templates/login_error.html delete mode 100644 gestioncof/templates/login_switch.html delete mode 100644 gestioncof/templates/logout.html delete mode 100644 gestioncof/templates/membres_clubs.html delete mode 100644 gestioncof/templates/registration.html delete mode 100644 gestioncof/templates/registration/password_change_done.html delete mode 100644 gestioncof/templates/registration/password_change_form.html delete mode 100644 gestioncof/templates/survey_status.html delete mode 100644 gestioncof/templates/tristate_js.html delete mode 100644 gestioncof/templates/utile_bda.html delete mode 100644 gestioncof/templatetags/utils.py delete mode 100644 gestioncof/tests/__init__.py delete mode 100644 gestioncof/tests/mixins.py delete mode 100644 gestioncof/tests/test_legacy.py delete mode 100644 gestioncof/tests/test_views.py delete mode 100644 gestioncof/tests/utils.py delete mode 100644 gestioncof/urls.py delete mode 100644 gestioncof/views.py delete mode 100644 gestioncof/widgets.py delete mode 100644 petitscours/__init__.py delete mode 100644 petitscours/forms.py delete mode 100644 petitscours/models.py delete mode 100644 petitscours/templates/petitscours/base_title.html delete mode 100644 petitscours/templates/petitscours/demande.html delete mode 100644 petitscours/templates/petitscours/demande_detail.html delete mode 100644 petitscours/templates/petitscours/demande_list.html delete mode 100644 petitscours/templates/petitscours/demande_raw.html delete mode 100644 petitscours/templates/petitscours/details_demande_infos.html delete mode 100644 petitscours/templates/petitscours/inscription.html delete mode 100644 petitscours/templates/petitscours/inscription_formset.html delete mode 100644 petitscours/templates/petitscours/mails/demandeur.txt delete mode 100644 petitscours/templates/petitscours/mails/eleve.txt delete mode 100644 petitscours/templates/petitscours/traitement_demande.html delete mode 100644 petitscours/templates/petitscours/traitement_demande_autre_niveau.html delete mode 100644 petitscours/templates/petitscours/traitement_demande_success.html delete mode 100644 petitscours/tests/__init__.py delete mode 100644 petitscours/tests/test_views.py delete mode 100644 petitscours/tests/utils.py delete mode 100644 petitscours/urls.py delete mode 100644 petitscours/views.py diff --git a/bda/__init__.py b/bda/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bda/admin.py b/bda/admin.py deleted file mode 100644 index f52f721d..00000000 --- a/bda/admin.py +++ /dev/null @@ -1,350 +0,0 @@ -from datetime import timedelta - -from dal.autocomplete import ModelSelect2 -from django import forms -from django.contrib import admin -from django.core.mail import send_mass_mail -from django.db.models import Count, Q, Sum -from django.template import loader -from django.template.defaultfilters import pluralize -from django.utils import timezone - -from bda.models import ( - Attribution, - CategorieSpectacle, - ChoixSpectacle, - Participant, - Quote, - Salle, - Spectacle, - SpectacleRevente, - Tirage, -) - - -class ReadOnlyMixin(object): - readonly_fields_update = () - - def get_readonly_fields(self, request, obj=None): - readonly_fields = super().get_readonly_fields(request, obj) - if obj is None: - return readonly_fields - else: - return readonly_fields + self.readonly_fields_update - - -class AttributionTabularAdminForm(forms.ModelForm): - listing = None - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - spectacles = Spectacle.objects.select_related("location") - if self.listing is not None: - spectacles = spectacles.filter(listing=self.listing) - self.fields["spectacle"].queryset = spectacles - - -class WithoutListingAttributionTabularAdminForm(AttributionTabularAdminForm): - listing = False - - -class WithListingAttributionTabularAdminForm(AttributionTabularAdminForm): - listing = True - - -class AttributionInline(admin.TabularInline): - model = Attribution - extra = 0 - listing = None - - def get_queryset(self, request): - qs = super().get_queryset(request) - if self.listing is not None: - qs = qs.filter(spectacle__listing=self.listing) - return qs - - -class WithListingAttributionInline(AttributionInline): - exclude = ("given",) - form = WithListingAttributionTabularAdminForm - listing = True - verbose_name_plural = "Attributions sur listing" - - -class WithoutListingAttributionInline(AttributionInline): - form = WithoutListingAttributionTabularAdminForm - listing = False - verbose_name_plural = "Attributions hors listing" - - -class ParticipantAdminForm(forms.ModelForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - queryset = Spectacle.objects.select_related("location") - - if self.instance.pk is not None: - queryset = queryset.filter(tirage=self.instance.tirage) - - self.fields["choicesrevente"].queryset = queryset - - -class ParticipantPaidFilter(admin.SimpleListFilter): - """ - Permet de filtrer les participants sur s'ils ont payé leurs places ou pas - """ - - title = "A payé" - parameter_name = "paid" - - def lookups(self, request, model_admin): - return ((True, "Oui"), (False, "Non")) - - def queryset(self, request, queryset): - return queryset.filter(paid=self.value()) - - -class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin): - inlines = [WithListingAttributionInline, WithoutListingAttributionInline] - - def get_queryset(self, request): - return self.model.objects.annotate_paid().annotate( - nb_places=Count("attributions"), - remain=Sum( - "attribution__spectacle__price", filter=Q(attribution__paid=False) - ), - total=Sum("attributions__price"), - ) - - def nb_places(self, obj): - return obj.nb_places - - nb_places.admin_order_field = "nb_places" - nb_places.short_description = "Nombre de places" - - def paid(self, obj): - return obj.paid - - paid.short_description = "A payé" - paid.boolean = True - paid.admin_order_field = "paid" - - def total(self, obj): - tot = obj.total - if tot: - return "%.02f €" % tot - else: - return "0 €" - - total.admin_order_field = "total" - total.short_description = "Total des places" - - def remain(self, obj): - rem = obj.remain - if rem: - return "%.02f €" % rem - else: - return "0 €" - - remain.admin_order_field = "remain" - remain.short_description = "Reste à payer" - - list_display = ("user", "nb_places", "total", "paid", "remain", "tirage") - list_filter = (ParticipantPaidFilter, "tirage") - search_fields = ("user__username", "user__first_name", "user__last_name") - actions = ["send_attribs"] - actions_on_bottom = True - list_per_page = 400 - readonly_fields = ("total", "paid") - readonly_fields_update = ("user", "tirage") - form = ParticipantAdminForm - - def send_attribs(self, request, queryset): - emails = [] - for member in queryset.all(): - subject = "Résultats du tirage au sort" - attribs = member.attributions.all() - context = {"member": member.user} - - template_name = "" - if len(attribs) == 0: - template_name = "bda/mails/attributions-decus.txt" - else: - template_name = "bda/mails/attributions.txt" - context["places"] = attribs - - message = loader.render_to_string(template_name, context) - emails.append((subject, message, "bda@ens.fr", [member.user.email])) - - send_mass_mail(emails) - count = len(queryset.all()) - if count == 1: - message_bit = "1 membre a" - plural = "" - else: - message_bit = "%d membres ont" % count - plural = "s" - self.message_user( - request, "%s été informé%s avec succès." % (message_bit, plural) - ) - - send_attribs.short_description = "Envoyer les résultats par mail" - - -class AttributionAdminForm(forms.ModelForm): - def clean(self): - cleaned_data = super().clean() - participant = cleaned_data.get("participant") - spectacle = cleaned_data.get("spectacle") - if participant and spectacle: - if participant.tirage != spectacle.tirage: - raise forms.ValidationError( - "Erreur : le participant et le spectacle n'appartiennent" - "pas au même tirage" - ) - return cleaned_data - - class Meta: - widgets = { - "participant": ModelSelect2(url="bda-participant-autocomplete"), - "spectacle": ModelSelect2(url="bda-spectacle-autocomplete"), - } - - -class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin): - list_display = ("id", "spectacle", "participant", "given", "paid") - search_fields = ( - "spectacle__title", - "participant__user__username", - "participant__user__first_name", - "participant__user__last_name", - ) - form = AttributionAdminForm - readonly_fields_update = ("spectacle", "participant") - - -class ChoixSpectacleAdmin(admin.ModelAdmin): - autocomplete_fields = ["participant", "spectacle"] - - def tirage(self, obj): - return obj.participant.tirage - - list_display = ("participant", "tirage", "spectacle", "priority", "double_choice") - list_filter = ("double_choice", "participant__tirage") - search_fields = ( - "participant__user__username", - "participant__user__first_name", - "participant__user__last_name", - "spectacle__title", - ) - - -class QuoteInline(admin.TabularInline): - model = Quote - - -class SpectacleAdmin(admin.ModelAdmin): - inlines = [QuoteInline] - model = Spectacle - list_display = ("title", "date", "tirage", "location", "slots", "price", "listing") - list_filter = ("location", "tirage") - search_fields = ("title", "location__name") - readonly_fields = ("rappel_sent",) - - -class TirageAdmin(admin.ModelAdmin): - model = Tirage - list_display = ("title", "ouverture", "fermeture", "active", "enable_do_tirage") - readonly_fields = ("tokens",) - list_filter = ("active",) - search_fields = ("title",) - - -class SalleAdmin(admin.ModelAdmin): - model = Salle - search_fields = ("name", "address") - - -class SpectacleReventeAdminForm(forms.ModelForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - qset = Participant.objects.select_related("user", "tirage") - - if self.instance.pk is not None: - qset = qset.filter(tirage=self.instance.seller.tirage) - - self.fields["confirmed_entry"].queryset = qset - self.fields["seller"].queryset = qset - self.fields["soldTo"].queryset = qset - - -class SpectacleReventeAdmin(admin.ModelAdmin): - """ - Administration des reventes de spectacles - """ - - model = SpectacleRevente - - def spectacle(self, obj): - """ - Raccourci vers le spectacle associé à la revente. - """ - return obj.attribution.spectacle - - list_display = ("spectacle", "seller", "date", "soldTo") - raw_id_fields = ("attribution",) - readonly_fields = ("date_tirage",) - search_fields = [ - "attribution__spectacle__title", - "seller__user__username", - "seller__user__first_name", - "seller__user__last_name", - ] - - actions = ["transfer", "reinit"] - actions_on_bottom = True - form = SpectacleReventeAdminForm - - def transfer(self, request, queryset): - """ - Effectue le transfert des reventes pour lesquels on connaît l'acheteur. - """ - reventes = queryset.exclude(soldTo__isnull=True).all() - count = reventes.count() - for revente in reventes: - attrib = revente.attribution - attrib.participant = revente.soldTo - attrib.save() - self.message_user( - request, - "%d attribution%s %s été transférée%s avec succès." - % (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)), - ) - - transfer.short_description = "Transférer les reventes sélectionnées" - - def reinit(self, request, queryset): - """ - Réinitialise les reventes. - """ - count = queryset.count() - for revente in queryset.filter( - attribution__spectacle__date__gte=timezone.now() - ): - revente.reset(new_date=timezone.now() - timedelta(hours=1)) - self.message_user( - request, - "%d attribution%s %s été réinitialisée%s avec succès." - % (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)), - ) - - reinit.short_description = "Réinitialiser les reventes sélectionnées" - - -admin.site.register(CategorieSpectacle) -admin.site.register(Spectacle, SpectacleAdmin) -admin.site.register(Salle, SalleAdmin) -admin.site.register(Participant, ParticipantAdmin) -admin.site.register(Attribution, AttributionAdmin) -admin.site.register(ChoixSpectacle, ChoixSpectacleAdmin) -admin.site.register(Tirage, TirageAdmin) -admin.site.register(SpectacleRevente, SpectacleReventeAdmin) diff --git a/bda/algorithm.py b/bda/algorithm.py deleted file mode 100644 index 078f2be8..00000000 --- a/bda/algorithm.py +++ /dev/null @@ -1,102 +0,0 @@ -import random - - -class Algorithm(object): - shows = None - ranks = None - origranks = None - double = None - - def __init__(self, shows, members, choices): - """Initialisation : - - on aggrège toutes les demandes pour chaque spectacle dans - show.requests - - on crée des tables de demandes pour chaque personne, afin de - pouvoir modifier les rankings""" - self.max_group = 2 * max(choice.priority for choice in choices) - self.shows = [] - showdict = {} - for show in shows: - show.nrequests = 0 - showdict[show] = show - show.requests = [] - self.shows.append(show) - self.ranks = {} - self.origranks = {} - self.choices = {} - next_rank = {} - member_shows = {} - for member in members: - self.ranks[member] = {} - self.choices[member] = {} - next_rank[member] = 1 - member_shows[member] = {} - for choice in choices: - member = choice.participant - if choice.spectacle in member_shows[member]: - continue - else: - member_shows[member][choice.spectacle] = True - showdict[choice.spectacle].requests.append(member) - showdict[choice.spectacle].nrequests += 2 if choice.double else 1 - self.ranks[member][choice.spectacle] = next_rank[member] - next_rank[member] += 2 if choice.double else 1 - self.choices[member][choice.spectacle] = choice - for member in members: - self.origranks[member] = dict(self.ranks[member]) - - def IncrementRanks(self, member, currank, increment=1): - for show in self.ranks[member]: - if self.ranks[member][show] > currank: - self.ranks[member][show] -= increment - - def appendResult(self, l, member, show): - l.append( - ( - member, - self.ranks[member][show], - self.origranks[member][show], - self.choices[member][show].double, - ) - ) - - def __call__(self, seed): - random.seed(seed) - results = [] - shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots, reverse=True) - for show in shows: - # On regroupe tous les gens ayant le même rang - groups = dict([(i, []) for i in range(1, self.max_group + 1)]) - for member in show.requests: - if self.ranks[member][show] == 0: - raise RuntimeError(member, show.title) - groups[self.ranks[member][show]].append(member) - # On passe à l'attribution - winners = [] - losers = [] - for i in range(1, self.max_group + 1): - group = list(groups[i]) - random.shuffle(group) - for member in group: - if self.choices[member][show].double: # double - if len(winners) + 1 < show.slots: - self.appendResult(winners, member, show) - self.appendResult(winners, member, show) - elif ( - not self.choices[member][show].autoquit - and len(winners) < show.slots - ): - self.appendResult(winners, member, show) - self.appendResult(losers, member, show) - else: - self.appendResult(losers, member, show) - self.appendResult(losers, member, show) - self.IncrementRanks(member, i, 2) - else: # simple - if len(winners) < show.slots: - self.appendResult(winners, member, show) - else: - self.appendResult(losers, member, show) - self.IncrementRanks(member, i) - results.append((show, winners, losers)) - return results diff --git a/bda/forms.py b/bda/forms.py deleted file mode 100644 index d1d0f74f..00000000 --- a/bda/forms.py +++ /dev/null @@ -1,184 +0,0 @@ -from django import forms -from django.forms.models import BaseInlineFormSet -from django.template import loader -from django.utils import timezone - -from bda.models import SpectacleRevente - - -class InscriptionInlineFormSet(BaseInlineFormSet): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # self.instance is a Participant object - tirage = self.instance.tirage - - # set once for all "spectacle" field choices - # - restrict choices to the spectacles of this tirage - # - force_choices avoid many db requests - spectacles = tirage.spectacle_set.select_related("location") - choices = [(sp.pk, str(sp)) for sp in spectacles] - self.force_choices("spectacle", choices) - - def force_choices(self, name, choices): - """Set choices of a field. - - As ModelChoiceIterator (default use to get choices of a - ModelChoiceField), it appends an empty selection if requested. - - """ - for form in self.forms: - field = form.fields[name] - if field.empty_label is not None: - field.choices = [("", field.empty_label)] + choices - else: - field.choices = choices - - -class TokenForm(forms.Form): - token = forms.CharField(widget=forms.widgets.Textarea()) - - -class TemplateLabelField(forms.ModelMultipleChoiceField): - """ - Extends ModelMultipleChoiceField to offer two more customization options : - - `label_from_instance` can be used with a template file - - the widget rendering template can be specified with `option_template_name` - """ - - def __init__( - self, - label_template_name=None, - context_object_name="obj", - option_template_name=None, - *args, - **kwargs - ): - super().__init__(*args, **kwargs) - self.label_template_name = label_template_name - self.context_object_name = context_object_name - if option_template_name is not None: - self.widget.option_template_name = option_template_name - - def label_from_instance(self, obj): - if self.label_template_name is None: - return super().label_from_instance(obj) - else: - return loader.render_to_string( - self.label_template_name, context={self.context_object_name: obj} - ) - - -# Formulaires pour revente_manage - - -class ResellForm(forms.Form): - def __init__(self, participant, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["attributions"] = TemplateLabelField( - queryset=participant.attribution_set.filter( - spectacle__date__gte=timezone.now(), paid=True - ) - .exclude(revente__seller=participant) - .select_related("spectacle", "spectacle__location", "participant__user"), - widget=forms.CheckboxSelectMultiple, - required=False, - label_template_name="bda/forms/attribution_label_table.html", - option_template_name="bda/forms/checkbox_table.html", - context_object_name="attribution", - ) - - -class AnnulForm(forms.Form): - def __init__(self, participant, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["reventes"] = TemplateLabelField( - label="", - queryset=participant.original_shows.filter( - attribution__spectacle__date__gte=timezone.now(), soldTo__isnull=True - ) - .select_related( - "attribution__spectacle", "attribution__spectacle__location" - ) - .order_by("-date"), - widget=forms.CheckboxSelectMultiple, - required=False, - label_template_name="bda/forms/revente_self_label_table.html", - option_template_name="bda/forms/checkbox_table.html", - context_object_name="revente", - ) - - -class SoldForm(forms.Form): - def __init__(self, participant, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["reventes"] = TemplateLabelField( - queryset=participant.original_shows.filter(soldTo__isnull=False) - .exclude(soldTo=participant) - .select_related( - "attribution__spectacle", "attribution__spectacle__location" - ), - widget=forms.CheckboxSelectMultiple, - label_template_name="bda/forms/revente_sold_label_table.html", - option_template_name="bda/forms/checkbox_table.html", - context_object_name="revente", - ) - - -# Formulaire pour revente_subscribe - - -class InscriptionReventeForm(forms.Form): - def __init__(self, tirage, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["spectacles"] = TemplateLabelField( - queryset=tirage.spectacle_set.select_related("location").filter( - date__gte=timezone.now() - ), - widget=forms.CheckboxSelectMultiple, - required=False, - label_template_name="bda/forms/spectacle_label_table.html", - option_template_name="bda/forms/checkbox_table.html", - context_object_name="spectacle", - ) - - -# Formulaires pour revente_tirages - - -class ReventeTirageAnnulForm(forms.Form): - def __init__(self, participant, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["reventes"] = TemplateLabelField( - queryset=participant.entered.filter(soldTo__isnull=True).select_related( - "attribution__spectacle", "seller__user" - ), - widget=forms.CheckboxSelectMultiple, - required=False, - label_template_name="bda/forms/revente_other_label_table.html", - option_template_name="bda/forms/checkbox_table.html", - context_object_name="revente", - ) - - -class ReventeTirageForm(forms.Form): - def __init__(self, participant, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.fields["reventes"] = TemplateLabelField( - queryset=( - SpectacleRevente.objects.filter( - notif_sent=True, - shotgun=False, - tirage_done=False, - attribution__spectacle__tirage=participant.tirage, - ) - .exclude(confirmed_entry=participant) - .select_related("attribution__spectacle") - ), - widget=forms.CheckboxSelectMultiple, - required=False, - label_template_name="bda/forms/revente_other_label_table.html", - option_template_name="bda/forms/checkbox_table.html", - context_object_name="revente", - ) diff --git a/bda/management/__init__.py b/bda/management/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bda/management/commands/__init__.py b/bda/management/commands/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bda/management/commands/loadbdadevdata.py b/bda/management/commands/loadbdadevdata.py deleted file mode 100644 index 186e1da7..00000000 --- a/bda/management/commands/loadbdadevdata.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -Crée deux tirages de test et y inscrit les utilisateurs -""" - -import os -import random - -from django.contrib.auth.models import User -from django.utils import timezone - -from bda.models import ChoixSpectacle, Participant, Salle, Spectacle, Tirage -from bda.views import do_tirage -from gestioncof.management.base import MyBaseCommand - -# Où sont stockés les fichiers json -DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data") - - -class Command(MyBaseCommand): - help = "Crée deux tirages de test et y inscrit les utilisateurs." - - def handle(self, *args, **options): - # --- - # Tirages - # --- - - Tirage.objects.all().delete() - Tirage.objects.bulk_create( - [ - Tirage( - title="Tirage de test 1", - ouverture=timezone.now() - timezone.timedelta(days=7), - fermeture=timezone.now(), - active=True, - ), - Tirage( - title="Tirage de test 2", - ouverture=timezone.now(), - fermeture=timezone.now() + timezone.timedelta(days=60), - active=True, - ), - ] - ) - tirages = Tirage.objects.all() - - # --- - # Salles - # --- - - locations = self.from_json("locations.json", DATA_DIR, Salle) - - # --- - # Spectacles - # --- - - def show_callback(show): - """ - Assigne un tirage, une date et un lieu à un spectacle et décide si - les places sont sur listing. - """ - show.tirage = random.choice(tirages) - show.listing = bool(random.randint(0, 1)) - show.date = show.tirage.fermeture + timezone.timedelta( - days=random.randint(60, 90) - ) - show.location = random.choice(locations) - return show - - shows = self.from_json("shows.json", DATA_DIR, Spectacle, show_callback) - - # --- - # Inscriptions - # --- - - self.stdout.write("Inscription des utilisateurs aux tirages") - ChoixSpectacle.objects.all().delete() - choices = [] - for user in User.objects.filter(profile__is_cof=True): - for tirage in tirages: - part, _ = Participant.objects.get_or_create(user=user, tirage=tirage) - shows = random.sample( - list(tirage.spectacle_set.all()), tirage.spectacle_set.count() // 2 - ) - for rank, show in enumerate(shows): - choices.append( - ChoixSpectacle( - participant=part, - spectacle=show, - priority=rank + 1, - double_choice=random.choice(["1", "double", "autoquit"]), - ) - ) - ChoixSpectacle.objects.bulk_create(choices) - self.stdout.write("- {:d} inscriptions générées".format(len(choices))) - - # --- - # On lance le premier tirage - # --- - - self.stdout.write("Lancement du premier tirage") - do_tirage(tirages[0], "dummy_token") diff --git a/bda/management/commands/manage_reventes.py b/bda/management/commands/manage_reventes.py deleted file mode 100644 index bd25a28e..00000000 --- a/bda/management/commands/manage_reventes.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Gestion en ligne de commande des reventes. -""" - -from django.core.management import BaseCommand -from django.utils import timezone - -from bda.models import SpectacleRevente - - -class Command(BaseCommand): - help = ( - "Envoie les mails de notification et effectue les tirages au sort des reventes" - ) - leave_locale_alone = True - - def handle(self, *args, **options): - now = timezone.now() - reventes = SpectacleRevente.objects.all() - for revente in reventes: - # Le spectacle est bientôt et on a pas encore envoyé de mail : - # on met la place au shotgun et on prévient. - if revente.is_urgent and not revente.notif_sent: - if revente.can_notif: - self.stdout.write(str(now)) - revente.mail_shotgun() - self.stdout.write( - "Mails de disponibilité immédiate envoyés " - "pour la revente [%s]" % revente - ) - - # Le spectacle est dans plus longtemps : on prévient - elif revente.can_notif and not revente.notif_sent: - self.stdout.write(str(now)) - revente.send_notif() - self.stdout.write( - "Mails d'inscription à la revente [%s] envoyés" % revente - ) - - # On fait le tirage - elif now >= revente.date_tirage and not revente.tirage_done: - self.stdout.write(str(now)) - winner = revente.tirage() - self.stdout.write("Tirage effectué pour la revente [%s]" % revente) - - if winner: - self.stdout.write("Gagnant : %s" % winner.user) - else: - self.stdout.write("Pas de gagnant ; place au shotgun") diff --git a/bda/management/commands/sendrappels.py b/bda/management/commands/sendrappels.py deleted file mode 100644 index 65026736..00000000 --- a/bda/management/commands/sendrappels.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Gestion en ligne de commande des mails de rappel. -""" - -from datetime import timedelta - -from django.core.management.base import BaseCommand -from django.utils import timezone - -from bda.models import Spectacle - - -class Command(BaseCommand): - help = ( - "Envoie les mails de rappel des spectacles dont la date approche.\n" - "Ne renvoie pas les mails déjà envoyés." - ) - leave_locale_alone = True - - def handle(self, *args, **options): - now = timezone.now() - delay = timedelta(days=4) - shows = ( - Spectacle.objects.filter(date__range=(now, now + delay)) - .filter(tirage__active=True) - .filter(rappel_sent__isnull=True) - .all() - ) - for show in shows: - show.send_rappel() - self.stdout.write("Mails de rappels pour %s envoyés avec succès." % show) - if not shows: - self.stdout.write("Aucun mail à envoyer.") diff --git a/bda/management/data/locations.json b/bda/management/data/locations.json deleted file mode 100644 index 7208409b..00000000 --- a/bda/management/data/locations.json +++ /dev/null @@ -1,26 +0,0 @@ -[ -{ - "name": "Cour\u00f4", - "address": "45 rue d'Ulm, cour\u00f4" -}, -{ - "name": "K-F\u00eat", - "address": "45 rue d'Ulm, escalier C, niveau -1" -}, -{ - "name": "Th\u00e9\u00e2tre", - "address": "45 rue d'Ulm, escalier C, niveau -1" -}, -{ - "name": "Cours Pasteur", - "address": "45 rue d'Ulm, cours pasteur" -}, -{ - "name": "Salle des actes", - "address": "45 rue d'Ulm, escalier A, niveau 1" -}, -{ - "name": "Amphi Rataud", - "address": "45 rue d'Ulm, NIR, niveau PB" -} -] diff --git a/bda/management/data/shows.json b/bda/management/data/shows.json deleted file mode 100644 index 660f2de9..00000000 --- a/bda/management/data/shows.json +++ /dev/null @@ -1,100 +0,0 @@ -[ -{ - "description": "Jazz / Funk", - "title": "Un super concert", - "price": 10.0, - "slots_description": "Debout", - "slots": 5 -}, -{ - "description": "Homemade", - "title": "Une super pi\u00e8ce", - "price": 10.0, - "slots_description": "Assises", - "slots": 60 -}, -{ - "description": "Plein air, soleil, bonne musique", - "title": "Concert pour la f\u00eate de la musique", - "price": 5.0, - "slots_description": "Debout, attention \u00e0 la fontaine", - "slots": 30 -}, -{ - "description": "Sous le regard s\u00e9v\u00e8re de Louis Pasteur", - "title": "Op\u00e9ra sans d\u00e9cors", - "price": 5.0, - "slots_description": "Assis sur l'herbe", - "slots": 20 -}, -{ - "description": "Buffet \u00e0 la fin", - "title": "Concert Trouv\u00e8re", - "price": 20.0, - "slots_description": "Assises", - "slots": 15 -}, -{ - "description": "Vive les maths", - "title": "Dessin \u00e0 la craie sur tableau noir", - "price": 10.0, - "slots_description": "Assises, tablette pour prendre des notes", - "slots": 30 -}, -{ - "description": "Une pi\u00e8ce \u00e0 un personnage", - "title": "D\u00e9cors, d\u00e9montage en musique", - "price": 0.0, - "slots_description": "Assises", - "slots": 20 -}, -{ - "description": "Annulera, annulera pas\u00a0?", - "title": "La Nuit", - "price": 27.0, - "slots_description": "", - "slots": 1000 -}, -{ - "description": "Le boum fait sa carte blanche", - "title": "Turbomix", - "price": 10.0, - "slots_description": "Debout les mains en l'air", - "slots": 20 -}, -{ - "description": "Unique repr\u00e9sentation", - "title": "Carinettes et trombone", - "price": 15.0, - "slots_description": "Chaises ikea", - "slots": 10 -}, -{ - "description": "Suivi d'une jam session", - "title": "Percussion sur rondins", - "price": 5.0, - "slots_description": "B\u00fbches", - "slots": 14 -}, -{ - "description": "\u00c9preuve sportive et artistique", - "title": "Bassin aux ernests, nage libre", - "price": 5.0, - "slots_description": "Humides", - "slots": 10 -}, -{ - "description": "Sonore", - "title": "Chant du barde", - "price": 13.0, - "slots_description": "Ne venez pas", - "slots": 20 -}, -{ - "description": "Cocorico", - "title": "Chant du coq", - "price": 4.0, - "slots_description": "bancs", - "slots": 15 -} -] diff --git a/bda/migrations/0001_initial.py b/bda/migrations/0001_initial.py deleted file mode 100644 index 5bc848c8..00000000 --- a/bda/migrations/0001_initial.py +++ /dev/null @@ -1,205 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] - - operations = [ - migrations.CreateModel( - name="Attribution", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("given", models.BooleanField(default=False, verbose_name="Donn\xe9e")), - ], - ), - migrations.CreateModel( - name="ChoixSpectacle", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "priority", - models.PositiveIntegerField(verbose_name=b"Priorit\xc3\xa9"), - ), - ( - "double_choice", - models.CharField( - default=b"1", - max_length=10, - verbose_name=b"Nombre de places", - choices=[ - (b"1", b"1 place"), - (b"autoquit", b"2 places si possible, 1 sinon"), - (b"double", b"2 places sinon rien"), - ], - ), - ), - ], - options={ - "ordering": ("priority",), - "verbose_name": "voeu", - "verbose_name_plural": "voeux", - }, - ), - migrations.CreateModel( - name="Participant", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("paid", models.BooleanField(default=False, verbose_name="A pay\xe9")), - ( - "paymenttype", - models.CharField( - blank=True, - max_length=6, - verbose_name="Moyen de paiement", - choices=[ - (b"cash", "Cash"), - (b"cb", b"CB"), - (b"cheque", "Ch\xe8que"), - (b"autre", "Autre"), - ], - ), - ), - ], - ), - migrations.CreateModel( - name="Salle", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("name", models.CharField(max_length=300, verbose_name=b"Nom")), - ("address", models.TextField(verbose_name=b"Adresse")), - ], - ), - migrations.CreateModel( - name="Spectacle", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("title", models.CharField(max_length=300, verbose_name=b"Titre")), - ("date", models.DateTimeField(verbose_name=b"Date & heure")), - ( - "description", - models.TextField(verbose_name=b"Description", blank=True), - ), - ( - "slots_description", - models.TextField( - verbose_name=b"Description des places", blank=True - ), - ), - ( - "price", - models.FloatField(verbose_name=b"Prix d'une place", blank=True), - ), - ("slots", models.IntegerField(verbose_name=b"Places")), - ( - "priority", - models.IntegerField(default=1000, verbose_name=b"Priorit\xc3\xa9"), - ), - ( - "location", - models.ForeignKey(to="bda.Salle", on_delete=models.CASCADE), - ), - ], - options={ - "ordering": ("priority", "date", "title"), - "verbose_name": "Spectacle", - }, - ), - migrations.AddField( - model_name="participant", - name="attributions", - field=models.ManyToManyField( - related_name="attributed_to", - through="bda.Attribution", - to="bda.Spectacle", - ), - ), - migrations.AddField( - model_name="participant", - name="choices", - field=models.ManyToManyField( - related_name="chosen_by", - through="bda.ChoixSpectacle", - to="bda.Spectacle", - ), - ), - migrations.AddField( - model_name="participant", - name="user", - field=models.OneToOneField( - to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE - ), - ), - migrations.AddField( - model_name="choixspectacle", - name="participant", - field=models.ForeignKey(to="bda.Participant", on_delete=models.CASCADE), - ), - migrations.AddField( - model_name="choixspectacle", - name="spectacle", - field=models.ForeignKey( - related_name="participants", - to="bda.Spectacle", - on_delete=models.CASCADE, - ), - ), - migrations.AddField( - model_name="attribution", - name="participant", - field=models.ForeignKey(to="bda.Participant", on_delete=models.CASCADE), - ), - migrations.AddField( - model_name="attribution", - name="spectacle", - field=models.ForeignKey( - related_name="attribues", to="bda.Spectacle", on_delete=models.CASCADE - ), - ), - migrations.AlterUniqueTogether( - name="choixspectacle", unique_together=set([("participant", "spectacle")]) - ), - ] diff --git a/bda/migrations/0002_add_tirage.py b/bda/migrations/0002_add_tirage.py deleted file mode 100644 index c2c6bd3c..00000000 --- a/bda/migrations/0002_add_tirage.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -from django.utils import timezone - - -def fill_tirage_fields(apps, schema_editor): - """ - Create a `Tirage` to fill new field `tirage` of `Participant` - and `Spectacle` already existing. - """ - Participant = apps.get_model("bda", "Participant") - Spectacle = apps.get_model("bda", "Spectacle") - Tirage = apps.get_model("bda", "Tirage") - - # These querysets only contains instances not linked to any `Tirage`. - participants = Participant.objects.filter(tirage=None) - spectacles = Spectacle.objects.filter(tirage=None) - - if not participants.count() and not spectacles.count(): - # No need to create a "trash" tirage. - return - - tirage = Tirage.objects.create( - title="Tirage de test (migration)", - active=False, - ouverture=timezone.now(), - fermeture=timezone.now(), - ) - - participants.update(tirage=tirage) - spectacles.update(tirage=tirage) - - -class Migration(migrations.Migration): - dependencies = [("bda", "0001_initial")] - - operations = [ - migrations.CreateModel( - name="Tirage", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("title", models.CharField(max_length=300, verbose_name=b"Titre")), - ( - "ouverture", - models.DateTimeField( - verbose_name=b"Date et heure d'ouverture du tirage" - ), - ), - ( - "fermeture", - models.DateTimeField( - verbose_name=b"Date et heure de fermerture du tirage" - ), - ), - ( - "token", - models.TextField(verbose_name=b"Graine du tirage", blank=True), - ), - ( - "active", - models.BooleanField(default=True, verbose_name=b"Tirage actif"), - ), - ], - ), - migrations.AlterField( - model_name="participant", - name="user", - field=models.ForeignKey( - to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE - ), - ), - # Create fields `spectacle` for `Participant` and `Spectacle` models. - # These fields are not nullable, but we first create them as nullable - # to give a default value for existing instances of these models. - migrations.AddField( - model_name="participant", - name="tirage", - field=models.ForeignKey( - to="bda.Tirage", null=True, on_delete=models.CASCADE - ), - ), - migrations.AddField( - model_name="spectacle", - name="tirage", - field=models.ForeignKey( - to="bda.Tirage", null=True, on_delete=models.CASCADE - ), - ), - migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop), - migrations.AlterField( - model_name="participant", - name="tirage", - field=models.ForeignKey(to="bda.Tirage", on_delete=models.CASCADE), - ), - migrations.AlterField( - model_name="spectacle", - name="tirage", - field=models.ForeignKey(to="bda.Tirage", on_delete=models.CASCADE), - ), - ] diff --git a/bda/migrations/0003_update_tirage_and_spectacle.py b/bda/migrations/0003_update_tirage_and_spectacle.py deleted file mode 100644 index 07f3742e..00000000 --- a/bda/migrations/0003_update_tirage_and_spectacle.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("bda", "0002_add_tirage")] - - operations = [ - migrations.AlterField( - model_name="spectacle", - name="price", - field=models.FloatField(verbose_name=b"Prix d'une place"), - ), - migrations.AlterField( - model_name="tirage", - name="active", - field=models.BooleanField(default=False, verbose_name=b"Tirage actif"), - ), - ] diff --git a/bda/migrations/0004_mails-rappel.py b/bda/migrations/0004_mails-rappel.py deleted file mode 100644 index 407353a4..00000000 --- a/bda/migrations/0004_mails-rappel.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("bda", "0003_update_tirage_and_spectacle")] - - operations = [ - migrations.AddField( - model_name="spectacle", - name="listing", - field=models.BooleanField( - default=False, verbose_name=b"Les places sont sur listing" - ), - preserve_default=False, - ), - migrations.AddField( - model_name="spectacle", - name="rappel_sent", - field=models.DateTimeField( - null=True, verbose_name=b"Mail de rappel envoy\xc3\xa9", blank=True - ), - ), - ] diff --git a/bda/migrations/0005_encoding.py b/bda/migrations/0005_encoding.py deleted file mode 100644 index 29ee0027..00000000 --- a/bda/migrations/0005_encoding.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("bda", "0004_mails-rappel")] - - operations = [ - migrations.AlterField( - model_name="choixspectacle", - name="priority", - field=models.PositiveIntegerField(verbose_name="Priorit\xe9"), - ), - migrations.AlterField( - model_name="spectacle", - name="priority", - field=models.IntegerField(default=1000, verbose_name="Priorit\xe9"), - ), - migrations.AlterField( - model_name="spectacle", - name="rappel_sent", - field=models.DateTimeField( - null=True, verbose_name="Mail de rappel envoy\xe9", blank=True - ), - ), - ] diff --git a/bda/migrations/0006_add_tirage_switch.py b/bda/migrations/0006_add_tirage_switch.py deleted file mode 100644 index 1535a5fe..00000000 --- a/bda/migrations/0006_add_tirage_switch.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models -from django.utils import timezone - - -def forwards_func(apps, schema_editor): - Tirage = apps.get_model("bda", "Tirage") - db_alias = schema_editor.connection.alias - for tirage in Tirage.objects.using(db_alias).all(): - if tirage.tokens: - tirage.tokens = 'Before %s\n"""%s"""\n' % ( - timezone.now().strftime("%y-%m-%d %H:%M:%S"), - tirage.tokens, - ) - tirage.save() - - -class Migration(migrations.Migration): - dependencies = [("bda", "0005_encoding")] - - operations = [ - migrations.RenameField("tirage", "token", "tokens"), - migrations.AddField( - model_name="tirage", - name="enable_do_tirage", - field=models.BooleanField( - default=False, verbose_name=b"Le tirage peut \xc3\xaatre lanc\xc3\xa9" - ), - ), - migrations.RunPython(forwards_func, migrations.RunPython.noop), - ] diff --git a/bda/migrations/0007_extends_spectacle.py b/bda/migrations/0007_extends_spectacle.py deleted file mode 100644 index 48865acb..00000000 --- a/bda/migrations/0007_extends_spectacle.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("bda", "0006_add_tirage_switch")] - - operations = [ - migrations.CreateModel( - name="CategorieSpectacle", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "name", - models.CharField(max_length=100, verbose_name="Nom", unique=True), - ), - ], - options={"verbose_name": "Cat\xe9gorie"}, - ), - migrations.CreateModel( - name="Quote", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("text", models.TextField(verbose_name="Citation")), - ("author", models.CharField(max_length=200, verbose_name="Auteur")), - ], - ), - migrations.AlterModelOptions( - name="spectacle", - options={"ordering": ("date", "title"), "verbose_name": "Spectacle"}, - ), - migrations.RemoveField(model_name="spectacle", name="priority"), - migrations.AddField( - model_name="spectacle", - name="ext_link", - field=models.CharField( - max_length=500, - verbose_name="Lien vers le site du spectacle", - blank=True, - ), - ), - migrations.AddField( - model_name="spectacle", - name="image", - field=models.ImageField( - upload_to="imgs/shows/", null=True, verbose_name="Image", blank=True - ), - ), - migrations.AlterField( - model_name="tirage", - name="enable_do_tirage", - field=models.BooleanField( - default=False, verbose_name="Le tirage peut \xeatre lanc\xe9" - ), - ), - migrations.AlterField( - model_name="tirage", - name="tokens", - field=models.TextField(verbose_name="Graine(s) du tirage", blank=True), - ), - migrations.AddField( - model_name="spectacle", - name="category", - field=models.ForeignKey( - blank=True, - to="bda.CategorieSpectacle", - on_delete=models.CASCADE, - null=True, - ), - ), - migrations.AddField( - model_name="spectacle", - name="vips", - field=models.TextField(verbose_name="Personnalit\xe9s", blank=True), - ), - migrations.AddField( - model_name="quote", - name="spectacle", - field=models.ForeignKey(to="bda.Spectacle", on_delete=models.CASCADE), - ), - ] diff --git a/bda/migrations/0008_py3.py b/bda/migrations/0008_py3.py deleted file mode 100644 index 3a7dfeb1..00000000 --- a/bda/migrations/0008_py3.py +++ /dev/null @@ -1,109 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("bda", "0007_extends_spectacle")] - - operations = [ - migrations.AlterField( - model_name="choixspectacle", - name="double_choice", - field=models.CharField( - verbose_name="Nombre de places", - choices=[ - ("1", "1 place"), - ("autoquit", "2 places si possible, 1 sinon"), - ("double", "2 places sinon rien"), - ], - max_length=10, - default="1", - ), - ), - migrations.AlterField( - model_name="participant", - name="paymenttype", - field=models.CharField( - blank=True, - choices=[ - ("cash", "Cash"), - ("cb", "CB"), - ("cheque", "Chèque"), - ("autre", "Autre"), - ], - max_length=6, - verbose_name="Moyen de paiement", - ), - ), - migrations.AlterField( - model_name="salle", - name="address", - field=models.TextField(verbose_name="Adresse"), - ), - migrations.AlterField( - model_name="salle", - name="name", - field=models.CharField(verbose_name="Nom", max_length=300), - ), - migrations.AlterField( - model_name="spectacle", - name="date", - field=models.DateTimeField(verbose_name="Date & heure"), - ), - migrations.AlterField( - model_name="spectacle", - name="description", - field=models.TextField(verbose_name="Description", blank=True), - ), - migrations.AlterField( - model_name="spectacle", - name="listing", - field=models.BooleanField(verbose_name="Les places sont sur listing"), - ), - migrations.AlterField( - model_name="spectacle", - name="price", - field=models.FloatField(verbose_name="Prix d'une place"), - ), - migrations.AlterField( - model_name="spectacle", - name="slots", - field=models.IntegerField(verbose_name="Places"), - ), - migrations.AlterField( - model_name="spectacle", - name="slots_description", - field=models.TextField(verbose_name="Description des places", blank=True), - ), - migrations.AlterField( - model_name="spectacle", - name="title", - field=models.CharField(verbose_name="Titre", max_length=300), - ), - migrations.AlterField( - model_name="tirage", - name="active", - field=models.BooleanField(verbose_name="Tirage actif", default=False), - ), - migrations.AlterField( - model_name="tirage", - name="fermeture", - field=models.DateTimeField( - verbose_name="Date et heure de fermerture du tirage" - ), - ), - migrations.AlterField( - model_name="tirage", - name="ouverture", - field=models.DateTimeField( - verbose_name="Date et heure d'ouverture du tirage" - ), - ), - migrations.AlterField( - model_name="tirage", - name="title", - field=models.CharField(verbose_name="Titre", max_length=300), - ), - ] diff --git a/bda/migrations/0009_revente.py b/bda/migrations/0009_revente.py deleted file mode 100644 index 7a547f85..00000000 --- a/bda/migrations/0009_revente.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import django.utils.timezone -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("bda", "0008_py3")] - - operations = [ - migrations.CreateModel( - name="SpectacleRevente", - fields=[ - ( - "id", - models.AutoField( - serialize=False, - primary_key=True, - auto_created=True, - verbose_name="ID", - ), - ), - ( - "date", - models.DateTimeField( - verbose_name="Date de mise en vente", - default=django.utils.timezone.now, - ), - ), - ( - "notif_sent", - models.BooleanField( - verbose_name="Notification envoyée", default=False - ), - ), - ( - "tirage_done", - models.BooleanField(verbose_name="Tirage effectué", default=False), - ), - ], - options={"verbose_name": "Revente"}, - ), - migrations.AddField( - model_name="participant", - name="choicesrevente", - field=models.ManyToManyField( - to="bda.Spectacle", related_name="subscribed", blank=True - ), - ), - migrations.AddField( - model_name="spectaclerevente", - name="answered_mail", - field=models.ManyToManyField( - to="bda.Participant", related_name="wanted", blank=True - ), - ), - migrations.AddField( - model_name="spectaclerevente", - name="attribution", - field=models.OneToOneField( - to="bda.Attribution", on_delete=models.CASCADE, related_name="revente" - ), - ), - migrations.AddField( - model_name="spectaclerevente", - name="seller", - field=models.ForeignKey( - to="bda.Participant", - on_delete=models.CASCADE, - verbose_name="Vendeur", - related_name="original_shows", - ), - ), - migrations.AddField( - model_name="spectaclerevente", - name="soldTo", - field=models.ForeignKey( - to="bda.Participant", - on_delete=models.CASCADE, - verbose_name="Vendue à", - null=True, - blank=True, - ), - ), - ] diff --git a/bda/migrations/0010_spectaclerevente_shotgun.py b/bda/migrations/0010_spectaclerevente_shotgun.py deleted file mode 100644 index ae0fdff1..00000000 --- a/bda/migrations/0010_spectaclerevente_shotgun.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from datetime import timedelta - -from django.db import migrations, models -from django.utils import timezone - - -def forwards_func(apps, schema_editor): - SpectacleRevente = apps.get_model("bda", "SpectacleRevente") - - for revente in SpectacleRevente.objects.all(): - is_expired = timezone.now() > revente.date_tirage() - is_direct = ( - revente.attribution.spectacle.date >= revente.date - and timezone.now() > revente.date + timedelta(minutes=15) - ) - revente.shotgun = is_expired or is_direct - revente.save() - - -class Migration(migrations.Migration): - dependencies = [("bda", "0009_revente")] - - operations = [ - migrations.AddField( - model_name="spectaclerevente", - name="shotgun", - field=models.BooleanField( - default=False, verbose_name="Disponible imm\xe9diatement" - ), - ), - migrations.RunPython(forwards_func, migrations.RunPython.noop), - ] diff --git a/bda/migrations/0011_tirage_appear_catalogue.py b/bda/migrations/0011_tirage_appear_catalogue.py deleted file mode 100644 index a8c49e2d..00000000 --- a/bda/migrations/0011_tirage_appear_catalogue.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("bda", "0010_spectaclerevente_shotgun")] - - operations = [ - migrations.AddField( - model_name="tirage", - name="appear_catalogue", - field=models.BooleanField( - default=False, verbose_name="Tirage à afficher dans le catalogue" - ), - ) - ] diff --git a/bda/migrations/0012_notif_time.py b/bda/migrations/0012_notif_time.py deleted file mode 100644 index 78ef8dce..00000000 --- a/bda/migrations/0012_notif_time.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("bda", "0011_tirage_appear_catalogue")] - - operations = [ - migrations.RenameField( - model_name="spectaclerevente", - old_name="answered_mail", - new_name="confirmed_entry", - ), - migrations.AlterField( - model_name="spectaclerevente", - name="confirmed_entry", - field=models.ManyToManyField( - blank=True, related_name="entered", to="bda.Participant" - ), - ), - migrations.AddField( - model_name="spectaclerevente", - name="notif_time", - field=models.DateTimeField( - blank=True, verbose_name="Moment d'envoi de la notification", null=True - ), - ), - ] diff --git a/bda/migrations/0012_swap_double_choice.py b/bda/migrations/0012_swap_double_choice.py deleted file mode 100644 index dcb8056d..00000000 --- a/bda/migrations/0012_swap_double_choice.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -def swap_double_choice(apps, schema_editor): - choices = apps.get_model("bda", "ChoixSpectacle").objects - - choices.filter(double_choice="double").update(double_choice="tmp") - choices.filter(double_choice="autoquit").update(double_choice="double") - choices.filter(double_choice="tmp").update(double_choice="autoquit") - - -class Migration(migrations.Migration): - dependencies = [("bda", "0011_tirage_appear_catalogue")] - - operations = [ - # Temporarily allow an extra "tmp" value for the `double_choice` field - migrations.AlterField( - model_name="choixspectacle", - name="double_choice", - field=models.CharField( - verbose_name="Nombre de places", - max_length=10, - default="1", - choices=[ - ("tmp", "tmp"), - ("1", "1 place"), - ("double", "2 places si possible, 1 sinon"), - ("autoquit", "2 places sinon rien"), - ], - ), - ), - migrations.RunPython(swap_double_choice, migrations.RunPython.noop), - migrations.AlterField( - model_name="choixspectacle", - name="double_choice", - field=models.CharField( - verbose_name="Nombre de places", - max_length=10, - default="1", - choices=[ - ("1", "1 place"), - ("double", "2 places si possible, 1 sinon"), - ("autoquit", "2 places sinon rien"), - ], - ), - ), - ] diff --git a/bda/migrations/0013_merge_20180524_2123.py b/bda/migrations/0013_merge_20180524_2123.py deleted file mode 100644 index b974abf2..00000000 --- a/bda/migrations/0013_merge_20180524_2123.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.13 on 2018-05-24 19:23 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [("bda", "0012_notif_time"), ("bda", "0012_swap_double_choice")] - - operations = [] diff --git a/bda/migrations/0014_attribution_paid_field.py b/bda/migrations/0014_attribution_paid_field.py deleted file mode 100644 index e5ef2b2d..00000000 --- a/bda/migrations/0014_attribution_paid_field.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.2 on 2019-06-03 19:13 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("bda", "0013_merge_20180524_2123")] - - operations = [ - migrations.AddField( - model_name="attribution", - name="paid", - field=models.BooleanField(default=False, verbose_name="Payée"), - ), - migrations.AddField( - model_name="attribution", - name="paymenttype", - field=models.CharField( - blank=True, - choices=[ - ("cash", "Cash"), - ("cb", "CB"), - ("cheque", "Chèque"), - ("autre", "Autre"), - ], - max_length=6, - verbose_name="Moyen de paiement", - ), - ), - ] diff --git a/bda/migrations/0015_move_bda_payment.py b/bda/migrations/0015_move_bda_payment.py deleted file mode 100644 index a39a159c..00000000 --- a/bda/migrations/0015_move_bda_payment.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 2.2 on 2019-06-03 19:30 - -from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist -from django.db import migrations - - -def set_attr_payment(apps, schema_editor): - Attribution = apps.get_model("bda", "Attribution") - for attr in Attribution.objects.all(): - attr.paid = attr.participant.paid - attr.paymenttype = attr.participant.paymenttype - attr.save() - - -def set_participant_payment(apps, schema_editor): - Participant = apps.get_model("bda", "Participant") - for part in Participant.objects.all(): - attr_set = part.attribution_set - part.paid = attr_set.exists() and not attr_set.filter(paid=False).exists() - try: - # S'il n'y a qu'un seul type de paiement, on le set - part.paymenttype = ( - attr_set.values_list("paymenttype", flat=True).distinct().get() - ) - # Sinon, whatever - except (ObjectDoesNotExist, MultipleObjectsReturned): - pass - part.save() - - -class Migration(migrations.Migration): - dependencies = [("bda", "0014_attribution_paid_field")] - - operations = [ - migrations.RunPython(set_attr_payment, set_participant_payment, atomic=True) - ] diff --git a/bda/migrations/0016_delete_participant_paid.py b/bda/migrations/0016_delete_participant_paid.py deleted file mode 100644 index 86a17b24..00000000 --- a/bda/migrations/0016_delete_participant_paid.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 2.2 on 2019-06-03 19:30 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [("bda", "0015_move_bda_payment")] - - operations = [ - migrations.RemoveField(model_name="participant", name="paid"), - migrations.RemoveField(model_name="participant", name="paymenttype"), - ] diff --git a/bda/migrations/0017_participant_accepte_charte.py b/bda/migrations/0017_participant_accepte_charte.py deleted file mode 100644 index 3157654b..00000000 --- a/bda/migrations/0017_participant_accepte_charte.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2 on 2019-09-18 16:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("bda", "0016_delete_participant_paid")] - - operations = [ - migrations.AddField( - model_name="participant", - name="accepte_charte", - field=models.BooleanField( - default=False, verbose_name="A accepté la charte BdA" - ), - ) - ] diff --git a/bda/migrations/0018_auto_20201021_1818.py b/bda/migrations/0018_auto_20201021_1818.py deleted file mode 100644 index 444f32d8..00000000 --- a/bda/migrations/0018_auto_20201021_1818.py +++ /dev/null @@ -1,37 +0,0 @@ -# Generated by Django 2.2.12 on 2020-10-21 16:18 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("bda", "0017_participant_accepte_charte"), - ] - - operations = [ - migrations.AlterModelOptions( - name="participant", - options={"ordering": ("-tirage", "user__last_name", "user__first_name")}, - ), - migrations.AddField( - model_name="tirage", - name="archived", - field=models.BooleanField(default=False, verbose_name="Archivé"), - ), - migrations.AlterField( - model_name="participant", - name="tirage", - field=models.ForeignKey( - limit_choices_to={"archived": False}, - on_delete=django.db.models.deletion.CASCADE, - to="bda.Tirage", - ), - ), - migrations.AddConstraint( - model_name="participant", - constraint=models.UniqueConstraint( - fields=("tirage", "user"), name="unique_tirage" - ), - ), - ] diff --git a/bda/migrations/__init__.py b/bda/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bda/models.py b/bda/models.py deleted file mode 100644 index 578f235c..00000000 --- a/bda/models.py +++ /dev/null @@ -1,490 +0,0 @@ -import calendar -import random -from datetime import timedelta - -from django.conf import settings -from django.contrib.auth.models import User -from django.contrib.sites.models import Site -from django.core import mail -from django.core.mail import EmailMessage, send_mass_mail -from django.db import models -from django.db.models import Count, Exists -from django.template import loader -from django.utils import formats, timezone - - -def get_generic_user(): - generic, _ = User.objects.get_or_create( - username="bda_generic", - defaults={"email": "bda@ens.fr", "first_name": "Bureau des arts"}, - ) - return generic - - -class Tirage(models.Model): - title = models.CharField("Titre", max_length=300) - ouverture = models.DateTimeField("Date et heure d'ouverture du tirage") - fermeture = models.DateTimeField("Date et heure de fermerture du tirage") - tokens = models.TextField("Graine(s) du tirage", blank=True) - active = models.BooleanField("Tirage actif", default=False) - appear_catalogue = models.BooleanField( - "Tirage à afficher dans le catalogue", default=False - ) - enable_do_tirage = models.BooleanField("Le tirage peut être lancé", default=False) - archived = models.BooleanField("Archivé", default=False) - - def __str__(self): - return "%s - %s" % ( - self.title, - formats.localize(timezone.template_localtime(self.fermeture)), - ) - - -class Salle(models.Model): - name = models.CharField("Nom", max_length=300) - address = models.TextField("Adresse") - - def __str__(self): - return self.name - - -class CategorieSpectacle(models.Model): - name = models.CharField("Nom", max_length=100, unique=True) - - def __str__(self): - return self.name - - class Meta: - verbose_name = "Catégorie" - - -class Spectacle(models.Model): - title = models.CharField("Titre", max_length=300) - category = models.ForeignKey( - CategorieSpectacle, on_delete=models.CASCADE, blank=True, null=True - ) - date = models.DateTimeField("Date & heure") - location = models.ForeignKey(Salle, on_delete=models.CASCADE) - vips = models.TextField("Personnalités", blank=True) - description = models.TextField("Description", blank=True) - slots_description = models.TextField("Description des places", blank=True) - image = models.ImageField("Image", blank=True, null=True, upload_to="imgs/shows/") - ext_link = models.CharField( - "Lien vers le site du spectacle", blank=True, max_length=500 - ) - price = models.FloatField("Prix d'une place") - slots = models.IntegerField("Places") - tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE) - listing = models.BooleanField("Les places sont sur listing") - rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True, null=True) - - class Meta: - verbose_name = "Spectacle" - ordering = ("date", "title") - - def timestamp(self): - return "%d" % calendar.timegm(self.date.utctimetuple()) - - def __str__(self): - return "%s - %s, %s, %.02f€" % ( - self.title, - formats.localize(timezone.template_localtime(self.date)), - self.location, - self.price, - ) - - def getImgUrl(self): - """ - Cette fonction permet d'obtenir l'URL de l'image, si elle existe - """ - try: - return self.image.url - except Exception: - return None - - def send_rappel(self): - """ - Envoie un mail de rappel à toutes les personnes qui ont une place pour - ce spectacle. - """ - # On récupère la liste des participants + le BdA - members = list( - User.objects.filter(participant__attributions=self) - .annotate(nb_attr=Count("id")) - .order_by() - ) - bda_generic = get_generic_user() - bda_generic.nb_attr = 1 - members.append(bda_generic) - # On écrit un mail personnalisé à chaque participant - mails = [ - ( - str(self), - loader.render_to_string( - "bda/mails/rappel.txt", - context={"member": member, "nb_attr": member.nb_attr, "show": self}, - ), - settings.MAIL_DATA["rappels"]["FROM"], - [member.email], - ) - for member in members - ] - send_mass_mail(mails) - # On enregistre le fait que l'envoi a bien eu lieu - self.rappel_sent = timezone.now() - self.save() - # On renvoie la liste des destinataires - return members - - @property - def is_past(self): - return self.date < timezone.now() - - -class Quote(models.Model): - spectacle = models.ForeignKey(Spectacle, on_delete=models.CASCADE) - text = models.TextField("Citation") - author = models.CharField("Auteur", max_length=200) - - -PAYMENT_TYPES = ( - ("cash", "Cash"), - ("cb", "CB"), - ("cheque", "Chèque"), - ("autre", "Autre"), -) - - -class Attribution(models.Model): - participant = models.ForeignKey("Participant", on_delete=models.CASCADE) - spectacle = models.ForeignKey( - Spectacle, on_delete=models.CASCADE, related_name="attribues" - ) - given = models.BooleanField("Donnée", default=False) - paid = models.BooleanField("Payée", default=False) - paymenttype = models.CharField( - "Moyen de paiement", max_length=6, choices=PAYMENT_TYPES, blank=True - ) - - def __str__(self): - return "%s -- %s, %s" % ( - self.participant.user, - self.spectacle.title, - self.spectacle.date, - ) - - -class ParticipantPaidQueryset(models.QuerySet): - """ - Un manager qui annote le queryset avec un champ `paid`, - indiquant si un participant a payé toutes ses attributions. - """ - - def annotate_paid(self): - # OuterRef permet de se référer à un champ d'un modèle non encore fixé - # Voir: - # https://docs.djangoproject.com/en/2.2/ref/models/expressions/#django.db.models.OuterRef - unpaid = Attribution.objects.filter( - participant=models.OuterRef("pk"), paid=False - ) - return self.annotate(paid=~Exists(unpaid)) - - -class Participant(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE) - choices = models.ManyToManyField( - Spectacle, through="ChoixSpectacle", related_name="chosen_by" - ) - attributions = models.ManyToManyField( - Spectacle, through="Attribution", related_name="attributed_to" - ) - tirage = models.ForeignKey( - Tirage, on_delete=models.CASCADE, limit_choices_to={"archived": False} - ) - accepte_charte = models.BooleanField("A accepté la charte BdA", default=False) - choicesrevente = models.ManyToManyField( - Spectacle, related_name="subscribed", blank=True - ) - - objects = ParticipantPaidQueryset.as_manager() - - def __str__(self): - return "%s - %s" % (self.user, self.tirage.title) - - class Meta: - ordering = ("-tirage", "user__last_name", "user__first_name") - constraints = [ - models.UniqueConstraint(fields=("tirage", "user"), name="unique_tirage"), - ] - - -DOUBLE_CHOICES = ( - ("1", "1 place"), - ("double", "2 places si possible, 1 sinon"), - ("autoquit", "2 places sinon rien"), -) - - -class ChoixSpectacle(models.Model): - participant = models.ForeignKey(Participant, on_delete=models.CASCADE) - spectacle = models.ForeignKey( - Spectacle, on_delete=models.CASCADE, related_name="participants" - ) - priority = models.PositiveIntegerField("Priorité") - double_choice = models.CharField( - "Nombre de places", default="1", choices=DOUBLE_CHOICES, max_length=10 - ) - - def get_double(self): - return self.double_choice != "1" - - double = property(get_double) - - def get_autoquit(self): - return self.double_choice == "autoquit" - - autoquit = property(get_autoquit) - - def __str__(self): - return "Vœux de %s pour %s" % ( - self.participant.user.get_full_name(), - self.spectacle.title, - ) - - class Meta: - ordering = ("priority",) - unique_together = (("participant", "spectacle"),) - verbose_name = "voeu" - verbose_name_plural = "voeux" - - -class SpectacleRevente(models.Model): - attribution = models.OneToOneField( - Attribution, on_delete=models.CASCADE, related_name="revente" - ) - date = models.DateTimeField("Date de mise en vente", default=timezone.now) - confirmed_entry = models.ManyToManyField( - Participant, related_name="entered", blank=True - ) - seller = models.ForeignKey( - Participant, - on_delete=models.CASCADE, - verbose_name="Vendeur", - related_name="original_shows", - ) - soldTo = models.ForeignKey( - Participant, - on_delete=models.CASCADE, - verbose_name="Vendue à", - blank=True, - null=True, - ) - - notif_sent = models.BooleanField("Notification envoyée", default=False) - - notif_time = models.DateTimeField( - "Moment d'envoi de la notification", blank=True, null=True - ) - - tirage_done = models.BooleanField("Tirage effectué", default=False) - - shotgun = models.BooleanField("Disponible immédiatement", default=False) - #### - # Some class attributes - ### - # TODO : settings ? - - # Temps minimum entre le tirage et le spectacle - min_margin = timedelta(days=5) - - # Temps entre la création d'une revente et l'envoi du mail - remorse_time = timedelta(hours=1) - - # Temps min/max d'attente avant le tirage - max_wait_time = timedelta(days=3) - min_wait_time = timedelta(days=1) - - @property - def real_notif_time(self): - if self.notif_time: - return self.notif_time - else: - return self.date + self.remorse_time - - @property - def date_tirage(self): - """Renvoie la date du tirage au sort de la revente.""" - - remaining_time = ( - self.attribution.spectacle.date - self.real_notif_time - self.min_margin - ) - - delay = min(remaining_time, self.max_wait_time) - - return self.real_notif_time + delay - - @property - def is_urgent(self): - """ - Renvoie True iff la revente doit être mise au shotgun directement. - Plus précisément, on doit avoir min_margin + min_wait_time de marge. - """ - spectacle_date = self.attribution.spectacle.date - return spectacle_date <= timezone.now() + self.min_margin + self.min_wait_time - - @property - def can_notif(self): - return timezone.now() >= self.date + self.remorse_time - - def __str__(self): - return "%s -- %s" % (self.seller, self.attribution.spectacle.title) - - class Meta: - verbose_name = "Revente" - - def reset(self, new_date=timezone.now()): - """Réinitialise la revente pour permettre une remise sur le marché""" - self.seller = self.attribution.participant - self.date = new_date - self.confirmed_entry.clear() - self.soldTo = None - self.notif_sent = False - self.notif_time = None - self.tirage_done = False - self.shotgun = False - self.save() - - def send_notif(self): - """ - Envoie une notification pour indiquer la mise en vente d'une place sur - BdA-Revente à tous les intéressés. - """ - inscrits = self.attribution.spectacle.subscribed.select_related("user") - mails = [ - ( - "BdA-Revente : {}".format(self.attribution.spectacle.title), - loader.render_to_string( - "bda/mails/revente-new.txt", - context={ - "member": participant.user, - "show": self.attribution.spectacle, - "revente": self, - "site": Site.objects.get_current(), - }, - ), - settings.MAIL_DATA["revente"]["FROM"], - [participant.user.email], - ) - for participant in inscrits - ] - send_mass_mail(mails) - self.notif_sent = True - self.notif_time = timezone.now() - self.save() - - def mail_shotgun(self): - """ - Envoie un mail à toutes les personnes intéréssées par le spectacle pour - leur indiquer qu'il est désormais disponible au shotgun. - """ - inscrits = self.attribution.spectacle.subscribed.select_related("user") - mails = [ - ( - "BdA-Revente : {}".format(self.attribution.spectacle.title), - loader.render_to_string( - "bda/mails/revente-shotgun.txt", - context={ - "member": participant.user, - "show": self.attribution.spectacle, - "site": Site.objects.get_current(), - }, - ), - settings.MAIL_DATA["revente"]["FROM"], - [participant.user.email], - ) - for participant in inscrits - ] - send_mass_mail(mails) - self.notif_sent = True - self.notif_time = timezone.now() - # Flag inutile, sauf si l'horloge interne merde - self.tirage_done = True - self.shotgun = True - self.save() - - def tirage(self, send_mails=True): - """ - Lance le tirage au sort associé à la revente. Un gagnant est choisi - parmis les personnes intéressées par le spectacle. Les personnes sont - ensuites prévenues par mail du résultat du tirage. - """ - inscrits = list(self.confirmed_entry.all()) - spectacle = self.attribution.spectacle - seller = self.seller - winner = None - - if inscrits: - # Envoie un mail au gagnant et au vendeur - winner = random.choice(inscrits) - self.soldTo = winner - if send_mails: - mails = [] - - context = { - "acheteur": winner.user, - "vendeur": seller.user, - "show": spectacle, - } - - subject = "BdA-Revente : {}".format(spectacle.title) - - mails.append( - EmailMessage( - subject=subject, - body=loader.render_to_string( - "bda/mails/revente-tirage-winner.txt", - context=context, - ), - from_email=settings.MAIL_DATA["revente"]["FROM"], - to=[winner.user.email], - ) - ) - mails.append( - EmailMessage( - subject=subject, - body=loader.render_to_string( - "bda/mails/revente-tirage-seller.txt", - context=context, - ), - from_email=settings.MAIL_DATA["revente"]["FROM"], - to=[seller.user.email], - reply_to=[winner.user.email], - ), - ) - - # Envoie un mail aux perdants - for inscrit in inscrits: - if inscrit != winner: - new_context = dict(context) - new_context["acheteur"] = inscrit.user - - mails.append( - EmailMessage( - subject=subject, - body=loader.render_to_string( - "bda/mails/revente-tirage-loser.txt", - context=new_context, - ), - from_email=settings.MAIL_DATA["revente"]["FROM"], - to=[inscrit.user.email], - ), - ) - - mail_conn = mail.get_connection() - mail_conn.send_messages(mails) - # Si personne ne veut de la place, elle part au shotgun - else: - self.shotgun = True - self.tirage_done = True - self.save() - return winner diff --git a/bda/static/bda/css/bda.css b/bda/static/bda/css/bda.css deleted file mode 100644 index 7f2c1d9a..00000000 --- a/bda/static/bda/css/bda.css +++ /dev/null @@ -1,125 +0,0 @@ -form#tokenform { - text-align: center; - font-size: 2em; -} - -label { - margin-right: 10px; - vertical-align: top; -} - -form#tokenform textarea { - font-size: 2em; - width: 350px; - height: 200px; - font-family: 'Droif Serif', serif; -} - -/* wft ? -input { - width: 400px; - font-size: 2em; -}*/ - -ul.losers { - display: inline; - margin: 0; - padding: 0; -} - -ul.losers li { - display: inline; -} - -span.details { - font-size: 0.7em; -} - -td { - border: 0px solid black; - padding: 2px; -} -.attribresult { - margin: 10px 0px; -} - -.spectacle-passe { - opacity:0.5; -} - -/** JQuery-Confirm box **/ - -.jconfirm .jconfirm-bg { - background-color: rgb(0,0,0,0.6) !important; -} - -.jconfirm .jconfirm-box { - padding:0; - border-radius:0 !important; - font-family:Roboto; -} - -.jconfirm .jconfirm-box .content-pane { - border-bottom:1px solid #ddd; - margin: 0px !important; -} - -.jconfirm .jconfirm-box .content { - padding: 5px; -} - -.jconfirm .jconfirm-box .content-pane { - border-bottom:1px solid #ddd; - margin: 0px !important; -} - -.jconfirm .jconfirm-box .content { - padding: 10px; -} - -.jconfirm .jconfirm-box .content a, -.jconfirm .jconfirm-box .content a:hover { - color: #D81138; - font-weight: bold; -} - -.jconfirm .jconfirm-box .buttons { - margin-top:-6px; /* j'arrive pas à voir pk y'a un espace au dessus sinon... */ - padding:0; - height:40px; -} - -.jconfirm .jconfirm-box .buttons button { - min-width:40px; - height:100%; - margin:0; - margin:0 !important; - border-radius: 0 !important; -} - -.jconfirm .jconfirm-box .buttons button:first-child:focus, -.jconfirm .jconfirm-box .buttons button:first-child:hover { - color:#FFF !important; - background:forestgreen !important; -} - -.jconfirm .jconfirm-box .buttons button:nth-child(2):focus, -.jconfirm .jconfirm-box .buttons button:nth-child(2):hover { - color:#FFF !important; - background:#D93A32 !important; -} - -.jconfirm .jconfirm-box div.title-c .title { - display: block; - - padding:0 15px; - height:40px; - line-height:40px; - - font-family:Dosis; - font-size:20px; - font-weight:bold; - - color:#FFF; - background-color:rgb(222, 130, 107); -} \ No newline at end of file diff --git a/bda/templates/bda-attrib-extra.html b/bda/templates/bda-attrib-extra.html deleted file mode 100644 index b2e05132..00000000 --- a/bda/templates/bda-attrib-extra.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "bda-attrib.html" %} - -{% block extracontent %} - -

Attributions (détails)

-

Token :

-
{{ token }}
-

Placés : {{ total_slots }} ; Déçus : {{ total_losers }}

- - -{% for member, shows in members2 %} - - - - - - -{% for show in shows %} - - - - - - -{% endfor %} -{% endfor %} -
{{ member.user.get_full_name }}{{ member.user.email }}Total: {{ member.total }}€
{{ show }}
-{% endblock %} diff --git a/bda/templates/bda-attrib.html b/bda/templates/bda-attrib.html deleted file mode 100644 index fac0de67..00000000 --- a/bda/templates/bda-attrib.html +++ /dev/null @@ -1,49 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - -{% block extra_head %} - -{% endblock %} - -{% block realcontent %} - -

Attributions

- -
-

Pour raison de sécurité, le lancement du tirage - a été désactivé. Vous pouvez le réactiver dans - l'interface admin

- -

Token :

-
{{ token }}
-

Placés : {{ total_slots }} ; Déçus : {{ total_losers }}

-{% if user.profile.is_buro %}

Déficit total: {{ total_deficit }} €, Opéra: {{ opera_deficit }} €, Attribué: {{ total_sold }} €

{% endif %} -

Temps de calcul : {{ duration|floatformat }}s

- -{% for show, members, losers in results %} -
-

{{ show.title }} - {{ show.date }} @ {{ show.location }}

-

-{{ show.nrequests }} demandes pour {{ show.slots }} places -{{ show.price }}€ par place{% if user.profile.is_buro and show.nrequests < show.slots %}, {{ show.deficit }}€ de déficit{% endif %} -

-Places : - -Déçus : -{% if not losers %}/{% else %} - -{% endif %} -
-{% endfor %} -{% block extracontent %} -{% endblock %} -{% endblock %} diff --git a/bda/templates/bda-emails.html b/bda/templates/bda-emails.html deleted file mode 100644 index 39744ae1..00000000 --- a/bda/templates/bda-emails.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

{{ spectacle }}

- -{% endblock %} diff --git a/bda/templates/bda-token.html b/bda/templates/bda-token.html deleted file mode 100644 index c464fb97..00000000 --- a/bda/templates/bda-token.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

Tirage au sort du BdA

-
- {% csrf_token %} - La graine : -
- {{ form.token }} -
- -
-{% endblock %} diff --git a/bda/templates/bda-unpaid.html b/bda/templates/bda-unpaid.html deleted file mode 100644 index a424dce2..00000000 --- a/bda/templates/bda-unpaid.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

Impayés

- -

Total : {{ unpaid|length }}

-{% endblock %} diff --git a/bda/templates/bda/etat-places.html b/bda/templates/bda/etat-places.html deleted file mode 100644 index 401cc856..00000000 --- a/bda/templates/bda/etat-places.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - -{% block realcontent %} -

État des inscriptions BdA

- - - - - - - - - - - - - {% for spectacle in spectacles %} - - - - - - - - - {% endfor %} - -
TitreDateLieuPlacesDemandesRatio
{{ spectacle.title }}{{ spectacle.date }}{{ spectacle.location }}{{ spectacle.slots }} places{{ spectacle.total }} demandes - {{ spectacle.ratio |floatformat }} -
- - Total : {{ total }} place{{ total|pluralize }} demandée{{ total|pluralize }} - sur {{ proposed }} place{{ proposed|pluralize }} proposée{{ proposed|pluralize }} - - -{% endblock %} diff --git a/bda/templates/bda/forms/attribution_label_table.html b/bda/templates/bda/forms/attribution_label_table.html deleted file mode 100644 index b50b8290..00000000 --- a/bda/templates/bda/forms/attribution_label_table.html +++ /dev/null @@ -1 +0,0 @@ -{% include 'bda/forms/spectacle_label_table.html' with spectacle=attribution.spectacle %} \ No newline at end of file diff --git a/bda/templates/bda/forms/checkbox_table.html b/bda/templates/bda/forms/checkbox_table.html deleted file mode 100644 index e13a9c9d..00000000 --- a/bda/templates/bda/forms/checkbox_table.html +++ /dev/null @@ -1,4 +0,0 @@ - - - {{ widget.label }} - \ No newline at end of file diff --git a/bda/templates/bda/forms/date_tirage.html b/bda/templates/bda/forms/date_tirage.html deleted file mode 100644 index 98b1c5e1..00000000 --- a/bda/templates/bda/forms/date_tirage.html +++ /dev/null @@ -1 +0,0 @@ -{{ revente.date_tirage }} diff --git a/bda/templates/bda/forms/revente_other_label_table.html b/bda/templates/bda/forms/revente_other_label_table.html deleted file mode 100644 index 99c82a08..00000000 --- a/bda/templates/bda/forms/revente_other_label_table.html +++ /dev/null @@ -1,3 +0,0 @@ -{% include 'bda/forms/spectacle_label_table.html' with spectacle=revente.attribution.spectacle %} -{% with user=revente.seller.user %} {{user.first_name}} {{user.last_name}} {% endwith%} -{% include 'bda/forms/date_tirage.html' %} \ No newline at end of file diff --git a/bda/templates/bda/forms/revente_self_label_table.html b/bda/templates/bda/forms/revente_self_label_table.html deleted file mode 100644 index 3f32b114..00000000 --- a/bda/templates/bda/forms/revente_self_label_table.html +++ /dev/null @@ -1,2 +0,0 @@ -{% include 'bda/forms/spectacle_label_table.html' with spectacle=revente.attribution.spectacle %} -{% include 'bda/forms/date_tirage.html' %} \ No newline at end of file diff --git a/bda/templates/bda/forms/revente_sold_label_table.html b/bda/templates/bda/forms/revente_sold_label_table.html deleted file mode 100644 index 31777648..00000000 --- a/bda/templates/bda/forms/revente_sold_label_table.html +++ /dev/null @@ -1,4 +0,0 @@ -{% include 'bda/forms/spectacle_label_table.html' with spectacle=revente.attribution.spectacle %} -{% with user=revente.soldTo.user %} -{{user.first_name}} {{user.last_name}} -{% endwith %} \ No newline at end of file diff --git a/bda/templates/bda/forms/spectacle_label_table.html b/bda/templates/bda/forms/spectacle_label_table.html deleted file mode 100644 index a2c4181f..00000000 --- a/bda/templates/bda/forms/spectacle_label_table.html +++ /dev/null @@ -1,4 +0,0 @@ -{{ spectacle.title }} -{{ spectacle.date }} -{{ spectacle.location }} -{{ spectacle.price |floatformat }}€ \ No newline at end of file diff --git a/bda/templates/bda/inscription-formset.html b/bda/templates/bda/inscription-formset.html deleted file mode 100644 index 88b65600..00000000 --- a/bda/templates/bda/inscription-formset.html +++ /dev/null @@ -1,41 +0,0 @@ -{% load bootstrap %} -{{ formset.non_form_errors.as_ul }} - -{{ formset.management_form }} -{% for form in formset.forms %} - {% if forloop.first %} - - {% for field in form.visible_fields %} - {% if field.name != "DELETE" and field.name != "priority" %} - - {% endif %} - {% endfor %} - - - - {% endif %} - - {% for field in form.visible_fields %} - {% if field.name != "DELETE" and field.name != "priority" %} - - {% endif %} - {% endfor %} - - -{% endfor %} - -
{{ field.label|safe|capfirst }}1
- {% if forloop.first %} - {{ form.non_field_errors }} - {% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %} - {% endif %} - {{ field.errors.as_ul }} - {{ field | bootstrap }} -
- - - - -
-
-
diff --git a/bda/templates/bda/inscription-tirage.html b/bda/templates/bda/inscription-tirage.html deleted file mode 100644 index 3f8091df..00000000 --- a/bda/templates/bda/inscription-tirage.html +++ /dev/null @@ -1,169 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - -{% block extra_head %} - - - - - - -{% endblock %} - -{% block realcontent %} - - -

Inscription au tirage au sort du BdA

-
- {% csrf_token %} - {% include "bda/inscription-formset.html" %} -
- Prix total actuel : {{ total_price }}€ -
- - - - -
-

- 1: cette liste de vœux est ordonnée (du plus important au moins important), pour ajuster la priorité vous pouvez déplacer chaque vœu.
-

-
-
- -{% if not charte %} - -{% endif %} -{% endblock %} diff --git a/bda/templates/bda/mails-rappel.html b/bda/templates/bda/mails-rappel.html deleted file mode 100644 index c0770e47..00000000 --- a/bda/templates/bda/mails-rappel.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

Mails de rappels

- {% if sent %} -

Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes

- - {% else %} -

Voulez vous envoyer les mails de rappel pour le spectacle {{ show.title }} ?

- {% endif %} - -
- {% if not sent %} -
- {% csrf_token %} -
- -
-
- {% endif %} -
- -
- -

Forme des mails

- -

Une seule place

- {% for part in exemple_mail_1place %} -
{{ part }}
- {% endfor %} - -

Deux places

- {% for part in exemple_mail_2places %} -
{{ part }}
- {% endfor %} - -{% endblock %} diff --git a/bda/templates/bda/mails/attributions-decus.txt b/bda/templates/bda/mails/attributions-decus.txt deleted file mode 100644 index 69fadff6..00000000 --- a/bda/templates/bda/mails/attributions-decus.txt +++ /dev/null @@ -1,10 +0,0 @@ -Cher-e {{ member.first_name }}, - -Tu t'es inscrit-e pour le tirage au sort du BdA. Malheureusement, tu n'as -obtenu aucune place. - -Nous proposons cependant de nombreuses offres hors-tirage tout au long de -l'année, et nous t'invitons à nous contacter si l'une d'entre elles -t'intéresse ! --- -Le Bureau des Arts \ No newline at end of file diff --git a/bda/templates/bda/mails/attributions.txt b/bda/templates/bda/mails/attributions.txt deleted file mode 100644 index 3fd10032..00000000 --- a/bda/templates/bda/mails/attributions.txt +++ /dev/null @@ -1,31 +0,0 @@ -Cher-e {{ member.first_name }}, - -Tu t'es inscrit-e pour le tirage au sort du BdA. Tu as été sélectionné-e -pour les spectacles suivants : -{% for place in places %} -- 1 place pour {{ place }}{% endfor %} - -*Paiement* -L'intégralité de ces places de spectacles est à régler dès maintenant et AVANT -vendredi prochain, au bureau du COF pendant les heures de permanences (du lundi au vendredi -entre 12h et 14h, et entre 18h et 20h). Des facilités de paiement sont bien -évidemment possibles : nous pouvons ne pas encaisser le chèque immédiatement, -ou bien découper votre paiement en deux fois. Pour ceux qui ne pourraient pas -venir payer au bureau, merci de nous contacter par mail. - -*Mode de retrait des places* -Au moment du paiement, certaines places vous seront remises directement, -d'autres seront à récupérer au cours de l'année, d'autres encore seront -nominatives et à retirer le soir même dans les théâtres correspondants. -Pour chaque spectacle, vous recevrez un mail quelques jours avant la -représentation vous indiquant le mode de retrait. - -Nous vous rappelons que l'obtention de places du BdA vous engage à -respecter les règles de fonctionnement : -https://bda.ens.fr/lequipe/charte-bda/ -Un système de revente des places via les mails BdA-revente est disponible -directement sur votre compte GestioCOF. - -En vous souhaitant de très beaux spectacles tout au long de l'année, --- -Le Bureau des Arts \ No newline at end of file diff --git a/bda/templates/bda/mails/rappel.txt b/bda/templates/bda/mails/rappel.txt deleted file mode 100644 index 74614cbb..00000000 --- a/bda/templates/bda/mails/rappel.txt +++ /dev/null @@ -1,23 +0,0 @@ -Bonjour {{ member.first_name }}, - -Nous te rappellons que tu as eu la chance d'obtenir {{ nb_attr|pluralize:"une place,deux places" }} -pour {{ show.title }}, le {{ show.date }} au {{ show.location }}. N'oublie pas de t'y rendre ! -{% if nb_attr == 2 %} -Tu as obtenu deux places pour ce spectacle. Nous te rappelons que -ces places sont strictement réservées aux personnes de moins de 28 ans. -{% endif %} -{% if show.listing %}Pour ce spectacle, tu as reçu {{ nb_attr|pluralize:"une place,des places" }} sur -listing. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la représentation -pour {{ nb_attr|pluralize:"la,les" }} retirer. -{% else %}Pour assister à ce spectacle, tu dois présenter les billets qui ont -été distribués au burô. -{% endif %} - -Si tu ne peux plus assister à cette représentation, tu peux -revendre ta place via BdA-revente, accessible directement sur -GestioCOF (lien "revendre une place du premier tirage" sur la page -d'accueil https://www.cof.ens.fr/gestion/). - -En te souhaitant un excellent spectacle, --- -Le Bureau des Arts \ No newline at end of file diff --git a/bda/templates/bda/mails/revente-new.txt b/bda/templates/bda/mails/revente-new.txt deleted file mode 100644 index 7344011a..00000000 --- a/bda/templates/bda/mails/revente-new.txt +++ /dev/null @@ -1,12 +0,0 @@ -Bonjour {{ member.first_name }} - -Une place pour le spectacle {{ show.title }} ({{ show.date }}) -a été postée sur BdA-Revente. - -Si ce spectacle t'intéresse toujours, merci de nous le signaler en cliquant -sur ce lien : https://{{ site }}{% url "bda-revente-confirm" revente.id %}. -Dans le cas où plusieurs personnes seraient intéressées, nous procèderons à -un tirage au sort le {{ revente.date_tirage|date:"DATE_FORMAT" }}. - -Chaleureusement, -Le BdA \ No newline at end of file diff --git a/bda/templates/bda/mails/revente-seller.txt b/bda/templates/bda/mails/revente-seller.txt deleted file mode 100644 index 851ac09c..00000000 --- a/bda/templates/bda/mails/revente-seller.txt +++ /dev/null @@ -1,13 +0,0 @@ -Bonjour {{ vendeur.first_name }}, - -Tu t’es bien inscrit·e pour revendre une place pour {{ show.title }}. - -{% with revente.date_tirage as time %} -Le tirage au sort entre tout·e·s les racheteuse·eur·s potentiel·le·s aura lieu -le {{ time|date:"DATE_FORMAT" }} à {{ time|time:"TIME_FORMAT" }} (dans {{time|timeuntil }}). -Si personne ne s’est inscrit pour racheter la place, celle-ci apparaîtra parmi -les « Places disponibles immédiatement à la revente » sur GestioCOF. -{% endwith %} - -Bonne revente ! -Le Bureau des Arts \ No newline at end of file diff --git a/bda/templates/bda/mails/revente-shotgun-seller.txt b/bda/templates/bda/mails/revente-shotgun-seller.txt deleted file mode 100644 index e67083fc..00000000 --- a/bda/templates/bda/mails/revente-shotgun-seller.txt +++ /dev/null @@ -1,6 +0,0 @@ -Bonjour {{ vendeur.first_name }} ! - -Je souhaiterais racheter ta place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) à {{ show.price|floatformat:2 }}€. -Contacte-moi si tu es toujours intéressé·e ! - -{{ acheteur.get_full_name }} ({{ acheteur.email }}) \ No newline at end of file diff --git a/bda/templates/bda/mails/revente-shotgun.txt b/bda/templates/bda/mails/revente-shotgun.txt deleted file mode 100644 index e7b1ce29..00000000 --- a/bda/templates/bda/mails/revente-shotgun.txt +++ /dev/null @@ -1,11 +0,0 @@ -Bonjour {{ member.first_name }} - -Une place pour le spectacle {{ show.title }} ({{ show.date }}) -a été postée sur BdA-Revente. - -Puisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour -cette place : elle est disponible immédiatement à l'adresse -https://{{ site }}{% url "bda-revente-buy" show.id %}, à la disposition de tous. - -Chaleureusement, -Le BdA \ No newline at end of file diff --git a/bda/templates/bda/mails/revente-tirage-loser.txt b/bda/templates/bda/mails/revente-tirage-loser.txt deleted file mode 100644 index c1d49a01..00000000 --- a/bda/templates/bda/mails/revente-tirage-loser.txt +++ /dev/null @@ -1,9 +0,0 @@ -Bonjour {{ acheteur.first_name }}, - -Tu t'étais inscrit·e pour la revente de la place de {{ vendeur.get_full_name }} -pour {{ show.title }}. -Malheureusement, une autre personne a été tirée au sort pour racheter la place. -Tu pourras certainement retenter ta chance pour une autre revente ! - -À très bientôt, -Le Bureau des Arts \ No newline at end of file diff --git a/bda/templates/bda/mails/revente-tirage-seller.txt b/bda/templates/bda/mails/revente-tirage-seller.txt deleted file mode 100644 index 7abff7ca..00000000 --- a/bda/templates/bda/mails/revente-tirage-seller.txt +++ /dev/null @@ -1,7 +0,0 @@ -Bonjour {{ vendeur.first_name }}, - -La personne tirée au sort pour racheter ta place pour {{ show.title }} est {{ acheteur.get_full_name }}. -Tu peux le/la contacter à l'adresse {{ acheteur.email }}, ou en répondant à ce mail. - -Chaleureusement, -Le BdA \ No newline at end of file diff --git a/bda/templates/bda/mails/revente-tirage-winner.txt b/bda/templates/bda/mails/revente-tirage-winner.txt deleted file mode 100644 index 11428ef7..00000000 --- a/bda/templates/bda/mails/revente-tirage-winner.txt +++ /dev/null @@ -1,7 +0,0 @@ -Bonjour {{ acheteur.first_name }}, - -Tu as été tiré·e au sort pour racheter une place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) à {{ show.price|floatformat:2 }}€. -Tu peux contacter le/la vendeur·se à l'adresse {{ vendeur.email }}. - -Chaleureusement, -Le BdA \ No newline at end of file diff --git a/bda/templates/bda/participants.html b/bda/templates/bda/participants.html deleted file mode 100644 index 4ab2d1f7..00000000 --- a/bda/templates/bda/participants.html +++ /dev/null @@ -1,71 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - -{% block realcontent %} -

{{ spectacle }}

- - - - - - - - - - - - {% for participant in participants %} - - - - - - - - {% endfor %} - -
NomPlacesAdresse MailPayéDonné
{{participant.name}}{{participant.nb_places}} place{{participant.nb_places|pluralize}}{{participant.email}} - {% if participant.paid %}Oui{% else %}Non{%endif%} - - {% if participant.given == participant.nb_places %}Oui - {% elif participant.given == 0 %}Non - {% else %}{{participant.given}}/{{participant.nb_places}} - {%endif%} -
-

Ajouter une attribution

-
-
- - -
- -
- - -
- -
- Page d'envoi manuel des mails de rappel -
- - - - -{% endblock %} diff --git a/bda/templates/bda/resume-inscription-tirage.html b/bda/templates/bda/resume-inscription-tirage.html deleted file mode 100644 index 0ad7ec0e..00000000 --- a/bda/templates/bda/resume-inscription-tirage.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} - {% if choices %} -

Vos vœux:

-
    - {% for choice in choices %} -
  1. {{ choice.spectacle }}{% if choice.double %} (deux places{% if autoquit %}, abandon automatique{% endif %}){% endif %}
  2. - {% endfor %} -
- {% else %} -

Vous n'avez enregistré aucun vœu pour le tirage au sort

- {% endif %} -{% endblock %} diff --git a/bda/templates/bda/resume_places.html b/bda/templates/bda/resume_places.html deleted file mode 100644 index fb314f1d..00000000 --- a/bda/templates/bda/resume_places.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

Places attribuées

- {% if places %} - - {% for place in places %} - - - - - - - - {% endfor %} -
{{place.spectacle.title}}{{place.spectacle.location}}{{place.spectacle.date}}{% if place.double %}deux places{%else%}une place{% endif %}{% if place.spectacle.listing %}sur listing{% else %}place physique{% endif %}
-

Total à payer : {{ total|floatformat }}€

-
-

Ne manque pas un spectacle avec le - calendrier - automatique !

- {% else %} -

Vous n'avez aucune place :(

- {% endif %} -{% endblock %} diff --git a/bda/templates/bda/revente/confirm-shotgun.html b/bda/templates/bda/revente/confirm-shotgun.html deleted file mode 100644 index d7614c25..00000000 --- a/bda/templates/bda/revente/confirm-shotgun.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - - -{%block realcontent %} -

Rachat d'une place

-
-{% csrf_token %} -
-Bonjour !
-
-Je souhaiterais racheter ta place pour {{spectacle.title}} le {{spectacle.date}} ({{spectacle.location}}) à {{spectacle.price}}€. 
-Contacte-moi si tu es toujours intéressé-e !
-
-{{user.get_full_name}} ({{user.email}})
-
- -
-

Note : ce mail sera envoyé à une personne au hasard revendant sa place.

-{%endblock%} diff --git a/bda/templates/bda/revente/confirmed.html b/bda/templates/bda/revente/confirmed.html deleted file mode 100644 index 780330bd..00000000 --- a/bda/templates/bda/revente/confirmed.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - -{% block realcontent %} -

Inscription à une revente

-

Votre inscription a bien été enregistrée !

-

Le tirage au sort pour cette revente ({{spectacle}}) sera effectué le {{date}}. - -{% endblock %} diff --git a/bda/templates/bda/revente/mail-success.html b/bda/templates/bda/revente/mail-success.html deleted file mode 100644 index 5e970eb7..00000000 --- a/bda/templates/bda/revente/mail-success.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - -{% block realcontent %} - -

Revente de place

-

Un mail a bien été envoyé à {{seller.get_full_name}} ({{seller.email}}), pour racheter une place pour {{spectacle.title}} !

-{% endblock %} diff --git a/bda/templates/bda/revente/manage.html b/bda/templates/bda/revente/manage.html deleted file mode 100644 index cd09f997..00000000 --- a/bda/templates/bda/revente/manage.html +++ /dev/null @@ -1,121 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - -{% block realcontent %} - -

Gestion des places que je revends

- -{% if resell_exists %} -
- -

Places non revendues

-
-
- - Cochez les places que vous souhaitez revendre, et validez. Vous aurez - ensuite 1h pour changer d'avis avant que la revente soit confirmée et - que les notifications soient envoyées aux intéressé·e·s. -
- {% csrf_token %} - - - - - - - - - - - - {% for checkbox in resellform.attributions %}{{ checkbox }}{% endfor %} - -
TitreLieuDatePrix
-
- -
-
- -
-{% endif %} - -{% if annul_exists %} -

Places en cours de revente

-
-
- - Vous pouvez annuler les reventes qui n'ont pas encore trouvé preneur·se. -
- {% csrf_token %} - - - - - - - - - - - - - {% for checkbox in annulform.reventes %}{{ checkbox }}{% endfor %} - -
TitreDateLieuPrixTirage le
- -
- -
-{% endif %} - -{% if sold_exists %} -

Places revendues

-
-
- - Pour chaque revente, vous devez soit l'annuler soit la confirmer pour - transférer la place la place à la personne tirée au sort. - - L'annulation sert par exemple à pouvoir remettre la place en jeu si - vous ne parvenez pas à entrer en contact avec la personne tirée au - sort. -
- {% csrf_token %} - - - - - - - - - - - - - {% for checkbox in soldform.reventes %}{{ checkbox }}{% endfor %} - -
TitreDateLieuPrixVendue à
- - -
-{% endif %} -{% if not resell_exists and not annul_exists and not sold_exists %} -

Plus de reventes possibles !

-{% endif %} - - - -{% endblock %} diff --git a/bda/templates/bda/revente/none.html b/bda/templates/bda/revente/none.html deleted file mode 100644 index eabb3dc7..00000000 --- a/bda/templates/bda/revente/none.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

BdA-Revente

-

Il n'y a plus de places en revente pour ce spectacle, désolé !

-{% endblock %} diff --git a/bda/templates/bda/revente/shotgun.html b/bda/templates/bda/revente/shotgun.html deleted file mode 100644 index a724032e..00000000 --- a/bda/templates/bda/revente/shotgun.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

Places disponibles immédiatement

- {% if spectacles %} - - - - - - - - - - - - {% for spectacle in spectacles %} - - {% include "bda/forms/spectacle_label_table.html" with spectacle=spectacle %} - - {% endfor %} - -
TitreDateLieuPrix
Racheter -
- {% else %} -

Pas de places disponibles immédiatement, désolé !

- {% endif %} - -{% endblock %} diff --git a/bda/templates/bda/revente/subscribe.html b/bda/templates/bda/revente/subscribe.html deleted file mode 100644 index e0a7176c..00000000 --- a/bda/templates/bda/revente/subscribe.html +++ /dev/null @@ -1,64 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles%} - -{% block realcontent %} -

Inscriptions pour BdA-Revente

-
-
- - Cochez les spectacles pour lesquels vous souhaitez recevoir une - notification quand une place est disponible en revente.
- Lorsque vous validez vos choix, si un tirage au sort est en cours pour - un des spectacles que vous avez sélectionné, vous serez automatiquement - inscrit à ce tirage. -
-
- {% csrf_token %} -
- - - - - - - - - - - - - - {% for checkbox in form.spectacles %}{{ checkbox }}{% endfor %} - -
TitreDateLieuPrix
- -
- -
- - -{% endblock %} diff --git a/bda/templates/bda/revente/tirages.html b/bda/templates/bda/revente/tirages.html deleted file mode 100644 index 4d9ac126..00000000 --- a/bda/templates/bda/revente/tirages.html +++ /dev/null @@ -1,99 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - -{% block realcontent %} - -

Tirages au sort de reventes

- -{% if annul_exists %} -

Les reventes auxquelles vous êtes inscrit·e

-
-
- - Voici la liste des reventes auxquelles vous êtes inscrit·e ; si vous ne souhaitez plus participer au tirage au sort vous pouvez vous en désister. -
- {% csrf_token %} - - - - - - - - - - - - - - {% for checkbox in annulform.reventes %}{{ checkbox }}{% endfor %} - -
TitreDateLieuPrixVendue parTirage le
-
- -
-
-{% endif %} - -
- -{% if sub_exists %} - -

Tirages en cours

-
-
- - Vous pouvez vous inscrire aux tirages en cours suivants. -
- {% csrf_token %} - - - - - - - - - - - - - - {% for checkbox in subform.reventes %}{{ checkbox }}{% endfor %} - -
TitreDateLieuPrixVendue parTirage le
-
- -
-
-{% endif %} - -{% if not annul_exists and not sub_exists %} -
- - Aucune revente n'est active pour le moment ! -
-{% endif %} - - - - -{% endblock %} diff --git a/bda/templates/bda/revente/wrongtime.html b/bda/templates/bda/revente/wrongtime.html deleted file mode 100644 index 18c417a2..00000000 --- a/bda/templates/bda/revente/wrongtime.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

Nope

- {% if revente.shotgun %} -

Le tirage au sort de cette revente a déjà été effectué !

- -

Si personne n'était intéressé, elle est maintenant disponible - ici.

- {% else %} -

Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !

- {% endif %} -{% endblock %} diff --git a/bda/templates/spectacle_list.html b/bda/templates/spectacle_list.html deleted file mode 100644 index 4539d730..00000000 --- a/bda/templates/spectacle_list.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - -{% block extra_head %} - -{% endblock %} - -{% block realcontent %} -

{{tirage_name}}

-

Liste des spectacles

- - - - - - - - - - - - - {% for spectacle in object_list %} - - - - - - - {% endfor %} - -
TitreDateLieuPrix
{{ spectacle.title }} {{ spectacle.date }}{{ spectacle.location }} - {{ spectacle.price |floatformat }}€ -
- - - - -

Exports

- -{% endblock %} diff --git a/bda/templates/tirage-failed.html b/bda/templates/tirage-failed.html deleted file mode 100644 index 74849487..00000000 --- a/bda/templates/tirage-failed.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

Raté, le tirage ne peut pas être lancé !

- -

Soit les inscriptions ne sont en pas encore fermées, soit le lancement du -tirage est désactivé. Si vous savez ce que vous faites, vous pouvez autoriser -le lancement du tirage dans -l'interface admin.

-{% endblock %} diff --git a/bda/tests/__init__.py b/bda/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bda/tests/mixins.py b/bda/tests/mixins.py deleted file mode 100644 index 1f690172..00000000 --- a/bda/tests/mixins.py +++ /dev/null @@ -1,65 +0,0 @@ -from django.utils import timezone - -from shared.tests.mixins import ViewTestCaseMixin - -from ..models import CategorieSpectacle, Salle, Spectacle, Tirage -from .utils import create_user - - -class BdAViewTestCaseMixin(ViewTestCaseMixin): - def get_users_base(self): - return { - "bda_other": create_user(username="bda_other"), - "bda_member": create_user(username="bda_member", is_cof=True), - "bda_staff": create_user(username="bda_staff", is_cof=True, is_buro=True), - } - - -class BdATestHelpers: - bda_testdata = False - - def setUp(self): - super().setUp() - - if self.bda_testdata: - self.load_bda_testdata() - - def load_bda_testdata(self): - self.tirage = Tirage.objects.create( - title="Test tirage", - appear_catalogue=True, - ouverture=timezone.now(), - fermeture=timezone.now(), - ) - self.category = CategorieSpectacle.objects.create(name="Category") - self.location = Salle.objects.create(name="here") - self.show1 = Spectacle.objects.create( - title="foo", - date=timezone.now(), - location=self.location, - price=0, - slots=42, - tirage=self.tirage, - listing=False, - category=self.category, - ) - self.show2 = Spectacle.objects.create( - title="bar", - date=timezone.now(), - location=self.location, - price=1, - slots=142, - tirage=self.tirage, - listing=False, - category=self.category, - ) - self.show3 = Spectacle.objects.create( - title="baz", - date=timezone.now(), - location=self.location, - price=2, - slots=242, - tirage=self.tirage, - listing=False, - category=self.category, - ) diff --git a/bda/tests/test_models.py b/bda/tests/test_models.py deleted file mode 100644 index abff407b..00000000 --- a/bda/tests/test_models.py +++ /dev/null @@ -1,100 +0,0 @@ -from datetime import timedelta -from unittest import mock - -from django.contrib.auth import get_user_model -from django.core import mail -from django.test import TestCase -from django.utils import timezone - -from bda.models import ( - Attribution, - Participant, - Salle, - Spectacle, - SpectacleRevente, - Tirage, -) - -User = get_user_model() - - -class SpectacleReventeTests(TestCase): - def setUp(self): - now = timezone.now() - - self.t = Tirage.objects.create( - title="Tirage", - ouverture=now - timedelta(days=7), - fermeture=now - timedelta(days=3), - active=True, - ) - self.s = Spectacle.objects.create( - title="Spectacle", - date=now + timedelta(days=20), - location=Salle.objects.create(name="Salle", address="Address"), - price=10.5, - slots=5, - tirage=self.t, - listing=False, - ) - - self.seller = Participant.objects.create( - user=User.objects.create(username="seller", email="seller@mail.net"), - tirage=self.t, - ) - self.p1 = Participant.objects.create( - user=User.objects.create(username="part1", email="part1@mail.net"), - tirage=self.t, - ) - self.p2 = Participant.objects.create( - user=User.objects.create(username="part2", email="part2@mail.net"), - tirage=self.t, - ) - self.p3 = Participant.objects.create( - user=User.objects.create(username="part3", email="part3@mail.net"), - tirage=self.t, - ) - - self.attr = Attribution.objects.create( - participant=self.seller, spectacle=self.s - ) - - self.rev = SpectacleRevente.objects.create( - attribution=self.attr, seller=self.seller - ) - - def test_tirage(self): - revente = self.rev - - wanted_by = [self.p1, self.p2, self.p3] - revente.confirmed_entry.set(wanted_by) - - with mock.patch("bda.models.random.choice") as mc: - # Set winner to self.p1. - mc.return_value = self.p1 - - revente.tirage() - - # Call to random.choice used participants in wanted_by. - mc_args, _ = mc.call_args - - self.assertEqual(set(mc_args[0]), set(wanted_by)) - - self.assertEqual(revente.soldTo, self.p1) - self.assertTrue(revente.tirage_done) - - mails = {m.to[0]: m for m in mail.outbox} - - self.assertEqual(len(mails), 4) - - m_seller = mails["seller@mail.net"] - self.assertListEqual(m_seller.to, ["seller@mail.net"]) - self.assertListEqual(m_seller.reply_to, ["part1@mail.net"]) - - m_winner = mails["part1@mail.net"] - self.assertListEqual(m_winner.to, ["part1@mail.net"]) - - self.assertCountEqual( - [mails["part2@mail.net"].to, mails["part3@mail.net"].to], - [["part2@mail.net"], ["part3@mail.net"]], - ) diff --git a/bda/tests/test_revente.py b/bda/tests/test_revente.py deleted file mode 100644 index 202e9494..00000000 --- a/bda/tests/test_revente.py +++ /dev/null @@ -1,79 +0,0 @@ -from datetime import timedelta - -from django.contrib.auth.models import User -from django.test import TestCase -from django.utils import timezone - -from bda.models import ( - Attribution, - CategorieSpectacle, - Participant, - Salle, - Spectacle, - SpectacleRevente, - Tirage, -) - - -class TestModels(TestCase): - def setUp(self): - self.tirage = Tirage.objects.create( - title="Tirage test", - appear_catalogue=True, - ouverture=timezone.now(), - fermeture=timezone.now(), - ) - self.category = CategorieSpectacle.objects.create(name="Category") - self.location = Salle.objects.create(name="here") - self.spectacle_soon = Spectacle.objects.create( - title="foo", - date=timezone.now() + timedelta(days=1), - location=self.location, - price=0, - slots=42, - tirage=self.tirage, - listing=False, - category=self.category, - ) - self.spectacle_later = Spectacle.objects.create( - title="bar", - date=timezone.now() + timedelta(days=30), - location=self.location, - price=0, - slots=42, - tirage=self.tirage, - listing=False, - category=self.category, - ) - - user_buyer = User.objects.create_user( - username="bda_buyer", password="testbuyer" - ) - user_seller = User.objects.create_user( - username="bda_seller", password="testseller" - ) - self.buyer = Participant.objects.create(user=user_buyer, tirage=self.tirage) - self.seller = Participant.objects.create(user=user_seller, tirage=self.tirage) - - self.attr_soon = Attribution.objects.create( - participant=self.seller, spectacle=self.spectacle_soon - ) - self.attr_later = Attribution.objects.create( - participant=self.seller, spectacle=self.spectacle_later - ) - self.revente_soon = SpectacleRevente.objects.create( - seller=self.seller, attribution=self.attr_soon - ) - self.revente_later = SpectacleRevente.objects.create( - seller=self.seller, attribution=self.attr_later - ) - - def test_urgent(self): - self.assertTrue(self.revente_soon.is_urgent) - self.assertFalse(self.revente_later.is_urgent) - - def test_tirage(self): - self.revente_soon.confirmed_entry.add(self.buyer) - - self.assertEqual(self.revente_soon.tirage(send_mails=False), self.buyer) - self.assertIsNone(self.revente_later.tirage(send_mails=False)) diff --git a/bda/tests/test_views.py b/bda/tests/test_views.py deleted file mode 100644 index 47cbd2bd..00000000 --- a/bda/tests/test_views.py +++ /dev/null @@ -1,368 +0,0 @@ -import json -from datetime import timedelta -from unittest import mock - -from django.contrib.auth import get_user_model -from django.test import Client, TestCase -from django.urls import reverse -from django.utils import formats, timezone - -from ..models import Participant, Tirage -from .mixins import BdATestHelpers, BdAViewTestCaseMixin - -User = get_user_model() - - -class InscriptionViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase): - url_name = "bda-tirage-inscription" - - http_methods = ["GET", "POST"] - - auth_user = "bda_member" - auth_forbidden = [None, "bda_other"] - - bda_testdata = True - - @property - def url_kwargs(self): - return {"tirage_id": self.tirage.id} - - @property - def url_expected(self): - return "/gestion/bda/inscription/{}".format(self.tirage.id) - - def test_get_opened(self): - self.tirage.ouverture = timezone.now() - timedelta(days=1) - self.tirage.fermeture = timezone.now() + timedelta(days=1) - self.tirage.save() - - resp = self.client.get(self.url) - - self.assertEqual(resp.status_code, 200) - self.assertFalse(resp.context["messages"]) - - def test_get_closed_future(self): - self.tirage.ouverture = timezone.now() + timedelta(days=1) - self.tirage.fermeture = timezone.now() + timedelta(days=2) - self.tirage.save() - - resp = self.client.get(self.url) - - self.assertEqual(resp.status_code, 200) - self.assertIn( - "Le tirage n'est pas encore ouvert : ouverture le {}".format( - formats.localize(timezone.template_localtime(self.tirage.ouverture)) - ), - [str(msg) for msg in resp.context["messages"]], - ) - - def test_get_closed_past(self): - self.tirage.ouverture = timezone.now() - timedelta(days=2) - self.tirage.fermeture = timezone.now() - timedelta(days=1) - self.tirage.save() - - resp = self.client.get(self.url) - - self.assertEqual(resp.status_code, 200) - self.assertIn( - " C'est fini : tirage au sort dans la journée !", - [str(msg) for msg in resp.context["messages"]], - ) - - def get_base_post_data(self): - return { - "choixspectacle_set-TOTAL_FORMS": "3", - "choixspectacle_set-INITIAL_FORMS": "0", - "choixspectacle_set-MIN_NUM_FORMS": "0", - "choixspectacle_set-MAX_NUM_FORMS": "1000", - } - - base_post_data = property(get_base_post_data) - - def test_post(self): - self.tirage.ouverture = timezone.now() - timedelta(days=1) - self.tirage.fermeture = timezone.now() + timedelta(days=1) - self.tirage.save() - - data = dict( - self.base_post_data, - **{ - "choixspectacle_set-TOTAL_FORMS": "2", - "choixspectacle_set-0-id": "", - "choixspectacle_set-0-participant": "", - "choixspectacle_set-0-spectacle": str(self.show1.pk), - "choixspectacle_set-0-double_choice": "1", - "choixspectacle_set-0-priority": "2", - "choixspectacle_set-1-id": "", - "choixspectacle_set-1-participant": "", - "choixspectacle_set-1-spectacle": str(self.show2.pk), - "choixspectacle_set-1-double_choice": "autoquit", - "choixspectacle_set-1-priority": "1", - } - ) - resp = self.client.post(self.url, data) - - self.assertEqual(resp.status_code, 200) - self.assertIn( - "Votre inscription a été mise à jour avec succès !", - [str(msg) for msg in resp.context["messages"]], - ) - participant = Participant.objects.get( - user=self.users["bda_member"], tirage=self.tirage - ) - self.assertSetEqual( - set( - participant.choixspectacle_set.values_list( - "priority", "spectacle_id", "double_choice" - ) - ), - {(1, self.show2.pk, "autoquit"), (2, self.show1.pk, "1")}, - ) - - def test_post_state_changed(self): - self.tirage.ouverture = timezone.now() - timedelta(days=1) - self.tirage.fermeture = timezone.now() + timedelta(days=1) - self.tirage.save() - - data = {"dbstate": "different"} - resp = self.client.post(self.url, data) - - self.assertEqual(resp.status_code, 200) - self.assertIn( - "Impossible d'enregistrer vos modifications : vous avez apporté d'autres " - "modifications entre temps.", - [str(msg) for msg in resp.context["messages"]], - ) - - -class PlacesViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase): - url_name = "bda-places-attribuees" - - auth_user = "bda_member" - auth_forbidden = [None, "bda_other"] - - bda_testdata = True - - @property - def url_kwargs(self): - return {"tirage_id": self.tirage.id} - - @property - def url_expected(self): - return "/gestion/bda/places/{}".format(self.tirage.id) - - -class EtatPlacesViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase): - url_name = "bda-etat-places" - - auth_user = "bda_member" - auth_forbidden = [None, "bda_other"] - - bda_testdata = True - - @property - def url_kwargs(self): - return {"tirage_id": self.tirage.id} - - @property - def url_expected(self): - return "/gestion/bda/etat-places/{}".format(self.tirage.id) - - -class TirageViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase): - url_name = "bda-tirage" - - http_methods = ["GET", "POST"] - - auth_user = "bda_staff" - auth_forbidden = [None, "bda_other", "bda_member"] - - bda_testdata = True - - @property - def url_kwargs(self): - return {"tirage_id": self.tirage.id} - - @property - def url_expected(self): - return "/gestion/bda/tirage/{}".format(self.tirage.id) - - def test_perform_tirage_disabled(self): - # Cannot be performed if disabled - self.tirage.enable_do_tirage = False - self.tirage.save() - resp = self.client.get(self.url) - self.assertTemplateUsed(resp, "tirage-failed.html") - - def test_perform_tirage_opened_registrations(self): - # Cannot be performed if registrations are still open - self.tirage.enable_do_tirage = True - self.tirage.fermeture = timezone.now() + timedelta(seconds=3600) - self.tirage.save() - resp = self.client.get(self.url) - self.assertTemplateUsed(resp, "tirage-failed.html") - - def test_perform_tirage(self): - # Otherwise, perform the tirage - self.tirage.enable_do_tirage = True - self.tirage.fermeture = timezone.now() - self.tirage.save() - resp = self.client.get(self.url) - self.assertTemplateNotUsed(resp, "tirage-failed.html") - - -class SpectacleListViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase): - url_name = "bda-liste-spectacles" - - auth_user = "bda_staff" - auth_forbidden = [None, "bda_other", "bda_member"] - - bda_testdata = True - - @property - def url_kwargs(self): - return {"tirage_id": self.tirage.id} - - @property - def url_expected(self): - return "/gestion/bda/spectacles/{}".format(self.tirage.id) - - -class SpectacleViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase): - url_name = "bda-spectacle" - - auth_user = "bda_staff" - auth_forbidden = [None, "bda_other", "bda_member"] - - bda_testdata = True - - @property - def url_kwargs(self): - return {"tirage_id": self.tirage.id, "spectacle_id": self.show1.id} - - @property - def url_expected(self): - return "/gestion/bda/spectacles/{}/{}".format(self.tirage.id, self.show1.id) - - -class UnpaidViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase): - url_name = "bda-unpaid" - - auth_user = "bda_staff" - auth_forbidden = [None, "bda_other", "bda_member"] - - bda_testdata = True - - @property - def url_kwargs(self): - return {"tirage_id": self.tirage.id} - - @property - def url_expected(self): - return "/gestion/bda/spectacles/unpaid/{}".format(self.tirage.id) - - -class SendRemindersViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase): - url_name = "bda-rappels" - - auth_user = "bda_staff" - auth_forbidden = [None, "bda_other", "bda_member"] - - bda_testdata = True - - @property - def url_kwargs(self): - return {"spectacle_id": self.show1.id} - - @property - def url_expected(self): - return "/gestion/bda/mails-rappel/{}".format(self.show1.id) - - def test_post(self): - resp = self.client.post(self.url) - self.assertEqual(200, resp.status_code) - # TODO: check that emails are sent - - -class CatalogueViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase): - auth_user = None - auth_forbidden = [] - - bda_testdata = True - - def test_api_list(self): - url_list = "/gestion/bda/catalogue/list" - resp = self.client.get(url_list) - self.assertJSONEqual( - resp.content.decode("utf-8"), - [{"id": self.tirage.id, "title": self.tirage.title}], - ) - - def test_api_details(self): - url_details = "/gestion/bda/catalogue/details?id={}".format(self.tirage.id) - resp = self.client.get(url_details) - self.assertJSONEqual( - resp.content.decode("utf-8"), - { - "categories": [{"id": self.category.id, "name": self.category.name}], - "locations": [{"id": self.location.id, "name": self.location.name}], - }, - ) - - def test_api_descriptions(self): - url_descriptions = "/gestion/bda/catalogue/descriptions?id={}".format( - self.tirage.id - ) - resp = self.client.get(url_descriptions) - raw = resp.content.decode("utf-8") - try: - results = json.loads(raw) - except ValueError: - self.fail("Not valid JSON: {}".format(raw)) - self.assertEqual(len(results), 3) - self.assertEqual( - {(s["title"], s["price"], s["slots"]) for s in results}, - {("foo", 0, 42), ("bar", 1, 142), ("baz", 2, 242)}, - ) - - -# ----- BdA Revente --------------------------------------- # - - -def make_participant(name: str, tirage: Tirage) -> User: - user = User.objects.create_user(username=name, password=name) - user.profile.is_cof = True - user.profile.save() - Participant.objects.create(user=user, tirage=tirage) - return user - - -class TestReventeManageTest(TestCase): - def setUp(self): - self.tirage = Tirage.objects.create( - title="tirage1", - ouverture=timezone.now(), - fermeture=timezone.now() + timedelta(days=90), - ) - self.user = make_participant("toto", self.tirage) - self.url = reverse("bda-revente-manage", args=[self.tirage.id]) - - # Signals handlers on login/logout send messages. - # Due to the way the Django' test Client performs login, this raise an - # error. As workaround, we mock the Django' messages module. - patcher_messages = mock.patch("gestioncof.signals.messages") - patcher_messages.start() - self.addCleanup(patcher_messages.stop) - - def test_can_get(self): - client = Client() - client.force_login( - self.user, backend="django.contrib.auth.backends.ModelBackend" - ) - r = client.get(self.url) - self.assertEqual(r.status_code, 200) - - -class TestBdaRevente: - pass - # TODO diff --git a/bda/tests/utils.py b/bda/tests/utils.py deleted file mode 100644 index 68f51fb6..00000000 --- a/bda/tests/utils.py +++ /dev/null @@ -1,36 +0,0 @@ -from datetime import timedelta - -from django.contrib.auth.models import User -from django.utils import timezone - -from ..models import CategorieSpectacle, Salle, Spectacle, Tirage - - -def create_user(username, is_cof=False, is_buro=False): - user = User.objects.create_user(username=username, password=username) - user.profile.is_cof = is_cof - user.profile.is_buro = is_buro - user.profile.save() - return user - - -def user_is_cof(user): - return (user is not None) and user.profile.is_cof - - -def user_is_staff(user): - return (user is not None) and user.profile.is_buro - - -def create_spectacle(**kwargs): - defaults = { - "title": "Title", - "category": CategorieSpectacle.objects.first(), - "date": (timezone.now() + timedelta(days=7)).date(), - "location": Salle.objects.first(), - "price": 10.0, - "slots": 20, - "tirage": Tirage.objects.first(), - "listing": False, - } - return Spectacle.objects.create(**dict(defaults, **kwargs)) diff --git a/bda/urls.py b/bda/urls.py deleted file mode 100644 index 5b452362..00000000 --- a/bda/urls.py +++ /dev/null @@ -1,74 +0,0 @@ -from django.conf.urls import url - -from bda import views -from bda.views import SpectacleListView -from gestioncof.decorators import buro_required - -urlpatterns = [ - url( - r"^inscription/(?P\d+)$", - views.inscription, - name="bda-tirage-inscription", - ), - url(r"^places/(?P\d+)$", views.places, name="bda-places-attribuees"), - url(r"^etat-places/(?P\d+)$", views.etat_places, name="bda-etat-places"), - url(r"^tirage/(?P\d+)$", views.tirage, name="bda-tirage"), - url( - r"^spectacles/(?P\d+)$", - buro_required(SpectacleListView.as_view()), - name="bda-liste-spectacles", - ), - url( - r"^spectacles/(?P\d+)/(?P\d+)$", - views.spectacle, - name="bda-spectacle", - ), - url( - r"^spectacles/unpaid/(?P\d+)$", - views.UnpaidParticipants.as_view(), - name="bda-unpaid", - ), - url( - r"^spectacles/autocomplete$", - views.spectacle_autocomplete, - name="bda-spectacle-autocomplete", - ), - url( - r"^participants/autocomplete$", - views.participant_autocomplete, - name="bda-participant-autocomplete", - ), - # Urls BdA-Revente - url( - r"^revente/(?P\d+)/manage$", - views.revente_manage, - name="bda-revente-manage", - ), - url( - r"^revente/(?P\d+)/subscribe$", - views.revente_subscribe, - name="bda-revente-subscribe", - ), - url( - r"^revente/(?P\d+)/tirages$", - views.revente_tirages, - name="bda-revente-tirages", - ), - url( - r"^revente/(?P\d+)/buy$", - views.revente_buy, - name="bda-revente-buy", - ), - url( - r"^revente/(?P\d+)/confirm$", - views.revente_confirm, - name="bda-revente-confirm", - ), - url( - r"^revente/(?P\d+)/shotgun$", - views.revente_shotgun, - name="bda-revente-shotgun", - ), - url(r"^mails-rappel/(?P\d+)$", views.send_rappel, name="bda-rappels"), - url(r"^catalogue/(?P[a-z]+)$", views.catalogue, name="bda-catalogue"), -] diff --git a/bda/views.py b/bda/views.py deleted file mode 100644 index d8852674..00000000 --- a/bda/views.py +++ /dev/null @@ -1,911 +0,0 @@ -import hashlib -import json -import random -import time -from collections import defaultdict - -from django.conf import settings -from django.contrib import messages -from django.core import serializers -from django.core.exceptions import NON_FIELD_ERRORS -from django.core.mail import send_mail, send_mass_mail -from django.db import transaction -from django.db.models import Count, Prefetch -from django.forms.models import inlineformset_factory -from django.http import HttpResponseBadRequest, HttpResponseRedirect, JsonResponse -from django.shortcuts import get_object_or_404, render -from django.template import loader -from django.template.defaultfilters import pluralize -from django.urls import reverse -from django.utils import formats, timezone -from django.views.generic.list import ListView - -from bda.algorithm import Algorithm -from bda.forms import ( - AnnulForm, - InscriptionInlineFormSet, - InscriptionReventeForm, - ResellForm, - ReventeTirageAnnulForm, - ReventeTirageForm, - SoldForm, - TokenForm, -) -from bda.models import ( - Attribution, - CategorieSpectacle, - ChoixSpectacle, - Participant, - Salle, - Spectacle, - SpectacleRevente, - Tirage, -) -from gestioncof.decorators import BuroRequiredMixin, buro_required, cof_required -from shared.views import Select2QuerySetView - - -@cof_required -def etat_places(request, tirage_id): - """ - Résumé des spectacles d'un tirage avec pour chaque spectacle : - - Le nombre de places en jeu - - Le nombre de demandes - - Le ratio demandes/places - Et le total de toutes les demandes - """ - tirage = get_object_or_404(Tirage, id=tirage_id) - - spectacles = tirage.spectacle_set.select_related("location") - spectacles_dict = {} # index of spectacle by id - - for spectacle in spectacles: - spectacle.total = 0 # init total requests - spectacles_dict[spectacle.id] = spectacle - - choices = ( - ChoixSpectacle.objects.filter(spectacle__in=spectacles) - .values("spectacle") - .annotate(total=Count("spectacle")) - ) - - # choices *by spectacles* whose only 1 place is requested - choices1 = choices.filter(double_choice="1") - # choices *by spectacles* whose 2 places is requested - choices2 = choices.exclude(double_choice="1") - - for spectacle in choices1: - pk = spectacle["spectacle"] - spectacles_dict[pk].total += spectacle["total"] - for spectacle in choices2: - pk = spectacle["spectacle"] - spectacles_dict[pk].total += 2 * spectacle["total"] - - # here, each spectacle.total contains the number of requests - - slots = 0 # proposed slots - total = 0 # requests - for spectacle in spectacles: - slots += spectacle.slots - total += spectacle.total - spectacle.ratio = spectacle.total / spectacle.slots - - context = { - "proposed": slots, - "spectacles": spectacles, - "total": total, - "tirage": tirage, - } - return render(request, "bda/etat-places.html", context) - - -def _hash_queryset(queryset): - data = serializers.serialize("json", queryset).encode("utf-8") - hasher = hashlib.sha256() - hasher.update(data) - return hasher.hexdigest() - - -@cof_required -def places(request, tirage_id): - tirage = get_object_or_404(Tirage, id=tirage_id) - participant, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage) - places = participant.attribution_set.order_by( - "spectacle__date", "spectacle" - ).select_related("spectacle", "spectacle__location") - total = sum(place.spectacle.price for place in places) - filtered_places = [] - places_dict = {} - spectacles = [] - dates = [] - warning = False - for place in places: - if place.spectacle in spectacles: - places_dict[place.spectacle].double = True - else: - place.double = False - places_dict[place.spectacle] = place - spectacles.append(place.spectacle) - filtered_places.append(place) - date = place.spectacle.date.date() - if date in dates: - warning = True - else: - dates.append(date) - # On prévient l'utilisateur s'il a deux places à la même date - if warning: - messages.warning( - request, - "Attention, vous avez reçu des places pour " - "des spectacles différents à la même date.", - ) - return render( - request, - "bda/resume_places.html", - { - "participant": participant, - "places": filtered_places, - "tirage": tirage, - "total": total, - }, - ) - - -@cof_required -def inscription(request, tirage_id): - """ - Vue d'inscription à un tirage BdA. - - On vérifie qu'on se situe bien entre la date d'ouverture et la date de - fermeture des inscriptions. - - On vérifie que l'inscription n'a pas été modifiée entre le moment où le - client demande le formulaire et le moment où il soumet son inscription - (autre session par exemple). - """ - tirage = get_object_or_404(Tirage, id=tirage_id) - if timezone.now() < tirage.ouverture: - # Le tirage n'est pas encore ouvert. - opening = formats.localize(timezone.template_localtime(tirage.ouverture)) - messages.error( - request, - "Le tirage n'est pas encore ouvert : " "ouverture le {:s}".format(opening), - ) - return render(request, "bda/resume-inscription-tirage.html", {}) - - participant, _ = Participant.objects.select_related("tirage").get_or_create( - user=request.user, tirage=tirage - ) - - if timezone.now() > tirage.fermeture: - # Le tirage est fermé. - choices = participant.choixspectacle_set.order_by("priority") - messages.error(request, " C'est fini : tirage au sort dans la journée !") - return render( - request, "bda/resume-inscription-tirage.html", {"choices": choices} - ) - - BdaFormSet = inlineformset_factory( - Participant, - ChoixSpectacle, - fields=("spectacle", "double_choice", "priority"), - formset=InscriptionInlineFormSet, - error_messages={ - NON_FIELD_ERRORS: { - "unique_together": "Vous avez déjà demandé ce voeu plus haut !" - } - }, - ) - - if request.method == "POST": - # use *this* queryset - dbstate = _hash_queryset(participant.choixspectacle_set.all()) - if "dbstate" in request.POST and dbstate != request.POST["dbstate"]: - formset = BdaFormSet(instance=participant) - messages.error( - request, - "Impossible d'enregistrer vos modifications " - ": vous avez apporté d'autres modifications " - "entre temps.", - ) - else: - formset = BdaFormSet(request.POST, instance=participant) - if formset.is_valid(): - formset.save() - formset = BdaFormSet(instance=participant) - participant.accepte_charte = True - participant.save() - messages.success( - request, "Votre inscription a été mise à jour avec succès !" - ) - else: - messages.error( - request, - "Une erreur s'est produite lors de l'enregistrement de vos vœux. " - "Avez-vous demandé plusieurs fois le même spectacle ?", - ) - else: - formset = BdaFormSet(instance=participant) - # use *this* queryset - dbstate = _hash_queryset(participant.choixspectacle_set.all()) - total_price = 0 - choices = participant.choixspectacle_set.select_related("spectacle") - for choice in choices: - total_price += choice.spectacle.price - if choice.double: - total_price += choice.spectacle.price - return render( - request, - "bda/inscription-tirage.html", - { - "formset": formset, - "total_price": total_price, - "dbstate": dbstate, - "tirage": tirage, - "charte": participant.accepte_charte, - }, - ) - - -def do_tirage(tirage_elt, token): - """ - Fonction auxiliaire à la vue ``tirage`` qui lance effectivement le tirage - après qu'on a vérifié que c'est légitime et que le token donné en argument - est correct. - Rend les résultats - """ - # Initialisation du dictionnaire data qui va contenir les résultats - start = time.time() - data = { - "shows": tirage_elt.spectacle_set.select_related("location"), - "token": token, - "members": tirage_elt.participant_set.select_related("user"), - "total_slots": 0, - "total_losers": 0, - "total_sold": 0, - "total_deficit": 0, - "opera_deficit": 0, - } - - # On lance le tirage - choices = ( - ChoixSpectacle.objects.filter(spectacle__tirage=tirage_elt) - .order_by("participant", "priority") - .select_related("participant", "participant__user", "spectacle") - ) - results = Algorithm(data["shows"], data["members"], choices)(token) - - # On compte les places attribuées et les déçus - for _, members, losers in results: - data["total_slots"] += len(members) - data["total_losers"] += len(losers) - - # On calcule le déficit et les bénéfices pour le BdA - # FIXME: le traitement de l'opéra est sale - for show, members, _ in results: - deficit = (show.slots - len(members)) * show.price - data["total_sold"] += show.slots * show.price - if deficit >= 0: - if "Opéra" in show.location.name: - data["opera_deficit"] += deficit - data["total_deficit"] += deficit - data["total_sold"] -= data["total_deficit"] - - # Participant objects are not shared accross spectacle results, - # so assign a single object for each Participant id - members_uniq = {} - members2 = {} - for show, members, _ in results: - for member, _, _, _ in members: - if member.id not in members_uniq: - members_uniq[member.id] = member - members2[member] = [] - member.total = 0 - member = members_uniq[member.id] - members2[member].append(show) - member.total += show.price - members2 = members2.items() - data["members2"] = sorted(members2, key=lambda m: m[0].user.last_name) - - # --- - # À partir d'ici, le tirage devient effectif - # --- - - # On suppression les vieilles attributions, on sauvegarde le token et on - # désactive le tirage - Attribution.objects.filter(spectacle__tirage=tirage_elt).delete() - tirage_elt.tokens += '{:s}\n"""{:s}"""\n'.format( - timezone.now().strftime("%y-%m-%d %H:%M:%S"), token - ) - tirage_elt.enable_do_tirage = False - tirage_elt.save() - - # On enregistre les nouvelles attributions - Attribution.objects.bulk_create( - [ - Attribution(spectacle=show, participant=member) - for show, members, _ in results - for member, _, _, _ in members - ] - ) - - # On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues - ChoixRevente = Participant.choicesrevente.through - - # Suppression des reventes demandées/enregistrées - # (si le tirage est relancé) - (ChoixRevente.objects.filter(spectacle__tirage=tirage_elt).delete()) - ( - SpectacleRevente.objects.filter( - attribution__spectacle__tirage=tirage_elt - ).delete() - ) - - lost_by = defaultdict(set) - for show, _, losers in results: - for loser, _, _, _ in losers: - lost_by[loser].add(show) - - ChoixRevente.objects.bulk_create( - ChoixRevente(participant=member, spectacle=show) - for member, shows in lost_by.items() - for show in shows - ) - - data["duration"] = time.time() - start - data["results"] = results - return data - - -@buro_required -def tirage(request, tirage_id): - tirage_elt = get_object_or_404(Tirage, id=tirage_id) - if not (tirage_elt.enable_do_tirage and tirage_elt.fermeture < timezone.now()): - return render(request, "tirage-failed.html", {"tirage": tirage_elt}) - if request.POST: - form = TokenForm(request.POST) - if form.is_valid(): - results = do_tirage(tirage_elt, form.cleaned_data["token"]) - return render(request, "bda-attrib-extra.html", results) - else: - form = TokenForm() - return render(request, "bda-token.html", {"form": form}) - - -@cof_required -def revente_manage(request, tirage_id): - """ - Gestion de ses propres reventes : - - Création d'une revente - - Annulation d'une revente - - Confirmation d'une revente = transfert de la place à la personne qui - rachète - - Annulation d'une revente après que le tirage a eu lieu - """ - tirage = get_object_or_404(Tirage, id=tirage_id) - participant, created = Participant.objects.annotate_paid().get_or_create( - user=request.user, tirage=tirage - ) - - resellform = ResellForm(participant, prefix="resell") - annulform = AnnulForm(participant, prefix="annul") - soldform = SoldForm(participant, prefix="sold") - - if request.method == "POST": - # On met en vente une place - if "resell" in request.POST: - resellform = ResellForm(participant, request.POST, prefix="resell") - if resellform.is_valid(): - mails = [] - attributions = resellform.cleaned_data["attributions"] - with transaction.atomic(): - for attribution in attributions: - revente, created = SpectacleRevente.objects.get_or_create( - attribution=attribution, defaults={"seller": participant} - ) - if not created: - revente.reset() - - context = { - "vendeur": participant.user, - "show": attribution.spectacle, - "revente": revente, - } - mails.append( - ( - "BdA-Revente : {}".format(attribution.spectacle), - loader.render_to_string( - "bda/mails/revente-seller.txt", context=context - ), - settings.MAIL_DATA["revente"]["FROM"], - [participant.user.email], - ) - ) - send_mass_mail(mails) - # On annule une revente - elif "annul" in request.POST: - annulform = AnnulForm(participant, request.POST, prefix="annul") - if annulform.is_valid(): - reventes = annulform.cleaned_data["reventes"] - for revente in reventes: - revente.delete() - # On confirme une vente en transférant la place à la personne qui a - # gagné le tirage - elif "transfer" in request.POST: - soldform = SoldForm(participant, request.POST, prefix="sold") - if soldform.is_valid(): - reventes = soldform.cleaned_data["reventes"] - for revente in reventes: - revente.attribution.participant = revente.soldTo - revente.attribution.save() - - # On annule la revente après le tirage au sort (par exemple si - # la personne qui a gagné le tirage ne se manifeste pas). La place est - # alors remise en vente - elif "reinit" in request.POST: - soldform = SoldForm(participant, request.POST, prefix="sold") - if soldform.is_valid(): - reventes = soldform.cleaned_data["reventes"] - for revente in reventes: - if revente.attribution.spectacle.date > timezone.now(): - # On antidate pour envoyer le mail plus vite - new_date = timezone.now() - SpectacleRevente.remorse_time - revente.reset(new_date=new_date) - - sold_exists = soldform.fields["reventes"].queryset.exists() - annul_exists = annulform.fields["reventes"].queryset.exists() - resell_exists = resellform.fields["attributions"].queryset.exists() - - return render( - request, - "bda/revente/manage.html", - { - "tirage": tirage, - "soldform": soldform, - "annulform": annulform, - "resellform": resellform, - "sold_exists": sold_exists, - "annul_exists": annul_exists, - "resell_exists": resell_exists, - }, - ) - - -@cof_required -def revente_tirages(request, tirage_id): - """ - Affiche à un participant la liste de toutes les reventes en cours (pour un - tirage donné) et lui permet de s'inscrire et se désinscrire à ces reventes. - """ - tirage = get_object_or_404(Tirage, id=tirage_id) - participant, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage) - subform = ReventeTirageForm(participant, prefix="subscribe") - annulform = ReventeTirageAnnulForm(participant, prefix="annul") - - if request.method == "POST": - if "subscribe" in request.POST: - subform = ReventeTirageForm(participant, request.POST, prefix="subscribe") - if subform.is_valid(): - reventes = subform.cleaned_data["reventes"] - count = reventes.count() - for revente in reventes: - revente.confirmed_entry.add(participant) - if count > 0: - messages.success( - request, - "Tu as bien été inscrit à {} revente{}".format( - count, pluralize(count) - ), - ) - elif "annul" in request.POST: - annulform = ReventeTirageAnnulForm( - participant, request.POST, prefix="annul" - ) - if annulform.is_valid(): - reventes = annulform.cleaned_data["reventes"] - count = reventes.count() - for revente in reventes: - revente.confirmed_entry.remove(participant) - if count > 0: - messages.success( - request, - "Tu as bien été désinscrit de {} revente{}".format( - count, pluralize(count) - ), - ) - - annul_exists = annulform.fields["reventes"].queryset.exists() - sub_exists = subform.fields["reventes"].queryset.exists() - - return render( - request, - "bda/revente/tirages.html", - { - "annulform": annulform, - "subform": subform, - "annul_exists": annul_exists, - "sub_exists": sub_exists, - }, - ) - - -@cof_required -def revente_confirm(request, revente_id): - revente = get_object_or_404(SpectacleRevente, id=revente_id) - participant, _ = Participant.objects.get_or_create( - user=request.user, tirage=revente.attribution.spectacle.tirage - ) - if not revente.notif_sent or revente.shotgun: - return render(request, "bda/revente/wrongtime.html", {"revente": revente}) - - revente.confirmed_entry.add(participant) - return render( - request, - "bda/revente/confirmed.html", - {"spectacle": revente.attribution.spectacle, "date": revente.date_tirage}, - ) - - -@cof_required -def revente_subscribe(request, tirage_id): - """ - Permet à un participant de sélectionner ses préférences pour les reventes. - Il recevra des notifications pour les spectacles qui l'intéressent et il - est automatiquement inscrit aux reventes en cours au moment où il ajoute un - spectacle à la liste des spectacles qui l'intéressent. - """ - tirage = get_object_or_404(Tirage, id=tirage_id) - participant, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage) - deja_revente = False - success = False - inscrit_revente = [] - if request.method == "POST": - form = InscriptionReventeForm(tirage, request.POST) - if form.is_valid(): - choices = form.cleaned_data["spectacles"] - participant.choicesrevente.set(choices) - participant.save() - for spectacle in choices: - qset = SpectacleRevente.objects.filter(attribution__spectacle=spectacle) - if qset.filter(shotgun=True, soldTo__isnull=True).exists(): - # Une place est disponible au shotgun, on suggère à - # l'utilisateur d'aller la récupérer - deja_revente = True - else: - # La place n'est pas disponible au shotgun, si des reventes - # pour ce spectacle existent déjà, on inscrit la personne à - # la revente ayant le moins d'inscrits - min_resell = ( - qset.filter(shotgun=False) - .annotate(nb_subscribers=Count("confirmed_entry")) - .order_by("nb_subscribers") - .first() - ) - if min_resell is not None: - min_resell.confirmed_entry.add(participant) - inscrit_revente.append(spectacle) - success = True - else: - form = InscriptionReventeForm( - tirage, initial={"spectacles": participant.choicesrevente.all()} - ) - # Messages - if success: - messages.success(request, "Votre inscription a bien été prise en compte") - if deja_revente: - messages.info( - request, - "Des reventes existent déjà pour certains de " - "ces spectacles, vérifiez les places " - "disponibles sans tirage !", - ) - if inscrit_revente: - shows = map("
  • {!s}
  • ".format, inscrit_revente) - msg = ( - "Vous avez été inscrit·e à des reventes en cours pour les spectacles " - "
      {:s}
    ".format("\n".join(shows)) - ) - messages.info(request, msg, extra_tags="safe") - - return render(request, "bda/revente/subscribe.html", {"form": form}) - - -@cof_required -def revente_buy(request, spectacle_id): - spectacle = get_object_or_404(Spectacle, id=spectacle_id) - tirage = spectacle.tirage - participant, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage) - reventes = SpectacleRevente.objects.filter( - attribution__spectacle=spectacle, soldTo__isnull=True - ) - - # Si l'utilisateur veut racheter une place qu'il est en train de revendre, - # on supprime la revente en question. - own_reventes = reventes.filter(seller=participant) - if len(own_reventes) > 0: - own_reventes[0].delete() - return HttpResponseRedirect(reverse("bda-revente-shotgun", args=[tirage.id])) - - reventes_shotgun = reventes.filter(shotgun=True) - - if not reventes_shotgun: - return render(request, "bda/revente/none.html", {}) - - if request.POST: - revente = random.choice(reventes_shotgun) - revente.soldTo = participant - revente.save() - context = { - "show": spectacle, - "acheteur": request.user, - "vendeur": revente.seller.user, - } - - send_mail( - "BdA-Revente : {}".format(spectacle.title), - loader.render_to_string( - "bda/mails/revente-shotgun-seller.txt", context=context - ), - request.user.email, - [revente.seller.user.email], - ) - - return render( - request, - "bda/revente/mail-success.html", - {"seller": revente.attribution.participant.user, "spectacle": spectacle}, - ) - - return render( - request, - "bda/revente/confirm-shotgun.html", - {"spectacle": spectacle, "user": request.user}, - ) - - -@cof_required -def revente_shotgun(request, tirage_id): - tirage = get_object_or_404(Tirage, id=tirage_id) - spectacles = ( - tirage.spectacle_set.filter(date__gte=timezone.now()) - .select_related("location") - .prefetch_related( - Prefetch( - "attribues", - queryset=( - Attribution.objects.filter( - revente__shotgun=True, revente__soldTo__isnull=True - ) - ), - to_attr="shotguns", - ) - ) - ) - shotgun = [sp for sp in spectacles if len(sp.shotguns) > 0] - - return render(request, "bda/revente/shotgun.html", {"spectacles": shotgun}) - - -@buro_required -def spectacle(request, tirage_id, spectacle_id): - tirage = get_object_or_404(Tirage, id=tirage_id) - spectacle = get_object_or_404(Spectacle, id=spectacle_id, tirage=tirage) - attributions = spectacle.attribues.select_related( - "participant", "participant__user" - ) - participants = {} - for attrib in attributions: - participant = attrib.participant - participant_info = { - "lastname": participant.user.last_name, - "name": participant.user.get_full_name, - "username": participant.user.username, - "email": participant.user.email, - "given": int(attrib.given), - "paid": attrib.paid, - "nb_places": 1, - } - if participant.id in participants: - participants[participant.id]["nb_places"] += 1 - participants[participant.id]["given"] += attrib.given - participants[participant.id]["paid"] &= attrib.paid - else: - participants[participant.id] = participant_info - - participants_info = sorted(participants.values(), key=lambda part: part["lastname"]) - return render( - request, - "bda/participants.html", - {"spectacle": spectacle, "participants": participants_info}, - ) - - -class SpectacleListView(ListView): - model = Spectacle - template_name = "spectacle_list.html" - - def get_queryset(self): - self.tirage = get_object_or_404(Tirage, id=self.kwargs["tirage_id"]) - categories = self.tirage.spectacle_set.select_related("location") - return categories - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["tirage_id"] = self.tirage.id - context["tirage_name"] = self.tirage.title - return context - - -class UnpaidParticipants(BuroRequiredMixin, ListView): - context_object_name = "unpaid" - template_name = "bda-unpaid.html" - - def get_queryset(self): - return ( - Participant.objects.annotate_paid() - .filter(tirage__id=self.kwargs["tirage_id"], paid=False) - .select_related("user") - ) - - -@buro_required -def send_rappel(request, spectacle_id): - show = get_object_or_404(Spectacle, id=spectacle_id) - # Mails d'exemples - subject = show.title - body_mail_1place = loader.render_to_string( - "bda/mails/rappel.txt", - context={"member": request.user, "show": show, "nb_attr": 1}, - ) - body_mail_2places = loader.render_to_string( - "bda/mails/rappel.txt", - context={"member": request.user, "show": show, "nb_attr": 2}, - ) - - # Contexte - ctxt = { - "show": show, - "exemple_mail_1place": (subject, body_mail_1place), - "exemple_mail_2places": (subject, body_mail_2places), - } - # Envoi confirmé - if request.method == "POST": - members = show.send_rappel() - ctxt["sent"] = True - ctxt["members"] = members - # Demande de confirmation - else: - ctxt["sent"] = False - if show.rappel_sent: - messages.warning( - request, - "Attention, un mail de rappel pour ce spectale a déjà été " - "envoyé le {}".format( - formats.localize(timezone.template_localtime(show.rappel_sent)) - ), - ) - return render(request, "bda/mails-rappel.html", ctxt) - - -def catalogue(request, request_type): - """ - Vue destinée à communiquer avec un client AJAX, fournissant soit : - - la liste des tirages - - les catégories et salles d'un tirage - - les descriptions d'un tirage (filtrées selon la catégorie et la salle) - """ - if request_type == "list": - # Dans ce cas on retourne la liste des tirages et de leur id en JSON - data_return = list( - Tirage.objects.filter(appear_catalogue=True).values("id", "title") - ) - return JsonResponse(data_return, safe=False) - if request_type == "details": - # Dans ce cas on retourne une liste des catégories et des salles - tirage_id = request.GET.get("id", None) - if tirage_id is None: - return HttpResponseBadRequest("Missing GET parameter: id ") - try: - tirage = get_object_or_404(Tirage, id=int(tirage_id)) - except ValueError: - return HttpResponseBadRequest("Bad format: int expected for `id`") - shows = tirage.spectacle_set.values_list("id", flat=True) - categories = list( - CategorieSpectacle.objects.filter(spectacle__in=shows) - .distinct() - .values("id", "name") - ) - locations = list( - Salle.objects.filter(spectacle__in=shows).distinct().values("id", "name") - ) - data_return = {"categories": categories, "locations": locations} - return JsonResponse(data_return, safe=False) - if request_type == "descriptions": - # Ici on retourne les descriptions correspondant à la catégorie et - # à la salle spécifiées - - tirage_id = request.GET.get("id", "") - categories = request.GET.get("category", "[]") - locations = request.GET.get("location", "[]") - try: - tirage_id = int(tirage_id) - categories_id = json.loads(categories) - locations_id = json.loads(locations) - # Integers expected - if not all(isinstance(id, int) for id in categories_id): - raise ValueError - if not all(isinstance(id, int) for id in locations_id): - raise ValueError - except ValueError: # Contient JSONDecodeError - return HttpResponseBadRequest( - "Parse error, please ensure the GET parameters have the " - "following types:\n" - "id: int, category: [int], location: [int]\n" - "Data received:\n" - "id = {}, category = {}, locations = {}".format( - request.GET.get("id", ""), - request.GET.get("category", "[]"), - request.GET.get("location", "[]"), - ) - ) - tirage = get_object_or_404(Tirage, id=tirage_id) - - shows_qs = tirage.spectacle_set.select_related("location").prefetch_related( - "quote_set" - ) - if categories_id and 0 not in categories_id: - shows_qs = shows_qs.filter(category__id__in=categories_id) - if locations_id and 0 not in locations_id: - shows_qs = shows_qs.filter(location__id__in=locations_id) - - # On convertit les descriptions à envoyer en une liste facilement - # JSONifiable (il devrait y avoir un moyen plus efficace en - # redéfinissant le serializer de JSON) - data_return = [ - { - "title": spectacle.title, - "category": str(spectacle.category), - "date": str( - formats.date_format( - timezone.localtime(spectacle.date), "SHORT_DATETIME_FORMAT" - ) - ), - "location": str(spectacle.location), - "vips": spectacle.vips, - "description": spectacle.description, - "slots_description": spectacle.slots_description, - "quotes": [ - dict(author=quote.author, text=quote.text) - for quote in spectacle.quote_set.all() - ], - "image": spectacle.getImgUrl(), - "ext_link": spectacle.ext_link, - "price": spectacle.price, - "slots": spectacle.slots, - } - for spectacle in shows_qs - ] - return JsonResponse(data_return, safe=False) - # Si la requête n'est pas de la forme attendue, on quitte avec une erreur - return HttpResponseBadRequest() - - -## -# Autocomplete views -# -# https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#create-an-autocomplete-view -## - - -class ParticipantAutocomplete(Select2QuerySetView): - model = Participant - search_fields = ("user__username", "user__first_name", "user__last_name") - - -participant_autocomplete = buro_required(ParticipantAutocomplete.as_view()) - - -class SpectacleAutocomplete(Select2QuerySetView): - model = Spectacle - search_fields = ("title",) - - -spectacle_autocomplete = buro_required(SpectacleAutocomplete.as_view()) diff --git a/bds/__init__.py b/bds/__init__.py deleted file mode 100644 index 5c287005..00000000 --- a/bds/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = "bds.apps.BdsConfig" diff --git a/bds/admin.py b/bds/admin.py deleted file mode 100644 index e7181670..00000000 --- a/bds/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib import admin - -from bds.models import BDSProfile - -admin.site.register(BDSProfile) diff --git a/bds/apps.py b/bds/apps.py deleted file mode 100644 index 5c0fa0fd..00000000 --- a/bds/apps.py +++ /dev/null @@ -1,29 +0,0 @@ -from django import apps as global_apps -from django.apps import AppConfig -from django.db.models import Q -from django.db.models.signals import post_migrate - - -def bds_group_perms(app_config, apps=global_apps, **kwargs): - try: - Permission = apps.get_model("auth", "Permission") - Group = apps.get_model("auth", "Group") - - group = Group.objects.get(name="Burô du BDS") - perms = Permission.objects.filter( - Q(content_type__app_label="bds") - | Q(content_type__app_label="auth") & Q(content_type__model="user") - ) - group.permissions.set(perms) - group.save() - - except (LookupError, Group.DoesNotExist): - return - - -class BdsConfig(AppConfig): - name = "bds" - verbose_name = "Gestion des adhérent·e·s du BDS" - - def ready(self): - post_migrate.connect(bds_group_perms, sender=self) diff --git a/bds/autocomplete.py b/bds/autocomplete.py deleted file mode 100644 index a9308cb2..00000000 --- a/bds/autocomplete.py +++ /dev/null @@ -1,63 +0,0 @@ -from urllib.parse import urlencode - -from django.contrib.auth import get_user_model -from django.db.models import Q -from django.urls import reverse -from django.utils.translation import gettext_lazy as _ - -from shared import autocomplete - -User = get_user_model() - - -class BDSMemberSearch(autocomplete.ModelSearch): - model = User - search_fields = ["username", "first_name", "last_name"] - verbose_name = _("Membres du BDS") - - def get_queryset_filter(self, *args, **kwargs): - qset_filter = super().get_queryset_filter(*args, **kwargs) - qset_filter &= Q(bds__is_member=True) - return qset_filter - - def result_uuid(self, user): - return user.username - - def result_link(self, user): - return reverse("bds:user.update", args=(user.pk,)) - - -class BDSOthersSearch(autocomplete.ModelSearch): - model = User - search_fields = ["username", "first_name", "last_name"] - verbose_name = _("Non-membres du BDS") - - def get_queryset_filter(self, *args, **kwargs): - qset_filter = super().get_queryset_filter(*args, **kwargs) - qset_filter &= Q(bds__isnull=True) | Q(bds__is_member=False) - return qset_filter - - def result_uuid(self, user): - return user.username - - def result_link(self, user): - return reverse("bds:user.update", args=(user.pk,)) - - -class BDSLDAPSearch(autocomplete.LDAPSearch): - def result_link(self, clipper): - url = reverse("bds:user.create.fromclipper", args=(clipper.clipper,)) - get = {"fullname": clipper.fullname, "mail": clipper.mail} - - return "{}?{}".format(url, urlencode(get)) - - -class BDSSearch(autocomplete.Compose): - search_units = [ - ("members", BDSMemberSearch()), - ("others", BDSOthersSearch()), - ("clippers", BDSLDAPSearch()), - ] - - -bds_search = BDSSearch() diff --git a/bds/forms.py b/bds/forms.py deleted file mode 100644 index 9be0fb5b..00000000 --- a/bds/forms.py +++ /dev/null @@ -1,41 +0,0 @@ -from django import forms -from django.contrib.auth import get_user_model -from django.contrib.auth.forms import UserCreationForm -from django.utils.translation import gettext_lazy as _ - -from bds.models import BDSProfile - -User = get_user_model() - - -class UserForm(forms.ModelForm): - is_buro = forms.BooleanField(label=_("Membre du Burô"), required=False) - - class Meta: - model = User - fields = ["email", "first_name", "last_name"] - - -class UserFromClipperForm(forms.ModelForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["username"].disabled = True - - class Meta: - model = User - fields = ["username", "email", "first_name", "last_name"] - - -class UserFromScratchForm(UserCreationForm): - class Meta: - model = User - fields = ["username", "email", "first_name", "last_name"] - - -class ProfileForm(forms.ModelForm): - class Meta: - model = BDSProfile - exclude = ["user"] - widgets = { - "birthdate": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d") - } diff --git a/bds/migrations/0001_initial.py b/bds/migrations/0001_initial.py deleted file mode 100644 index b78a752b..00000000 --- a/bds/migrations/0001_initial.py +++ /dev/null @@ -1,141 +0,0 @@ -# Generated by Django 2.2 on 2019-07-17 12:48 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - -import bds.models - - -class Migration(migrations.Migration): - initial = True - - dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] - - operations = [ - migrations.CreateModel( - name="BDSProfile", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "phone", - models.CharField( - blank=True, max_length=20, verbose_name="téléphone" - ), - ), - ( - "occupation", - models.CharField( - choices=[ - ("EXT", "Extérieur"), - ("1A", "1A"), - ("2A", "2A"), - ("3A", "3A"), - ("4A", "4A"), - ("MAG", "Magistérien"), - ("ARC", "Archicube"), - ("DOC", "Doctorant"), - ("CST", "CST"), - ("PER", "Personnel ENS"), - ], - default="1A", - max_length=3, - verbose_name="occupation", - ), - ), - ( - "departement", - models.CharField( - blank=True, max_length=50, verbose_name="département" - ), - ), - ( - "birthdate", - models.DateField( - blank=True, null=True, verbose_name="date de naissance" - ), - ), - ( - "mails_bds", - models.BooleanField( - default=False, verbose_name="recevoir les mails du BDS" - ), - ), - ( - "is_buro", - models.BooleanField( - default=False, verbose_name="membre du Burô du BDS" - ), - ), - ( - "has_certificate", - models.BooleanField( - default=False, verbose_name="certificat médical" - ), - ), - ( - "certificate_file", - models.FileField( - blank=True, - upload_to=bds.models.BDSProfile.get_certificate_filename, - verbose_name="fichier de certificat médical", - ), - ), - ( - "ASPSL_number", - models.CharField( - blank=True, - max_length=50, - null=True, - verbose_name="numéro AS PSL", - ), - ), - ( - "FFSU_number", - models.CharField( - blank=True, max_length=50, null=True, verbose_name="numéro FFSU" - ), - ), - ( - "cotisation_period", - models.CharField( - choices=[ - ("ANN", "Année"), - ("SE1", "Premier semestre"), - ("SE2", "Deuxième semestre"), - ("NO", "Aucune"), - ], - default="NO", - max_length=3, - verbose_name="inscription", - ), - ), - ( - "registration_date", - models.DateField( - auto_now_add=True, verbose_name="date d'inscription" - ), - ), - ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - related_name="bds", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "verbose_name": "Profil BDS", - "verbose_name_plural": "Profils BDS", - }, - ) - ] diff --git a/bds/migrations/0002_bds_group.py b/bds/migrations/0002_bds_group.py deleted file mode 100644 index 73f57885..00000000 --- a/bds/migrations/0002_bds_group.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.2 on 2019-07-17 14:56 - -from django.db import migrations - - -def create_bds_buro_group(apps, schema_editor): - Group = apps.get_model("auth", "Group") - Group.objects.get_or_create(name="Burô du BDS") - - -class Migration(migrations.Migration): - dependencies = [("bds", "0001_initial")] - - operations = [ - migrations.RunPython(create_bds_buro_group, migrations.RunPython.noop) - ] diff --git a/bds/migrations/0003_staff_permission.py b/bds/migrations/0003_staff_permission.py deleted file mode 100644 index 7f501af1..00000000 --- a/bds/migrations/0003_staff_permission.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-20 22:48 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("bds", "0002_bds_group"), - ] - - operations = [ - migrations.AlterModelOptions( - name="bdsprofile", - options={ - "permissions": (("is_team", "est membre du burô"),), - "verbose_name": "Profil BDS", - "verbose_name_plural": "Profils BDS", - }, - ), - migrations.RemoveField( - model_name="bdsprofile", - name="is_buro", - ), - ] diff --git a/bds/migrations/0004_is_member_cotiz_type.py b/bds/migrations/0004_is_member_cotiz_type.py deleted file mode 100644 index 3b550fdf..00000000 --- a/bds/migrations/0004_is_member_cotiz_type.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-22 10:20 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("bds", "0003_staff_permission"), - ] - - operations = [ - migrations.AddField( - model_name="bdsprofile", - name="cotisation_type", - field=models.CharField( - choices=[ - ("ETU", "Étudiant"), - ("NOR", "Normalien"), - ("EXT", "Extérieur"), - ("ARC", "Archicube"), - ], - default="Normalien", - max_length=9, - verbose_name="type de cotisation", - ), - preserve_default=False, - ), - migrations.AddField( - model_name="bdsprofile", - name="is_member", - field=models.BooleanField(default=False, verbose_name="adhérent⋅e du BDS"), - ), - ] diff --git a/bds/migrations/0005_remove_bdsprofile_certificate_file.py b/bds/migrations/0005_remove_bdsprofile_certificate_file.py deleted file mode 100644 index 3b7232ff..00000000 --- a/bds/migrations/0005_remove_bdsprofile_certificate_file.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 2.2.14 on 2020-07-27 20:14 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("bds", "0004_is_member_cotiz_type"), - ] - - operations = [ - migrations.RemoveField( - model_name="bdsprofile", - name="certificate_file", - ), - ] diff --git a/bds/migrations/0006_bdsprofile_comments.py b/bds/migrations/0006_bdsprofile_comments.py deleted file mode 100644 index 514c4d55..00000000 --- a/bds/migrations/0006_bdsprofile_comments.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2.12 on 2020-08-28 12:14 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("bds", "0005_remove_bdsprofile_certificate_file"), - ] - - operations = [ - migrations.AddField( - model_name="bdsprofile", - name="comments", - field=models.TextField( - blank=True, - help_text="Attention : l'utilisateur·ice dispose d'un droit d'accès" - " aux données le/la concernant, dont le contenu de ce champ !", - verbose_name="commentaires", - ), - ), - ] diff --git a/bds/migrations/__init__.py b/bds/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bds/mixins.py b/bds/mixins.py deleted file mode 100644 index 43607055..00000000 --- a/bds/mixins.py +++ /dev/null @@ -1,122 +0,0 @@ -from django.contrib.auth.mixins import PermissionRequiredMixin -from django.core.exceptions import ImproperlyConfigured -from django.http import HttpResponseRedirect -from django.views.generic.base import ContextMixin, TemplateResponseMixin, View - - -class StaffRequiredMixin(PermissionRequiredMixin): - permission_required = "bds.is_team" - - -class MultipleFormMixin(ContextMixin): - """Mixin pour gérer plusieurs formulaires dans la même vue. - Le fonctionnement est relativement identique à celui de - FormMixin, dont la documentation est disponible ici : - https://docs.djangoproject.com/en/3.0/ref/class-based-views/mixins-editing/ - - Les principales différences sont : - - au lieu de form_class, il faut donner comme attribut un dict de la forme - {: }, avec tous les formulaires à instancier. On - peut aussi redéfinir `get_form_classes` - - - les données initiales se récupèrent pour chaque form via l'attribut - `_initial` ou la fonction `get__initial`. De même, - si certaines forms sont des `ModelForm`s, on peut définir la fonction - `get__instance`. - - - chaque form a un préfixe rajouté, par défaut , mais qui peut - être customisé via `prefixes` ou `get_prefixes`. - """ - - form_classes = {} - prefixes = {} - initial = {} - - success_url = None - - def get_form_classes(self): - return self.form_classes - - def get_initial(self, form_name): - initial_attr = "%s_initial" % form_name - - initial_method = "get_%s_initial" % form_name - initial_method = getattr(self, initial_method, None) - - if hasattr(self, initial_attr): - return getattr(self, initial_attr) - elif callable(initial_method): - return initial_method() - else: - return self.initial.copy() - - def get_prefix(self, form_name): - return self.prefixes.get(form_name, form_name) - - def get_instance(self, form_name): - # Au cas où certaines des forms soient des ModelForms - instance_method = "get_%s_instance" % form_name - instance_method = getattr(self, instance_method, None) - - if callable(instance_method): - return instance_method() - else: - return None - - def get_form_kwargs(self, form_name): - kwargs = { - "initial": self.get_initial(form_name), - "prefix": self.get_prefix(form_name), - "instance": self.get_instance(form_name), - } - - if self.request.method in ("POST", "PUT"): - kwargs.update({"data": self.request.POST, "files": self.request.FILES}) - - return kwargs - - def get_forms(self): - form_classes = self.get_form_classes() - return { - form_name: form_class(**self.get_form_kwargs(form_name)) - for form_name, form_class in form_classes.items() - } - - def get_success_url(self): - if not self.success_url: - raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.") - return str(self.success_url) - - def form_valid(self, forms): - # on garde le nom form_valid pour l'interface avec SuccessMessageMixin - return HttpResponseRedirect(self.get_success_url()) - - def form_invalid(self, forms): - """If the form is invalid, render the invalid form.""" - return self.render_to_response(self.get_context_data(forms=forms)) - - -class ProcessMultipleFormView(View): - """Équivalent de `ProcessFormView` pour plusieurs forms. - Note : il faut que *tous* les formulaires soient valides pour - qu'ils soient sauvegardés ! - """ - - def get(self, request, *args, **kwargs): - forms = self.get_forms() - return self.render_to_response(self.get_context_data(forms=forms)) - - def post(self, request, *args, **kwargs): - forms = self.get_forms() - if all(form.is_valid() for form in forms.values()): - return self.form_valid(forms) - else: - return self.form_invalid(forms) - - -class BaseMultipleFormView(MultipleFormMixin, ProcessMultipleFormView): - pass - - -class MultipleFormView(TemplateResponseMixin, BaseMultipleFormView): - pass diff --git a/bds/models.py b/bds/models.py deleted file mode 100644 index 39540653..00000000 --- a/bds/models.py +++ /dev/null @@ -1,113 +0,0 @@ -from datetime import date -from os.path import splitext - -from django.contrib.auth import get_user_model -from django.db import models -from django.utils import timezone -from django.utils.translation import gettext_lazy as _ - -from shared.utils import choices_length - -User = get_user_model() - - -class BDSProfile(models.Model): - OCCUPATION_CHOICES = ( - ("EXT", "Extérieur"), - ("1A", "1A"), - ("2A", "2A"), - ("3A", "3A"), - ("4A", "4A"), - ("MAG", "Magistérien"), - ("ARC", "Archicube"), - ("DOC", "Doctorant"), - ("CST", "CST"), - ("PER", "Personnel ENS"), - ) - - TYPE_COTIZ_CHOICES = ( - ("ETU", "Étudiant"), - ("NOR", "Normalien"), - ("EXT", "Extérieur"), - ("ARC", "Archicube"), - ) - - COTIZ_DURATION_CHOICES = ( - ("ANN", "Année"), - ("SE1", "Premier semestre"), - ("SE2", "Deuxième semestre"), - ("NO", "Aucune"), - ) - - def get_certificate_filename(instance, filename): - _, ext = splitext(filename) # récupère l'extension du fichier - year = str(date.now().year) - return "certifs/{username}-{year}.{ext}".format( - username=instance.user.username, year=year, ext=ext - ) - - user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="bds") - phone = models.CharField(_("téléphone"), max_length=20, blank=True) - occupation = models.CharField( - _("occupation"), - default="1A", - choices=OCCUPATION_CHOICES, - max_length=choices_length(OCCUPATION_CHOICES), - ) - departement = models.CharField(_("département"), max_length=50, blank=True) - birthdate = models.DateField( - auto_now_add=False, - auto_now=False, - verbose_name=_("date de naissance"), - blank=True, - null=True, - ) - - is_member = models.BooleanField(_("adhérent⋅e du BDS"), default=False) - - mails_bds = models.BooleanField(_("recevoir les mails du BDS"), default=False) - - has_certificate = models.BooleanField(_("certificat médical"), default=False) - - ASPSL_number = models.CharField( - _("numéro AS PSL"), max_length=50, blank=True, null=True - ) - FFSU_number = models.CharField( - _("numéro FFSU"), max_length=50, blank=True, null=True - ) - cotisation_period = models.CharField( - _("inscription"), default="NO", choices=COTIZ_DURATION_CHOICES, max_length=3 - ) - registration_date = models.DateField( - auto_now_add=True, verbose_name=_("date d'inscription") - ) - cotisation_type = models.CharField( - _("type de cotisation"), choices=TYPE_COTIZ_CHOICES, max_length=9 - ) - - comments = models.TextField( - _("commentaires"), - blank=True, - help_text=_( - "Attention : l'utilisateur·ice dispose d'un droit d'accès aux données " - "le/la concernant, dont le contenu de ce champ !" - ), - ) - - @classmethod - def expired_members(cls): - now = timezone.now() - qs = cls.objects.filter(is_member=True) - if now.month > 1 and now.month < 7: - return qs.filter(cotisation_period="SE1") - elif now.month < 2 or now.month > 8: - return qs.none() - return qs - - class Meta: - verbose_name = _("Profil BDS") - verbose_name_plural = _("Profils BDS") - permissions = (("is_team", _("est membre du burô")),) - - def __str__(self): - return self.user.username diff --git a/bds/static/bds/css/bds.css b/bds/static/bds/css/bds.css deleted file mode 100644 index 86491213..00000000 --- a/bds/static/bds/css/bds.css +++ /dev/null @@ -1 +0,0 @@ -/*! bulma.io v0.9.0 | MIT License | github.com/jgthms/bulma */@keyframes spinAround{from{transform:rotate(0deg)}to{transform:rotate(359deg)}}.is-unselectable,.tabs,.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.breadcrumb,.file,.button,.modal-close,.delete{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid transparent;border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:.625em}.tabs:not(:last-child),.pagination:not(:last-child),.message:not(:last-child),.level:not(:last-child),.breadcrumb:not(:last-child),.highlight:not(:last-child),.block:not(:last-child),.title:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.progress:not(:last-child),.notification:not(:last-child),.content:not(:last-child),.box:not(:last-child){margin-bottom:1.5rem}.modal-close,.delete{-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,.2);border:none;border-radius:290486px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}.modal-close::before,.delete::before,.modal-close::after,.delete::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.modal-close::before,.delete::before{height:2px;width:50%}.modal-close::after,.delete::after{height:50%;width:2px}.modal-close:hover,.delete:hover,.modal-close:focus,.delete:focus{background-color:rgba(10,10,10,.3)}.modal-close:active,.delete:active{background-color:rgba(10,10,10,.4)}.is-small.modal-close,.is-small.delete{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.modal-close,.is-medium.delete{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.modal-close,.is-large.delete{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.control.is-loading::after,.select.is-loading::after,.loader,.button.is-loading::after{animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:290486px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.is-overlay,.modal-background,.modal,.image.is-square img,.image.is-square .has-ratio,.image.is-1by1 img,.image.is-1by1 .has-ratio,.image.is-5by4 img,.image.is-5by4 .has-ratio,.image.is-4by3 img,.image.is-4by3 .has-ratio,.image.is-3by2 img,.image.is-3by2 .has-ratio,.image.is-5by3 img,.image.is-5by3 .has-ratio,.image.is-16by9 img,.image.is-16by9 .has-ratio,.image.is-2by1 img,.image.is-2by1 .has-ratio,.image.is-3by1 img,.image.is-3by1 .has-ratio,.image.is-4by5 img,.image.is-4by5 .has-ratio,.image.is-3by4 img,.image.is-3by4 .has-ratio,.image.is-2by3 img,.image.is-2by3 .has-ratio,.image.is-3by5 img,.image.is-3by5 .has-ratio,.image.is-9by16 img,.image.is-9by16 .has-ratio,.image.is-1by2 img,.image.is-1by2 .has-ratio,.image.is-1by3 img,.image.is-1by3 .has-ratio{bottom:0;left:0;position:absolute;right:0;top:0}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.file-cta,.file-name,.select select,.textarea,.input,.button{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.5em - 1px);padding-left:calc(0.75em - 1px);padding-right:calc(0.75em - 1px);padding-top:calc(0.5em - 1px);position:relative;vertical-align:top}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus,.pagination-ellipsis:focus,.file-cta:focus,.file-name:focus,.select select:focus,.textarea:focus,.input:focus,.button:focus,.is-focused.pagination-previous,.is-focused.pagination-next,.is-focused.pagination-link,.is-focused.pagination-ellipsis,.is-focused.file-cta,.is-focused.file-name,.select select.is-focused,.is-focused.textarea,.is-focused.input,.is-focused.button,.pagination-previous:active,.pagination-next:active,.pagination-link:active,.pagination-ellipsis:active,.file-cta:active,.file-name:active,.select select:active,.textarea:active,.input:active,.button:active,.is-active.pagination-previous,.is-active.pagination-next,.is-active.pagination-link,.is-active.pagination-ellipsis,.is-active.file-cta,.is-active.file-name,.select select.is-active,.is-active.textarea,.is-active.input,.is-active.button{outline:none}[disabled].pagination-previous,[disabled].pagination-next,[disabled].pagination-link,[disabled].pagination-ellipsis,[disabled].file-cta,[disabled].file-name,.select select[disabled],[disabled].textarea,[disabled].input,[disabled].button,fieldset[disabled] .pagination-previous,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input,fieldset[disabled] .button{cursor:not-allowed}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,select,textarea{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue","Helvetica","Arial",sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:monospace}body{color:#000;font-size:1em;font-weight:400;line-height:1.5}a{color:#3273dc;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:#f5f5f5;color:#f14668;font-size:.875em;font-weight:normal;padding:.25em .5em .25em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type=checkbox],input[type=radio]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#363636;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#000;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#363636}.box{background-color:#fff;border-radius:6px;box-shadow:0 .5em 1em -0.125em rgba(10,10,10,.1),0 0px 0 1px rgba(10,10,10,.02);color:#000;display:block;padding:1.25rem}a.box:hover,a.box:focus{box-shadow:0 .5em 1em -0.125em rgba(10,10,10,.1),0 0 0 1px #3273dc}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2),0 0 0 1px #3273dc}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#363636;cursor:pointer;justify-content:center;padding-bottom:calc(0.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(0.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-small,.button .icon.is-medium,.button .icon.is-large{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-0.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-0.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-0.5em - 1px);margin-right:calc(-0.5em - 1px)}.button:hover,.button.is-hovered{border-color:#b5b5b5;color:#363636}.button:focus,.button.is-focused{border-color:#3273dc;color:#363636}.button:focus:not(:active),.button.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.button:active,.button.is-active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#000;text-decoration:underline}.button.is-text:hover,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text.is-focused{background-color:#f5f5f5;color:#363636}.button.is-text:active,.button.is-text.is-active{background-color:#e8e8e8;color:#363636}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white:hover,.button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white:focus,.button.is-white.is-focused{border-color:transparent;color:#0a0a0a}.button.is-white:focus:not(:active),.button.is-white.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.button.is-white:active,.button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:transparent;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted:hover,.button.is-white.is-inverted.is-hovered{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined:hover,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-outlined.is-loading:hover::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined:hover,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading:hover::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black:hover,.button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}.button.is-black:focus,.button.is-black.is-focused{border-color:transparent;color:#fff}.button.is-black:focus:not(:active),.button.is-black.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.button.is-black:active,.button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:transparent;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted:hover,.button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined:hover,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-outlined.is-loading:hover::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined:hover,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading:hover::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light:hover,.button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light:focus,.button.is-light.is-focused{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light:focus:not(:active),.button.is-light.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.button.is-light:active,.button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:transparent;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted:hover,.button.is-light.is-inverted.is-hovered{background-color:rgba(0,0,0,.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7) !important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined:hover,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-outlined.is-loading:hover::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7) !important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-light.is-inverted.is-outlined:hover,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading:hover::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-dark{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark:hover,.button.is-dark.is-hovered{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark:focus,.button.is-dark.is-focused{border-color:transparent;color:#fff}.button.is-dark:focus:not(:active),.button.is-dark.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.button.is-dark:active,.button.is-dark.is-active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],fieldset[disabled] .button.is-dark{background-color:#363636;border-color:transparent;box-shadow:none}.button.is-dark.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted:hover,.button.is-dark.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined:hover,.button.is-dark.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.button.is-dark.is-outlined.is-focused{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-outlined.is-loading:hover::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-dark.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined:hover,.button.is-dark.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined.is-focused{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading:hover::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary{background-color:#00d1b2;border-color:transparent;color:#fff}.button.is-primary:hover,.button.is-primary.is-hovered{background-color:#00c4a7;border-color:transparent;color:#fff}.button.is-primary:focus,.button.is-primary.is-focused{border-color:transparent;color:#fff}.button.is-primary:focus:not(:active),.button.is-primary.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.button.is-primary:active,.button.is-primary.is-active{background-color:#00b89c;border-color:transparent;color:#fff}.button.is-primary[disabled],fieldset[disabled] .button.is-primary{background-color:#00d1b2;border-color:transparent;box-shadow:none}.button.is-primary.is-inverted{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted:hover,.button.is-primary.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],fieldset[disabled] .button.is-primary.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#00d1b2}.button.is-primary.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;color:#00d1b2}.button.is-primary.is-outlined:hover,.button.is-primary.is-outlined.is-hovered,.button.is-primary.is-outlined:focus,.button.is-primary.is-outlined.is-focused{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.button.is-primary.is-outlined.is-loading::after{border-color:transparent transparent #00d1b2 #00d1b2 !important}.button.is-primary.is-outlined.is-loading:hover::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-outlined.is-loading:focus::after,.button.is-primary.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;box-shadow:none;color:#00d1b2}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined:hover,.button.is-primary.is-inverted.is-outlined.is-hovered,.button.is-primary.is-inverted.is-outlined:focus,.button.is-primary.is-inverted.is-outlined.is-focused{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-outlined.is-loading:hover::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #00d1b2 #00d1b2 !important}.button.is-primary.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light{background-color:#ebfffc;color:#00947e}.button.is-primary.is-light:hover,.button.is-primary.is-light.is-hovered{background-color:#defffa;border-color:transparent;color:#00947e}.button.is-primary.is-light:active,.button.is-primary.is-light.is-active{background-color:#d1fff8;border-color:transparent;color:#00947e}.button.is-link{background-color:#3273dc;border-color:transparent;color:#fff}.button.is-link:hover,.button.is-link.is-hovered{background-color:#276cda;border-color:transparent;color:#fff}.button.is-link:focus,.button.is-link.is-focused{border-color:transparent;color:#fff}.button.is-link:focus:not(:active),.button.is-link.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.button.is-link:active,.button.is-link.is-active{background-color:#2366d1;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#3273dc;border-color:transparent;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#3273dc}.button.is-link.is-inverted:hover,.button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3273dc}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined{background-color:transparent;border-color:#3273dc;color:#3273dc}.button.is-link.is-outlined:hover,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined.is-focused{background-color:#3273dc;border-color:#3273dc;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #3273dc #3273dc !important}.button.is-link.is-outlined.is-loading:hover::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#3273dc;box-shadow:none;color:#3273dc}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined:hover,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#3273dc}.button.is-link.is-inverted.is-outlined.is-loading:hover::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #3273dc #3273dc !important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eef3fc;color:#2160c4}.button.is-link.is-light:hover,.button.is-link.is-light.is-hovered{background-color:#e3ecfa;border-color:transparent;color:#2160c4}.button.is-link.is-light:active,.button.is-link.is-light.is-active{background-color:#d8e4f8;border-color:transparent;color:#2160c4}.button.is-info{background-color:#3298dc;border-color:transparent;color:#fff}.button.is-info:hover,.button.is-info.is-hovered{background-color:#2793da;border-color:transparent;color:#fff}.button.is-info:focus,.button.is-info.is-focused{border-color:transparent;color:#fff}.button.is-info:focus:not(:active),.button.is-info.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.button.is-info:active,.button.is-info.is-active{background-color:#238cd1;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3298dc;border-color:transparent;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3298dc}.button.is-info.is-inverted:hover,.button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3298dc}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined{background-color:transparent;border-color:#3298dc;color:#3298dc}.button.is-info.is-outlined:hover,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined.is-focused{background-color:#3298dc;border-color:#3298dc;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3298dc #3298dc !important}.button.is-info.is-outlined.is-loading:hover::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3298dc;box-shadow:none;color:#3298dc}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined:hover,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#3298dc}.button.is-info.is-inverted.is-outlined.is-loading:hover::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #3298dc #3298dc !important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eef6fc;color:#1d72aa}.button.is-info.is-light:hover,.button.is-info.is-light.is-hovered{background-color:#e3f1fa;border-color:transparent;color:#1d72aa}.button.is-info.is-light:active,.button.is-info.is-light.is-active{background-color:#d8ebf8;border-color:transparent;color:#1d72aa}.button.is-success{background-color:#48c774;border-color:transparent;color:#fff}.button.is-success:hover,.button.is-success.is-hovered{background-color:#3ec46d;border-color:transparent;color:#fff}.button.is-success:focus,.button.is-success.is-focused{border-color:transparent;color:#fff}.button.is-success:focus:not(:active),.button.is-success.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.button.is-success:active,.button.is-success.is-active{background-color:#3abb67;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#48c774;border-color:transparent;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#48c774}.button.is-success.is-inverted:hover,.button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#48c774}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined{background-color:transparent;border-color:#48c774;color:#48c774}.button.is-success.is-outlined:hover,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined.is-focused{background-color:#48c774;border-color:#48c774;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #48c774 #48c774 !important}.button.is-success.is-outlined.is-loading:hover::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#48c774;box-shadow:none;color:#48c774}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined:hover,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#48c774}.button.is-success.is-inverted.is-outlined.is-loading:hover::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #48c774 #48c774 !important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#effaf3;color:#257942}.button.is-success.is-light:hover,.button.is-success.is-light.is-hovered{background-color:#e6f7ec;border-color:transparent;color:#257942}.button.is-success.is-light:active,.button.is-success.is-light.is-active{background-color:#dcf4e4;border-color:transparent;color:#257942}.button.is-warning{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning:hover,.button.is-warning.is-hovered{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning:focus,.button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning:focus:not(:active),.button.is-warning.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.button.is-warning:active,.button.is-warning.is-active{background-color:#ffd83d;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffdd57;border-color:transparent;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);color:#ffdd57}.button.is-warning.is-inverted:hover,.button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#ffdd57}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7) !important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;color:#ffdd57}.button.is-warning.is-outlined:hover,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined.is-focused{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffdd57 #ffdd57 !important}.button.is-warning.is-outlined.is-loading:hover::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7) !important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;box-shadow:none;color:#ffdd57}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-warning.is-inverted.is-outlined:hover,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,.7);color:#ffdd57}.button.is-warning.is-inverted.is-outlined.is-loading:hover::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ffdd57 #ffdd57 !important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-warning.is-light{background-color:#fffbeb;color:#947600}.button.is-warning.is-light:hover,.button.is-warning.is-light.is-hovered{background-color:#fff8de;border-color:transparent;color:#947600}.button.is-warning.is-light:active,.button.is-warning.is-light.is-active{background-color:#fff6d1;border-color:transparent;color:#947600}.button.is-danger{background-color:#f14668;border-color:transparent;color:#fff}.button.is-danger:hover,.button.is-danger.is-hovered{background-color:#f03a5f;border-color:transparent;color:#fff}.button.is-danger:focus,.button.is-danger.is-focused{border-color:transparent;color:#fff}.button.is-danger:focus:not(:active),.button.is-danger.is-focused:not(:active){box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.button.is-danger:active,.button.is-danger.is-active{background-color:#ef2e55;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#f14668;border-color:transparent;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#f14668}.button.is-danger.is-inverted:hover,.button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f14668}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;color:#f14668}.button.is-danger.is-outlined:hover,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined.is-focused{background-color:#f14668;border-color:#f14668;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f14668 #f14668 !important}.button.is-danger.is-outlined.is-loading:hover::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;box-shadow:none;color:#f14668}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined:hover,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-outlined.is-loading:hover::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f14668 #f14668 !important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.button.is-danger.is-light:hover,.button.is-danger.is-light.is-hovered{background-color:#fde0e6;border-color:transparent;color:#cc0f35}.button.is-danger.is-light:active,.button.is-danger.is-light.is-active{background-color:#fcd4dc;border-color:transparent;color:#cc0f35}.button.is-small{border-radius:2px;font-size:.75rem}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent !important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em / 2));top:calc(50% - (1em / 2));position:absolute !important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#7a7a7a;box-shadow:none;pointer-events:none}.button.is-rounded{border-radius:290486px;padding-left:calc(1em + 0.25em);padding-right:calc(1em + 0.25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-0.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){border-radius:2px;font-size:.75rem}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button:hover,.buttons.has-addons .button.is-hovered{z-index:2}.buttons.has-addons .button:focus,.buttons.has-addons .button.is-focused,.buttons.has-addons .button:active,.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-selected{z-index:3}.buttons.has-addons .button:focus:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-selected:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width: 1024px){.container{max-width:960px}}@media screen and (max-width: 1215px){.container.is-widescreen{max-width:1152px}}@media screen and (max-width: 1407px){.container.is-fullhd{max-width:1344px}}@media screen and (min-width: 1216px){.container{max-width:1152px}}@media screen and (min-width: 1408px){.container{max-width:1344px}}.content li+li{margin-top:.25em}.content p:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content ul:not(:last-child),.content blockquote:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#363636;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:.8em}.content h5{font-size:1.125em;margin-bottom:.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol:not([type]).is-lower-alpha{list-style-type:lower-alpha}.content ol:not([type]).is-lower-roman{list-style-type:lower-roman}.content ol:not([type]).is-upper-alpha{list-style-type:upper-alpha}.content ol:not([type]).is-upper-roman{list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal}.content sup,.content sub{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.content table th{color:#363636}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#363636}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#363636}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small{font-size:.75rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.image{display:block;position:relative}.image img{display:block;height:auto;width:100%}.image img.is-rounded{border-radius:290486px}.image.is-fullwidth{width:100%}.image.is-square img,.image.is-square .has-ratio,.image.is-1by1 img,.image.is-1by1 .has-ratio,.image.is-5by4 img,.image.is-5by4 .has-ratio,.image.is-4by3 img,.image.is-4by3 .has-ratio,.image.is-3by2 img,.image.is-3by2 .has-ratio,.image.is-5by3 img,.image.is-5by3 .has-ratio,.image.is-16by9 img,.image.is-16by9 .has-ratio,.image.is-2by1 img,.image.is-2by1 .has-ratio,.image.is-3by1 img,.image.is-3by1 .has-ratio,.image.is-4by5 img,.image.is-4by5 .has-ratio,.image.is-3by4 img,.image.is-3by4 .has-ratio,.image.is-2by3 img,.image.is-2by3 .has-ratio,.image.is-3by5 img,.image.is-3by5 .has-ratio,.image.is-9by16 img,.image.is-9by16 .has-ratio,.image.is-1by2 img,.image.is-1by2 .has-ratio,.image.is-1by3 img,.image.is-1by3 .has-ratio{height:100%;width:100%}.image.is-square,.image.is-1by1{padding-top:100%}.image.is-5by4{padding-top:80%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-5by3{padding-top:60%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-3by1{padding-top:33.3333%}.image.is-4by5{padding-top:125%}.image.is-3by4{padding-top:133.3333%}.image.is-2by3{padding-top:150%}.image.is-3by5{padding-top:166.6666%}.image.is-9by16{padding-top:177.7777%}.image.is-1by2{padding-top:200%}.image.is-1by3{padding-top:300%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:transparent}.notification>.delete{right:.5rem;position:absolute;top:.5rem}.notification .title,.notification .subtitle,.notification .content{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.notification.is-dark{background-color:#363636;color:#fff}.notification.is-primary{background-color:#00d1b2;color:#fff}.notification.is-primary.is-light{background-color:#ebfffc;color:#00947e}.notification.is-link{background-color:#3273dc;color:#fff}.notification.is-link.is-light{background-color:#eef3fc;color:#2160c4}.notification.is-info{background-color:#3298dc;color:#fff}.notification.is-info.is-light{background-color:#eef6fc;color:#1d72aa}.notification.is-success{background-color:#48c774;color:#fff}.notification.is-success.is-light{background-color:#effaf3;color:#257942}.notification.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.notification.is-warning.is-light{background-color:#fffbeb;color:#947600}.notification.is-danger{background-color:#f14668;color:#fff}.notification.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:290486px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#000}.progress::-moz-progress-bar{background-color:#000}.progress::-ms-fill{background-color:#000;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right, white 30%, #ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right, whitesmoke 30%, #ededed 30%)}.progress.is-dark::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate{background-image:linear-gradient(to right, #363636 30%, #ededed 30%)}.progress.is-primary::-webkit-progress-value{background-color:#00d1b2}.progress.is-primary::-moz-progress-bar{background-color:#00d1b2}.progress.is-primary::-ms-fill{background-color:#00d1b2}.progress.is-primary:indeterminate{background-image:linear-gradient(to right, #00d1b2 30%, #ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#3273dc}.progress.is-link::-moz-progress-bar{background-color:#3273dc}.progress.is-link::-ms-fill{background-color:#3273dc}.progress.is-link:indeterminate{background-image:linear-gradient(to right, #3273dc 30%, #ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3298dc}.progress.is-info::-moz-progress-bar{background-color:#3298dc}.progress.is-info::-ms-fill{background-color:#3298dc}.progress.is-info:indeterminate{background-image:linear-gradient(to right, #3298dc 30%, #ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#48c774}.progress.is-success::-moz-progress-bar{background-color:#48c774}.progress.is-success::-ms-fill{background-color:#48c774}.progress.is-success:indeterminate{background-image:linear-gradient(to right, #48c774 30%, #ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffdd57}.progress.is-warning::-moz-progress-bar{background-color:#ffdd57}.progress.is-warning::-ms-fill{background-color:#ffdd57}.progress.is-warning:indeterminate{background-image:linear-gradient(to right, #ffdd57 30%, #ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#f14668}.progress.is-danger::-moz-progress-bar{background-color:#f14668}.progress.is-danger::-ms-fill{background-color:#f14668}.progress.is-danger:indeterminate{background-image:linear-gradient(to right, #f14668 30%, #ededed 30%)}.progress:indeterminate{animation-duration:1.5s;animation-iteration-count:infinite;animation-name:moveIndeterminate;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right, black 30%, #ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress.is-small{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#363636}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.table td.is-link,.table th.is-link{background-color:#3273dc;border-color:#3273dc;color:#fff}.table td.is-info,.table th.is-info{background-color:#3298dc;border-color:#3298dc;color:#fff}.table td.is-success,.table th.is-success{background-color:#48c774;border-color:#48c774;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,.7)}.table td.is-danger,.table th.is-danger{background-color:#f14668;border-color:#f14668;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#00d1b2;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#363636}.table th:not([align]){text-align:inherit}.table tr.is-selected{background-color:#00d1b2;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:transparent}.table thead td,.table thead th{border-width:0 0 2px;color:#363636}.table tfoot{background-color:transparent}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#363636}.table tbody{background-color:transparent}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:.25em .5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag{margin-bottom:.5rem}.tags .tag:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-0.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag{margin-right:.25rem;margin-left:.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child){margin-left:.5rem}.tags.is-right .tag:not(:last-child){margin-right:0}.tags.has-addons .tag{margin-right:0}.tags.has-addons .tag:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#000;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:.75em;padding-right:.75em;white-space:nowrap}.tag:not(body) .delete{margin-left:.25rem;margin-right:-0.375rem}.tag:not(body).is-white{background-color:#fff;color:#0a0a0a}.tag:not(body).is-black{background-color:#0a0a0a;color:#fff}.tag:not(body).is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.tag:not(body).is-dark{background-color:#363636;color:#fff}.tag:not(body).is-primary{background-color:#00d1b2;color:#fff}.tag:not(body).is-primary.is-light{background-color:#ebfffc;color:#00947e}.tag:not(body).is-link{background-color:#3273dc;color:#fff}.tag:not(body).is-link.is-light{background-color:#eef3fc;color:#2160c4}.tag:not(body).is-info{background-color:#3298dc;color:#fff}.tag:not(body).is-info.is-light{background-color:#eef6fc;color:#1d72aa}.tag:not(body).is-success{background-color:#48c774;color:#fff}.tag:not(body).is-success.is-light{background-color:#effaf3;color:#257942}.tag:not(body).is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.tag:not(body).is-warning.is-light{background-color:#fffbeb;color:#947600}.tag:not(body).is-danger{background-color:#f14668;color:#fff}.tag:not(body).is-danger.is-light{background-color:#feecf0;color:#cc0f35}.tag:not(body).is-normal{font-size:.75rem}.tag:not(body).is-medium{font-size:1rem}.tag:not(body).is-large{font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child){margin-left:-0.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-0.375em}.tag:not(body) .icon:first-child:last-child{margin-left:-0.375em;margin-right:-0.375em}.tag:not(body).is-delete{margin-left:1px;padding:0;position:relative;width:2em}.tag:not(body).is-delete::before,.tag:not(body).is-delete::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag:not(body).is-delete::before{height:1px;width:50%}.tag:not(body).is-delete::after{height:50%;width:1px}.tag:not(body).is-delete:hover,.tag:not(body).is-delete:focus{background-color:#e8e8e8}.tag:not(body).is-delete:active{background-color:#dbdbdb}.tag:not(body).is-rounded{border-radius:290486px}a.tag:hover{text-decoration:underline}.title,.subtitle{word-break:break-word}.title em,.title span,.subtitle em,.subtitle span{font-weight:inherit}.title sub,.subtitle sub{font-size:.75em}.title sup,.subtitle sup{font-size:.75em}.title .tag,.subtitle .tag{vertical-align:middle}.title{color:#363636;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title+.highlight{margin-top:-0.75rem}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#000;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#363636;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.highlight{font-weight:400;max-width:100%;overflow:hidden;padding:0}.highlight pre{overflow:auto;max-width:100%}.number{align-items:center;background-color:#f5f5f5;border-radius:290486px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:.25rem .5rem;text-align:center;vertical-align:top}.select select,.textarea,.input{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#363636}.select select::-moz-placeholder,.textarea::-moz-placeholder,.input::-moz-placeholder{color:rgba(54,54,54,.3)}.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder,.input::-webkit-input-placeholder{color:rgba(54,54,54,.3)}.select select:-moz-placeholder,.textarea:-moz-placeholder,.input:-moz-placeholder{color:rgba(54,54,54,.3)}.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder,.input:-ms-input-placeholder{color:rgba(54,54,54,.3)}.select select:hover,.textarea:hover,.input:hover,.select select.is-hovered,.is-hovered.textarea,.is-hovered.input{border-color:#b5b5b5}.select select:focus,.textarea:focus,.input:focus,.select select.is-focused,.is-focused.textarea,.is-focused.input,.select select:active,.textarea:active,.input:active,.select select.is-active,.is-active.textarea,.is-active.input{border-color:#3273dc;box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.select select[disabled],[disabled].textarea,[disabled].input,fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .textarea,fieldset[disabled] .input{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.select select[disabled]::-moz-placeholder,[disabled].textarea::-moz-placeholder,[disabled].input::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder{color:rgba(122,122,122,.3)}.select select[disabled]::-webkit-input-placeholder,[disabled].textarea::-webkit-input-placeholder,[disabled].input::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder{color:rgba(122,122,122,.3)}.select select[disabled]:-moz-placeholder,[disabled].textarea:-moz-placeholder,[disabled].input:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder{color:rgba(122,122,122,.3)}.select select[disabled]:-ms-input-placeholder,[disabled].textarea:-ms-input-placeholder,[disabled].input:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder{color:rgba(122,122,122,.3)}.textarea,.input{box-shadow:inset 0 .0625em .125em rgba(10,10,10,.05);max-width:100%;width:100%}[readonly].textarea,[readonly].input{box-shadow:none}.is-white.textarea,.is-white.input{border-color:#fff}.is-white.textarea:focus,.is-white.input:focus,.is-white.is-focused.textarea,.is-white.is-focused.input,.is-white.textarea:active,.is-white.input:active,.is-white.is-active.textarea,.is-white.is-active.input{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.is-black.textarea,.is-black.input{border-color:#0a0a0a}.is-black.textarea:focus,.is-black.input:focus,.is-black.is-focused.textarea,.is-black.is-focused.input,.is-black.textarea:active,.is-black.input:active,.is-black.is-active.textarea,.is-black.is-active.input{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.is-light.textarea,.is-light.input{border-color:#f5f5f5}.is-light.textarea:focus,.is-light.input:focus,.is-light.is-focused.textarea,.is-light.is-focused.input,.is-light.textarea:active,.is-light.input:active,.is-light.is-active.textarea,.is-light.is-active.input{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.is-dark.textarea,.is-dark.input{border-color:#363636}.is-dark.textarea:focus,.is-dark.input:focus,.is-dark.is-focused.textarea,.is-dark.is-focused.input,.is-dark.textarea:active,.is-dark.input:active,.is-dark.is-active.textarea,.is-dark.is-active.input{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.is-primary.textarea,.is-primary.input{border-color:#00d1b2}.is-primary.textarea:focus,.is-primary.input:focus,.is-primary.is-focused.textarea,.is-primary.is-focused.input,.is-primary.textarea:active,.is-primary.input:active,.is-primary.is-active.textarea,.is-primary.is-active.input{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.is-link.textarea,.is-link.input{border-color:#3273dc}.is-link.textarea:focus,.is-link.input:focus,.is-link.is-focused.textarea,.is-link.is-focused.input,.is-link.textarea:active,.is-link.input:active,.is-link.is-active.textarea,.is-link.is-active.input{box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.is-info.textarea,.is-info.input{border-color:#3298dc}.is-info.textarea:focus,.is-info.input:focus,.is-info.is-focused.textarea,.is-info.is-focused.input,.is-info.textarea:active,.is-info.input:active,.is-info.is-active.textarea,.is-info.is-active.input{box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.is-success.textarea,.is-success.input{border-color:#48c774}.is-success.textarea:focus,.is-success.input:focus,.is-success.is-focused.textarea,.is-success.is-focused.input,.is-success.textarea:active,.is-success.input:active,.is-success.is-active.textarea,.is-success.is-active.input{box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.is-warning.textarea,.is-warning.input{border-color:#ffdd57}.is-warning.textarea:focus,.is-warning.input:focus,.is-warning.is-focused.textarea,.is-warning.is-focused.input,.is-warning.textarea:active,.is-warning.input:active,.is-warning.is-active.textarea,.is-warning.is-active.input{box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.is-danger.textarea,.is-danger.input{border-color:#f14668}.is-danger.textarea:focus,.is-danger.input:focus,.is-danger.is-focused.textarea,.is-danger.is-focused.input,.is-danger.textarea:active,.is-danger.input:active,.is-danger.is-active.textarea,.is-danger.is-active.input{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.is-small.textarea,.is-small.input{border-radius:2px;font-size:.75rem}.is-medium.textarea,.is-medium.input{font-size:1.25rem}.is-large.textarea,.is-large.input{font-size:1.5rem}.is-fullwidth.textarea,.is-fullwidth.input{display:block;width:100%}.is-inline.textarea,.is-inline.input{display:inline;width:auto}.input.is-rounded{border-radius:290486px;padding-left:calc(calc(0.75em - 1px) + 0.375em);padding-right:calc(calc(0.75em - 1px) + 0.375em)}.input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(0.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.radio,.checkbox{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.radio input,.checkbox input{cursor:pointer}.radio:hover,.checkbox:hover{color:#363636}[disabled].radio,[disabled].checkbox,fieldset[disabled] .radio,fieldset[disabled] .checkbox{color:#7a7a7a;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#3273dc;right:1.125em;z-index:4}.select.is-rounded select{border-radius:290486px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#363636}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select:hover,.select.is-white select.is-hovered{border-color:#f2f2f2}.select.is-white select:focus,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select.is-active{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select:hover,.select.is-black select.is-hovered{border-color:#000}.select.is-black select:focus,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select.is-active{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select:hover,.select.is-light select.is-hovered{border-color:#e8e8e8}.select.is-light select:focus,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select.is-active{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.select.is-dark:not(:hover)::after{border-color:#363636}.select.is-dark select{border-color:#363636}.select.is-dark select:hover,.select.is-dark select.is-hovered{border-color:#292929}.select.is-dark select:focus,.select.is-dark select.is-focused,.select.is-dark select:active,.select.is-dark select.is-active{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.select.is-primary:not(:hover)::after{border-color:#00d1b2}.select.is-primary select{border-color:#00d1b2}.select.is-primary select:hover,.select.is-primary select.is-hovered{border-color:#00b89c}.select.is-primary select:focus,.select.is-primary select.is-focused,.select.is-primary select:active,.select.is-primary select.is-active{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.select.is-link:not(:hover)::after{border-color:#3273dc}.select.is-link select{border-color:#3273dc}.select.is-link select:hover,.select.is-link select.is-hovered{border-color:#2366d1}.select.is-link select:focus,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select.is-active{box-shadow:0 0 0 .125em rgba(50,115,220,.25)}.select.is-info:not(:hover)::after{border-color:#3298dc}.select.is-info select{border-color:#3298dc}.select.is-info select:hover,.select.is-info select.is-hovered{border-color:#238cd1}.select.is-info select:focus,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select.is-active{box-shadow:0 0 0 .125em rgba(50,152,220,.25)}.select.is-success:not(:hover)::after{border-color:#48c774}.select.is-success select{border-color:#48c774}.select.is-success select:hover,.select.is-success select.is-hovered{border-color:#3abb67}.select.is-success select:focus,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select.is-active{box-shadow:0 0 0 .125em rgba(72,199,116,.25)}.select.is-warning:not(:hover)::after{border-color:#ffdd57}.select.is-warning select{border-color:#ffdd57}.select.is-warning select:hover,.select.is-warning select.is-hovered{border-color:#ffd83d}.select.is-warning select:focus,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select.is-active{box-shadow:0 0 0 .125em rgba(255,221,87,.25)}.select.is-danger:not(:hover)::after{border-color:#f14668}.select.is-danger select{border-color:#f14668}.select.is-danger select:hover,.select.is-danger select.is-hovered{border-color:#ef2e55}.select.is-danger select:focus,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select.is-active{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.select.is-small{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#7a7a7a}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:.625em;transform:none}.select.is-loading.is-small:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white:hover .file-cta,.file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white:focus .file-cta,.file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,255,255,.25);color:#0a0a0a}.file.is-white:active .file-cta,.file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black:hover .file-cta,.file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black:focus .file-cta,.file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(10,10,10,.25);color:#fff}.file.is-black:active .file-cta,.file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light:hover .file-cta,.file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light:focus .file-cta,.file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(245,245,245,.25);color:rgba(0,0,0,.7)}.file.is-light:active .file-cta,.file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-dark .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark:hover .file-cta,.file.is-dark.is-hovered .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark:focus .file-cta,.file.is-dark.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(54,54,54,.25);color:#fff}.file.is-dark:active .file-cta,.file.is-dark.is-active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta{background-color:#00d1b2;border-color:transparent;color:#fff}.file.is-primary:hover .file-cta,.file.is-primary.is-hovered .file-cta{background-color:#00c4a7;border-color:transparent;color:#fff}.file.is-primary:focus .file-cta,.file.is-primary.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(0,209,178,.25);color:#fff}.file.is-primary:active .file-cta,.file.is-primary.is-active .file-cta{background-color:#00b89c;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#3273dc;border-color:transparent;color:#fff}.file.is-link:hover .file-cta,.file.is-link.is-hovered .file-cta{background-color:#276cda;border-color:transparent;color:#fff}.file.is-link:focus .file-cta,.file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(50,115,220,.25);color:#fff}.file.is-link:active .file-cta,.file.is-link.is-active .file-cta{background-color:#2366d1;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3298dc;border-color:transparent;color:#fff}.file.is-info:hover .file-cta,.file.is-info.is-hovered .file-cta{background-color:#2793da;border-color:transparent;color:#fff}.file.is-info:focus .file-cta,.file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(50,152,220,.25);color:#fff}.file.is-info:active .file-cta,.file.is-info.is-active .file-cta{background-color:#238cd1;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#48c774;border-color:transparent;color:#fff}.file.is-success:hover .file-cta,.file.is-success.is-hovered .file-cta{background-color:#3ec46d;border-color:transparent;color:#fff}.file.is-success:focus .file-cta,.file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,199,116,.25);color:#fff}.file.is-success:active .file-cta,.file.is-success.is-active .file-cta{background-color:#3abb67;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning:hover .file-cta,.file.is-warning.is-hovered .file-cta{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning:focus .file-cta,.file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,221,87,.25);color:rgba(0,0,0,.7)}.file.is-warning:active .file-cta,.file.is-warning.is-active .file-cta{background-color:#ffd83d;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-danger .file-cta{background-color:#f14668;border-color:transparent;color:#fff}.file.is-danger:hover .file-cta,.file.is-danger.is-hovered .file-cta{background-color:#f03a5f;border-color:transparent;color:#fff}.file.is-danger:focus .file-cta,.file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(241,70,104,.25);color:#fff}.file.is-danger:active .file-cta,.file.is-danger.is-active .file-cta{background-color:#ef2e55;border-color:transparent;color:#fff}.file.is-small{font-size:.75rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#363636}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#363636}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#000}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#363636;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:.5em}.label.is-small{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark{color:#363636}.help.is-primary{color:#00d1b2}.help.is-link{color:#3273dc}.help.is-info{color:#3298dc}.help.is-success{color:#48c774}.help.is-warning{color:#ffdd57}.help.is-danger{color:#f14668}.field:not(:last-child){margin-bottom:.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .button:not([disabled]).is-hovered,.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control .input:not([disabled]).is-hovered,.field.has-addons .control .select select:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]).is-hovered{z-index:2}.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .button:not([disabled]).is-focused,.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button:not([disabled]).is-active,.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control .input:not([disabled]).is-focused,.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control .input:not([disabled]).is-active,.field.has-addons .control .select select:not([disabled]):focus,.field.has-addons .control .select select:not([disabled]).is-focused,.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select:not([disabled]).is-active{z-index:3}.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .button:not([disabled]).is-focused:hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button:not([disabled]).is-active:hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control .input:not([disabled]).is-focused:hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control .input:not([disabled]).is-active:hover,.field.has-addons .control .select select:not([disabled]):focus:hover,.field.has-addons .control .select select:not([disabled]).is-focused:hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select:not([disabled]).is-active:hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width: 768px){.field-label{margin-bottom:.5rem}}@media screen and (min-width: 769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small{font-size:.75rem;padding-top:.375em}.field-label.is-normal{padding-top:.375em}.field-label.is-medium{font-size:1.25rem;padding-top:.375em}.field-label.is-large{font-size:1.5rem;padding-top:.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#000}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute !important;right:.625em;top:.625em;z-index:4}.control.is-loading.is-small:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#3273dc;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#363636;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"/"}.breadcrumb ul,.breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"→"}.breadcrumb.has-bullet-separator li+li::before{content:"•"}.breadcrumb.has-dot-separator li+li::before{content:"·"}.breadcrumb.has-succeeds-separator li+li::before{content:"≻"}.card{background-color:#fff;box-shadow:0 .5em 1em -0.125em rgba(10,10,10,.1),0 0px 0 1px rgba(10,10,10,.02);color:#000;max-width:100%;position:relative}.card-header{background-color:transparent;align-items:stretch;box-shadow:0 .125em .25em rgba(10,10,10,.1);display:flex}.card-header-title{align-items:center;color:#363636;display:flex;flex-grow:1;font-weight:700;padding:.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{align-items:center;cursor:pointer;display:flex;justify-content:center;padding:.75rem 1rem}.card-image{display:block;position:relative}.card-content{background-color:transparent;padding:1.5rem}.card-footer{background-color:transparent;border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:0 .5em 1em -0.125em rgba(10,10,10,.1),0 0px 0 1px rgba(10,10,10,.02);padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#000;display:block;font-size:.875rem;line-height:1.5;padding:.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#3273dc;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .title,.level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width: 768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#000;display:block;padding:.5em .75em}.menu-list a:hover{background-color:#f5f5f5;color:#363636}.menu-list a.is-active{background-color:#3273dc;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#7a7a7a;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark{background-color:#fafafa}.message.is-dark .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body{border-color:#363636}.message.is-primary{background-color:#ebfffc}.message.is-primary .message-header{background-color:#00d1b2;color:#fff}.message.is-primary .message-body{border-color:#00d1b2;color:#00947e}.message.is-link{background-color:#eef3fc}.message.is-link .message-header{background-color:#3273dc;color:#fff}.message.is-link .message-body{border-color:#3273dc;color:#2160c4}.message.is-info{background-color:#eef6fc}.message.is-info .message-header{background-color:#3298dc;color:#fff}.message.is-info .message-body{border-color:#3298dc;color:#1d72aa}.message.is-success{background-color:#effaf3}.message.is-success .message-header{background-color:#48c774;color:#fff}.message.is-success .message-body{border-color:#48c774;color:#257942}.message.is-warning{background-color:#fffbeb}.message.is-warning .message-header{background-color:#ffdd57;color:rgba(0,0,0,.7)}.message.is-warning .message-body{border-color:#ffdd57;color:#947600}.message.is-danger{background-color:#feecf0}.message.is-danger .message-header{background-color:#f14668;color:#fff}.message.is-danger .message-body{border-color:#f14668;color:#cc0f35}.message-header{align-items:center;background-color:#000;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#000;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:transparent}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,.86)}.modal-content,.modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px),print{.modal-content,.modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-head,.modal-card-foot{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#363636;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand>.navbar-item,.navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1024px){.navbar.is-white .navbar-start>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-start .navbar-link::after,.navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand>.navbar-item,.navbar.is-black .navbar-brand .navbar-link{color:#fff}.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-black .navbar-start>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-end .navbar-link{color:#fff}.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-start .navbar-link::after,.navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand>.navbar-item,.navbar.is-light .navbar-brand .navbar-link{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width: 1024px){.navbar.is-light .navbar-start>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-end .navbar-link{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-start .navbar-link::after,.navbar.is-light .navbar-end .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,.7)}}.navbar.is-dark{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand>.navbar-item,.navbar.is-dark .navbar-brand .navbar-link{color:#fff}.navbar.is-dark .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-dark .navbar-start>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.navbar.is-dark .navbar-end .navbar-link{color:#fff}.navbar.is-dark .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end .navbar-link.is-active{background-color:#292929;color:#fff}.navbar.is-dark .navbar-start .navbar-link::after,.navbar.is-dark .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary{background-color:#00d1b2;color:#fff}.navbar.is-primary .navbar-brand>.navbar-item,.navbar.is-primary .navbar-brand .navbar-link{color:#fff}.navbar.is-primary .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand .navbar-link.is-active{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-primary .navbar-start>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.navbar.is-primary .navbar-end .navbar-link{color:#fff}.navbar.is-primary .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end .navbar-link.is-active{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-start .navbar-link::after,.navbar.is-primary .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active{background-color:#00d1b2;color:#fff}}.navbar.is-link{background-color:#3273dc;color:#fff}.navbar.is-link .navbar-brand>.navbar-item,.navbar.is-link .navbar-brand .navbar-link{color:#fff}.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-link .navbar-start>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-end .navbar-link{color:#fff}.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end .navbar-link.is-active{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-start .navbar-link::after,.navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#3273dc;color:#fff}}.navbar.is-info{background-color:#3298dc;color:#fff}.navbar.is-info .navbar-brand>.navbar-item,.navbar.is-info .navbar-brand .navbar-link{color:#fff}.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-info .navbar-start>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-end .navbar-link{color:#fff}.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end .navbar-link.is-active{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-start .navbar-link::after,.navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#238cd1;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3298dc;color:#fff}}.navbar.is-success{background-color:#48c774;color:#fff}.navbar.is-success .navbar-brand>.navbar-item,.navbar.is-success .navbar-brand .navbar-link{color:#fff}.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-success .navbar-start>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-end .navbar-link{color:#fff}.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end .navbar-link.is-active{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-start .navbar-link::after,.navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#3abb67;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#48c774;color:#fff}}.navbar.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand>.navbar-item,.navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width: 1024px){.navbar.is-warning .navbar-start>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-start .navbar-link::after,.navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ffd83d;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffdd57;color:rgba(0,0,0,.7)}}.navbar.is-danger{background-color:#f14668;color:#fff}.navbar.is-danger .navbar-brand>.navbar-item,.navbar.is-danger .navbar-brand .navbar-link{color:#fff}.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-danger .navbar-start>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-end .navbar-link{color:#fff}.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-start .navbar-link::after,.navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f14668;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}html.has-navbar-fixed-top,body.has-navbar-fixed-top{padding-top:3.25rem}html.has-navbar-fixed-bottom,body.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#000;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color,opacity,transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,.05)}.navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#000;display:block;line-height:1.5;padding:.5rem .75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}a.navbar-item,.navbar-link{cursor:pointer}a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover,a.navbar-item.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,.navbar-link.is-active{background-color:#fafafa;color:#3273dc}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(0.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:transparent;border-bottom-color:#3273dc}.navbar-item.is-tab.is-active{background-color:transparent;border-bottom-color:#3273dc;border-bottom-style:solid;border-bottom-width:3px;color:#3273dc;padding-bottom:calc(0.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#3273dc;margin-top:-0.375em;right:1.125em}.navbar-dropdown{font-size:.875rem;padding-bottom:.5rem;padding-top:.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:.5rem 0}@media screen and (max-width: 1023px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,.1);padding:.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}html.has-navbar-fixed-top-touch,body.has-navbar-fixed-top-touch{padding-top:3.25rem}html.has-navbar-fixed-bottom-touch,body.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width: 1024px){.navbar,.navbar-menu,.navbar-start,.navbar-end{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-start,.navbar.is-spaced .navbar-end{align-items:center}.navbar.is-spaced a.navbar-item,.navbar.is-spaced .navbar-link{border-radius:4px}.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#3273dc}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(0.25em, -0.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,.1);display:none;font-size:.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#3273dc}.navbar.is-spaced .navbar-dropdown,.navbar-dropdown.is-boxed{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity,transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.navbar>.container .navbar-brand,.container>.navbar .navbar-brand{margin-left:-0.75rem}.navbar>.container .navbar-menu,.container>.navbar .navbar-menu{margin-right:-0.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-desktop{top:0}html.has-navbar-fixed-top-desktop,body.has-navbar-fixed-top-desktop{padding-top:3.25rem}html.has-navbar-fixed-bottom-desktop,body.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}html.has-spaced-navbar-fixed-top,body.has-spaced-navbar-fixed-top{padding-top:5.25rem}html.has-spaced-navbar-fixed-bottom,body.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}a.navbar-item.is-active,.navbar-link.is-active{color:#0a0a0a}a.navbar-item.is-active:not(:focus):not(:hover),.navbar-link.is-active:not(:focus):not(:hover){background-color:transparent}.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link,.navbar-item.has-dropdown.is-active .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-0.25rem}.pagination.is-small{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-previous,.pagination.is-rounded .pagination-next{padding-left:1em;padding-right:1em;border-radius:290486px}.pagination.is-rounded .pagination-link{border-radius:290486px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-previous,.pagination-next,.pagination-link{border-color:#dbdbdb;color:#363636;min-width:2.5em}.pagination-previous:hover,.pagination-next:hover,.pagination-link:hover{border-color:#b5b5b5;color:#363636}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus{border-color:#3273dc}.pagination-previous:active,.pagination-next:active,.pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2)}.pagination-previous[disabled],.pagination-next[disabled],.pagination-link[disabled]{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#7a7a7a;opacity:.5}.pagination-previous,.pagination-next{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#3273dc;border-color:#3273dc;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}@media screen and (max-width: 768px){.pagination{flex-wrap:wrap}.pagination-previous,.pagination-next{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:0 .5em 1em -0.125em rgba(10,10,10,.1),0 0px 0 1px rgba(10,10,10,.02);font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading{background-color:#00d1b2;color:#fff}.panel.is-primary .panel-tabs a.is-active{border-bottom-color:#00d1b2}.panel.is-primary .panel-block.is-active .panel-icon{color:#00d1b2}.panel.is-link .panel-heading{background-color:#3273dc;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#3273dc}.panel.is-link .panel-block.is-active .panel-icon{color:#3273dc}.panel.is-info .panel-heading{background-color:#3298dc;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3298dc}.panel.is-info .panel-block.is-active .panel-icon{color:#3298dc}.panel.is-success .panel-heading{background-color:#48c774;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#48c774}.panel.is-success .panel-block.is-active .panel-icon{color:#48c774}.panel.is-warning .panel-heading{background-color:#ffdd57;color:rgba(0,0,0,.7)}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ffdd57}.panel.is-warning .panel-block.is-active .panel-icon{color:#ffdd57}.panel.is-danger .panel-heading{background-color:#f14668;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f14668}.panel.is-danger .panel-block.is-active .panel-icon{color:#f14668}.panel-tabs:not(:last-child),.panel-block:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#363636;font-size:1.25em;font-weight:700;line-height:1.25;padding:.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#000}.panel-list a:hover{color:#3273dc}.panel-block{align-items:center;color:#363636;display:flex;justify-content:flex-start;padding:.5em .75em}.panel-block input[type=checkbox]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#3273dc;color:#363636}.panel-block.is-active .panel-icon{color:#3273dc}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#7a7a7a;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#000;display:flex;justify-content:center;margin-bottom:-1px;padding:.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#363636;color:#363636}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#3273dc;color:#3273dc}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:.75em;padding-right:.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:transparent !important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#3273dc;border-color:#3273dc;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:290486px;border-top-left-radius:290486px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:290486px;border-top-right-radius:290486px;padding-right:1.25em}.tabs.is-small{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0%}.columns.is-mobile>.column.is-1{flex:none;width:8.3333333333%}.columns.is-mobile>.column.is-offset-1{margin-left:8.3333333333%}.columns.is-mobile>.column.is-2{flex:none;width:16.6666666667%}.columns.is-mobile>.column.is-offset-2{margin-left:16.6666666667%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.3333333333%}.columns.is-mobile>.column.is-offset-4{margin-left:33.3333333333%}.columns.is-mobile>.column.is-5{flex:none;width:41.6666666667%}.columns.is-mobile>.column.is-offset-5{margin-left:41.6666666667%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.3333333333%}.columns.is-mobile>.column.is-offset-7{margin-left:58.3333333333%}.columns.is-mobile>.column.is-8{flex:none;width:66.6666666667%}.columns.is-mobile>.column.is-offset-8{margin-left:66.6666666667%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.3333333333%}.columns.is-mobile>.column.is-offset-10{margin-left:83.3333333333%}.columns.is-mobile>.column.is-11{flex:none;width:91.6666666667%}.columns.is-mobile>.column.is-offset-11{margin-left:91.6666666667%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){.column.is-narrow-mobile{flex:none}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0%}.column.is-1-mobile{flex:none;width:8.3333333333%}.column.is-offset-1-mobile{margin-left:8.3333333333%}.column.is-2-mobile{flex:none;width:16.6666666667%}.column.is-offset-2-mobile{margin-left:16.6666666667%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.3333333333%}.column.is-offset-4-mobile{margin-left:33.3333333333%}.column.is-5-mobile{flex:none;width:41.6666666667%}.column.is-offset-5-mobile{margin-left:41.6666666667%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.3333333333%}.column.is-offset-7-mobile{margin-left:58.3333333333%}.column.is-8-mobile{flex:none;width:66.6666666667%}.column.is-offset-8-mobile{margin-left:66.6666666667%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.3333333333%}.column.is-offset-10-mobile{margin-left:83.3333333333%}.column.is-11-mobile{flex:none;width:91.6666666667%}.column.is-offset-11-mobile{margin-left:91.6666666667%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0%}.column.is-1,.column.is-1-tablet{flex:none;width:8.3333333333%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.3333333333%}.column.is-2,.column.is-2-tablet{flex:none;width:16.6666666667%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.6666666667%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.3333333333%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.3333333333%}.column.is-5,.column.is-5-tablet{flex:none;width:41.6666666667%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.6666666667%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.3333333333%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.3333333333%}.column.is-8,.column.is-8-tablet{flex:none;width:66.6666666667%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.6666666667%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.3333333333%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.3333333333%}.column.is-11,.column.is-11-tablet{flex:none;width:91.6666666667%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.6666666667%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1023px){.column.is-narrow-touch{flex:none}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0%}.column.is-1-touch{flex:none;width:8.3333333333%}.column.is-offset-1-touch{margin-left:8.3333333333%}.column.is-2-touch{flex:none;width:16.6666666667%}.column.is-offset-2-touch{margin-left:16.6666666667%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.3333333333%}.column.is-offset-4-touch{margin-left:33.3333333333%}.column.is-5-touch{flex:none;width:41.6666666667%}.column.is-offset-5-touch{margin-left:41.6666666667%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.3333333333%}.column.is-offset-7-touch{margin-left:58.3333333333%}.column.is-8-touch{flex:none;width:66.6666666667%}.column.is-offset-8-touch{margin-left:66.6666666667%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.3333333333%}.column.is-offset-10-touch{margin-left:83.3333333333%}.column.is-11-touch{flex:none;width:91.6666666667%}.column.is-offset-11-touch{margin-left:91.6666666667%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1024px){.column.is-narrow-desktop{flex:none}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0%}.column.is-1-desktop{flex:none;width:8.3333333333%}.column.is-offset-1-desktop{margin-left:8.3333333333%}.column.is-2-desktop{flex:none;width:16.6666666667%}.column.is-offset-2-desktop{margin-left:16.6666666667%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.3333333333%}.column.is-offset-4-desktop{margin-left:33.3333333333%}.column.is-5-desktop{flex:none;width:41.6666666667%}.column.is-offset-5-desktop{margin-left:41.6666666667%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.3333333333%}.column.is-offset-7-desktop{margin-left:58.3333333333%}.column.is-8-desktop{flex:none;width:66.6666666667%}.column.is-offset-8-desktop{margin-left:66.6666666667%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.3333333333%}.column.is-offset-10-desktop{margin-left:83.3333333333%}.column.is-11-desktop{flex:none;width:91.6666666667%}.column.is-offset-11-desktop{margin-left:91.6666666667%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){.column.is-narrow-widescreen{flex:none}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0%}.column.is-1-widescreen{flex:none;width:8.3333333333%}.column.is-offset-1-widescreen{margin-left:8.3333333333%}.column.is-2-widescreen{flex:none;width:16.6666666667%}.column.is-offset-2-widescreen{margin-left:16.6666666667%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.3333333333%}.column.is-offset-4-widescreen{margin-left:33.3333333333%}.column.is-5-widescreen{flex:none;width:41.6666666667%}.column.is-offset-5-widescreen{margin-left:41.6666666667%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.3333333333%}.column.is-offset-7-widescreen{margin-left:58.3333333333%}.column.is-8-widescreen{flex:none;width:66.6666666667%}.column.is-offset-8-widescreen{margin-left:66.6666666667%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.3333333333%}.column.is-offset-10-widescreen{margin-left:83.3333333333%}.column.is-11-widescreen{flex:none;width:91.6666666667%}.column.is-offset-11-widescreen{margin-left:91.6666666667%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){.column.is-narrow-fullhd{flex:none}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0%}.column.is-1-fullhd{flex:none;width:8.3333333333%}.column.is-offset-1-fullhd{margin-left:8.3333333333%}.column.is-2-fullhd{flex:none;width:16.6666666667%}.column.is-offset-2-fullhd{margin-left:16.6666666667%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.3333333333%}.column.is-offset-4-fullhd{margin-left:33.3333333333%}.column.is-5-fullhd{flex:none;width:41.6666666667%}.column.is-offset-5-fullhd{margin-left:41.6666666667%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.3333333333%}.column.is-offset-7-fullhd{margin-left:58.3333333333%}.column.is-8-fullhd{flex:none;width:66.6666666667%}.column.is-offset-8-fullhd{margin-left:66.6666666667%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.3333333333%}.column.is-offset-10-fullhd{margin-left:83.3333333333%}.column.is-11-fullhd{flex:none;width:91.6666666667%}.column.is-offset-11-fullhd{margin-left:91.6666666667%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-0.75rem;margin-right:-0.75rem;margin-top:-0.75rem}.columns:last-child{margin-bottom:-0.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - 0.75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0 !important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width: 769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1024px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable .column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){.columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px)and (max-width: 1407px){.columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-0-fullhd{--columnGap: 0rem}}.columns.is-variable.is-1{--columnGap: 0.25rem}@media screen and (max-width: 768px){.columns.is-variable.is-1-mobile{--columnGap: 0.25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-1-tablet{--columnGap: 0.25rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-1-tablet-only{--columnGap: 0.25rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-1-touch{--columnGap: 0.25rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-1-desktop{--columnGap: 0.25rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-1-desktop-only{--columnGap: 0.25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-1-widescreen{--columnGap: 0.25rem}}@media screen and (min-width: 1216px)and (max-width: 1407px){.columns.is-variable.is-1-widescreen-only{--columnGap: 0.25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-1-fullhd{--columnGap: 0.25rem}}.columns.is-variable.is-2{--columnGap: 0.5rem}@media screen and (max-width: 768px){.columns.is-variable.is-2-mobile{--columnGap: 0.5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-2-tablet{--columnGap: 0.5rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-2-tablet-only{--columnGap: 0.5rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-2-touch{--columnGap: 0.5rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-2-desktop{--columnGap: 0.5rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-2-desktop-only{--columnGap: 0.5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-2-widescreen{--columnGap: 0.5rem}}@media screen and (min-width: 1216px)and (max-width: 1407px){.columns.is-variable.is-2-widescreen-only{--columnGap: 0.5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-2-fullhd{--columnGap: 0.5rem}}.columns.is-variable.is-3{--columnGap: 0.75rem}@media screen and (max-width: 768px){.columns.is-variable.is-3-mobile{--columnGap: 0.75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-3-tablet{--columnGap: 0.75rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-3-tablet-only{--columnGap: 0.75rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-3-touch{--columnGap: 0.75rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-3-desktop{--columnGap: 0.75rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-3-desktop-only{--columnGap: 0.75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-3-widescreen{--columnGap: 0.75rem}}@media screen and (min-width: 1216px)and (max-width: 1407px){.columns.is-variable.is-3-widescreen-only{--columnGap: 0.75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-3-fullhd{--columnGap: 0.75rem}}.columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){.columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px)and (max-width: 1407px){.columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-4-fullhd{--columnGap: 1rem}}.columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){.columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px)and (max-width: 1407px){.columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}.columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){.columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px)and (max-width: 1407px){.columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}.columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){.columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px)and (max-width: 1407px){.columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}.columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){.columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px),print{.columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px)and (max-width: 1023px){.columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1024px)and (max-width: 1215px){.columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px)and (max-width: 1407px){.columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-8-fullhd{--columnGap: 2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:min-content}.tile.is-ancestor{margin-left:-0.75rem;margin-right:-0.75rem;margin-top:-0.75rem}.tile.is-ancestor:last-child{margin-bottom:-0.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0 !important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.3333333333%}.tile.is-2{flex:none;width:16.6666666667%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.3333333333%}.tile.is-5{flex:none;width:41.6666666667%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.3333333333%}.tile.is-8{flex:none;width:66.6666666667%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.3333333333%}.tile.is-11{flex:none;width:91.6666666667%}.tile.is-12{flex:none;width:100%}}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#363636 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c1c !important}.has-background-dark{background-color:#363636 !important}.has-text-primary{color:#00d1b2 !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#009e86 !important}.has-background-primary{background-color:#00d1b2 !important}.has-text-primary-light{color:#ebfffc !important}a.has-text-primary-light:hover,a.has-text-primary-light:focus{color:#b8fff4 !important}.has-background-primary-light{background-color:#ebfffc !important}.has-text-primary-dark{color:#00947e !important}a.has-text-primary-dark:hover,a.has-text-primary-dark:focus{color:#00c7a9 !important}.has-background-primary-dark{background-color:#00947e !important}.has-text-link{color:#3273dc !important}a.has-text-link:hover,a.has-text-link:focus{color:#205bbc !important}.has-background-link{background-color:#3273dc !important}.has-text-link-light{color:#eef3fc !important}a.has-text-link-light:hover,a.has-text-link-light:focus{color:#c2d5f5 !important}.has-background-link-light{background-color:#eef3fc !important}.has-text-link-dark{color:#2160c4 !important}a.has-text-link-dark:hover,a.has-text-link-dark:focus{color:#3b79de !important}.has-background-link-dark{background-color:#2160c4 !important}.has-text-info{color:#3298dc !important}a.has-text-info:hover,a.has-text-info:focus{color:#207dbc !important}.has-background-info{background-color:#3298dc !important}.has-text-info-light{color:#eef6fc !important}a.has-text-info-light:hover,a.has-text-info-light:focus{color:#c2e0f5 !important}.has-background-info-light{background-color:#eef6fc !important}.has-text-info-dark{color:#1d72aa !important}a.has-text-info-dark:hover,a.has-text-info-dark:focus{color:#248fd6 !important}.has-background-info-dark{background-color:#1d72aa !important}.has-text-success{color:#48c774 !important}a.has-text-success:hover,a.has-text-success:focus{color:#34a85c !important}.has-background-success{background-color:#48c774 !important}.has-text-success-light{color:#effaf3 !important}a.has-text-success-light:hover,a.has-text-success-light:focus{color:#c8eed6 !important}.has-background-success-light{background-color:#effaf3 !important}.has-text-success-dark{color:#257942 !important}a.has-text-success-dark:hover,a.has-text-success-dark:focus{color:#31a058 !important}.has-background-success-dark{background-color:#257942 !important}.has-text-warning{color:#ffdd57 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#ffd324 !important}.has-background-warning{background-color:#ffdd57 !important}.has-text-warning-light{color:#fffbeb !important}a.has-text-warning-light:hover,a.has-text-warning-light:focus{color:#fff1b8 !important}.has-background-warning-light{background-color:#fffbeb !important}.has-text-warning-dark{color:#947600 !important}a.has-text-warning-dark:hover,a.has-text-warning-dark:focus{color:#c79f00 !important}.has-background-warning-dark{background-color:#947600 !important}.has-text-danger{color:#f14668 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#ee1742 !important}.has-background-danger{background-color:#f14668 !important}.has-text-danger-light{color:#feecf0 !important}a.has-text-danger-light:hover,a.has-text-danger-light:focus{color:#fabdc9 !important}.has-background-danger-light{background-color:#feecf0 !important}.has-text-danger-dark{color:#cc0f35 !important}a.has-text-danger-dark:hover,a.has-text-danger-dark:focus{color:#ee2049 !important}.has-background-danger-dark{background-color:#cc0f35 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#363636 !important}.has-background-grey-darker{background-color:#363636 !important}.has-text-grey-dark{color:#4a4a4a !important}.has-background-grey-dark{background-color:#4a4a4a !important}.has-text-grey{color:#7a7a7a !important}.has-background-grey{background-color:#7a7a7a !important}.has-text-grey-light{color:#b5b5b5 !important}.has-background-grey-light{background-color:#b5b5b5 !important}.has-text-grey-lighter{color:#dbdbdb !important}.has-background-grey-lighter{background-color:#dbdbdb !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-clipped{overflow:hidden !important}.is-relative{position:relative !important}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.mt-1{margin-top:.25rem !important}.mr-1{margin-right:.25rem !important}.mb-1{margin-bottom:.25rem !important}.ml-1{margin-left:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.mt-2{margin-top:.5rem !important}.mr-2{margin-right:.5rem !important}.mb-2{margin-bottom:.5rem !important}.ml-2{margin-left:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.mt-3{margin-top:.75rem !important}.mr-3{margin-right:.75rem !important}.mb-3{margin-bottom:.75rem !important}.ml-3{margin-left:.75rem !important}.mx-3{margin-left:.75rem !important;margin-right:.75rem !important}.my-3{margin-top:.75rem !important;margin-bottom:.75rem !important}.mt-4{margin-top:1rem !important}.mr-4{margin-right:1rem !important}.mb-4{margin-bottom:1rem !important}.ml-4{margin-left:1rem !important}.mx-4{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1rem !important;margin-bottom:1rem !important}.mt-5{margin-top:1.5rem !important}.mr-5{margin-right:1.5rem !important}.mb-5{margin-bottom:1.5rem !important}.ml-5{margin-left:1.5rem !important}.mx-5{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.mt-6{margin-top:3rem !important}.mr-6{margin-right:3rem !important}.mb-6{margin-bottom:3rem !important}.ml-6{margin-left:3rem !important}.mx-6{margin-left:3rem !important;margin-right:3rem !important}.my-6{margin-top:3rem !important;margin-bottom:3rem !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.pt-1{padding-top:.25rem !important}.pr-1{padding-right:.25rem !important}.pb-1{padding-bottom:.25rem !important}.pl-1{padding-left:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.pt-2{padding-top:.5rem !important}.pr-2{padding-right:.5rem !important}.pb-2{padding-bottom:.5rem !important}.pl-2{padding-left:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.pt-3{padding-top:.75rem !important}.pr-3{padding-right:.75rem !important}.pb-3{padding-bottom:.75rem !important}.pl-3{padding-left:.75rem !important}.px-3{padding-left:.75rem !important;padding-right:.75rem !important}.py-3{padding-top:.75rem !important;padding-bottom:.75rem !important}.pt-4{padding-top:1rem !important}.pr-4{padding-right:1rem !important}.pb-4{padding-bottom:1rem !important}.pl-4{padding-left:1rem !important}.px-4{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1rem !important;padding-bottom:1rem !important}.pt-5{padding-top:1.5rem !important}.pr-5{padding-right:1.5rem !important}.pb-5{padding-bottom:1.5rem !important}.pl-5{padding-left:1.5rem !important}.px-5{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.pt-6{padding-top:3rem !important}.pr-6{padding-right:3rem !important}.pb-6{padding-bottom:3rem !important}.pl-6{padding-left:3rem !important}.px-6{padding-left:3rem !important;padding-right:3rem !important}.py-6{padding-top:3rem !important;padding-bottom:3rem !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px),print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1023px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1024px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px),print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1023px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1024px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px)and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px),print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1023px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1024px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px)and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px),print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1023px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1024px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px)and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px),print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1023px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1024px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px)and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:monospace !important}.is-family-code{font-family:monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px),print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1023px){.is-block-touch{display:block !important}}@media screen and (min-width: 1024px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px)and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px),print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1023px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1024px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px)and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px),print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1023px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1024px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px)and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px),print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1023px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1024px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px)and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px),print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1023px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1024px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px)and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px),print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1023px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1024px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px)and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px),print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px)and (max-width: 1023px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1023px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1024px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1024px)and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px)and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:none}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1023px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,.7)}.hero.is-white a.navbar-item:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white .navbar-link:hover,.hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg, #e8e3e4 0%, white 71%, white 100%)}@media screen and (max-width: 768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e8e3e4 0%, white 71%, white 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,.7)}.hero.is-black a.navbar-item:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black .navbar-link:hover,.hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg, black 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, black 0%, #0a0a0a 71%, #181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,.7)}.hero.is-light .subtitle{color:rgba(0,0,0,.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width: 1023px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,.7)}.hero.is-light a.navbar-item:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light .navbar-link:hover,.hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.hero.is-light .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, whitesmoke 71%, white 100%)}@media screen and (max-width: 768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, whitesmoke 71%, white 100%)}}.hero.is-dark{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong{color:inherit}.hero.is-dark .title{color:#fff}.hero.is-dark .subtitle{color:rgba(255,255,255,.9)}.hero.is-dark .subtitle a:not(.button),.hero.is-dark .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-dark .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.hero.is-dark .navbar-link{color:rgba(255,255,255,.7)}.hero.is-dark a.navbar-item:hover,.hero.is-dark a.navbar-item.is-active,.hero.is-dark .navbar-link:hover,.hero.is-dark .navbar-link.is-active{background-color:#292929;color:#fff}.hero.is-dark .tabs a{color:#fff;opacity:.9}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}@media screen and (max-width: 768px){.hero.is-dark.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}}.hero.is-primary{background-color:#00d1b2;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong{color:inherit}.hero.is-primary .title{color:#fff}.hero.is-primary .subtitle{color:rgba(255,255,255,.9)}.hero.is-primary .subtitle a:not(.button),.hero.is-primary .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-primary .navbar-menu{background-color:#00d1b2}}.hero.is-primary .navbar-item,.hero.is-primary .navbar-link{color:rgba(255,255,255,.7)}.hero.is-primary a.navbar-item:hover,.hero.is-primary a.navbar-item.is-active,.hero.is-primary .navbar-link:hover,.hero.is-primary .navbar-link.is-active{background-color:#00b89c;color:#fff}.hero.is-primary .tabs a{color:#fff;opacity:.9}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#00d1b2}.hero.is-primary.is-bold{background-image:linear-gradient(141deg, #009e6c 0%, #00d1b2 71%, #00e7eb 100%)}@media screen and (max-width: 768px){.hero.is-primary.is-bold .navbar-menu{background-image:linear-gradient(141deg, #009e6c 0%, #00d1b2 71%, #00e7eb 100%)}}.hero.is-link{background-color:#3273dc;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-link .navbar-menu{background-color:#3273dc}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,.7)}.hero.is-link a.navbar-item:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link .navbar-link:hover,.hero.is-link .navbar-link.is-active{background-color:#2366d1;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3273dc}.hero.is-link.is-bold{background-image:linear-gradient(141deg, #1577c6 0%, #3273dc 71%, #4366e5 100%)}@media screen and (max-width: 768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1577c6 0%, #3273dc 71%, #4366e5 100%)}}.hero.is-info{background-color:#3298dc;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-info .navbar-menu{background-color:#3298dc}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,.7)}.hero.is-info a.navbar-item:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info .navbar-link:hover,.hero.is-info .navbar-link.is-active{background-color:#238cd1;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3298dc}.hero.is-info.is-bold{background-image:linear-gradient(141deg, #159dc6 0%, #3298dc 71%, #4389e5 100%)}@media screen and (max-width: 768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #159dc6 0%, #3298dc 71%, #4389e5 100%)}}.hero.is-success{background-color:#48c774;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-success .navbar-menu{background-color:#48c774}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,.7)}.hero.is-success a.navbar-item:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success .navbar-link:hover,.hero.is-success .navbar-link.is-active{background-color:#3abb67;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#48c774}.hero.is-success.is-bold{background-image:linear-gradient(141deg, #29b342 0%, #48c774 71%, #56d296 100%)}@media screen and (max-width: 768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #29b342 0%, #48c774 71%, #56d296 100%)}}.hero.is-warning{background-color:#ffdd57;color:rgba(0,0,0,.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width: 1023px){.hero.is-warning .navbar-menu{background-color:#ffdd57}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,.7)}.hero.is-warning a.navbar-item:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning .navbar-link.is-active{background-color:#ffd83d;color:rgba(0,0,0,.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#ffdd57}.hero.is-warning.is-bold{background-image:linear-gradient(141deg, #ffaf24 0%, #ffdd57 71%, #fffa70 100%)}@media screen and (max-width: 768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ffaf24 0%, #ffdd57 71%, #fffa70 100%)}}.hero.is-danger{background-color:#f14668;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-danger .navbar-menu{background-color:#f14668}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,.7)}.hero.is-danger a.navbar-item:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger .navbar-link.is-active{background-color:#ef2e55;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f14668}.hero.is-danger.is-bold{background-image:linear-gradient(141deg, #fa0a62 0%, #f14668 71%, #f7595f 100%)}@media screen and (max-width: 768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #fa0a62 0%, #f14668 71%, #f7595f 100%)}}.hero.is-small .hero-body{padding:1.5rem}@media screen and (min-width: 769px),print{.hero.is-medium .hero-body{padding:9rem 1.5rem}}@media screen and (min-width: 769px),print{.hero.is-large .hero-body{padding:18rem 1.5rem}}.hero.is-halfheight .hero-body,.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}.hero.is-halfheight .hero-body>.container,.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%, -50%, 0)}.hero-video.is-transparent{opacity:.3}@media screen and (max-width: 768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:.75rem}}@media screen and (min-width: 769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-head,.hero-foot{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}.section{padding:3rem 1.5rem}@media screen and (min-width: 1024px){.section.is-medium{padding:9rem 1.5rem}.section.is-large{padding:18rem 1.5rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem}html,body{background:#ddcecc;font-size:18px}a{text-decoration:none;color:#a82305}#search-bar{background-color:#3e2263;padding:0 1em;margin-bottom:0}#search-bar #logout-mobile{display:none}@media screen and (max-width: 768px){#search-bar{display:flex;flex-wrap:wrap}#search-bar #logout-mobile{display:flex;flex-direction:row-reverse;justify-content:space-between}#search-bar #logout{display:none}#search-bar #search-input{flex:0 1 100%}}#search-bar :first-child,#search-bar :last-child{justify-content:space-between}#search-bar :last-child{flex-direction:row-reverse}#search-bar input{border-radius:0;margin:10px 0}.highlight{text-decoration:underline;font-weight:bold}.yourlabs-autocomplete ul{list-style:none;padding:0;margin:0}.yourlabs-autocomplete ul li{height:2em;line-height:2em;padding:0}.yourlabs-autocomplete ul li a{color:inherit}.yourlabs-autocomplete ul li.hilight{background:#e8554e}.autocomplete-item{display:block;width:480px;height:100%;padding:2px 10px;margin:0}.autocomplete-header{background:#b497e1}.autocomplete-value,.autocomplete-new,.autocomplete-more{background:#fff}input[type=submit]{background-color:#562f89;color:#fff}input[type=submit]:hover{background-color:#3e2263;color:#fff}.button.is-primary{background-color:#562f89;color:#fff}.button.is-primary:hover{background-color:#3e2263;color:#fff}.notification{padding:.5em 0;font-size:1.2em;text-align:center}.modal-card-head{background-color:#3e2263}.modal-card-head .modal-card-title{color:#fff}/*# sourceMappingURL=bds.css.map */ diff --git a/bds/static/bds/css/bds.css.map b/bds/static/bds/css/bds.css.map deleted file mode 100644 index bcea8c10..00000000 --- a/bds/static/bds/css/bds.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sourceRoot":"","sources":["../../../../shared/static/src/bulma/bulma.sass","../../../../shared/static/src/bulma/sass/utilities/animations.sass","../../../../shared/static/src/bulma/sass/utilities/mixins.sass","../../../../shared/static/src/bulma/sass/utilities/initial-variables.sass","../../../../shared/static/src/bulma/sass/utilities/controls.sass","../../../../shared/static/src/bulma/sass/base/minireset.sass","../../../../shared/static/src/bulma/sass/base/generic.sass","../../src/sass/bds.scss","../../../../shared/static/src/bulma/sass/elements/box.sass","../../../../shared/static/src/bulma/sass/elements/button.sass","../../../../shared/static/src/bulma/sass/elements/container.sass","../../../../shared/static/src/bulma/sass/elements/content.sass","../../../../shared/static/src/bulma/sass/elements/icon.sass","../../../../shared/static/src/bulma/sass/elements/image.sass","../../../../shared/static/src/bulma/sass/elements/notification.sass","../../../../shared/static/src/bulma/sass/elements/progress.sass","../../../../shared/static/src/bulma/sass/elements/table.sass","../../../../shared/static/src/bulma/sass/utilities/derived-variables.scss","../../../../shared/static/src/bulma/sass/elements/tag.sass","../../../../shared/static/src/bulma/sass/elements/title.sass","../../../../shared/static/src/bulma/sass/elements/other.sass","../../../../shared/static/src/bulma/sass/form/shared.sass","../../../../shared/static/src/bulma/sass/form/input-textarea.sass","../../../../shared/static/src/bulma/sass/form/checkbox-radio.sass","../../../../shared/static/src/bulma/sass/form/select.sass","../../../../shared/static/src/bulma/sass/form/file.sass","../../../../shared/static/src/bulma/sass/form/tools.sass","../../../../shared/static/src/bulma/sass/components/breadcrumb.sass","../../../../shared/static/src/bulma/sass/components/card.sass","../../../../shared/static/src/bulma/sass/components/dropdown.sass","../../../../shared/static/src/bulma/sass/components/level.sass","../../../../shared/static/src/bulma/sass/components/media.sass","../../../../shared/static/src/bulma/sass/components/menu.sass","../../../../shared/static/src/bulma/sass/components/message.sass","../../../../shared/static/src/bulma/sass/components/modal.sass","../../../../shared/static/src/bulma/sass/components/navbar.sass","../../../../shared/static/src/bulma/sass/components/pagination.sass","../../../../shared/static/src/bulma/sass/components/panel.sass","../../../../shared/static/src/bulma/sass/components/tabs.sass","../../../../shared/static/src/bulma/sass/grid/columns.sass","../../../../shared/static/src/bulma/sass/grid/tiles.sass","../../../../shared/static/src/bulma/sass/helpers/color.sass","../../../../shared/static/src/bulma/sass/helpers/float.sass","../../../../shared/static/src/bulma/sass/helpers/other.sass","../../../../shared/static/src/bulma/sass/helpers/overflow.sass","../../../../shared/static/src/bulma/sass/helpers/position.sass","../../../../shared/static/src/bulma/sass/helpers/spacing.sass","../../../../shared/static/src/bulma/sass/helpers/typography.sass","../../../../shared/static/src/bulma/sass/helpers/visibility.sass","../../../../shared/static/src/bulma/sass/layout/hero.sass","../../../../shared/static/src/bulma/sass/layout/section.sass","../../../../shared/static/src/bulma/sass/layout/footer.sass"],"names":[],"mappings":"CACA,8DCDA,sBACE,KACE,uBACF,GACE,0BC+JJ,kJANE,2BACA,yBACA,sBACA,qBACA,iBAqBF,yFAfE,6BACA,kBACA,eACA,aACA,YACA,cACA,cACA,qBACA,oBACA,kBACA,QACA,yBACA,wBACA,aAMA,8YACE,cC3IY,ODkNhB,qBAhEE,qBACA,wBACA,mCACA,YACA,cC/He,SDgIf,eACA,oBACA,qBACA,YACA,cACA,YACA,YACA,gBACA,eACA,gBACA,eACA,aACA,kBACA,mBACA,WACA,wEAEE,iBCzMW,KD0MX,WACA,cACA,SACA,kBACA,QACA,0DACA,+BACF,qCACE,WACA,UACF,mCACE,WACA,UACF,kEAEE,mCACF,mCACE,mCAEF,uCACE,YACA,gBACA,eACA,gBACA,eACA,WACF,yCACE,YACA,gBACA,eACA,gBACA,eACA,WACF,uCACE,YACA,gBACA,eACA,gBACA,eACA,WAiBJ,uFAXE,2CACA,yBACA,cCjMe,SDkMf,+BACA,6BACA,WACA,cACA,WACA,kBACA,UAYF,ywBANE,OADgB,EAEhB,KAFgB,EAGhB,kBACA,MAJgB,EAKhB,IALgB,EE7OlB,yIA3BE,qBACA,wBACA,mBACA,6BACA,cDqDO,ICpDP,gBACA,oBACA,UDkBO,KCjBP,OAfe,MAgBf,2BACA,YAhBoB,IAiBpB,eAfyB,kBAgBzB,aAf2B,mBAgB3B,cAhB2B,mBAiB3B,YAlByB,kBAmBzB,kBACA,mBAEA,w3BAIE,aACF,slBAEE,mBCrCJ,2EAEA,yGAuBE,SACA,UAGF,kBAME,eACA,mBAGF,GACE,gBAGF,6BAIE,SAGF,KACE,sBAGA,qBAGE,mBAGJ,UAEE,YACA,eAGF,OACE,SAGF,MACE,yBACA,iBAEF,MAEE,UACA,gCACE,mBC/CJ,KACE,iBHjBa,KGkBb,UAhCU,KAiCV,kCACA,mCACA,UAlCe,MAmCf,WAhCgB,OAiChB,WAhCgB,OAiChB,eApCe,mBAqCf,sBAEF,kDAOE,cAEF,kCAKE,YH5BkB,4JG8BpB,SAEE,6BACA,4BACA,YHjCiB,UGmCnB,KACE,MC9DK,KD+DL,UAzDe,IA0Df,YH1Bc,IG2Bd,YAzDiB,IA6DnB,EACE,MHnDa,QGoDb,eACA,qBACA,SACE,mBACF,QACE,MHzEW,QG2Ef,KACE,iBHrEa,QGsEb,MH3Da,QG4Db,UApEU,OAqEV,YAtEY,OAuEZ,QAxEa,iBA0Ef,GACE,iBH5Ea,QG6Eb,YACA,cACA,OAvEU,IAwEV,OAvEU,SAyEZ,IACE,YACA,eAEF,uCAEE,wBAEF,MACE,UAtFgB,OAwFlB,KACE,mBACA,oBAEF,OACE,MHzGa,QG0Gb,YHpEY,IGwEd,SACE,YAEF,IJzDE,iCI2DA,iBH5Ga,QG6Gb,MCvHK,KDwHL,UAhGc,OAiGd,gBACA,QAjGY,eAkGZ,gBACA,iBACA,SACE,6BACA,mBACA,UAtGiB,IAuGjB,UAGF,kBAEE,mBACA,4CACE,mBACJ,SACE,MHvIW,QKGf,KAEE,iBLIa,KKHb,cL0Da,IKzDb,WAVW,qEAWX,MDXK,KCYL,cACA,QAZY,QAeZ,wBAEE,WAfoB,wDAgBtB,aACE,WAhBqB,oDCuCzB,QAGE,iBNlCa,KMmCb,aNxCa,QMyCb,aLhDqB,IKiDrB,MN9Ca,QM+Cb,eAGA,uBACA,eAlDwB,kBAmDxB,aAlD0B,IAmD1B,cAnD0B,IAoD1B,YArDwB,kBAsDxB,kBACA,mBACA,eACE,cAEA,oFAIE,aACA,YACF,2CPwEA,YOvE0B,mBPuE1B,aOtE0B,MAC1B,2CPqEA,YOpE0B,MPoE1B,aOnE0B,mBAC1B,qCACE,+BACA,gCAEJ,iCAEE,aN3EW,QM4EX,MN/EW,QMgFb,iCAEE,aNlEW,QMmEX,MNnFW,QMoFX,2DACE,6CACJ,iCAEE,aNvFW,QMwFX,MNzFW,QM2Fb,gBACE,6BACA,yBACA,MFjGG,KEkGH,gBA/EqB,UAgFrB,kGAIE,iBN7FS,QM8FT,MNrGS,QMsGX,iDAEE,yBACA,MNzGS,QM0GX,6DAEE,6BACA,yBACA,gBAIF,iBACE,iBAHM,KAIN,yBACA,MAJa,QAKb,mDAEE,yBACA,yBACA,MATW,QAUb,mDAEE,yBACA,MAbW,QAcX,6EACE,8CACJ,mDAEE,yBACA,yBACA,MApBW,QAqBb,+DAEE,iBAxBI,KAyBJ,yBACA,gBACF,6BACE,iBA3BW,QA4BX,MA7BI,KA8BJ,2EAEE,sBACF,uFAEE,iBAlCS,QAmCT,yBACA,gBACA,MAtCE,KAwCJ,mCACE,gEACJ,6BACE,6BACA,aA5CI,KA6CJ,MA7CI,KA8CJ,sJAIE,iBAlDE,KAmDF,aAnDE,KAoDF,MAnDS,QAqDT,+CACE,0DAKA,8NACE,gEACN,uFAEE,6BACA,aAjEE,KAkEF,gBACA,MAnEE,KAoEN,yCACE,6BACA,aArEW,QAsEX,MAtEW,QAuEX,sMAIE,iBA3ES,QA4ET,MA7EE,KAmFA,8QACE,0DACN,+GAEE,6BACA,aAvFS,QAwFT,gBACA,MAzFS,QACf,iBACE,iBAHM,QAIN,yBACA,MAJa,KAKb,mDAEE,yBACA,yBACA,MATW,KAUb,mDAEE,yBACA,MAbW,KAcX,6EACE,2CACJ,mDAEE,sBACA,yBACA,MApBW,KAqBb,+DAEE,iBAxBI,QAyBJ,yBACA,gBACF,6BACE,iBA3BW,KA4BX,MA7BI,QA8BJ,2EAEE,yBACF,uFAEE,iBAlCS,KAmCT,yBACA,gBACA,MAtCE,QAwCJ,mCACE,0DACJ,6BACE,6BACA,aA5CI,QA6CJ,MA7CI,QA8CJ,sJAIE,iBAlDE,QAmDF,aAnDE,QAoDF,MAnDS,KAqDT,+CACE,gEAKA,8NACE,0DACN,uFAEE,6BACA,aAjEE,QAkEF,gBACA,MAnEE,QAoEN,yCACE,6BACA,aArEW,KAsEX,MAtEW,KAuEX,sMAIE,iBA3ES,KA4ET,MA7EE,QAmFA,8QACE,gEACN,+GAEE,6BACA,aAvFS,KAwFT,gBACA,MAzFS,KACf,iBACE,iBAHM,QAIN,yBACA,MAJa,eAKb,mDAEE,sBACA,yBACA,MATW,eAUb,mDAEE,yBACA,MAbW,eAcX,6EACE,8CACJ,mDAEE,yBACA,yBACA,MApBW,eAqBb,+DAEE,iBAxBI,QAyBJ,yBACA,gBACF,6BACE,iBA3BW,eA4BX,MA7BI,QA8BJ,2EAEE,gCACF,uFAEE,iBAlCS,eAmCT,yBACA,gBACA,MAtCE,QAwCJ,mCACE,8EACJ,6BACE,6BACA,aA5CI,QA6CJ,MA7CI,QA8CJ,sJAIE,iBAlDE,QAmDF,aAnDE,QAoDF,MAnDS,eAqDT,+CACE,gEAKA,8NACE,8EACN,uFAEE,6BACA,aAjEE,QAkEF,gBACA,MAnEE,QAoEN,yCACE,6BACA,aArEW,eAsEX,MAtEW,eAuEX,sMAIE,iBA3ES,eA4ET,MA7EE,QAmFA,8QACE,gEACN,+GAEE,6BACA,aAvFS,eAwFT,gBACA,MAzFS,eACf,gBACE,iBAHM,QAIN,yBACA,MAJa,KAKb,iDAEE,yBACA,yBACA,MATW,KAUb,iDAEE,yBACA,MAbW,KAcX,2EACE,2CACJ,iDAEE,yBACA,yBACA,MApBW,KAqBb,6DAEE,iBAxBI,QAyBJ,yBACA,gBACF,4BACE,iBA3BW,KA4BX,MA7BI,QA8BJ,yEAEE,yBACF,qFAEE,iBAlCS,KAmCT,yBACA,gBACA,MAtCE,QAwCJ,kCACE,0DACJ,4BACE,6BACA,aA5CI,QA6CJ,MA7CI,QA8CJ,kJAIE,iBAlDE,QAmDF,aAnDE,QAoDF,MAnDS,KAqDT,8CACE,gEAKA,0NACE,0DACN,qFAEE,6BACA,aAjEE,QAkEF,gBACA,MAnEE,QAoEN,wCACE,6BACA,aArEW,KAsEX,MAtEW,KAuEX,kMAIE,iBA3ES,KA4ET,MA7EE,QAmFA,0QACE,gEACN,6GAEE,6BACA,aAvFS,KAwFT,gBACA,MAzFS,KACf,mBACE,iBAHM,QAIN,yBACA,MAJa,KAKb,uDAEE,yBACA,yBACA,MATW,KAUb,uDAEE,yBACA,MAbW,KAcX,iFACE,4CACJ,uDAEE,yBACA,yBACA,MApBW,KAqBb,mEAEE,iBAxBI,QAyBJ,yBACA,gBACF,+BACE,iBA3BW,KA4BX,MA7BI,QA8BJ,+EAEE,yBACF,2FAEE,iBAlCS,KAmCT,yBACA,gBACA,MAtCE,QAwCJ,qCACE,0DACJ,+BACE,6BACA,aA5CI,QA6CJ,MA7CI,QA8CJ,8JAIE,iBAlDE,QAmDF,aAnDE,QAoDF,MAnDS,KAqDT,iDACE,gEAKA,sOACE,0DACN,2FAEE,6BACA,aAjEE,QAkEF,gBACA,MAnEE,QAoEN,2CACE,6BACA,aArEW,KAsEX,MAtEW,KAuEX,8MAIE,iBA3ES,KA4ET,MA7EE,QAmFA,sRACE,gEACN,mHAEE,6BACA,aAvFS,KAwFT,gBACA,MAzFS,KA8FX,4BACE,iBAHY,QAIZ,MAHW,QAIX,yEAEE,yBACA,yBACA,MARS,QASX,yEAEE,yBACA,yBACA,MAbS,QA5FjB,gBACE,iBAHM,QAIN,yBACA,MAJa,KAKb,iDAEE,yBACA,yBACA,MATW,KAUb,iDAEE,yBACA,MAbW,KAcX,2EACE,6CACJ,iDAEE,yBACA,yBACA,MApBW,KAqBb,6DAEE,iBAxBI,QAyBJ,yBACA,gBACF,4BACE,iBA3BW,KA4BX,MA7BI,QA8BJ,yEAEE,yBACF,qFAEE,iBAlCS,KAmCT,yBACA,gBACA,MAtCE,QAwCJ,kCACE,0DACJ,4BACE,6BACA,aA5CI,QA6CJ,MA7CI,QA8CJ,kJAIE,iBAlDE,QAmDF,aAnDE,QAoDF,MAnDS,KAqDT,8CACE,gEAKA,0NACE,0DACN,qFAEE,6BACA,aAjEE,QAkEF,gBACA,MAnEE,QAoEN,wCACE,6BACA,aArEW,KAsEX,MAtEW,KAuEX,kMAIE,iBA3ES,KA4ET,MA7EE,QAmFA,0QACE,gEACN,6GAEE,6BACA,aAvFS,KAwFT,gBACA,MAzFS,KA8FX,yBACE,iBAHY,QAIZ,MAHW,QAIX,mEAEE,yBACA,yBACA,MARS,QASX,mEAEE,yBACA,yBACA,MAbS,QA5FjB,gBACE,iBAHM,QAIN,yBACA,MAJa,KAKb,iDAEE,yBACA,yBACA,MATW,KAUb,iDAEE,yBACA,MAbW,KAcX,2EACE,6CACJ,iDAEE,yBACA,yBACA,MApBW,KAqBb,6DAEE,iBAxBI,QAyBJ,yBACA,gBACF,4BACE,iBA3BW,KA4BX,MA7BI,QA8BJ,yEAEE,yBACF,qFAEE,iBAlCS,KAmCT,yBACA,gBACA,MAtCE,QAwCJ,kCACE,0DACJ,4BACE,6BACA,aA5CI,QA6CJ,MA7CI,QA8CJ,kJAIE,iBAlDE,QAmDF,aAnDE,QAoDF,MAnDS,KAqDT,8CACE,gEAKA,0NACE,0DACN,qFAEE,6BACA,aAjEE,QAkEF,gBACA,MAnEE,QAoEN,wCACE,6BACA,aArEW,KAsEX,MAtEW,KAuEX,kMAIE,iBA3ES,KA4ET,MA7EE,QAmFA,0QACE,gEACN,6GAEE,6BACA,aAvFS,KAwFT,gBACA,MAzFS,KA8FX,yBACE,iBAHY,QAIZ,MAHW,QAIX,mEAEE,yBACA,yBACA,MARS,QASX,mEAEE,yBACA,yBACA,MAbS,QA5FjB,mBACE,iBAHM,QAIN,yBACA,MAJa,KAKb,uDAEE,yBACA,yBACA,MATW,KAUb,uDAEE,yBACA,MAbW,KAcX,iFACE,6CACJ,uDAEE,yBACA,yBACA,MApBW,KAqBb,mEAEE,iBAxBI,QAyBJ,yBACA,gBACF,+BACE,iBA3BW,KA4BX,MA7BI,QA8BJ,+EAEE,yBACF,2FAEE,iBAlCS,KAmCT,yBACA,gBACA,MAtCE,QAwCJ,qCACE,0DACJ,+BACE,6BACA,aA5CI,QA6CJ,MA7CI,QA8CJ,8JAIE,iBAlDE,QAmDF,aAnDE,QAoDF,MAnDS,KAqDT,iDACE,gEAKA,sOACE,0DACN,2FAEE,6BACA,aAjEE,QAkEF,gBACA,MAnEE,QAoEN,2CACE,6BACA,aArEW,KAsEX,MAtEW,KAuEX,8MAIE,iBA3ES,KA4ET,MA7EE,QAmFA,sRACE,gEACN,mHAEE,6BACA,aAvFS,KAwFT,gBACA,MAzFS,KA8FX,4BACE,iBAHY,QAIZ,MAHW,QAIX,yEAEE,yBACA,yBACA,MARS,QASX,yEAEE,yBACA,yBACA,MAbS,QA5FjB,mBACE,iBAHM,QAIN,yBACA,MAJa,eAKb,uDAEE,yBACA,yBACA,MATW,eAUb,uDAEE,yBACA,MAbW,eAcX,iFACE,6CACJ,uDAEE,yBACA,yBACA,MApBW,eAqBb,mEAEE,iBAxBI,QAyBJ,yBACA,gBACF,+BACE,iBA3BW,eA4BX,MA7BI,QA8BJ,+EAEE,gCACF,2FAEE,iBAlCS,eAmCT,yBACA,gBACA,MAtCE,QAwCJ,qCACE,8EACJ,+BACE,6BACA,aA5CI,QA6CJ,MA7CI,QA8CJ,8JAIE,iBAlDE,QAmDF,aAnDE,QAoDF,MAnDS,eAqDT,iDACE,gEAKA,sOACE,8EACN,2FAEE,6BACA,aAjEE,QAkEF,gBACA,MAnEE,QAoEN,2CACE,6BACA,aArEW,eAsEX,MAtEW,eAuEX,8MAIE,iBA3ES,eA4ET,MA7EE,QAmFA,sRACE,gEACN,mHAEE,6BACA,aAvFS,eAwFT,gBACA,MAzFS,eA8FX,4BACE,iBAHY,QAIZ,MAHW,QAIX,yEAEE,yBACA,yBACA,MARS,QASX,yEAEE,yBACA,yBACA,MAbS,QA5FjB,kBACE,iBAHM,QAIN,yBACA,MAJa,KAKb,qDAEE,yBACA,yBACA,MATW,KAUb,qDAEE,yBACA,MAbW,KAcX,+EACE,6CACJ,qDAEE,yBACA,yBACA,MApBW,KAqBb,iEAEE,iBAxBI,QAyBJ,yBACA,gBACF,8BACE,iBA3BW,KA4BX,MA7BI,QA8BJ,6EAEE,yBACF,yFAEE,iBAlCS,KAmCT,yBACA,gBACA,MAtCE,QAwCJ,oCACE,0DACJ,8BACE,6BACA,aA5CI,QA6CJ,MA7CI,QA8CJ,0JAIE,iBAlDE,QAmDF,aAnDE,QAoDF,MAnDS,KAqDT,gDACE,gEAKA,kOACE,0DACN,yFAEE,6BACA,aAjEE,QAkEF,gBACA,MAnEE,QAoEN,0CACE,6BACA,aArEW,KAsEX,MAtEW,KAuEX,0MAIE,iBA3ES,KA4ET,MA7EE,QAmFA,kRACE,gEACN,iHAEE,6BACA,aAvFS,KAwFT,gBACA,MAzFS,KA8FX,2BACE,iBAHY,QAIZ,MAHW,QAIX,uEAEE,yBACA,yBACA,MARS,QASX,uEAEE,yBACA,yBACA,MAbS,QAenB,iBA9LA,cN+Ba,IM9Bb,iBA+LA,kBA7LA,UNHO,KMkMP,kBA7LA,UNNO,QMqMP,iBA7LA,UNTO,OMyMP,6CAEE,iBN/NW,KMgOX,aNrOW,QMsOX,WApNqB,KAqNrB,QApNsB,GAqNxB,qBACE,aACA,WACF,mBACE,6BACA,oBACA,0BP/OF,kBAKE,2BACA,0BO4OE,6BACJ,kBACE,iBNhPW,QMiPX,aNpPW,QMqPX,MNvPW,QMwPX,gBACA,oBACF,mBACE,cN5La,SM6Lb,gCACA,iCAEJ,SACE,mBACA,aACA,eACA,2BACA,iBACE,oBACA,qDP9HA,aO+H0B,MAC5B,oBACE,sBACF,0BACE,mBAGA,0EAjPF,cN+Ba,IM9Bb,iBAmPE,0EA/OF,UNNO,QMwPL,0EAhPF,UNTO,OM6PH,8CACE,4BACA,yBACF,6CACE,6BACA,0BPrJJ,aOsJ4B,KAC1B,uCPvJF,aOwJ4B,EAC1B,yEAEE,UACF,0LAKE,UACA,wNACE,UACJ,wCACE,YACA,cACN,qBACE,uBAEE,iEACE,mBACA,oBACN,kBACE,yBAEE,8DACE,mBACA,oBChUR,WACE,YACA,cACA,kBACA,WACA,oBACE,eACA,aP4CE,KO3CF,cP2CE,KO1CF,WRsFF,sCQ/FF,WAWI,iBR8FA,sCQ5FA,yBACE,kBR0GF,sCQxGA,qBACE,kBR6FF,sCQ9GJ,WAmBI,kBR0GA,sCQ7HJ,WAqBI,kBCDF,eACE,iBASA,sNACE,kBACJ,wEAME,MRlCW,QQmCX,YREc,IQDd,YAxC0B,MAyC5B,YACE,cACA,mBACA,8BACE,eACJ,YACE,iBACA,sBACA,8BACE,oBACJ,YACE,gBACA,sBACA,8BACE,oBACJ,YACE,iBACA,mBACF,YACE,kBACA,sBACF,YACE,cACA,kBACF,oBACE,iBRvDW,QDmIX,YS3I6B,kBAiE7B,QAhEyB,aAiE3B,YACE,4BTwEA,YSvEwB,IACxB,eACA,wBACE,wBACA,uCACE,4BACF,uCACE,4BACF,uCACE,4BACF,uCACE,4BACN,YACE,wBT0DA,YSzDwB,IACxB,eACA,eACE,uBACA,gBACA,kBACE,uBACN,YTkDE,YSjDwB,IAC1B,gBACE,gBACA,iBACA,kBACA,kCACE,eACF,iCACE,kBACF,oBACE,qBACF,2BACE,kBACJ,aT9CA,iCSgDE,gBACA,QAvGkB,aAwGlB,gBACA,iBACF,0BAEE,cACF,eACE,WACA,oCAEE,OA/GsB,kBAgHtB,aA/G4B,QAgH5B,QA/GuB,WAgHvB,mBACF,kBACE,MRxHS,QQyHT,+BACE,mBAEF,gDAEE,aAtH+B,QAuH/B,MR/HO,QQiIT,gDAEE,aAzH+B,QA0H/B,MRpIO,QQwIL,4EAEE,sBAER,qBACE,aAEJ,kBACE,URhHK,OQiHP,mBACE,URpHK,QQqHP,kBACE,URvHK,OS9BT,MACE,mBACA,oBACA,uBACA,OATgB,OAUhB,MAVgB,OAYhB,eACE,OAZoB,KAapB,MAboB,KActB,gBACE,OAdqB,KAerB,MAfqB,KAgBvB,eACE,OAhBoB,KAiBpB,MAjBoB,KCDxB,OACE,cACA,kBACA,WACE,cACA,YACA,WACA,sBACE,cV6DW,SU5Df,oBACE,WAkBA,wtBAGE,YACA,WACJ,gCAEE,iBACF,eACE,gBACF,eACE,gBACF,eACE,qBACF,eACE,gBACF,gBACE,mBACF,eACE,gBACF,eACE,qBACF,eACE,iBACF,eACE,sBACF,eACE,iBACF,eACE,sBACF,gBACE,sBACF,eACE,iBACF,eACE,iBAGA,gBACE,YACA,WAFF,gBACE,YACA,WAFF,gBACE,YACA,WAFF,gBACE,YACA,WAFF,gBACE,YACA,WAFF,gBACE,YACA,WAFF,kBACE,aACA,YC/DN,cAEE,iBXIa,QWHb,cX2DO,IW1DP,kBAEE,QATuB,8BAYzB,iDACE,mBACA,0BACF,qBACE,mBACF,qCAEE,WXRW,KWSb,uBACE,uBACF,sBZ8HE,MY7Hc,MACd,kBACA,UACF,oEAGE,mBAKA,uBACE,iBAHM,KAIN,MAHa,QACf,uBACE,iBAHM,QAIN,MAHa,KACf,uBACE,iBAHM,QAIN,MAHa,eACf,sBACE,iBAHM,QAIN,MAHa,KACf,yBACE,iBAHM,QAIN,MAHa,KAQX,kCACE,iBAHY,QAIZ,MAHW,QANjB,sBACE,iBAHM,QAIN,MAHa,KAQX,+BACE,iBAHY,QAIZ,MAHW,QANjB,sBACE,iBAHM,QAIN,MAHa,KAQX,+BACE,iBAHY,QAIZ,MAHW,QANjB,yBACE,iBAHM,QAIN,MAHa,KAQX,kCACE,iBAHY,QAIZ,MAHW,QANjB,yBACE,iBAHM,QAIN,MAHa,eAQX,kCACE,iBAHY,QAIZ,MAHW,QANjB,wBACE,iBAHM,QAIN,MAHa,KAQX,iCACE,iBAHY,QAIZ,MAHW,QCtCrB,UAEE,qBACA,wBACA,YACA,cZ4De,SY3Df,cACA,OZwBO,KYvBP,gBACA,UACA,WACA,gCACE,iBZPY,QYQd,kCACE,iBRjBG,KQkBL,6BACE,iBRnBG,KQoBL,oBACE,iBRrBG,KQsBH,YAKE,2CACE,iBAHI,KAIN,sCACE,iBALI,KAMN,6BACE,iBAPI,KAQN,iCACE,mEAPF,2CACE,iBAHI,QAIN,sCACE,iBALI,QAMN,6BACE,iBAPI,QAQN,iCACE,qEAPF,2CACE,iBAHI,QAIN,sCACE,iBALI,QAMN,6BACE,iBAPI,QAQN,iCACE,wEAPF,0CACE,iBAHI,QAIN,qCACE,iBALI,QAMN,4BACE,iBAPI,QAQN,gCACE,qEAPF,6CACE,iBAHI,QAIN,wCACE,iBALI,QAMN,+BACE,iBAPI,QAQN,mCACE,qEAPF,0CACE,iBAHI,QAIN,qCACE,iBALI,QAMN,4BACE,iBAPI,QAQN,gCACE,qEAPF,0CACE,iBAHI,QAIN,qCACE,iBALI,QAMN,4BACE,iBAPI,QAQN,gCACE,qEAPF,6CACE,iBAHI,QAIN,wCACE,iBALI,QAMN,+BACE,iBAPI,QAQN,mCACE,qEAPF,6CACE,iBAHI,QAIN,wCACE,iBALI,QAMN,+BACE,iBAPI,QAQN,mCACE,qEAPF,4CACE,iBAHI,QAIN,uCACE,iBALI,QAMN,8BACE,iBAPI,QAQN,kCACE,qEAEN,wBACE,mBApC8B,KAqC9B,mCACA,iCACA,iCACA,iBZjCY,QYkCZ,mEACA,6BACA,4BACA,0BACA,8CACE,6BACF,2CACE,6BAGJ,mBACE,OZlBK,OYmBP,oBACE,OZtBK,QYuBP,mBACE,OZzBK,OY2BT,6BACE,KACE,2BACF,GACE,6BCzCJ,OAEE,iBbZa,Kaab,MbtBa,QauBb,oBAEE,OA5BgB,kBA6BhB,aA5BsB,QA6BtB,QA5BiB,WA6BjB,mBAKE,sCACE,iBAHM,KAIN,aAJM,KAKN,MAJa,QACf,sCACE,iBAHM,QAIN,aAJM,QAKN,MAJa,KACf,sCACE,iBAHM,QAIN,aAJM,QAKN,MAJa,eACf,oCACE,iBAHM,QAIN,aAJM,QAKN,MAJa,KACf,0CACE,iBAHM,QAIN,aAJM,QAKN,MAJa,KACf,oCACE,iBAHM,QAIN,aAJM,QAKN,MAJa,KACf,oCACE,iBAHM,QAIN,aAJM,QAKN,MAJa,KACf,0CACE,iBAHM,QAIN,aAJM,QAKN,MAJa,KACf,0CACE,iBAHM,QAIN,aAJM,QAKN,MAJa,eACf,wCACE,iBAHM,QAIN,aAJM,QAKN,MAJa,KAMjB,wCACE,mBACA,SACF,4CACE,iBb5BS,Qa6BT,MC5Ba,KD6Bb,0GAEE,mBACJ,8CACE,sBACJ,UACE,MblDW,QamDX,uBACE,mBAEF,sBACE,iBbzCS,Qa0CT,MCzCa,KD0Cb,qDAEE,mBACF,kDAEE,aC/CW,KDgDX,mBACN,aACE,iBA1D0B,YA2D1B,gCAEE,aAlEyB,QAmEzB,MbrES,QasEb,aACE,iBA9D0B,YA+D1B,gCAEE,aAtEyB,QAuEzB,Mb3ES,Qa4Eb,aACE,iBArE0B,YAwEtB,4DAEE,sBAGN,4CAEE,iBAGE,wEAEE,wBACR,oBACE,WAII,qDACE,iBb3FK,Qa+FL,gEACE,iBbhGG,QaiGH,gFACE,iBbnGC,QaqGX,wCAEE,mBAIE,6DACE,iBb3GK,Qa6Gf,iBd7DE,iCcgEA,cACA,kBACA,eE3HF,MACE,mBACA,aACA,eACA,2BACA,WACE,oBACA,4BhBoIA,agBnI0B,MAC5B,iBACE,sBACF,uBACE,mBAGA,qDACE,UfgBG,KedL,qDACE,UfYG,QeXP,kBACE,uBACA,uBACE,oBACA,mBACJ,eACE,yBAEE,sCACE,kBACF,qCACE,eAEJ,sBhB0GA,agBzG0B,EACxB,wChBwGF,YgBvG4B,EAEtB,yBACA,4BAIJ,uCAEI,0BACA,6BAKV,eACE,mBACA,iBf7Ca,Qe8Cb,cfUO,IeTP,MXzDK,KW0DL,oBACA,UfxBO,OeyBP,WACA,uBACA,gBACA,mBACA,oBACA,mBACA,uBhB2EE,YgB1EwB,OhB0ExB,agBzEwB,UAKxB,wBACE,iBAHM,KAIN,MAHa,QACf,wBACE,iBAHM,QAIN,MAHa,KACf,wBACE,iBAHM,QAIN,MAHa,eACf,uBACE,iBAHM,QAIN,MAHa,KACf,0BACE,iBAHM,QAIN,MAHa,KAQX,mCACE,iBAHY,QAIZ,MAHW,QANjB,uBACE,iBAHM,QAIN,MAHa,KAQX,gCACE,iBAHY,QAIZ,MAHW,QANjB,uBACE,iBAHM,QAIN,MAHa,KAQX,gCACE,iBAHY,QAIZ,MAHW,QANjB,0BACE,iBAHM,QAIN,MAHa,KAQX,mCACE,iBAHY,QAIZ,MAHW,QANjB,0BACE,iBAHM,QAIN,MAHa,eAQX,mCACE,iBAHY,QAIZ,MAHW,QANjB,yBACE,iBAHM,QAIN,MAHa,KAQX,kCACE,iBAHY,QAIZ,MAHW,QAKnB,yBACE,UflDK,OemDP,yBACE,UfrDK,KesDP,wBACE,UfxDK,Qe0DL,kDhBkDA,YgBjD0B,ShBiD1B,agBhD0B,QAC1B,kDhB+CA,YgB9C0B,QhB8C1B,agB7C0B,SAC1B,4ChB4CA,YgB3C0B,ShB2C1B,agB1C0B,SAE5B,yBhBwCE,YgB7IgB,IAuGhB,UACA,kBACA,UACA,iEAEE,8BACA,WACA,cACA,SACA,kBACA,QACA,0DACA,+BACF,iCACE,WACA,UACF,gCACE,WACA,UACF,8DAEE,yBACF,gCACE,yBACJ,0BACE,cf5Da,Se+Df,YACE,0BCpHJ,iBAGE,sBACA,kDAEE,oBACF,yBACE,UApBa,MAqBf,yBACE,UArBa,MAsBf,2BACE,sBAEJ,OACE,MhB5Ba,QgB+Bb,UhBHO,KgBIP,YhBKgB,IgBJhB,YAnCkB,MAoClB,cACE,MApCiB,QAqCjB,YApCkB,QAqCpB,kBACE,oBACF,iCACE,WA7BuB,SAiCvB,YACE,UFgFE,KEjFJ,YACE,UFgFE,OEjFJ,YACE,UFgFE,KEjFJ,YACE,UFgFE,OEjFJ,YACE,UFgFE,QEjFJ,YACE,UFgFE,KEjFJ,YACE,UFgFE,OE9ER,UACE,MZnDK,KYsDL,UhBrBO,QgBsBP,YhBjBc,IgBkBd,YA7CqB,KA8CrB,iBACE,MhBvDW,QgBwDX,YhBnBc,IgBoBhB,iCACE,WA/CuB,SAmDvB,eACE,UF8DE,KE/DJ,eACE,UF8DE,OE/DJ,eACE,UF8DE,KE/DJ,eACE,UF8DE,OE/DJ,eACE,UF8DE,QE/DJ,eACE,UF8DE,KE/DJ,eACE,UF8DE,OG7HR,SACE,cACA,eACA,mBACA,kBACA,yBAEF,WAEE,YjB0Bc,IiBzBd,eACA,gBACA,UACA,eACE,cACA,eAKJ,QACE,mBACA,iBjBfa,QiBgBb,cjB0Ce,SiBzCf,oBACA,UjBKO,QiBJP,WACA,uBACA,oBACA,gBACA,qBACA,kBACA,mBCeF,gCAxBE,iBlBda,KkBeb,alBpBa,QkBqBb,clBsCO,IkBrCP,MlB1Ba,QD6DX,sFmBjCA,MA7BsB,kBnB8DtB,iHmBjCA,MA7BsB,kBnB8DtB,mFmBjCA,MA7BsB,kBnB8DtB,kGmBjCA,MA7BsB,kBA8BxB,mHAEE,alB5BW,QkB6Bb,sOAIE,alBpBW,QkBqBX,6CACF,yLAEE,iBlBjCW,QkBkCX,alBlCW,QkBmCX,gBACA,MlBzCW,QD2DX,uTmBhBE,MAjC6B,qBnBiD/B,sXmBhBE,MAjC6B,qBnBiD/B,gTmBhBE,MAjC6B,qBnBiD/B,mVmBhBE,MAjC6B,qBCdnC,iBAEE,WDFa,0CCGb,eACA,WACA,qCACE,gBAIA,mCACE,aAFM,KAGN,gNAIE,8CANJ,mCACE,aAFM,QAGN,gNAIE,2CANJ,mCACE,aAFM,QAGN,gNAIE,8CANJ,iCACE,aAFM,QAGN,wMAIE,2CANJ,uCACE,aAFM,QAGN,gOAIE,4CANJ,iCACE,aAFM,QAGN,wMAIE,6CANJ,iCACE,aAFM,QAGN,wMAIE,6CANJ,uCACE,aAFM,QAGN,gOAIE,6CANJ,uCACE,aAFM,QAGN,gOAIE,6CANJ,qCACE,aAFM,QAGN,wNAIE,6CAEN,mClBsBA,cDwBa,ICvBb,UDPO,OmBdP,qClBuBA,UDXO,QmBVP,mClBuBA,UDdO,OmBNP,2CACE,cACA,WACF,qCACE,eACA,WAIF,kBACE,cnBgCa,SmB/Bb,gDACA,iDACF,iBACE,6BACA,yBACA,gBACA,eACA,gBAEJ,UAEE,cACA,eACA,eACA,QlB7C2B,mBkB8C3B,gBACA,sBACE,WAxDkB,KAyDlB,WAxDkB,IAyDpB,gBACE,eAEF,yBACE,YC/DJ,iBACE,eACA,qBACA,iBACA,kBACA,6BACE,eACF,6BACE,MpBFW,QoBGb,4FAEE,MpBHW,QoBIX,mBAOF,crB6HE,YqB5HwB,KCpB5B,QACE,qBACA,eACA,kBACA,mBACA,0BACE,OpBDa,MoBGb,kDAEE,arBYS,QDkIX,MsB7IgB,QACd,UAEF,0BACE,crBwDW,SDyEb,asBhI2B,IAC7B,eAEE,eACA,cACA,cACA,eACA,aACA,2BACE,aACF,uEAEE,arBfS,QqBgBX,+BtBmHA,csBlH2B,MAC3B,yBACE,YACA,UACA,gCACE,iBAGJ,wDACE,arBjCS,QqBsCT,oCACE,aAHI,KAIN,wBACE,aALI,KAMJ,iEAEE,qBACF,kIAIE,8CAXJ,oCACE,aAHI,QAIN,wBACE,aALI,QAMJ,iEAEE,kBACF,kIAIE,2CAXJ,oCACE,aAHI,QAIN,wBACE,aALI,QAMJ,iEAEE,qBACF,kIAIE,8CAXJ,mCACE,aAHI,QAIN,uBACE,aALI,QAMJ,+DAEE,qBACF,8HAIE,2CAXJ,sCACE,aAHI,QAIN,0BACE,aALI,QAMJ,qEAEE,qBACF,0IAIE,4CAXJ,mCACE,aAHI,QAIN,uBACE,aALI,QAMJ,+DAEE,qBACF,8HAIE,6CAXJ,mCACE,aAHI,QAIN,uBACE,aALI,QAMJ,+DAEE,qBACF,8HAIE,6CAXJ,sCACE,aAHI,QAIN,0BACE,aALI,QAMJ,qEAEE,qBACF,0IAIE,6CAXJ,sCACE,aAHI,QAIN,0BACE,aALI,QAMJ,qEAEE,qBACF,0IAIE,6CAXJ,qCACE,aAHI,QAIN,yBACE,aALI,QAMJ,mEAEE,qBACF,sIAIE,6CAER,iBpBbA,cDwBa,ICvBb,UDPO,OqBqBP,kBpBZA,UDXO,QqByBP,iBpBZA,UDdO,OqB8BL,2BACE,arB1DS,QqB2Db,qBACE,WACA,4BACE,WAEF,0BAEE,aACA,kBtB6EF,MsB5EgB,OACd,WACA,eACF,kCACE,UrB1CG,OqB2CL,mCACE,UrB9CG,QqB+CL,kCACE,UrBjDG,OsBtBT,MAEE,oBACA,aACA,2BACA,kBAMI,yBACE,iBAJI,KAKJ,yBACA,MALW,QAQX,mEACE,yBACA,yBACA,MAXS,QAcX,mEACE,yBACA,0CACA,MAjBS,QAoBX,mEACE,yBACA,yBACA,MAvBS,QAEb,yBACE,iBAJI,QAKJ,yBACA,MALW,KAQX,mEACE,yBACA,yBACA,MAXS,KAcX,mEACE,yBACA,uCACA,MAjBS,KAoBX,mEACE,sBACA,yBACA,MAvBS,KAEb,yBACE,iBAJI,QAKJ,yBACA,MALW,eAQX,mEACE,sBACA,yBACA,MAXS,eAcX,mEACE,yBACA,0CACA,MAjBS,eAoBX,mEACE,yBACA,yBACA,MAvBS,eAEb,wBACE,iBAJI,QAKJ,yBACA,MALW,KAQX,iEACE,yBACA,yBACA,MAXS,KAcX,iEACE,yBACA,uCACA,MAjBS,KAoBX,iEACE,yBACA,yBACA,MAvBS,KAEb,2BACE,iBAJI,QAKJ,yBACA,MALW,KAQX,uEACE,yBACA,yBACA,MAXS,KAcX,uEACE,yBACA,wCACA,MAjBS,KAoBX,uEACE,yBACA,yBACA,MAvBS,KAEb,wBACE,iBAJI,QAKJ,yBACA,MALW,KAQX,iEACE,yBACA,yBACA,MAXS,KAcX,iEACE,yBACA,yCACA,MAjBS,KAoBX,iEACE,yBACA,yBACA,MAvBS,KAEb,wBACE,iBAJI,QAKJ,yBACA,MALW,KAQX,iEACE,yBACA,yBACA,MAXS,KAcX,iEACE,yBACA,yCACA,MAjBS,KAoBX,iEACE,yBACA,yBACA,MAvBS,KAEb,2BACE,iBAJI,QAKJ,yBACA,MALW,KAQX,uEACE,yBACA,yBACA,MAXS,KAcX,uEACE,yBACA,yCACA,MAjBS,KAoBX,uEACE,yBACA,yBACA,MAvBS,KAEb,2BACE,iBAJI,QAKJ,yBACA,MALW,eAQX,uEACE,yBACA,yBACA,MAXS,eAcX,uEACE,yBACA,yCACA,MAjBS,eAoBX,uEACE,yBACA,yBACA,MAvBS,eAEb,0BACE,iBAJI,QAKJ,yBACA,MALW,KAQX,qEACE,yBACA,yBACA,MAXS,KAcX,qEACE,yBACA,yCACA,MAjBS,KAoBX,qEACE,yBACA,yBACA,MAvBS,KAyBjB,eACE,UtBVK,OsBWP,gBACE,UtBdK,QsBgBH,+BACE,eACN,eACE,UtBpBK,OsBsBH,8BACE,eAGJ,yBACE,6BACA,0BACF,0BACE,4BACA,yBAEA,kCACE,kBACF,mCACE,aAEJ,2BACE,sBACF,yBACE,sBACA,YACA,gBACF,0BACE,uBACF,0BACE,aACA,YACA,8BACE,eAEF,uCACE,eAEF,wCACE,eAEF,uCACE,eAEF,kCACE,0BACF,mCACE,0BACA,uBACN,kBACE,uBAEA,+BACE,WACF,8BACE,YACA,eACJ,eACE,yBACA,yBACE,0BACF,0BACE,0BACA,2BACA,SAEN,YACE,oBACA,aACA,eACA,2BACA,gBACA,kBAEE,4BACE,sBACA,MtB1HS,QsB2HX,6BACE,qBAEF,6BACE,yBACA,MtBhIS,QsBiIX,8BACE,qBAEN,YACE,YACA,OACA,UACA,aACA,kBACA,MACA,WAEF,qBAGE,atB5Ia,QsB6Ib,ctBlFO,IsBmFP,cACA,iBACA,kBACA,mBAEF,UACE,iBtBjJa,QsBkJb,MlB5JK,KkB8JP,WACE,atBxJa,QsByJb,aA1JuB,MA2JvB,aA1JuB,cA2JvB,cACA,UA3JoB,KA4JpB,gBACA,mBACA,uBAEF,WACE,mBACA,aACA,WACA,uBvB/BE,auBgCsB,KACxB,UACA,eACE,eC9KJ,OACE,cACA,cACA,UvB6BO,KuB5BP,YvBmCY,IuBlCZ,wBACE,mBAEF,gBACE,UvBwBK,OuBvBP,iBACE,UvBoBK,QuBnBP,gBACE,UvBiBK,OuBfT,MACE,cACA,UvBgBO,OuBfP,kBAGE,eACE,MAFM,KACR,eACE,MAFM,QACR,eACE,MAFM,QACR,cACE,MAFM,QACR,iBACE,MAFM,QACR,cACE,MAFM,QACR,cACE,MAFM,QACR,iBACE,MAFM,QACR,iBACE,MAFM,QACR,gBACE,MAFM,QAOV,wBACE,qBAEF,kBACE,aACA,2BAEE,4CxByGF,awBxG4B,KAExB,wNAGE,gBAEF,sMAII,6BACA,0BAKJ,mMAII,4BACA,yBAQF,iXAEE,UACF,kuBAIE,UACA,0yBACE,UACR,uCACE,YACA,cACJ,sCACE,uBACF,mCACE,yBAEA,gDACE,YACA,cACN,kBACE,aACA,2BACA,2BACE,cACA,4CACE,gBxB+CJ,awB9C4B,OAC1B,uCACE,YACA,cACJ,sCACE,uBACF,mCACE,yBACF,uCACE,eAEE,4HAEE,qBACJ,kDACE,uBACF,wDACE,gBxB9BN,2CwB+BA,qBAEI,cAGJ,oBACE,kBxBzCF,qCwBuCF,aAII,qBxBvCF,2CwBmCF,aAMI,aACA,YACA,cxBgBA,awBfwB,OACxB,iBACA,sBACE,UvB9FG,OuB+FH,mBACF,uBACE,mBACF,uBACE,UvBrGG,QuBsGH,mBACF,sB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eACF,0CACE,UACA,UACF,wCACE,UACA,UACF,yCACE,UACA,UACF,2CACE,UACA,UACF,0CACE,UACA,UACF,oDACE,gBACF,gDACE,qBACF,0CACE,gBACF,+CACE,qBACF,iDACE,gBACF,+CACE,gBACF,gDACE,gBACF,kDACE,gBACF,iDACE,gBAEA,gCACE,UACA,SACF,uCACE,eAJF,gCACE,UACA,oBACF,uCACE,0BAJF,gCACE,UACA,qBACF,uCACE,2BAJF,gCACE,UACA,UACF,uCACE,gBAJF,gCACE,UACA,qBACF,uCACE,2BAJF,gCACE,UACA,qBACF,uCACE,2BAJF,gCACE,UACA,UACF,uCACE,gBAJF,gCACE,UACA,qBACF,uCACE,2BAJF,gCACE,UACA,qBACF,uCACE,2BAJF,gCACE,UACA,UACF,uCACE,gBAJF,iCACE,UACA,qBACF,wCACE,2BAJF,iCACE,UACA,qBACF,wCACE,2BAJF,iCACE,UACA,WACF,wCACE,iBrCkBJ,qCqChBE,yBACE,UACF,uBACE,UACA,WACF,iCACE,UACA,UACF,6BACE,UACA,eACF,uBACE,UACA,UACF,4BACE,UACA,eACF,8BACE,UACA,UACF,4BACE,UACA,UACF,6BACE,UACA,UACF,+BACE,UACA,UACF,8BACE,UACA,UACF,wCACE,gBACF,oCACE,qBACF,8BACE,gBACF,mCACE,qBACF,qCACE,gBACF,mCACE,gBACF,oCACE,gBACF,sCACE,gBACF,qCACE,gBAEA,oBACE,UACA,SACF,2BACE,eAJF,oBACE,UACA,oBACF,2BACE,0BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,WACF,4BACE,kBrCnCN,2CqCqCE,2CAEE,UACF,uCAEE,UACA,WACF,2DAEE,UACA,UACF,mDAEE,UACA,eACF,uCAEE,UACA,UACF,iDAEE,UACA,eACF,qDAEE,UACA,UACF,iDAEE,UACA,UACF,mDAEE,UACA,UACF,uDAEE,UACA,UACF,qDAEE,UACA,UACF,yEAEE,gBACF,iEAEE,qBACF,qDAEE,gBACF,+DAEE,qBACF,mEAEE,gBACF,+DAEE,gBACF,iEAEE,gBACF,qEAEE,gBACF,mEAEE,gBAEA,iCAEE,UACA,SACF,+CAEE,eANF,iCAEE,UACA,oBACF,+CAEE,0BANF,iCAEE,UACA,qBACF,+CAEE,2BANF,iCAEE,UACA,UACF,+CAEE,gBANF,iCAEE,UACA,qBACF,+CAEE,2BANF,iCAEE,UACA,qBACF,+CAEE,2BANF,iCAEE,UACA,UACF,+CAEE,gBANF,iCAEE,UACA,qBACF,+CAEE,2BANF,iCAEE,UACA,qBACF,+CAEE,2BANF,iCAEE,UACA,UACF,+CAEE,gBANF,mCAEE,UACA,qBACF,iDAEE,2BANF,mCAEE,UACA,qBACF,iDAEE,2BANF,mCAEE,UACA,WACF,iDAEE,kBrC1GN,sCqC4GE,wBACE,UACF,sBACE,UACA,WACF,gCACE,UACA,UACF,4BACE,UACA,eACF,sBACE,UACA,UACF,2BACE,UACA,eACF,6BACE,UACA,UACF,2BACE,UACA,UACF,4BACE,UACA,UACF,8BACE,UACA,UACF,6BACE,UACA,UACF,uCACE,gBACF,mCACE,qBACF,6BACE,gBACF,kCACE,qBACF,oCACE,gBACF,kCACE,gBACF,mCACE,gBACF,qCACE,gBACF,oCACE,gBAEA,mBACE,UACA,SACF,0BACE,eAJF,mBACE,UACA,oBACF,0BACE,0BAJF,mBACE,UACA,qBACF,0BACE,2BAJF,mBACE,UACA,UACF,0BACE,gBAJF,mBACE,UACA,qBACF,0BACE,2BAJF,mBACE,UACA,qBACF,0BACE,2BAJF,mBACE,UACA,UACF,0BACE,gBAJF,mBACE,UACA,qBACF,0BACE,2BAJF,mBACE,UACA,qBACF,0BACE,2BAJF,mBACE,UACA,UACF,0BACE,gBAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,WACF,2BACE,kBrC/JN,sCqCiKE,0BACE,UACF,wBACE,UACA,WACF,kCACE,UACA,UACF,8BACE,UACA,eACF,wBACE,UACA,UACF,6BACE,UACA,eACF,+BACE,UACA,UACF,6BACE,UACA,UACF,8BACE,UACA,UACF,gCACE,UACA,UACF,+BACE,UACA,UACF,yCACE,gBACF,qCACE,qBACF,+BACE,gBACF,oCACE,qBACF,sCACE,gBACF,oCACE,gBACF,qCACE,gBACF,uCACE,gBACF,sCACE,gBAEA,qBACE,UACA,SACF,4BACE,eAJF,qBACE,UACA,oBACF,4BACE,0BAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,UACF,4BACE,gBAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,UACF,4BACE,gBAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,UACF,4BACE,gBAJF,sBACE,UACA,qBACF,6BACE,2BAJF,sBACE,UACA,qBACF,6BACE,2BAJF,sBACE,UACA,WACF,6BACE,kBrCzMJ,sCqC2MA,6BACE,UACF,2BACE,UACA,WACF,qCACE,UACA,UACF,iCACE,UACA,eACF,2BACE,UACA,UACF,gCACE,UACA,eACF,kCACE,UACA,UACF,gCACE,UACA,UACF,iCACE,UACA,UACF,mCACE,UACA,UACF,kCACE,UACA,UACF,4CACE,gBACF,wCACE,qBACF,kCACE,gBACF,uCACE,qBACF,yCACE,gBACF,uCACE,gBACF,wCACE,gBACF,0CACE,gBACF,yCACE,gBAEA,wBACE,UACA,SACF,+BACE,eAJF,wBACE,UACA,oBACF,+BACE,0BAJF,wBACE,UACA,qBACF,+BACE,2BAJF,wBACE,UACA,UACF,+BACE,gBAJF,wBACE,UACA,qBACF,+BACE,2BAJF,wBACE,UACA,qBACF,+BACE,2BAJF,wBACE,UACA,UACF,+BACE,gBAJF,wBACE,UACA,qBACF,+BACE,2BAJF,wBACE,UACA,qBACF,+BACE,2BAJF,wBACE,UACA,UACF,+BACE,gBAJF,yBACE,UACA,qBACF,gCACE,2BAJF,yBACE,UACA,qBACF,gCACE,2BAJF,yBACE,UACA,WACF,gCACE,kBrCnPJ,sCqCqPA,yBACE,UACF,uBACE,UACA,WACF,iCACE,UACA,UACF,6BACE,UACA,eACF,uBACE,UACA,UACF,4BACE,UACA,eACF,8BACE,UACA,UACF,4BACE,UACA,UACF,6BACE,UACA,UACF,+BACE,UACA,UACF,8BACE,UACA,UACF,wCACE,gBACF,oCACE,qBACF,8BACE,gBACF,mCACE,qBACF,qCACE,gBACF,mCACE,gBACF,oCACE,gBACF,sCACE,gBACF,qCACE,gBAEA,oBACE,UACA,SACF,2BACE,eAJF,oBACE,UACA,oBACF,2BACE,0BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,qBACF,2BACE,2BAJF,oBACE,UACA,UACF,2BACE,gBAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,qBACF,4BACE,2BAJF,qBACE,UACA,WACF,4BACE,kBAER,SACE,qBACA,sBACA,oBACA,oBACE,uBACF,0BACE,qCAEF,qBACE,uBACF,oBACE,cACA,eACA,aACA,4BACE,SACA,qBACF,qCACE,qBACF,+BACE,gBACJ,mBACE,aACF,sBACE,eACF,sBACE,mBrCnXF,2CqCsXE,0BACE,crC3WJ,sCqC8WE,oBACE,cAGJ,qBACE,qBACA,wCACA,yCACA,6BACE,8BACA,+BAEA,0BACE,kBrC3YN,qCqC6YM,iCACE,mBrC1YR,2CqC4YM,iCACE,mBrCzYR,4DqC2YM,sCACE,mBrCxYR,sCqC0YM,gCACE,mBrCvYR,sCqCyYM,kCACE,mBrCrYN,6DqCuYI,uCACE,mBrC9XN,sCqCgYI,qCACE,mBrC5XN,6DqC8XI,0CACE,mBrCrXN,sCqCuXI,iCACE,mBA5BJ,0BACE,qBrC3YN,qCqC6YM,iCACE,sBrC1YR,2CqC4YM,iCACE,sBrCzYR,4DqC2YM,sCACE,sBrCxYR,sCqC0YM,gCACE,sBrCvYR,sCqCyYM,kCACE,sBrCrYN,6DqCuYI,uCACE,sBrC9XN,sCqCgYI,qCACE,sBrC5XN,6DqC8XI,0CACE,sBrCrXN,sCqCuXI,iCACE,sBA5BJ,0BACE,oBrC3YN,qCqC6YM,iCACE,qBrC1YR,2CqC4YM,iCACE,qBrCzYR,4DqC2YM,sCACE,qBrCxYR,sCqC0YM,gCACE,qBrCvYR,sCqCyYM,kCACE,qBrCrYN,6DqCuYI,uCACE,qBrC9XN,sCqCgYI,qCACE,qBrC5XN,6DqC8XI,0CACE,qBrCrXN,sCqCuXI,iCACE,qBA5BJ,0BACE,qBrC3YN,qCqC6YM,iCACE,sBrC1YR,2CqC4YM,iCACE,sBrCzYR,4DqC2YM,sCACE,sBrCxYR,sCqC0YM,gCACE,sBrCvYR,sCqCyYM,kCACE,sBrCrYN,6DqCuYI,uCACE,sBrC9XN,sCqCgYI,qCACE,sBrC5XN,6DqC8XI,0CACE,sBrCrXN,sCqCuXI,iCACE,sBA5BJ,0BACE,kBrC3YN,qCqC6YM,iCACE,mBrC1YR,2CqC4YM,iCACE,mBrCzYR,4DqC2YM,sCACE,mBrCxYR,sCqC0YM,gCACE,mBrCvYR,sCqCyYM,kCACE,mBrCrYN,6DqCuYI,uCACE,mBrC9XN,sCqCgYI,qCACE,mBrC5XN,6DqC8XI,0CACE,mBrCrXN,sCqCuXI,iCACE,mBA5BJ,0BACE,qBrC3YN,qCqC6YM,iCACE,sBrC1YR,2CqC4YM,iCACE,sBrCzYR,4DqC2YM,sCACE,sBrCxYR,sCqC0YM,gCACE,sBrCvYR,sCqCyYM,kCACE,sBrCrYN,6DqCuYI,uCACE,sBrC9XN,sCqCgYI,qCACE,sBrC5XN,6DqC8XI,0CACE,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,gCADF,MACE,8BAGF,MACE,8BACA,+BAGF,MACE,6BACA,gCAXF,MACE,2BADF,MACE,6BADF,MACE,8BADF,MACE,4BAGF,MACE,4BACA,6BAGF,MACE,2BACA,8BAXF,MACE,6BADF,MACE,+BADF,MACE,gCADF,MACE,8BAGF,MACE,8BACA,+BAGF,MACE,6BACA,gCAXF,MACE,2BADF,MACE,6BADF,MACE,8BADF,MACE,4BAGF,MACE,4BACA,6BAGF,MACE,2BACA,8BAXF,MACE,yBADF,MACE,2BADF,MACE,4BADF,MACE,0BAGF,MACE,0BACA,2BAGF,MACE,yBACA,4BAXF,MACE,8BADF,MACE,gCADF,MACE,iCADF,MACE,+BAGF,MACE,+BACA,gCAGF,MACE,8BACA,iCAXF,MACE,6BADF,MACE,+BADF,MACE,gCADF,MACE,8BAGF,MACE,8BACA,+BAGF,MACE,6BACA,gCAXF,MACE,8BADF,MACE,gCADF,MACE,iCADF,MACE,+BAGF,MACE,+BACA,gCAGF,MACE,8BACA,iCAXF,MACE,4BADF,MACE,8BADF,MACE,+BADF,MACE,6BAGF,MACE,6BACA,8BAGF,MACE,4BACA,+BAXF,MACE,8BADF,MACE,gCADF,MACE,iCADF,MACE,+BAGF,MACE,+BACA,gCAGF,MACE,8BACA,iCAXF,MACE,4BADF,MACE,8BADF,MACE,+BADF,MACE,6BAGF,MACE,6BACA,8BAGF,MACE,4BACA,+BCxBJ,WACE,0BADF,WACE,4BADF,WACE,0BADF,WACE,4BADF,WACE,6BADF,WACE,0BADF,WACE,4B7C6EJ,qC6C9EE,kBACE,0BADF,kBACE,4BADF,kBACE,0BADF,kBACE,4BADF,kBACE,6BADF,kBACE,0BADF,kBACE,6B7CiFJ,2C6ClFE,kBACE,0BADF,kBACE,4BADF,kBACE,0BADF,kBACE,4BADF,kBACE,6BADF,kBACE,0BADF,kBACE,6B7CyFJ,sC6C1FE,iBACE,0BADF,iBACE,4BADF,iBACE,0BADF,iBACE,4BADF,iBACE,6BADF,iBACE,0BADF,iBACE,6B7C6FJ,sC6C9FE,mBACE,0BADF,mBACE,4BADF,mBACE,0BADF,mBACE,4BADF,mBACE,6BADF,mBACE,0BADF,mBACE,6B7C4GF,sC6C7GA,sBACE,0BADF,sBACE,4BADF,sBACE,0BADF,sBACE,4BADF,sBACE,6BADF,sBACE,0BADF,sBACE,6B7C2HF,sC6C5HA,kBACE,0BADF,kBACE,4BADF,kBACE,0BADF,kBACE,4BADF,kBACE,6BADF,kBACE,0BADF,kBACE,6BAyBJ,mBACE,6BADF,oBACE,8BADF,eACE,2BADF,gBACE,4B7CmDF,qC6C/CE,0BACE,8B7CkDJ,2C6ChDE,0BACE,8B7CmDJ,4D6CjDE,+BACE,8B7CoDJ,sC6ClDE,yBACE,8B7CqDJ,sC6CnDE,2BACE,8B7CuDF,6D6CrDA,gCACE,8B7C8DF,sC6C5DA,8BACE,8B7CgEF,6D6C9DA,mCACE,8B7CuEF,sC6CrEA,0BACE,8B7CsBJ,qC6C/CE,2BACE,+B7CkDJ,2C6ChDE,2BACE,+B7CmDJ,4D6CjDE,gCACE,+B7CoDJ,sC6ClDE,0BACE,+B7CqDJ,sC6CnDE,4BACE,+B7CuDF,6D6CrDA,iCACE,+B7C8DF,sC6C5DA,+BACE,+B7CgEF,6D6C9DA,oCACE,+B7CuEF,sC6CrEA,2BACE,+B7CsBJ,qC6C/CE,sBACE,4B7CkDJ,2C6ChDE,sBACE,4B7CmDJ,4D6CjDE,2BACE,4B7CoDJ,sC6ClDE,qBACE,4B7CqDJ,sC6CnDE,uBACE,4B7CuDF,6D6CrDA,4BACE,4B7C8DF,sC6C5DA,0BACE,4B7CgEF,6D6C9DA,+BACE,4B7CuEF,sC6CrEA,sBACE,4B7CsBJ,qC6C/CE,uBACE,6B7CkDJ,2C6ChDE,uBACE,6B7CmDJ,4D6CjDE,4BACE,6B7CoDJ,sC6ClDE,sBACE,6B7CqDJ,sC6CnDE,wBACE,6B7CuDF,6D6CrDA,6BACE,6B7C8DF,sC6C5DA,2BACE,6B7CgEF,6D6C9DA,gCACE,6B7CuEF,sC6CrEA,uBACE,6BAEN,gBACE,qCAEF,cACE,oCAEF,cACE,oCAEF,WACE,6BAEF,uBACE,2BACF,wBACE,2BACF,wBACE,2BACF,0BACE,2BACF,sBACE,2BAEF,mBACE,mLAEF,qBACE,mLAEF,sBACE,mLAEF,qBACE,iCAEF,gBACE,iCC5FA,UACE,yB9C2EF,qC8CzEE,iBACE,0B9C4EJ,2C8C1EE,iBACE,0B9C6EJ,4D8C3EE,sBACE,0B9C8EJ,sC8C5EE,gBACE,0B9C+EJ,sC8C7EE,kBACE,0B9CiFF,6D8C/EA,uBACE,0B9CwFF,sC8CtFA,qBACE,0B9C0FF,6D8CxFA,0BACE,0B9CiGF,sC8C/FA,iBACE,0BA5BJ,SACE,wB9C2EF,qC8CzEE,gBACE,yB9C4EJ,2C8C1EE,gBACE,yB9C6EJ,4D8C3EE,qBACE,yB9C8EJ,sC8C5EE,eACE,yB9C+EJ,sC8C7EE,iBACE,yB9CiFF,6D8C/EA,sBACE,yB9CwFF,sC8CtFA,oBACE,yB9C0FF,6D8CxFA,yBACE,yB9CiGF,sC8C/FA,gBACE,yBA5BJ,WACE,0B9C2EF,qC8CzEE,kBACE,2B9C4EJ,2C8C1EE,kBACE,2B9C6EJ,4D8C3EE,uBACE,2B9C8EJ,sC8C5EE,iBACE,2B9C+EJ,sC8C7EE,mBACE,2B9CiFF,6D8C/EA,wBACE,2B9CwFF,sC8CtFA,sBACE,2B9C0FF,6D8CxFA,2BACE,2B9CiGF,sC8C/FA,kBACE,2BA5BJ,iBACE,gC9C2EF,qC8CzEE,wBACE,iC9C4EJ,2C8C1EE,wBACE,iC9C6EJ,4D8C3EE,6BACE,iC9C8EJ,sC8C5EE,uBACE,iC9C+EJ,sC8C7EE,yBACE,iC9CiFF,6D8C/EA,8BACE,iC9CwFF,sC8CtFA,4BACE,iC9C0FF,6D8CxFA,iCACE,iC9CiGF,sC8C/FA,wBACE,iCA5BJ,gBACE,+B9C2EF,qC8CzEE,uBACE,gC9C4EJ,2C8C1EE,uBACE,gC9C6EJ,4D8C3EE,4BACE,gC9C8EJ,sC8C5EE,sBACE,gC9C+EJ,sC8C7EE,wBACE,gC9CiFF,6D8C/EA,6BACE,gC9CwFF,sC8CtFA,2BACE,gC9C0FF,6D8CxFA,gCACE,gC9CiGF,sC8C/FA,uBACE,gCAEN,WACE,wBAEF,YACE,uBACA,iCACA,wBACA,2BACA,qBACA,6BACA,8BACA,uB9CmCA,qC8C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","file":"bds.css"} \ No newline at end of file diff --git a/bds/static/bds/images/logo.svg b/bds/static/bds/images/logo.svg deleted file mode 100644 index 15292488..00000000 --- a/bds/static/bds/images/logo.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/bds/static/bds/images/logo_square.svg b/bds/static/bds/images/logo_square.svg deleted file mode 100644 index 25c1aefd..00000000 --- a/bds/static/bds/images/logo_square.svg +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - diff --git a/bds/static/bds/images/logout.svg b/bds/static/bds/images/logout.svg deleted file mode 100644 index 12489bbd..00000000 --- a/bds/static/bds/images/logout.svg +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - -image/svg+xmlOpenclipart diff --git a/bds/static/bds/js/bds.js b/bds/static/bds/js/bds.js deleted file mode 100644 index 3c2fb49b..00000000 --- a/bds/static/bds/js/bds.js +++ /dev/null @@ -1,7 +0,0 @@ -$(function () { - // Close notifications when delete button is pressed - $(".notification .delete").on("click", function () { - $(this).parent().remove(); - }); - -}); \ No newline at end of file diff --git a/bds/static/src/sass/bds.scss b/bds/static/src/sass/bds.scss deleted file mode 100644 index dad677a8..00000000 --- a/bds/static/src/sass/bds.scss +++ /dev/null @@ -1,152 +0,0 @@ -// Compilation command : -// sass -I shared/static/src/ --watch bds/static/src/sass/bds.scss bds/static/bds/css/bds.css --style compressed - -$text: black; - -@import "bulma/bulma.sass"; - -$primary_color: #3e2263; -$background_color: #ddcecc; - -html, body { - background: $background_color; - font-size: 18px; -} - -a { - text-decoration: none; - color: #a82305; -} - -/* header */ - -#search-bar { - background-color: $primary_color; - padding: 0 1em; - margin-bottom: 0; - - #logout-mobile { - display: none; - } - - @include mobile { - display: flex; - flex-wrap: wrap; - - #logout-mobile { - display: flex; - flex-direction: row-reverse; - justify-content: space-between; - } - - #logout { - display: none; - } - - #search-input { - flex: 0 1 100%; - } - } - - // Workaround : `justify-content : ` pas encore supporté - // Voir https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content - & :first-child, & :last-child { - justify-content: space-between; - } - - & :last-child { - flex-direction: row-reverse; - } - - input { - border-radius: 0; - margin: 10px 0; - } -} - -/* Autocomplétion du BDS */ - -.highlight { - text-decoration: underline; - font-weight: bold; -} - -.yourlabs-autocomplete { - ul { - list-style: none; - padding: 0; - margin: 0; - - li { - height: 2em; - line-height: 2em; - padding: 0; - - a { - color: inherit; - } - } - - li.hilight { - background: #e8554e; - } - } -} - -.autocomplete-item { - display: block; - width: 480px; - height: 100%; - padding: 2px 10px; - margin: 0; -} - -.autocomplete-header { - background: #b497e1; -} - -.autocomplete-value, .autocomplete-new, .autocomplete-more { - background: white; -} - -/* --- Forms --- */ - -$button_color: lighten($primary_color, 10); - -input[type="submit"] { - background-color: $button_color; - color: findColorInvert($button_color); - - &:hover { - background-color: $primary_color; - color: findColorInvert($primary_color); - } -} - -.button.is-primary { - background-color: $button_color; - color: findColorInvert($button_color); - - &:hover { - background-color: $primary_color; - color: findColorInvert($primary_color); - } -} - -/* --- Message styling --- */ - -.notification { - padding: 0.5em 0; - font-size: 1.2em; - text-align: center; -} - -/* --- Modals --- */ - -.modal-card-head { - background-color: $primary_color; - - .modal-card-title { - color: findColorInvert($primary_color); - } -} diff --git a/bds/templates/bds/base.html b/bds/templates/bds/base.html deleted file mode 100644 index f456f6dc..00000000 --- a/bds/templates/bds/base.html +++ /dev/null @@ -1,50 +0,0 @@ -{% load staticfiles %} -{% load bulma_utils %} - - - - - {{ site.name }} - - - - - {# CSS #} - - - - {# Javascript #} - - - - - {% block extra_head %}{% endblock extra_head %} - - - {% include "bds/nav.html" %} - {% block layout %} -
    -
    -
    - - {% if messages %} - {% for message in messages %} -
    - {% if 'safe' in message.tags %} - {{ message|safe }} - {% else %} - {{ message }} - {% endif %} - -
    - {% endfor %} - {% endif %} - - {% block content %} - {% endblock content %} -
    -
    -
    - {% endblock layout %} - - diff --git a/bds/templates/bds/expired_members.html b/bds/templates/bds/expired_members.html deleted file mode 100644 index c5b53dbc..00000000 --- a/bds/templates/bds/expired_members.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "bds/base.html" %} - - -{% block content %} - -

    Liste des adhésions expirées

    - -{% if object_list %} -
    -
      - {% for p in object_list %} -
    • {{ p.user.first_name }} {{ p.user.last_name }} ({{ p.user.username }}), {{ p.get_cotisation_period_display }}
    • - {% endfor %} -
    -
    - - -{% endif %} - -{% endblock %} diff --git a/bds/templates/bds/forms/checkbox.html b/bds/templates/bds/forms/checkbox.html deleted file mode 100644 index 109ba75c..00000000 --- a/bds/templates/bds/forms/checkbox.html +++ /dev/null @@ -1,16 +0,0 @@ -
    - {% if field.auto_id %} - - {% endif %} - {% for error in field.errors %} - {{ error }} - {% endfor %} - - {% if field.help_text %} -

    - {{ field.help_text|safe }} -

    - {% endif %} -
    \ No newline at end of file diff --git a/bds/templates/bds/forms/field.html b/bds/templates/bds/forms/field.html deleted file mode 100644 index 132d6520..00000000 --- a/bds/templates/bds/forms/field.html +++ /dev/null @@ -1,33 +0,0 @@ -{% load bulma_utils %} - -
    - {% if field|is_checkbox %} - - {% include "bds/forms/checkbox.html" with field=field %} - - {% elif field|is_radio %} - - {% include "bds/forms/radio.html" with field=field %} - - {% elif field|is_input %} - - {% include "bds/forms/input.html" with field=field %} - - {% elif field|is_textarea %} - - {% include "bds/forms/textarea.html" with field=field %} - - {% elif field|is_select %} - - {% include "bds/forms/select.html" with field=field %} - - {% elif field|is_file %} - - {% include "bds/forms/file.html" with field=field %} - - {% else %} - - {% include "bds/forms/other.html" with field=field %} - - {% endif %} -
    \ No newline at end of file diff --git a/bds/templates/bds/forms/file.html b/bds/templates/bds/forms/file.html deleted file mode 100644 index 65c249ef..00000000 --- a/bds/templates/bds/forms/file.html +++ /dev/null @@ -1,31 +0,0 @@ -{% load bulma_utils %} -{% load i18n %} - - - -
    - - - - {% for error in field.errors %} - {{ error }} - {% endfor %} - - {% if field.help_text %} -

    - {{ field.help_text|safe }} -

    - {% endif %} -
    \ No newline at end of file diff --git a/bds/templates/bds/forms/form.html b/bds/templates/bds/forms/form.html deleted file mode 100644 index fdc9dc00..00000000 --- a/bds/templates/bds/forms/form.html +++ /dev/null @@ -1,22 +0,0 @@ -{% if errors %} - {% if form.non_field_errors %} -
    -
    - -
    -
    - {% for non_field_error in form.non_field_errors %} - {{ non_field_error }} - {% endfor %} -
    -
    - {% endif %} -{% endif %} - -{% for field in form.hidden_fields %} - {{ field }} -{% endfor %} - -{% for field in form.visible_fields %} - {% include 'bds/forms/field.html' %} -{% endfor %} \ No newline at end of file diff --git a/bds/templates/bds/forms/input.html b/bds/templates/bds/forms/input.html deleted file mode 100644 index 51c6f252..00000000 --- a/bds/templates/bds/forms/input.html +++ /dev/null @@ -1,19 +0,0 @@ -{% load bulma_utils %} - - - -
    - {{ field|bulmafy:'input' }} - - {% for error in field.errors %} - {{ error }} - {% endfor %} - - {% if field.help_text %} -

    - {{ field.help_text|safe }} -

    - {% endif %} -
    \ No newline at end of file diff --git a/bds/templates/bds/forms/other.html b/bds/templates/bds/forms/other.html deleted file mode 100644 index 4e1cdc8e..00000000 --- a/bds/templates/bds/forms/other.html +++ /dev/null @@ -1,19 +0,0 @@ -{% if field.auto_id %} - -{% endif %} - -
    - {{ field }} - - {% for error in field.errors %} - {{ error }} - {% endfor %} - - {% if field.help_text %} -

    - {{ field.help_text|safe }} -

    - {% endif %} -
    \ No newline at end of file diff --git a/bds/templates/bds/forms/radio.html b/bds/templates/bds/forms/radio.html deleted file mode 100644 index d2e3b141..00000000 --- a/bds/templates/bds/forms/radio.html +++ /dev/null @@ -1,24 +0,0 @@ -{% if field.auto_id %} - -{% endif %} - -
    - {% for choice in field %} - - {% endfor %} - - {% for error in field.errors %} - {{ error }} - {% endfor %} - - {% if field.help_text %} -

    - {{ field.help_text|safe }} -

    - {% endif %} -
    \ No newline at end of file diff --git a/bds/templates/bds/forms/select.html b/bds/templates/bds/forms/select.html deleted file mode 100644 index 2a064311..00000000 --- a/bds/templates/bds/forms/select.html +++ /dev/null @@ -1,21 +0,0 @@ -{% load bulma_utils %} - - - -
    - - {{ field }} - - - {% for error in field.errors %} - {{ error }} - {% endfor %} - - {% if field.help_text %} -

    - {{ field.help_text|safe }} -

    - {% endif %} -
    \ No newline at end of file diff --git a/bds/templates/bds/forms/textarea.html b/bds/templates/bds/forms/textarea.html deleted file mode 100644 index c5bc50f4..00000000 --- a/bds/templates/bds/forms/textarea.html +++ /dev/null @@ -1,19 +0,0 @@ -{% load bulma_utils %} - - - -
    - {{ field|bulmafy:'textarea' }} - - {% for error in field.errors %} - {{ error }} - {% endfor %} - - {% if field.help_text %} -

    - {{ field.help_text|safe }} -

    - {% endif %} -
    \ No newline at end of file diff --git a/bds/templates/bds/home.html b/bds/templates/bds/home.html deleted file mode 100644 index aedbaa25..00000000 --- a/bds/templates/bds/home.html +++ /dev/null @@ -1,62 +0,0 @@ -{% extends "bds/base.html" %} -{% load bulma_utils %} - -{% block layout %} -
    -
    -
    -
    -
    -

    {{ member_count }}

    - adhérent·e·s -
    -
    -
    -
    -
    -
    - - {% if messages %} - {% for message in messages %} -
    - {% if 'safe' in message.tags %} - {{ message|safe }} - {% else %} - {{ message }} - {% endif %} - -
    - {% endfor %} - {% endif %} -
    - Bienvenue sur GestioBDS ! - -
    -
    - - Télécharger la liste des membres (CSV) - - Liste des adhésions expirées ({{ nb_expired }}) - -
    -
    - - Le site est encore en développement. -
    - Suivez notre avancement sur - - cette milestone sur le gitlab de l'ENS. -
    - Faites vos remarques par mail à - klub-dev@ens.fr - ou en ouvrant une - - issue. -
    -
    -
    -
    -{% endblock layout %} - - - diff --git a/bds/templates/bds/nav.html b/bds/templates/bds/nav.html deleted file mode 100644 index ff167189..00000000 --- a/bds/templates/bds/nav.html +++ /dev/null @@ -1,57 +0,0 @@ -{% load i18n %} -{% load static %} - - - - diff --git a/bds/templates/bds/search_results.html b/bds/templates/bds/search_results.html deleted file mode 100644 index fec629df..00000000 --- a/bds/templates/bds/search_results.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "shared/search_results.html" %} -{% load i18n %} - -{% block extra_section %} -
  • - {% if not results %} - - {% trans "Aucune correspondance trouvée" %} - - {% else %} - - {% trans "Pas dans la liste ?" %} - - {% endif %} -
  • -
  • - - {% trans "Créer un compte" %} - -
  • -{% endblock %} diff --git a/bds/templates/bds/user_create.html b/bds/templates/bds/user_create.html deleted file mode 100644 index a36874cb..00000000 --- a/bds/templates/bds/user_create.html +++ /dev/null @@ -1,33 +0,0 @@ -{% extends "bds/base.html" %} -{% load i18n %} - - -{% block content %} - -{% for form in forms.values %} - {% for error in form.non_field_errors %} -
    - {{ error }} -
    - {% endfor %} -{% endfor %} - -

    {% trans "Création d'un profil" %}

    - -
    -
    - {% csrf_token %} - - {% for form in forms.values %} - {% include "bds/forms/form.html" with form=form errors=False %} - {% endfor %} - -
    -

    - -

    -
    -
    -
    - -{% endblock %} diff --git a/bds/templates/bds/user_update.html b/bds/templates/bds/user_update.html deleted file mode 100644 index 1f54abe2..00000000 --- a/bds/templates/bds/user_update.html +++ /dev/null @@ -1,104 +0,0 @@ -{% extends "bds/base.html" %} -{% load i18n %} - - -{% block content %} - -{% for form in forms.values %} -{% for error in form.non_field_errors %} -
    - {{ error }} -
    -{% endfor %} -{% endfor %} - -

    {% trans "Modification du profil " %}{{ view.user.username }}

    - -
    -
    - {% csrf_token %} - - {% for form in forms.values %} - {% include "bds/forms/form.html" with form=form errors=False %} - {% endfor %} -
    - -
    - {% csrf_token %} -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    - - - - - - -{% endblock %} diff --git a/bds/templatetags/bulma_utils.py b/bds/templatetags/bulma_utils.py deleted file mode 100644 index e0e15e2f..00000000 --- a/bds/templatetags/bulma_utils.py +++ /dev/null @@ -1,74 +0,0 @@ -from django import forms, template - -register = template.Library() - - -@register.filter -def widget_type(field): - return field.field.widget - - -@register.filter -def is_select(field): - return isinstance(field.field.widget, forms.Select) - - -@register.filter -def is_multiple_select(field): - return isinstance(field.field.widget, forms.SelectMultiple) - - -@register.filter -def is_textarea(field): - return isinstance(field.field.widget, forms.Textarea) - - -@register.filter -def is_input(field): - return isinstance( - field.field.widget, - ( - forms.TextInput, - forms.NumberInput, - forms.EmailInput, - forms.PasswordInput, - forms.URLInput, - ), - ) - - -@register.filter -def is_checkbox(field): - return isinstance(field.field.widget, forms.CheckboxInput) - - -@register.filter -def is_multiple_checkbox(field): - return isinstance(field.field.widget, forms.CheckboxSelectMultiple) - - -@register.filter -def is_radio(field): - return isinstance(field.field.widget, forms.RadioSelect) - - -@register.filter -def is_file(field): - return isinstance(field.field.widget, forms.FileInput) - - -@register.filter -def bulmafy(field, css_class): - if len(field.errors) > 0: - css_class += " is-danger" - field_classes = field.field.widget.attrs.get("class", "") - field_classes += f" {css_class}" - return field.as_widget(attrs={"class": field_classes}) - - -@register.filter -def bulma_message_tag(tag): - if tag == "error": - return "danger" - - return tag diff --git a/bds/tests/__init__.py b/bds/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bds/tests/test_views.py b/bds/tests/test_views.py deleted file mode 100644 index ef6139f4..00000000 --- a/bds/tests/test_views.py +++ /dev/null @@ -1,76 +0,0 @@ -from unittest import mock - -from django.conf import settings -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Permission -from django.test import Client, TestCase -from django.urls import reverse, reverse_lazy - -User = get_user_model() - - -def give_bds_buro_permissions(user: User) -> None: - perm = Permission.objects.get(content_type__app_label="bds", codename="is_team") - user.user_permissions.add(perm) - - -def login_url(next=None): - login_url = reverse_lazy(settings.LOGIN_URL) - if next is None: - return login_url - else: - return "{}?next={}".format(login_url, next) - - -class TestHomeView(TestCase): - @mock.patch("gestioncof.signals.messages") - def test_get(self, mock_messages): - user = User.objects.create_user(username="random_user") - give_bds_buro_permissions(user) - self.client.force_login( - user, backend="django.contrib.auth.backends.ModelBackend" - ) - resp = self.client.get(reverse("bds:home")) - self.assertEquals(resp.status_code, 200) - - -class TestRegistrationView(TestCase): - @mock.patch("gestioncof.signals.messages") - def test_get_autocomplete(self, mock_messages): - user = User.objects.create_user(username="toto") - url = reverse("bds:autocomplete") + "?q=foo" - client = Client() - - # Anonymous GET - resp = client.get(url) - self.assertRedirects(resp, login_url(next=url)) - - # Logged-in but unprivileged GET - client.force_login(user, backend="django.contrib.auth.backends.ModelBackend") - resp = client.get(url) - self.assertEquals(resp.status_code, 403) - - # Burô user GET - give_bds_buro_permissions(user) - resp = client.get(url) - self.assertEquals(resp.status_code, 200) - - @mock.patch("gestioncof.signals.messages") - def test_get(self, mock_messages): - user = User.objects.create_user(username="toto") - url = reverse("bds:user.update", args=(user.id,)) - client = Client() - - # Anonymous GET - resp = client.get(url) - self.assertRedirects(resp, login_url(next=url)) - - # Logged-in but unprivileged GET - client.force_login(user, backend="django.contrib.auth.backends.ModelBackend") - resp = client.get(url) - self.assertEquals(resp.status_code, 403) - - # Burô user GET - give_bds_buro_permissions(user) - resp = client.get(url) - self.assertEquals(resp.status_code, 200) diff --git a/bds/urls.py b/bds/urls.py deleted file mode 100644 index 95065773..00000000 --- a/bds/urls.py +++ /dev/null @@ -1,24 +0,0 @@ -from django.urls import path - -from bds import views - -app_name = "bds" -urlpatterns = [ - path("", views.Home.as_view(), name="home"), - path("autocomplete", views.BDSAutocompleteView.as_view(), name="autocomplete"), - path("user/update/", views.UserUpdateView.as_view(), name="user.update"), - path("user/create/", views.UserCreateView.as_view(), name="user.create"), - path( - "user/create/", - views.UserCreateView.as_view(), - name="user.create.fromclipper", - ), - path("user/delete/", views.UserDeleteView.as_view(), name="user.delete"), - path("members", views.export_members, name="export.members"), - path( - "members/expired", - views.ResetMembershipListView.as_view(), - name="members.expired", - ), - path("members/reset", views.ResetMembershipView.as_view(), name="members.reset"), -] diff --git a/bds/views.py b/bds/views.py deleted file mode 100644 index 5fc4f316..00000000 --- a/bds/views.py +++ /dev/null @@ -1,184 +0,0 @@ -import csv - -from django.contrib import messages -from django.contrib.auth import get_user_model -from django.contrib.auth.decorators import permission_required -from django.contrib.auth.models import Permission -from django.http import HttpResponse -from django.shortcuts import get_object_or_404 -from django.urls import reverse, reverse_lazy -from django.utils.translation import gettext_lazy as _ -from django.views.generic import DeleteView, ListView, RedirectView, TemplateView - -from bds.autocomplete import bds_search -from bds.forms import ProfileForm, UserForm, UserFromClipperForm, UserFromScratchForm -from bds.mixins import MultipleFormView, StaffRequiredMixin -from bds.models import BDSProfile -from shared.views import AutocompleteView - -User = get_user_model() - - -class BDSAutocompleteView(StaffRequiredMixin, AutocompleteView): - template_name = "bds/search_results.html" - search_composer = bds_search - - -class Home(StaffRequiredMixin, TemplateView): - template_name = "bds/home.html" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["member_count"] = BDSProfile.objects.filter(is_member=True).count() - context["nb_expired"] = BDSProfile.expired_members().count() - return context - - -class UserUpdateView(StaffRequiredMixin, MultipleFormView): - template_name = "bds/user_update.html" - - form_classes = { - "user": UserForm, - "profile": ProfileForm, - } - - def get_user_initial(self): - return {"is_buro": self.get_user_instance().has_perm("bds.is_team")} - - def dispatch(self, request, *args, **kwargs): - self.user = get_object_or_404(User, pk=self.kwargs["pk"]) - return super().dispatch(request, *args, **kwargs) - - def get_user_instance(self): - return self.user - - def get_profile_instance(self): - return getattr(self.user, "bds", None) - - def get_success_url(self): - return reverse("bds:user.update", args=(self.user.pk,)) - - def form_valid(self, forms): - user = forms["user"].save() - profile = forms["profile"].save(commit=False) - perm = Permission.objects.get(content_type__app_label="bds", codename="is_team") - if forms["user"].cleaned_data["is_buro"]: - user.user_permissions.add(perm) - else: - user.user_permissions.remove(perm) - profile.user = user - profile.save() - messages.success(self.request, _("Profil mis à jour avec succès !")) - - return super().form_valid(forms) - - def form_invalid(self, forms): - messages.error(self.request, _("Veuillez corriger les erreurs ci-dessous")) - return super().form_invalid(forms) - - -class UserCreateView(StaffRequiredMixin, MultipleFormView): - template_name = "bds/user_create.html" - - def get_form_classes(self): - profile_class = ProfileForm - - if "clipper" in self.kwargs: - user_class = UserFromClipperForm - else: - user_class = UserFromScratchForm - - return {"user": user_class, "profile": profile_class} - - def get_user_initial(self): - if "clipper" in self.kwargs: - clipper = self.kwargs["clipper"] - email = self.request.GET.get("mail", "{}@clipper.ens.fr".format(clipper)) - fullname = self.request.GET.get("fullname", None) - - if fullname: - # Heuristique : le premier mot est le prénom - first_name = fullname.split()[0] - last_name = " ".join(fullname.split()[1:]) - else: - first_name = "" - last_name = "" - - return { - "username": clipper, - "email": email, - "first_name": first_name, - "last_name": last_name, - } - else: - return {} - - def get_success_url(self): - return reverse("bds:user.update", args=(self.user.pk,)) - - def form_valid(self, forms): - # On redéfinit self.user pour get_success_url - self.user = forms["user"].save() - profile = forms["profile"].save(commit=False) - profile.user = self.user - profile.save() - messages.success(self.request, _("Profil créé avec succès !")) - - return super().form_valid(forms) - - def form_invalid(self, forms): - messages.error(self.request, _("Veuillez corriger les erreurs ci-dessous")) - return super().form_invalid(forms) - - -class UserDeleteView(StaffRequiredMixin, DeleteView): - model = User - success_url = reverse_lazy("bds:home") - success_message = "Profil supprimé avec succès !" - - def delete(self, request, *args, **kwargs): - # SuccessMessageMixin does not work with DeleteView, see : - # https://code.djangoproject.com/ticket/21926 - messages.success(request, self.success_message) - - return super().delete(request, *args, **kwargs) - - -class ResetMembershipListView(StaffRequiredMixin, ListView): - model = BDSProfile - template_name = "bds/expired_members.html" - - def get_queryset(self): - return BDSProfile.expired_members() - - -class ResetMembershipView(StaffRequiredMixin, RedirectView): - url = reverse_lazy("bds:members.expired") - - def get(self, request, *args, **kwargs): - qs = BDSProfile.expired_members() - nb = qs.count() - - qs.update(cotisation_period="NO", is_member=False, mails_bds=False) - - messages.success(request, f"{nb} adhésions réinitialisées") - - return super().get(request, *args, **kwargs) - - -@permission_required("bds.is_team") -def export_members(request): - response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = "attachment; filename=membres_bds.csv" - - writer = csv.writer(response) - for profile in BDSProfile.objects.filter(is_member=True).all(): - user = profile.user - bits = [ - user.username, - user.get_full_name(), - user.email, - ] - writer.writerow([str(bit) for bit in bits]) - - return response diff --git a/clubs/__init__.py b/clubs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/clubs/admin.py b/clubs/admin.py deleted file mode 100644 index 8ff3cd00..00000000 --- a/clubs/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib import admin - -from clubs.models import Club - -admin.site.register(Club) diff --git a/clubs/apps.py b/clubs/apps.py deleted file mode 100644 index a7d4b8e0..00000000 --- a/clubs/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class ClubsConfig(AppConfig): - name = "clubs" diff --git a/clubs/migrations/0001_initial.py b/clubs/migrations/0001_initial.py deleted file mode 100644 index 8b9d60bc..00000000 --- a/clubs/migrations/0001_initial.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 2.2.6 on 2019-10-06 17:57 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - initial = True - - dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] - - operations = [ - migrations.CreateModel( - name="Club", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "name", - models.CharField( - max_length=1000, unique=True, verbose_name="nom du club" - ), - ), - ( - "description", - models.TextField(blank=True, verbose_name="description"), - ), - ( - "respos", - models.ManyToManyField( - blank=True, - to=settings.AUTH_USER_MODEL, - verbose_name="responsables du club", - ), - ), - ], - ) - ] diff --git a/clubs/migrations/__init__.py b/clubs/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/clubs/models.py b/clubs/models.py deleted file mode 100644 index e407bbfd..00000000 --- a/clubs/models.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.contrib.auth import get_user_model -from django.db import models -from django.utils.translation import gettext_lazy as _ - -User = get_user_model() - - -class Club(models.Model): - name = models.CharField(_("nom du club"), max_length=1000, unique=True) - description = models.TextField(_("description"), blank=True) - respos = models.ManyToManyField( - User, verbose_name=_("responsables du club"), blank=True - ) - - def __str__(self): - return self.name diff --git a/clubs/views.py b/clubs/views.py deleted file mode 100644 index e69de29b..00000000 diff --git a/events/__init__.py b/events/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/events/admin.py b/events/admin.py deleted file mode 100644 index 26cf0433..00000000 --- a/events/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib import admin - -from events.models import Event - -admin.site.register(Event) diff --git a/events/apps.py b/events/apps.py deleted file mode 100644 index 60b376ce..00000000 --- a/events/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class EventsConfig(AppConfig): - name = "events" diff --git a/events/migrations/0001_event.py b/events/migrations/0001_event.py deleted file mode 100644 index 4b70d087..00000000 --- a/events/migrations/0001_event.py +++ /dev/null @@ -1,66 +0,0 @@ -# Generated by Django 2.2.6 on 2019-10-05 12:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - initial = True - - dependencies = [] - - operations = [ - migrations.CreateModel( - name="Event", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("title", models.CharField(max_length=200, verbose_name="titre")), - ("location", models.CharField(max_length=200, verbose_name="lieu")), - ( - "start_date", - models.DateTimeField( - blank=True, null=True, verbose_name="date de début" - ), - ), - ( - "end_date", - models.DateTimeField( - blank=True, null=True, verbose_name="date de fin" - ), - ), - ( - "description", - models.TextField(blank=True, verbose_name="description"), - ), - ( - "image", - models.ImageField( - blank=True, - null=True, - upload_to="imgs/events/", - verbose_name="image", - ), - ), - ( - "registration_open", - models.BooleanField( - default=True, verbose_name="inscriptions ouvertes" - ), - ), - ( - "old", - models.BooleanField( - default=False, verbose_name="archiver (événement fini)" - ), - ), - ], - options={"verbose_name": "événement", "verbose_name_plural": "événements"}, - ) - ] diff --git a/events/migrations/0002_event_subscribers.py b/events/migrations/0002_event_subscribers.py deleted file mode 100644 index b8980a78..00000000 --- a/events/migrations/0002_event_subscribers.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.2.6 on 2019-10-05 13:03 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("events", "0001_event"), - ] - - operations = [ - migrations.AddField( - model_name="event", - name="subscribers", - field=models.ManyToManyField( - to=settings.AUTH_USER_MODEL, verbose_name="inscrit⋅e⋅s" - ), - ) - ] diff --git a/events/migrations/0003_options_and_extra_fields.py b/events/migrations/0003_options_and_extra_fields.py deleted file mode 100644 index 0e993e7b..00000000 --- a/events/migrations/0003_options_and_extra_fields.py +++ /dev/null @@ -1,198 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-22 14:54 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("events", "0002_event_subscribers"), - ] - - operations = [ - migrations.CreateModel( - name="ExtraField", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "name", - models.CharField( - max_length=200, verbose_name="champ d'événement supplémentaire" - ), - ), - ( - "field_type", - models.CharField( - choices=[ - ("shorttext", "texte court (une ligne)"), - ("longtext", "texte long (plusieurs lignes)"), - ], - max_length=9, - verbose_name="type de champ", - ), - ), - ], - ), - migrations.CreateModel( - name="Option", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "name", - models.CharField(max_length=200, verbose_name="option d'événement"), - ), - ( - "multi_choices", - models.BooleanField(default=False, verbose_name="choix multiples"), - ), - ], - options={ - "verbose_name": "option d'événement", - "verbose_name_plural": "options d'événement", - }, - ), - migrations.CreateModel( - name="OptionChoice", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("choice", models.CharField(max_length=200, verbose_name="choix")), - ( - "option", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="choices", - to="events.Option", - ), - ), - ], - options={ - "verbose_name": "choix d'option d'événement", - "verbose_name_plural": "choix d'option d'événement", - }, - ), - migrations.CreateModel( - name="Registration", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ], - options={ - "verbose_name": "inscription à un événement", - "verbose_name_plural": "inscriptions à un événement", - }, - ), - migrations.RemoveField(model_name="event", name="subscribers"), - migrations.AddField( - model_name="event", - name="subscribers", - field=models.ManyToManyField( - through="events.Registration", - to=settings.AUTH_USER_MODEL, - verbose_name="inscrit⋅e⋅s", - ), - ), - migrations.AddField( - model_name="registration", - name="event", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="events.Event" - ), - ), - migrations.AddField( - model_name="registration", - name="options_choices", - field=models.ManyToManyField(to="events.OptionChoice"), - ), - migrations.AddField( - model_name="registration", - name="user", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL - ), - ), - migrations.AddField( - model_name="option", - name="event", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="options", - to="events.Event", - ), - ), - migrations.CreateModel( - name="ExtraFieldContent", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("content", models.TextField(verbose_name="contenu du champ")), - ( - "field", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="events.ExtraField", - ), - ), - ( - "registration", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="extra_info", - to="events.Registration", - ), - ), - ], - options={ - "verbose_name": "contenu d'un champ événement supplémentaire", - "verbose_name_plural": "contenus d'un champ événement supplémentaire", - }, - ), - migrations.AddField( - model_name="extrafield", - name="event", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="extra_fields", - to="events.Event", - ), - ), - ] diff --git a/events/migrations/0004_unique_constraints.py b/events/migrations/0004_unique_constraints.py deleted file mode 100644 index 4e73a8a8..00000000 --- a/events/migrations/0004_unique_constraints.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 2.2.12 on 2020-05-20 15:41 - -from django.conf import settings -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("events", "0003_options_and_extra_fields"), - ] - - operations = [ - migrations.AlterUniqueTogether( - name="extrafield", - unique_together={("event", "name")}, - ), - migrations.AlterUniqueTogether( - name="extrafieldcontent", - unique_together={("field", "registration")}, - ), - migrations.AlterUniqueTogether( - name="option", - unique_together={("event", "name")}, - ), - migrations.AlterUniqueTogether( - name="optionchoice", - unique_together={("option", "choice")}, - ), - migrations.AlterUniqueTogether( - name="registration", - unique_together={("event", "user")}, - ), - ] diff --git a/events/migrations/__init__.py b/events/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/events/models.py b/events/models.py deleted file mode 100644 index 7b536c86..00000000 --- a/events/models.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -Event framework for GestioCOF and GestioBDS. - -The events implemented in this module provide two types of customisations to event -creators (the COF and BDS staff): options and extra (text) fields. - -Options -------- - -An option is an extra field in the registration form with a predefined list of available -choices. Any number of options can be added to an event. - -For instance, a typical use-case is events where meals are served to participants -with different possible menus, say: vegeterian / vegan / without pork / etc. This -example can be implemented with an `Option(name="menu")` and an `OptionChoice` for each -available menu. - -In this example, the choice was exclusive: participants can only chose one menu. For -situations, where multiple choices can be made at the same time, use the `multi_choices` -flag. - -Extra fields ------------- - -Extra fields can also be added to the registration form that can receive arbitrary text. -Typically, this can be a "remark" field (prefer the LONGTEXT option in this case) or -small form entries such as "phone number" or "emergency contact" (prefer the SHORTTEXT -option in this case). -""" - -from django.contrib.auth import get_user_model -from django.core.exceptions import ValidationError -from django.db import models -from django.utils.translation import gettext_lazy as _ - -User = get_user_model() - - -class Event(models.Model): - title = models.CharField(_("titre"), max_length=200) - location = models.CharField(_("lieu"), max_length=200) - start_date = models.DateTimeField(_("date de début"), blank=True, null=True) - end_date = models.DateTimeField(_("date de fin"), blank=True, null=True) - description = models.TextField(_("description"), blank=True) - image = models.ImageField( - _("image"), blank=True, null=True, upload_to="imgs/events/" - ) - registration_open = models.BooleanField(_("inscriptions ouvertes"), default=True) - old = models.BooleanField(_("archiver (événement fini)"), default=False) - subscribers = models.ManyToManyField( - User, through="Registration", verbose_name=_("inscrit⋅e⋅s") - ) - - class Meta: - verbose_name = _("événement") - verbose_name_plural = _("événements") - - def __str__(self): - return self.title - - -class Option(models.Model): - """Extra form fields with a limited set of available choices. - - The available choices are given by `OptionChoice`s (see below). A typical use-case - is events where the participants have the choice between different menus (e.g. - vegan / vegetarian / without-pork / etc). - """ - - event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="options") - name = models.CharField(_("option d'événement"), max_length=200) - multi_choices = models.BooleanField(_("choix multiples"), default=False) - - class Meta: - verbose_name = _("option d'événement") - verbose_name_plural = _("options d'événement") - unique_together = [["event", "name"]] - - def __str__(self): - return self.name - - -class OptionChoice(models.Model): - """A possible choice for an event option.""" - - option = models.ForeignKey(Option, on_delete=models.CASCADE, related_name="choices") - choice = models.CharField(_("choix"), max_length=200) - - class Meta: - verbose_name = _("choix d'option d'événement") - verbose_name_plural = _("choix d'option d'événement") - unique_together = [["option", "choice"]] - - def __str__(self): - return self.choice - - -class ExtraField(models.Model): - """Extra event field receiving arbitrary text. - - Extra text field that can be added by event creators to the event registration form. - Typical examples are "remarks" fields (of type LONGTEXT) or more specific fields - such as "emergency contact" (of type SHORTTEXT probably?). - """ - - LONGTEXT = "longtext" - SHORTTEXT = "shorttext" - - FIELD_TYPE = [ - (SHORTTEXT, _("texte court (une ligne)")), - (LONGTEXT, _("texte long (plusieurs lignes)")), - ] - - event = models.ForeignKey( - Event, on_delete=models.CASCADE, related_name="extra_fields" - ) - name = models.CharField(_("champ d'événement supplémentaire"), max_length=200) - field_type = models.CharField(_("type de champ"), max_length=9, choices=FIELD_TYPE) - - class Meta: - unique_together = [["event", "name"]] - - -class ExtraFieldContent(models.Model): - """Value entered in an extra field.""" - - field = models.ForeignKey(ExtraField, on_delete=models.CASCADE) - registration = models.ForeignKey( - "Registration", on_delete=models.CASCADE, related_name="extra_info" - ) - content = models.TextField(_("contenu du champ")) - - def clean(self): - if self.registration.event != self.field.event: - raise ValidationError( - _("Inscription et champ texte incohérents pour ce commentaire") - ) - - class Meta: - verbose_name = _("contenu d'un champ événement supplémentaire") - verbose_name_plural = _("contenus d'un champ événement supplémentaire") - unique_together = [["field", "registration"]] - - def __str__(self): - max_length = 50 - if len(self.content) > max_length: - return self.content[: max_length - 1] + "…" - else: - return self.content - - -class Registration(models.Model): - """A user registration to an event.""" - - event = models.ForeignKey(Event, on_delete=models.CASCADE) - user = models.ForeignKey(User, on_delete=models.CASCADE) - options_choices = models.ManyToManyField(OptionChoice) - - def clean(self): - if not all((ch.option.event == self.event for ch in self.options_choices)): - raise ValidationError( - _("Choix d'options incohérents avec l'événement pour cette inscription") - ) - - class Meta: - verbose_name = _("inscription à un événement") - verbose_name_plural = _("inscriptions à un événement") - unique_together = [["event", "user"]] - - def __str__(self): - return "inscription de {} à {}".format(self.user, self.event) diff --git a/events/tests/__init__.py b/events/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/events/tests/test_views.py b/events/tests/test_views.py deleted file mode 100644 index 611f1871..00000000 --- a/events/tests/test_views.py +++ /dev/null @@ -1,161 +0,0 @@ -from unittest import mock - -from django.conf import settings -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Permission -from django.test import Client, TestCase -from django.urls import reverse - -from events.models import ( - Event, - ExtraField, - ExtraFieldContent, - Option, - OptionChoice, - Registration, -) -from shared.tests.mixins import CSVResponseMixin - -User = get_user_model() - - -def make_user(name): - return User.objects.create_user(username=name, password=name) - - -def make_staff_user(name): - view_event_perm = Permission.objects.get( - codename="view_event", - content_type__app_label="events", - ) - user = make_user(name) - user.user_permissions.add(view_event_perm) - return user - - -class MessagePatch: - def setUp(self): - # Signals handlers on login/logout send messages. - # Due to the way the Django' test Client performs login, this raise an - # error. As workaround, we mock the Django' messages module. - patcher_messages = mock.patch("gestioncof.signals.messages") - patcher_messages.start() - self.addCleanup(patcher_messages.stop) - - -class CSVExportAccessTest(MessagePatch, TestCase): - def setUp(self): - super().setUp() - - self.staff = make_staff_user("staff") - self.u1 = make_user("toto") - self.event = Event.objects.create(title="test_event", location="somewhere") - self.url = reverse("events:csv-participants", args=[self.event.id]) - - def test_get(self): - client = Client() - client.force_login( - self.staff, backend="django.contrib.auth.backends.ModelBackend" - ) - r = client.get(self.url) - self.assertEqual(r.status_code, 200) - - def test_anonymous(self): - client = Client() - r = client.get(self.url) - login_url = "{}?next={}".format(reverse(settings.LOGIN_URL), self.url) - self.assertRedirects(r, login_url, fetch_redirect_response=False) - - def test_unauthorised(self): - client = Client() - client.force_login(self.u1, backend="django.contrib.auth.backends.ModelBackend") - r = client.get(self.url) - self.assertEqual(r.status_code, 403) - - -class CSVExportContentTest(MessagePatch, CSVResponseMixin, TestCase): - def setUp(self): - super().setUp() - - self.event = Event.objects.create(title="test_event", location="somewhere") - self.url = reverse("events:csv-participants", args=[self.event.id]) - - self.u1 = User.objects.create_user( - username="toto_foo", first_name="toto", last_name="foo", email="toto@a.b" - ) - self.u2 = User.objects.create_user( - username="titi_bar", first_name="titi", last_name="bar", email="titi@a.b" - ) - self.staff = make_staff_user("staff") - self.client = Client() - self.client.force_login( - self.staff, backend="django.contrib.auth.backends.ModelBackend" - ) - - def test_simple_event(self): - self.event.subscribers.set([self.u1, self.u2]) - - response = self.client.get(self.url) - - self.assertCSVEqual( - response, - [ - { - "username": "toto_foo", - "prénom": "toto", - "nom de famille": "foo", - "email": "toto@a.b", - }, - { - "username": "titi_bar", - "prénom": "titi", - "nom de famille": "bar", - "email": "titi@a.b", - }, - ], - ) - - def test_complex_event(self): - registration = Registration.objects.create(event=self.event, user=self.u1) - # Set up some options - option1 = Option.objects.create( - event=self.event, name="abc", multi_choices=False - ) - option2 = Option.objects.create( - event=self.event, name="def", multi_choices=True - ) - OptionChoice.objects.bulk_create( - [ - OptionChoice(option=option1, choice="a"), - OptionChoice(option=option1, choice="b"), - OptionChoice(option=option1, choice="c"), - OptionChoice(option=option2, choice="d"), - OptionChoice(option=option2, choice="e"), - OptionChoice(option=option2, choice="f"), - ] - ) - registration.options_choices.set( - OptionChoice.objects.filter(choice__in=["d", "f"]) - ) - registration.options_choices.add(OptionChoice.objects.get(choice="a")) - # And an extra field - field = ExtraField.objects.create(event=self.event, name="remarks") - ExtraFieldContent.objects.create( - field=field, registration=registration, content="hello" - ) - - response = self.client.get(self.url) - self.assertCSVEqual( - response, - [ - { - "username": "toto_foo", - "prénom": "toto", - "nom de famille": "foo", - "email": "toto@a.b", - "abc": "a", - "def": "d & f", - "remarks": "hello", - } - ], - ) diff --git a/events/urls.py b/events/urls.py deleted file mode 100644 index df7a8c70..00000000 --- a/events/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from django.urls import path - -from events import views - -app_name = "events" -urlpatterns = [ - path( - "csv/participants/", - views.participants_csv, - name="csv-participants", - ) -] diff --git a/events/views.py b/events/views.py deleted file mode 100644 index b47ae76f..00000000 --- a/events/views.py +++ /dev/null @@ -1,55 +0,0 @@ -import csv - -from django.contrib.auth.decorators import login_required, permission_required -from django.http import HttpResponse -from django.shortcuts import get_object_or_404 -from django.utils.text import slugify - -from events.models import Event, Registration - - -@login_required -@permission_required("events.view_event", raise_exception=True) -def participants_csv(request, event_id): - event = get_object_or_404(Event, id=event_id) - - # Create a CSV response - filename = "{}-participants.csv".format(slugify(event.title)) - response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = 'attachment; filename="{}"'.format(filename) - writer = csv.writer(response) - - # The first line of the file is a header - header = ["username", "email", "prénom", "nom de famille"] - options_names = list(event.options.values_list("name", flat=True).order_by("id")) - header += options_names - extra_fields = list( - event.extra_fields.values_list("name", flat=True).order_by("id") - ) - header += extra_fields - writer.writerow(header) - - # Next, one line by registered user - registrations = Registration.objects.filter(event=event) - for registration in registrations: - user = registration.user - row = [user.username, user.email, user.first_name, user.last_name] - - # Options - all_choices = registration.options_choices.values_list("choice", flat=True) - options_choices = [ - " & ".join(all_choices.filter(option__id=id).order_by("id")) - for id in event.options.values_list("id", flat=True).order_by("id") - ] - row += options_choices - # Extra info - extra_info = list( - registration.extra_info.values_list("content", flat=True).order_by( - "field__id" - ) - ) - row += extra_info - - writer.writerow(row) - - return response diff --git a/gestioasso/__init__.py b/gestioasso/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioasso/apps.py b/gestioasso/apps.py deleted file mode 100644 index 2beb01c7..00000000 --- a/gestioasso/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib.staticfiles.apps import StaticFilesConfig - - -class IgnoreSrcStaticFilesConfig(StaticFilesConfig): - ignore_patterns = StaticFilesConfig.ignore_patterns + ["src/**"] diff --git a/gestioasso/asgi.py b/gestioasso/asgi.py deleted file mode 100644 index 773acaa0..00000000 --- a/gestioasso/asgi.py +++ /dev/null @@ -1,8 +0,0 @@ -import os - -from channels.asgi import get_channel_layer - -if "DJANGO_SETTINGS_MODULE" not in os.environ: - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings") - -channel_layer = get_channel_layer() diff --git a/gestioasso/locale/__init__.py b/gestioasso/locale/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioasso/locale/en/__init__.py b/gestioasso/locale/en/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioasso/locale/en/formats.py b/gestioasso/locale/en/formats.py deleted file mode 100644 index abd616db..00000000 --- a/gestioasso/locale/en/formats.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- encoding: utf-8 -*- - -""" -English formatting. -""" - -from __future__ import unicode_literals - -DATETIME_FORMAT = r"l N j, Y \a\t P" -DATE_FORMAT = r"l N j, Y" -TIME_FORMAT = r"P" diff --git a/gestioasso/locale/fr/__init__.py b/gestioasso/locale/fr/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioasso/locale/fr/formats.py b/gestioasso/locale/fr/formats.py deleted file mode 100644 index 6fd5b5ba..00000000 --- a/gestioasso/locale/fr/formats.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Formats français. -""" - -DATETIME_FORMAT = r"l j F Y \à H\hi" -DATE_FORMAT = r"l j F Y" -TIME_FORMAT = r"H\hi" diff --git a/gestioasso/routing.py b/gestioasso/routing.py deleted file mode 100644 index 3c2e5718..00000000 --- a/gestioasso/routing.py +++ /dev/null @@ -1,3 +0,0 @@ -from channels.routing import include - -routing = [include("kfet.routing.routing", path=r"^/ws/k-fet")] diff --git a/gestioasso/settings/.gitignore b/gestioasso/settings/.gitignore deleted file mode 100644 index 21425062..00000000 --- a/gestioasso/settings/.gitignore +++ /dev/null @@ -1 +0,0 @@ -secret.py diff --git a/gestioasso/settings/__init__.py b/gestioasso/settings/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioasso/settings/bds_prod.py b/gestioasso/settings/bds_prod.py deleted file mode 100644 index 361ed7cb..00000000 --- a/gestioasso/settings/bds_prod.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -Settings de production de GestioBDS. - -Surcharge les settings définis dans common.py -""" - -from .common import * # NOQA -from .common import INSTALLED_APPS - -# --- -# BDS-only Django settings -# --- - -ALLOWED_HOSTS = ["bds.ens.fr", "www.bds.ens.fr", "dev.cof.ens.fr"] - -INSTALLED_APPS += ["bds", "events", "clubs", "authens"] - -STATIC_ROOT = "/srv/bds.ens.fr/public/gestion2/static" -STATIC_URL = "/gestion2/static/" -MEDIA_ROOT = "/srv/bds.ens.fr/gestion2/media" -MEDIA_URL = "/gestion2/media/" - - -# --- -# Auth-related stuff -# --- - -AUTHENTICATION_BACKENDS = [ - "django.contrib.auth.backends.ModelBackend", - "authens.backends.ENSCASBackend", - "authens.backends.OldCASBackend", -] - -AUTHENS_USE_OLDCAS = False - -LOGIN_URL = "authens:login" -LOGIN_REDIRECT_URL = "bds:home" -LOGOUT_REDIRECT_URL = "bds:home" diff --git a/gestioasso/settings/cof_prod.py b/gestioasso/settings/cof_prod.py deleted file mode 100644 index 7e24f936..00000000 --- a/gestioasso/settings/cof_prod.py +++ /dev/null @@ -1,234 +0,0 @@ -""" -Settings de production de GestioCOF. - -Surcharge les settings définis dans common.py -""" - -import os -from datetime import timedelta - -from django.utils import timezone - -from .common import * # NOQA -from .common import ( - AUTHENTICATION_BACKENDS, - BASE_DIR, - INSTALLED_APPS, - MIDDLEWARE, - TEMPLATES, - import_secret, -) - -# --- -# COF-specific secrets -# --- - -REDIS_PASSWD = import_secret("REDIS_PASSWD") -REDIS_DB = import_secret("REDIS_DB") -REDIS_HOST = import_secret("REDIS_HOST") -REDIS_PORT = import_secret("REDIS_PORT") - -HCAPTCHA_SITEKEY = import_secret("HCAPTCHA_SITEKEY") -HCAPTCHA_SECRET = import_secret("HCAPTCHA_SECRET") -KFETOPEN_TOKEN = import_secret("KFETOPEN_TOKEN") - -# --- -# COF-only Django settings -# --- - -ALLOWED_HOSTS = ["cof.ens.fr", "www.cof.ens.fr", "dev.cof.ens.fr"] - -INSTALLED_APPS = ( - [ - "gestioncof", - # Must be before django admin - # https://github.com/infoportugal/wagtail-modeltranslation/issues/193 - "wagtail_modeltranslation", - "wagtail_modeltranslation.makemigrations", - "wagtail_modeltranslation.migrate", - "modeltranslation", - ] - + INSTALLED_APPS - + [ - "bda", - "petitscours", - "hcaptcha", - "kfet", - "kfet.open", - "channels", - "djconfig", - "wagtail.contrib.forms", - "wagtail.contrib.redirects", - "wagtail.embeds", - "wagtail.sites", - "wagtail.users", - "wagtail.snippets", - "wagtail.documents", - "wagtail.images", - "wagtail.search", - "wagtail.admin", - "wagtail.core", - "wagtail.contrib.modeladmin", - "wagtail.contrib.routable_page", - "wagtailmenus", - "modelcluster", - "taggit", - "kfet.auth", - "kfet.cms", - "gestioncof.cms", - "django_js_reverse", - ] -) - -MIDDLEWARE = ( - ["corsheaders.middleware.CorsMiddleware"] - + MIDDLEWARE - + [ - "djconfig.middleware.DjConfigMiddleware", - "wagtail.core.middleware.SiteMiddleware", - "wagtail.contrib.redirects.middleware.RedirectMiddleware", - ] -) - -TEMPLATES[0]["OPTIONS"]["context_processors"] += [ - "wagtailmenus.context_processors.wagtailmenus", - "djconfig.context_processors.config", - "gestioncof.shared.context_processor", - "kfet.auth.context_processors.temporary_auth", - "kfet.context_processors.config", -] - -STATIC_ROOT = os.path.join( - os.path.dirname(os.path.dirname(BASE_DIR)), "public", "gestion", "static" -) - -STATIC_URL = "/gestion/static/" -MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "media") -MEDIA_URL = "/gestion/media/" - -CORS_ORIGIN_WHITELIST = ("bda.ens.fr", "www.bda.ens.fr" "cof.ens.fr", "www.cof.ens.fr") - - -# --- -# Auth-related stuff -# --- - -AUTHENTICATION_BACKENDS = ( - [ - # Must be in first - "kfet.auth.backends.BlockFrozenAccountBackend" - ] - + AUTHENTICATION_BACKENDS - + [ - "gestioncof.shared.COFCASBackend", - "kfet.auth.backends.GenericBackend", - ] -) -LOGIN_URL = "cof-login" -LOGIN_REDIRECT_URL = "home" - -# --- -# Cache settings -# --- - -CACHES = { - "default": { - "BACKEND": "redis_cache.RedisCache", - "LOCATION": "redis://:{passwd}@{host}:{port}/{db}".format( - passwd=REDIS_PASSWD, host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB - ), - } -} - - -# --- -# Channels settings -# --- - -CHANNEL_LAYERS = { - "default": { - "BACKEND": "asgi_redis.RedisChannelLayer", - "CONFIG": { - "hosts": [ - ( - "redis://:{passwd}@{host}:{port}/{db}".format( - passwd=REDIS_PASSWD, - host=REDIS_HOST, - port=REDIS_PORT, - db=REDIS_DB, - ) - ) - ] - }, - "ROUTING": "gestioasso.routing.routing", - } -} - - -# --- -# reCAPTCHA settings -# https://github.com/praekelt/django-recaptcha -# -# Default settings authorize reCAPTCHA usage for local developement. -# Public and private keys are appended in the 'prod' module settings. -# --- - -NOCAPTCHA = True -RECAPTCHA_USE_SSL = True - - -# --- -# Wagtail settings -# --- - -WAGTAIL_SITE_NAME = "GestioCOF" -WAGTAIL_ENABLE_UPDATE_CHECK = False -TAGGIT_CASE_INSENSITIVE = True - - -# --- -# Django-js-reverse settings -# --- - -JS_REVERSE_JS_VAR_NAME = "django_urls" -# Quand on aura namespace les urls... -# JS_REVERSE_INCLUDE_ONLY_NAMESPACES = ['k-fet'] - - -# --- -# Mail config -# --- - -MAIL_DATA = { - "petits_cours": { - "FROM": "Le COF ", - "BCC": "archivescof@gmail.com", - "REPLYTO": "cof@ens.fr", - }, - "rappels": {"FROM": "Le BdA ", "REPLYTO": "Le BdA "}, - "rappel_negatif": { - "FROM": "La K-Fêt ", - "REPLYTO": "La K-Fêt ", - }, - "revente": { - "FROM": "BdA-Revente ", - "REPLYTO": "BdA-Revente ", - }, -} - -# --- -# kfet history limits -# --- - -# L'historique n'est accesible que d'aujourd'hui -# à aujourd'hui - KFET_HISTORY_DATE_LIMIT -KFET_HISTORY_DATE_LIMIT = timedelta(days=7) - -# Limite plus longue pour les chefs/trez -# (qui ont la permission kfet.access_old_history) -KFET_HISTORY_LONG_DATE_LIMIT = timedelta(days=30) - -# These accounts don't represent actual people and can be freely accessed -# Identification based on trigrammes -KFET_HISTORY_NO_DATE_LIMIT_TRIGRAMMES = ["LIQ", "#13"] -KFET_HISTORY_NO_DATE_LIMIT = timezone.datetime(1794, 10, 30) # AKA the distant past diff --git a/gestioasso/settings/common.py b/gestioasso/settings/common.py deleted file mode 100644 index cabe7000..00000000 --- a/gestioasso/settings/common.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -Settings par défaut et settings communs à GestioCOF et GestioBDS. -""" - -import os -import sys - -# --- -# Secrets -# --- - -try: - from . import secret -except ImportError: - raise ImportError( - "The secret.py file is missing.\n" - "For a development environment, simply copy secret_example.py" - ) - - -def import_secret(name): - """ - Shorthand for importing a value from the secret module and raising an - informative exception if a secret is missing. - """ - try: - return getattr(secret, name) - except AttributeError: - raise RuntimeError("Secret missing: {}".format(name)) - - -SECRET_KEY = import_secret("SECRET_KEY") -ADMINS = import_secret("ADMINS") -SERVER_EMAIL = import_secret("SERVER_EMAIL") -EMAIL_HOST = import_secret("EMAIL_HOST") - -DBNAME = import_secret("DBNAME") -DBUSER = import_secret("DBUSER") -DBPASSWD = import_secret("DBPASSWD") - -LDAP_SERVER_URL = import_secret("LDAP_SERVER_URL") - - -# --- -# Default Django settings -# --- - -DEBUG = False # False by default feels safer -TESTING = len(sys.argv) > 1 and sys.argv[1] == "test" -BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -INSTALLED_APPS = [ - "shared", - # Must be before 'django.contrib.admin'. - # https://django-autocomplete-light.readthedocs.io/en/master/install.html - "dal", - "dal_select2", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.sites", - "django.contrib.messages", - "django.contrib.admin", - "django.contrib.admindocs", - "gestioasso.apps.IgnoreSrcStaticFilesConfig", - "django_cas_ng", - "bootstrapform", - "widget_tweaks", -] - -MIDDLEWARE = [ - "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", - "django.middleware.security.SecurityMiddleware", - "django.middleware.locale.LocaleMiddleware", -] - -ROOT_URLCONF = "gestioasso.urls" - -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - "django.template.context_processors.i18n", - "django.template.context_processors.media", - "django.template.context_processors.static", - ] - }, - } -] - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql_psycopg2", - "NAME": DBNAME, - "USER": DBUSER, - "PASSWORD": DBPASSWD, - "HOST": os.environ.get("DBHOST", "localhost"), - } -} - -SITE_ID = 1 - - -# --- -# Internationalization -# https://docs.djangoproject.com/en/1.8/topics/i18n/ -# --- - -LANGUAGE_CODE = "fr-fr" -TIME_ZONE = "Europe/Paris" -USE_I18N = True -USE_L10N = True -USE_TZ = True -LANGUAGES = (("fr", "Français"), ("en", "English")) -FORMAT_MODULE_PATH = "gestioasso.locale" - - -# --- -# Auth-related stuff -# --- - -AUTHENTICATION_BACKENDS = ["django.contrib.auth.backends.ModelBackend"] - -CAS_SERVER_URL = "https://cas.eleves.ens.fr/" -CAS_VERSION = "2" -CAS_LOGIN_MSG = None -CAS_IGNORE_REFERER = True -CAS_REDIRECT_URL = "/" -CAS_EMAIL_FORMAT = "%s@clipper.ens.fr" diff --git a/gestioasso/settings/dev.py b/gestioasso/settings/dev.py deleted file mode 100644 index cd254b7a..00000000 --- a/gestioasso/settings/dev.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Settings utilisés dans la VM Vagrant. -Active toutes les applications (de GestioCOF et de GestioBDS). - -Surcharge les settings définis dans common.py -""" - -import os - -from . import bds_prod -from .cof_prod import * # NOQA -from .cof_prod import INSTALLED_APPS, MIDDLEWARE, TESTING - -# --- -# Merge COF and BDS configs -# --- - -for app in bds_prod.INSTALLED_APPS: - if app not in INSTALLED_APPS: - INSTALLED_APPS.append(app) - -# --- -# Tweaks for debug/local development -# --- - -ALLOWED_HOSTS = [] - -DEBUG = True -EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" - -if TESTING: - PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] - -STATIC_URL = "/static/" -STATIC_ROOT = "/srv/gestiocof/static" -MEDIA_URL = "/media/" -MEDIA_ROOT = "/srv/gestiocof/media" - - -# --- -# Debug tool bar -# --- - - -def show_toolbar(request): - """ - On active la debug-toolbar en mode développement local sauf : - - dans l'admin où ça ne sert pas à grand chose; - - si la variable d'environnement DJANGO_NO_DDT est à 1 → ça permet de la désactiver - sans modifier ce fichier en exécutant `export DJANGO_NO_DDT=1` dans le terminal - qui lance `./manage.py runserver`. - - Autre side effect de cette fonction : on ne fait pas la vérification de INTERNAL_IPS - que ferait la debug-toolbar par défaut, ce qui la fait fonctionner aussi à - l'intérieur de Vagrant (comportement non testé depuis un moment…) - """ - env_no_ddt = bool(os.environ.get("DJANGO_NO_DDT", None)) - return DEBUG and not env_no_ddt and not request.path.startswith("/admin/") - - -if not TESTING: - INSTALLED_APPS += ["debug_toolbar"] - MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE - DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar} diff --git a/gestioasso/settings/local.py b/gestioasso/settings/local.py deleted file mode 100644 index 5c8c2734..00000000 --- a/gestioasso/settings/local.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Settings utilisés lors d'un développement en local (dans un virtualenv). -Active toutes les applications (de GestioCOF et de GestioBDS). - -Surcharge les settings définis dans common.py -""" -import os - -from . import bds_prod -from .cof_prod import * # NOQA -from .cof_prod import BASE_DIR, INSTALLED_APPS, MIDDLEWARE, TESTING - -# --- -# Merge COF and BDS configs -# --- - -for app in bds_prod.INSTALLED_APPS: - if app not in INSTALLED_APPS: - INSTALLED_APPS.append(app) - -# --- -# Tweaks for debug/local development -# --- - -ALLOWED_HOSTS = [] - -DEBUG = True -EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" - -if TESTING: - PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] - -STATIC_URL = "/static/" -MEDIA_URL = "/media/" -MEDIA_ROOT = os.path.join(BASE_DIR, "media") - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(BASE_DIR, "db.sqlite3"), - } -} - -# Use the default cache backend for local development -CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}} - -# Use the default in memory asgi backend for local development -CHANNEL_LAYERS = { - "default": { - "BACKEND": "asgiref.inmemory.ChannelLayer", - "ROUTING": "gestioasso.routing.routing", - } -} - - -# --- -# Debug tool bar -# --- - - -def show_toolbar(request): - """ - On active la debug-toolbar en mode développement local sauf : - - dans l'admin où ça ne sert pas à grand chose; - - si la variable d'environnement DJANGO_NO_DDT est à 1 → ça permet de la désactiver - sans modifier ce fichier en exécutant `export DJANGO_NO_DDT=1` dans le terminal - qui lance `./manage.py runserver`. - - Autre side effect de cette fonction : on ne fait pas la vérification de INTERNAL_IPS - que ferait la debug-toolbar par défaut, ce qui la fait fonctionner aussi à - l'intérieur de Vagrant (comportement non testé depuis un moment…) - """ - env_no_ddt = bool(os.environ.get("DJANGO_NO_DDT", None)) - return DEBUG and not env_no_ddt and not request.path.startswith("/admin/") - - -if not TESTING: - INSTALLED_APPS += ["debug_toolbar"] - MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE - DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar} diff --git a/gestioasso/settings/secret_example.py b/gestioasso/settings/secret_example.py deleted file mode 100644 index b93aeb4f..00000000 --- a/gestioasso/settings/secret_example.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Secrets à re-définir en production. -""" - -SECRET_KEY = "q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah" -ADMINS = None -SERVER_EMAIL = "root@vagrant" -EMAIL_HOST = "localhost" - -DBUSER = "cof_gestion" -DBNAME = "cof_gestion" -DBPASSWD = "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4" - -REDIS_PASSWD = "dummy" -REDIS_PORT = 6379 -REDIS_DB = 0 -REDIS_HOST = "127.0.0.1" - -HCAPTCHA_SITEKEY = "10000000-ffff-ffff-ffff-000000000001" -HCAPTCHA_SECRET = "0x0000000000000000000000000000000000000000" - -EMAIL_HOST = None - -KFETOPEN_TOKEN = "plop" -LDAP_SERVER_URL = None diff --git a/gestioasso/urls.py b/gestioasso/urls.py deleted file mode 100644 index 10173fbb..00000000 --- a/gestioasso/urls.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -Fichier principal de configuration des urls du projet GestioCOF -""" -from django.conf import settings -from django.conf.urls.i18n import i18n_patterns -from django.conf.urls.static import static -from django.contrib import admin -from django.urls import include, path -from django.views.generic.base import RedirectView - -bds_is_alone = ( - "bds" in settings.INSTALLED_APPS and "gestioncof" not in settings.INSTALLED_APPS -) - -admin.autodiscover() -urlpatterns = [ - # Website administration (independent from installed apps) - path("admin/doc/", include("django.contrib.admindocs.urls")), - path("admin/", admin.site.urls), -] - -if not bds_is_alone: - # Redirection / → /gestion, only useful for developpers. - urlpatterns.append(path("", RedirectView.as_view(url="gestion/"))) - -# App-specific urls - -app_dict = { - "bds": "" if bds_is_alone else "bds/", - "kfet": "k-fet/", - # Below = GestioCOF → goes under gestion/ - "gestioncof": "gestion/", - "bda": "gestion/bda/", - "petitscours": "gestion/petitcours/", - "events": "gestion/event_v2/", # the events module is still experimental ! - "authens": "gestion/authens/", -} -for app_name, url_prefix in app_dict.items(): - if app_name in settings.INSTALLED_APPS: - urlpatterns += [path(url_prefix, include("{}.urls".format(app_name)))] - - -if "django_js_reverse" in settings.INSTALLED_APPS: - from django_js_reverse.views import urls_js - - urlpatterns += [ - path("jsreverse/", urls_js, name="js_reverse"), - ] - -if "debug_toolbar" in settings.INSTALLED_APPS: - import debug_toolbar - - urlpatterns += [path("__debug__/", include(debug_toolbar.urls))] - -if settings.DEBUG: - # Si on est en production, MEDIA_ROOT est servi par Apache. - # Il faut dire à Django de servir MEDIA_ROOT lui-même en développement. - urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) - - -# Wagtail URLs (wagtail.core urls must be last, as catch-all) -if "wagtail.core" in settings.INSTALLED_APPS: - from wagtail.admin import urls as wagtailadmin_urls - from wagtail.core import urls as wagtail_urls - from wagtail.documents import urls as wagtaildocs_urls - - urlpatterns += [ - path("cms/", include(wagtailadmin_urls)), - path("documents/", include(wagtaildocs_urls)), - ] - urlpatterns += i18n_patterns( - path("", include(wagtail_urls)), prefix_default_language=False - ) diff --git a/gestioasso/wsgi.py b/gestioasso/wsgi.py deleted file mode 100644 index bdd9a64c..00000000 --- a/gestioasso/wsgi.py +++ /dev/null @@ -1,6 +0,0 @@ -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings.bds_prod") -application = get_wsgi_application() diff --git a/gestioncof/__init__.py b/gestioncof/__init__.py deleted file mode 100644 index 3bb260b9..00000000 --- a/gestioncof/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = "gestioncof.apps.GestioncofConfig" diff --git a/gestioncof/admin.py b/gestioncof/admin.py deleted file mode 100644 index 89e4160d..00000000 --- a/gestioncof/admin.py +++ /dev/null @@ -1,310 +0,0 @@ -from dal.autocomplete import ModelSelect2 -from django import forms -from django.contrib import admin -from django.contrib.auth.admin import UserAdmin -from django.contrib.auth.models import Group, Permission, User -from django.db.models import Q -from django.urls import reverse -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ - -from gestioncof.models import ( - Club, - CofProfile, - Event, - EventCommentField, - EventOption, - EventOptionChoice, - EventRegistration, - Survey, - SurveyQuestion, - SurveyQuestionAnswer, -) -from petitscours.models import ( - PetitCoursAbility, - PetitCoursAttribution, - PetitCoursAttributionCounter, - PetitCoursDemande, - PetitCoursSubject, -) - - -def add_link_field(target_model="", field="", link_text=str, desc_text=str): - def add_link(cls): - reverse_name = target_model or cls.model.__name__.lower() - - def link(self, instance): - app_name = instance._meta.app_label - reverse_path = "admin:%s_%s_change" % (app_name, reverse_name) - link_obj = getattr(instance, field, None) or instance - if not link_obj.id: - return "" - url = reverse(reverse_path, args=(link_obj.id,)) - return mark_safe("%s" % (url, link_text(link_obj))) - - link.allow_tags = True - link.short_description = desc_text(reverse_name + " link") - cls.link = link - cls.readonly_fields = list(getattr(cls, "readonly_fields", [])) + ["link"] - return cls - - return add_link - - -class SurveyQuestionAnswerInline(admin.TabularInline): - model = SurveyQuestionAnswer - - -@add_link_field( - desc_text=lambda x: "Réponses", link_text=lambda x: "Éditer les réponses" -) -class SurveyQuestionInline(admin.TabularInline): - model = SurveyQuestion - - -class SurveyQuestionAdmin(admin.ModelAdmin): - search_fields = ("survey__title", "answer") - inlines = [SurveyQuestionAnswerInline] - - -class SurveyAdmin(admin.ModelAdmin): - search_fields = ("title", "details") - inlines = [SurveyQuestionInline] - - -class EventOptionChoiceInline(admin.TabularInline): - model = EventOptionChoice - - -@add_link_field(desc_text=lambda x: "Choix", link_text=lambda x: "Éditer les choix") -class EventOptionInline(admin.TabularInline): - model = EventOption - - -class EventCommentFieldInline(admin.TabularInline): - model = EventCommentField - - -class EventOptionAdmin(admin.ModelAdmin): - search_fields = ("event__title", "name") - inlines = [EventOptionChoiceInline] - - -class EventAdmin(admin.ModelAdmin): - search_fields = ("title", "location", "description") - inlines = [EventOptionInline, EventCommentFieldInline] - - -class CofProfileInline(admin.StackedInline): - model = CofProfile - inline_classes = ("collapse open",) - - -def ProfileInfo(field, short_description, boolean=False): - def getter(self): - try: - return getattr(self.profile, field) - except CofProfile.DoesNotExist: - return "" - - getter.short_description = short_description - getter.boolean = boolean - return getter - - -User.profile_phone = ProfileInfo("phone", "Téléphone") -User.profile_occupation = ProfileInfo("occupation", "Occupation") -User.profile_departement = ProfileInfo("departement", "Departement") -User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True) -User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BdA", True) -User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente", "ML BdA-R", True) - - -class UserProfileAdmin(UserAdmin): - def is_buro(self, obj): - try: - return obj.profile.is_buro - except CofProfile.DoesNotExist: - return False - - is_buro.short_description = "Membre du Buro" - is_buro.boolean = True - - def is_cof(self, obj): - try: - return obj.profile.is_cof - except CofProfile.DoesNotExist: - return False - - is_cof.short_description = "Membre du COF" - is_cof.boolean = True - - list_display = UserAdmin.list_display + ( - "profile_phone", - "profile_occupation", - "profile_mailing_cof", - "profile_mailing_bda", - "profile_mailing_bda_revente", - "is_cof", - "is_buro", - ) - list_display_links = ("username", "email", "first_name", "last_name") - list_filter = UserAdmin.list_filter + ( - "profile__is_cof", - "profile__is_buro", - "profile__mailing_cof", - "profile__mailing_bda", - ) - search_fields = UserAdmin.search_fields + ("profile__phone",) - inlines = [CofProfileInline] - - staff_fieldsets = [ - (None, {"fields": ["username", "password"]}), - (_("Personal info"), {"fields": ["first_name", "last_name", "email"]}), - ] - - def get_fieldsets(self, request, user=None): - if not request.user.is_superuser: - return self.staff_fieldsets - return super().get_fieldsets(request, user) - - def save_model(self, request, user, form, change): - cof_group, created = Group.objects.get_or_create(name="COF") - if created: - # Si le groupe COF n'était pas déjà dans la bdd - # On lui assigne les bonnes permissions - perms = Permission.objects.filter( - Q(content_type__app_label="gestioncof") - | Q(content_type__app_label="bda") - | (Q(content_type__app_label="auth") & Q(content_type__model="user")) - ) - cof_group.permissions.set(perms) - # On y associe les membres du Burô - cof_group.user_set.set(User.objects.filter(profile__is_buro=True)) - # Sauvegarde - cof_group.save() - # le Burô est staff et appartient au groupe COF - if user.profile.is_buro: - user.is_staff = True - user.groups.add(cof_group) - else: - user.is_staff = False - user.groups.remove(cof_group) - user.save() - - -# FIXME: This is absolutely horrible. -def user_str(self): - if self.first_name and self.last_name: - return "{} ({})".format(self.get_full_name(), self.username) - else: - return self.username - - -User.__str__ = user_str - - -class EventRegistrationAdminForm(forms.ModelForm): - class Meta: - widgets = {"user": ModelSelect2(url="cof-user-autocomplete")} - - -class EventRegistrationAdmin(admin.ModelAdmin): - form = EventRegistrationAdminForm - - list_display = ("__str__", "event", "user", "paid") - list_filter = ("paid",) - search_fields = ( - "user__username", - "user__first_name", - "user__last_name", - "user__email", - "event__title", - ) - - -class PetitCoursAbilityAdmin(admin.ModelAdmin): - list_display = ("user", "matiere", "niveau", "agrege") - search_fields = ( - "user__username", - "user__first_name", - "user__last_name", - "user__email", - "matiere__name", - "niveau", - ) - list_filter = ("matiere", "niveau", "agrege") - - -class PetitCoursAttributionAdmin(admin.ModelAdmin): - list_display = ("user", "demande", "matiere", "rank") - search_fields = ("user__username", "matiere__name") - - -class PetitCoursAttributionCounterAdmin(admin.ModelAdmin): - list_display = ("user", "matiere", "count") - list_filter = ("matiere",) - search_fields = ( - "user__username", - "user__first_name", - "user__last_name", - "user__email", - "matiere__name", - ) - actions = ["reset"] - actions_on_bottom = True - - def reset(self, request, queryset): - queryset.update(count=0) - - reset.short_description = "Remise à zéro du compteur" - - -class PetitCoursDemandeAdmin(admin.ModelAdmin): - list_display = ( - "name", - "email", - "agrege_requis", - "niveau", - "created", - "traitee", - "processed", - ) - list_filter = ("traitee", "niveau") - search_fields = ("name", "email", "phone", "lieu", "remarques") - readonly_fields = ("created",) - - -class ClubAdminForm(forms.ModelForm): - def clean(self): - cleaned_data = super().clean() - respos = cleaned_data.get("respos") - members = cleaned_data.get("membres") - for respo in respos.all(): - if respo not in members: - raise forms.ValidationError( - "Erreur : le respo %s n'est pas membre du club." - % respo.get_full_name() - ) - return cleaned_data - - -class ClubAdmin(admin.ModelAdmin): - list_display = ["name"] - form = ClubAdminForm - - -admin.site.register(Survey, SurveyAdmin) -admin.site.register(SurveyQuestion, SurveyQuestionAdmin) -admin.site.register(Event, EventAdmin) -admin.site.register(EventOption, EventOptionAdmin) -admin.site.unregister(User) -admin.site.register(User, UserProfileAdmin) -admin.site.register(CofProfile) -admin.site.register(Club, ClubAdmin) -admin.site.register(PetitCoursSubject) -admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin) -admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin) -admin.site.register(PetitCoursAttributionCounter, PetitCoursAttributionCounterAdmin) -admin.site.register(PetitCoursDemande, PetitCoursDemandeAdmin) -admin.site.register(EventRegistration, EventRegistrationAdmin) diff --git a/gestioncof/apps.py b/gestioncof/apps.py deleted file mode 100644 index 0ac33f93..00000000 --- a/gestioncof/apps.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.apps import AppConfig - - -class GestioncofConfig(AppConfig): - name = "gestioncof" - verbose_name = "Gestion des adhérents du COF" - - def ready(self): - from . import signals # noqa - - self.register_config() - - def register_config(self): - import djconfig - - from .forms import GestioncofConfigForm - - djconfig.register(GestioncofConfigForm) diff --git a/gestioncof/autocomplete.py b/gestioncof/autocomplete.py deleted file mode 100644 index 9570acb5..00000000 --- a/gestioncof/autocomplete.py +++ /dev/null @@ -1,58 +0,0 @@ -from django.contrib.auth import get_user_model -from django.db.models import Q -from django.urls import reverse -from django.utils.translation import gettext_lazy as _ - -from shared import autocomplete - -User = get_user_model() - - -class COFMemberSearch(autocomplete.ModelSearch): - model = User - search_fields = ["username", "first_name", "last_name"] - verbose_name = _("Membres du COF") - - def get_queryset_filter(self, *args, **kwargs): - qset_filter = super().get_queryset_filter(*args, **kwargs) - qset_filter &= Q(profile__is_cof=True) - return qset_filter - - def result_uuid(self, user): - return user.username - - def result_link(self, user): - return reverse("user-registration", args=(user.username,)) - - -class COFOthersSearch(autocomplete.ModelSearch): - model = User - search_fields = ["username", "first_name", "last_name"] - verbose_name = _("Non-membres du COF") - - def get_queryset_filter(self, *args, **kwargs): - qset_filter = super().get_queryset_filter(*args, **kwargs) - qset_filter &= Q(profile__is_cof=False) - return qset_filter - - def result_uuid(self, user): - return user.username - - def result_link(self, user): - return reverse("user-registration", args=(user.username,)) - - -class COFLDAPSearch(autocomplete.LDAPSearch): - def result_link(self, clipper): - return reverse("clipper-registration", args=(clipper.clipper, clipper.fullname)) - - -class COFAutocomplete(autocomplete.Compose): - search_units = [ - ("members", COFMemberSearch()), - ("others", COFOthersSearch()), - ("clippers", COFLDAPSearch()), - ] - - -cof_autocomplete = COFAutocomplete() diff --git a/gestioncof/cms/__init__.py b/gestioncof/cms/__init__.py deleted file mode 100644 index 043b644d..00000000 --- a/gestioncof/cms/__init__.py +++ /dev/null @@ -1 +0,0 @@ -default_app_config = "gestioncof.cms.apps.COFCMSAppConfig" diff --git a/gestioncof/cms/apps.py b/gestioncof/cms/apps.py deleted file mode 100644 index 3cd041cc..00000000 --- a/gestioncof/cms/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class COFCMSAppConfig(AppConfig): - name = "gestioncof.cms" - label = "cofcms" - verbose_name = "CMS COF" diff --git a/gestioncof/cms/fixtures/examplesite.json b/gestioncof/cms/fixtures/examplesite.json deleted file mode 100644 index 67e12161..00000000 --- a/gestioncof/cms/fixtures/examplesite.json +++ /dev/null @@ -1,696 +0,0 @@ -[ -{ - "model": "wagtailcore.page", - "pk": 27, - "fields": { - "path": "000100010002", - "depth": 3, - "numchild": 3, - "title": "Site du COF", - "title_fr": "Site du COF", - "title_en": null, - "draft_title": "Site du COF", - "slug": "site", - "slug_fr": "site", - "slug_en": null, - "content_type": [ - "cofcms", - "cofrootpage" - ], - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/", - "url_path_fr": "/global/site/", - "url_path_en": "/global/site/", - "owner": [ - "root" - ], - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": false, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T20:54:14.724Z", - "last_published_at": "2019-02-04T20:54:14.724Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 28, - "fields": { - "path": "0001000100020001", - "depth": 4, - "numchild": 0, - "title": "Pr\u00e9sentation", - "title_fr": "Pr\u00e9sentation", - "title_en": "Presentation", - "draft_title": "Pr\u00e9sentation", - "slug": "pr\u00e9sentation", - "slug_fr": "pr\u00e9sentation", - "slug_en": null, - "content_type": [ - "cofcms", - "cofpage" - ], - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/pr\u00e9sentation/", - "url_path_fr": "/global/site/pr\u00e9sentation/", - "url_path_en": "/global/site/pr\u00e9sentation/", - "owner": [ - "root" - ], - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": true, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T20:55:06.574Z", - "last_published_at": "2019-02-04T21:42:00.461Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 29, - "fields": { - "path": "0001000100020002", - "depth": 4, - "numchild": 2, - "title": "Actualit\u00e9s", - "title_fr": "Actualit\u00e9s", - "title_en": "News", - "draft_title": "Actualit\u00e9s", - "slug": "actualit\u00e9s", - "slug_fr": "actualit\u00e9s", - "slug_en": "news", - "content_type": [ - "cofcms", - "cofactuindexpage" - ], - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/actualit\u00e9s/", - "url_path_fr": "/global/site/actualit\u00e9s/", - "url_path_en": "/global/site/news/", - "owner": [ - "root" - ], - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": true, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T20:58:47.657Z", - "last_published_at": "2019-02-04T21:43:55.575Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 30, - "fields": { - "path": "00010001000200020001", - "depth": 5, - "numchild": 0, - "title": "Grosse teuf en K-F\u00eat", - "title_fr": "Grosse teuf en K-F\u00eat", - "title_en": "Big feast in K-F\u00eat", - "draft_title": "Grosse teuf en K-F\u00eat", - "slug": "grosse-teuf-en-k-f\u00eat", - "slug_fr": "grosse-teuf-en-k-f\u00eat", - "slug_en": "big-feast-in-k-f\u00eat", - "content_type": [ - "cofcms", - "cofactupage" - ], - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/actualit\u00e9s/grosse-teuf-en-k-f\u00eat/", - "url_path_fr": "/global/site/actualit\u00e9s/grosse-teuf-en-k-f\u00eat/", - "url_path_en": "/global/site/news/big-feast-in-k-f\u00eat/", - "owner": [ - "root" - ], - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": false, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T21:04:39.422Z", - "last_published_at": "2019-02-04T21:04:39.422Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 31, - "fields": { - "path": "00010001000200020002", - "depth": 5, - "numchild": 0, - "title": "Les 48h des Arts", - "title_fr": "Les 48h des Arts", - "title_en": null, - "draft_title": "Les 48h des Arts", - "slug": "les-48h-des-arts", - "slug_fr": "les-48h-des-arts", - "slug_en": null, - "content_type": [ - "cofcms", - "cofactupage" - ], - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/actualit\u00e9s/les-48h-des-arts/", - "url_path_fr": "/global/site/actualit\u00e9s/les-48h-des-arts/", - "url_path_en": "/global/site/news/les-48h-des-arts/", - "owner": [ - "root" - ], - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": false, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T21:05:27.190Z", - "last_published_at": "2019-02-04T21:05:27.190Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 32, - "fields": { - "path": "0001000100020003", - "depth": 4, - "numchild": 1, - "title": "Clubs", - "title_fr": "Clubs", - "title_en": null, - "draft_title": "Clubs", - "slug": "clubs", - "slug_fr": "clubs", - "slug_en": null, - "content_type": [ - "cofcms", - "cofdirectorypage" - ], - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/clubs/", - "url_path_fr": "/global/site/clubs/", - "url_path_en": "/global/site/clubs/", - "owner": [ - "root" - ], - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": true, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T21:44:23.382Z", - "last_published_at": "2019-02-04T21:44:23.382Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 33, - "fields": { - "path": "00010001000200030001", - "depth": 5, - "numchild": 0, - "title": "Arts Plastiques", - "title_fr": "Arts Plastiques", - "title_en": null, - "draft_title": "Arts Plastiques", - "slug": "arts-plastiques", - "slug_fr": "arts-plastiques", - "slug_en": null, - "content_type": [ - "cofcms", - "cofdirectoryentrypage" - ], - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/clubs/arts-plastiques/", - "url_path_fr": "/global/site/clubs/arts-plastiques/", - "url_path_en": "/global/site/clubs/arts-plastiques/", - "owner": [ - "root" - ], - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": false, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T21:48:58.013Z", - "last_published_at": "2019-02-04T21:48:58.013Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.collection", - "pk": 3, - "fields": { - "path": "00010002", - "depth": 2, - "numchild": 0, - "name": "COF" - } -}, -{ - "model": "cofcms.cofrootpage", - "pk": 27, - "fields": { - "introduction": "

    Bienvenue sur le site du COF !

    ", - "introduction_fr": "

    Bienvenue sur le site du COF !

    ", - "introduction_en": "

    " - } -}, -{ - "model": "cofcms.cofpage", - "pk": 28, - "fields": { - "body": "[{\"type\": \"paragraph\", \"id\": \"0b3a92bd-1e27-433b-842c-ab4f0a2750ad\", \"value\": \"

    On est le COF on est tout gentil

    \"}]", - "body_fr": "[{\"type\": \"paragraph\", \"id\": \"0b3a92bd-1e27-433b-842c-ab4f0a2750ad\", \"value\": \"

    On est le COF on est tout gentil

    \"}]", - "body_en": "[]" - } -}, -{ - "model": "cofcms.cofactuindexpage", - "pk": 29, - "fields": {} -}, -{ - "model": "cofcms.cofactupage", - "pk": 30, - "fields": { - "chapo": "Grosse teuf en K-F\u00eat", - "chapo_fr": "Grosse teuf en K-F\u00eat", - "chapo_en": "Big typar in K-F\u00eat", - "body": "

    Viens boire en K-F\u00eat

    ", - "body_fr": "

    Viens boire en K-F\u00eat

    ", - "body_en": "

    ", - "image": 34, - "is_event": true, - "date_start": "2019-02-07T21:00:00Z", - "date_end": "2019-02-08T03:00:00Z", - "all_day": false - } -}, -{ - "model": "cofcms.cofactupage", - "pk": 31, - "fields": { - "chapo": "", - "chapo_fr": "", - "chapo_en": "", - "body": "

    C'est l'art

    ", - "body_fr": "

    C'est l'art

    ", - "body_en": "

    ", - "image": 37, - "is_event": true, - "date_start": "2019-03-16T21:05:00Z", - "date_end": "2019-03-24T21:05:00Z", - "all_day": true - } -}, -{ - "model": "cofcms.cofdirectorypage", - "pk": 32, - "fields": { - "introduction": "

    Ce sont les clubs

    ", - "introduction_fr": "

    Ce sont les clubs

    ", - "introduction_en": "

    ", - "alphabetique": true - } -}, -{ - "model": "cofcms.cofdirectoryentrypage", - "pk": 33, - "fields": { - "body": "

    Club Arts Plastiques

    ", - "body_fr": "

    Club Arts Plastiques

    ", - "body_en": "

    ", - "links": "[{\"type\": \"contact\", \"id\": \"cf198b98-0b84-4f38-ac00-6d883cfd60a4\", \"value\": {\"email\": \"artsplastiques@ens.fr\", \"texte\": \"Liste Mails\"}}]", - "links_fr": "[{\"type\": \"contact\", \"id\": \"cf198b98-0b84-4f38-ac00-6d883cfd60a4\", \"value\": {\"email\": \"artsplastiques@ens.fr\", \"texte\": \"Liste Mails\"}}]", - "links_en": "[]", - "image": 37 - } -}, -{ - "model": "wagtailimages.image", - "pk": 33, - "fields": { - "collection": 3, - "title": "COF-17", - "file": "original_images/cof-768x576.jpg", - "width": 768, - "height": 576, - "created_at": "2018-01-22T18:49:25.647Z", - "uploaded_by_user": [ - "root" - ], - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": 132330, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 34, - "fields": { - "collection": 3, - "title": "Singin in the RENS", - "file": "original_images/singin.jpg", - "width": 682, - "height": 361, - "created_at": "2018-01-22T19:13:49.753Z", - "uploaded_by_user": [ - "root" - ], - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 35, - "fields": { - "collection": 3, - "title": "Retour du Bur\u00f4", - "file": "original_images/retour.jpg", - "width": 614, - "height": 211, - "created_at": "2018-01-22T19:16:25.375Z", - "uploaded_by_user": [ - "root" - ], - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 36, - "fields": { - "collection": 3, - "title": "elections 18", - "file": "original_images/elections.png", - "width": 850, - "height": 406, - "created_at": "2018-01-22T19:21:31.954Z", - "uploaded_by_user": [ - "root" - ], - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 37, - "fields": { - "collection": 3, - "title": "Arts Plastiques", - "file": "original_images/ArtsPla.png", - "width": 150, - "height": 150, - "created_at": "2018-01-22T20:11:56.461Z", - "uploaded_by_user": [ - "root" - ], - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 38, - "fields": { - "collection": 3, - "title": "MGEN", - "file": "original_images/MGEN.jpg", - "width": 300, - "height": 204, - "created_at": "2018-01-22T20:20:41.712Z", - "uploaded_by_user": [ - "root" - ], - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 39, - "fields": { - "collection": 3, - "title": "MAIF", - "file": "original_images/Logo-MAIF.gif", - "width": 300, - "height": 290, - "created_at": "2018-01-28T16:20:13.828Z", - "uploaded_by_user": [ - "root" - ], - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 7, - "fields": { - "sort_order": 0, - "link_page": null, - "link_url": "https://www.cof.ens.fr/bda/", - "url_append": "", - "handle": "", - "link_text": "BdA", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 8, - "fields": { - "sort_order": 1, - "link_page": null, - "link_url": "https://www.cof.ens.fr/bds/", - "url_append": "", - "handle": "", - "link_text": "BdS", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 9, - "fields": { - "sort_order": 2, - "link_page": null, - "link_url": "https://www.cof.ens.fr/gestion", - "url_append": "", - "handle": "", - "link_text": "GestioCOF", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 10, - "fields": { - "sort_order": 3, - "link_page": null, - "link_url": "https://www.cof.ens.fr/bocal", - "url_append": "", - "handle": "", - "link_text": "Le BOcal", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 11, - "fields": { - "sort_order": 4, - "link_page": null, - "link_url": "https://photos.cof.ens.fr/", - "url_append": "", - "handle": "", - "link_text": "Serveur photos", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 12, - "fields": { - "sort_order": 5, - "link_page": null, - "link_url": "https://www.eleves.ens.fr", - "url_append": "", - "handle": "", - "link_text": "Services \u00e9l\u00e8ves ENS", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 20, - "fields": { - "sort_order": 0, - "link_page": 28, - "link_url": null, - "url_append": "", - "handle": "", - "link_text": "", - "allow_subnav": false, - "menu": 4 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 21, - "fields": { - "sort_order": 1, - "link_page": 29, - "link_url": null, - "url_append": "", - "handle": "", - "link_text": "", - "allow_subnav": false, - "menu": 4 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 22, - "fields": { - "sort_order": 2, - "link_page": 32, - "link_url": null, - "url_append": "", - "handle": "", - "link_text": "", - "allow_subnav": false, - "menu": 4 - } -}, -{ - "model": "wagtailmenus.flatmenu", - "pk": 2, - "fields": { - "site": [ - "localhost", - 8000 - ], - "title": "COF - liens externes", - "handle": "cof-nav-ext", - "heading": "", - "max_levels": 1, - "use_specific": 1 - } -}, -{ - "model": "wagtailmenus.flatmenu", - "pk": 4, - "fields": { - "site": [ - "localhost", - 8000 - ], - "title": "COF - liens internes", - "handle": "cof-nav-int", - "heading": "", - "max_levels": 1, - "use_specific": 1 - } -} -] diff --git a/gestioncof/cms/fixtures/examplesite0.json b/gestioncof/cms/fixtures/examplesite0.json deleted file mode 100644 index 816ebe82..00000000 --- a/gestioncof/cms/fixtures/examplesite0.json +++ /dev/null @@ -1,641 +0,0 @@ -[ -{ - "model": "wagtailcore.page", - "pk": 27, - "fields": { - "path": "000100010002", - "depth": 3, - "numchild": 3, - "title": "Site du COF", - "title_fr": "Site du COF", - "title_en": null, - "draft_title": "Site du COF", - "slug": "site", - "slug_fr": "site", - "slug_en": null, - "content_type": 89, - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/", - "url_path_fr": "/global/site/", - "url_path_en": "/global/site/", - "owner": 165, - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": false, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T20:54:14.724Z", - "last_published_at": "2019-02-04T20:54:14.724Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 28, - "fields": { - "path": "0001000100020001", - "depth": 4, - "numchild": 0, - "title": "Pr\u00e9sentation", - "title_fr": "Pr\u00e9sentation", - "title_en": "Presentation", - "draft_title": "Pr\u00e9sentation", - "slug": "pr\u00e9sentation", - "slug_fr": "pr\u00e9sentation", - "slug_en": null, - "content_type": 88, - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/pr\u00e9sentation/", - "url_path_fr": "/global/site/pr\u00e9sentation/", - "url_path_en": "/global/site/pr\u00e9sentation/", - "owner": 165, - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": true, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T20:55:06.574Z", - "last_published_at": "2019-02-04T21:42:00.461Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 29, - "fields": { - "path": "0001000100020002", - "depth": 4, - "numchild": 2, - "title": "Actualit\u00e9s", - "title_fr": "Actualit\u00e9s", - "title_en": "News", - "draft_title": "Actualit\u00e9s", - "slug": "actualit\u00e9s", - "slug_fr": "actualit\u00e9s", - "slug_en": "news", - "content_type": 92, - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/actualit\u00e9s/", - "url_path_fr": "/global/site/actualit\u00e9s/", - "url_path_en": "/global/site/news/", - "owner": 165, - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": true, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T20:58:47.657Z", - "last_published_at": "2019-02-04T21:43:55.575Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 30, - "fields": { - "path": "00010001000200020001", - "depth": 5, - "numchild": 0, - "title": "Grosse teuf en K-F\u00eat", - "title_fr": "Grosse teuf en K-F\u00eat", - "title_en": "Big feast in K-F\u00eat", - "draft_title": "Grosse teuf en K-F\u00eat", - "slug": "grosse-teuf-en-k-f\u00eat", - "slug_fr": "grosse-teuf-en-k-f\u00eat", - "slug_en": "big-feast-in-k-f\u00eat", - "content_type": 93, - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/actualit\u00e9s/grosse-teuf-en-k-f\u00eat/", - "url_path_fr": "/global/site/actualit\u00e9s/grosse-teuf-en-k-f\u00eat/", - "url_path_en": "/global/site/news/big-feast-in-k-f\u00eat/", - "owner": 165, - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": false, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T21:04:39.422Z", - "last_published_at": "2019-02-04T21:04:39.422Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 31, - "fields": { - "path": "00010001000200020002", - "depth": 5, - "numchild": 0, - "title": "Les 48h des Arts", - "title_fr": "Les 48h des Arts", - "title_en": null, - "draft_title": "Les 48h des Arts", - "slug": "les-48h-des-arts", - "slug_fr": "les-48h-des-arts", - "slug_en": null, - "content_type": 93, - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/actualit\u00e9s/les-48h-des-arts/", - "url_path_fr": "/global/site/actualit\u00e9s/les-48h-des-arts/", - "url_path_en": "/global/site/news/les-48h-des-arts/", - "owner": 165, - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": false, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T21:05:27.190Z", - "last_published_at": "2019-02-04T21:05:27.190Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 32, - "fields": { - "path": "0001000100020003", - "depth": 4, - "numchild": 1, - "title": "Clubs", - "title_fr": "Clubs", - "title_en": null, - "draft_title": "Clubs", - "slug": "clubs", - "slug_fr": "clubs", - "slug_en": null, - "content_type": 87, - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/clubs/", - "url_path_fr": "/global/site/clubs/", - "url_path_en": "/global/site/clubs/", - "owner": 165, - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": true, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T21:44:23.382Z", - "last_published_at": "2019-02-04T21:44:23.382Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.page", - "pk": 33, - "fields": { - "path": "00010001000200030001", - "depth": 5, - "numchild": 0, - "title": "Arts Plastiques", - "title_fr": "Arts Plastiques", - "title_en": null, - "draft_title": "Arts Plastiques", - "slug": "arts-plastiques", - "slug_fr": "arts-plastiques", - "slug_en": null, - "content_type": 90, - "live": true, - "has_unpublished_changes": false, - "url_path": "/global/site/clubs/arts-plastiques/", - "url_path_fr": "/global/site/clubs/arts-plastiques/", - "url_path_en": "/global/site/clubs/arts-plastiques/", - "owner": 165, - "seo_title": "", - "seo_title_fr": null, - "seo_title_en": null, - "show_in_menus": false, - "search_description": "", - "search_description_fr": "", - "search_description_en": "", - "go_live_at": null, - "expire_at": null, - "expired": false, - "locked": false, - "first_published_at": "2019-02-04T21:48:58.013Z", - "last_published_at": "2019-02-04T21:48:58.013Z", - "latest_revision_created_at": null, - "live_revision": null - } -}, -{ - "model": "wagtailcore.collection", - "pk": 3, - "fields": { - "path": "00010002", - "depth": 2, - "numchild": 0, - "name": "COF" - } -}, - { - "model": "wagtailimages.image", - "pk": 33, - "fields": { - "collection": 3, - "title": "COF-17", - "file": "original_images/cof-768x576.jpg", - "width": 768, - "height": 576, - "created_at": "2018-01-22T18:49:25.647Z", - "uploaded_by_user": 165, - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": 132330, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 34, - "fields": { - "collection": 3, - "title": "Singin in the RENS", - "file": "original_images/singin.jpg", - "width": 682, - "height": 361, - "created_at": "2018-01-22T19:13:49.753Z", - "uploaded_by_user": 165, - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 35, - "fields": { - "collection": 3, - "title": "Retour du Bur\u00f4", - "file": "original_images/retour.jpg", - "width": 614, - "height": 211, - "created_at": "2018-01-22T19:16:25.375Z", - "uploaded_by_user": 165, - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 36, - "fields": { - "collection": 3, - "title": "elections 18", - "file": "original_images/elections.png", - "width": 850, - "height": 406, - "created_at": "2018-01-22T19:21:31.954Z", - "uploaded_by_user": 165, - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 37, - "fields": { - "collection": 3, - "title": "Arts Plastiques", - "file": "original_images/ArtsPla.png", - "width": 150, - "height": 150, - "created_at": "2018-01-22T20:11:56.461Z", - "uploaded_by_user": 165, - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 38, - "fields": { - "collection": 3, - "title": "MGEN", - "file": "original_images/MGEN.jpg", - "width": 300, - "height": 204, - "created_at": "2018-01-22T20:20:41.712Z", - "uploaded_by_user": 165, - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "wagtailimages.image", - "pk": 39, - "fields": { - "collection": 3, - "title": "MAIF", - "file": "original_images/Logo-MAIF.gif", - "width": 300, - "height": 290, - "created_at": "2018-01-28T16:20:13.828Z", - "uploaded_by_user": 165, - "focal_point_x": null, - "focal_point_y": null, - "focal_point_width": null, - "focal_point_height": null, - "file_size": null, - "file_hash": "" - } -}, -{ - "model": "cofcms.cofrootpage", - "pk": 27, - "fields": { - "introduction": "

    Bienvenue sur le site du COF !

    ", - "introduction_fr": "

    Bienvenue sur le site du COF !

    ", - "introduction_en": "

    " - } -}, -{ - "model": "cofcms.cofpage", - "pk": 28, - "fields": { - "body": "[{\"value\": \"

    On est le COF on est tout gentil

    \", \"type\": \"paragraph\", \"id\": \"0b3a92bd-1e27-433b-842c-ab4f0a2750ad\"}]", - "body_fr": "[{\"value\": \"

    On est le COF on est tout gentil

    \", \"type\": \"paragraph\", \"id\": \"0b3a92bd-1e27-433b-842c-ab4f0a2750ad\"}]", - "body_en": "[]" - } -}, -{ - "model": "cofcms.cofactuindexpage", - "pk": 29, - "fields": {} -}, -{ - "model": "cofcms.cofactupage", - "pk": 30, - "fields": { - "chapo": "Grosse teuf en K-F\u00eat", - "chapo_fr": "Grosse teuf en K-F\u00eat", - "chapo_en": "Big typar in K-F\u00eat", - "body": "

    Viens boire en K-F\u00eat

    ", - "body_fr": "

    Viens boire en K-F\u00eat

    ", - "body_en": "

    ", - "image": 34, - "is_event": true, - "date_start": "2019-02-07T21:00:00Z", - "date_end": "2019-02-08T03:00:00Z", - "all_day": false - } -}, -{ - "model": "cofcms.cofactupage", - "pk": 31, - "fields": { - "chapo": "", - "chapo_fr": "", - "chapo_en": "", - "body": "

    C'est l'art

    ", - "body_fr": "

    C'est l'art

    ", - "body_en": "

    ", - "image": 37, - "is_event": true, - "date_start": "2019-03-16T21:05:00Z", - "date_end": "2019-03-24T21:05:00Z", - "all_day": true - } -}, -{ - "model": "cofcms.cofdirectorypage", - "pk": 32, - "fields": { - "introduction": "

    Ce sont les clubs

    ", - "introduction_fr": "

    Ce sont les clubs

    ", - "introduction_en": "

    ", - "alphabetique": true - } -}, -{ - "model": "cofcms.cofdirectoryentrypage", - "pk": 33, - "fields": { - "body": "

    Club Arts Plastiques

    ", - "body_fr": "

    Club Arts Plastiques

    ", - "body_en": "

    ", - "links": "[{\"value\": {\"texte\": \"Liste Mails\", \"email\": \"artsplastiques@ens.fr\"}, \"type\": \"contact\", \"id\": \"cf198b98-0b84-4f38-ac00-6d883cfd60a4\"}]", - "links_fr": "[{\"value\": {\"texte\": \"Liste Mails\", \"email\": \"artsplastiques@ens.fr\"}, \"type\": \"contact\", \"id\": \"cf198b98-0b84-4f38-ac00-6d883cfd60a4\"}]", - "links_en": "[]", - "image": 37 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 7, - "fields": { - "sort_order": 0, - "link_page": null, - "link_url": "https://www.cof.ens.fr/bda/", - "url_append": "", - "handle": "", - "link_text": "BdA", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 8, - "fields": { - "sort_order": 1, - "link_page": null, - "link_url": "https://www.cof.ens.fr/bds/", - "url_append": "", - "handle": "", - "link_text": "BdS", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 9, - "fields": { - "sort_order": 2, - "link_page": null, - "link_url": "https://www.cof.ens.fr/gestion", - "url_append": "", - "handle": "", - "link_text": "GestioCOF", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 10, - "fields": { - "sort_order": 3, - "link_page": null, - "link_url": "https://www.cof.ens.fr/bocal", - "url_append": "", - "handle": "", - "link_text": "Le BOcal", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 11, - "fields": { - "sort_order": 4, - "link_page": null, - "link_url": "https://photos.cof.ens.fr/", - "url_append": "", - "handle": "", - "link_text": "Serveur photos", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 12, - "fields": { - "sort_order": 5, - "link_page": null, - "link_url": "https://www.eleves.ens.fr", - "url_append": "", - "handle": "", - "link_text": "Services \u00e9l\u00e8ves ENS", - "allow_subnav": false, - "menu": 2 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 20, - "fields": { - "sort_order": 0, - "link_page": 28, - "link_url": null, - "url_append": "", - "handle": "", - "link_text": "", - "allow_subnav": false, - "menu": 4 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 21, - "fields": { - "sort_order": 1, - "link_page": 29, - "link_url": null, - "url_append": "", - "handle": "", - "link_text": "", - "allow_subnav": false, - "menu": 4 - } -}, -{ - "model": "wagtailmenus.flatmenuitem", - "pk": 22, - "fields": { - "sort_order": 2, - "link_page": 32, - "link_url": null, - "url_append": "", - "handle": "", - "link_text": "", - "allow_subnav": false, - "menu": 4 - } -}, -{ - "model": "wagtailmenus.flatmenu", - "pk": 2, - "fields": { - "site": 2, - "title": "COF - liens externes", - "handle": "cof-nav-ext", - "heading": "", - "max_levels": 1, - "use_specific": 1 - } -}, -{ - "model": "wagtailmenus.flatmenu", - "pk": 4, - "fields": { - "site": 2, - "title": "COF - liens internes", - "handle": "cof-nav-int", - "heading": "", - "max_levels": 1, - "use_specific": 1 - } -} -] diff --git a/gestioncof/cms/forms.py b/gestioncof/cms/forms.py deleted file mode 100644 index d2766bf0..00000000 --- a/gestioncof/cms/forms.py +++ /dev/null @@ -1,15 +0,0 @@ -import re - -from django import forms -from django.utils.translation import gettext as _ - - -class CaptchaForm(forms.Form): - answer = forms.CharField(label="Réponse", max_length=32) - - def clean_answer(self): - value = self.cleaned_data["answer"] - if not re.match(r"(les|the)? *ernests?", value.strip().lower()): - raise forms.ValidationError(_("Réponse incorrecte")) - - return value diff --git a/gestioncof/cms/locale/en/LC_MESSAGES/django.mo b/gestioncof/cms/locale/en/LC_MESSAGES/django.mo deleted file mode 100644 index 40b2f45f278955732dff6ee50759475c87e798a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1740 zcmZvbOK&4Z5XXCAd70O;iv&U-3eYONChJ&Y3C@}*WD}B=^T75ldq7CNGi|$J+&${< zNkCB!a6sY%aOW5wKtf2IxcLJBUjT8~11Apq1>j#}XEGboa{E^`(^XyF^RGWIp8rl@ zJd61v=C7D9VSaKBe;B`m>);>Y!{GA|2=OHNGWY@*&A0(R2R{aR-2r$R{2Hu*N8sDw z&)|dLU*KclUGM_<4|oau7kmx8ghk8XyWmscKKLg14aoKV1ip%zegkj7zjt1UN5IxY zLOcPkf%u8ejJLsO;C~8oeHr);xDP%G{s2bcFCbEiyCB#1H^}?noADfiFT=k8E`e7; ze(yj&-&Y{l_an$x_%tS;3*qJ9`{2R*axB-&dp(ZHcfkYKPz?Sqd@iIb298+_{sw#= zuJw3aWaC;GzTduf1^;YqonhT5pWQ00ZfRXFEU`kVR=HS-(cyD7ZgSLheOyJWz`Dnbk;T5SY^)>Ag0zEZT}h zTRT^EH9T$7;Dd$CJGR_d9NSoL?ex(G$4-$#WgLL7Lo^WjdLRO_hBx)5#o zfli_i@_~5a-gD~lB0^>BxPz_n_Fuqn=j1QYpWJIFv#&`hE0O{B3#K*sm&6_L+QKTEj<4v>7yM@s19)-86SHjn=Ij`6d}C5Uy`EmiXhsY`t#il@=|$u~fSq zeW;9b(|UI;K6{|Xe0~0Et!R*ww&>-h@;@%E-mo;+ORFnS%xRF=U;!F0KFW3cImtMN zxW!JIK&0DhAOGE<-7myWGAX2gEc$N96V`22{I*YFsA%$SY$1R3YlNfFl9eh3v8wvUI_rgPj9CRr9chM;_z(8wZ5*#l-k!HP|@hn7yz z%APCkJ3FdM6HBJ85&r>G CbmrFp diff --git a/gestioncof/cms/locale/en/LC_MESSAGES/django.po b/gestioncof/cms/locale/en/LC_MESSAGES/django.po deleted file mode 100644 index d586f73e..00000000 --- a/gestioncof/cms/locale/en/LC_MESSAGES/django.po +++ /dev/null @@ -1,118 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -msgid "" -msgstr "" -"Project-Id-Version: \n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-08-22 12:28+0200\n" -"PO-Revision-Date: 2020-08-22 12:29+0200\n" -"Last-Translator: \n" -"Language-Team: \n" -"Language: en\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.0.6\n" - -#: templates/cofcms/base.html:7 -msgid "Association des élèves de l'ENS Ulm" -msgstr "Student Association of the École Normale Supérieure" - -#: templates/cofcms/calendar.html:9 -msgid "LMMJVSD" -msgstr "MTuWThFSaSu" - -#: templates/cofcms/calendar.html:17 -msgid "Le " -msgstr "On " - -#: templates/cofcms/cof_actu_index_page.html:10 -msgid "Calendrier" -msgstr "Calendar" - -#: templates/cofcms/cof_actu_index_page.html:25 -#: templates/cofcms/cof_actu_index_page.html:47 -msgid "Actualités plus récentes" -msgstr "Newer" - -#: templates/cofcms/cof_actu_index_page.html:28 -#: templates/cofcms/cof_actu_index_page.html:50 -msgid "Actualités plus anciennes" -msgstr "Older" - -#: templates/cofcms/cof_actu_index_page.html:41 -msgid "Lire plus" -msgstr "Read more" - -#: templates/cofcms/cof_actu_page.html:7 -msgid "A lieu" -msgstr "Happens" - -#: templates/cofcms/cof_directory_page.html:9 -msgid "Accès rapide" -msgstr "Quick access" - -#: templates/cofcms/cof_directory_page.html:39 -msgid "Afficher l'adresse mail" -msgstr "Show mail address" - -#: templates/cofcms/cof_root_page.html:11 -msgid "Agenda" -msgstr "Agenda" - -#: templates/cofcms/sympa.html:4 -msgid "Redirection vers" -msgstr "Redirecting to" - -#: templates/cofcms/sympa.html:18 -msgid "Comment s'appellent les poissons du bassin ?" -msgstr "How are called the fish in the school's pond?" - -#: templatetags/cofcms_tags.py:134 templatetags/cofcms_tags.py:163 -#, python-brace-format -msgid "le {datestart}" -msgstr "on {datestart}" - -#: templatetags/cofcms_tags.py:136 -#, python-brace-format -msgid "le {datestart} de {timestart} à {timeend}" -msgstr "on {datestart} from {timestart} to {timeend}" - -#: templatetags/cofcms_tags.py:147 -#, python-brace-format -msgid "du {datestart} au {dateend}{common}" -msgstr "from {datestart} to {dateend}{common}" - -#: templatetags/cofcms_tags.py:153 -#, python-brace-format -msgid "du {datestart}{common} à {timestart} au {dateend} à {timeend}" -msgstr "from {datestart}{common} {timestart} to {dateend} {timeend}" - -#: templatetags/cofcms_tags.py:165 -#, python-brace-format -msgid "le {datestart} à {timestart}" -msgstr "on {datestart} at {timestart}" - -#: views.py:16 -msgid "Réponse incorrecte" -msgstr "Invalid answer" - -#~ msgid "Listes mail" -#~ msgstr "Mailing lists" - -#~ msgid "" -#~ "\n" -#~ " Tous les abonnements aux listes de diffusion de mail à l'ENS se gèrent sur le serveur de mail SYMPA\n" -#~ "\n" -#~ " Pour y accéder : Merci de répondre à cette question anti-robots.\n" -#~ " " -#~ msgstr "" -#~ "\n" -#~ " All the mailing list subscriptions can be managed through the SYMPA mail server\n" -#~ "\n" -#~ " In order to access it, please answer this antispam question.\n" -#~ " " diff --git a/gestioncof/cms/migrations/0001_initial.py b/gestioncof/cms/migrations/0001_initial.py deleted file mode 100644 index 36a3ff1f..00000000 --- a/gestioncof/cms/migrations/0001_initial.py +++ /dev/null @@ -1,472 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.18 on 2019-02-04 20:45 -from __future__ import unicode_literals - -import django.db.models.deletion -import wagtail.contrib.routable_page.models -import wagtail.core.blocks -import wagtail.core.fields -import wagtail.images.blocks -from django.db import migrations, models - -import gestioncof.cms.models - - -class Migration(migrations.Migration): - initial = True - - dependencies = [ - ("wagtailimages", "0021_image_file_hash"), - ("wagtailcore", "0040_page_draft_title"), - ] - - operations = [ - migrations.CreateModel( - name="COFActuIndexPage", - fields=[ - ( - "page_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="wagtailcore.Page", - ), - ) - ], - options={ - "verbose_name": "Index des actualités", - "verbose_name_plural": "Indexs des actualités", - }, - bases=("wagtailcore.page", gestioncof.cms.models.COFActuIndexMixin), - ), - migrations.CreateModel( - name="COFActuPage", - fields=[ - ( - "page_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="wagtailcore.Page", - ), - ), - ( - "chapo", - models.TextField(blank=True, verbose_name="Description rapide"), - ), - ( - "chapo_fr", - models.TextField( - blank=True, null=True, verbose_name="Description rapide" - ), - ), - ( - "chapo_en", - models.TextField( - blank=True, null=True, verbose_name="Description rapide" - ), - ), - ("body", wagtail.core.fields.RichTextField(verbose_name="Contenu")), - ( - "body_fr", - wagtail.core.fields.RichTextField( - null=True, verbose_name="Contenu" - ), - ), - ( - "body_en", - wagtail.core.fields.RichTextField( - null=True, verbose_name="Contenu" - ), - ), - ( - "is_event", - models.BooleanField(default=True, verbose_name="Évènement"), - ), - ( - "date_start", - models.DateTimeField(verbose_name="Date et heure de début"), - ), - ( - "date_end", - models.DateTimeField( - blank=True, - default=None, - null=True, - verbose_name="Date et heure de fin", - ), - ), - ( - "all_day", - models.BooleanField(default=False, verbose_name="Toute la journée"), - ), - ( - "image", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="+", - to="wagtailimages.Image", - verbose_name="Image à la Une", - ), - ), - ], - options={"verbose_name": "Actualité", "verbose_name_plural": "Actualités"}, - bases=( - wagtail.contrib.routable_page.models.RoutablePageMixin, - "wagtailcore.page", - ), - ), - migrations.CreateModel( - name="COFDirectoryEntryPage", - fields=[ - ( - "page_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="wagtailcore.Page", - ), - ), - ("body", wagtail.core.fields.RichTextField(verbose_name="Description")), - ( - "body_fr", - wagtail.core.fields.RichTextField( - null=True, verbose_name="Description" - ), - ), - ( - "body_en", - wagtail.core.fields.RichTextField( - null=True, verbose_name="Description" - ), - ), - ( - "links", - wagtail.core.fields.StreamField( - [ - ( - "lien", - wagtail.core.blocks.StructBlock( - [ - ( - "url", - wagtail.core.blocks.URLBlock(required=True), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "contact", - wagtail.core.blocks.StructBlock( - [ - ( - "email", - wagtail.core.blocks.EmailBlock( - required=True - ), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ] - ), - ), - ( - "links_fr", - wagtail.core.fields.StreamField( - [ - ( - "lien", - wagtail.core.blocks.StructBlock( - [ - ( - "url", - wagtail.core.blocks.URLBlock(required=True), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "contact", - wagtail.core.blocks.StructBlock( - [ - ( - "email", - wagtail.core.blocks.EmailBlock( - required=True - ), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ], - null=True, - ), - ), - ( - "links_en", - wagtail.core.fields.StreamField( - [ - ( - "lien", - wagtail.core.blocks.StructBlock( - [ - ( - "url", - wagtail.core.blocks.URLBlock(required=True), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "contact", - wagtail.core.blocks.StructBlock( - [ - ( - "email", - wagtail.core.blocks.EmailBlock( - required=True - ), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ], - null=True, - ), - ), - ( - "image", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="+", - to="wagtailimages.Image", - verbose_name="Image", - ), - ), - ], - options={ - "verbose_name": "Entrée d'annuaire", - "verbose_name_plural": "Entrées d'annuaire", - }, - bases=("wagtailcore.page",), - ), - migrations.CreateModel( - name="COFDirectoryPage", - fields=[ - ( - "page_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="wagtailcore.Page", - ), - ), - ( - "introduction", - wagtail.core.fields.RichTextField(verbose_name="Introduction"), - ), - ( - "introduction_fr", - wagtail.core.fields.RichTextField( - null=True, verbose_name="Introduction" - ), - ), - ( - "introduction_en", - wagtail.core.fields.RichTextField( - null=True, verbose_name="Introduction" - ), - ), - ( - "alphabetique", - models.BooleanField( - default=True, verbose_name="Tri par ordre alphabétique ?" - ), - ), - ], - options={ - "verbose_name": "Annuaire (clubs, partenaires, bons plans...)", - "verbose_name_plural": "Annuaires", - }, - bases=("wagtailcore.page",), - ), - migrations.CreateModel( - name="COFPage", - fields=[ - ( - "page_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="wagtailcore.Page", - ), - ), - ( - "body", - wagtail.core.fields.StreamField( - [ - ( - "heading", - wagtail.core.blocks.CharBlock(classname="full title"), - ), - ("paragraph", wagtail.core.blocks.RichTextBlock()), - ("image", wagtail.images.blocks.ImageChooserBlock()), - ( - "iframe", - wagtail.core.blocks.StructBlock( - [ - ( - "url", - wagtail.core.blocks.URLBlock( - "Adresse de la page" - ), - ), - ( - "height", - wagtail.core.blocks.CharBlock( - "Hauteur (en pixels)" - ), - ), - ] - ), - ), - ] - ), - ), - ( - "body_fr", - wagtail.core.fields.StreamField( - [ - ( - "heading", - wagtail.core.blocks.CharBlock(classname="full title"), - ), - ("paragraph", wagtail.core.blocks.RichTextBlock()), - ("image", wagtail.images.blocks.ImageChooserBlock()), - ( - "iframe", - wagtail.core.blocks.StructBlock( - [ - ( - "url", - wagtail.core.blocks.URLBlock( - "Adresse de la page" - ), - ), - ( - "height", - wagtail.core.blocks.CharBlock( - "Hauteur (en pixels)" - ), - ), - ] - ), - ), - ], - null=True, - ), - ), - ( - "body_en", - wagtail.core.fields.StreamField( - [ - ( - "heading", - wagtail.core.blocks.CharBlock(classname="full title"), - ), - ("paragraph", wagtail.core.blocks.RichTextBlock()), - ("image", wagtail.images.blocks.ImageChooserBlock()), - ( - "iframe", - wagtail.core.blocks.StructBlock( - [ - ( - "url", - wagtail.core.blocks.URLBlock( - "Adresse de la page" - ), - ), - ( - "height", - wagtail.core.blocks.CharBlock( - "Hauteur (en pixels)" - ), - ), - ] - ), - ), - ], - null=True, - ), - ), - ], - options={ - "verbose_name": "Page normale COF", - "verbose_name_plural": "Pages normales COF", - }, - bases=("wagtailcore.page",), - ), - migrations.CreateModel( - name="COFRootPage", - fields=[ - ( - "page_ptr", - models.OneToOneField( - auto_created=True, - on_delete=django.db.models.deletion.CASCADE, - parent_link=True, - primary_key=True, - serialize=False, - to="wagtailcore.Page", - ), - ), - ( - "introduction", - wagtail.core.fields.RichTextField(verbose_name="Introduction"), - ), - ( - "introduction_fr", - wagtail.core.fields.RichTextField( - null=True, verbose_name="Introduction" - ), - ), - ( - "introduction_en", - wagtail.core.fields.RichTextField( - null=True, verbose_name="Introduction" - ), - ), - ], - options={ - "verbose_name": "Racine site du COF", - "verbose_name_plural": "Racines site du COF", - }, - bases=("wagtailcore.page", gestioncof.cms.models.COFActuIndexMixin), - ), - ] diff --git a/gestioncof/cms/migrations/0002_auto_20190523_1521.py b/gestioncof/cms/migrations/0002_auto_20190523_1521.py deleted file mode 100644 index 1a0a87d4..00000000 --- a/gestioncof/cms/migrations/0002_auto_20190523_1521.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 2.2 on 2019-05-23 13:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("cofcms", "0001_initial")] - - operations = [ - migrations.AlterField( - model_name="cofactupage", - name="all_day", - field=models.BooleanField( - blank=True, default=False, verbose_name="Toute la journée" - ), - ), - migrations.AlterField( - model_name="cofactupage", - name="is_event", - field=models.BooleanField( - blank=True, default=True, verbose_name="Évènement" - ), - ), - migrations.AlterField( - model_name="cofdirectorypage", - name="alphabetique", - field=models.BooleanField( - blank=True, default=True, verbose_name="Tri par ordre alphabétique ?" - ), - ), - ] diff --git a/gestioncof/cms/migrations/0003_directory_entry_optional_links.py b/gestioncof/cms/migrations/0003_directory_entry_optional_links.py deleted file mode 100644 index 6555236b..00000000 --- a/gestioncof/cms/migrations/0003_directory_entry_optional_links.py +++ /dev/null @@ -1,106 +0,0 @@ -# Generated by Django 2.2.8 on 2019-12-20 16:22 - -import wagtail.core.blocks -import wagtail.core.fields -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("cofcms", "0002_auto_20190523_1521"), - ] - - operations = [ - migrations.AlterField( - model_name="cofdirectoryentrypage", - name="links", - field=wagtail.core.fields.StreamField( - [ - ( - "lien", - wagtail.core.blocks.StructBlock( - [ - ("url", wagtail.core.blocks.URLBlock(required=True)), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "contact", - wagtail.core.blocks.StructBlock( - [ - ( - "email", - wagtail.core.blocks.EmailBlock(required=True), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ], - blank=True, - ), - ), - migrations.AlterField( - model_name="cofdirectoryentrypage", - name="links_en", - field=wagtail.core.fields.StreamField( - [ - ( - "lien", - wagtail.core.blocks.StructBlock( - [ - ("url", wagtail.core.blocks.URLBlock(required=True)), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "contact", - wagtail.core.blocks.StructBlock( - [ - ( - "email", - wagtail.core.blocks.EmailBlock(required=True), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ], - blank=True, - null=True, - ), - ), - migrations.AlterField( - model_name="cofdirectoryentrypage", - name="links_fr", - field=wagtail.core.fields.StreamField( - [ - ( - "lien", - wagtail.core.blocks.StructBlock( - [ - ("url", wagtail.core.blocks.URLBlock(required=True)), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "contact", - wagtail.core.blocks.StructBlock( - [ - ( - "email", - wagtail.core.blocks.EmailBlock(required=True), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ], - blank=True, - null=True, - ), - ), - ] diff --git a/gestioncof/cms/migrations/0004_auto_20200829_2314.py b/gestioncof/cms/migrations/0004_auto_20200829_2314.py deleted file mode 100644 index dd525a2c..00000000 --- a/gestioncof/cms/migrations/0004_auto_20200829_2314.py +++ /dev/null @@ -1,133 +0,0 @@ -# Generated by Django 2.2.15 on 2020-08-29 21:14 - -import wagtail.core.blocks -import wagtail.core.fields -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("cofcms", "0003_directory_entry_optional_links"), - ] - - operations = [ - migrations.AlterField( - model_name="cofdirectoryentrypage", - name="links", - field=wagtail.core.fields.StreamField( - [ - ( - "lien", - wagtail.core.blocks.StructBlock( - [ - ("url", wagtail.core.blocks.URLBlock(required=True)), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "contact", - wagtail.core.blocks.StructBlock( - [ - ( - "email", - wagtail.core.blocks.EmailBlock(required=True), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "info", - wagtail.core.blocks.StructBlock( - [ - ("nom", wagtail.core.blocks.CharBlock(required=False)), - ("texte", wagtail.core.blocks.CharBlock(required=True)), - ] - ), - ), - ], - blank=True, - ), - ), - migrations.AlterField( - model_name="cofdirectoryentrypage", - name="links_en", - field=wagtail.core.fields.StreamField( - [ - ( - "lien", - wagtail.core.blocks.StructBlock( - [ - ("url", wagtail.core.blocks.URLBlock(required=True)), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "contact", - wagtail.core.blocks.StructBlock( - [ - ( - "email", - wagtail.core.blocks.EmailBlock(required=True), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "info", - wagtail.core.blocks.StructBlock( - [ - ("nom", wagtail.core.blocks.CharBlock(required=False)), - ("texte", wagtail.core.blocks.CharBlock(required=True)), - ] - ), - ), - ], - blank=True, - null=True, - ), - ), - migrations.AlterField( - model_name="cofdirectoryentrypage", - name="links_fr", - field=wagtail.core.fields.StreamField( - [ - ( - "lien", - wagtail.core.blocks.StructBlock( - [ - ("url", wagtail.core.blocks.URLBlock(required=True)), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "contact", - wagtail.core.blocks.StructBlock( - [ - ( - "email", - wagtail.core.blocks.EmailBlock(required=True), - ), - ("texte", wagtail.core.blocks.CharBlock()), - ] - ), - ), - ( - "info", - wagtail.core.blocks.StructBlock( - [ - ("nom", wagtail.core.blocks.CharBlock(required=False)), - ("texte", wagtail.core.blocks.CharBlock(required=True)), - ] - ), - ), - ], - blank=True, - null=True, - ), - ), - ] diff --git a/gestioncof/cms/migrations/__init__.py b/gestioncof/cms/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioncof/cms/models.py b/gestioncof/cms/models.py deleted file mode 100644 index 57881084..00000000 --- a/gestioncof/cms/models.py +++ /dev/null @@ -1,229 +0,0 @@ -from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator -from django.db import models -from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel -from wagtail.contrib.routable_page.models import RoutablePageMixin, route -from wagtail.core import blocks -from wagtail.core.fields import RichTextField, StreamField -from wagtail.core.models import Page -from wagtail.images.blocks import ImageChooserBlock -from wagtail.images.edit_handlers import ImageChooserPanel - - -# Page pouvant afficher des actualités -class COFActuIndexMixin: - @property - def actus(self): - actus = COFActuPage.objects.live().order_by("-date_start").descendant_of(self) - return actus - - -# Racine du site du COF -class COFRootPage(RoutablePageMixin, Page, COFActuIndexMixin): - introduction = RichTextField("Introduction") - - content_panels = Page.content_panels + [ - FieldPanel("introduction", classname="full") - ] - - subpage_types = ["COFActuIndexPage", "COFPage", "COFDirectoryPage"] - - class Meta: - verbose_name = "Racine site du COF" - verbose_name_plural = "Racines site du COF" - - @property - def actus(self): - return super().actus[:4] - - # Mini calendrier - @route(r"^calendar/(\d+)/(\d+)/$") - def calendar(self, request, year, month): - from .views import raw_calendar_view - - return raw_calendar_view(request, int(year), int(month)) - - # Captcha Mailing-listes - @route(r"^sympa/captcha/$") - def sympa_captcha(self, request): - from .views import sympa_captcha_form_view - - return sympa_captcha_form_view(request) - - -# Block iframe -class IFrameBlock(blocks.StructBlock): - url = blocks.URLBlock("Adresse de la page") - height = blocks.CharBlock("Hauteur (en pixels)") - - class Meta: - verbose_name = "Page incluse (iframe, à utiliser avec précaution)" - verbose_name_plural = "Pages incluses (iframes, à utiliser avec précaution)" - template = "cofcms/iframe_block.html" - - -# Page lambda du site -class COFPage(Page): - body = StreamField( - [ - ("heading", blocks.CharBlock(classname="full title")), - ("paragraph", blocks.RichTextBlock()), - ("image", ImageChooserBlock()), - ("iframe", IFrameBlock()), - ] - ) - - content_panels = Page.content_panels + [StreamFieldPanel("body")] - - subpage_types = ["COFDirectoryPage", "COFPage"] - parent_page_types = ["COFPage", "COFRootPage"] - - class Meta: - verbose_name = "Page normale COF" - verbose_name_plural = "Pages normales COF" - - -# Actualités -class COFActuIndexPage(Page, COFActuIndexMixin): - subpage_types = ["COFActuPage"] - parent_page_types = ["COFRootPage"] - - class Meta: - verbose_name = "Index des actualités" - verbose_name_plural = "Indexs des actualités" - - def get_context(self, request): - context = super().get_context(request) - actus = COFActuPage.objects.live().descendant_of(self).order_by("-date_end") - - page = request.GET.get("page") - paginator = Paginator(actus, 5) - try: - actus = paginator.page(page) - except PageNotAnInteger: - actus = paginator.page(1) - except EmptyPage: - actus = paginator.page(paginator.num_pages) - - context["actus"] = actus - return context - - -class COFActuPage(RoutablePageMixin, Page): - chapo = models.TextField("Description rapide", blank=True) - body = RichTextField("Contenu") - image = models.ForeignKey( - "wagtailimages.Image", - verbose_name="Image à la Une", - null=True, - blank=True, - on_delete=models.SET_NULL, - related_name="+", - ) - is_event = models.BooleanField("Évènement", default=True, blank=True) - date_start = models.DateTimeField("Date et heure de début") - date_end = models.DateTimeField( - "Date et heure de fin", blank=True, default=None, null=True - ) - all_day = models.BooleanField("Toute la journée", default=False, blank=True) - - content_panels = Page.content_panels + [ - ImageChooserPanel("image"), - FieldPanel("chapo"), - FieldPanel("body", classname="full"), - FieldPanel("is_event"), - FieldPanel("date_start"), - FieldPanel("date_end"), - FieldPanel("all_day"), - ] - - subpage_types = [] - parent_page_types = ["COFActuIndexPage"] - - class Meta: - verbose_name = "Actualité" - verbose_name_plural = "Actualités" - - -# Annuaires (Clubs, partenaires, bonnes adresses) -class COFDirectoryPage(Page): - introduction = RichTextField("Introduction") - alphabetique = models.BooleanField( - "Tri par ordre alphabétique ?", default=True, blank=True - ) - - content_panels = Page.content_panels + [ - FieldPanel("introduction"), - FieldPanel("alphabetique"), - ] - - subpage_types = ["COFActuPage", "COFDirectoryEntryPage"] - parent_page_types = ["COFRootPage", "COFPage"] - - @property - def entries(self): - entries = COFDirectoryEntryPage.objects.live().descendant_of(self) - if self.alphabetique: - entries = entries.order_by("title") - return entries - - class Meta: - verbose_name = "Annuaire (clubs, partenaires, bons plans...)" - verbose_name_plural = "Annuaires" - - -class COFDirectoryEntryPage(Page): - body = RichTextField("Description") - links = StreamField( - [ - ( - "lien", - blocks.StructBlock( - [ - ("url", blocks.URLBlock(required=True)), - ("texte", blocks.CharBlock()), - ] - ), - ), - ( - "contact", - blocks.StructBlock( - [ - ("email", blocks.EmailBlock(required=True)), - ("texte", blocks.CharBlock()), - ] - ), - ), - ( - "info", - blocks.StructBlock( - [ - ("nom", blocks.CharBlock(required=False)), - ("texte", blocks.CharBlock(required=True)), - ] - ), - ), - ], - blank=True, - ) - - image = models.ForeignKey( - "wagtailimages.Image", - verbose_name="Image", - null=True, - blank=True, - on_delete=models.SET_NULL, - related_name="+", - ) - - content_panels = Page.content_panels + [ - ImageChooserPanel("image"), - FieldPanel("body", classname="full"), - StreamFieldPanel("links"), - ] - - subpage_types = [] - parent_page_types = ["COFDirectoryPage"] - - class Meta: - verbose_name = "Entrée d'annuaire" - verbose_name_plural = "Entrées d'annuaire" diff --git a/gestioncof/cms/static/cofcms/config.rb b/gestioncof/cms/static/cofcms/config.rb deleted file mode 100644 index 826a3727..00000000 --- a/gestioncof/cms/static/cofcms/config.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'compass/import-once/activate' -# Require any additional compass plugins here. - -# Set this to the root of your project when deployed: -http_path = "/" -css_dir = "css" -sass_dir = "sass" -images_dir = "images" -javascripts_dir = "js" - -# You can select your preferred output style here (can be overridden via the command line): -# output_style = :expanded or :nested or :compact or :compressed - -# To enable relative paths to assets via compass helper functions. Uncomment: -# relative_assets = true - -# To disable debugging comments that display the original location of your selectors. Uncomment: -# line_comments = false - - -# If you prefer the indented syntax, you might want to regenerate this -# project again passing --syntax sass, or you can uncomment this: -# preferred_syntax = :sass -# and then run: -# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass diff --git a/gestioncof/cms/static/cofcms/css/screen.css b/gestioncof/cms/static/cofcms/css/screen.css deleted file mode 100644 index bbd90344..00000000 --- a/gestioncof/cms/static/cofcms/css/screen.css +++ /dev/null @@ -1,781 +0,0 @@ -/* Welcome to Compass. - * In this file you should write your main styles. (or centralize your imports) - * Import this file using the following HTML or equivalent: - * */ -/* line 5, ../../../../../../../../../../var/lib/gems/2.5.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font: inherit; - font-size: 100%; - vertical-align: baseline; -} - -/* line 22, ../../../../../../../../../../var/lib/gems/2.5.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ -html { - line-height: 1; -} - -/* line 24, ../../../../../../../../../../var/lib/gems/2.5.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ -ol, ul { - list-style: none; -} - -/* line 26, ../../../../../../../../../../var/lib/gems/2.5.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ -table { - border-collapse: collapse; - border-spacing: 0; -} - -/* line 28, ../../../../../../../../../../var/lib/gems/2.5.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ -caption, th, td { - text-align: left; - font-weight: normal; - vertical-align: middle; -} - -/* line 30, ../../../../../../../../../../var/lib/gems/2.5.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ -q, blockquote { - quotes: none; -} -/* line 103, ../../../../../../../../../../var/lib/gems/2.5.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ -q:before, q:after, blockquote:before, blockquote:after { - content: ""; - content: none; -} - -/* line 32, ../../../../../../../../../../var/lib/gems/2.5.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ -a img { - border: none; -} - -/* line 116, ../../../../../../../../../../var/lib/gems/2.5.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */ -article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { - display: block; -} - -/* line 10, ../sass/screen.scss */ -*, *:after, *:before { - box-sizing: border-box; -} - -/* line 14, ../sass/screen.scss */ -body { - background: #fefefe; - font: 17px "Source Sans Pro", "sans-serif"; - color: #000; -} - -/* line 20, ../sass/screen.scss */ -header { - background: #5B0012; -} - -/* line 24, ../sass/screen.scss */ -h1, h2 { - font-family: "Carter One", "serif"; -} - -/* line 28, ../sass/screen.scss */ -h1 { - font-size: 2.3em; - color: #90001C; -} - -/* line 33, ../sass/screen.scss */ -h2 { - font-size: 1.6em; - color: #b01432; -} - -/* line 39, ../sass/screen.scss */ -a { - color: #CC9500; - text-decoration: none; - font-weight: bold; - padding: 0 2px; - margin: 0 -2px; -} -/* line 45, ../sass/screen.scss */ -a:hover { - text-decoration: underline; -} - -/* line 50, ../sass/screen.scss */ -h2 a { - font-weight: inherit; - color: inherit; -} - -/* line 56, ../sass/screen.scss */ -header a { - color: #fefefe; -} -/* line 58, ../sass/screen.scss */ -header a:hover { - text-decoration: none; -} -/* line 62, ../sass/screen.scss */ -header section { - display: flex; - width: 100%; - justify-content: space-between; - align-items: stretch; -} -/* line 68, ../sass/screen.scss */ -header section.bottom-menu { - justify-content: space-around; - text-align: center; - background: #90001C; -} -/* line 74, ../sass/screen.scss */ -header h1 { - padding: 0 15px; -} -/* line 77, ../sass/screen.scss */ -header nav { - display: inline-flex; -} -/* line 79, ../sass/screen.scss */ -header nav ul { - display: inline-flex; - flex-wrap: wrap; -} -/* line 82, ../sass/screen.scss */ -header nav ul li { - display: inline-block; -} -/* line 84, ../sass/screen.scss */ -header nav ul li > * { - display: block; - padding: 10px 15px; - font-weight: bold; -} -/* line 89, ../sass/screen.scss */ -header nav ul li > *:hover { - background: #280008; -} -/* line 95, ../sass/screen.scss */ -header nav .lang-select { - display: inline-block; - height: 100%; - vertical-align: top; - position: relative; -} -/* line 101, ../sass/screen.scss */ -header nav .lang-select:before { - content: ""; - color: #fff; - position: absolute; - top: 0; - left: 0; - border-left: 1px solid #fff; - height: calc(100% - 20px); - margin: 10px 0; - padding-left: 10px; -} -/* line 113, ../sass/screen.scss */ -header nav .lang-select a { - padding: 10px 20px; - display: block; -} -/* line 117, ../sass/screen.scss */ -header nav .lang-select a img { - display: block; - width: auto; - max-height: 20px; - vertical-align: middle; -} - -/* line 128, ../sass/screen.scss */ -article { - line-height: 1.4; -} -/* line 130, ../sass/screen.scss */ -article p, article ul { - margin: 0.4em 0; -} -/* line 133, ../sass/screen.scss */ -article ul { - padding-left: 20px; -} -/* line 135, ../sass/screen.scss */ -article ul li { - list-style: outside; -} -/* line 139, ../sass/screen.scss */ -article:last-child { - margin-bottom: 30px; -} - -/* line 144, ../sass/screen.scss */ -.container { - max-width: 1000px; - margin: 0 auto; - position: relative; -} -/* line 149, ../sass/screen.scss */ -.container .aside-wrap { - position: absolute; - top: 30px; - height: 100%; - width: 25%; - left: 6px; -} -/* line 156, ../sass/screen.scss */ -.container .aside-wrap .aside { - color: #222; - position: fixed; - position: sticky; - top: 5px; - width: 100%; - background: #FFC500; - padding: 15px; - box-shadow: -3px 3px 1px rgba(34, 6, 12, 0.3); -} -/* line 166, ../sass/screen.scss */ -.container .aside-wrap .aside h2 { - color: #000; -} -/* line 170, ../sass/screen.scss */ -.container .aside-wrap .aside .calendar { - margin: 0 auto; - display: block; -} -/* line 174, ../sass/screen.scss */ -.container .aside-wrap .aside .calendar:last-child { - margin-bottom: 40px; -} -/* line 179, ../sass/screen.scss */ -.container .aside-wrap .aside a { - color: #000; - text-decoration: none; - background-image: linear-gradient(to top, rgba(255, 255, 255, 0.7) 30%, rgba(255, 255, 255, 0) 30%); -} -/* line 183, ../sass/screen.scss */ -.container .aside-wrap .aside a:hover { - text-decoration: underline; -} -/* line 188, ../sass/screen.scss */ -.container .aside-wrap .aside .aside-content { - max-height: 70vh; - max-height: calc(80vh - 150px); - overflow-y: auto; - overflow-x: hidden; -} -/* line 196, ../sass/screen.scss */ -.container .aside-wrap .aside ul.directory li { - list-style: "*" inside; - padding-left: 10px; - text-indent: -10px; - margin-bottom: 5px; -} -/* line 206, ../sass/screen.scss */ -.container .content { - max-width: 900px; - margin-left: auto; - margin-right: 6px; -} -/* line 211, ../sass/screen.scss */ -.container .content h3 { - font-weight: bold; - font-size: 1.2em; -} -/* line 216, ../sass/screen.scss */ -.container .content h4 { - font-weight: bold; - font-style: italic; -} -/* line 221, ../sass/screen.scss */ -.container .content b, .container .content strong { - font-weight: bold; -} -/* line 225, ../sass/screen.scss */ -.container .content i { - font-style: italic; -} -/* line 229, ../sass/screen.scss */ -.container .content .intro, .container .content article.paragraph, .container .content article.entry { - line-height: 1.5; -} -/* line 232, ../sass/screen.scss */ -.container .content .intro a, .container .content article.paragraph a, .container .content article.entry a { - color: #000; - background-image: linear-gradient(to top, rgba(255, 197, 0, 0.8) 30%, rgba(255, 197, 0, 0) 30%); - text-decoration: none; -} -/* line 238, ../sass/screen.scss */ -.container .content .intro ul, .container .content .intro ol, .container .content article.paragraph ul, .container .content article.paragraph ol, .container .content article.entry ul, .container .content article.entry ol { - padding-left: 1em; -} -/* line 244, ../sass/screen.scss */ -.container .content .intro ul li, .container .content article.paragraph ul li, .container .content article.entry ul li { - list-style: disc; -} -/* line 247, ../sass/screen.scss */ -.container .content .intro ol li, .container .content article.paragraph ol li, .container .content article.entry ol li { - list-style: arabic; -} -/* line 252, ../sass/screen.scss */ -.container .content .intro { - border-bottom: 3px solid #7f7f7f; - margin: 20px 0; - margin-top: 5px; - padding: 15px 5px; -} -/* line 261, ../sass/screen.scss */ -.container .content section article { - background: #fff; - padding: 20px 30px; - box-shadow: -3px 3px 1px rgba(34, 6, 12, 0.3); - border: 1px solid rgba(34, 6, 12, 0.1); - border-radius: 2px; -} -/* line 269, ../sass/screen.scss */ -.container .content section article + h2 { - margin-top: 15px; -} -/* line 273, ../sass/screen.scss */ -.container .content section article + article { - margin-top: 25px; -} -/* line 277, ../sass/screen.scss */ -.container .content section .image { - margin: 15px 0; - text-align: center; - padding: 20px; -} -/* line 282, ../sass/screen.scss */ -.container .content section .image img { - max-width: 100%; - height: auto; - box-shadow: -7px 7px 1px rgba(34, 6, 12, 0.2); -} -/* line 290, ../sass/screen.scss */ -.container .content section.directory article.entry { - width: 90%; - max-width: 600px; - max-height: 100%; - position: relative; - margin-left: 6%; -} -/* line 297, ../sass/screen.scss */ -.container .content section.directory article.entry .entry-image { - display: block; - float: right; - width: 150px; - background: #fff; - box-shadow: -4px 4px 1px rgba(34, 6, 12, 0.2); - border-right: 1px solid rgba(34, 6, 12, 0.2); - border-top: 1px solid rgba(34, 6, 12, 0.2); - padding: 1px; - overflow: hidden; - margin-left: 10px; - margin-bottom: 10px; - transform: translateX(10px); - line-height: 0; -} -/* line 312, ../sass/screen.scss */ -.container .content section.directory article.entry .entry-image img { - width: auto; - height: auto; - max-width: 100%; - max-height: 100%; -} -/* line 320, ../sass/screen.scss */ -.container .content section.directory article.entry ul.links { - margin-top: 10px; - border-top: 1px solid #90001C; - padding-top: 10px; - font-weight: bold; -} -/* line 326, ../sass/screen.scss */ -.container .content section.directory article.entry ul.links .label { - font-weight: normal; -} -/* line 333, ../sass/screen.scss */ -.container .content section.actuhome { - display: flex; - flex-wrap: wrap; - justify-content: space-around; - align-items: top; -} -/* line 339, ../sass/screen.scss */ -.container .content section.actuhome article + article { - margin: 0; -} -/* line 343, ../sass/screen.scss */ -.container .content section.actuhome article.actu { - position: relative; - background: none; - box-shadow: none; - border: none; - max-width: 400px; - min-width: 300px; - flex: 1; -} -/* line 352, ../sass/screen.scss */ -.container .content section.actuhome article.actu .actu-header { - position: relative; - box-shadow: -4px 5px 1px rgba(34, 6, 12, 0.3); - border-right: 1px solid rgba(34, 6, 12, 0.2); - border-top: 1px solid rgba(34, 6, 12, 0.2); - min-height: 180px; - padding: 0; - margin: 0; - overflow: hidden; - background-size: cover; - background-position: center center; - background-repeat: no-repeat; -} -/* line 365, ../sass/screen.scss */ -.container .content section.actuhome article.actu .actu-header h2 { - position: absolute; - width: 100%; - bottom: 0; - left: 0; - padding: 5px; - text-shadow: 0 0 5px rgba(34, 6, 12, 0.8); - background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent); -} -/* line 373, ../sass/screen.scss */ -.container .content section.actuhome article.actu .actu-header h2 a { - color: #fff; -} -/* line 379, ../sass/screen.scss */ -.container .content section.actuhome article.actu .actu-misc { - background: white; - box-shadow: -2px 2px 1px rgba(34, 6, 12, 0.2); - border: 1px solid rgba(34, 6, 12, 0.2); - border-radius: 2px; - margin: 0 10px; - padding: 15px; - padding-top: 5px; -} -/* line 388, ../sass/screen.scss */ -.container .content section.actuhome article.actu .actu-misc .actu-minical { - display: block; -} -/* line 391, ../sass/screen.scss */ -.container .content section.actuhome article.actu .actu-misc .actu-dates { - display: block; - text-align: right; - font-size: 0.9em; -} -/* line 398, ../sass/screen.scss */ -.container .content section.actuhome article.actu .actu-overlay { - display: block; - background: none; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 5; - opacity: 0; -} -/* line 414, ../sass/screen.scss */ -.container .content section.actulist article.actu { - display: flex; - width: 100%; - padding: 0; -} -/* line 419, ../sass/screen.scss */ -.container .content section.actulist article.actu .actu-image { - width: 30%; - max-width: 200px; - background-size: cover; - background-position: center center; -} -/* line 425, ../sass/screen.scss */ -.container .content section.actulist article.actu .actu-infos { - padding: 15px; - flex: 1; -} -/* line 429, ../sass/screen.scss */ -.container .content section.actulist article.actu .actu-infos .actu-dates { - font-weight: bold; - font-size: 0.9em; -} -/* line 439, ../sass/screen.scss */ -.container .aside-wrap + .content { - max-width: 70%; -} - -/* line 444, ../sass/screen.scss */ -.calendar { - color: rgba(0, 0, 0, 0.8); - width: 200px; -} -/* line 448, ../sass/screen.scss */ -.calendar td, .calendar th { - text-align: center; - vertical-align: middle; - border: 2px solid transparent; - padding: 1px; -} -/* line 455, ../sass/screen.scss */ -.calendar th { - font-weight: bold; -} -/* line 459, ../sass/screen.scss */ -.calendar td { - font-size: 0.8em; - width: 28px; - height: 28px; -} -/* line 464, ../sass/screen.scss */ -.calendar td.out { - opacity: 0.3; -} -/* line 467, ../sass/screen.scss */ -.calendar td.today { - border-bottom-color: #000; -} -/* line 470, ../sass/screen.scss */ -.calendar td:nth-child(7), .calendar td:nth-child(6) { - background: rgba(0, 0, 0, 0.2); -} -/* line 473, ../sass/screen.scss */ -.calendar td.hasevent { - position: relative; - font-weight: bold; - color: #90001C; - font-size: 1em; -} -/* line 479, ../sass/screen.scss */ -.calendar td.hasevent > a { - padding: 3px; - color: #90001C !important; - background: none !important; -} -/* line 485, ../sass/screen.scss */ -.calendar td.hasevent ul.cal-events { - font-size: 0.9em; - text-align: left; - display: none; - position: absolute; - z-index: 2; - background: #fff; - width: 100px; - left: -30px; - margin-top: 10px; - padding: 5px; - background-color: #90001C; -} -/* line 498, ../sass/screen.scss */ -.calendar td.hasevent ul.cal-events .datename { - display: none; -} -/* line 501, ../sass/screen.scss */ -.calendar td.hasevent ul.cal-events:before { - top: -12px; - left: 38px; - content: ""; - position: absolute; - border: 6px solid transparent; - border-bottom-color: #90001C; -} -/* line 509, ../sass/screen.scss */ -.calendar td.hasevent ul.cal-events a { - color: #fff; - background: none !important; -} -/* line 515, ../sass/screen.scss */ -.calendar td.hasevent > a:hover { - background-color: #90001C; - color: #fff !important; -} -/* line 519, ../sass/screen.scss */ -.calendar td.hasevent > a:hover + ul.cal-events { - display: block; -} -/* line 527, ../sass/screen.scss */ -.calendar tr.head th { - position: relative; -} -/* line 529, ../sass/screen.scss */ -.calendar tr.head th a { - position: absolute; - display: block; - width: 150%; - padding: 5px; - top: -5px; - background: none !important; -} -/* line 537, ../sass/screen.scss */ -.calendar tr.head th:first-child a { - left: 0; - text-align: left; -} -/* line 541, ../sass/screen.scss */ -.calendar tr.head th:last-child a { - text-align: right; - right: 0; -} - -/* line 549, ../sass/screen.scss */ -#calendar-wrap .details { - border-top: 1px solid #90001C; - margin-top: 15px; - padding-top: 10px; -} -/* line 554, ../sass/screen.scss */ -#calendar-wrap .details li.datename { - font-weight: bold; - font-size: 1.1em; - margin-bottom: 5px; -} -/* line 555, ../sass/screen.scss */ -#calendar-wrap .details li.datename:after { - content: " :"; -} - -/* line 1, ../sass/_responsive.scss */ -header .minimenu { - display: none; -} - -@media only screen and (max-width: 600px) { - /* line 6, ../sass/_responsive.scss */ - header { - position: fixed; - top: 0; - left: 0; - z-index: 10; - width: 100%; - max-height: 100vh; - height: 60px; - overflow: hidden; - } - /* line 16, ../sass/_responsive.scss */ - header .minimenu { - display: block; - position: absolute; - right: 3px; - top: 3px; - } - /* line 23, ../sass/_responsive.scss */ - header section { - display: block; - } - /* line 25, ../sass/_responsive.scss */ - header section nav { - display: none; - } - - /* line 31, ../sass/_responsive.scss */ - header.expanded { - overflow: auto; - height: auto; - } - /* line 35, ../sass/_responsive.scss */ - header.expanded nav { - display: block; - text-align: center; - } - /* line 38, ../sass/_responsive.scss */ - header.expanded nav ul { - flex-wrap: wrap; - justify-content: right; - } - /* line 41, ../sass/_responsive.scss */ - header.expanded nav ul li > * { - padding: 18px; - } - - /* line 48, ../sass/_responsive.scss */ - .container { - margin-top: 65px; - } - /* line 51, ../sass/_responsive.scss */ - .container .content { - max-width: unset; - margin: 6px; - } - /* line 56, ../sass/_responsive.scss */ - .container .content section article { - padding: 10px; - } - /* line 60, ../sass/_responsive.scss */ - .container .content section .image { - padding: 0; - margin: 10px -6px; - } - /* line 65, ../sass/_responsive.scss */ - .container .content section.directory article.entry { - width: 100%; - margin-left: 0; - } - /* line 72, ../sass/_responsive.scss */ - .container .aside-wrap + .content { - max-width: unset; - margin-top: 120px; - } - /* line 77, ../sass/_responsive.scss */ - .container .aside-wrap { - z-index: 3; - top: 60px; - position: fixed; - width: 100%; - margin: 0; - height: auto; - left: 0; - } - /* line 86, ../sass/_responsive.scss */ - .container .aside-wrap .aside { - margin: 0; - padding: 0; - top: 0; - position: unset; - } - /* line 92, ../sass/_responsive.scss */ - .container .aside-wrap .aside > h2 { - position: relative; - cursor: pointer; - padding: 5px 10px; - } - /* line 96, ../sass/_responsive.scss */ - .container .aside-wrap .aside > h2:after { - content: "v"; - font-family: "Source Sans Pro", "sans-serif"; - font-weight: bold; - color: #CC9500; - position: absolute; - right: 10px; - } - /* line 106, ../sass/_responsive.scss */ - .container .aside-wrap .aside:not(.expanded) .aside-content { - display: none; - } - /* line 111, ../sass/_responsive.scss */ - .container .aside-wrap .aside ul { - text-align: center; - } - /* line 113, ../sass/_responsive.scss */ - .container .aside-wrap .aside ul li { - display: inline-block; - } - /* line 115, ../sass/_responsive.scss */ - .container .aside-wrap .aside ul li > * { - display: block; - padding: 15px; - } - /* line 122, ../sass/_responsive.scss */ - .container .aside-wrap .aside .aside-content { - max-height: calc(100vh - 110px); - } -} diff --git a/gestioncof/cms/static/cofcms/images/en.png b/gestioncof/cms/static/cofcms/images/en.png deleted file mode 100644 index f3a0d4da76c36748aad0d30529fdc2c491f74d46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3372 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

    !f0?ye{W> zd1b~vo*TcDi&yh*DR&5grED1-t3SdrA_byDp7Jl}M`@pJ^ayMK0000|gW!U_%O?XxI14-? ziy0WWg+Z8+Vb&Z8pdfpRr>`sfBQ`l+HjW?dm4|>rk|nMYCC>S|xv6<249-QVi6yBi z3gww484B*6z5(HleBwYwQ#@T9Lo)8Yy|9tDDM7^b;pgVIHcy?%BexFSuxes9U?~(R zOJGWpIoy{pk5$`7+E#t%Tity{d;JZ2_-|ZavSrUpecA1g8+U1?Y_&^|doH{*^OBwS z?Vyj(ITachm^g?e{*>%iP`EKI@W{6ODSu@@F#4)V9Z*XM;0C&n!PC{xWt~$(695T- BUI+jH diff --git a/gestioncof/cms/static/cofcms/images/minimenu.svg b/gestioncof/cms/static/cofcms/images/minimenu.svg deleted file mode 100644 index 46a31695..00000000 --- a/gestioncof/cms/static/cofcms/images/minimenu.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/gestioncof/cms/static/cofcms/js/calendar.js b/gestioncof/cms/static/cofcms/js/calendar.js deleted file mode 100644 index dbc1024d..00000000 --- a/gestioncof/cms/static/cofcms/js/calendar.js +++ /dev/null @@ -1,32 +0,0 @@ -$(function(){ - var mem = {}; - var ctt = $("#calendar-wrap"); - makeInteractive(); - - function makeInteractive () { - $(".cal-btn").on("click", loadCalendar); - $(".hasevent a").on("click", showEvents); - } - - function loadCalendar () { - var url = $(this).attr("cal-dest"); - if (mem[url] != undefined) { - ctt.html(mem[url]); - makeInteractive(); - return; - } - ctt.innerText = "Chargement..."; - ctt.load(url, function () { - mem[url] = this.innerHTML; - makeInteractive(); - }); - } - - function showEvents() { - ctt.find(".details").remove(); - ctt.append( - $("

    ", {class:"details"}) - .html(this.nextElementSibling.outerHTML) - ); - } -}); diff --git a/gestioncof/cms/static/cofcms/js/script.js b/gestioncof/cms/static/cofcms/js/script.js deleted file mode 100644 index 0c520e9f..00000000 --- a/gestioncof/cms/static/cofcms/js/script.js +++ /dev/null @@ -1,13 +0,0 @@ -$(function() { - $(".facteur").on("click", function(){ - var $this = $(this); - var sticker = $this.attr('data-mref') - .replace(/pont/g, '.') - .replace(/arbre/g, '@') - .replace(/(.)-/g, '$1'); - - var boite = $("", {href:"ma"+"il"+"to:"+sticker}).text(sticker); - $(this).before(boite) - .remove(); - }) -}); diff --git a/gestioncof/cms/static/cofcms/sass/_colors.scss b/gestioncof/cms/static/cofcms/sass/_colors.scss deleted file mode 100644 index e07429f8..00000000 --- a/gestioncof/cms/static/cofcms/sass/_colors.scss +++ /dev/null @@ -1,11 +0,0 @@ -$fond: #fefefe; -$bandeau: #5B0012; -$sousbandeau: #90001C; -$aside: #FFC500; -$titre: $sousbandeau; -$lien: #CC9500; -$headerlien: $fond; -$ombres: darken(desaturate($bandeau, 30%), 10%); - -$bodyfont: "Source Sans Pro", "sans-serif"; -$headfont: "Carter One", "serif"; diff --git a/gestioncof/cms/static/cofcms/sass/_responsive.scss b/gestioncof/cms/static/cofcms/sass/_responsive.scss deleted file mode 100644 index 7cf4cb29..00000000 --- a/gestioncof/cms/static/cofcms/sass/_responsive.scss +++ /dev/null @@ -1,128 +0,0 @@ -header .minimenu { - display: none; -} - -@media only screen and (max-width: 600px) { - header { - position: fixed; - top: 0; - left: 0; - z-index: 10; - width: 100%; - max-height: 100vh; - height: 60px; - overflow: hidden; - - .minimenu { - display: block; - position: absolute; - right: 3px; - top: 3px; - } - - section { - display: block; - nav { - display: none; - } - } - } - - header.expanded { - overflow: auto; - height: auto; - - nav { - display: block; - text-align: center; - ul { - flex-wrap: wrap; - justify-content: right; - li > * { - padding: 18px; - } - } - } - } - - .container { - margin-top: 65px; - - .content { - max-width: unset; - margin: 6px; - - section { - article { - padding: 10px; - } - - .image { - padding: 0; - margin: 10px -6px; - } - - &.directory article.entry { - width: 100%; - margin-left: 0; - } - } - } - - .aside-wrap + .content { - max-width: unset; - margin-top: 120px; - } - - .aside-wrap { - z-index: 3; - top: 60px; - position: fixed; - width: 100%; - margin: 0; - height: auto; - left: 0; - - .aside { - margin: 0; - padding: 0; - top: 0; - position: unset; - - & > h2 { - position: relative; - cursor: pointer; - padding: 5px 10px; - &:after { - content: "v"; - font-family: $bodyfont; - font-weight: bold; - color: $lien; - position: absolute; - right: 10px; - } - } - &:not(.expanded) { - .aside-content { - display: none; - } - } - - ul { - text-align: center; - li { - display: inline-block; - & > * { - display: block; - padding: 15px; - } - } - } - - .aside-content { - max-height: calc(100vh - 110px); - } - } - } - } -} diff --git a/gestioncof/cms/static/cofcms/sass/screen.scss b/gestioncof/cms/static/cofcms/sass/screen.scss deleted file mode 100644 index 2d7c1ad4..00000000 --- a/gestioncof/cms/static/cofcms/sass/screen.scss +++ /dev/null @@ -1,564 +0,0 @@ -/* Welcome to Compass. - * In this file you should write your main styles. (or centralize your imports) - * Import this file using the following HTML or equivalent: - * */ - -@import "compass/reset"; - -@import "_colors"; - -*, *:after, *:before { - box-sizing: border-box; -} - -body { - background: $fond; - font: 17px $bodyfont; - color: #000; -} - -header { - background: $bandeau; -} - -h1, h2 { - font-family: $headfont; -} - -h1 { - font-size: 2.3em; - color: $titre; -} - -h2 { - font-size: 1.6em; - color: desaturate(lighten($titre, 10%), 20%); -} - - -a { - color: $lien; - text-decoration: none; - font-weight: bold; - padding: 0 2px; - margin: 0 -2px; - &:hover { - text-decoration: underline; - } -} - -h2 a { - font-weight: inherit; - color: inherit; -} - -header { - a { - color: $headerlien; - &:hover { - text-decoration: none; - } - } - section { - display: flex; - width: 100%; - justify-content: space-between; - align-items: stretch; - - &.bottom-menu { - justify-content: space-around; - text-align: center; - background: $sousbandeau; - } - } - h1 { - padding: 0 15px; - } - nav { - display: inline-flex; - ul { - display: inline-flex; - flex-wrap: wrap; - li { - display: inline-block; - & > * { - display: block; - padding: 10px 15px; - font-weight: bold; - - &:hover { - background: darken($bandeau, 10%); - } - } - } - } - .lang-select { - display: inline-block; - height: 100%; - vertical-align: top; - position: relative; - - &:before { - content: ""; - color: #fff; - position: absolute; - top: 0; - left: 0; - border-left: 1px solid #fff; - height: calc(100% - 20px); - margin: 10px 0; - padding-left: 10px; - } - - a { - padding: 10px 20px; - display: block; - - img { - display: block; - width: auto; - max-height: 20px; - vertical-align: middle; - } - } - } - } -} - -article { - line-height: 1.4; - p, ul { - margin: 0.4em 0; - } - ul { - padding-left: 20px; - li { - list-style: outside; - } - } - &:last-child { - margin-bottom: 30px; - } -} - -.container { - max-width: 1000px; - margin: 0 auto; - position: relative; - - .aside-wrap { - position: absolute; - top: 30px; - height: 100%; - width: 25%; - left: 6px; - - .aside { - color: #222; - position: fixed; - position: sticky; - top: 5px; - width: 100%; - background: $aside; - padding: 15px; - box-shadow: -3px 3px 1px rgba($ombres, 0.3); - - h2 { - color: #000; - } - - .calendar { - margin: 0 auto; - display: block; - - &:last-child { - margin-bottom: 40px; - } - } - - a { - color: #000; - text-decoration: none; - background-image: linear-gradient(to top, rgba(#fff, 0.7) 30%, rgba(#fff, 0) 30%); - &:hover { - text-decoration: underline; - } - } - - .aside-content { - max-height: 70vh; - max-height: calc(80vh - 150px); - overflow-y: auto; - overflow-x: hidden; - } - - ul.directory { - li { - list-style: "*" inside; - padding-left: 10px; - text-indent: -10px; - margin-bottom: 5px; - } - } - } - } - - .content { - max-width: 900px; - margin-left: auto; - margin-right: 6px; - - h3 { - font-weight: bold; - font-size: 1.2em; - } - - h4 { - font-weight: bold; - font-style: italic; - } - - b, strong { - font-weight: bold; - } - - i { - font-style: italic; - } - - .intro, article.paragraph, article.entry { - line-height: 1.5; - - a { - color: #000; - background-image: linear-gradient(to top, rgba($aside, 0.8) 30%, rgba($aside, 0) 30%); - text-decoration: none; - } - - ul, ol { - padding-left: 1em; - li { - - } - } - ul li { - list-style: disc; - } - ol li { - list-style: arabic; - } - } - - .intro { - border-bottom: 3px solid darken($fond, 50%); - margin: 20px 0; - margin-top: 5px; - padding: 15px 5px; - } - - - section { - article { - background: #fff; - padding: 20px 30px;; - box-shadow: -3px 3px 1px rgba($ombres, 0.3); - border: 1px solid rgba($ombres, 0.1); - border-radius: 2px; - } - - article + h2 { - margin-top: 15px; - } - - article + article { - margin-top: 25px; - } - - .image { - margin: 15px 0; - text-align: center; - padding: 20px; - - img { - max-width: 100%; - height: auto; - box-shadow: -7px 7px 1px rgba($ombres, 0.2); - } - } - - &.directory { - article.entry { - width: 90%; - max-width: 600px; - max-height: 100%; - position: relative; - margin-left: 6%; - - .entry-image { - display: block; - float: right; - width: 150px; - background: #fff; - box-shadow: -4px 4px 1px rgba($ombres, 0.2); - border-right: 1px solid rgba($ombres, 0.2); - border-top: 1px solid rgba($ombres, 0.2); - padding: 1px; - overflow: hidden; - margin-left: 10px; - margin-bottom: 10px; - transform: translateX(10px); - line-height: 0; - - img { - width: auto; - height: auto; - max-width: 100%; - max-height: 100%; - } - } - - ul.links { - margin-top: 10px; - border-top: 1px solid $sousbandeau; - padding-top: 10px; - font-weight: bold; - - .label { - font-weight: normal; - } - } - } - } - - &.actuhome { - display: flex; - flex-wrap: wrap; - justify-content: space-around; - align-items: top; - - article + article { - margin: 0; - } - - article.actu { - position: relative; - background: none; - box-shadow: none; - border: none; - max-width: 400px; - min-width: 300px; - flex: 1; - - .actu-header { - position: relative; - box-shadow: -4px 5px 1px rgba($ombres, 0.3); - border-right: 1px solid rgba($ombres, 0.2); - border-top: 1px solid rgba($ombres, 0.2); - min-height: 180px; - padding: 0; - margin: 0; - overflow: hidden; - background-size: cover; - background-position: center center; - background-repeat: no-repeat; - - h2 { - position: absolute; - width: 100%; - bottom: 0; - left: 0; - padding: 5px; - text-shadow: 0 0 5px rgba($ombres, 0.8); - background: linear-gradient(to top, rgba(#000, 0.7), rgba(#000, 0)); - a { - color: #fff; - } - } - } - - .actu-misc { - background: lighten($fond, 15%); - box-shadow: -2px 2px 1px rgba($ombres, 0.2); - border: 1px solid rgba($ombres, 0.2); - border-radius: 2px; - margin: 0 10px; - padding: 15px; - padding-top: 5px; - - .actu-minical { - display: block; - } - .actu-dates { - display: block; - text-align: right; - font-size: 0.9em; - } - } - - .actu-overlay { - display: block; - background: none; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 5; - opacity: 0; - } - - } - } - - &.actulist { - article.actu { - display:flex; - width: 100%; - padding: 0; - - .actu-image { - width: 30%; - max-width: 200px; - background-size: cover; - background-position: center center; - } - .actu-infos { - padding: 15px; - flex: 1; - - .actu-dates { - font-weight: bold; - font-size: 0.9em; - } - } - } - } - } - } - - .aside-wrap + .content { - max-width: 70%; - } -} - -.calendar { - color: rgba(#000, 0.8); - width: 200px; - - td, th { - text-align: center; - vertical-align: middle; - border: 2px solid transparent; - padding: 1px; - } - - th { - font-weight: bold; - } - - td { - font-size: 0.8em; - width: 28px; - height: 28px; - - &.out { - opacity: 0.3; - } - &.today { - border-bottom-color: #000; - } - &:nth-child(7), &:nth-child(6) { - background: rgba(#000, 0.2); - } - &.hasevent { - position: relative; - font-weight: bold; - color: $sousbandeau; - font-size: 1em; - - & > a { - padding: 3px; - color: $sousbandeau !important; - background: none !important; - } - - ul.cal-events { - font-size: 0.9em; - text-align: left; - display: none; - position: absolute; - z-index: 2; - background: #fff; - width: 100px; - left: -30px; - margin-top: 10px; - padding: 5px; - background-color: $sousbandeau; - - .datename { - display: none; - } - &:before { - top: -12px; - left: 38px; - content: ""; - position: absolute; - border: 6px solid transparent; - border-bottom-color: $sousbandeau; - } - a { - color: #fff; - background: none !important; - } - } - - & > a:hover { - background-color: $sousbandeau; - color: #fff !important; - - & + ul.cal-events { - display: block; - } - } - } - } - - tr.head { - th { - position: relative; - a { - position: absolute; - display: block; - width: 150%; - padding: 5px; - top: -5px; - background: none !important; - } - &:first-child a { - left: 0; - text-align: left; - } - &:last-child a { - text-align: right; - right: 0; - } - } - } -} - -#calendar-wrap .details { - border-top: 1px solid $sousbandeau; - margin-top: 15px; - padding-top: 10px; - - li.datename { - &:after { - content: " :"; - } - font-weight: bold; - font-size: 1.1em; - margin-bottom: 5px; - } -} - -@import "_responsive"; diff --git a/gestioncof/cms/templates/cofcms/base.html b/gestioncof/cms/templates/cofcms/base.html deleted file mode 100644 index c420115f..00000000 --- a/gestioncof/cms/templates/cofcms/base.html +++ /dev/null @@ -1,62 +0,0 @@ -{% load static menu_tags wagtailuserbar i18n wagtailcore_tags %} - - - - - - {% block title %} - {% if page.seo_title %} - {{ page.seo_title }} | - {% elif page.title %} - {{ page.title }} | - {% endif %} - {% trans "Association des élèves de l'ENS Ulm" %} - {% endblock %} - - - - {% block extra_head %}{% endblock %} - - - - - - - -
    - {% block superaside %}{% endblock %} - -
    - {% block content %}{% endblock %} -
    -
    - {% wagtailuserbar %} - - diff --git a/gestioncof/cms/templates/cofcms/base_aside.html b/gestioncof/cms/templates/cofcms/base_aside.html deleted file mode 100644 index f27d9a06..00000000 --- a/gestioncof/cms/templates/cofcms/base_aside.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "cofcms/base.html" %} - -{% block superaside %} -
    -
    -

    {% block aside_title %}{% endblock %}

    -
    - {% block aside %}{% endblock %} -
    -
    -
    -{% endblock %} diff --git a/gestioncof/cms/templates/cofcms/base_nav.html b/gestioncof/cms/templates/cofcms/base_nav.html deleted file mode 100644 index f8b3fa65..00000000 --- a/gestioncof/cms/templates/cofcms/base_nav.html +++ /dev/null @@ -1,20 +0,0 @@ -{% load wagtailcore_tags i18n cofcms_tags %} - diff --git a/gestioncof/cms/templates/cofcms/calendar.html b/gestioncof/cms/templates/cofcms/calendar.html deleted file mode 100644 index c06a23c7..00000000 --- a/gestioncof/cms/templates/cofcms/calendar.html +++ /dev/null @@ -1,28 +0,0 @@ -{% load wagtailcore_tags wagtailroutablepage_tags static i18n %} - - - - - - - - {% blocktrans %}{% endblocktrans %} - {% for week in weeks %} - - {% for day in week %} - - {% endfor %} - - {% endfor %} - -
    <{{ this_month|date:"F Y" }}>
    LMMJVSD
    - {% if day.events %} - {{ day.day }} -
      -
    • {% trans "Le " %}{{ day.date|date }}
    • - {% for event in day.events %} -
    • {{ event.title }}
    • - {% endfor %} -
    - {% else %}{{ day.day }}{% endif %} -
    diff --git a/gestioncof/cms/templates/cofcms/calendar_raw.html b/gestioncof/cms/templates/cofcms/calendar_raw.html deleted file mode 100644 index 9ee1aa79..00000000 --- a/gestioncof/cms/templates/cofcms/calendar_raw.html +++ /dev/null @@ -1,2 +0,0 @@ -{% load cofcms_tags %} -{% calendar month year %} diff --git a/gestioncof/cms/templates/cofcms/cof_actu_index_page.html b/gestioncof/cms/templates/cofcms/cof_actu_index_page.html deleted file mode 100644 index 4508a66c..00000000 --- a/gestioncof/cms/templates/cofcms/cof_actu_index_page.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends "cofcms/base_aside.html" %} -{% load wagtailimages_tags cofcms_tags wagtailcore_tags static i18n %} - -{% block extra_head %} - {{ block.super }} - - -{% endblock %} - -{% block aside_title %}{% trans "Calendrier" %}{% endblock %} -{% block aside %} -
    - {% calendar %} -
    -{% endblock %} - -{% block content %} -
    -

    {{ page.title }}

    -
    {{ page.introduction|richtext }}
    -
    - -
    - {% if actus.has_previous %} - {% trans "Actualités plus récentes" %} - {% endif %} - {% if actus.has_next %} - {% trans "Actualités plus anciennes" %} - {% endif %} - - {% for actu in page.actus %} - - {% endfor %} - - {% if actus.has_previous %} - {% trans "Actualités plus récentes" %} - {% endif %} - {% if actus.has_next %} - {% trans "Actualités plus anciennes" %} - {% endif %} -
    -{% endblock %} diff --git a/gestioncof/cms/templates/cofcms/cof_actu_page.html b/gestioncof/cms/templates/cofcms/cof_actu_page.html deleted file mode 100644 index 5cd88134..00000000 --- a/gestioncof/cms/templates/cofcms/cof_actu_page.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "cofcms/base.html" %} -{% load wagtailcore_tags wagtailimages_tags cofcms_tags i18n %} - -{% block content %} -
    -

    {{ page.title }}

    -

    {% trans "A lieu" %} {{ page|dates }}

    -

    {{ page.chapo }}

    -
    - -
    -
    {% image page.image width-700 %}
    -
    - {{ page.body|richtext }} -
    -
    -{% endblock %} diff --git a/gestioncof/cms/templates/cofcms/cof_directory_entry_page.html b/gestioncof/cms/templates/cofcms/cof_directory_entry_page.html deleted file mode 100644 index 21090f25..00000000 --- a/gestioncof/cms/templates/cofcms/cof_directory_entry_page.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "cofcms/base.html" %} - -{% block extra_head %} - -{% endblock %} - -{% block content %} -

    - Comment t'es arrivé⋅e ici toi ? -

    -{% endblock %} diff --git a/gestioncof/cms/templates/cofcms/cof_directory_page.html b/gestioncof/cms/templates/cofcms/cof_directory_page.html deleted file mode 100644 index da0fa3ce..00000000 --- a/gestioncof/cms/templates/cofcms/cof_directory_page.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends "cofcms/base_aside.html" %} -{% load wagtailcore_tags wagtailimages_tags cofcms_tags static i18n %} - -{% block extra_head %} - {{ block.super }} - - -{% endblock %} -{% block aside_title %}{% trans "Accès rapide" %}{% endblock %} -{% block aside %} - -{% endblock %} - -{% block content %} -
    -

    {{ page.title }}

    -
    {{ page.introduction|richtext }}
    -
    - -
    - {% for entry in page.entries %} -
    - {% if entry.image %} -
    {% image entry.image width-150 class="entry-img" %}
    - {% endif %} -

    {{ entry.title }}

    -
    {{ entry.body|richtext }}
    - {% if entry.links %} - - {% endif %} -
    - {% endfor %} -
    -{% endblock %} diff --git a/gestioncof/cms/templates/cofcms/cof_page.html b/gestioncof/cms/templates/cofcms/cof_page.html deleted file mode 100644 index 278b39e5..00000000 --- a/gestioncof/cms/templates/cofcms/cof_page.html +++ /dev/null @@ -1,29 +0,0 @@ -{% extends "cofcms/base.html" %} -{% load wagtailcore_tags wagtailimages_tags cofcms_tags %} - -{% block content %} -
    -

    {{ page.title }}

    -
    {{ page.introduction|richtext }}
    -
    - -
    - {% for block in page.body %} - {% if block.block_type == "heading" %} -

    {{ block.value }}

    - {% elif block.block_type == "paragraph" %} -
    - {{ block.value|richtext }} -
    - {% elif block.block_type == "image" %} -
    - {% image block.value width-800 %} -
    - {% elif block.block_type == "iframe" %} -
    - -
    - {% endif %} - {% endfor %} -
    -{% endblock %} diff --git a/gestioncof/cms/templates/cofcms/cof_root_page.html b/gestioncof/cms/templates/cofcms/cof_root_page.html deleted file mode 100644 index 27a39494..00000000 --- a/gestioncof/cms/templates/cofcms/cof_root_page.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "cofcms/base_aside.html" %} -{% load static cofcms_tags wagtailimages_tags wagtailroutablepage_tags i18n wagtailcore_tags %} - - -{% block extra_head %} - {{ block.super }} - - -{% endblock %} - -{% block aside_title %}{% trans "Agenda" %}{% endblock %} -{% block aside %} -
    - {% calendar %} -
    -{% endblock %} - -{% block content %} -
    -

    {{ page.title }}

    -
    {{ page.introduction|richtext }}
    -
    - -
    - {% for actu in page.actus %} -
    - -
    - {% if actu.is_event %} - {% mini_calendar actu %}{{ actu|dates }} - {% else %} - {{ actu.body|richtext|truncatewords_html:10 }} - {% endif %} -
    - -
    - {% endfor %} -
    -{% endblock %} diff --git a/gestioncof/cms/templates/cofcms/mini_calendar.html b/gestioncof/cms/templates/cofcms/mini_calendar.html deleted file mode 100644 index fc750631..00000000 --- a/gestioncof/cms/templates/cofcms/mini_calendar.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - {% for day in days %} - - {% endfor %} - - -
    - {{ day.day }} -
    diff --git a/gestioncof/cms/templates/cofcms/sympa.html b/gestioncof/cms/templates/cofcms/sympa.html deleted file mode 100644 index 16a76f1b..00000000 --- a/gestioncof/cms/templates/cofcms/sympa.html +++ /dev/null @@ -1,32 +0,0 @@ -{% load i18n static %} - -{% if redirect %} -

    {% trans "Redirection vers" %} {{ redirect }}

    - -{% else %} - - - - - - -
    -
    - {% csrf_token %} -

    {% blocktrans %}Comment s'appellent les poissons du bassin ?{% endblocktrans %}

    - {% for field in form %} - {% for error in field.errors %} -

    {% trans error %}

    - {% endfor %} -

    - {{ field }} -

    - {% endfor %} - -
    -
    - - -{% endif %} diff --git a/gestioncof/cms/templatetags/__init__.py b/gestioncof/cms/templatetags/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioncof/cms/templatetags/cofcms_tags.py b/gestioncof/cms/templatetags/cofcms_tags.py deleted file mode 100644 index f9e62aed..00000000 --- a/gestioncof/cms/templatetags/cofcms_tags.py +++ /dev/null @@ -1,167 +0,0 @@ -from datetime import date, timedelta - -from django import template -from django.utils import formats, timezone -from django.utils.translation import ugettext as _ - -from ..models import COFActuPage, COFRootPage - -register = template.Library() - - -@register.filter() -def obfuscate_mail(value): - val = value.replace("", "-").replace("@", "arbre").replace(".", "pont")[1:] - return val - - -@register.inclusion_tag("cofcms/calendar.html", takes_context=True) -def calendar(context, month=None, year=None): - now = timezone.now() - if month is None: - month_start = date(now.year, now.month, 1) - else: - month_start = date(year, month, 1) - next_month = month_start + timedelta(days=32) - next_month = date(next_month.year, next_month.month, 1) - prev_month = month_start - timedelta(days=2) - month_prestart = month_start - timedelta(days=month_start.weekday()) - month_postend = next_month + timedelta(days=(next_month.weekday() + 6) % 7) - events = ( - COFActuPage.objects.live() - .filter(date_start__range=[month_prestart, month_postend], is_event=True) - .order_by("-date_start") - ) - events = list(events) - weeks = [] - curday = month_prestart - deltaday = timedelta(days=1) - while curday < next_month and len(weeks) < 10: - week = [] - for k in range(7): - curevents = [] - for k in range(len(events) - 1, -1, -1): - e = events[k] - if e.date_start.date() > curday: - break - if (e.date_start if e.date_end is None else e.date_end).date() < curday: - del events[k] - else: - curevents.append(e) - day = { - "day": curday.day, - "date": curday, - "class": ( - ("today " if curday == now.date() else "") - + ( - "in " - if ( - curday.month == month_start.month - and curday.year == month_start.year - ) - else "out " - ) - + ("hasevent" if len(curevents) > 0 else "") - ), - "events": curevents, - } - week.append(day) - curday += deltaday - weeks.append(week) - - # Calendar next/prev urls - try: - utilpage = COFRootPage.objects.live()[0] - except COFRootPage.DoesNotExist: - utilpage = None - request = context["request"] - burl = utilpage.get_url(request) - prev_url = burl + utilpage.reverse_subpage( - "calendar", args=[str(prev_month.year), str(prev_month.month)] - ) - next_url = burl + utilpage.reverse_subpage( - "calendar", args=[str(next_month.year), str(next_month.month)] - ) - context.push( - { - "events": events, - "weeks": weeks, - "this_month": month_start, - "prev_month": prev_url, - "next_month": next_url, - } - ) - return context - - -@register.inclusion_tag("cofcms/mini_calendar.html") -def mini_calendar(event): - days = [] - today = timezone.now().date() - date_start = event.date_start.date() - date_end = event.date_end.date() if event.date_end else date_start - week_start = date_start - timedelta(days=date_start.weekday()) - curday = week_start - for i in range(7): - days.append( - { - "day": curday.day, - "hasevent": curday >= date_start and curday <= date_end, - "today": curday == today, - } - ) - curday += timedelta(days=1) - return {"days": days} - - -@register.filter() -def dates(event): - def factorize_suffix(a, b): - i = -1 - imin = -min(len(a), len(b)) - while i > imin and a[i] == b[i]: - i -= 1 - if i == -1: - return (a, b, "") - else: - return (a[: i + 1], b[: i + 1], a[i + 1 :]) - - datestart_string = formats.date_format(event.date_start) - timestart_string = formats.time_format(event.date_start) - if event.date_end: - if event.date_end.date() == event.date_start.date(): - if event.all_day: - return _("le {datestart}").format(datestart=datestart_string) - else: - return _("le {datestart} de {timestart} à {timeend}").format( - datestart=datestart_string, - timestart=timestart_string, - timeend=formats.time_format(event.date_end), - ) - else: - dateend_string = formats.date_format(event.date_end) - diffstart, diffend, common = factorize_suffix( - datestart_string, dateend_string - ) - if event.all_day: - return _("du {datestart} au {dateend}{common}").format( - datestart=diffstart, dateend=diffend, common=common - ) - - else: - return _( - "du {datestart}{common} à {timestart} au {dateend} à {timeend}" - ).format( - datestart=diffstart, - common=common, - timestart=timestart_string, - dateend=diffend, - timeend=formats.time_format(event.date_end), - ) - else: - if event.all_day: - return _("le {datestart}").format(datestart=datestart_string) - else: - return _("le {datestart} à {timestart}").format( - datestart=datestart_string, timestart=timestart_string - ) diff --git a/gestioncof/cms/translation.py b/gestioncof/cms/translation.py deleted file mode 100644 index ea5a6c9d..00000000 --- a/gestioncof/cms/translation.py +++ /dev/null @@ -1,41 +0,0 @@ -from modeltranslation.decorators import register -from modeltranslation.translator import TranslationOptions - -from .models import ( - COFActuIndexPage, - COFActuPage, - COFDirectoryEntryPage, - COFDirectoryPage, - COFPage, - COFRootPage, -) - - -@register(COFRootPage) -class COFRootPageTr(TranslationOptions): - fields = ("introduction",) - - -@register(COFPage) -class COFPageTr(TranslationOptions): - fields = ("body",) - - -@register(COFActuIndexPage) -class COFActuIndexPageTr(TranslationOptions): - fields = () - - -@register(COFActuPage) -class COFActuPageTr(TranslationOptions): - fields = ("chapo", "body") - - -@register(COFDirectoryPage) -class COFDirectoryPageTr(TranslationOptions): - fields = ("introduction",) - - -@register(COFDirectoryEntryPage) -class COFDirectoryEntryPageTr(TranslationOptions): - fields = ("body", "links") diff --git a/gestioncof/cms/urls.py b/gestioncof/cms/urls.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioncof/cms/views.py b/gestioncof/cms/views.py deleted file mode 100644 index 83a67ae6..00000000 --- a/gestioncof/cms/views.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.shortcuts import render - -from gestioncof.cms.forms import CaptchaForm - - -def raw_calendar_view(request, year, month): - return render(request, "cofcms/calendar_raw.html", {"month": month, "year": year}) - - -def sympa_captcha_form_view(request): - if request.method == "POST": - form = CaptchaForm(request.POST) - if form.is_valid(): - return render( - request, - "cofcms/sympa.html", - {"redirect": "https://lists.ens.fr/wws/lists/"}, - ) - else: - form = CaptchaForm() - - return render(request, "cofcms/sympa.html", {"form": form}) diff --git a/gestioncof/csv_views.py b/gestioncof/csv_views.py deleted file mode 100644 index 3c163091..00000000 --- a/gestioncof/csv_views.py +++ /dev/null @@ -1,76 +0,0 @@ -import csv - -from django.apps import apps -from django.http import HttpResponse, HttpResponseForbidden -from django.template.defaultfilters import slugify - - -def export(qs, fields=None): - model = qs.model - response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = "attachment; filename=%s.csv" % slugify( - model.__name__ - ) - writer = csv.writer(response) - # Write headers to CSV file - if fields: - headers = fields - else: - headers = [] - for field in model._meta.fields: - headers.append(field.name) - writer.writerow(headers) - # Write data to CSV file - for obj in qs: - row = [] - for field in headers: - if field in headers: - val = getattr(obj, field) - if callable(val): - val = val() - row.append(val) - writer.writerow(row) - # Return CSV file to browser as download - return response - - -def admin_list_export( - request, model_name, app_label, queryset=None, fields=None, list_display=True -): - """ - Put the following line in your urls.py BEFORE your admin include: - - path( - "admin///csv/", - csv_views.admin_list_export, - {"fields": ["username"]}, - ), - """ - if not request.user.is_staff: - return HttpResponseForbidden() - if not queryset: - model = apps.get_model(app_label, model_name) - queryset = model.objects.all() - queryset = queryset.filter(profile__is_cof=True) - if not fields: - if list_display and len(queryset.model._meta.admin.list_display) > 1: - fields = queryset.model._meta.admin.list_display - else: - fields = None - return export(queryset, fields) - """ - Create your own change_list.html for your admin view and put something - like this in it: - {% block object-tools %} - - {% endblock %} - """ diff --git a/gestioncof/decorators.py b/gestioncof/decorators.py deleted file mode 100644 index e227ed45..00000000 --- a/gestioncof/decorators.py +++ /dev/null @@ -1,81 +0,0 @@ -import logging -from functools import wraps - -from django.contrib.auth.decorators import login_required -from django.contrib.auth.mixins import PermissionRequiredMixin -from django.shortcuts import render - -logger = logging.getLogger(__name__) - - -def cof_required(view_func): - """Décorateur qui vérifie que l'utilisateur est connecté et membre du COF. - - - Si l'utilisteur n'est pas connecté, il est redirigé vers la page de - connexion - - Si l'utilisateur est connecté mais pas membre du COF, il obtient une - page d'erreur lui demandant de s'inscrire au COF - """ - - def is_cof(user): - try: - return user.profile.is_cof - except AttributeError: - return False - - @wraps(view_func) - def _wrapped_view(request, *args, **kwargs): - if is_cof(request.user): - return view_func(request, *args, **kwargs) - - return render(request, "cof-denied.html", status=403) - - return login_required(_wrapped_view) - - -def buro_required(view_func): - """Décorateur qui vérifie que l'utilisateur est connecté et membre du burô. - - - Si l'utilisateur n'est pas connecté, il est redirigé vers la page de - connexion - - Si l'utilisateur est connecté mais pas membre du burô, il obtient une - page d'erreur 403 Forbidden - """ - - def is_buro(user): - try: - return user.profile.is_buro - except AttributeError: - return False - - @wraps(view_func) - def _wrapped_view(request, *args, **kwargs): - if is_buro(request.user): - return view_func(request, *args, **kwargs) - - return render(request, "buro-denied.html", status=403) - - return login_required(_wrapped_view) - - -class CofRequiredMixin(PermissionRequiredMixin): - def has_permission(self): - if not self.request.user.is_authenticated: - return False - try: - return self.request.user.profile.is_cof - except AttributeError: - return False - - -class BuroRequiredMixin(PermissionRequiredMixin): - def has_permission(self): - if not self.request.user.is_authenticated: - return False - try: - return self.request.user.profile.is_buro - except AttributeError: - logger.error( - "L'utilisateur %s n'a pas de profil !", self.request.user.username - ) - return False diff --git a/gestioncof/fixtures/gestion.json b/gestioncof/fixtures/gestion.json deleted file mode 100644 index ae6466f8..00000000 --- a/gestioncof/fixtures/gestion.json +++ /dev/null @@ -1,199 +0,0 @@ -[ -{ - "fields": { - "old": false, - "details": "Il nous casse les oreilles, qu'est ce qu'on en fait\u00a0?", - "survey_open": true, - "title": "Sort du barde" - }, - "model": "gestioncof.survey", - "pk": 1 -}, -{ - "fields": { - "question": "Sanction s'il chante", - "survey": 1, - "multi_answers": true - }, - "model": "gestioncof.surveyquestion", - "pk": 1 -}, -{ - "fields": { - "question": "Est-ce qu'on le garde\u00a0?", - "survey": 1, - "multi_answers": false - }, - "model": "gestioncof.surveyquestion", - "pk": 2 -}, -{ - "fields": { - "answer": "On l'ernestise", - "survey_question": 1 - }, - "model": "gestioncof.surveyquestionanswer", - "pk": 1 -}, -{ - "fields": { - "answer": "On ligote", - "survey_question": 1 - }, - "model": "gestioncof.surveyquestionanswer", - "pk": 2 -}, -{ - "fields": { - "answer": "On le prive de banquet", - "survey_question": 1 - }, - "model": "gestioncof.surveyquestionanswer", - "pk": 3 -}, -{ - "fields": { - "answer": "Oui", - "survey_question": 2 - }, - "model": "gestioncof.surveyquestionanswer", - "pk": 4 -}, -{ - "fields": { - "answer": "Non", - "survey_question": 2 - }, - "model": "gestioncof.surveyquestionanswer", - "pk": 5 -}, -{ - "fields": { - "old": false, - "description": "On va casser du romain.", - "end_date": "2016-09-12T00:00:00Z", - "title": "Bataille de Gergovie", - "image": "", - "location": "Gergovie", - "registration_open": true, - "start_date": "2016-09-09T00:00:00Z" - }, - "model": "gestioncof.event", - "pk": 1 -}, -{ - "fields": { - "default": "", - "event": 1, - "fieldtype": "text", - "name": "Commentaires" - }, - "model": "gestioncof.eventcommentfield", - "pk": 1 -}, -{ - "fields": { - "multi_choices": true, - "event": 1, - "name": "Potion magique" - }, - "model": "gestioncof.eventoption", - "pk": 1 -}, -{ - "fields": { - "event_option": 1, - "value": "Je suis alergique" - }, - "model": "gestioncof.eventoptionchoice", - "pk": 1 -}, -{ - "fields": { - "event_option": 1, - "value": "J'en veux" - }, - "model": "gestioncof.eventoptionchoice", - "pk": 2 -}, -{ - "fields": { - "event_option": 1, - "value": "Je suis tomb\u00e9 dans la marmite quand j'\u00e9tais petit" - }, - "model": "gestioncof.eventoptionchoice", - "pk": 3 -}, -{ - "fields": { - "name": "Bagarre" - }, - "model": "gestioncof.petitcourssubject", - "pk": 1 -}, -{ - "fields": { - "name": "Lancer de menhir" - }, - "model": "gestioncof.petitcourssubject", - "pk": 2 -}, -{ - "fields": { - "name": "Pr\u00e9paration de potions" - }, - "model": "gestioncof.petitcourssubject", - "pk": 3 -}, -{ - "fields": { - "name": "Chant" - }, - "model": "gestioncof.petitcourssubject", - "pk": 4 -}, -{ - "fields": { - "traitee": false, - "remarques": "En grande difficult\u00e9", - "quand": "weekend (dimanche) / soir apr\u00e8s les cours", - "name": "Jules C\u00e9sar", - "created": "2016-07-15T11:12:35Z", - "niveau": "prepa1styear", - "agrege_requis": false, - "phone": "", - "traitee_par": null, - "matieres": [ - 1 - ], - "lieu": "Al\u00e9sia", - "freq": "3 fois / semaine", - "email": "jules.cesar@polytechnique.edu", - "processed": null - }, - "model": "gestioncof.petitcoursdemande", - "pk": 1 -}, -{ - "fields": { - "traitee": false, - "remarques": "", - "quand": "Weekends", - "name": "Jules C\u00e9sar", - "created": "2016-07-15T11:13:26Z", - "niveau": "lycee", - "agrege_requis": true, - "phone": "", - "traitee_par": null, - "matieres": [ - 3 - ], - "lieu": "\u00e0 domicile", - "freq": "toutes les semaines", - "email": "jules.cesar@polytechnique.edu", - "processed": null - }, - "model": "gestioncof.petitcoursdemande", - "pk": 2 -} -] diff --git a/gestioncof/fixtures/sites.json b/gestioncof/fixtures/sites.json deleted file mode 100644 index de965f9b..00000000 --- a/gestioncof/fixtures/sites.json +++ /dev/null @@ -1,10 +0,0 @@ -[ -{ - "fields": { - "domain": "localhost:8000", - "name": "GestioCOF - dev - local" - }, - "model": "sites.site", - "pk": 1 -} -] diff --git a/gestioncof/forms.py b/gestioncof/forms.py deleted file mode 100644 index 2a57f970..00000000 --- a/gestioncof/forms.py +++ /dev/null @@ -1,460 +0,0 @@ -from django import forms -from django.contrib.auth import get_user_model -from django.contrib.auth.forms import AuthenticationForm -from django.forms.formsets import BaseFormSet, formset_factory -from django.forms.widgets import CheckboxSelectMultiple, RadioSelect -from django.utils.translation import ugettext_lazy as _ -from djconfig.forms import ConfigForm - -from bda.models import Spectacle -from gestioncof.models import CalendarSubscription, Club, CofProfile, EventCommentValue -from gestioncof.widgets import TriStateCheckbox - -User = get_user_model() - - -class ExteAuthenticationForm(AuthenticationForm): - """ - Formulaire pour l'authentification des extés : renvoie une erreur si la personne - qui essaie de s'authentifier n'a pas de mot de passe. L'erreur dépend de si la - personne a un login clipper ou non. - """ - - def clean(self): - username = self.cleaned_data.get("username") - - if username is not None: - try: - user = User.objects.get(username=username) - if not user.has_usable_password() or user.password in ("", "!"): - profile, created = CofProfile.objects.get_or_create(user=user) - if profile.login_clipper: - raise forms.ValidationError( - _("L'utilisateur·ice a un login clipper !"), - code="has_clipper", - ) - else: - raise forms.ValidationError( - _("L'utilisateur·ice n'a pas de mot de passe"), - code="no_password", - ) - except User.DoesNotExist: - pass - - return super().clean() - - -class EventForm(forms.Form): - def __init__(self, *args, **kwargs): - event = kwargs.pop("event") - self.event = event - current_choices = kwargs.pop("current_choices", None) - super().__init__(*args, **kwargs) - choices = {} - if current_choices: - for choice in current_choices.all(): - if choice.event_option.id not in choices: - choices[choice.event_option.id] = [choice.id] - else: - choices[choice.event_option.id].append(choice.id) - all_choices = choices - for option in event.options.all(): - choices = [(choice.id, choice.value) for choice in option.choices.all()] - if option.multi_choices: - initial = [] if option.id not in all_choices else all_choices[option.id] - field = forms.MultipleChoiceField( - label=option.name, - choices=choices, - widget=CheckboxSelectMultiple, - required=False, - initial=initial, - ) - else: - initial = ( - None if option.id not in all_choices else all_choices[option.id][0] - ) - field = forms.ChoiceField( - label=option.name, - choices=choices, - widget=RadioSelect, - required=False, - initial=initial, - ) - field.option_id = option.id - self.fields["option_%d" % option.id] = field - - def choices(self): - for name, value in self.cleaned_data.items(): - if name.startswith("option_"): - yield (self.fields[name].option_id, value) - - -class SurveyForm(forms.Form): - def __init__(self, *args, **kwargs): - survey = kwargs.pop("survey") - current_answers = kwargs.pop("current_answers", None) - super().__init__(*args, **kwargs) - answers = {} - if current_answers: - for answer in current_answers.all(): - if answer.survey_question.id not in answers: - answers[answer.survey_question.id] = [answer.id] - else: - answers[answer.survey_question.id].append(answer.id) - for question in survey.questions.all(): - choices = [(answer.id, answer.answer) for answer in question.answers.all()] - if question.multi_answers: - initial = [] if question.id not in answers else answers[question.id] - field = forms.MultipleChoiceField( - label=question.question, - choices=choices, - widget=CheckboxSelectMultiple, - required=False, - initial=initial, - ) - else: - initial = ( - None if question.id not in answers else answers[question.id][0] - ) - field = forms.ChoiceField( - label=question.question, - choices=choices, - widget=RadioSelect, - required=False, - initial=initial, - ) - field.question_id = question.id - self.fields["question_%d" % question.id] = field - - def answers(self): - for name, value in self.cleaned_data.items(): - if name.startswith("question_"): - yield (self.fields[name].question_id, value) - - -class SurveyStatusFilterForm(forms.Form): - def __init__(self, *args, **kwargs): - survey = kwargs.pop("survey") - super().__init__(*args, **kwargs) - for question in survey.questions.all(): - for answer in question.answers.all(): - name = "question_%d_answer_%d" % (question.id, answer.id) - if self.is_bound and self.data.get(self.add_prefix(name), None): - initial = self.data.get(self.add_prefix(name), None) - else: - initial = "none" - field = forms.ChoiceField( - label="%s : %s" % (question.question, answer.answer), - choices=[("yes", "yes"), ("no", "no"), ("none", "none")], - widget=TriStateCheckbox, - required=False, - initial=initial, - ) - field.question_id = question.id - field.answer_id = answer.id - self.fields[name] = field - - def filters(self): - for name, value in self.cleaned_data.items(): - if name.startswith("question_"): - yield ( - self.fields[name].question_id, - self.fields[name].answer_id, - value, - ) - - -class EventStatusFilterForm(forms.Form): - def __init__(self, *args, **kwargs): - event = kwargs.pop("event") - super().__init__(*args, **kwargs) - for option in event.options.all(): - for choice in option.choices.all(): - name = "option_%d_choice_%d" % (option.id, choice.id) - if self.is_bound and self.data.get(self.add_prefix(name), None): - initial = self.data.get(self.add_prefix(name), None) - else: - initial = "none" - field = forms.ChoiceField( - label="%s : %s" % (option.name, choice.value), - choices=[("yes", "yes"), ("no", "no"), ("none", "none")], - widget=TriStateCheckbox, - required=False, - initial=initial, - ) - field.option_id = option.id - field.choice_id = choice.id - self.fields[name] = field - # has_paid - name = "event_has_paid" - if self.is_bound and self.data.get(self.add_prefix(name), None): - initial = self.data.get(self.add_prefix(name), None) - else: - initial = "none" - field = forms.ChoiceField( - label="Événement payé", - choices=[("yes", "yes"), ("no", "no"), ("none", "none")], - widget=TriStateCheckbox, - required=False, - initial=initial, - ) - self.fields[name] = field - - def filters(self): - for name, value in self.cleaned_data.items(): - if name.startswith("option_"): - yield (self.fields[name].option_id, self.fields[name].choice_id, value) - elif name == "event_has_paid": - yield ("has_paid", None, value) - - -class UserForm(forms.ModelForm): - class Meta: - model = User - fields = ["first_name", "last_name", "email"] - - -class PhoneForm(forms.ModelForm): - class Meta: - model = CofProfile - fields = ["phone"] - - -class ProfileForm(forms.ModelForm): - class Meta: - model = CofProfile - fields = [ - "phone", - "mailing_cof", - "mailing_bda", - "mailing_bda_revente", - "mailing_unernestaparis", - ] - - -class RegistrationUserForm(forms.ModelForm): - def __init__(self, *args, **kw): - super().__init__(*args, **kw) - self.fields["username"].help_text = "" - - class Meta: - model = User - fields = ("username", "first_name", "last_name", "email") - - -class RegistrationPassUserForm(RegistrationUserForm): - """ - Formulaire pour changer le mot de passe d'un utilisateur. - """ - - password1 = forms.CharField(label=_("Mot de passe"), widget=forms.PasswordInput) - password2 = forms.CharField( - label=_("Confirmation du mot de passe"), widget=forms.PasswordInput - ) - - def clean_password2(self): - pass1 = self.cleaned_data["password1"] - pass2 = self.cleaned_data["password2"] - if pass1 and pass2: - if pass1 != pass2: - raise forms.ValidationError(_("Mots de passe non identiques.")) - return pass2 - - def save(self, commit=True, *args, **kwargs): - user = super().save(commit, *args, **kwargs) - user.set_password(self.cleaned_data["password2"]) - if commit: - user.save() - return user - - -class RegistrationProfileForm(forms.ModelForm): - def __init__(self, *args, **kw): - super().__init__(*args, **kw) - self.fields["mailing_cof"].initial = True - self.fields["mailing_bda"].initial = True - self.fields["mailing_bda_revente"].initial = True - self.fields["mailing_unernestaparis"].initial = True - - self.fields.keyOrder = [ - "login_clipper", - "phone", - "occupation", - "departement", - "is_cof", - "type_cotiz", - "mailing_cof", - "mailing_bda", - "mailing_bda_revente", - "mailing_unernestaparis", - "comments", - ] - - class Meta: - model = CofProfile - fields = ( - "login_clipper", - "phone", - "occupation", - "departement", - "is_cof", - "type_cotiz", - "mailing_cof", - "mailing_bda", - "mailing_bda_revente", - "mailing_unernestaparis", - "comments", - ) - - -STATUS_CHOICES = ( - ("no", "Non"), - ("wait", "Oui mais attente paiement"), - ("paid", "Oui payé"), -) - - -class AdminEventForm(forms.Form): - status = forms.ChoiceField( - label="Inscription", initial="no", choices=STATUS_CHOICES, widget=RadioSelect - ) - - def __init__(self, *args, **kwargs): - self.event = kwargs.pop("event") - registration = kwargs.pop("current_registration", None) - current_choices, paid = ( - (registration.options.all(), registration.paid) - if registration is not None - else ([], None) - ) - if paid is True: - kwargs["initial"] = {"status": "paid"} - elif paid is False: - kwargs["initial"] = {"status": "wait"} - else: - kwargs["initial"] = {"status": "no"} - super().__init__(*args, **kwargs) - choices = {} - for choice in current_choices: - if choice.event_option.id not in choices: - choices[choice.event_option.id] = [choice.id] - else: - choices[choice.event_option.id].append(choice.id) - all_choices = choices - for option in self.event.options.all(): - choices = [(choice.id, choice.value) for choice in option.choices.all()] - if option.multi_choices: - initial = [] if option.id not in all_choices else all_choices[option.id] - field = forms.MultipleChoiceField( - label=option.name, - choices=choices, - widget=CheckboxSelectMultiple, - required=False, - initial=initial, - ) - else: - initial = ( - None if option.id not in all_choices else all_choices[option.id][0] - ) - field = forms.ChoiceField( - label=option.name, - choices=choices, - widget=RadioSelect, - required=False, - initial=initial, - ) - field.option_id = option.id - self.fields["option_%d" % option.id] = field - for commentfield in self.event.commentfields.all(): - initial = commentfield.default - if registration is not None: - try: - initial = registration.comments.get( - commentfield=commentfield - ).content - except EventCommentValue.DoesNotExist: - pass - widget = ( - forms.Textarea if commentfield.fieldtype == "text" else forms.TextInput - ) - field = forms.CharField( - label=commentfield.name, widget=widget, required=False, initial=initial - ) - field.comment_id = commentfield.id - self.fields["comment_%d" % commentfield.id] = field - - def choices(self): - for name, value in self.cleaned_data.items(): - if name.startswith("option_"): - yield (self.fields[name].option_id, value) - - def comments(self): - for name, value in self.cleaned_data.items(): - if name.startswith("comment_"): - yield (self.fields[name].comment_id, value) - - -class BaseEventRegistrationFormset(BaseFormSet): - def __init__(self, *args, **kwargs): - self.events = kwargs.pop("events") - self.current_registrations = kwargs.pop("current_registrations", None) - self.extra = len(self.events) - super().__init__(*args, **kwargs) - - def _construct_form(self, index, **kwargs): - kwargs["event"] = self.events[index] - if self.current_registrations is not None: - kwargs["current_registration"] = self.current_registrations[index] - return super()._construct_form(index, **kwargs) - - -EventFormset = formset_factory(AdminEventForm, BaseEventRegistrationFormset) - - -class CalendarForm(forms.ModelForm): - subscribe_to_events = forms.BooleanField( - initial=True, label="Événements du COF", required=False - ) - subscribe_to_my_shows = forms.BooleanField( - initial=True, - label="Les spectacles pour lesquels j'ai obtenu une place", - required=False, - ) - other_shows = forms.ModelMultipleChoiceField( - label="Spectacles supplémentaires", - queryset=Spectacle.objects.filter(tirage__active=True), - widget=forms.CheckboxSelectMultiple, - required=False, - ) - - class Meta: - model = CalendarSubscription - fields = ["subscribe_to_events", "subscribe_to_my_shows", "other_shows"] - - -class ClubsForm(forms.Form): - """ - Formulaire d'inscription d'un membre à plusieurs clubs du COF. - """ - - clubs = forms.ModelMultipleChoiceField( - label="Inscriptions aux clubs du COF", - queryset=Club.objects.all(), - widget=forms.CheckboxSelectMultiple, - required=False, - ) - - -# --- -# Announcements banner -# TODO: move this to the `gestion` app once the supportBDS branch is merged -# --- - - -class GestioncofConfigForm(ConfigForm): - gestion_banner = forms.CharField( - label=_("Announcements banner"), - help_text=_("An empty banner disables annoucements"), - max_length=2048, - required=False, - ) diff --git a/gestioncof/management/__init__.py b/gestioncof/management/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioncof/management/base.py b/gestioncof/management/base.py deleted file mode 100644 index 7d7bcc30..00000000 --- a/gestioncof/management/base.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Un mixin à utiliser avec BaseCommand pour charger des objets depuis un json -""" - -import json -import os - -from django.core.management.base import BaseCommand - - -class MyBaseCommand(BaseCommand): - """ - Ajoute une méthode ``from_json`` qui charge des objets à partir d'un json. - """ - - def from_json(self, filename, data_dir, klass, callback=lambda obj: obj): - """ - Charge les objets contenus dans le fichier json référencé par - ``filename`` dans la base de donnée. La fonction callback est appelées - sur chaque objet avant enregistrement. - """ - self.stdout.write("Chargement de {:s}".format(filename)) - with open(os.path.join(data_dir, filename), "r") as file: - descriptions = json.load(file) - objects = [] - nb_new = 0 - for description in descriptions: - qset = klass.objects.filter(**description) - try: - objects.append(qset.get()) - except klass.DoesNotExist: - obj = klass(**description) - obj = callback(obj) - obj.save() - objects.append(obj) - nb_new += 1 - self.stdout.write("- {:d} objets créés".format(nb_new)) - self.stdout.write( - "- {:d} objets gardés en l'état".format(len(objects) - nb_new) - ) - return objects diff --git a/gestioncof/management/commands/__init__.py b/gestioncof/management/commands/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioncof/management/commands/loaddevdata.py b/gestioncof/management/commands/loaddevdata.py deleted file mode 100644 index 05336050..00000000 --- a/gestioncof/management/commands/loaddevdata.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -Charge des données de test dans la BDD -- Utilisateurs -- Sondage -- Événement -- Petits cours -""" - - -import os -import random - -from django.contrib.auth.models import User -from django.core.management import call_command - -from gestioncof.management.base import MyBaseCommand -from petitscours.models import ( - LEVELS_CHOICES, - PetitCoursAbility, - PetitCoursAttributionCounter, - PetitCoursSubject, -) - -# Où sont stockés les fichiers json -DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data") - - -class Command(MyBaseCommand): - help = "Charge des données de test dans la BDD" - - def add_arguments(self, parser): - """ - Permet de ne pas créer l'utilisateur "root". - """ - parser.add_argument( - "--no-root", - action="store_true", - dest="no-root", - default=False, - help='Ne crée pas l\'utilisateur "root"', - ) - - def handle(self, *args, **options): - # --- - # Utilisateurs - # --- - - # Gaulois - gaulois = self.from_json("gaulois.json", DATA_DIR, User) - for user in gaulois: - user.profile.is_cof = True - user.profile.save() - - # Romains - self.from_json("romains.json", DATA_DIR, User) - - # Root - no_root = options.get("no-root", False) - if not no_root: - self.stdout.write("Création de l'utilisateur root") - root, _ = User.objects.get_or_create( - username="root", - first_name="super", - last_name="user", - email="root@localhost", - ) - root.set_password("root") - root.is_staff = True - root.is_superuser = True - root.profile.is_cof = True - root.profile.is_buro = True - root.profile.save() - root.save() - - # --- - # Petits cours - # --- - - self.stdout.write("Inscriptions au système des petits cours") - levels = [id for (id, verbose) in LEVELS_CHOICES] - subjects = list(PetitCoursSubject.objects.all()) - nb_of_teachers = 0 - for user in gaulois: - if random.randint(0, 1): - nb_of_teachers += 1 - # L'utilisateur reçoit les demandes de petits cours - user.profile.petits_cours_accept = True - user.save() - # L'utilisateur est compétent dans une matière - subject = random.choice(subjects) - if not PetitCoursAbility.objects.filter( - user=user, matiere=subject - ).exists(): - PetitCoursAbility.objects.create( - user=user, - matiere=subject, - niveau=random.choice(levels), - agrege=bool(random.randint(0, 1)), - ) - # On initialise son compteur d'attributions - PetitCoursAttributionCounter.objects.get_or_create( - user=user, matiere=subject - ) - self.stdout.write("- {:d} inscriptions".format(nb_of_teachers)) - - # --- - # Le BdA - # --- - - call_command("loadbdadevdata") - - # --- - # La K-Fêt - # --- - - call_command("loadkfetdevdata") diff --git a/gestioncof/management/data/gaulois.json b/gestioncof/management/data/gaulois.json deleted file mode 100644 index b562aef0..00000000 --- a/gestioncof/management/data/gaulois.json +++ /dev/null @@ -1,368 +0,0 @@ -[ - { - "username": "Abraracourcix", - "email": "Abraracourcix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Abraracourcix" - }, - { - "username": "Acidenitrix", - "email": "Acidenitrix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Acidenitrix" - }, - { - "username": "Agecanonix", - "email": "Agecanonix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Agecanonix" - }, - { - "username": "Alambix", - "email": "Alambix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Alambix" - }, - { - "username": "Amerix", - "email": "Amerix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Amerix" - }, - { - "username": "Amnesix", - "email": "Amnesix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Amnesix" - }, - { - "username": "Aniline", - "email": "Aniline.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Aniline" - }, - { - "username": "Aplusbegalix", - "email": "Aplusbegalix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Aplusbegalix" - }, - { - "username": "Archeopterix", - "email": "Archeopterix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Archeopterix" - }, - { - "username": "Assurancetourix", - "email": "Assurancetourix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Assurancetourix" - }, - { - "username": "Asterix", - "email": "Asterix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Asterix" - }, - { - "username": "Astronomix", - "email": "Astronomix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Astronomix" - }, - { - "username": "Avoranfix", - "email": "Avoranfix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Avoranfix" - }, - { - "username": "Barometrix", - "email": "Barometrix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Barometrix" - }, - { - "username": "Beaufix", - "email": "Beaufix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Beaufix" - }, - { - "username": "Berlix", - "email": "Berlix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Berlix" - }, - { - "username": "Bonemine", - "email": "Bonemine.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Bonemine" - }, - { - "username": "Boufiltre", - "email": "Boufiltre.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Boufiltre" - }, - { - "username": "Catedralgotix", - "email": "Catedralgotix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Catedralgotix" - }, - { - "username": "CesarLabeldecadix", - "email": "CesarLabeldecadix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "CesarLabeldecadix" - }, - { - "username": "Cetautomatix", - "email": "Cetautomatix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Cetautomatix" - }, - { - "username": "Cetyounix", - "email": "Cetyounix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Cetyounix" - }, - { - "username": "Changeledix", - "email": "Changeledix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Changeledix" - }, - { - "username": "Chanteclairix", - "email": "Chanteclairix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Chanteclairix" - }, - { - "username": "Cicatrix", - "email": "Cicatrix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Cicatrix" - }, - { - "username": "Comix", - "email": "Comix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Comix" - }, - { - "username": "Diagnostix", - "email": "Diagnostix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Diagnostix" - }, - { - "username": "Doublepolemix", - "email": "Doublepolemix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Doublepolemix" - }, - { - "username": "Eponine", - "email": "Eponine.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Eponine" - }, - { - "username": "Falbala", - "email": "Falbala.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Falbala" - }, - { - "username": "Fanzine", - "email": "Fanzine.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Fanzine" - }, - { - "username": "Gelatine", - "email": "Gelatine.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Gelatine" - }, - { - "username": "Goudurix", - "email": "Goudurix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Goudurix" - }, - { - "username": "Homeopatix", - "email": "Homeopatix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Homeopatix" - }, - { - "username": "Idefix", - "email": "Idefix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Idefix" - }, - { - "username": "Ielosubmarine", - "email": "Ielosubmarine.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Ielosubmarine" - }, - { - "username": "Keskonrix", - "email": "Keskonrix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Keskonrix" - }, - { - "username": "Lentix", - "email": "Lentix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Lentix" - }, - { - "username": "Maestria", - "email": "Maestria.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Maestria" - }, - { - "username": "MaitrePanix", - "email": "MaitrePanix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "MaitrePanix" - }, - { - "username": "MmeAgecanonix", - "email": "MmeAgecanonix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "MmeAgecanonix" - }, - { - "username": "Moralelastix", - "email": "Moralelastix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Moralelastix" - }, - { - "username": "Obelix", - "email": "Obelix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Obelix" - }, - { - "username": "Obelodalix", - "email": "Obelodalix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Obelodalix" - }, - { - "username": "Odalix", - "email": "Odalix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Odalix" - }, - { - "username": "Ordralfabetix", - "email": "Ordralfabetix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Ordralfabetix" - }, - { - "username": "Orthopedix", - "email": "Orthopedix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Orthopedix" - }, - { - "username": "Panoramix", - "email": "Panoramix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Panoramix" - }, - { - "username": "Plaintcontrix", - "email": "Plaintcontrix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Plaintcontrix" - }, - { - "username": "Praline", - "email": "Praline.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Praline" - }, - { - "username": "Prefix", - "email": "Prefix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Prefix" - }, - { - "username": "Prolix", - "email": "Prolix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Prolix" - }, - { - "username": "Pronostix", - "email": "Pronostix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Pronostix" - }, - { - "username": "Quatredeusix", - "email": "Quatredeusix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Quatredeusix" - }, - { - "username": "Saingesix", - "email": "Saingesix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Saingesix" - }, - { - "username": "Segregationnix", - "email": "Segregationnix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Segregationnix" - }, - { - "username": "Septantesix", - "email": "Septantesix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Septantesix" - }, - { - "username": "Tournedix", - "email": "Tournedix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Tournedix" - }, - { - "username": "Tragicomix", - "email": "Tragicomix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Tragicomix" - }, - { - "username": "Coriza", - "email": "Coriza.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Coriza" - }, - { - "username": "Zerozerosix", - "email": "Zerozerosix.gaulois@ens.fr", - "last_name": "Gaulois", - "first_name": "Zerozerosix" - } -] diff --git a/gestioncof/management/data/romains.json b/gestioncof/management/data/romains.json deleted file mode 100644 index 731603f9..00000000 --- a/gestioncof/management/data/romains.json +++ /dev/null @@ -1,614 +0,0 @@ -[ - { - "username": "Abel", - "email": "Abel.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Abel" - }, - { - "username": "Abelardus", - "email": "Abelardus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Abelardus" - }, - { - "username": "Abrahamus", - "email": "Abrahamus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Abrahamus" - }, - { - "username": "Acacius", - "email": "Acacius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Acacius" - }, - { - "username": "Accius", - "email": "Accius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Accius" - }, - { - "username": "Achaicus", - "email": "Achaicus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Achaicus" - }, - { - "username": "Achill", - "email": "Achill.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Achill" - }, - { - "username": "Achilles", - "email": "Achilles.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Achilles" - }, - { - "username": "Achilleus", - "email": "Achilleus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Achilleus" - }, - { - "username": "Acrisius", - "email": "Acrisius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Acrisius" - }, - { - "username": "Actaeon", - "email": "Actaeon.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Actaeon" - }, - { - "username": "Acteon", - "email": "Acteon.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Acteon" - }, - { - "username": "Adalricus", - "email": "Adalricus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Adalricus" - }, - { - "username": "Adelfonsus", - "email": "Adelfonsus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Adelfonsus" - }, - { - "username": "Adelphus", - "email": "Adelphus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Adelphus" - }, - { - "username": "Adeodatus", - "email": "Adeodatus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Adeodatus" - }, - { - "username": "Adolfus", - "email": "Adolfus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Adolfus" - }, - { - "username": "Adolphus", - "email": "Adolphus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Adolphus" - }, - { - "username": "Adrastus", - "email": "Adrastus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Adrastus" - }, - { - "username": "Adrianus", - "email": "Adrianus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Adrianus" - }, - { - "username": "\u00c6gidius", - "email": "\u00c6gidius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "\u00c6gidius" - }, - { - "username": "\u00c6lia", - "email": "\u00c6lia.Romain@ens.fr", - "last_name": "Romain", - "first_name": "\u00c6lia" - }, - { - "username": "\u00c6lianus", - "email": "\u00c6lianus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "\u00c6lianus" - }, - { - "username": "\u00c6milianus", - "email": "\u00c6milianus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "\u00c6milianus" - }, - { - "username": "\u00c6milius", - "email": "\u00c6milius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "\u00c6milius" - }, - { - "username": "Aeneas", - "email": "Aeneas.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Aeneas" - }, - { - "username": "\u00c6olus", - "email": "\u00c6olus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "\u00c6olus" - }, - { - "username": "\u00c6schylus", - "email": "\u00c6schylus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "\u00c6schylus" - }, - { - "username": "\u00c6son", - "email": "\u00c6son.Romain@ens.fr", - "last_name": "Romain", - "first_name": "\u00c6son" - }, - { - "username": "\u00c6sop", - "email": "\u00c6sop.Romain@ens.fr", - "last_name": "Romain", - "first_name": "\u00c6sop" - }, - { - "username": "\u00c6ther", - "email": "\u00c6ther.Romain@ens.fr", - "last_name": "Romain", - "first_name": "\u00c6ther" - }, - { - "username": "\u00c6tius", - "email": "\u00c6tius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "\u00c6tius" - }, - { - "username": "Agapetus", - "email": "Agapetus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Agapetus" - }, - { - "username": "Agapitus", - "email": "Agapitus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Agapitus" - }, - { - "username": "Agapius", - "email": "Agapius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Agapius" - }, - { - "username": "Agathangelus", - "email": "Agathangelus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Agathangelus" - }, - { - "username": "Aigidius", - "email": "Aigidius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Aigidius" - }, - { - "username": "Aiolus", - "email": "Aiolus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Aiolus" - }, - { - "username": "Ajax", - "email": "Ajax.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Ajax" - }, - { - "username": "Alair", - "email": "Alair.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alair" - }, - { - "username": "Alaricus", - "email": "Alaricus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alaricus" - }, - { - "username": "Albanus", - "email": "Albanus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Albanus" - }, - { - "username": "Alberic", - "email": "Alberic.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alberic" - }, - { - "username": "Albericus", - "email": "Albericus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Albericus" - }, - { - "username": "Albertus", - "email": "Albertus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Albertus" - }, - { - "username": "Albinus", - "email": "Albinus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Albinus" - }, - { - "username": "Albus", - "email": "Albus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Albus" - }, - { - "username": "Alcaeus", - "email": "Alcaeus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alcaeus" - }, - { - "username": "Alcander", - "email": "Alcander.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alcander" - }, - { - "username": "Alcimus", - "email": "Alcimus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alcimus" - }, - { - "username": "Alcinder", - "email": "Alcinder.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alcinder" - }, - { - "username": "Alerio", - "email": "Alerio.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alerio" - }, - { - "username": "Alexandrus", - "email": "Alexandrus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alexandrus" - }, - { - "username": "Alexis", - "email": "Alexis.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alexis" - }, - { - "username": "Alexius", - "email": "Alexius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alexius" - }, - { - "username": "Alexus", - "email": "Alexus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alexus" - }, - { - "username": "Alfonsus", - "email": "Alfonsus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alfonsus" - }, - { - "username": "Alfredus", - "email": "Alfredus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alfredus" - }, - { - "username": "Almericus", - "email": "Almericus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Almericus" - }, - { - "username": "Aloisius", - "email": "Aloisius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Aloisius" - }, - { - "username": "Aloysius", - "email": "Aloysius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Aloysius" - }, - { - "username": "Alphaeus", - "email": "Alphaeus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alphaeus" - }, - { - "username": "Alpheaus", - "email": "Alpheaus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alpheaus" - }, - { - "username": "Alpheus", - "email": "Alpheus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alpheus" - }, - { - "username": "Alphoeus", - "email": "Alphoeus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alphoeus" - }, - { - "username": "Alphonsus", - "email": "Alphonsus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alphonsus" - }, - { - "username": "Alphonzus", - "email": "Alphonzus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alphonzus" - }, - { - "username": "Alvinius", - "email": "Alvinius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alvinius" - }, - { - "username": "Alvredus", - "email": "Alvredus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Alvredus" - }, - { - "username": "Amadeus", - "email": "Amadeus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amadeus" - }, - { - "username": "Amaliricus", - "email": "Amaliricus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amaliricus" - }, - { - "username": "Amandus", - "email": "Amandus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amandus" - }, - { - "username": "Amantius", - "email": "Amantius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amantius" - }, - { - "username": "Amarandus", - "email": "Amarandus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amarandus" - }, - { - "username": "Amaranthus", - "email": "Amaranthus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amaranthus" - }, - { - "username": "Amatus", - "email": "Amatus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amatus" - }, - { - "username": "Ambrosianus", - "email": "Ambrosianus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Ambrosianus" - }, - { - "username": "Ambrosius", - "email": "Ambrosius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Ambrosius" - }, - { - "username": "Amedeus", - "email": "Amedeus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amedeus" - }, - { - "username": "Americus", - "email": "Americus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Americus" - }, - { - "username": "Amlethus", - "email": "Amlethus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amlethus" - }, - { - "username": "Amletus", - "email": "Amletus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amletus" - }, - { - "username": "Amor", - "email": "Amor.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amor" - }, - { - "username": "Ampelius", - "email": "Ampelius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Ampelius" - }, - { - "username": "Amphion", - "email": "Amphion.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Amphion" - }, - { - "username": "Anacletus", - "email": "Anacletus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Anacletus" - }, - { - "username": "Anastasius", - "email": "Anastasius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Anastasius" - }, - { - "username": "Anastatius", - "email": "Anastatius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Anastatius" - }, - { - "username": "Anastius", - "email": "Anastius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Anastius" - }, - { - "username": "Anatolius", - "email": "Anatolius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Anatolius" - }, - { - "username": "Androcles", - "email": "Androcles.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Androcles" - }, - { - "username": "Andronicus", - "email": "Andronicus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Andronicus" - }, - { - "username": "Anencletus", - "email": "Anencletus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Anencletus" - }, - { - "username": "Angelicus", - "email": "Angelicus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Angelicus" - }, - { - "username": "Angelus", - "email": "Angelus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Angelus" - }, - { - "username": "Anicetus", - "email": "Anicetus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Anicetus" - }, - { - "username": "Antigonus", - "email": "Antigonus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Antigonus" - }, - { - "username": "Antipater", - "email": "Antipater.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Antipater" - }, - { - "username": "Antoninus", - "email": "Antoninus.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Antoninus" - }, - { - "username": "Antonius", - "email": "Antonius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Antonius" - }, - { - "username": "Aphrodisius", - "email": "Aphrodisius.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Aphrodisius" - }, - { - "username": "Apollinaris", - "email": "Apollinaris.Romain@ens.fr", - "last_name": "Romain", - "first_name": "Apollinaris" - } -] diff --git a/gestioncof/migrations/0001_initial.py b/gestioncof/migrations/0001_initial.py deleted file mode 100644 index a3edc13d..00000000 --- a/gestioncof/migrations/0001_initial.py +++ /dev/null @@ -1,855 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] - - operations = [ - migrations.CreateModel( - name="Clipper", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "username", - models.CharField(max_length=20, verbose_name=b"Identifiant"), - ), - ( - "fullname", - models.CharField(max_length=200, verbose_name=b"Nom complet"), - ), - ], - ), - migrations.CreateModel( - name="Club", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("name", models.CharField(max_length=200, verbose_name=b"Nom")), - ("description", models.TextField(verbose_name=b"Description")), - ( - "membres", - models.ManyToManyField( - related_name="clubs", to=settings.AUTH_USER_MODEL - ), - ), - ( - "respos", - models.ManyToManyField( - related_name="clubs_geres", to=settings.AUTH_USER_MODEL - ), - ), - ], - ), - migrations.CreateModel( - name="CofProfile", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "login_clipper", - models.CharField( - max_length=8, verbose_name=b"Login clipper", blank=True - ), - ), - ( - "is_cof", - models.BooleanField(default=False, verbose_name=b"Membre du COF"), - ), - ( - "num", - models.IntegerField( - default=0, - verbose_name=b"Num\xc3\xa9ro d'adh\xc3\xa9rent", - blank=True, - ), - ), - ( - "phone", - models.CharField( - max_length=20, - verbose_name=b"T\xc3\xa9l\xc3\xa9phone", - blank=True, - ), - ), - ( - "occupation", - models.CharField( - default=b"1A", - max_length=9, - verbose_name="Occupation", - choices=[ - (b"exterieur", "Ext\xe9rieur"), - (b"1A", "1A"), - (b"2A", "2A"), - (b"3A", "3A"), - (b"4A", "4A"), - (b"archicube", "Archicube"), - (b"doctorant", "Doctorant"), - (b"CST", "CST"), - ], - ), - ), - ( - "departement", - models.CharField( - max_length=50, verbose_name="D\xe9partement", blank=True - ), - ), - ( - "type_cotiz", - models.CharField( - default=b"normalien", - max_length=9, - verbose_name="Type de cotisation", - choices=[ - (b"etudiant", "Normalien \xe9tudiant"), - (b"normalien", "Normalien \xe9l\xe8ve"), - (b"exterieur", "Ext\xe9rieur"), - ], - ), - ), - ( - "mailing_cof", - models.BooleanField( - default=False, verbose_name=b"Recevoir les mails COF" - ), - ), - ( - "mailing_bda", - models.BooleanField( - default=False, verbose_name=b"Recevoir les mails BdA" - ), - ), - ( - "mailing_bda_revente", - models.BooleanField( - default=False, - verbose_name=b"Recevoir les mails de revente de places BdA", - ), - ), - ( - "comments", - models.TextField( - verbose_name=b"Commentaires visibles uniquement par le Buro", - blank=True, - ), - ), - ( - "is_buro", - models.BooleanField( - default=False, verbose_name=b"Membre du Bur\xc3\xb4" - ), - ), - ( - "petits_cours_accept", - models.BooleanField( - default=False, verbose_name=b"Recevoir des petits cours" - ), - ), - ( - "petits_cours_remarques", - models.TextField( - default=b"", - verbose_name="Remarques et pr\xe9cisions pour les petits cours", - blank=True, - ), - ), - ( - "user", - models.OneToOneField( - related_name="profile", - to=settings.AUTH_USER_MODEL, - on_delete=models.CASCADE, - ), - ), - ], - options={ - "verbose_name": "Profil COF", - "verbose_name_plural": "Profils COF", - }, - ), - migrations.CreateModel( - name="CustomMail", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("shortname", models.SlugField()), - ("title", models.CharField(max_length=200, verbose_name=b"Titre")), - ("content", models.TextField(verbose_name=b"Contenu")), - ( - "comments", - models.TextField( - verbose_name=b"Informations contextuelles sur le mail", - blank=True, - ), - ), - ], - options={"verbose_name": "Mails personnalisables"}, - ), - migrations.CreateModel( - name="Event", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("title", models.CharField(max_length=200, verbose_name=b"Titre")), - ("location", models.CharField(max_length=200, verbose_name=b"Lieu")), - ( - "start_date", - models.DateField( - null=True, verbose_name=b"Date de d\xc3\xa9but", blank=True - ), - ), - ( - "end_date", - models.DateField( - null=True, verbose_name=b"Date de fin", blank=True - ), - ), - ( - "description", - models.TextField(verbose_name=b"Description", blank=True), - ), - ( - "registration_open", - models.BooleanField( - default=True, verbose_name=b"Inscriptions ouvertes" - ), - ), - ( - "old", - models.BooleanField( - default=False, - verbose_name=b"Archiver (\xc3\xa9v\xc3\xa9nement fini)", - ), - ), - ], - options={"verbose_name": "\xc9v\xe9nement"}, - ), - migrations.CreateModel( - name="EventCommentField", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("name", models.CharField(max_length=200, verbose_name=b"Champ")), - ( - "fieldtype", - models.CharField( - default=b"text", - max_length=10, - verbose_name=b"Type", - choices=[(b"text", "Texte long"), (b"char", "Texte court")], - ), - ), - ( - "default", - models.TextField( - verbose_name=b"Valeur par d\xc3\xa9faut", blank=True - ), - ), - ( - "event", - models.ForeignKey( - related_name="commentfields", - to="gestioncof.Event", - on_delete=models.CASCADE, - ), - ), - ], - options={"verbose_name": "Champ"}, - ), - migrations.CreateModel( - name="EventCommentValue", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "content", - models.TextField(null=True, verbose_name=b"Contenu", blank=True), - ), - ( - "commentfield", - models.ForeignKey( - related_name="values", - to="gestioncof.EventCommentField", - on_delete=models.CASCADE, - ), - ), - ], - ), - migrations.CreateModel( - name="EventOption", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("name", models.CharField(max_length=200, verbose_name=b"Option")), - ( - "multi_choices", - models.BooleanField(default=False, verbose_name=b"Choix multiples"), - ), - ( - "event", - models.ForeignKey( - related_name="options", - to="gestioncof.Event", - on_delete=models.CASCADE, - ), - ), - ], - options={"verbose_name": "Option"}, - ), - migrations.CreateModel( - name="EventOptionChoice", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("value", models.CharField(max_length=200, verbose_name=b"Valeur")), - ( - "event_option", - models.ForeignKey( - related_name="choices", - to="gestioncof.EventOption", - on_delete=models.CASCADE, - ), - ), - ], - options={"verbose_name": "Choix"}, - ), - migrations.CreateModel( - name="EventRegistration", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "paid", - models.BooleanField(default=False, verbose_name=b"A pay\xc3\xa9"), - ), - ( - "event", - models.ForeignKey(to="gestioncof.Event", on_delete=models.CASCADE), - ), - ( - "filledcomments", - models.ManyToManyField( - to="gestioncof.EventCommentField", - through="gestioncof.EventCommentValue", - ), - ), - ("options", models.ManyToManyField(to="gestioncof.EventOptionChoice")), - ( - "user", - models.ForeignKey( - to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE - ), - ), - ], - options={"verbose_name": "Inscription"}, - ), - migrations.CreateModel( - name="PetitCoursAbility", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "niveau", - models.CharField( - max_length=12, - verbose_name="Niveau", - choices=[ - (b"college", "Coll\xe8ge"), - (b"lycee", "Lyc\xe9e"), - (b"prepa1styear", "Pr\xe9pa 1\xe8re ann\xe9e / L1"), - (b"prepa2ndyear", "Pr\xe9pa 2\xe8me ann\xe9e / L2"), - (b"licence3", "Licence 3"), - (b"other", "Autre (pr\xe9ciser dans les commentaires)"), - ], - ), - ), - ( - "agrege", - models.BooleanField(default=False, verbose_name="Agr\xe9g\xe9"), - ), - ], - options={ - "verbose_name": "Comp\xe9tence petits cours", - "verbose_name_plural": "Comp\xe9tences des petits cours", - }, - ), - migrations.CreateModel( - name="PetitCoursAttribution", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "date", - models.DateTimeField( - auto_now_add=True, verbose_name="Date d'attribution" - ), - ), - ("rank", models.IntegerField(verbose_name=b"Rang dans l'email")), - ( - "selected", - models.BooleanField( - default=False, verbose_name="S\xe9lectionn\xe9 par le demandeur" - ), - ), - ], - options={ - "verbose_name": "Attribution de petits cours", - "verbose_name_plural": "Attributions de petits cours", - }, - ), - migrations.CreateModel( - name="PetitCoursAttributionCounter", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "count", - models.IntegerField(default=0, verbose_name=b"Nombre d'envois"), - ), - ], - options={ - "verbose_name": "Compteur d'attribution de petits cours", - "verbose_name_plural": "Compteurs d'attributions de petits cours", - }, - ), - migrations.CreateModel( - name="PetitCoursDemande", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "name", - models.CharField(max_length=200, verbose_name="Nom/pr\xe9nom"), - ), - ( - "email", - models.CharField(max_length=300, verbose_name="Adresse email"), - ), - ( - "phone", - models.CharField( - max_length=20, - verbose_name="T\xe9l\xe9phone (facultatif)", - blank=True, - ), - ), - ( - "quand", - models.CharField( - help_text="Indiquez ici la p\xe9riode d\xe9sir\xe9e pour les petits cours (vacances scolaires, semaine, week-end).", - max_length=300, - verbose_name="Quand ?", - blank=True, - ), - ), - ( - "freq", - models.CharField( - help_text="Indiquez ici la fr\xe9quence envisag\xe9e (hebdomadaire, 2 fois par semaine, ...)", - max_length=300, - verbose_name="Fr\xe9quence", - blank=True, - ), - ), - ( - "lieu", - models.CharField( - help_text="Si vous avez avez une pr\xe9f\xe9rence sur le lieu.", - max_length=300, - verbose_name="Lieu (si pr\xe9f\xe9rence)", - blank=True, - ), - ), - ( - "agrege_requis", - models.BooleanField( - default=False, verbose_name="Agr\xe9g\xe9 requis" - ), - ), - ( - "niveau", - models.CharField( - default=b"", - max_length=12, - verbose_name="Niveau", - choices=[ - (b"college", "Coll\xe8ge"), - (b"lycee", "Lyc\xe9e"), - (b"prepa1styear", "Pr\xe9pa 1\xe8re ann\xe9e / L1"), - (b"prepa2ndyear", "Pr\xe9pa 2\xe8me ann\xe9e / L2"), - (b"licence3", "Licence 3"), - (b"other", "Autre (pr\xe9ciser dans les commentaires)"), - ], - ), - ), - ( - "remarques", - models.TextField( - verbose_name="Remarques et pr\xe9cisions", blank=True - ), - ), - ( - "traitee", - models.BooleanField(default=False, verbose_name="Trait\xe9e"), - ), - ( - "processed", - models.DateTimeField(verbose_name="Date de traitement", blank=True), - ), - ( - "created", - models.DateTimeField( - auto_now_add=True, verbose_name="Date de cr\xe9ation" - ), - ), - ], - options={ - "verbose_name": "Demande de petits cours", - "verbose_name_plural": "Demandes de petits cours", - }, - ), - migrations.CreateModel( - name="PetitCoursSubject", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("name", models.CharField(max_length=30, verbose_name="Mati\xe8re")), - ( - "users", - models.ManyToManyField( - related_name="petits_cours_matieres", - through="gestioncof.PetitCoursAbility", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "verbose_name": "Mati\xe8re de petits cours", - "verbose_name_plural": "Mati\xe8res des petits cours", - }, - ), - migrations.CreateModel( - name="Survey", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("title", models.CharField(max_length=200, verbose_name=b"Titre")), - ( - "details", - models.TextField(verbose_name=b"D\xc3\xa9tails", blank=True), - ), - ( - "survey_open", - models.BooleanField(default=True, verbose_name=b"Sondage ouvert"), - ), - ( - "old", - models.BooleanField( - default=False, verbose_name=b"Archiver (sondage fini)" - ), - ), - ], - options={"verbose_name": "Sondage"}, - ), - migrations.CreateModel( - name="SurveyAnswer", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ) - ], - options={"verbose_name": "R\xe9ponses"}, - ), - migrations.CreateModel( - name="SurveyQuestion", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "question", - models.CharField(max_length=200, verbose_name=b"Question"), - ), - ( - "multi_answers", - models.BooleanField(default=False, verbose_name=b"Choix multiples"), - ), - ( - "survey", - models.ForeignKey( - related_name="questions", - to="gestioncof.Survey", - on_delete=models.CASCADE, - ), - ), - ], - options={"verbose_name": "Question"}, - ), - migrations.CreateModel( - name="SurveyQuestionAnswer", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ( - "answer", - models.CharField(max_length=200, verbose_name=b"R\xc3\xa9ponse"), - ), - ( - "survey_question", - models.ForeignKey( - related_name="answers", - to="gestioncof.SurveyQuestion", - on_delete=models.CASCADE, - ), - ), - ], - options={"verbose_name": "R\xe9ponse"}, - ), - migrations.AddField( - model_name="surveyanswer", - name="answers", - field=models.ManyToManyField( - related_name="selected_by", to="gestioncof.SurveyQuestionAnswer" - ), - ), - migrations.AddField( - model_name="surveyanswer", - name="survey", - field=models.ForeignKey(to="gestioncof.Survey", on_delete=models.CASCADE), - ), - migrations.AddField( - model_name="surveyanswer", - name="user", - field=models.ForeignKey( - to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE - ), - ), - migrations.AddField( - model_name="petitcoursdemande", - name="matieres", - field=models.ManyToManyField( - related_name="demandes", - verbose_name="Mati\xe8res", - to="gestioncof.PetitCoursSubject", - ), - ), - migrations.AddField( - model_name="petitcoursdemande", - name="traitee_par", - field=models.ForeignKey( - blank=True, - to=settings.AUTH_USER_MODEL, - null=True, - on_delete=models.CASCADE, - ), - ), - migrations.AddField( - model_name="petitcoursattributioncounter", - name="matiere", - field=models.ForeignKey( - verbose_name="Matiere", - to="gestioncof.PetitCoursSubject", - on_delete=models.CASCADE, - ), - ), - migrations.AddField( - model_name="petitcoursattributioncounter", - name="user", - field=models.ForeignKey( - to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE - ), - ), - migrations.AddField( - model_name="petitcoursattribution", - name="demande", - field=models.ForeignKey( - verbose_name="Demande", - to="gestioncof.PetitCoursDemande", - on_delete=models.CASCADE, - ), - ), - migrations.AddField( - model_name="petitcoursattribution", - name="matiere", - field=models.ForeignKey( - verbose_name="Mati\xe8re", - to="gestioncof.PetitCoursSubject", - on_delete=models.CASCADE, - ), - ), - migrations.AddField( - model_name="petitcoursattribution", - name="user", - field=models.ForeignKey( - to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE - ), - ), - migrations.AddField( - model_name="petitcoursability", - name="matiere", - field=models.ForeignKey( - verbose_name="Mati\xe8re", - to="gestioncof.PetitCoursSubject", - on_delete=models.CASCADE, - ), - ), - migrations.AddField( - model_name="petitcoursability", - name="user", - field=models.ForeignKey( - to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE - ), - ), - migrations.AddField( - model_name="eventcommentvalue", - name="registration", - field=models.ForeignKey( - related_name="comments", - to="gestioncof.EventRegistration", - on_delete=models.CASCADE, - ), - ), - migrations.AlterUniqueTogether( - name="surveyanswer", unique_together=set([("user", "survey")]) - ), - migrations.AlterUniqueTogether( - name="eventregistration", unique_together=set([("user", "event")]) - ), - ] diff --git a/gestioncof/migrations/0002_enable_unprocessed_demandes.py b/gestioncof/migrations/0002_enable_unprocessed_demandes.py deleted file mode 100644 index 239b8831..00000000 --- a/gestioncof/migrations/0002_enable_unprocessed_demandes.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0001_initial")] - - operations = [ - migrations.AlterField( - model_name="petitcoursdemande", - name="processed", - field=models.DateTimeField( - null=True, verbose_name="Date de traitement", blank=True - ), - ) - ] diff --git a/gestioncof/migrations/0003_event_image.py b/gestioncof/migrations/0003_event_image.py deleted file mode 100644 index eeede90b..00000000 --- a/gestioncof/migrations/0003_event_image.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0002_enable_unprocessed_demandes")] - - operations = [ - migrations.AddField( - model_name="event", - name="image", - field=models.ImageField( - upload_to=b"imgs/events/", null=True, verbose_name=b"Image", blank=True - ), - ) - ] diff --git a/gestioncof/migrations/0004_registration_mail.py b/gestioncof/migrations/0004_registration_mail.py deleted file mode 100644 index 92cb58a0..00000000 --- a/gestioncof/migrations/0004_registration_mail.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations - - -def create_mail(apps, schema_editor): - CustomMail = apps.get_model("gestioncof", "CustomMail") - db_alias = schema_editor.connection.alias - if CustomMail.objects.filter(shortname="bienvenue").count() == 0: - CustomMail.objects.using(db_alias).bulk_create( - [ - CustomMail( - shortname="bienvenue", - title="Bienvenue au COF", - content="Mail de bienvenue au COF, envoyé automatiquement à " - + "l'inscription.\n\n" - + "Les balises {{ ... }} sont interprétées comme expliqué " - + "ci-dessous à l'envoi.", - comments="{{ nom }} \t fullname de la personne.\n" - + "{{ prenom }} \t prénom de la personne.", - ) - ] - ) - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0003_event_image")] - - operations = [ - # Pas besoin de supprimer le mail lors de la migration dans l'autre - # sens. - migrations.RunPython(create_mail, migrations.RunPython.noop) - ] diff --git a/gestioncof/migrations/0005_encoding.py b/gestioncof/migrations/0005_encoding.py deleted file mode 100644 index b02da10c..00000000 --- a/gestioncof/migrations/0005_encoding.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0004_registration_mail")] - - operations = [ - migrations.AlterModelOptions( - name="custommail", - options={ - "verbose_name": "Mail personnalisable", - "verbose_name_plural": "Mails personnalisables", - }, - ), - migrations.AlterModelOptions( - name="eventoptionchoice", - options={"verbose_name": "Choix", "verbose_name_plural": "Choix"}, - ), - migrations.AlterField( - model_name="cofprofile", - name="is_buro", - field=models.BooleanField(default=False, verbose_name="Membre du Bur\xf4"), - ), - migrations.AlterField( - model_name="cofprofile", - name="num", - field=models.IntegerField( - default=0, verbose_name="Num\xe9ro d'adh\xe9rent", blank=True - ), - ), - migrations.AlterField( - model_name="cofprofile", - name="phone", - field=models.CharField( - max_length=20, verbose_name="T\xe9l\xe9phone", blank=True - ), - ), - migrations.AlterField( - model_name="event", - name="old", - field=models.BooleanField( - default=False, verbose_name="Archiver (\xe9v\xe9nement fini)" - ), - ), - migrations.AlterField( - model_name="event", - name="start_date", - field=models.DateField( - null=True, verbose_name="Date de d\xe9but", blank=True - ), - ), - migrations.AlterField( - model_name="eventcommentfield", - name="default", - field=models.TextField(verbose_name="Valeur par d\xe9faut", blank=True), - ), - migrations.AlterField( - model_name="eventregistration", - name="paid", - field=models.BooleanField(default=False, verbose_name="A pay\xe9"), - ), - migrations.AlterField( - model_name="survey", - name="details", - field=models.TextField(verbose_name="D\xe9tails", blank=True), - ), - migrations.AlterField( - model_name="surveyquestionanswer", - name="answer", - field=models.CharField(max_length=200, verbose_name="R\xe9ponse"), - ), - ] diff --git a/gestioncof/migrations/0006_add_calendar.py b/gestioncof/migrations/0006_add_calendar.py deleted file mode 100644 index 51a11089..00000000 --- a/gestioncof/migrations/0006_add_calendar.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("bda", "0004_mails-rappel"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("gestioncof", "0005_encoding"), - ] - - operations = [ - migrations.CreateModel( - name="CalendarSubscription", - fields=[ - ( - "id", - models.AutoField( - verbose_name="ID", - serialize=False, - auto_created=True, - primary_key=True, - ), - ), - ("token", models.UUIDField()), - ("subscribe_to_events", models.BooleanField(default=True)), - ("subscribe_to_my_shows", models.BooleanField(default=True)), - ("other_shows", models.ManyToManyField(to="bda.Spectacle")), - ( - "user", - models.OneToOneField( - to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE - ), - ), - ], - ), - migrations.AlterModelOptions( - name="custommail", - options={ - "verbose_name": "Mail personnalisable", - "verbose_name_plural": "Mails personnalisables", - }, - ), - migrations.AlterModelOptions( - name="eventoptionchoice", - options={"verbose_name": "Choix", "verbose_name_plural": "Choix"}, - ), - migrations.AlterField( - model_name="event", - name="end_date", - field=models.DateTimeField( - null=True, verbose_name=b"Date de fin", blank=True - ), - ), - migrations.AlterField( - model_name="event", - name="start_date", - field=models.DateTimeField( - null=True, verbose_name=b"Date de d\xc3\xa9but", blank=True - ), - ), - ] diff --git a/gestioncof/migrations/0007_alter_club.py b/gestioncof/migrations/0007_alter_club.py deleted file mode 100644 index 46a61bc2..00000000 --- a/gestioncof/migrations/0007_alter_club.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0006_add_calendar")] - - operations = [ - migrations.AlterField( - model_name="club", - name="name", - field=models.CharField(unique=True, max_length=200, verbose_name="Nom"), - ), - migrations.AlterField( - model_name="club", - name="description", - field=models.TextField(verbose_name="Description", blank=True), - ), - migrations.AlterField( - model_name="club", - name="membres", - field=models.ManyToManyField( - related_name="clubs", to=settings.AUTH_USER_MODEL, blank=True - ), - ), - migrations.AlterField( - model_name="club", - name="respos", - field=models.ManyToManyField( - related_name="clubs_geres", to=settings.AUTH_USER_MODEL, blank=True - ), - ), - migrations.AlterField( - model_name="event", - name="start_date", - field=models.DateTimeField( - null=True, verbose_name="Date de d\xe9but", blank=True - ), - ), - ] diff --git a/gestioncof/migrations/0008_py3.py b/gestioncof/migrations/0008_py3.py deleted file mode 100644 index 60c72e7b..00000000 --- a/gestioncof/migrations/0008_py3.py +++ /dev/null @@ -1,275 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -def forwards(apps, schema_editor): - Profile = apps.get_model("gestioncof", "CofProfile") - Profile.objects.update(comments="") - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0007_alter_club")] - - operations = [ - migrations.AlterField( - model_name="clipper", - name="fullname", - field=models.CharField(verbose_name="Nom complet", max_length=200), - ), - migrations.AlterField( - model_name="clipper", - name="username", - field=models.CharField(verbose_name="Identifiant", max_length=20), - ), - migrations.AlterField( - model_name="cofprofile", - name="comments", - field=models.TextField( - verbose_name="Commentaires visibles par l'utilisateur", blank=True - ), - ), - migrations.AlterField( - model_name="cofprofile", - name="is_cof", - field=models.BooleanField(verbose_name="Membre du COF", default=False), - ), - migrations.AlterField( - model_name="cofprofile", - name="login_clipper", - field=models.CharField( - verbose_name="Login clipper", max_length=8, blank=True - ), - ), - migrations.AlterField( - model_name="cofprofile", - name="mailing_bda", - field=models.BooleanField( - verbose_name="Recevoir les mails BdA", default=False - ), - ), - migrations.AlterField( - model_name="cofprofile", - name="mailing_bda_revente", - field=models.BooleanField( - verbose_name="Recevoir les mails de revente de places BdA", - default=False, - ), - ), - migrations.AlterField( - model_name="cofprofile", - name="mailing_cof", - field=models.BooleanField( - verbose_name="Recevoir les mails COF", default=False - ), - ), - migrations.AlterField( - model_name="cofprofile", - name="occupation", - field=models.CharField( - verbose_name="Occupation", - choices=[ - ("exterieur", "Extérieur"), - ("1A", "1A"), - ("2A", "2A"), - ("3A", "3A"), - ("4A", "4A"), - ("archicube", "Archicube"), - ("doctorant", "Doctorant"), - ("CST", "CST"), - ], - max_length=9, - default="1A", - ), - ), - migrations.AlterField( - model_name="cofprofile", - name="petits_cours_accept", - field=models.BooleanField( - verbose_name="Recevoir des petits cours", default=False - ), - ), - migrations.AlterField( - model_name="cofprofile", - name="petits_cours_remarques", - field=models.TextField( - blank=True, - verbose_name="Remarques et précisions pour les petits cours", - default="", - ), - ), - migrations.AlterField( - model_name="cofprofile", - name="type_cotiz", - field=models.CharField( - verbose_name="Type de cotisation", - choices=[ - ("etudiant", "Normalien étudiant"), - ("normalien", "Normalien élève"), - ("exterieur", "Extérieur"), - ], - max_length=9, - default="normalien", - ), - ), - migrations.AlterField( - model_name="custommail", - name="comments", - field=models.TextField( - verbose_name="Informations contextuelles sur le mail", blank=True - ), - ), - migrations.AlterField( - model_name="custommail", - name="content", - field=models.TextField(verbose_name="Contenu"), - ), - migrations.AlterField( - model_name="custommail", - name="title", - field=models.CharField(verbose_name="Titre", max_length=200), - ), - migrations.AlterField( - model_name="event", - name="description", - field=models.TextField(verbose_name="Description", blank=True), - ), - migrations.AlterField( - model_name="event", - name="end_date", - field=models.DateTimeField( - null=True, verbose_name="Date de fin", blank=True - ), - ), - migrations.AlterField( - model_name="event", - name="image", - field=models.ImageField( - upload_to="imgs/events/", null=True, verbose_name="Image", blank=True - ), - ), - migrations.AlterField( - model_name="event", - name="location", - field=models.CharField(verbose_name="Lieu", max_length=200), - ), - migrations.AlterField( - model_name="event", - name="registration_open", - field=models.BooleanField( - verbose_name="Inscriptions ouvertes", default=True - ), - ), - migrations.AlterField( - model_name="event", - name="title", - field=models.CharField(verbose_name="Titre", max_length=200), - ), - migrations.AlterField( - model_name="eventcommentfield", - name="fieldtype", - field=models.CharField( - verbose_name="Type", - choices=[("text", "Texte long"), ("char", "Texte court")], - max_length=10, - default="text", - ), - ), - migrations.AlterField( - model_name="eventcommentfield", - name="name", - field=models.CharField(verbose_name="Champ", max_length=200), - ), - migrations.AlterField( - model_name="eventcommentvalue", - name="content", - field=models.TextField(null=True, verbose_name="Contenu", blank=True), - ), - migrations.AlterField( - model_name="eventoption", - name="multi_choices", - field=models.BooleanField(verbose_name="Choix multiples", default=False), - ), - migrations.AlterField( - model_name="eventoption", - name="name", - field=models.CharField(verbose_name="Option", max_length=200), - ), - migrations.AlterField( - model_name="eventoptionchoice", - name="value", - field=models.CharField(verbose_name="Valeur", max_length=200), - ), - migrations.AlterField( - model_name="petitcoursability", - name="niveau", - field=models.CharField( - choices=[ - ("college", "Collège"), - ("lycee", "Lycée"), - ("prepa1styear", "Prépa 1ère année / L1"), - ("prepa2ndyear", "Prépa 2ème année / L2"), - ("licence3", "Licence 3"), - ("other", "Autre (préciser dans les commentaires)"), - ], - max_length=12, - verbose_name="Niveau", - ), - ), - migrations.AlterField( - model_name="petitcoursattribution", - name="rank", - field=models.IntegerField(verbose_name="Rang dans l'email"), - ), - migrations.AlterField( - model_name="petitcoursattributioncounter", - name="count", - field=models.IntegerField(verbose_name="Nombre d'envois", default=0), - ), - migrations.AlterField( - model_name="petitcoursdemande", - name="niveau", - field=models.CharField( - verbose_name="Niveau", - choices=[ - ("college", "Collège"), - ("lycee", "Lycée"), - ("prepa1styear", "Prépa 1ère année / L1"), - ("prepa2ndyear", "Prépa 2ème année / L2"), - ("licence3", "Licence 3"), - ("other", "Autre (préciser dans les commentaires)"), - ], - max_length=12, - default="", - ), - ), - migrations.AlterField( - model_name="survey", - name="old", - field=models.BooleanField( - verbose_name="Archiver (sondage fini)", default=False - ), - ), - migrations.AlterField( - model_name="survey", - name="survey_open", - field=models.BooleanField(verbose_name="Sondage ouvert", default=True), - ), - migrations.AlterField( - model_name="survey", - name="title", - field=models.CharField(verbose_name="Titre", max_length=200), - ), - migrations.AlterField( - model_name="surveyquestion", - name="multi_answers", - field=models.BooleanField(verbose_name="Choix multiples", default=False), - ), - migrations.AlterField( - model_name="surveyquestion", - name="question", - field=models.CharField(verbose_name="Question", max_length=200), - ), - migrations.RunPython(forwards, migrations.RunPython.noop), - ] diff --git a/gestioncof/migrations/0009_delete_clipper.py b/gestioncof/migrations/0009_delete_clipper.py deleted file mode 100644 index bcd54711..00000000 --- a/gestioncof/migrations/0009_delete_clipper.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0008_py3")] - - operations = [migrations.DeleteModel(name="Clipper")] diff --git a/gestioncof/migrations/0010_delete_custommail.py b/gestioncof/migrations/0010_delete_custommail.py deleted file mode 100644 index ffc281cf..00000000 --- a/gestioncof/migrations/0010_delete_custommail.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0009_delete_clipper")] - - operations = [migrations.DeleteModel(name="CustomMail")] diff --git a/gestioncof/migrations/0011_longer_clippers.py b/gestioncof/migrations/0011_longer_clippers.py deleted file mode 100644 index d4f8549d..00000000 --- a/gestioncof/migrations/0011_longer_clippers.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0010_delete_custommail")] - - operations = [ - migrations.AlterField( - model_name="cofprofile", - name="login_clipper", - field=models.CharField( - verbose_name="Login clipper", blank=True, max_length=32 - ), - ) - ] diff --git a/gestioncof/migrations/0011_remove_cofprofile_num.py b/gestioncof/migrations/0011_remove_cofprofile_num.py deleted file mode 100644 index 88ad31d5..00000000 --- a/gestioncof/migrations/0011_remove_cofprofile_num.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0010_delete_custommail")] - - operations = [migrations.RemoveField(model_name="cofprofile", name="num")] diff --git a/gestioncof/migrations/0012_merge.py b/gestioncof/migrations/0012_merge.py deleted file mode 100644 index 518c150c..00000000 --- a/gestioncof/migrations/0012_merge.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("gestioncof", "0011_remove_cofprofile_num"), - ("gestioncof", "0011_longer_clippers"), - ] - - operations = [] diff --git a/gestioncof/migrations/0013_pei.py b/gestioncof/migrations/0013_pei.py deleted file mode 100644 index 791e63f6..00000000 --- a/gestioncof/migrations/0013_pei.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0012_merge")] - - operations = [ - migrations.AlterField( - model_name="cofprofile", - name="occupation", - field=models.CharField( - verbose_name="Occupation", - max_length=9, - default="1A", - choices=[ - ("exterieur", "Extérieur"), - ("1A", "1A"), - ("2A", "2A"), - ("3A", "3A"), - ("4A", "4A"), - ("archicube", "Archicube"), - ("doctorant", "Doctorant"), - ("CST", "CST"), - ("PEI", "PEI"), - ], - ), - ), - migrations.AlterField( - model_name="cofprofile", - name="type_cotiz", - field=models.CharField( - verbose_name="Type de cotisation", - max_length=9, - default="normalien", - choices=[ - ("etudiant", "Normalien étudiant"), - ("normalien", "Normalien élève"), - ("exterieur", "Extérieur"), - ("gratis", "Gratuit"), - ], - ), - ), - ] diff --git a/gestioncof/migrations/0014_cofprofile_mailing_unernestaparis.py b/gestioncof/migrations/0014_cofprofile_mailing_unernestaparis.py deleted file mode 100644 index efb1fa40..00000000 --- a/gestioncof/migrations/0014_cofprofile_mailing_unernestaparis.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.15 on 2018-09-02 21:13 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0013_pei")] - - operations = [ - migrations.AddField( - model_name="cofprofile", - name="mailing_unernestaparis", - field=models.BooleanField( - default=False, verbose_name="Recevoir les mails unErnestAParis" - ), - ) - ] diff --git a/gestioncof/migrations/0015_psql_choices_niveaux.py b/gestioncof/migrations/0015_psql_choices_niveaux.py deleted file mode 100644 index 17de1a95..00000000 --- a/gestioncof/migrations/0015_psql_choices_niveaux.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.18 on 2019-01-07 22:18 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0014_cofprofile_mailing_unernestaparis")] - - operations = [ - migrations.AlterField( - model_name="petitcoursability", - name="niveau", - field=models.CharField( - choices=[ - ("college", "Collège"), - ("lycee", "Lycée"), - ("prepa1styear", "Prépa 1ère année / L1"), - ("prepa2ndyear", "Prépa 2ème année / L2"), - ("licence3", "Licence 3"), - ("master1", "Master (1ère ou 2ème année)"), - ("other", "Autre (préciser dans les commentaires)"), - ], - max_length=12, - verbose_name="Niveau", - ), - ), - migrations.AlterField( - model_name="petitcoursdemande", - name="niveau", - field=models.CharField( - choices=[ - ("college", "Collège"), - ("lycee", "Lycée"), - ("prepa1styear", "Prépa 1ère année / L1"), - ("prepa2ndyear", "Prépa 2ème année / L2"), - ("licence3", "Licence 3"), - ("master1", "Master (1ère ou 2ème année)"), - ("other", "Autre (préciser dans les commentaires)"), - ], - default="", - max_length=12, - verbose_name="Niveau", - ), - ), - ] diff --git a/gestioncof/migrations/0016_unique_clippers.py b/gestioncof/migrations/0016_unique_clippers.py deleted file mode 100644 index be0a9a87..00000000 --- a/gestioncof/migrations/0016_unique_clippers.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.18 on 2019-01-14 21:36 -from __future__ import unicode_literals - -from django.db import migrations, models - - -def empty_clippers_to_null(apps, schema_editor): - CofProfile = apps.get_model("gestioncof", "CofProfile") - CofProfile.objects.filter(login_clipper="").update(login_clipper=None) - - -def null_clippers_to_empty(apps, schema_editor): - CofProfile = apps.get_model("gestioncof", "CofProfile") - CofProfile.objects.filter(login_clipper__isnull=True).update(login_clipper="") - - -class Migration(migrations.Migration): - dependencies = [("gestioncof", "0015_psql_choices_niveaux")] - - operations = [ - # First, only set null to True (unique is still False) - migrations.AlterField( - model_name="cofprofile", - name="login_clipper", - field=models.CharField( - blank=True, max_length=32, null=True, verbose_name="Login clipper" - ), - ), - # Then, set all empty login_clippers to null - migrations.RunPython(empty_clippers_to_null), - # Finally set unique to True - migrations.AlterField( - model_name="cofprofile", - name="login_clipper", - field=models.CharField( - blank=True, - max_length=32, - null=True, - unique=True, - verbose_name="Login clipper", - ), - ), - ] diff --git a/gestioncof/migrations/0017_petitscours_uniqueness.py b/gestioncof/migrations/0017_petitscours_uniqueness.py deleted file mode 100644 index 079e7b4e..00000000 --- a/gestioncof/migrations/0017_petitscours_uniqueness.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.12 on 2020-09-14 09:39 - -from django.conf import settings -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("gestioncof", "0016_unique_clippers"), - ] - - operations = [ - migrations.AlterUniqueTogether( - name="petitcoursability", - unique_together={("user", "niveau", "matiere")}, - ), - ] diff --git a/gestioncof/migrations/0018_petitscours_email.py b/gestioncof/migrations/0018_petitscours_email.py deleted file mode 100644 index fe65afb5..00000000 --- a/gestioncof/migrations/0018_petitscours_email.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.12 on 2020-10-26 12:35 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("gestioncof", "0017_petitscours_uniqueness"), - ] - - operations = [ - migrations.AlterField( - model_name="petitcoursdemande", - name="email", - field=models.EmailField(max_length=300, verbose_name="Adresse email"), - ), - ] diff --git a/gestioncof/migrations/0019_cofprofile_date_adhesion.py b/gestioncof/migrations/0019_cofprofile_date_adhesion.py deleted file mode 100644 index 5e70398b..00000000 --- a/gestioncof/migrations/0019_cofprofile_date_adhesion.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.28 on 2023-05-22 09:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("gestioncof", "0018_petitscours_email"), - ] - - operations = [ - migrations.AddField( - model_name="cofprofile", - name="date_adhesion", - field=models.DateField( - blank=True, null=True, verbose_name="Date d'adhésion" - ), - ), - ] diff --git a/gestioncof/migrations/__init__.py b/gestioncof/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioncof/models.py b/gestioncof/models.py deleted file mode 100644 index d16b3db2..00000000 --- a/gestioncof/models.py +++ /dev/null @@ -1,268 +0,0 @@ -from django.contrib.auth.models import User -from django.db import models -from django.db.models.signals import post_delete, post_save -from django.dispatch import receiver -from django.utils.translation import ugettext_lazy as _ - -from bda.models import Spectacle -from shared.utils import choices_length - -TYPE_COMMENT_FIELD = (("text", _("Texte long")), ("char", _("Texte court"))) - - -class CofProfile(models.Model): - STATUS_EXTE = "exterieur" - STATUS_1A = "1A" - STATUS_2A = "2A" - STATUS_3A = "3A" - STATUS_4A = "4A" - STATUS_ARCHI = "archicube" - STATUS_DOCTORANT = "doctorant" - STATUS_CST = "CST" - STATUS_PEI = "PEI" - - OCCUPATION_CHOICES = ( - (STATUS_EXTE, _("Extérieur")), - (STATUS_1A, _("1A")), - (STATUS_2A, _("2A")), - (STATUS_3A, _("3A")), - (STATUS_4A, _("4A")), - (STATUS_ARCHI, _("Archicube")), - (STATUS_DOCTORANT, _("Doctorant")), - (STATUS_CST, _("CST")), - (STATUS_PEI, _("PEI")), - ) - - COTIZ_ETUDIANT = "etudiant" - COTIZ_NORMALIEN = "normalien" - COTIZ_EXTE = "exterieur" - COTIZ_GRATIS = "gratis" - - TYPE_COTIZ_CHOICES = ( - (COTIZ_ETUDIANT, _("Normalien étudiant")), - (COTIZ_NORMALIEN, _("Normalien élève")), - (COTIZ_EXTE, _("Extérieur")), - (COTIZ_GRATIS, _("Gratuit")), - ) - - user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile") - login_clipper = models.CharField( - "Login clipper", max_length=32, blank=True, unique=True, null=True - ) - is_cof = models.BooleanField("Membre du COF", default=False) - date_adhesion = models.DateField("Date d'adhésion", blank=True, null=True) - phone = models.CharField("Téléphone", max_length=20, blank=True) - occupation = models.CharField( - _("Occupation"), - default="1A", - choices=OCCUPATION_CHOICES, - max_length=choices_length(OCCUPATION_CHOICES), - ) - departement = models.CharField(_("Département"), max_length=50, blank=True) - type_cotiz = models.CharField( - _("Type de cotisation"), - default="normalien", - choices=TYPE_COTIZ_CHOICES, - max_length=choices_length(TYPE_COTIZ_CHOICES), - ) - mailing_cof = models.BooleanField("Recevoir les mails COF", default=False) - mailing_bda = models.BooleanField("Recevoir les mails BdA", default=False) - mailing_unernestaparis = models.BooleanField( - "Recevoir les mails unErnestAParis", default=False - ) - mailing_bda_revente = models.BooleanField( - "Recevoir les mails de revente de places BdA", default=False - ) - comments = models.TextField("Commentaires visibles par l'utilisateur", blank=True) - is_buro = models.BooleanField("Membre du Burô", default=False) - petits_cours_accept = models.BooleanField( - "Recevoir des petits cours", default=False - ) - petits_cours_remarques = models.TextField( - _("Remarques et précisions pour les petits cours"), blank=True, default="" - ) - - class Meta: - verbose_name = "Profil COF" - verbose_name_plural = "Profils COF" - - def __str__(self): - return self.user.username - - -@receiver(post_save, sender=User) -def create_user_profile(sender, instance, created, **kwargs): - if created: - CofProfile.objects.get_or_create(user=instance) - - -@receiver(post_delete, sender=CofProfile) -def post_delete_user(sender, instance, *args, **kwargs): - instance.user.delete() - - -class Club(models.Model): - name = models.CharField("Nom", max_length=200, unique=True) - description = models.TextField("Description", blank=True) - respos = models.ManyToManyField(User, related_name="clubs_geres", blank=True) - membres = models.ManyToManyField(User, related_name="clubs", blank=True) - - def __str__(self): - return self.name - - -class Event(models.Model): - title = models.CharField("Titre", max_length=200) - location = models.CharField("Lieu", max_length=200) - start_date = models.DateTimeField("Date de début", blank=True, null=True) - end_date = models.DateTimeField("Date de fin", blank=True, null=True) - description = models.TextField("Description", blank=True) - image = models.ImageField("Image", blank=True, null=True, upload_to="imgs/events/") - registration_open = models.BooleanField("Inscriptions ouvertes", default=True) - old = models.BooleanField("Archiver (événement fini)", default=False) - - class Meta: - verbose_name = "Événement" - - def __str__(self): - return self.title - - -class EventCommentField(models.Model): - event = models.ForeignKey( - Event, on_delete=models.CASCADE, related_name="commentfields" - ) - name = models.CharField("Champ", max_length=200) - fieldtype = models.CharField( - "Type", max_length=10, choices=TYPE_COMMENT_FIELD, default="text" - ) - default = models.TextField("Valeur par défaut", blank=True) - - class Meta: - verbose_name = "Champ" - - def __str__(self): - return self.name - - -class EventCommentValue(models.Model): - commentfield = models.ForeignKey( - EventCommentField, on_delete=models.CASCADE, related_name="values" - ) - registration = models.ForeignKey( - "EventRegistration", on_delete=models.CASCADE, related_name="comments" - ) - content = models.TextField("Contenu", blank=True, null=True) - - def __str__(self): - return "Commentaire de %s" % self.commentfield - - -class EventOption(models.Model): - event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name="options") - name = models.CharField("Option", max_length=200) - multi_choices = models.BooleanField("Choix multiples", default=False) - - class Meta: - verbose_name = "Option" - - def __str__(self): - return self.name - - -class EventOptionChoice(models.Model): - event_option = models.ForeignKey( - EventOption, on_delete=models.CASCADE, related_name="choices" - ) - value = models.CharField("Valeur", max_length=200) - - class Meta: - verbose_name = "Choix" - verbose_name_plural = "Choix" - - def __str__(self): - return self.value - - -class EventRegistration(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE) - event = models.ForeignKey(Event, on_delete=models.CASCADE) - options = models.ManyToManyField(EventOptionChoice) - filledcomments = models.ManyToManyField( - EventCommentField, through=EventCommentValue - ) - paid = models.BooleanField("A payé", default=False) - - class Meta: - verbose_name = "Inscription" - unique_together = ("user", "event") - - def __str__(self): - return "Inscription de {} à {}".format(self.user, self.event.title) - - -class Survey(models.Model): - title = models.CharField("Titre", max_length=200) - details = models.TextField("Détails", blank=True) - survey_open = models.BooleanField("Sondage ouvert", default=True) - old = models.BooleanField("Archiver (sondage fini)", default=False) - - class Meta: - verbose_name = "Sondage" - - def __str__(self): - return self.title - - -class SurveyQuestion(models.Model): - survey = models.ForeignKey( - Survey, on_delete=models.CASCADE, related_name="questions" - ) - question = models.CharField("Question", max_length=200) - multi_answers = models.BooleanField("Choix multiples", default=False) - - class Meta: - verbose_name = "Question" - - def __str__(self): - return self.question - - -class SurveyQuestionAnswer(models.Model): - survey_question = models.ForeignKey( - SurveyQuestion, on_delete=models.CASCADE, related_name="answers" - ) - answer = models.CharField("Réponse", max_length=200) - - class Meta: - verbose_name = "Réponse" - - def __str__(self): - return self.answer - - -class SurveyAnswer(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE) - survey = models.ForeignKey(Survey, on_delete=models.CASCADE) - answers = models.ManyToManyField(SurveyQuestionAnswer, related_name="selected_by") - - class Meta: - verbose_name = "Réponses" - unique_together = ("user", "survey") - - def __str__(self): - return "Réponse de %s sondage %s" % ( - self.user.get_full_name(), - self.survey.title, - ) - - -class CalendarSubscription(models.Model): - token = models.UUIDField() - user = models.OneToOneField(User, on_delete=models.CASCADE) - other_shows = models.ManyToManyField(Spectacle) - subscribe_to_events = models.BooleanField(default=True) - subscribe_to_my_shows = models.BooleanField(default=True) - - def __str__(self): - return "Calendrier de %s" % self.user.get_full_name() diff --git a/gestioncof/shared.py b/gestioncof/shared.py deleted file mode 100644 index 1a5bd32e..00000000 --- a/gestioncof/shared.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.conf import settings -from django.contrib.sites.models import Site -from django_cas_ng.backends import CASBackend - - -class COFCASBackend(CASBackend): - def clean_username(self, username): - # Le CAS de l'ENS accepte les logins avec des espaces au début - # et à la fin, ainsi qu’avec une casse variable. On normalise pour - # éviter les doublons. - return username.strip().lower() - - def configure_user(self, user): - clipper = user.username - user.profile.login_clipper = clipper - user.profile.save() - user.email = settings.CAS_EMAIL_FORMAT % clipper - user.save() - return user - - -def context_processor(request): - """Append extra data to the context of the given request""" - data = {"user": request.user, "site": Site.objects.get_current()} - return data diff --git a/gestioncof/signals.py b/gestioncof/signals.py deleted file mode 100644 index cf4b1f16..00000000 --- a/gestioncof/signals.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.contrib import messages -from django.contrib.auth.signals import user_logged_in -from django.dispatch import receiver -from django.utils.translation import ugettext_lazy as _ -from django_cas_ng.signals import cas_user_authenticated - - -@receiver(user_logged_in) -def messages_on_out_login(request, user, **kwargs): - if user.backend.startswith("django.contrib.auth"): - msg = _("Connexion à GestioCOF réussie. Bienvenue {}.").format( - user.get_short_name() - ) - messages.success(request, msg) - - -@receiver(cas_user_authenticated) -def messages_on_cas_login(request, user, **kwargs): - msg = _("Connexion à GestioCOF par CAS réussie. Bienvenue {}.").format( - user.get_short_name() - ) - messages.success(request, msg) diff --git a/gestioncof/static/gestioncof/css/cof.css b/gestioncof/static/gestioncof/css/cof.css deleted file mode 100644 index f98313a6..00000000 --- a/gestioncof/static/gestioncof/css/cof.css +++ /dev/null @@ -1,1151 +0,0 @@ -html,body { - background-color : #A7D4CD; - /* - font-family: 'Hind', sans-serif; - */ - font-family: 'Roboto', sans-serif; - font-weight: 300; -} - -#principal { - background-color : white; - padding : 0; -} - -.no-padding { - padding : 0; -} - -.espace { - clear: both; - min-height : 30px; -} - -.no-bottom-margin { - margin-bottom: 0px !important; -} - -.spacer { - clear: both; -} - -a { - color: #111; - background: transparent; -} - -div.empty-form { - padding-bottom: 2em; -} - -a:hover { - color: #444; - background: transparent; - text-decoration: none; -} - -form#profile table { - border-collapse: collapse; -} - -table#bda_formset { - width: 100%; -} - -.bda-field-spectacle select { - width: 96%; - margin: 0 2%; -} - -.bda-field-double_choice select { - width: 94%; - margin: 0 3%; -} - -.bda-field-double, .bda-field-autoquit, .bda-field-priority { - text-align: center; -} - -.bda-field-double { - width: 15%; -} - -.bda-field-autoquit { - width: 15%; -} - -.tools-cell { - width: 32px; -} - -/* -.tools { - width: 32px; -} - -.tools a.icon.drag-handler, .tools a.icon.delete-handler { - float: left; -} - -.tools a.icon.drag-handler { - margin-right: 5px; -}*/ - -.form-horizontal .form-group { - margin-right: 0px; - margin-left: 0px; -} - -form .control-label { - color: #B56A59; -} - -.remove-btn { - color: #E6516A !important; - font-size: large; -} - -.drag-btn { - color :#4F504B !important; - font-size: large; -} - -.btn-primary, -.btn-primary:active, -.btn-primary:focus, -.btn-primary.focus { - color: #fff; - background-color: #DE826B; - border-color: #AB6452; -} -.btn-primary:active:hover, -.btn-primary:hover { - color: #fff; - background-color: #D89C8D; - border-color: #AB6452; -} - -label { - font-weight: normal; -} - -form#bda_form { - margin-top:15px; -} - -form#bda_form p { - font-size: 0.8em; -} - -form#bda_form thead { - color : #B56A59 -} - -form#bda_form label.control-label { - display: none; -} - -form#bda_form .form-group { - margin-top : 15px; -} - -form#bda_form thead { - background-color: #F7F7F7; -} - -form#bda_form { - margin-top: 0px; - margin-left: -20px; - margin-right: -20px; -} - -table#bda_formset { - border-spacing: 0px 5px; -} - -table#bda_formset th:first-child { - padding-left: 20px; -} - -form.petit-cours_form table#bda_formset td:first-child, -form.petit-cours_form table#bda_formset th:first-child { - padding-left: 20px; -} - -form.petit-cours_form table#bda_formset td:last-child, -form.petit-cours_form table#bda_formset th:last-child { - padding-right: 20px; -} - -form.petit-cours_form .remove-btn { - margin-top : 10px; -} - -table#bda-shotgun td { - padding: 14px; -} - -table#bda-shotgun td.button { - padding: 8px; -} - -table#bda-shotgun a, table#bda-shotgun a:hover { - color: #FFF -} - -tr.dynamic-form td { - background: #F0F0F0; -} - - -tr.dynamic-form.predelete td { - background-color: #FFECEC; -} - - - -tr.ui-sortable-placeholder td, .placeholder-cell { - background: transparent; -} - -/* -tr.dynamic-form td { - border-width: 1px 1px 1px 0px; - border-style: solid; - border-color: #888; - background: #EEE; -} - -tr.dynamic-form td:first-child { - border-left-width: 1px; -} - -tr.dynamic-form.predelete td { - background-color: #FFECEC; - border-width: 1px 1px 1px 0px; - border-style: solid; - border-color: #CCC; -} - -tr.dynamic-form.predelete td:first-child { - border-top-left-radius: 10px; - border-bottom-left-radius: 10px; - border: 1px solid #CCC; -} - -tr.dynamic-form.predelete td:last-child { - border-top-right-radius: 10px; - border-bottom-right-radius: 10px; -} - - -.ui-sortable-helper { -} - -tr.ui-sortable-placeholder td, .placeholder-cell { - background: transparent; - border-width: 1px 1px 1px 0px; - border-style: solid; - border-color: #CCC; -} - -tr.ui-sortable-placeholder td:first-child { - border-top-left-radius: 10px; - border-bottom-left-radius: 10px; - border: 1px solid #CCC; -} - -tr.ui-sortable-placeholder td:last-child { - border-top-right-radius: 10px; - border-bottom-right-radius: 10px; -}*/ - -form { - padding: 0; -} - -#form-placeholder { - margin-top: 15px; -} - -#form-placeholder form#profile { - margin-top: 10px; -} - -form#profile table td, form#profile table th { - width: 400px; -} - -/* centered columns styles */ -.row-centered { - text-align:center; -} -.col-centered { - display:inline-block; - float:none; - /* reset the text-align */ - text-align:left; - /* inline-block space fix */ -} - -form#profile table th { - text-align: right; -} - -fieldset { - border: 0; - margin: 0; - padding: 0; - float: left; - clear: none; - width: auto; -} - -fieldset legend { - display: none; -} - -#main-login-container { - margin-top : 7em; - margin-bottom: 7em; -} - -#main-login-container .banner { - padding-right: 15px; - padding-left: 15px; -} - -#main-login { - background-color: #DE826B; -} - -#main-login .btn-primary { - background-color: #B56A59 -} - -.title-link { - float: none; - display: block; -} - -@media (min-width: 480px) { - .title-link { - float: right !important; - } -} - -#main-content { - background-color: white ; - padding: 20px; -} - -#main-content a, -#main-content a:hover { - color : #D81138; -} - -#main-content h3 { - font-weight: 700; - font-size: large; - color:#DE826B; - padding-bottom: .5em; -} - -#main-content h4 { - color:#DE826B; - padding-bottom: .5em; -} - -#main-content h2, -#main-content h3.horizontal-title { - display : block; - padding : 12px; - margin: -20px -20px 10px -20px ; - font-family: 'Dosis', sans-serif; - font-weight: 700; - color: white; - border-style: solid; - border-color: #4F504B; -} - -#main-content h2 { - font-size: x-large; - background-color:#DE826B; - border-width: 0px 0px 5px 0px; -} - -#main-content h2 a { - color : #C9E5E1; -} - -#main-content h2 a:hover { - color : #ABB8B6; -} - -#main-content h3.horizontal-title { - font-weight: 700; - font-size: large; - background-color:#EAA594; - border-width: 0px 0px 3px 0px; -} - - - -/* -main-container { - max-width: 95%; - width: 1000px; - margin: 7em auto; - display: block; -}*/ -/* -#main { - border: 15px solid #333; - -webkit-border-radius: 20px; - -moz-border-radius: 20px; - border-radius: 20px; - padding: 2em; - box-shadow: 0 0 100px #AAA inset; -}*/ -/* -#main #main-content { - font-size: 1.25em; - margin-top: 10px; -} - -#main #main-content ul { - line-height: 1.3em; -} - -#main h1 { - font-size: 3em; -} - -#main h1 a { - color: #333; - text-decoration: none; -} - -#main h2 { - font-size: 1.5em; - margin-bottom: 10px; -} - -#main h3 { - font-size: 1.3em; - margin-top: 5px; -} - -#main h4 { - margin-top: 10px; - font-weight: bold; -} - -#main p { - margin-top: 8px; - margin-bottom: 8px; -}*/ - -.home-link { - font-family: 'Dosis', sans-serif; - font-weight: 700; - font-size: large ; -} - -.banner h1 { - display : inline; -} - -.block-title { - display : block; - padding : 10px; - font-family: 'Dosis', sans-serif; - font-weight: 700; -} - -h3.block-title { - color: white; - background-color:#DE826B; - border-style: solid; - border-width: 0px 0px 5px 0px; - border-color: #4F504B; -} - -h4.block-title { - color: white; - background-color:#EAA594; - border-style: solid; - border-width: 0px 0px 2px 0px; - border-color: #4F504B; -} - -.hm-block h4 { - color: #B56A59; -} - -.hm-block { - background-color : white ; - padding : 15px; -} - -.buro-user-hm, -.normal-user-hm, -.foot-banner{ - padding-left : 0px; - padding-right : 0px; -} - -@media (min-width: 768px) { - .buro-user-hm, - .normal-user-hm, - .foot-banner { - padding-left : 15px; - padding-right : 15px; - } -} - - -.container-fluid { - padding : 0; -} - -.buro-user-hm h3.block-title { - background: repeating-linear-gradient( - 45deg, - #DE826B, - #DE826B 4px, - #B56A59 4px, - #B56A59 8px - ); -} - - -.home-menu h3.block-title { - font-size: large; - text-transform: uppercase; -} - -#main-content form li { - list-style: none; -} - -#main form ul.errorlist li { - font-weight: bold; - color: #B00000; - background-color: transparent; - list-style: disc; -} - -.inscription-bottom { - display: block; - padding: 0px 20px 20px 20px ; - margin-top : 15px; -} - -.bda-prix { - font-weight: bold; - color:#DE826B; -} - -.table-top { - background-color: #F0F0F0; - margin: -20px; - margin-top: 0px; - padding:20px; - padding-bottom: 28px; - padding-top: 8px; -} - -form#bda_form ul.errorlist li { - padding-left: 0px; - list-style: none; -} - -#main-login.login_block { - padding: 2em; -} - -#login_clipper, #login_outsider { - height: 200px; - text-align: center; - font-size: 2em; - font-weight: bold; - text-decoration: none; - color: #FFF; - padding: 70px 20px 40px 20px; -} - -#login_clipper { - background-color: #49A5E3; -} - -#login_clipper:hover { - background-color: #71B5E3; -} - -#login_outsider { - background-color: #E36748; -} - -#login_outsider:hover { - background-color: #E38871; -} - -#main-login label { - font-size: 11px; -} - -#main-login label span.accesskey { - text-decoration: underline; -} - -#main-login input { - letter-spacing: 1px; -} - -#main-login .btn-row { - float: right; -} - -.btn-submit, .btn-addmore { - float: none; - clear: none; - display: inline; - letter-spacing: 0; - color: #333; - padding: 5px; - text-transform: uppercofe; - font-variant: small-caps; - border: 1px solid #ccc; - background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#EEE), to(#CCC)); - background: -moz-linear-gradient(19% 75% 90deg,#EEE, #CCC); - background: linear-gradient(center top, #EEE, #CCC); -} - -#main-login .btn-reset { - float: none; - clear: none; - margin-left: 5px; - border: 0; - border-left: 1px solid #ddd; - background: transparent; - color: #777; - text-transform: lowercase; - letter-spacing: 0; -} - -/* RESET --------------------------------- */ -/* reset some properties for elements since defaults are not crossbrowser - http: //meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/ */ -html,body,div,span,h1,h2,h3,h4,p,a,img,ul,li,fieldset,form,label,legend { - margin: 0; - padding: 0; - border: 0; - outline: 0; - /* - font-weight: inherit; - font-style: inherit; - font-family: inherit; - */ - font-size: 100%; - vertical-align: baseline; -} - -:focus { - outline: 0; -} - -ul { - font-size: 1.1em; - padding: 6px 0 6px 12px; -} - -.home-menu ul { - padding: 0px 0px 15px 0px; - list-style-type: none; -} - -.home-menu ul:last-child { - padding: 0; - list-style-type: none; -} - -/* -body { - font: normal 400 62.5%/1.0 Verdana, sans-serif; - background-color: #eee; - color: #333; -}*/ - -/* HEADER --------------------------------- */ - -header { - background-color :#4F504B; - font-size:large; - color: white; - padding: 0; -} - -header .banner { - padding-top : 15px; - padding-bottom : 15px; -} - -header a, -header a:hover, -header a:focus, -header a:active { - color : white ; -} - - -.user-is-cof { - color : #ADE297; -} -.user-is-not-cof { - color: #EE8585; -} - -/* -#header #homelink { - float: right; -}*/ - -tt { - font-weight: bold; -} - -header h1 { - font-family: 'Dosis', sans-serif; - font-size: x-large; -} - -header h2 { - display : inline-block; -} - -.member-status { - float: left; - display : block; - width:100%; - padding-top: 3px; -} - -@media (min-width: 480px) { - .member-status { - float: right; - display: inline; - width: auto; - } -} - -.hidden-xxs { - display: none; -} -@media (min-width: 480px) { - .hidden-xxs { - display: inline; - } -} - -.secondary { - float:right; -} - -header .btn-toolbar { - float : left ; -} - -header .btn-default { - color: white; - background-color: #4F504B; - border-color: white; -} -header .btn-default:focus, -header .btn-default.focus { - color: white; - background-color: #686965; - border-color: white; -} -header .btn-default:hover { - color: white; - background-color: #686965; - border-color: white; -} -header .btn-default:active, -header .btn-default.active, -header .open > .dropdown-toggle.btn-default { - background-color: #686965; - border-color: white; -} - -/* Announcements banner ------------------ */ - -#banner { - background-color: #d86b01; - width: 100%; - text-align: center; - padding: 10px; - color: white; - font-size: larger; -} - - -/* FORMS --------------------------------- */ - -input[disabled], input[readonly] { - color: #BBB; -} - -input[type="text"], input[type=password] { - border: 1px solid #888; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - box-shadow: 0 0 3px #AAA inset; - padding: 0px 2px 0px 2px; -} - -input#search_autocomplete { - width: 90%; - font-size: 18px; - height: 40px; - padding: 10px 8px; - margin: 0 auto; - margin-top: 0px; - display: block; - color: #aaa; -} - -input#search_autocomplete:focus { - color: #343a4a; -} - -input[type=number][readonly] { - -moz-appearance:textfield; -} - -input[type=number][readonly]::-webkit-inner-spin-button, -input[type=number][readonly]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; -} - -.autocomplete { - margin-bottom:5px; -} - -.yourlabs-autocomplete ul { - list-style-type: none; - padding: 0; - margin: 0; - font-size: 1.5em; -} - -.yourlabs-autocomplete li { - padding: 6px 5px; - display: block; -} - -.yourlabs-autocomplete li a { - text-decoration: none; -} - -.yourlabs-autocomplete li a span.highlight { - text-decoration: underline; - font-weight: bold; -} - -li.autocomplete-header { - background-color: #FFEF9E; - padding: 3px 5px; - color: #000; -} - -li.autocomplete-value, -li.autocomplete-new { - cursor: pointer; -} - -.yourlabs-autocomplete li.hilight { - background-color: #EA4839; - color: #FFF; -} - -.yourlabs-autocomplete li.hilight a {color: #FFF; } -.yourlabs-autocomplete { - border: solid; - border-width: 0px 1px 1px 1px; - border-color: #B3B3B3; - background: #FAFAFA; - display: block; -} -.yourlabs-autocomplete.outer-container { position: relative; } - -.yourlabs-autocomplete.inner-container { - position: absolute; - left: -1px; - top: 2px; - border: 1px solid #B7B7B7; - width: 618px; - background: #fff; -} - -#main-content > .yourlabs-autocomplete { - display:none; -} - -.yourlabs-autocomplete.inner-container { - margin: 0; - padding: 0; - overflow-x: hidden; - overflow-y: auto; - position: relative; - list-style-type: none; -} - -hr { - border-color : #D4D6C8; -} - -/* -hr { - border: 0; - height: 1px; - background: #333; - background: -webkit-gradient(linear, left top, right top, color-stop(0%,hsla(0,0%,70%,0)), color-stop(50%,hsla(0,0%,70%,.75)), color-stop(100%,hsla(0,0%,70%,0))); - background: -webkit-linear-gradient(left, hsla(0,0%,70%,0) 0%, hsla(0,0%,70%,.75) 50%, hsla(0,0%,70%,0) 100%); - background: -moz-linear-gradient(left, hsla(0,0%,70%,0) 0%, hsla(0,0%,70%,.75) 50%, hsla(0,0%,70%,0) 100%); - background: -ms-linear-gradient(left, hsla(0,0%,70%,0) 0%, hsla(0,0%,70%,.75) 50%, hsla(0,0%,70%,0) 100%); - background: -o-linear-gradient(left, hsla(0,0%,70%,0) 0%, hsla(0,0%,70%,.75) 50%, hsla(0,0%,70%,0) 100%); - background: linear-gradient(left, hsla(0,0%,70%,0) 0%, hsla(0,0%,70%,.75) 50%, hsla(0,0%,70%,0) 100%); -}*/ - -.tristate:hover { - cursor: pointer; -} - -abbr[title] { - cursor: help; - border-bottom: 1px dotted #999999; -} - -abbr.initialism { - font-size: 90%; - text-transform: uppercase; -} - -blockquote { - padding: 0 0 0 15px; - margin: 0 0 20px; - border-left: 5px solid #eeeeee; -} - -blockquote p { - margin-bottom: 0; - font-size: 16px; - font-weight: 300; - line-height: 25px; -} - -blockquote small { - display: block; - line-height: 20px; - color: #999999; -} - -blockquote small:before { - content: '\2014 \00A0'; -} - -blockquote.pull-right { - float: right; - padding-right: 15px; - padding-left: 0; - border-right: 5px solid #eeeeee; - border-left: 0; -} - -blockquote.pull-right p, -blockquote.pull-right small { - text-align: right; -} - -blockquote.pull-right small:before { - content: ''; -} - -blockquote.pull-right small:after { - content: '\00A0 \2014'; -} - -q:before, -q:after, -blockquote:before, -blockquote:after { - content: ""; -} - -address { - display: block; - margin-bottom: 20px; - font-style: normal; - line-height: 20px; -} - -code, -pre { - padding: 0 3px 2px; - font-family: Monaco, Menlo, Consolas, "Courier New", monospace; - font-size: 12px; - color: #333333; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -code { - padding: 2px 4px; - color: #d14; - background-color: #f7f7f9; - border: 1px solid #e1e1e8; -} - -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 20px; - word-break: break-all; - word-wrap: break-word; - white-space: pre; - white-space: pre-wrap; - background-color: #f5f5f5; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.15); - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -pre.prettyprint { - margin-bottom: 20px; -} - -pre code { - padding: 0; - color: inherit; - background-color: transparent; - border: 0; -} - -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; -} - - -/* Louis pour etat places*/ - - -.greenratio { - background-color: #ADE297; - text-align: center; -} -.orangeratio { - background-color: #EEE285; - text-align: center; -} -.redratio { - background-color: #EE8585; - text-align: center; -} - -th[data-sort]{ - cursor:pointer; -} - -tbody tr.clickable-row { - cursor:pointer; -} - -tr.awesome{ - color: red; -} - -.title-foot { - padding-right:0px; - padding-left:0px; -} - -.foot-banner { - font-family: 'Dosis', sans-serif; - background-color :#4F504B; - color: white; - padding: 20px; -} - -.petitcours-raw { - padding:20px; - background:#fff; - -} - -/* Messages */ - -.messages .alert .close { - top:0; - right:0; -} - -.messages .alert { - padding:10px 15px; - margin:0; - border:0; - border-radius:0; -} - -div.messages div.alert-info { - background-color: #659C94; -} - -div.messages div.alert-success { - background-color: #41C342; -} - -div.messages div.alert-warning { - background-color: #efa50f; -} - -div.messages div.alert-error { - background-color: #e14b4b; -} - -div.messages div.alert-info div.container, -div.messages div.alert-error div.container, -div.messages div.alert-warning div.container, -div.messages div.alert-success div.container { - color: white; -} - -div.messages div.alert div.container a { - color: inherit; -} - -/* Help text */ - -p.help-block { - margin: 5px auto; - width: 90%; -} - -div.bg-info { - border-radius: 3px; - padding: 0.3em 1em; - margin-left: 1em; - margin-right: 1em; -} - -.bootstrap-form-reduce > .form-group { - margin-top: -16px; -} diff --git a/gestioncof/static/gestioncof/src/jquery.ui.touch-punch.js b/gestioncof/static/gestioncof/src/jquery.ui.touch-punch.js deleted file mode 100644 index 16ce41d1..00000000 --- a/gestioncof/static/gestioncof/src/jquery.ui.touch-punch.js +++ /dev/null @@ -1,180 +0,0 @@ -/*! - * jQuery UI Touch Punch 0.2.3 - * - * Copyright 2011–2014, Dave Furfero - * Dual licensed under the MIT or GPL Version 2 licenses. - * - * Depends: - * jquery.ui.widget.js - * jquery.ui.mouse.js - */ -(function ($) { - - // Detect touch support - $.support.touch = 'ontouchend' in document; - - // Ignore browsers without touch support - if (!$.support.touch) { - return; - } - - var mouseProto = $.ui.mouse.prototype, - _mouseInit = mouseProto._mouseInit, - _mouseDestroy = mouseProto._mouseDestroy, - touchHandled; - - /** - * Simulate a mouse event based on a corresponding touch event - * @param {Object} event A touch event - * @param {String} simulatedType The corresponding mouse event - */ - function simulateMouseEvent (event, simulatedType) { - - // Ignore multi-touch events - if (event.originalEvent.touches.length > 1) { - return; - } - - event.preventDefault(); - - var touch = event.originalEvent.changedTouches[0], - simulatedEvent = document.createEvent('MouseEvents'); - - // Initialize the simulated mouse event using the touch event's coordinates - simulatedEvent.initMouseEvent( - simulatedType, // type - true, // bubbles - true, // cancelable - window, // view - 1, // detail - touch.screenX, // screenX - touch.screenY, // screenY - touch.clientX, // clientX - touch.clientY, // clientY - false, // ctrlKey - false, // altKey - false, // shiftKey - false, // metaKey - 0, // button - null // relatedTarget - ); - - // Dispatch the simulated event to the target element - event.target.dispatchEvent(simulatedEvent); - } - - /** - * Handle the jQuery UI widget's touchstart events - * @param {Object} event The widget element's touchstart event - */ - mouseProto._touchStart = function (event) { - - var self = this; - - // Ignore the event if another widget is already being handled - if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) { - return; - } - - // Set the flag to prevent other widgets from inheriting the touch event - touchHandled = true; - - // Track movement to determine if interaction was a click - self._touchMoved = false; - - // Simulate the mouseover event - simulateMouseEvent(event, 'mouseover'); - - // Simulate the mousemove event - simulateMouseEvent(event, 'mousemove'); - - // Simulate the mousedown event - simulateMouseEvent(event, 'mousedown'); - }; - - /** - * Handle the jQuery UI widget's touchmove events - * @param {Object} event The document's touchmove event - */ - mouseProto._touchMove = function (event) { - - // Ignore event if not handled - if (!touchHandled) { - return; - } - - // Interaction was not a click - this._touchMoved = true; - - // Simulate the mousemove event - simulateMouseEvent(event, 'mousemove'); - }; - - /** - * Handle the jQuery UI widget's touchend events - * @param {Object} event The document's touchend event - */ - mouseProto._touchEnd = function (event) { - - // Ignore event if not handled - if (!touchHandled) { - return; - } - - // Simulate the mouseup event - simulateMouseEvent(event, 'mouseup'); - - // Simulate the mouseout event - simulateMouseEvent(event, 'mouseout'); - - // If the touch interaction did not move, it should trigger a click - if (!this._touchMoved) { - - // Simulate the click event - simulateMouseEvent(event, 'click'); - } - - // Unset the flag to allow other widgets to inherit the touch event - touchHandled = false; - }; - - /** - * A duck punch of the $.ui.mouse _mouseInit method to support touch events. - * This method extends the widget with bound touch event handlers that - * translate touch events to mouse events and pass them to the widget's - * original mouse event handling methods. - */ - mouseProto._mouseInit = function () { - - var self = this; - - // Delegate the touch handlers to the widget's element - self.element.bind({ - touchstart: $.proxy(self, '_touchStart'), - touchmove: $.proxy(self, '_touchMove'), - touchend: $.proxy(self, '_touchEnd') - }); - - // Call the original $.ui.mouse init method - _mouseInit.call(self); - }; - - /** - * Remove the touch event handlers - */ - mouseProto._mouseDestroy = function () { - - var self = this; - - // Delegate the touch handlers to the widget's element - self.element.unbind({ - touchstart: $.proxy(self, '_touchStart'), - touchmove: $.proxy(self, '_touchMove'), - touchend: $.proxy(self, '_touchEnd') - }); - - // Call the original $.ui.mouse destroy method - _mouseDestroy.call(self); - }; - -})(jQuery); \ No newline at end of file diff --git a/gestioncof/static/gestioncof/src/stupidtable.js b/gestioncof/static/gestioncof/src/stupidtable.js deleted file mode 100644 index 705346dc..00000000 --- a/gestioncof/static/gestioncof/src/stupidtable.js +++ /dev/null @@ -1,158 +0,0 @@ -// Stupid jQuery table plugin. - -// Call on a table -// sortFns: Sort functions for your datatypes. -(function($) { - - $.fn.stupidtable = function(sortFns) { - return this.each(function() { - var $table = $(this); - sortFns = sortFns || {}; - - // ==================================================== // - // Utility functions // - // ==================================================== // - - // Merge sort functions with some default sort functions. - sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns); - - // Return the resulting indexes of a sort so we can apply - // this result elsewhere. This returns an array of index numbers. - // return[0] = x means "arr's 0th element is now at x" - var sort_map = function(arr, sort_function, reverse_column) { - var map = []; - var index = 0; - if (reverse_column) { - for (var i = arr.length-1; i >= 0; i--) { - map.push(i); - } - } - else { - var sorted = arr.slice(0).sort(sort_function); - for (var i=0; i b) return +1; - return 0; - }, - "string-ins": function(a, b) { - a = a.toLowerCase(); - b = b.toLowerCase(); - if (a < b) return -1; - if (a > b) return +1; - return 0; - } - }; - -})(jQuery); diff --git a/gestioncof/static/gestioncof/vendor/jquery.ui.touch-punch.min.js b/gestioncof/static/gestioncof/vendor/jquery.ui.touch-punch.min.js deleted file mode 100644 index 31272ce6..00000000 --- a/gestioncof/static/gestioncof/vendor/jquery.ui.touch-punch.min.js +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * jQuery UI Touch Punch 0.2.3 - * - * Copyright 2011–2014, Dave Furfero - * Dual licensed under the MIT or GPL Version 2 licenses. - * - * Depends: - * jquery.ui.widget.js - * jquery.ui.mouse.js - */ -!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); \ No newline at end of file diff --git a/gestioncof/static/gestioncof/vendor/stupidtable.min.js b/gestioncof/static/gestioncof/vendor/stupidtable.min.js deleted file mode 100644 index f80299f1..00000000 --- a/gestioncof/static/gestioncof/vendor/stupidtable.min.js +++ /dev/null @@ -1,3 +0,0 @@ -(function(d){d.fn.stupidtable=function(b){return this.each(function(){var a=d(this);b=b||{};b=d.extend({},d.fn.stupidtable.default_sort_fns,b);a.on("click","th",function(){var n=a.children("tbody").children("tr"),e=d(this),j=0,m=d.fn.stupidtable.dir;a.find("th").slice(0,e.index()).each(function(){var a=d(this).attr("colspan")||1;j+=parseInt(a,10)});var l=e.data("sort-dir")===m.ASC?m.DESC:m.ASC,p=l==m.DESC?e.data("sort-desc")||e.data("sort")||null:e.data("sort")||null;null!==p&&(a.trigger("beforetablesort", -{column:j,direction:l}),a.css("display"),setTimeout(function(){var k=[],c=b[p];n.each(function(a,b){var c=d(b).children().eq(j),e=c.data("sort-value"),c="undefined"!==typeof e?e:c.text();k.push(c)});var f=[],g=0;if(e.data("sort-dir")&&!e.data("sort-desc"))for(c=k.length-1;0<=c;c--)f.push(c);else for(var h=k.slice(0).sort(c),c=0;ca?1:0},"string-ins":function(b,a){b=b.toLowerCase();a=a.toLowerCase();return ba?1:0}}})(jQuery); diff --git a/gestioncof/templates/404.html b/gestioncof/templates/404.html deleted file mode 100644 index 73f713f6..00000000 --- a/gestioncof/templates/404.html +++ /dev/null @@ -1,65 +0,0 @@ -{% load static %} - - - - - - COF ENS - Page non trouvée / Page not found - - - - - - -

    404

    -

    Page non trouvée Page not found

    -
    -

    → retourner sur le site du COF - back to the COF's website

    -

    → retourner sur GestioCOF - back to GestioCOF

    -

    → retourner sur le site de la K-Fêt - back to the K-Fêt's website

    -
    - - diff --git a/gestioncof/templates/500.html b/gestioncof/templates/500.html deleted file mode 100644 index 451991a2..00000000 --- a/gestioncof/templates/500.html +++ /dev/null @@ -1,5 +0,0 @@ -500 -

    500

    -

    Wouh pinaise, y'a une cacahuète dans le beurre !

    - - diff --git a/gestioncof/templates/base.html b/gestioncof/templates/base.html deleted file mode 100644 index d313ee9d..00000000 --- a/gestioncof/templates/base.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load staticfiles %} - - - - - {{ site.name }} - - - - - {# CSS #} - - - - - - - {# JS #} - - - - {% block extra_head %}{% endblock %} - - - {% block content %}{% endblock %} - - diff --git a/gestioncof/templates/base_title.html b/gestioncof/templates/base_title.html deleted file mode 100644 index 2e9687dd..00000000 --- a/gestioncof/templates/base_title.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "gestioncof/base_header.html" %} - -{% block interm_content %} - -
    -
    -
    - {% block realcontent %}{% endblock %} -
    -
    -
    -
    -
    -

    Pour tout problème : cof@ens.fr.

    -
    -
    -
    -
    - -{% endblock %} diff --git a/gestioncof/templates/buro-denied.html b/gestioncof/templates/buro-denied.html deleted file mode 100644 index 1e477751..00000000 --- a/gestioncof/templates/buro-denied.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

    Section réservée au Burô.

    -{% endblock %} diff --git a/gestioncof/templates/cof-denied.html b/gestioncof/templates/cof-denied.html deleted file mode 100644 index b2a12717..00000000 --- a/gestioncof/templates/cof-denied.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

    Section réservée aux membres du COF -- merci de vous inscrire au COF ou de passer au COF/nous envoyer un mail si vous êtes déjà membre :)

    -{% endblock %} diff --git a/gestioncof/templates/event_status.html b/gestioncof/templates/event_status.html deleted file mode 100644 index 40bda7db..00000000 --- a/gestioncof/templates/event_status.html +++ /dev/null @@ -1,51 +0,0 @@ -{% extends "base_title.html" %} -{% load utils %} - -{% block page_size %}col-sm-8{% endblock %} - -{% block realcontent %} -

    Événement: {{ event.title }}{% if user.is_staff %} Administration{% endif %}

    - {% if event.details %} -

    {{ event.details }}

    -
    - {% endif %} - {% include "tristate_js.html" %} -

    Filtres

    -
    - {% csrf_token %} - {{ form.as_p }} - -
    -
    -

    Résultats globaux

    - {% for option in options %} -

    {{ option.value }}

    -
      - {% for choice in option.choices.all %} -
    • {{ choice.value }} : {{ choices_count|key:choice.id }}
    • - {% endfor %} -
    - {% endfor %} -
    -

    Réponses individuelles

    -
      - {% for user_choice in user_choices %}{% with user_choice.user as auser %} - {% if user_choice.options.all %} -
    • - {% if auser.first_name and auser.last_name %}{{ auser.first_name }} {{ auser.last_name }} - {% else %}{{ auser.username }}{% endif %} : -
        - {% for choice in user_choice.options.all %} -
      • {{ choice.event_option.name }} : {{ choice.value }}
      • - {% endfor %} -
      -
    • - {% endif %} - {% endwith %}{% endfor %} -
    -
    -

    Mailing list

    - -{% endblock %} diff --git a/gestioncof/templates/gestioncof/banner_update.html b/gestioncof/templates/gestioncof/banner_update.html deleted file mode 100644 index b2432eae..00000000 --- a/gestioncof/templates/gestioncof/banner_update.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base_title.html" %} -{% load bootstrap %} -{% load i18n %} - -{% block page_size %}col-sm-8{%endblock%} - -{% block realcontent %} -

    {% trans "Global configuration" %}

    -
    -
    - {% csrf_token %} - - {% for field in form %} - {{ field | bootstrap }} - {% endfor %} - -
    -
    - -
    -
    -{% endblock %} diff --git a/gestioncof/templates/gestioncof/base_header.html b/gestioncof/templates/gestioncof/base_header.html deleted file mode 100644 index e5f757a7..00000000 --- a/gestioncof/templates/gestioncof/base_header.html +++ /dev/null @@ -1,44 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
    - -
    - -{% if config.gestion_banner %} - -{% endif %} - -{% if messages %} - {% for message in messages %} -
    -
    -
    - - {% if 'safe' in message.tags %} - {{ message|safe }} - {% else %} - {{ message }} - {% endif %} -
    -
    -
    - {% endfor %} -{% endif %} -{% block interm_content %}{% endblock %} -{% endblock %} diff --git a/gestioncof/templates/gestioncof/calendar_subscription.html b/gestioncof/templates/gestioncof/calendar_subscription.html deleted file mode 100644 index 4b9e3cbb..00000000 --- a/gestioncof/templates/gestioncof/calendar_subscription.html +++ /dev/null @@ -1,49 +0,0 @@ -{% extends "base_title.html" %} -{% load bootstrap %} - -{% block realcontent %} - -

    Calendrier dynamique

    - -

    Ce formulaire vous permet de définir un calendrier dynamique compatible avec -n'importe quel logiciel ou application d'agenda. Vous pouvez choisir de -souscrire aux événements du COF et/ou aux spectacles BdA. -

    - -{% if token %} -

    Votre calendrier (compatible avec toutes les applications d'agenda) se trouve à -cette adresse.

    - -
      -
    • Pour l'ajouter à Thunderbird (lightning), il faut copier ce lien et aller - dans Fichier > Nouveau > Agenda puis choisir Sur le - réseau et le format .ics.
    • -
    • Avec Apple, il suffit de cliquer sur lien et d'ouvrir le fichier avec - l'application calendrier.
    • -
    • Google Agenda permet d'importer le fichier au format .ics depuis le menu - Préférences.
    • -
    -{% endif %} - -
    - -
    -{% csrf_token %} -{{ form | bootstrap }} -

    - - -

    - - -
    - - -{% endblock %} diff --git a/gestioncof/templates/gestioncof/event.html b/gestioncof/templates/gestioncof/event.html deleted file mode 100644 index f388bc25..00000000 --- a/gestioncof/templates/gestioncof/event.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

    Événement: {{ event.title }}

    - {% if event.details %} -

    {{ event.details }}

    - {% endif %} -
    - {% csrf_token %} - {{ form.as_p }} - -
    -{% endblock %} diff --git a/gestioncof/templates/gestioncof/home.html b/gestioncof/templates/gestioncof/home.html deleted file mode 100644 index 45b3e225..00000000 --- a/gestioncof/templates/gestioncof/home.html +++ /dev/null @@ -1,150 +0,0 @@ -{% extends "gestioncof/base_header.html" %} -{% load wagtailcore_tags %} - -{% block homelink %} -{% endblock %} - -{% block interm_content %} - -
    -
    -
    - {% if open_surveys %} -

    Sondages en cours

    -
    - -
    - {% endif %} - - - {% if user.profile.is_cof %} - {% if open_tirages %} -

    Tirages du BdA

    -
    - {% for tirage in open_tirages %} - - {% endfor %} -
    - {% endif %} - {% endif %} - -

    K-Fêt

    -
    -
      - {# TODO: Since Django 1.9, we can store result with "as", allowing proper value management (if None) #} -
    • Page d'accueil
    • -
    • Calendrier
    • - {% if perms.kfet.is_team %} -
    • K-Psul
    • - {% endif %} -
    -
    - -

    Divers

    -
    - -
    -
    - {% if user.profile.is_buro %} -
    -

    Administration

    -
    - - -
    -

    Gestion tirages BdA

    -
    - {% if active_tirages %} - {% for tirage in active_tirages %} - - {% endfor %} - {% else %} - Pas de tirage ouvert pour l'instant. - {% endif %} -
    - -

    Liens utiles

    - -
    - {% endif %} -
    - - {% if not user.profile.is_buro %} -
    -
    -
    -

    - Dévelopé par KDEns - - Pour tout problème : cof@ens.fr. -

    -
    -
    -
    - {% else %} -
    -
    -
    -

    - Dévelopé par KDEns - - Pour tout problème : cof@ens.fr. -

    -
    -
    -
    - {% endif %} - -
    -{% endblock %} diff --git a/gestioncof/templates/gestioncof/mails/welcome.txt b/gestioncof/templates/gestioncof/mails/welcome.txt deleted file mode 100644 index aa7ff326..00000000 --- a/gestioncof/templates/gestioncof/mails/welcome.txt +++ /dev/null @@ -1,11 +0,0 @@ -Bonjour {{ member.first_name }} et bienvenue au COF ! - -Tu trouveras plein de trucs cool sur le site du COF : https://cof.ens.fr/ et notre page Facebook : https://www.facebook.com/cof.ulm -Et n'oublie pas d'aller découvrir GestioCOF, la plateforme de gestion du COF ! -Si tu as des questions, tu peux nous envoyer un mail à cof@ens.fr (on aime le spam), ou passer nous voir au Burô près de la Courô du lundi au vendredi de 12h à 14h et de 18h à 20h. - -Retrouvez les évènements de rentrée pour les conscrit.e.s et les vieux/vieilles organisés par le COF et ses clubs ici : https://cof.ens.fr/planningrentree. - -Amicalement, - -Ton COF qui t'aime. \ No newline at end of file diff --git a/gestioncof/templates/gestioncof/profile.html b/gestioncof/templates/gestioncof/profile.html deleted file mode 100644 index 9d418ef6..00000000 --- a/gestioncof/templates/gestioncof/profile.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "base_title.html" %} -{% load bootstrap %} - -{% block page_size %}col-sm-8{%endblock%} - -{% block realcontent %} -

    Modifier mon profil

    -
    -
    - {% csrf_token %} - {{ user_form | bootstrap }} - {{ profile_form | bootstrap }} -
    - - {% if user.profile.comments %} -
    -

    Commentaires

    -

    {{ user.profile.comments }}

    -
    - {% endif %} - -
    - -
    -
    -{% endblock %} diff --git a/gestioncof/templates/gestioncof/registration_form.html b/gestioncof/templates/gestioncof/registration_form.html deleted file mode 100644 index 37f24cff..00000000 --- a/gestioncof/templates/gestioncof/registration_form.html +++ /dev/null @@ -1,32 +0,0 @@ -{% load bootstrap %} - - {% if login_clipper %} -

    Inscription associée au compte clipper {{ login_clipper }}

    - {% elif member %} -

    Inscription du compte GestioCOF existant {{ member.username }}

    - {% else %} -

    Inscription d'un nouveau compte (extérieur ?)

    - {% endif %} -
    - {% csrf_token %} - - {{ user_form | bootstrap }} - {{ profile_form | bootstrap }} -
    -
    - - {{ clubs_form | bootstrap }} -
    - {{ event_formset.management_form }} - {% for event_form in event_formset %} -
    -

    Inscription {{ event_form.event.title }} :

    - - {{ event_form | bootstrap }} -
    - {% endfor %} - {% if login_clipper or member %} - - {% endif %} - -
    diff --git a/gestioncof/templates/gestioncof/registration_post.html b/gestioncof/templates/gestioncof/registration_post.html deleted file mode 100644 index 5eca28c9..00000000 --- a/gestioncof/templates/gestioncof/registration_post.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

    Inscription d'un nouveau membre

    -
    - {% include "gestioncof/registration_form.html" %} -
    -{% endblock %} diff --git a/gestioncof/templates/gestioncof/reset_comptes.html b/gestioncof/templates/gestioncof/reset_comptes.html deleted file mode 100644 index 55d54376..00000000 --- a/gestioncof/templates/gestioncof/reset_comptes.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

    Remise à zéro des membres COF

    - {% if is_done%} -

    {{nb_adherents}} compte{{ nb_adherents|pluralize }} désinscrit{{ nb_adherents|pluralize }} du COF.

    - {% else%} -
    ATTENTION : Cette action est irréversible.
    -

    Voulez-vous vraiment remettre à zéro le statut COF de tous les membres actuels ?

    -
    - {% csrf_token %} -
    - {% endif %} -{% endblock %} \ No newline at end of file diff --git a/gestioncof/templates/gestioncof/search_results.html b/gestioncof/templates/gestioncof/search_results.html deleted file mode 100644 index fdcd36ce..00000000 --- a/gestioncof/templates/gestioncof/search_results.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "shared/search_results.html" %} -{% load i18n %} - -{% block extra_section %} -
  • - {% if not results %} - - {% trans "Aucune correspondance trouvée" %} - - {% else %} - - {% trans "Pas dans la liste ?" %} - - {% endif %} -
  • -
  • - - {% trans "Créer un compte" %} - -
  • -{% endblock %} diff --git a/gestioncof/templates/gestioncof/survey.html b/gestioncof/templates/gestioncof/survey.html deleted file mode 100644 index 9d4d67b3..00000000 --- a/gestioncof/templates/gestioncof/survey.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "base_title.html" %} -{% load bootstrap %} - -{% block page_size %}col-sm-8{% endblock %} - -{% block realcontent %} -

    Sondage: {{ survey.title }}

    - {% if survey.details %} -

    {{ survey.details }}

    - {% endif %} -
    - {% csrf_token %} - {{ form | bootstrap}} - -
    - {% if current_answer %} - - {% endif %} - -
    -
    -{% endblock %} diff --git a/gestioncof/templates/gestioncof/utile_cof.html b/gestioncof/templates/gestioncof/utile_cof.html deleted file mode 100644 index 71a3b865..00000000 --- a/gestioncof/templates/gestioncof/utile_cof.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "base_title.html" %} - -{% block homelink %} -{% endblock %} - -{% block realcontent %} -

    Liens utiles du COF

    -

    COF

    - - -

    Mega

    - - -

    Note : pour ouvrir les fichiers .csv avec Excel, il faut - passer par Fichier > Importer et sélectionner la - virgule comme séparateur.

    -{% endblock %} diff --git a/gestioncof/templates/liste_clubs.html b/gestioncof/templates/liste_clubs.html deleted file mode 100644 index c248a7a6..00000000 --- a/gestioncof/templates/liste_clubs.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends "base_title.html" %} - -{% block page_size %}col-sm-8{% endblock %} - -{% block realcontent %} -

    Clubs enregistrés sur GestioCOF

    - -{% endblock %} diff --git a/gestioncof/templates/liste_mails.html b/gestioncof/templates/liste_mails.html deleted file mode 100644 index 2cc7e150..00000000 --- a/gestioncof/templates/liste_mails.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

    {{ titre }}

    - -{% endblock %} diff --git a/gestioncof/templates/login.html b/gestioncof/templates/login.html deleted file mode 100644 index bfc2dbb8..00000000 --- a/gestioncof/templates/login.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends "base.html" %} -{% load bootstrap %} - -{% block content %} -
    -
    -
    -
    - -
    - -
    -
    -
    - -{% endblock %} diff --git a/gestioncof/templates/login_error.html b/gestioncof/templates/login_error.html deleted file mode 100644 index fc736afc..00000000 --- a/gestioncof/templates/login_error.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} - {% if error_code == "has_clipper" %} -

    Votre identifiant est lié à un compte clipper

    -

    Veuillez vous connecter à l'aide de votre compte clipper

    - {% elif error_code == "no_password" %} -

    Votre compte n'a pas de mot de passe associé

    -

    Veuillez nous contacter pour que nous en définissions un et que nous vous le transmettions !

    - {% endif %} -{% endblock %} diff --git a/gestioncof/templates/login_switch.html b/gestioncof/templates/login_switch.html deleted file mode 100644 index d361493b..00000000 --- a/gestioncof/templates/login_switch.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
    -
    -
    -
    - -
    - -
    -
    -
    - -{% endblock %} diff --git a/gestioncof/templates/logout.html b/gestioncof/templates/logout.html deleted file mode 100644 index ebda0ac7..00000000 --- a/gestioncof/templates/logout.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

    - Déconnexion réussie. À bientôt ! -

    -{% endblock %} \ No newline at end of file diff --git a/gestioncof/templates/membres_clubs.html b/gestioncof/templates/membres_clubs.html deleted file mode 100644 index 8c932ed5..00000000 --- a/gestioncof/templates/membres_clubs.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "base_title.html" %} - - -{% block realcontent %} -

    {{ club }}

    - - -{% if club.respos.exists %} -

    Respo{{ club.respos.all|pluralize }}

    - -{% for member in club.respos.all %} - - - - - -{% endfor %} -
    {{ member }}{{ member.email }}
    -{% else %} -

    Pas de respo

    -{% endif %} - - -{% if club.membres.exists %} -

    Liste des membres

    - -{% for member in members_no_respo %} - - - - - -{% endfor %} -
    {{ member }}{{ member.email }}
    -{% else %} -Ce club ne comporte actuellement aucun membre. -{% endif %} - -{% endblock %} diff --git a/gestioncof/templates/registration.html b/gestioncof/templates/registration.html deleted file mode 100644 index 2ef997e1..00000000 --- a/gestioncof/templates/registration.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - -{% block page_size %}col-sm-8{% endblock %} - -{% block extra_head %} - -{% endblock %} - -{% block realcontent %} -

    Inscription d'un nouveau membre

    -

    Les mots contenant des caractères non alphanumériques seront ignorés

    - -
    -
    - -{% endblock %} diff --git a/gestioncof/templates/registration/password_change_done.html b/gestioncof/templates/registration/password_change_done.html deleted file mode 100644 index 9f2c4a60..00000000 --- a/gestioncof/templates/registration/password_change_done.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base_title.html" %} - -{% block homelink %} -{% endblock %} - -{% block realcontent %} -

    Mot de passe modifié avec succès !

    -

    Retour au menu principal

    -{% endblock %} diff --git a/gestioncof/templates/registration/password_change_form.html b/gestioncof/templates/registration/password_change_form.html deleted file mode 100644 index d9a3f66a..00000000 --- a/gestioncof/templates/registration/password_change_form.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "base_title.html" %} -{% load bootstrap %} - -{% block page_size %}col-sm-8{% endblock %} - -{% block realcontent %} -

    Changement de mot de passe

    -
    - {% csrf_token %} - {{ form | bootstrap }} - -
    -{% endblock %} diff --git a/gestioncof/templates/survey_status.html b/gestioncof/templates/survey_status.html deleted file mode 100644 index 0e630c6e..00000000 --- a/gestioncof/templates/survey_status.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends "base_title.html" %} -{% load utils %} - -{% block page_size %}col-sm-8{% endblock %} - -{% block realcontent %} -

    Sondage: {{ survey.title }}{% if user.is_staff %} Administration{% endif %}

    - {% if survey.details %} -

    {{ survey.details }}

    -
    - {% endif %} -

    Filtres

    - {% include "tristate_js.html" %} -
    - {% csrf_token %} - {{ form.as_p }} - -
    -
    -

    Résultats globaux

    -
    - {% for question in questions %} -

    {{ question.question }}

    -
      - {% for answer in question.answers.all %} -
    • {{ answer.answer }} : {{ answers_count|key:answer.id }}
    • - {% endfor %} -
    - {% endfor %} -
    -

    Réponses individuelles

    -
      - {% for user_answer in user_answers %}{% with user_answer.user as auser %} - {% if user_answer.answers.all %} -
    • - {% if auser.first_name and auser.last_name %}{{ auser.first_name }} {{ auser.last_name }} - {% else %}{{ auser.username }}{% endif %} : -
        - {% for answer in user_answer.answers.all %} -
      • {{ answer.survey_question.question }} : {{ answer.answer }}
      • - {% endfor %} -
      -
    • - {% endif %} - {% endwith %}{% endfor %} -
    -
    -

    Mailing list

    - -{% endblock %} diff --git a/gestioncof/templates/tristate_js.html b/gestioncof/templates/tristate_js.html deleted file mode 100644 index af906ebe..00000000 --- a/gestioncof/templates/tristate_js.html +++ /dev/null @@ -1,70 +0,0 @@ -{% load staticfiles %} - diff --git a/gestioncof/templates/utile_bda.html b/gestioncof/templates/utile_bda.html deleted file mode 100644 index 11e96f72..00000000 --- a/gestioncof/templates/utile_bda.html +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "base_title.html" %} - -{% block homelink %} -{% endblock %} - -{% block realcontent %} -

    Liens utiles du BdA

    -

    Listes mail

    - -{% endblock %} diff --git a/gestioncof/templatetags/utils.py b/gestioncof/templatetags/utils.py deleted file mode 100644 index 6b2122b6..00000000 --- a/gestioncof/templatetags/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -from django import template - -register = template.Library() - - -@register.filter -def key(d, key_name): - try: - value = d[key_name] - except KeyError: - from django.conf import settings - - value = settings.TEMPLATE_STRING_IF_INVALID - return value diff --git a/gestioncof/tests/__init__.py b/gestioncof/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gestioncof/tests/mixins.py b/gestioncof/tests/mixins.py deleted file mode 100644 index 5c8d767a..00000000 --- a/gestioncof/tests/mixins.py +++ /dev/null @@ -1,74 +0,0 @@ -from gestioncof.models import Event -from shared.tests.mixins import ( - CSVResponseMixin, - ViewTestCaseMixin as BaseViewTestCaseMixin, -) - -from .utils import create_member, create_staff, create_user - - -class MegaHelperMixin(CSVResponseMixin): - """ - Mixin pour aider aux tests du MEGA: création de l'event et de plusieurs - inscriptions, avec options et commentaires. - """ - - def setUp(self): - super().setUp() - - u1 = create_user("u1") - u1.first_name = "first" - u1.last_name = "last" - u1.email = "user@mail.net" - u1.save() - u1.profile.phone = "0123456789" - u1.profile.departement = "Dept" - u1.profile.comments = "profile.comments" - u1.profile.save() - - u2 = create_user("u2") - u2.profile.save() - - m = Event.objects.create(title="MEGA 2018") - - cf1 = m.commentfields.create(name="Commentaires") - cf2 = m.commentfields.create(name="Comment Field 2", fieldtype="char") - - option_type = m.options.create(name="Orga ? Conscrit ?") - choice_orga = option_type.choices.create(value="Orga") - choice_conscrit = option_type.choices.create(value="Conscrit") - - mr1 = m.eventregistration_set.create(user=u1) - mr1.options.add(choice_orga) - mr1.comments.create(commentfield=cf1, content="Comment 1") - mr1.comments.create(commentfield=cf2, content="Comment 2") - - mr2 = m.eventregistration_set.create(user=u2) - mr2.options.add(choice_conscrit) - - self.u1 = u1 - self.u2 = u2 - self.m = m - self.choice_orga = choice_orga - self.choice_conscrit = choice_conscrit - - -class ViewTestCaseMixin(BaseViewTestCaseMixin): - """ - TestCase extension to ease testing of cof views. - - Most information can be found in the base parent class doc. - This class performs some changes to users management, detailed below. - - During setup, three users are created: - - 'user': a basic user without any permission, - - 'member': (profile.is_cof is True), - - 'staff': (profile.is_cof is True) && (profile.is_buro is True). - """ - - def get_users_base(self): - return { - "user": create_user("user"), - "member": create_member("member"), - "staff": create_staff("staff"), - } diff --git a/gestioncof/tests/test_legacy.py b/gestioncof/tests/test_legacy.py deleted file mode 100644 index cc7ddbf7..00000000 --- a/gestioncof/tests/test_legacy.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -This file demonstrates writing tests using the unittest module. These will pass -when you run "manage.py test". - -Replace this with more appropriate tests for your application. -""" - -from django.test import TestCase - -from gestioncof.models import CofProfile, User - - -class SimpleTest(TestCase): - def test_delete_user(self): - u = User(username="foo", first_name="foo", last_name="bar") - - # to each user there's a cofprofile associated - u.save() - self.assertTrue(CofProfile.objects.filter(user__username="foo").exists()) - - # there's no point in having a cofprofile without a user associated. - u.delete() - self.assertFalse(CofProfile.objects.filter(user__username="foo").exists()) - - # there's no point in having a user without a cofprofile associated. - u.save() - CofProfile.objects.get(user__username="foo").delete() - self.assertFalse(User.objects.filter(username="foo").exists()) diff --git a/gestioncof/tests/test_views.py b/gestioncof/tests/test_views.py deleted file mode 100644 index 80d33c14..00000000 --- a/gestioncof/tests/test_views.py +++ /dev/null @@ -1,1209 +0,0 @@ -import uuid -from datetime import date, timedelta - -from django.contrib import messages -from django.contrib.auth import get_user_model -from django.contrib.messages.api import get_messages -from django.contrib.messages.storage.base import Message -from django.core import mail -from django.core.mail import EmailMessage -from django.template import loader -from django.test import Client, TestCase, override_settings -from django.urls import reverse - -from bda.models import Salle, Tirage -from gestioncof.models import CalendarSubscription, Club, Event, Survey, SurveyAnswer -from gestioncof.tests.mixins import MegaHelperMixin, ViewTestCaseMixin -from shared.autocomplete import Clipper, LDAPSearch -from shared.tests.mixins import CSVResponseMixin, ICalMixin, MockLDAPMixin - -from .utils import create_member, create_root, create_user - -User = get_user_model() - - -class RegistrationViewTests(ViewTestCaseMixin, TestCase): - url_name = "registration" - url_expected = "/gestion/registration/" - - http_methods = ["GET", "POST"] - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def test_get(self): - r = self.client.get(self.url) - self.assertEqual(r.status_code, 200) - - @property - def _minimal_data(self): - return { - "first_name": "", - "last_name": "", - "email": "", - # 'is_cof': '1', - "login_clipper": "", - "phone": "", - "occupation": "1A", - "departement": "", - "type_cotiz": "normalien", - "comments": "", - # 'user_exists': '1', - "events-TOTAL_FORMS": "0", - "events-INITIAL_FORMS": "0", - "events-MIN_NUM_FORMS": "0", - "events-MAX_NUM_FORMS": "1000", - } - - def test_post_new(self): - r = self.client.post( - self.url, - dict( - self._minimal_data, - **{ - "username": "username", - "first_name": "first", - "last_name": "last", - "email": "username@mail.net", - "is_cof": "1", - }, - ), - ) - - self.assertEqual(r.status_code, 200) - u = User.objects.get(username="username") - expected_message = Message( - messages.SUCCESS, - ( - "L'inscription de first last (username@mail.net) a été " - "enregistrée avec succès.\n" - "Il est désormais membre du COF n°{} !".format(u.pk) - ), - ) - self.assertIn(expected_message, get_messages(r.wsgi_request)) - - self.assertEqual(u.first_name, "first") - self.assertEqual(u.last_name, "last") - self.assertEqual(u.email, "username@mail.net") - - def test_post_edit(self): - u = self.users["user"] - - r = self.client.post( - self.url, - dict( - self._minimal_data, - **{ - "username": "user", - "first_name": "first", - "last_name": "last", - "email": "user@mail.net", - "is_cof": "1", - "user_exists": "1", - }, - ), - ) - - self.assertEqual(r.status_code, 200) - u.refresh_from_db() - expected_message = Message( - messages.SUCCESS, - ( - "L'inscription de first last (user@mail.net) a été " - "enregistrée avec succès.\n" - "Il est désormais membre du COF n°{} !".format(u.pk) - ), - ) - self.assertIn(expected_message, get_messages(r.wsgi_request)) - - self.assertEqual(u.first_name, "first") - self.assertEqual(u.last_name, "last") - self.assertEqual(u.email, "user@mail.net") - - def _test_mail_welcome(self, was_cof, is_cof, expect_mail): - u = self.users["member"] if was_cof else self.users["user"] - - data = dict( - self._minimal_data, - **{"username": u.username, "email": "user@mail.net", "user_exists": "1"}, - ) - if is_cof: - data["is_cof"] = "1" - self.client.post(self.url, data) - - u.refresh_from_db() - - def _is_sent(): - welcome_msg = EmailMessage( - subject="Bienvenue au COF", - body=loader.render_to_string( - "gestioncof/mails/welcome.txt", context={"member": "u"} - ), - ) - for m in mail.outbox: - if m.subject == welcome_msg.subject: - return True - return False - - self.assertEqual(_is_sent(), expect_mail) - - def test_mail_welcome_0(self): - self._test_mail_welcome(was_cof=False, is_cof=False, expect_mail=False) - - def test_mail_welcome_1(self): - self._test_mail_welcome(was_cof=False, is_cof=True, expect_mail=True) - - def test_mail_welcome_2(self): - self._test_mail_welcome(was_cof=True, is_cof=False, expect_mail=False) - - def test_mail_welcome_3(self): - self._test_mail_welcome(was_cof=True, is_cof=True, expect_mail=False) - - def test_events(self): - e = Event.objects.create() - - cf1 = e.commentfields.create(name="Comment Field 1") - cf2 = e.commentfields.create(name="Comment Field 2", fieldtype="char") - - o1 = e.options.create(name="Option 1") - o2 = e.options.create(name="Option 2", multi_choices=True) - - oc1 = o1.choices.create(value="O1 - Choice 1") - o1.choices.create(value="O1 - Choice 2") - oc3 = o2.choices.create(value="O2 - Choice 1") - o2.choices.create(value="O2 - Choice 2") - - self.client.post( - self.url, - dict( - self._minimal_data, - **{ - "username": "user", - "user_exists": "1", - "events-TOTAL_FORMS": "1", - "events-INITIAL_FORMS": "0", - "events-MIN_NUM_FORMS": "0", - "events-MAX_NUM_FORMS": "1000", - "events-0-status": "paid", - "events-0-option_{}".format(o1.pk): [str(oc1.pk)], - "events-0-option_{}".format(o2.pk): [str(oc3.pk)], - "events-0-comment_{}".format(cf1.pk): "comment 1", - "events-0-comment_{}".format(cf2.pk): "", - }, - ), - ) - - er = e.eventregistration_set.get(user=self.users["user"]) - self.assertQuerysetEqual(er.options.all(), map(repr, [oc1, oc3]), ordered=False) - self.assertCountEqual( - er.comments.values_list("content", flat=True), ["comment 1"] - ) - - -class RegistrationFormViewTests(ViewTestCaseMixin, TestCase): - urls_conf = [ - {"name": "empty-registration", "expected": "/gestion/registration/empty"}, - { - "name": "user-registration", - "kwargs": {"username": "user"}, - "expected": "/gestion/registration/user/user", - }, - { - "name": "clipper-registration", - "kwargs": {"login_clipper": "uid", "fullname": "First Last1 Last2"}, - "expected": "/gestion/registration/clipper/uid/First%20Last1%20Last2", - }, - ] - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def test_empty(self): - r = self.client.get(self.reversed_urls[0]) - - self.assertIn("user_form", r.context) - self.assertIn("profile_form", r.context) - self.assertIn("event_formset", r.context) - self.assertIn("clubs_form", r.context) - - def test_username(self): - u = self.users["user"] - u.first_name = "first" - u.last_name = "last" - u.save() - - r = self.client.get(self.reversed_urls[1]) - - self.assertIn("user_form", r.context) - self.assertIn("profile_form", r.context) - self.assertIn("event_formset", r.context) - self.assertIn("clubs_form", r.context) - user_form = r.context["user_form"] - self.assertEqual(user_form["username"].initial, "user") - self.assertEqual(user_form["first_name"].initial, "first") - self.assertEqual(user_form["last_name"].initial, "last") - - def test_clipper(self): - r = self.client.get(self.reversed_urls[2]) - - self.assertIn("user_form", r.context) - self.assertIn("profile_form", r.context) - self.assertIn("event_formset", r.context) - self.assertIn("clubs_form", r.context) - user_form = r.context["user_form"] - profile_form = r.context["profile_form"] - self.assertEqual(user_form["first_name"].initial, "First") - self.assertEqual(user_form["last_name"].initial, "Last1 Last2") - self.assertEqual(user_form["email"].initial, "uid@clipper.ens.fr") - self.assertEqual(profile_form["login_clipper"].initial, "uid") - - -@override_settings(LDAP_SERVER_URL="ldap_url") -class RegistrationAutocompleteViewTests(MockLDAPMixin, ViewTestCaseMixin, TestCase): - url_name = "cof.registration.autocomplete" - url_expected = "/gestion/registration/autocomplete" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def setUp(self): - super().setUp() - - self.u1 = create_user("uu_u1", attrs={"first_name": "abc", "last_name": "xyz"}) - self.u2 = create_user("uu_u2", attrs={"first_name": "wyz", "last_name": "abd"}) - self.m1 = create_member( - "uu_m1", attrs={"first_name": "ebd", "last_name": "wyv"} - ) - - self.mockLDAP([]) - - def _test(self, query, expected_others, expected_members, expected_clippers): - r = self.client.get(self.url, {"q": query}) - - self.assertEqual(r.status_code, 200) - - def extract(section): - return [r.verbose_name for r in section.entries] - - others = [] - members = [] - clippers = [] - for section in r.context["results"]: - if section.name == "others": - others = extract(section) - elif section.name == "members": - members = extract(section) - elif section.name == "clippers": - clippers = extract(section) - else: - raise ValueError("Unexpected section name: {}".format(section.name)) - - self.assertQuerysetEqual( - others, map(str, expected_others), ordered=False, transform=str - ) - self.assertQuerysetEqual( - members, map(str, expected_members), ordered=False, transform=str - ) - self.assertSetEqual( - set(clippers), set(map(LDAPSearch().result_verbose_name, expected_clippers)) - ) - - def test_username(self): - self._test("uu", [self.u1, self.u2], [self.m1], []) - - def test_firstname(self): - self._test("ab", [self.u1, self.u2], [], []) - - def test_lastname(self): - self._test("wy", [self.u2], [self.m1], []) - - def test_multi_query(self): - self._test("wy bd", [self.u2], [self.m1], []) - - def test_clipper(self): - mock_ldap = self.mockLDAP([("uid", "first last", "mail")]) - - self._test("aa bb", [], [], [Clipper("uid", "first last", "mail")]) - - mock_ldap.ldap_obj.search_s.assert_called_once_with( - "dc=spi,dc=ens,dc=fr", - mock_ldap.SCOPE_SUBTREE, - "(&(|(cn=*aa*)(uid=*aa*))(|(cn=*bb*)(uid=*bb*)))", - ["cn", "uid", "mail"], - ) - - def test_clipper_escaped(self): - mock_ldap = self.mockLDAP([]) - - self._test("; & | (", [], [], []) - - mock_ldap.ldap_obj.search_s.assert_not_called() - - def test_clipper_no_duplicate(self): - self.mockLDAP([("uid", "abc", "mail")]) - - self._test("abc", [self.u1], [], [Clipper("uid", "abc", "mail")]) - - self.u1.username = "uid" - self.u1.save() - - self._test("abc", [self.u1], [], []) - - -class HomeViewTests(ViewTestCaseMixin, TestCase): - url_name = "home" - url_expected = "/gestion/" - - auth_user = "user" - auth_forbidden = [None] - - def test(self): - r = self.client.get(self.url) - self.assertEqual(r.status_code, 200) - - -class ProfileViewTests(ViewTestCaseMixin, TestCase): - url_name = "profile" - url_expected = "/gestion/profile" - - http_methods = ["GET", "POST"] - - auth_user = "member" - auth_forbidden = [None] - - def test_get(self): - r = self.client.get(self.url) - self.assertEqual(r.status_code, 200) - - def test_post(self): - u = self.users["member"] - - r = self.client.post( - self.url, - { - "u-first_name": "First", - "u-last_name": "Last", - "p-phone": "", - # 'mailing_cof': '1', - # 'mailing_bda': '1', - # 'mailing_bda_revente': '1', - }, - ) - - self.assertEqual(r.status_code, 200) - expected_message = Message( - messages.SUCCESS, ("Votre profil a été mis à jour avec succès !") - ) - self.assertIn(expected_message, get_messages(r.wsgi_request)) - u.refresh_from_db() - self.assertEqual(u.first_name, "First") - self.assertEqual(u.last_name, "Last") - self.assertFalse(u.profile.mailing_cof) - self.assertFalse(u.profile.mailing_bda) - self.assertFalse(u.profile.mailing_bda_revente) - - -class UtilsViewTests(ViewTestCaseMixin, TestCase): - url_name = "utile_cof" - url_expected = "/gestion/utile_cof" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def test(self): - r = self.client.get(self.url) - self.assertEqual(r.status_code, 200) - - -class MailingListDiffCof(ViewTestCaseMixin, TestCase): - url_name = "ml_diffcof" - url_expected = "/gestion/utile_cof/diff_cof" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def setUp(self): - super().setUp() - - self.u1 = create_member("u1", attrs={"mailing_cof": True}) - self.u2 = create_member("u2", attrs={"mailing_cof": False}) - self.u3 = create_user("u3", attrs={"mailing_cof": True}) - - def test(self): - r = self.client.get(self.url) - - self.assertEqual(r.status_code, 200) - self.assertEqual(r.context["personnes"].get(), self.u1.profile) - - -class ConfigUpdateViewTests(ViewTestCaseMixin, TestCase): - url_name = "config.edit" - url_expected = "/gestion/config" - - http_methods = ["GET", "POST"] - - auth_user = "root" - auth_forbidden = [None, "user", "member", "staff"] - - def get_users_extra(self): - return {"root": create_root("root")} - - def test_get(self): - r = self.client.get(self.url) - self.assertEqual(r.status_code, 200) - - def test_post(self): - r = self.client.post(self.url, {"gestion_banner": "Announcement !"}) - - self.assertRedirects(r, reverse("home")) - - -class UserAutocompleteViewTests(ViewTestCaseMixin, TestCase): - url_name = "cof-user-autocomplete" - url_expected = "/gestion/user/autocomplete" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def test(self): - r = self.client.get(self.url, {"q": "user"}) - - self.assertEqual(r.status_code, 200) - - -class ExportMembersViewTests(CSVResponseMixin, ViewTestCaseMixin, TestCase): - url_name = "export.members" - url_expected = "/gestion/export/members" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def test(self): - u1, u2 = self.users["member"], self.users["staff"] - u1.first_name = "first" - u1.last_name = "last" - u1.email = "user@mail.net" - u1.save() - u1.profile.date_adhesion = date(2023, 5, 22) - u1.profile.phone = "0123456789" - u1.profile.departement = "Dept" - u1.profile.save() - - r = self.client.get(self.url) - - self.assertEqual(r.status_code, 200) - - self.assertCSVEqual( - r, - [ - [ - str(u1.pk), - "member", - "first", - "last", - "user@mail.net", - "0123456789", - "1A", - "Dept", - "normalien", - "2023-05-22", - ], - [str(u2.pk), "staff", "", "", "", "", "1A", "", "normalien", "None"], - ], - ) - - -class ExportMegaViewTests(MegaHelperMixin, ViewTestCaseMixin, TestCase): - url_name = "export.mega.all" - url_expected = "/gestion/export/mega/all" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def test(self): - r = self.client.get(self.url) - - self.assertEqual(r.status_code, 200) - self.assertCSVEqual( - r, - [ - [ - "u1", - "first", - "last", - "user@mail.net", - "0123456789", - str(self.u1.pk), - "profile.comments", - "Comment 1---Comment 2", - ], - ["u2", "", "", "", "", str(self.u2.pk), "", ""], - ], - ) - - -class ExportMegaOrgasViewTests(MegaHelperMixin, ViewTestCaseMixin, TestCase): - url_name = "export.mega.orgas" - url_expected = "/gestion/export/mega/orgas" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def test(self): - r = self.client.get(self.url) - - self.assertEqual(r.status_code, 200) - self.assertCSVEqual( - r, - [ - [ - "u1", - "first", - "last", - "user@mail.net", - "0123456789", - str(self.u1.pk), - "profile.comments", - "Comment 1---Comment 2", - ] - ], - ) - - -class ExportMegaParticipantsViewTests(MegaHelperMixin, ViewTestCaseMixin, TestCase): - url_name = "export.mega.participants" - url_expected = "/gestion/export/mega/participants" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def test(self): - r = self.client.get(self.url) - - self.assertEqual(r.status_code, 200) - self.assertCSVEqual( - r, - [["u2", "", "", "", "", str(self.u2.pk), "", ""]], - ) - - -class ExportMegaRemarksViewTests(MegaHelperMixin, ViewTestCaseMixin, TestCase): - url_name = "export.mega.remarks" - url_expected = "/gestion/export/mega/avecremarques" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def test(self): - r = self.client.get(self.url) - - self.assertEqual(r.status_code, 200) - self.assertCSVEqual( - r, - [ - [ - "u1", - "first", - "last", - "user@mail.net", - "0123456789", - str(self.u1.pk), - "profile.comments", - "Comment 1", - ] - ], - ) - - -class ClubListViewTests(ViewTestCaseMixin, TestCase): - url_name = "liste-clubs" - url_expected = "/gestion/clubs/liste" - - auth_user = "member" - auth_forbidden = [None, "user"] - - def setUp(self): - super().setUp() - - self.c1 = Club.objects.create(name="Club1") - self.c2 = Club.objects.create(name="Club2") - - m = self.users["member"] - self.c1.membres.add(m) - self.c1.respos.add(m) - - def test_as_member(self): - r = self.client.get(self.url) - - self.assertEqual(r.status_code, 200) - self.assertEqual(r.context["owned_clubs"].get(), self.c1) - self.assertEqual(r.context["other_clubs"].get(), self.c2) - - def test_as_staff(self): - u = self.users["staff"] - c = Client() - c.force_login(u, backend="django.contrib.auth.backends.ModelBackend") - - r = c.get(self.url) - - self.assertEqual(r.status_code, 200) - self.assertQuerysetEqual( - r.context["owned_clubs"], map(repr, [self.c1, self.c2]), ordered=False - ) - - -class ClubMembersViewTests(ViewTestCaseMixin, TestCase): - url_name = "membres-club" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - @property - def url_kwargs(self): - return {"name": self.c.name} - - @property - def url_expected(self): - return "/gestion/clubs/membres/{}".format(self.c.name) - - def setUp(self): - super().setUp() - - self.u1 = create_user("u1") - self.u2 = create_user("u2") - - self.c = Club.objects.create(name="Club") - self.c.membres.add(self.u1, self.u2) - self.c.respos.add(self.u1) - - def test_as_staff(self): - r = self.client.get(self.url) - - self.assertEqual(r.status_code, 200) - self.assertEqual(r.context["members_no_respo"].get(), self.u2) - - def test_as_respo(self): - u = self.users["user"] - self.c.respos.add(u) - - c = Client() - c.force_login(u, backend="django.contrib.auth.backends.ModelBackend") - r = c.get(self.url) - - self.assertEqual(r.status_code, 200) - - -class ClubChangeRespoViewTests(ViewTestCaseMixin, TestCase): - url_name = "change-respo" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - @property - def url_kwargs(self): - return {"club_name": self.c.name, "user_id": self.users["user"].pk} - - @property - def url_expected(self): - return "/gestion/clubs/change_respo/{}/{}".format( - self.c.name, self.users["user"].pk - ) - - def setUp(self): - super().setUp() - - self.c = Club.objects.create(name="Club") - - def test(self): - u = self.users["user"] - expected_redirect = reverse("membres-club", kwargs={"name": self.c.name}) - self.c.membres.add(u) - - r = self.client.get(self.url) - self.assertRedirects(r, expected_redirect) - self.assertIn(u, self.c.respos.all()) - - self.client.get(self.url) - self.assertNotIn(u, self.c.respos.all()) - - -class CalendarViewTests(ViewTestCaseMixin, TestCase): - url_name = "calendar" - url_expected = "/gestion/calendar/subscription" - - auth_user = "member" - auth_forbidden = [None, "user"] - - post_expected_message = Message( - messages.SUCCESS, "Calendrier mis à jour avec succès." - ) - - def test_get(self): - r = self.client.get(self.url) - self.assertEqual(r.status_code, 200) - - def test_post_new(self): - r = self.client.post( - self.url, - { - "subscribe_to_events": True, - "subscribe_to_my_shows": True, - "other_shows": [], - }, - ) - - self.assertEqual(r.status_code, 200) - self.assertIn(self.post_expected_message, get_messages(r.wsgi_request)) - cs = self.users["member"].calendarsubscription - self.assertTrue(cs.subscribe_to_events) - self.assertTrue(cs.subscribe_to_my_shows) - - def test_post_edit(self): - u = self.users["member"] - token = uuid.uuid4() - cs = CalendarSubscription.objects.create(token=token, user=u) - - r = self.client.post(self.url, {"other_shows": []}) - - self.assertEqual(r.status_code, 200) - self.assertIn(self.post_expected_message, get_messages(r.wsgi_request)) - cs.refresh_from_db() - self.assertEqual(cs.token, token) - self.assertFalse(cs.subscribe_to_events) - self.assertFalse(cs.subscribe_to_my_shows) - - def test_post_other_shows(self): - t = Tirage.objects.create(ouverture=self.now, fermeture=self.now, active=True) - location = Salle.objects.create() - s = t.spectacle_set.create( - date=self.now, price=3.5, slots=20, location=location, listing=True - ) - - r = self.client.post(self.url, {"other_shows": [str(s.pk)]}) - - self.assertEqual(r.status_code, 200) - - -class CalendarICSViewTests(ICalMixin, ViewTestCaseMixin, TestCase): - url_name = "calendar.ics" - - auth_user = None - auth_forbidden = [] - - @property - def url_kwargs(self): - return {"token": self.token} - - @property - def url_expected(self): - return "/gestion/calendar/{}/calendar.ics".format(self.token) - - def setUp(self): - super().setUp() - - self.token = uuid.uuid4() - - self.t = Tirage.objects.create( - ouverture=self.now, fermeture=self.now, active=True - ) - location = Salle.objects.create(name="Location") - self.s1 = self.t.spectacle_set.create( - price=1, - slots=10, - location=location, - listing=True, - title="Spectacle 1", - date=self.now + timedelta(days=1), - ) - self.s2 = self.t.spectacle_set.create( - price=2, - slots=20, - location=location, - listing=True, - title="Spectacle 2", - date=self.now + timedelta(days=2), - ) - self.s3 = self.t.spectacle_set.create( - price=3, - slots=30, - location=location, - listing=True, - title="Spectacle 3", - date=self.now + timedelta(days=3), - ) - - def test(self): - u = self.users["user"] - p = u.participant_set.create(tirage=self.t) - p.attribution_set.create(spectacle=self.s1) - - self.cs = CalendarSubscription.objects.create( - user=u, - token=self.token, - subscribe_to_my_shows=True, - subscribe_to_events=True, - ) - self.cs.other_shows.add(self.s2) - - r = self.client.get(self.url) - - def get_dt_from_ical(v): - return v.dt - - self.assertCalEqual( - r.content.decode("utf-8"), - [ - { - "summary": "Spectacle 1", - "dtstart": ( - get_dt_from_ical, - ((self.now + timedelta(days=1)).replace(microsecond=0)), - ), - "dtend": ( - get_dt_from_ical, - ( - (self.now + timedelta(days=1, hours=2)).replace( - microsecond=0 - ) - ), - ), - "location": "Location", - "uid": "show-{}-{}@example.com".format(self.s1.pk, self.t.pk), - }, - { - "summary": "Spectacle 2", - "dtstart": ( - get_dt_from_ical, - ((self.now + timedelta(days=2)).replace(microsecond=0)), - ), - "dtend": ( - get_dt_from_ical, - ( - (self.now + timedelta(days=2, hours=2)).replace( - microsecond=0 - ) - ), - ), - "location": "Location", - "uid": "show-{}-{}@example.com".format(self.s2.pk, self.t.pk), - }, - ], - ) - - -class EventViewTests(ViewTestCaseMixin, TestCase): - url_name = "event.details" - http_methods = ["GET", "POST"] - - auth_user = "user" - auth_forbidden = [None] - - post_expected_message = Message( - messages.SUCCESS, - ( - "Votre inscription a bien été enregistrée ! Vous pouvez cependant la " - "modifier jusqu'à la fin des inscriptions." - ), - ) - - @property - def url_kwargs(self): - return {"event_id": self.e.pk} - - @property - def url_expected(self): - return "/gestion/event/{}".format(self.e.pk) - - def setUp(self): - super().setUp() - - self.e = Event.objects.create() - - self.ecf1 = self.e.commentfields.create(name="Comment Field 1") - self.ecf2 = self.e.commentfields.create( - name="Comment Field 2", fieldtype="char" - ) - - self.o1 = self.e.options.create(name="Option 1") - self.o2 = self.e.options.create(name="Option 2", multi_choices=True) - - self.oc1 = self.o1.choices.create(value="O1 - Choice 1") - self.oc2 = self.o1.choices.create(value="O1 - Choice 2") - self.oc3 = self.o2.choices.create(value="O2 - Choice 1") - self.oc4 = self.o2.choices.create(value="O2 - Choice 2") - - def test_get(self): - r = self.client.get(self.url) - self.assertEqual(r.status_code, 200) - - def test_post_new(self): - r = self.client.post( - self.url, - { - "option_{}".format(self.o1.pk): [str(self.oc1.pk)], - "option_{}".format(self.o2.pk): [str(self.oc3.pk), str(self.oc4.pk)], - }, - ) - - self.assertEqual(r.status_code, 200) - self.assertIn(self.post_expected_message, get_messages(r.wsgi_request)) - - er = self.e.eventregistration_set.get(user=self.users["user"]) - self.assertQuerysetEqual( - er.options.all(), map(repr, [self.oc1, self.oc3, self.oc4]), ordered=False - ) - # TODO: Make the view care about comments. - # self.assertQuerysetEqual( - # er.comments.all(), map(repr, []), - # ordered=False, - # ) - - def test_post_edit(self): - er = self.e.eventregistration_set.create(user=self.users["user"]) - er.options.add(self.oc1, self.oc3, self.oc4) - er.comments.create(commentfield=self.ecf1, content="Comment 1") - - r = self.client.post( - self.url, - { - "option_{}".format(self.o1.pk): [], - "option_{}".format(self.o2.pk): [str(self.oc3.pk)], - }, - ) - - self.assertEqual(r.status_code, 200) - self.assertIn(self.post_expected_message, get_messages(r.wsgi_request)) - - er.refresh_from_db() - self.assertQuerysetEqual(er.options.all(), map(repr, [self.oc3]), ordered=False) - # TODO: Make the view care about comments. - # self.assertQuerysetEqual( - # er.comments.all(), map(repr, []), - # ordered=False, - # ) - - -class EventStatusViewTests(ViewTestCaseMixin, TestCase): - url_name = "event.details.status" - - http_methods = ["GET", "POST"] - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - @property - def url_kwargs(self): - return {"event_id": self.e.pk} - - @property - def url_expected(self): - return "/gestion/event/{}/status".format(self.e.pk) - - def setUp(self): - super().setUp() - - self.e = Event.objects.create() - - self.cf1 = self.e.commentfields.create(name="Comment Field 1") - self.cf2 = self.e.commentfields.create(name="Comment Field 2", fieldtype="char") - - self.o1 = self.e.options.create(name="Option 1") - self.o2 = self.e.options.create(name="Option 2", multi_choices=True) - - self.oc1 = self.o1.choices.create(value="O1 - Choice 1") - self.oc2 = self.o1.choices.create(value="O1 - Choice 2") - self.oc3 = self.o2.choices.create(value="O2 - Choice 1") - self.oc4 = self.o2.choices.create(value="O2 - Choice 2") - - self.er1 = self.e.eventregistration_set.create(user=self.users["user"]) - self.er1.options.add(self.oc1) - self.er2 = self.e.eventregistration_set.create(user=self.users["member"]) - - def _get_oc_filter_name(self, oc): - return "option_{}_choice_{}".format(oc.event_option.pk, oc.pk) - - def _test_filters(self, filters, expected): - r = self.client.post( - self.url, {self._get_oc_filter_name(oc): v for oc, v in filters} - ) - - self.assertEqual(r.status_code, 200) - self.assertQuerysetEqual( - r.context["user_choices"], map(repr, expected), ordered=False - ) - - def test_filter_none(self): - self._test_filters([(self.oc1, "none")], [self.er1, self.er2]) - - def test_filter_yes(self): - self._test_filters([(self.oc1, "yes")], [self.er1]) - - def test_filter_no(self): - self._test_filters([(self.oc1, "no")], [self.er2]) - - -class SurveyViewTests(ViewTestCaseMixin, TestCase): - url_name = "survey.details" - http_methods = ["GET", "POST"] - - auth_user = "user" - auth_forbidden = [None] - - post_expected_message = Message( - messages.SUCCESS, - ( - "Votre réponse a bien été enregistrée ! Vous pouvez cependant la " - "modifier jusqu'à la fin du sondage." - ), - ) - - @property - def url_kwargs(self): - return {"survey_id": self.s.pk} - - @property - def url_expected(self): - return "/gestion/survey/{}".format(self.s.pk) - - def setUp(self): - super().setUp() - - self.s = Survey.objects.create(title="Title") - - self.q1 = self.s.questions.create(question="Question 1 ?") - self.q2 = self.s.questions.create(question="Question 2 ?", multi_answers=True) - - self.qa1 = self.q1.answers.create(answer="Q1 - Answer 1") - self.qa2 = self.q1.answers.create(answer="Q1 - Answer 2") - self.qa3 = self.q2.answers.create(answer="Q2 - Answer 1") - self.qa4 = self.q2.answers.create(answer="Q2 - Answer 2") - - def test_get(self): - r = self.client.get(self.url) - self.assertEqual(r.status_code, 200) - - def test_post_new(self): - r = self.client.post( - self.url, - { - "question_{}".format(self.q1.pk): [str(self.qa1.pk)], - "question_{}".format(self.q2.pk): [str(self.qa3.pk), str(self.qa4.pk)], - }, - ) - - self.assertEqual(r.status_code, 200) - self.assertIn(self.post_expected_message, get_messages(r.wsgi_request)) - - a = self.s.surveyanswer_set.get(user=self.users["user"]) - self.assertQuerysetEqual( - a.answers.all(), map(repr, [self.qa1, self.qa3, self.qa4]), ordered=False - ) - - def test_post_edit(self): - a = self.s.surveyanswer_set.create(user=self.users["user"]) - a.answers.add(self.qa1, self.qa1, self.qa4) - - r = self.client.post( - self.url, - { - "question_{}".format(self.q1.pk): [], - "question_{}".format(self.q2.pk): [str(self.qa3.pk)], - }, - ) - - self.assertEqual(r.status_code, 200) - self.assertIn(self.post_expected_message, get_messages(r.wsgi_request)) - - a.refresh_from_db() - self.assertQuerysetEqual(a.answers.all(), map(repr, [self.qa3]), ordered=False) - - def test_post_delete(self): - a = self.s.surveyanswer_set.create(user=self.users["user"]) - a.answers.add(self.qa1, self.qa4) - - r = self.client.post(self.url, {"delete": "1"}) - - self.assertEqual(r.status_code, 200) - expected_message = Message( - messages.SUCCESS, "Votre réponse a bien été supprimée" - ) - self.assertIn(expected_message, get_messages(r.wsgi_request)) - - with self.assertRaises(SurveyAnswer.DoesNotExist): - a.refresh_from_db() - - def test_forbidden_closed(self): - self.s.survey_open = False - self.s.save() - - r = self.client.get(self.url) - - self.assertNotEqual(r.status_code, 200) - - def test_forbidden_old(self): - self.s.old = True - self.s.save() - - r = self.client.get(self.url) - - self.assertNotEqual(r.status_code, 200) - - -class SurveyStatusViewTests(ViewTestCaseMixin, TestCase): - url_name = "survey.details.status" - - http_methods = ["GET", "POST"] - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - @property - def url_kwargs(self): - return {"survey_id": self.s.pk} - - @property - def url_expected(self): - return "/gestion/survey/{}/status".format(self.s.pk) - - def setUp(self): - super().setUp() - - self.s = Survey.objects.create(title="Title") - - self.q1 = self.s.questions.create(question="Question 1 ?") - self.q2 = self.s.questions.create(question="Question 2 ?", multi_answers=True) - - self.qa1 = self.q1.answers.create(answer="Q1 - Answer 1") - self.qa2 = self.q1.answers.create(answer="Q1 - Answer 2") - self.qa3 = self.q2.answers.create(answer="Q2 - Answer 1") - self.qa4 = self.q2.answers.create(answer="Q2 - Answer 2") - - self.a1 = self.s.surveyanswer_set.create(user=self.users["user"]) - self.a1.answers.add(self.qa1) - self.a2 = self.s.surveyanswer_set.create(user=self.users["member"]) - - def test_get(self): - r = self.client.get(self.url) - self.assertEqual(r.status_code, 200) - - def _get_qa_filter_name(self, qa): - return "question_{}_answer_{}".format(qa.survey_question.pk, qa.pk) - - def _test_filters(self, filters, expected): - r = self.client.post( - self.url, {self._get_qa_filter_name(qa): v for qa, v in filters} - ) - - self.assertEqual(r.status_code, 200) - self.assertQuerysetEqual( - r.context["user_answers"], map(repr, expected), ordered=False - ) - - def test_filter_none(self): - self._test_filters([(self.qa1, "none")], [self.a1, self.a2]) - - def test_filter_yes(self): - self._test_filters([(self.qa1, "yes")], [self.a1]) - - def test_filter_no(self): - self._test_filters([(self.qa1, "no")], [self.a2]) diff --git a/gestioncof/tests/utils.py b/gestioncof/tests/utils.py deleted file mode 100644 index 7325e350..00000000 --- a/gestioncof/tests/utils.py +++ /dev/null @@ -1,68 +0,0 @@ -from django.contrib.auth import get_user_model - -User = get_user_model() - - -def _create_user(username, is_cof=False, is_staff=False, attrs=None): - if attrs is None: - attrs = {} - - password = attrs.pop("password", username) - - user_keys = ["first_name", "last_name", "email", "is_staff", "is_superuser"] - user_attrs = {k: v for k, v in attrs.items() if k in user_keys} - - profile_keys = [ - "is_cof", - "login_clipper", - "phone", - "occupation", - "departement", - "type_cotiz", - "mailing_cof", - "mailing_bda", - "mailing_bda_revente", - "comments", - "is_buro", - "petit_cours_accept", - "petit_cours_remarques", - ] - profile_attrs = {k: v for k, v in attrs.items() if k in profile_keys} - - if is_cof: - profile_attrs["is_cof"] = True - - if is_staff: - # At the moment, admin is accessible by COF staff. - user_attrs["is_staff"] = True - profile_attrs["is_buro"] = True - - user = User(username=username, **user_attrs) - user.set_password(password) - user.save() - - for k, v in profile_attrs.items(): - setattr(user.profile, k, v) - user.profile.save() - - return user - - -def create_user(username, attrs=None): - return _create_user(username, attrs=attrs) - - -def create_member(username, attrs=None): - return _create_user(username, is_cof=True, attrs=attrs) - - -def create_staff(username, attrs=None): - return _create_user(username, is_cof=True, is_staff=True, attrs=attrs) - - -def create_root(username, attrs=None): - if attrs is None: - attrs = {} - attrs.setdefault("is_staff", True) - attrs.setdefault("is_superuser", True) - return _create_user(username, attrs=attrs) diff --git a/gestioncof/urls.py b/gestioncof/urls.py deleted file mode 100644 index d0ba75c7..00000000 --- a/gestioncof/urls.py +++ /dev/null @@ -1,165 +0,0 @@ -from django.contrib.auth import views as django_auth_views -from django.urls import include, path -from django.views.generic.base import TemplateView -from django_cas_ng import views as django_cas_views - -from gestioncof import csv_views, views - -export_patterns = [ - path("members", views.export_members, name="export.members"), - path( - "mega/avecremarques", - views.export_mega_remarksonly, - name="export.mega.remarks", - ), - path( - "mega/participants", - views.export_mega_participants, - name="export.mega.participants", - ), - path("mega/orgas", views.export_mega_orgas, name="export.mega.orgas"), - path("mega/all", views.export_mega, name="export.mega.all"), -] - -surveys_patterns = [ - path("/status", views.survey_status, name="survey.details.status"), - path("", views.survey, name="survey.details"), -] - -events_patterns = [ - path("", views.event, name="event.details"), - path("/status", views.event_status, name="event.details.status"), -] - -calendar_patterns = [ - path("subscription", views.calendar, name="calendar"), - path("/calendar.ics", views.calendar_ics, name="calendar.ics"), -] - -clubs_patterns = [ - path("membres/", views.membres_club, name="membres-club"), - path("liste", views.liste_clubs, name="liste-clubs"), - path( - "change_respo//", - views.change_respo, - name="change-respo", - ), -] - -registration_patterns = [ - # Inscription d'un nouveau membre - path("", views.registration, name="registration"), - path( - "clipper//", - views.registration_form2, - name="clipper-registration", - ), - path( - "user/", - views.registration_form2, - name="user-registration", - ), - path( - "empty", - views.registration_form2, - name="empty-registration", - ), - # Autocompletion - path( - "autocomplete", - views.RegistrationAutocompleteView.as_view(), - name="cof.registration.autocomplete", - ), -] - -urlpatterns = [ - path( - "admin///csv/", - csv_views.admin_list_export, - {"fields": ["username"]}, - ), - # ----- - # Misc - # ----- - path("", views.HomeView.as_view(), name="home"), - path("reset_comptes/", views.ResetComptes.as_view(), name="reset_comptes"), - path( - "user/autocomplete", - views.UserAutocompleteView.as_view(), - name="cof-user-autocomplete", - ), - path("config", views.ConfigUpdate.as_view(), name="config.edit"), - # ----- - # Authentification - # ----- - path( - "cof/denied", - TemplateView.as_view(template_name="cof-denied.html"), - name="cof-denied", - ), - path("cas/login", django_cas_views.LoginView.as_view(), name="cas_login_view"), - path("cas/logout", django_cas_views.LogoutView.as_view()), - path( - "outsider/login", - views.LoginExtView.as_view(), - name="ext_login_view", - ), - path( - "outsider/logout", - django_auth_views.LogoutView.as_view(), - {"next_page": "home"}, - ), - path("login", views.login, name="cof-login"), - path("logout", views.logout, name="cof-logout"), - path("admin/logout/", views.logout), - # ----- - # Infos persos - # ----- - path("profile", views.profile, name="profile"), - path( - "outsider/password-change", - django_auth_views.PasswordChangeView.as_view(), - name="password_change", - ), - path( - "outsider/password-change-done", - django_auth_views.PasswordChangeDoneView.as_view(), - name="password_change_done", - ), - # ----- - # Liens utiles du COF et du BdA - # ----- - path("utile_cof", views.utile_cof, name="utile_cof"), - path("utile_bda", views.utile_bda, name="utile_bda"), - path("utile_bda/bda_diff", views.liste_bdadiff, name="ml_diffbda"), - path("utile_cof/diff_cof", views.liste_diffcof, name="ml_diffcof"), - path( - "utile_bda/bda_revente", - views.liste_bdarevente, - name="ml_bda_revente", - ), - # ----- - # Inscription d'un nouveau membre - # ----- - path("registration/", include(registration_patterns)), - # ----- - # Les exports - # ----- - path("export/", include(export_patterns)), - # ----- - # Les sondages - # ----- - path("survey/", include(surveys_patterns)), - # ----- - # Evenements - # ----- - path("event/", include(events_patterns)), - # ----- - # Calendrier - # ----- - path("calendar/", include(calendar_patterns)), - # ----- - # Clubs - # ----- - path("clubs/", include(clubs_patterns)), -] diff --git a/gestioncof/views.py b/gestioncof/views.py deleted file mode 100644 index 37353346..00000000 --- a/gestioncof/views.py +++ /dev/null @@ -1,980 +0,0 @@ -import csv -import uuid -from datetime import date, timedelta -from smtplib import SMTPRecipientsRefused -from urllib.parse import parse_qs, urlencode, urlparse, urlunparse - -from django.contrib import messages -from django.contrib.auth.decorators import login_required -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib.auth.models import User -from django.contrib.auth.views import ( - LoginView as DjangoLoginView, - LogoutView as DjangoLogoutView, - redirect_to_login, -) -from django.contrib.sites.models import Site -from django.core.mail import send_mail -from django.http import Http404, HttpResponse, HttpResponseForbidden -from django.shortcuts import get_object_or_404, redirect, render -from django.template import loader -from django.urls import reverse_lazy -from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ -from django.views.generic import FormView, TemplateView -from django_cas_ng.views import LogoutView as CasLogoutView -from icalendar import Calendar, Event as Vevent - -from bda.models import Spectacle, Tirage -from gestioncof.autocomplete import cof_autocomplete -from gestioncof.decorators import BuroRequiredMixin, buro_required, cof_required -from gestioncof.forms import ( - CalendarForm, - ClubsForm, - EventForm, - EventFormset, - EventStatusFilterForm, - ExteAuthenticationForm, - GestioncofConfigForm, - PhoneForm, - ProfileForm, - RegistrationPassUserForm, - RegistrationProfileForm, - RegistrationUserForm, - SurveyForm, - SurveyStatusFilterForm, - UserForm, -) -from gestioncof.models import ( - CalendarSubscription, - Club, - CofProfile, - Event, - EventCommentField, - EventCommentValue, - EventOption, - EventOptionChoice, - EventRegistration, - Survey, - SurveyAnswer, - SurveyQuestion, - SurveyQuestionAnswer, -) -from shared.views import AutocompleteView, Select2QuerySetView - - -class HomeView(LoginRequiredMixin, TemplateView): - template_name = "gestioncof/home.html" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["surveys"] = Survey.objects.filter(old=False) - context["events"] = Event.objects.filter(old=False) - context["open_surveys"] = Survey.objects.filter(survey_open=True, old=False) - context["active_tirages"] = Tirage.objects.filter(active=True) - context["open_tirages"] = Tirage.objects.filter( - active=True, ouverture__lte=timezone.now() - ) - context["now"] = timezone.now() - return context - - -class ResetComptes(BuroRequiredMixin, TemplateView): - template_name = "gestioncof/reset_comptes.html" - - def post(self, request): - nb_adherents = CofProfile.objects.filter(is_cof=True).count() - CofProfile.objects.update( - is_cof=False, - date_adhesion=None, - mailing_cof=False, - mailing_bda=False, - mailing_bda_revente=False, - mailing_unernestaparis=False, - ) - context = super().get_context_data() - context["is_done"] = True - context["nb_adherents"] = nb_adherents - return render(request, self.template_name, context) - - -def login(request): - if request.user.is_authenticated: - return redirect("home") - context = {} - if request.method == "GET" and "next" in request.GET: - context["next"] = request.GET["next"] - return render(request, "login_switch.html", context) - - -class LoginExtView(DjangoLoginView): - template_name = "login.html" - form_class = ExteAuthenticationForm - - def form_invalid(self, form): - for e in form.non_field_errors().as_data(): - if e.code in ["has_clipper", "no_password"]: - return render(self.request, "login_error.html", {"error_code": e.code}) - return super().form_invalid(form) - - -class CustomCasLogoutView(CasLogoutView): - """ - Actuellement, le CAS de l'ENS est pété et n'a pas le bon paramètre GET - pour rediriger après déconnexion. On change la redirection à la main - dans la vue de logout. - """ - - def get(self, request): - # CasLogoutView.get() retourne un HttpResponseRedirect - response = super().get(request) - parse_result = urlparse(response.url) - qd = parse_qs(parse_result.query) - - if "url" in qd.keys(): - # Le 2e pop est nécessaire car CAS n'aime pas - # les paramètres sous forme de liste - qd["service"] = qd.pop("url").pop() - - # La méthode _replace est documentée ! - new_url = parse_result._replace(query=urlencode(qd)) - - return redirect(urlunparse(new_url)) - - -@login_required -def logout(request, next_page=None): - if next_page is None: - next_page = request.GET.get("next", None) - - profile = getattr(request.user, "profile", None) - - if profile and profile.login_clipper: - if next_page is None: - # On ne voit pas les messages quand on se déconnecte de CAS - msg = None - else: - msg = _("Déconnexion de GestioCOF et CAS réussie. À bientôt {}.") - logout_view = CustomCasLogoutView.as_view() - else: - msg = _("Déconnexion de GestioCOF réussie. À bientôt {}.") - logout_view = DjangoLogoutView.as_view( - next_page=next_page, template_name="logout.html" - ) - - if msg is not None: - messages.success(request, msg.format(request.user.get_short_name())) - return logout_view(request) - - -@login_required -def survey(request, survey_id): - survey = get_object_or_404( - Survey.objects.prefetch_related("questions", "questions__answers"), id=survey_id - ) - if not survey.survey_open or survey.old: - raise Http404 - success = False - deleted = False - if request.method == "POST": - form = SurveyForm(request.POST, survey=survey) - if request.POST.get("delete"): - try: - current_answer = SurveyAnswer.objects.get( - user=request.user, survey=survey - ) - current_answer.delete() - current_answer = None - except SurveyAnswer.DoesNotExist: - current_answer = None - form = SurveyForm(survey=survey) - success = True - deleted = True - else: - if form.is_valid(): - all_answers = [] - for question_id, answers_ids in form.answers(): - question = get_object_or_404( - SurveyQuestion, id=question_id, survey=survey - ) - if type(answers_ids) != list: - answers_ids = [answers_ids] - if not question.multi_answers and len(answers_ids) > 1: - raise Http404 - for answer_id in answers_ids: - if not answer_id: - continue - answer_id = int(answer_id) - answer = SurveyQuestionAnswer.objects.get( - id=answer_id, survey_question=question - ) - all_answers.append(answer) - try: - current_answer = SurveyAnswer.objects.get( - user=request.user, survey=survey - ) - except SurveyAnswer.DoesNotExist: - current_answer = SurveyAnswer(user=request.user, survey=survey) - current_answer.save() - current_answer.answers.set(all_answers) - current_answer.save() - success = True - else: - try: - current_answer = SurveyAnswer.objects.get(user=request.user, survey=survey) - form = SurveyForm(survey=survey, current_answers=current_answer.answers) - except SurveyAnswer.DoesNotExist: - current_answer = None - form = SurveyForm(survey=survey) - # Messages - if success: - if deleted: - messages.success(request, "Votre réponse a bien été supprimée") - else: - messages.success( - request, - "Votre réponse a bien été enregistrée ! Vous " - "pouvez cependant la modifier jusqu'à la fin " - "du sondage.", - ) - return render( - request, - "gestioncof/survey.html", - {"survey": survey, "form": form, "current_answer": current_answer}, - ) - - -def get_event_form_choices(event, form): - all_choices = [] - for option_id, choices_ids in form.choices(): - option = get_object_or_404(EventOption, id=option_id, event=event) - if type(choices_ids) != list: - choices_ids = [choices_ids] - if not option.multi_choices and len(choices_ids) > 1: - raise Http404 - for choice_id in choices_ids: - if not choice_id: - continue - choice_id = int(choice_id) - choice = EventOptionChoice.objects.get(id=choice_id, event_option=option) - all_choices.append(choice) - return all_choices - - -def update_event_form_comments(event, form, registration): - for commentfield_id, value in form.comments(): - field = get_object_or_404(EventCommentField, id=commentfield_id, event=event) - if value == field.default: - continue - (storage, _) = EventCommentValue.objects.get_or_create( - commentfield=field, registration=registration - ) - storage.content = value - storage.save() - - -@login_required -def event(request, event_id): - event = get_object_or_404(Event, id=event_id) - if (not event.registration_open) or event.old: - raise Http404 - success = False - if request.method == "POST": - form = EventForm(request.POST, event=event) - if form.is_valid(): - all_choices = get_event_form_choices(event, form) - (current_registration, _) = EventRegistration.objects.get_or_create( - user=request.user, event=event - ) - current_registration.options.set(all_choices) - current_registration.save() - success = True - else: - try: - current_registration = EventRegistration.objects.get( - user=request.user, event=event - ) - form = EventForm(event=event, current_choices=current_registration.options) - except EventRegistration.DoesNotExist: - form = EventForm(event=event) - # Messages - if success: - messages.success( - request, - "Votre inscription a bien été enregistrée ! " - "Vous pouvez cependant la modifier jusqu'à " - "la fin des inscriptions.", - ) - return render(request, "gestioncof/event.html", {"event": event, "form": form}) - - -def clean_post_for_status(initial): - d = initial.copy() - for k, v in d.items(): - if k.startswith("id_"): - del d[k] - d[k[3:]] = v - return d - - -@buro_required -def event_status(request, event_id): - event = get_object_or_404(Event, id=event_id) - registrations_query = EventRegistration.objects.filter(event=event) - post_data = clean_post_for_status(request.POST) - form = EventStatusFilterForm(post_data or None, event=event) - if form.is_valid(): - for option_id, choice_id, value in form.filters(): - if option_id == "has_paid": - if value == "yes": - registrations_query = registrations_query.filter(paid=True) - elif value == "no": - registrations_query = registrations_query.filter(paid=False) - continue - choice = get_object_or_404( - EventOptionChoice, id=choice_id, event_option__id=option_id - ) - if value == "none": - continue - if value == "yes": - registrations_query = registrations_query.filter( - options__id__exact=choice.id - ) - elif value == "no": - registrations_query = registrations_query.exclude( - options__id__exact=choice.id - ) - user_choices = registrations_query.prefetch_related("user").all() - options = EventOption.objects.filter(event=event).all() - choices_count = {} - for option in options: - for choice in option.choices.all(): - choices_count[choice.id] = 0 - for user_choice in user_choices: - for choice in user_choice.options.all(): - choices_count[choice.id] += 1 - return render( - request, - "event_status.html", - { - "event": event, - "user_choices": user_choices, - "options": options, - "choices_count": choices_count, - "form": form, - }, - ) - - -@buro_required -def survey_status(request, survey_id): - survey = get_object_or_404(Survey, id=survey_id) - answers_query = SurveyAnswer.objects.filter(survey=survey) - post_data = clean_post_for_status(request.POST) - form = SurveyStatusFilterForm(post_data or None, survey=survey) - if form.is_valid(): - for question_id, answer_id, value in form.filters(): - answer = get_object_or_404( - SurveyQuestionAnswer, id=answer_id, survey_question__id=question_id - ) - if value == "none": - continue - if value == "yes": - answers_query = answers_query.filter(answers__id__exact=answer.id) - elif value == "no": - answers_query = answers_query.exclude(answers__id__exact=answer.id) - user_answers = answers_query.prefetch_related("user").all() - questions = SurveyQuestion.objects.filter(survey=survey).all() - answers_count = {} - for question in questions: - for answer in question.answers.all(): - answers_count[answer.id] = 0 - for user_answer in user_answers: - for answer in user_answer.answers.all(): - answers_count[answer.id] += 1 - return render( - request, - "survey_status.html", - { - "survey": survey, - "user_answers": user_answers, - "questions": questions, - "answers_count": answers_count, - "form": form, - }, - ) - - -@login_required -def profile(request): - user = request.user - data = request.POST if request.method == "POST" else None - user_form = UserForm(data=data, instance=user, prefix="u") - profile_form_klass = ProfileForm if user.profile.is_cof else PhoneForm - profile_form = profile_form_klass(data=data, instance=user.profile, prefix="p") - if request.method == "POST": - if user_form.is_valid() and profile_form.is_valid(): - user_form.save() - profile_form.save() - messages.success(request, _("Votre profil a été mis à jour avec succès !")) - context = {"user_form": user_form, "profile_form": profile_form} - return render(request, "gestioncof/profile.html", context) - - -def registration_set_ro_fields(user_form, profile_form): - user_form.fields["username"].widget.attrs["readonly"] = True - profile_form.fields["login_clipper"].widget.attrs["readonly"] = True - - -@buro_required -def registration_form2(request, login_clipper=None, username=None, fullname=None): - events = Event.objects.filter(old=False).all() - member = None - if login_clipper: - try: # check if the given user is already registered - member = User.objects.get(username=login_clipper) - username = member.username - login_clipper = None - except User.DoesNotExist: - # new user, but prefill - # user - user_form = RegistrationUserForm( - initial={ - "username": login_clipper, - "email": "%s@clipper.ens.fr" % login_clipper, - } - ) - if fullname: - bits = fullname.split(" ") - user_form.fields["first_name"].initial = bits[0] - if len(bits) > 1: - user_form.fields["last_name"].initial = " ".join(bits[1:]) - # profile - profile_form = RegistrationProfileForm( - initial={"login_clipper": login_clipper} - ) - registration_set_ro_fields(user_form, profile_form) - # events & clubs - event_formset = EventFormset(events=events, prefix="events") - clubs_form = ClubsForm() - if username: - member = get_object_or_404(User, username=username) - (profile, _) = CofProfile.objects.get_or_create(user=member) - # already existing, prefill - user_form = RegistrationUserForm(instance=member) - profile_form = RegistrationProfileForm(instance=profile) - registration_set_ro_fields(user_form, profile_form) - # events - current_registrations = [] - for event in events: - try: - current_registrations.append( - EventRegistration.objects.get(user=member, event=event) - ) - except EventRegistration.DoesNotExist: - current_registrations.append(None) - event_formset = EventFormset( - events=events, prefix="events", current_registrations=current_registrations - ) - # Clubs - clubs_form = ClubsForm(initial={"clubs": member.clubs.all()}) - elif not login_clipper: - # new user - user_form = RegistrationPassUserForm() - profile_form = RegistrationProfileForm() - event_formset = EventFormset(events=events, prefix="events") - clubs_form = ClubsForm() - return render( - request, - "gestioncof/registration_form.html", - { - "member": member, - "login_clipper": login_clipper, - "user_form": user_form, - "profile_form": profile_form, - "event_formset": event_formset, - "clubs_form": clubs_form, - }, - ) - - -def notify_new_member(request, member: User): - if not member.email: - messages.warning( - request, - "GestioCOF n'a pas d'adresse mail pour {}, ".format(member) - + "aucun email de bienvenue n'a été envoyé", - ) - return - - # Try to send a welcome email and report SMTP errors - try: - send_mail( - "Bienvenue au COF", - loader.render_to_string( - "gestioncof/mails/welcome.txt", context={"member": member} - ), - "cof@ens.fr", - [member.email], - ) - except SMTPRecipientsRefused: - messages.error( - request, - "Error lors de l'envoi de l'email de bienvenue à {} ({})".format( - member, member.email - ), - ) - - -@buro_required -def registration(request): - if request.POST: - request_dict = request.POST.copy() - member = None - login_clipper = None - - # ----- - # Remplissage des formulaires - # ----- - - if "password1" in request_dict or "password2" in request_dict: - user_form = RegistrationPassUserForm(request_dict) - else: - user_form = RegistrationUserForm(request_dict) - profile_form = RegistrationProfileForm(request_dict) - clubs_form = ClubsForm(request_dict) - events = Event.objects.filter(old=False).all() - event_formset = EventFormset(events=events, data=request_dict, prefix="events") - if "user_exists" in request_dict and request_dict["user_exists"]: - username = request_dict["username"] - try: - member = User.objects.get(username=username) - user_form = RegistrationUserForm(request_dict, instance=member) - if member.profile.login_clipper: - login_clipper = member.profile.login_clipper - except User.DoesNotExist: - pass - else: - pass - - # ----- - # Validation des formulaires - # ----- - - if user_form.is_valid(): - member = user_form.save() - profile, _ = CofProfile.objects.get_or_create(user=member) - was_cof = profile.is_cof - # Maintenant on remplit le formulaire de profil - profile_form = RegistrationProfileForm(request_dict, instance=profile) - if ( - profile_form.is_valid() - and event_formset.is_valid() - and clubs_form.is_valid() - ): - # Enregistrement du profil - profile = profile_form.save() - if profile.is_cof and not was_cof: - notify_new_member(request, member) - profile.date_adhesion = date.today() - profile.save() - - # Enregistrement des inscriptions aux événements - for form in event_formset: - if "status" not in form.cleaned_data: - form.cleaned_data["status"] = "no" - if form.cleaned_data["status"] == "no": - try: - current_registration = EventRegistration.objects.get( - user=member, event=form.event - ) - current_registration.delete() - except EventRegistration.DoesNotExist: - pass - continue - all_choices = get_event_form_choices(form.event, form) - ( - current_registration, - created_reg, - ) = EventRegistration.objects.get_or_create( - user=member, event=form.event - ) - update_event_form_comments(form.event, form, current_registration) - current_registration.options.set(all_choices) - current_registration.paid = form.cleaned_data["status"] == "paid" - current_registration.save() - # if form.event.title == "Mega 15" and created_reg: - # field = EventCommentField.objects.get( - # event=form.event, name="Commentaires") - # try: - # comments = EventCommentValue.objects.get( - # commentfield=field, - # registration=current_registration).content - # except EventCommentValue.DoesNotExist: - # comments = field.default - # FIXME : il faut faire quelque chose de propre ici, - # par exemple écrire un mail générique pour - # l'inscription aux événements et/ou donner la - # possibilité d'associer un mail aux événements - # send_custom_mail(...) - # Enregistrement des inscriptions aux clubs - member.clubs.clear() - for club in clubs_form.cleaned_data["clubs"]: - club.membres.add(member) - club.save() - - # --- - # Success - # --- - - msg = ( - "L'inscription de {:s} ({:s}) a été " - "enregistrée avec succès.".format( - member.get_full_name(), member.email - ) - ) - if profile.is_cof: - msg += "\nIl est désormais membre du COF n°{:d} !".format( - member.profile.id - ) - messages.success(request, msg, extra_tags="safe") - return render( - request, - "gestioncof/registration_post.html", - { - "user_form": user_form, - "profile_form": profile_form, - "member": member, - "login_clipper": login_clipper, - "event_formset": event_formset, - "clubs_form": clubs_form, - }, - ) - else: - return render(request, "registration.html") - - -# ----- -# Clubs -# ----- - - -@login_required -def membres_club(request, name): - # Vérification des permissions : l'utilisateur doit être membre du burô - # ou respo du club. - user = request.user - club = get_object_or_404(Club, name=name) - if not request.user.profile.is_buro and club not in user.clubs_geres.all(): - return HttpResponseForbidden("

    Permission denied

    ") - members_no_respo = club.membres.exclude(clubs_geres=club).all() - return render( - request, - "membres_clubs.html", - {"club": club, "members_no_respo": members_no_respo}, - ) - - -@buro_required -def change_respo(request, club_name, user_id): - club = get_object_or_404(Club, name=club_name) - user = get_object_or_404(User, id=user_id) - if user in club.respos.all(): - club.respos.remove(user) - elif user in club.membres.all(): - club.respos.add(user) - else: - raise Http404 - return redirect("membres-club", name=club_name) - - -@cof_required -def liste_clubs(request): - clubs = Club.objects - if request.user.profile.is_buro: - data = {"owned_clubs": clubs.all()} - else: - data = { - "owned_clubs": request.user.clubs_geres.all(), - "other_clubs": clubs.exclude(respos=request.user), - } - return render(request, "liste_clubs.html", data) - - -@buro_required -def export_members(request): - response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = "attachment; filename=membres_cof.csv" - - writer = csv.writer(response) - for profile in CofProfile.objects.filter(is_cof=True).all(): - user = profile.user - bits = [ - user.id, - user.username, - user.first_name, - user.last_name, - user.email, - profile.phone, - profile.occupation, - profile.departement, - profile.type_cotiz, - profile.date_adhesion, - ] - writer.writerow([str(bit) for bit in bits]) - - return response - - -# ---------------------------------------- -# Début des exports Mega machins hardcodés -# ---------------------------------------- - - -MEGA_YEAR = 2018 -MEGA_EVENT_NAME = "MEGA 2018" -MEGA_COMMENTFIELD_NAME = "Commentaires" -MEGA_CONSCRITORGAFIELD_NAME = "Orga ? Conscrit ?" -MEGA_CONSCRIT = "Conscrit" -MEGA_ORGA = "Orga" - - -def csv_export_mega(filename, qs): - response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = "attachment; filename=" + filename - writer = csv.writer(response) - - for reg in qs.all(): - user = reg.user - profile = user.profile - comments = "---".join([comment.content for comment in reg.comments.all()]) - bits = [ - user.username, - user.first_name, - user.last_name, - user.email, - profile.phone, - user.id, - profile.comments if profile.comments else "", - comments, - ] - - writer.writerow([str(bit) for bit in bits]) - - return response - - -@buro_required -def export_mega_remarksonly(request): - filename = "remarques_mega_{}.csv".format(MEGA_YEAR) - response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = "attachment; filename=" + filename - writer = csv.writer(response) - - event = Event.objects.get(title=MEGA_EVENT_NAME) - commentfield = event.commentfields.get(name=MEGA_COMMENTFIELD_NAME) - for val in commentfield.values.all(): - reg = val.registration - user = reg.user - profile = user.profile - bits = [ - user.username, - user.first_name, - user.last_name, - user.email, - profile.phone, - profile.id, - profile.comments, - val.content, - ] - writer.writerow([str(bit) for bit in bits]) - - return response - - -# @buro_required -# def export_mega_bytype(request, type): -# types = {"orga-actif": "Orga élève", -# "orga-branleur": "Orga étudiant", -# "conscrit-eleve": "Conscrit élève", -# "conscrit-etudiant": "Conscrit étudiant"} -# -# if type not in types: -# raise Http404 -# -# event = Event.objects.get(title="MEGA 2017") -# type_option = event.options.get(name="Type") -# participant_type = type_option.choices.get(value=types[type]).id -# qs = EventRegistration.objects.filter(event=event).filter( -# options__id__exact=participant_type) -# return csv_export_mega(type + '_mega_2017.csv', qs) - - -@buro_required -def export_mega_orgas(request): - event = Event.objects.get(title=MEGA_EVENT_NAME) - type_option = event.options.get(name=MEGA_CONSCRITORGAFIELD_NAME) - participant_type = type_option.choices.get(value=MEGA_ORGA).id - qs = EventRegistration.objects.filter(event=event).filter( - options__id=participant_type - ) - return csv_export_mega("orgas_mega_{}.csv".format(MEGA_YEAR), qs) - - -@buro_required -def export_mega_participants(request): - event = Event.objects.get(title=MEGA_EVENT_NAME) - type_option = event.options.get(name=MEGA_CONSCRITORGAFIELD_NAME) - participant_type = type_option.choices.get(value=MEGA_CONSCRIT).id - qs = EventRegistration.objects.filter(event=event).filter( - options__id=participant_type - ) - return csv_export_mega("conscrits_mega_{}.csv".format(MEGA_YEAR), qs) - - -@buro_required -def export_mega(request): - event = Event.objects.get(title=MEGA_EVENT_NAME) - qs = EventRegistration.objects.filter(event=event).order_by("user__username") - return csv_export_mega("all_mega_{}.csv".format(MEGA_YEAR), qs) - - -# ------------------------------ -# Fin des exports Mega hardcodés -# ------------------------------ - - -@buro_required -def utile_cof(request): - return render(request, "gestioncof/utile_cof.html", {}) - - -@buro_required -def utile_bda(request): - tirages = Tirage.objects.all() - return render(request, "utile_bda.html", {"tirages": tirages}) - - -@buro_required -def liste_bdadiff(request): - titre = "BdA diffusion" - personnes = CofProfile.objects.filter(mailing_bda=True, is_cof=True).all() - return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes}) - - -@buro_required -def liste_bdarevente(request): - titre = "BdA revente" - personnes = CofProfile.objects.filter(mailing_bda_revente=True, is_cof=True).all() - return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes}) - - -@buro_required -def liste_diffcof(request): - titre = "Diffusion COF" - personnes = CofProfile.objects.filter(mailing_cof=True, is_cof=True).all() - return render(request, "liste_mails.html", {"titre": titre, "personnes": personnes}) - - -@cof_required -def calendar(request): - try: - instance = CalendarSubscription.objects.get(user=request.user) - except CalendarSubscription.DoesNotExist: - instance = None - if request.method == "POST": - form = CalendarForm(request.POST, instance=instance) - if form.is_valid(): - subscription = form.save(commit=False) - if instance is None: - subscription.user = request.user - subscription.token = uuid.uuid4() - subscription.save() - form.save_m2m() - messages.success(request, "Calendrier mis à jour avec succès.") - return render( - request, - "gestioncof/calendar_subscription.html", - {"form": form, "token": str(subscription.token)}, - ) - else: - messages.error(request, "Formulaire incorrect.") - return render( - request, "gestioncof/calendar_subscription.html", {"form": form} - ) - else: - return render( - request, - "gestioncof/calendar_subscription.html", - { - "form": CalendarForm(instance=instance), - "token": instance.token if instance else None, - }, - ) - - -def calendar_ics(request, token): - subscription = get_object_or_404(CalendarSubscription, token=token) - shows = subscription.other_shows.all() - if subscription.subscribe_to_my_shows: - shows |= Spectacle.objects.filter( - attribues__participant__user=subscription.user, tirage__active=True - ) - shows = shows.distinct() - vcal = Calendar() - site = Site.objects.get_current() - for show in shows: - vevent = Vevent() - vevent.add("dtstart", show.date) - vevent.add("dtend", show.date + timedelta(seconds=7200)) - vevent.add("summary", show.title) - vevent.add("location", show.location.name) - vevent.add( - "uid", "show-{:d}-{:d}@{:s}".format(show.pk, show.tirage_id, site.domain) - ) - vcal.add_component(vevent) - if subscription.subscribe_to_events: - for event in Event.objects.filter(old=False).all(): - vevent = Vevent() - vevent.add("dtstart", event.start_date) - vevent.add("dtend", event.end_date) - vevent.add("summary", event.title) - vevent.add("location", event.location) - vevent.add("description", event.description) - vevent.add("uid", "event-{:d}@{:s}".format(event.pk, site.domain)) - vcal.add_component(vevent) - response = HttpResponse(content=vcal.to_ical()) - response["Content-Type"] = "text/calendar" - return response - - -class ConfigUpdate(FormView): - form_class = GestioncofConfigForm - template_name = "gestioncof/banner_update.html" - success_url = reverse_lazy("home") - - def dispatch(self, request, *args, **kwargs): - if request.user is None or not request.user.is_superuser: - return redirect_to_login(request.get_full_path()) - return super().dispatch(request, *args, **kwargs) - - def form_valid(self, form): - form.save() - return super().form_valid(form) - - -## -# Autocomplete views -# -# https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#create-an-autocomplete-view -## - - -class UserAutocompleteView(BuroRequiredMixin, Select2QuerySetView): - model = User - search_fields = ("username", "first_name", "last_name") - - -class RegistrationAutocompleteView(BuroRequiredMixin, AutocompleteView): - template_name = "gestioncof/search_results.html" - search_composer = cof_autocomplete diff --git a/gestioncof/widgets.py b/gestioncof/widgets.py deleted file mode 100644 index 9206d441..00000000 --- a/gestioncof/widgets.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.forms.utils import flatatt -from django.forms.widgets import Widget -from django.utils.safestring import mark_safe - - -class TriStateCheckbox(Widget): - def __init__(self, attrs=None, choices=()): - super().__init__(attrs) - # choices can be any iterable, but we may need to render this widget - # multiple times. Thus, collapse it into a list so it can be consumed - # more than once. - self.choices = list(choices) - - def render(self, name, value, attrs=None, choices=(), renderer=None): - if value is None: - value = "none" - attrs["value"] = value - final_attrs = self.build_attrs(self.attrs, attrs) - output = ['' % flatatt(final_attrs)] - return mark_safe("\n".join(output)) diff --git a/petitscours/__init__.py b/petitscours/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/petitscours/forms.py b/petitscours/forms.py deleted file mode 100644 index 0d9f38bc..00000000 --- a/petitscours/forms.py +++ /dev/null @@ -1,52 +0,0 @@ -from django import forms -from django.contrib.auth.models import User -from django.forms import ModelForm -from django.forms.models import inlineformset_factory -from django.utils.translation import gettext_lazy as _ -from hcaptcha.fields import hCaptchaField - -from petitscours.models import PetitCoursAbility, PetitCoursDemande - - -class hCaptchaFieldWithErrors(hCaptchaField): - """ - Pour l'instant, hCaptchaField ne supporte pas le paramètre `error_messages` lors de - l'initialisation. Du coup, on les redéfinit à la main. - """ - - default_error_messages = { - "required": _("Veuillez vérifier que vous êtes bien humain·e."), - "error_hcaptcha": _("Erreur lors de la vérification."), - "invalid_hcaptcha": _("Échec de la vérification !"), - } - - -class DemandeForm(ModelForm): - captcha = hCaptchaFieldWithErrors() - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields["matieres"].help_text = "" - - class Meta: - model = PetitCoursDemande - fields = ( - "name", - "email", - "phone", - "quand", - "freq", - "lieu", - "matieres", - "agrege_requis", - "niveau", - "remarques", - ) - widgets = {"matieres": forms.CheckboxSelectMultiple} - - -MatieresFormSet = inlineformset_factory( - User, - PetitCoursAbility, - fields=("matiere", "niveau", "agrege"), -) diff --git a/petitscours/models.py b/petitscours/models.py deleted file mode 100644 index 8e5d4884..00000000 --- a/petitscours/models.py +++ /dev/null @@ -1,241 +0,0 @@ -from django.contrib.auth.models import User -from django.db import models -from django.db.models import Min -from django.utils.functional import cached_property -from django.utils.translation import ugettext_lazy as _ - -from shared.utils import choices_length - -LEVELS_CHOICES = ( - ("college", _("Collège")), - ("lycee", _("Lycée")), - ("prepa1styear", _("Prépa 1ère année / L1")), - ("prepa2ndyear", _("Prépa 2ème année / L2")), - ("licence3", _("Licence 3")), - ("master1", _("Master (1ère ou 2ème année)")), - ("other", _("Autre (préciser dans les commentaires)")), -) - - -class PetitCoursSubject(models.Model): - name = models.CharField(_("Matière"), max_length=30) - users = models.ManyToManyField( - User, related_name="petits_cours_matieres", through="PetitCoursAbility" - ) - - class Meta: - app_label = "gestioncof" - verbose_name = "Matière de petits cours" - verbose_name_plural = "Matières des petits cours" - - def __str__(self): - return self.name - - -class PetitCoursAbility(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE) - matiere = models.ForeignKey( - PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matière") - ) - niveau = models.CharField( - _("Niveau"), choices=LEVELS_CHOICES, max_length=choices_length(LEVELS_CHOICES) - ) - agrege = models.BooleanField(_("Agrégé"), default=False) - - class Meta: - app_label = "gestioncof" - verbose_name = "Compétence petits cours" - verbose_name_plural = "Compétences des petits cours" - unique_together = ("user", "niveau", "matiere") - - def __str__(self): - return "{:s} - {!s} - {:s}".format( - self.user.username, self.matiere, self.niveau - ) - - @cached_property - def counter(self) -> int: - """Le compteur d'attribution associé au professeur pour cette matière.""" - - return PetitCoursAttributionCounter.get_uptodate(self.user, self.matiere).count - - -class PetitCoursDemande(models.Model): - name = models.CharField(_("Nom/prénom"), max_length=200) - email = models.EmailField(_("Adresse email"), max_length=300) - phone = models.CharField(_("Téléphone (facultatif)"), max_length=20, blank=True) - quand = models.CharField( - _("Quand ?"), - help_text=_( - "Indiquez ici la période désirée pour les petits" - " cours (vacances scolaires, semaine, week-end)." - ), - max_length=300, - blank=True, - ) - freq = models.CharField( - _("Fréquence"), - help_text=_( - "Indiquez ici la fréquence envisagée " - "(hebdomadaire, 2 fois par semaine, ...)" - ), - max_length=300, - blank=True, - ) - lieu = models.CharField( - _("Lieu (si préférence)"), - help_text=_("Si vous avez avez une préférence sur le lieu."), - max_length=300, - blank=True, - ) - - matieres = models.ManyToManyField( - PetitCoursSubject, verbose_name=_("Matières"), related_name="demandes" - ) - agrege_requis = models.BooleanField(_("Agrégé requis"), default=False) - niveau = models.CharField( - _("Niveau"), - default="", - choices=LEVELS_CHOICES, - max_length=choices_length(LEVELS_CHOICES), - ) - - remarques = models.TextField(_("Remarques et précisions"), blank=True) - - traitee = models.BooleanField(_("Traitée"), default=False) - traitee_par = models.ForeignKey( - User, on_delete=models.CASCADE, blank=True, null=True - ) - processed = models.DateTimeField(_("Date de traitement"), blank=True, null=True) - created = models.DateTimeField(_("Date de création"), auto_now_add=True) - - def get_candidates(self, redo=False): - """ - Donne la liste des profs disponibles pour chaque matière de la demande. - - On ne donne que les agrégés si c'est demandé - - Si ``redo`` vaut ``True``, cela signifie qu'on retraite la demande et - il ne faut pas proposer à nouveau des noms qui ont déjà été proposés - """ - for matiere in self.matieres.all(): - candidates = PetitCoursAbility.objects.filter( - matiere=matiere, - niveau=self.niveau, - user__profile__is_cof=True, - user__profile__petits_cours_accept=True, - ) - if self.agrege_requis: - candidates = candidates.filter(agrege=True) - if redo: - attrs = self.petitcoursattribution_set.filter(matiere=matiere) - already_proposed = [attr.user for attr in attrs] - candidates = candidates.exclude(user__in=already_proposed) - candidates = candidates.order_by("?").select_related().all() - yield (matiere, candidates) - - def get_proposals(self, *, max_candidates: int = None, redo: bool = False): - """Calcule une proposition de profs pour la demande. - - Args: - max_candidates (optionnel; défaut: `None`): Le nombre maximum de - candidats à proposer par demande. Si `None` ou non spécifié, - il n'y a pas de limite. - - redo (optionel; défaut: `False`): Détermine si on re-calcule les - propositions pour la demande (les professeurs à qui on a déjà - proposé cette demande sont exclus). - - Returns: - proposals: Le dictionnaire qui associe à chaque matière la liste - des professeurs proposés. Les matières pour lesquelles aucun - professeur n'est disponible ne sont pas présentes dans - `proposals`. - unsatisfied: La liste des matières pour lesquelles aucun - professeur n'est disponible. - """ - - proposals = {} - unsatisfied = [] - for matiere, candidates in self.get_candidates(redo=redo): - if not candidates: - unsatisfied.append(matiere) - else: - proposals[matiere] = matiere_proposals = [] - - candidates = sorted(candidates, key=lambda c: c.counter) - candidates = candidates[:max_candidates] - for candidate in candidates[:max_candidates]: - matiere_proposals.append(candidate.user) - - return proposals, unsatisfied - - def get_absolute_url(self): - from django.urls import reverse - - return reverse("petits-cours-demande-details", kwargs={"pk": str(self.id)}) - - class Meta: - app_label = "gestioncof" - verbose_name = "Demande de petits cours" - verbose_name_plural = "Demandes de petits cours" - - def __str__(self): - return "Demande {:d} du {:s}".format(self.id, self.created.strftime("%d %b %Y")) - - -class PetitCoursAttribution(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE) - demande = models.ForeignKey( - PetitCoursDemande, on_delete=models.CASCADE, verbose_name=_("Demande") - ) - matiere = models.ForeignKey( - PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matière") - ) - date = models.DateTimeField(_("Date d'attribution"), auto_now_add=True) - rank = models.IntegerField("Rang dans l'email") - selected = models.BooleanField(_("Sélectionné par le demandeur"), default=False) - - class Meta: - app_label = "gestioncof" - verbose_name = "Attribution de petits cours" - verbose_name_plural = "Attributions de petits cours" - - def __str__(self): - return "Attribution de la demande {:d} à {:s} pour {!s}".format( - self.demande.id, self.user.username, self.matiere - ) - - -class PetitCoursAttributionCounter(models.Model): - user = models.ForeignKey(User, on_delete=models.CASCADE) - matiere = models.ForeignKey( - PetitCoursSubject, on_delete=models.CASCADE, verbose_name=_("Matiere") - ) - count = models.IntegerField("Nombre d'envois", default=0) - - @classmethod - def get_uptodate(cls, user, matiere): - """ - Donne le compteur de l'utilisateur pour cette matière. Si le compteur - n'existe pas encore, il est initialisé avec le minimum des valeurs des - compteurs de tout le monde. - """ - counter, created = cls.objects.get_or_create(user=user, matiere=matiere) - if created: - mincount = ( - cls.objects.filter(matiere=matiere) - .exclude(user=user) - .aggregate(Min("count"))["count__min"] - ) - counter.count = mincount or 0 - counter.save() - return counter - - class Meta: - app_label = "gestioncof" - verbose_name = "Compteur d'attribution de petits cours" - verbose_name_plural = "Compteurs d'attributions de petits cours" - - def __str__(self): - return "{:d} demandes envoyées à {:s} pour {!s}".format( - self.count, self.user.username, self.matiere - ) diff --git a/petitscours/templates/petitscours/base_title.html b/petitscours/templates/petitscours/base_title.html deleted file mode 100644 index fa872ed0..00000000 --- a/petitscours/templates/petitscours/base_title.html +++ /dev/null @@ -1,2 +0,0 @@ -{% extends "base_title.html" %} - diff --git a/petitscours/templates/petitscours/demande.html b/petitscours/templates/petitscours/demande.html deleted file mode 100644 index 7a41f160..00000000 --- a/petitscours/templates/petitscours/demande.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "base_title.html" %} - -{% block realcontent %} -

    Demande de petits cours

    - {% if success %} -

    Votre demande a été enregistrée avec succès !

    - {% else %} -
    - {% csrf_token %} - - {{ form.as_table }} -
    - -
    - {% endif %} -{% endblock %} diff --git a/petitscours/templates/petitscours/demande_detail.html b/petitscours/templates/petitscours/demande_detail.html deleted file mode 100644 index d7f9ca8b..00000000 --- a/petitscours/templates/petitscours/demande_detail.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends "petitscours/base_title.html" %} -{% load staticfiles %} - -{% block page_size %}col-sm-8{% endblock %} - -{% block realcontent %} -

    - Demande de petits cours  - - Modifier - -

    - {% include "petitscours/details_demande_infos.html" %} -
    - - - {% if demande.traitee %} - - - - - {% endif %} -
    Traitée
    Traitée par {{ demande.traitee_par }}
    Traitée le {{ demande.processed }}
    - Attributions -
      - {% for attrib in attributions %} -
    • {{ attrib.user.get_full_name }} pour {{ attrib.matiere }}, {{ attrib.date|date:"d E Y" }}
    • - {% endfor %} -
    -
    -
    - Retour à la liste des demandes - - {% if demande.traitee %} -
    - -
    - {% else %} -
    - -
    - {% endif %} -
    -{% endblock %} diff --git a/petitscours/templates/petitscours/demande_list.html b/petitscours/templates/petitscours/demande_list.html deleted file mode 100644 index e4c3c782..00000000 --- a/petitscours/templates/petitscours/demande_list.html +++ /dev/null @@ -1,47 +0,0 @@ -{% extends "petitscours/base_title.html" %} -{% load staticfiles %} - -{% block realcontent %} -

    Demandes de petits cours

    -{% if object_list %} - - - - - - - - - - - {% for demande in object_list %} - - - - - - - - - {% endfor %} - -
    NomMatièresDateTraitéepar
    {{ demande.name }}{% for matiere in demande.matieres.all %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}{{ demande.created|date:"d E Y" }}{% if demande.traitee_par %}{{ demande.traitee_par.username }}{% else %}{% endif %}Détails
    - {% if is_paginated %} - - {% endif %} -{% else %} -

    Aucune demande :(

    -{% endif %} -{% endblock %} diff --git a/petitscours/templates/petitscours/demande_raw.html b/petitscours/templates/petitscours/demande_raw.html deleted file mode 100644 index a218a0e0..00000000 --- a/petitscours/templates/petitscours/demande_raw.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "base.html" %} - -{% load bootstrap %} - -{% block content %} -
    -{% if success %} -

    Votre demande a été enregistrée avec succès !

    -{% else %} -
    - {% csrf_token %} - - {{ form | bootstrap }} -
    - -
    -{% endif %} -
    -{% endblock %} diff --git a/petitscours/templates/petitscours/details_demande_infos.html b/petitscours/templates/petitscours/details_demande_infos.html deleted file mode 100644 index 39cee1d3..00000000 --- a/petitscours/templates/petitscours/details_demande_infos.html +++ /dev/null @@ -1,14 +0,0 @@ -{% load staticfiles %} - - - - - - - - - - - - -
    Date {{ demande.created }}
    Nom/prénom {{ demande.name }}
    Email {{ demande.email }}
    Téléphone {{ demande.phone }}
    Lieu {{ demande.lieu }}
    Quand {{ demande.quand }}
    Fréquence {{ demande.freq }}
    Matières {% for matiere in demande.matieres.all %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}
    Niveau souhaité {{ demande.get_niveau_display }}
    Agrégé requis
    Remarques {{ demande.remarques }}
    diff --git a/petitscours/templates/petitscours/inscription.html b/petitscours/templates/petitscours/inscription.html deleted file mode 100644 index 9512e0b3..00000000 --- a/petitscours/templates/petitscours/inscription.html +++ /dev/null @@ -1,117 +0,0 @@ -{% extends "base_title.html" %} -{% load staticfiles %} - -{% block extra_head %} - - - - -{% endblock %} - -{% block realcontent %} - - -

    Inscription pour donner des cours particuliers

    - {% if success %} -

    Votre inscription a été mise à jour avec succès !

    - {% endif %} -
    - {% csrf_token %} -
    Recevoir des propositions de petits cours
    - {% include "petitscours/inscription_formset.html" %} -
    - -
    -
    - Remarques: -
    - -

    - 1: spécifiez les matières pour lesquelles vous êtes compétent
    - 2: spécifiez pour chaque matière le ou les niveaux pour lesquels vous souhaitez recevoir des demandes
    - 3: spécifiez si vous êtes titulaire de l'agrégation pour cette matière
    - 4: pour supprimer une ligne, cliquez sur la croix puis appuyer sur Enregistrer
    -

    -
    -
    -{% endblock %} diff --git a/petitscours/templates/petitscours/inscription_formset.html b/petitscours/templates/petitscours/inscription_formset.html deleted file mode 100644 index 40311772..00000000 --- a/petitscours/templates/petitscours/inscription_formset.html +++ /dev/null @@ -1,40 +0,0 @@ -{% load bootstrap %} -{{ formset.non_form_errors.as_ul }} - -{{ formset.management_form }} -{% for form in formset.forms %} - {% if forloop.first %} - - {% for field in form.visible_fields %} - - {% endfor %} - - - {% endif %} - - {% for field in form.visible_fields %} - {% if field.name != "DELETE" and field.name != "priority" %} - - {% endif %} - {% endfor %} - - -{% endfor %} - -
    - {% if field.name != "DELETE" %} - {{ field.label|safe|capfirst }} - {% endif %} - {{ forloop.counter }} -
    - {% if forloop.first %} - {{ form.non_field_errors }} - {% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %} - {% endif %} - {{ field | bootstrap }} -
    - - -
    -
    -
    diff --git a/petitscours/templates/petitscours/mails/demandeur.txt b/petitscours/templates/petitscours/mails/demandeur.txt deleted file mode 100644 index 69fed436..00000000 --- a/petitscours/templates/petitscours/mails/demandeur.txt +++ /dev/null @@ -1,17 +0,0 @@ -Bonjour, - -Je vous contacte au sujet de votre annonce passée sur le site du COF pour rentrer en contact avec un élève normalien pour des cours particuliers. Voici les coordonnées d'élèves qui sont motivés par de tels cours et correspondent aux critères que vous nous aviez transmis : - -{% for matiere, proposed in proposals %}¤ {{ matiere }} :{% for user in proposed %} - ¤ {{ user.get_full_name }}{% if user.profile.phone %}, {{ user.profile.phone }}{% endif %}{% if user.email %}, {{ user.email }}{% endif %}{% endfor %} - -{% endfor %}{% if unsatisfied %}Nous n'avons cependant pas pu trouver d'élève disponible pour des cours de {% for matiere in unsatisfied %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}. - -{% endif %}Si pour une raison ou une autre ces numéros ne suffisaient pas, n'hésitez pas à répondre à cet e-mail et je vous en ferai parvenir d'autres sans problème. -{% if extra|length > 0 %} -{{ extra|safe }} -{% endif %} -Cordialement, - --- -Le COF, BdE de l'ENS \ No newline at end of file diff --git a/petitscours/templates/petitscours/mails/eleve.txt b/petitscours/templates/petitscours/mails/eleve.txt deleted file mode 100644 index 5f2d4750..00000000 --- a/petitscours/templates/petitscours/mails/eleve.txt +++ /dev/null @@ -1,28 +0,0 @@ -Salut, - -Le COF a reçu une demande de petit cours qui te correspond. Tu es en haut de la liste d'attente donc on a transmis tes coordonnées, ainsi que celles de 2 autres qui correspondaient aussi (c'est la vie, on donne les numéros 3 par 3 pour que ce soit plus souple). Voici quelques infos sur l'annonce en question : - -¤ Nom : {{ demande.name }} - -¤ Période : {{ demande.quand }} - -¤ Fréquence : {{ demande.freq }} - -¤ Lieu (si préféré) : {{ demande.lieu }} - -{% if matieres|length > 1 %}¤ Matières : -{% for matiere in matieres %} ¤ {{ matiere }} -{% endfor %}{% else %}¤ Matière : {% for matiere in matieres %}{{ matiere }} -{% endfor %}{% endif %} -¤ Niveau : {{ demande.get_niveau_display }} - -¤ Remarques diverses (désolé pour les balises HTML) : {{ demande.remarques }} - -Voilà, cette personne te contactera peut-être sous peu, tu pourras voir les détails directement avec elle (prix, modalités, ...). Pour indication, 30 Euro/h semble être la moyenne. - -Si tu te rends compte qu'en fait tu ne peux pas/plus donner de cours en ce moment, ça serait cool que tu décoches la case "Recevoir des propositions de petits cours" sur GestioCOF. Ensuite dès que tu voudras réapparaître tu pourras recocher la case et tu seras à nouveau sur la liste. - -À bientôt, - --- -Le COF, pour les petits cours \ No newline at end of file diff --git a/petitscours/templates/petitscours/traitement_demande.html b/petitscours/templates/petitscours/traitement_demande.html deleted file mode 100644 index e95edb61..00000000 --- a/petitscours/templates/petitscours/traitement_demande.html +++ /dev/null @@ -1,59 +0,0 @@ -{% extends "petitscours/base_title.html" %} - -{% block realcontent %} -

    - Traitement de la demande de petits cours {{ demande.id }} - - Modifier - -

    - {% include "petitscours/details_demande_infos.html" %} -
    - {% if errors %} -
    - Attention: -
      {% for error in errors %}
    • {{ error }}
    • {% endfor %}
    -
    - {% endif %} - {% if unsatisfied %} -
    - Attention: Impossible de trouver des propositions pour les matières suivantes: -
      - {% for matiere in unsatisfied %}
    • {{ matiere }}
    • {% endfor %} -
    -
    - {% endif %} - {% if proposals %} -
    - {% csrf_token %} - Propositions: -
      - {% for proposeduser, matieres in proposed_for %} -
    • {{ proposeduser }} pour {% for matiere in matieres %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}
    • - {% endfor %} -
    -

    Mails pour les membres proposés :

    - {% for proposeduser, mail in proposed_mails %} -
    Pour {{ proposeduser }}:
    - {% with object=mail.0 content=mail.1 %} -
    {{ object }}
    -
    {{ content }}
    - {% endwith %} - {% endfor %} -

    Mail pour l'auteur de la demande :

    - {% with object=mainmail.0 content=mainmail.1 %} -
    {{ object }}
    -
    {{ content|safe }}
    - {% endwith %} - - {% if redo %}{% endif %} - -
    - {% else %} -

    Impossible de trouver des propositions pour cette demande

    -
    Traitement manuel obligatoire !
    - {% endif %} -

    - Retour à la liste des demandes -

    -{% endblock %} diff --git a/petitscours/templates/petitscours/traitement_demande_autre_niveau.html b/petitscours/templates/petitscours/traitement_demande_autre_niveau.html deleted file mode 100644 index cb3ec379..00000000 --- a/petitscours/templates/petitscours/traitement_demande_autre_niveau.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends "petitscours/base_title.html" %} -{% load staticfiles %} - -{% block realcontent %} -

    - Traitement de la demande de petits cours {{ demande.id }} - - Modifier - -

    - {% include "petitscours/details_demande_infos.html" %} -
    -
    - Attention: demande de petits cours spécifiant le niveau "Autre niveau": choisissez les candidats correspondant aux remarques de la demande. S'il y a moins de 3 candidats adaptés, ne mettre que ceux qui conviennent, pas besoin de faire du bourrage :) -
    - {% if unsatisfied %} -
    - Attention: Impossible de trouver des propositions pour les matières suivantes: -
      - {% for matiere in unsatisfied %}
    • {{ matiere }}
    • {% endfor %} -
    -
    - {% endif %} - {% if proposals %} -
    - {% csrf_token %} - {% for matiere, users in proposals %} -

    {{ matiere }}

    - {% for i in "012"|make_list %}{% if i|add:"0" < users|length %} -
    - Proposition {{ i|add:"1" }}: - -
    -

    -
    - -
    - {% endif %} - {% endfor %} - {% endfor %} - - {% if redo %}{% endif %} - -
    - {% else %} -

    Impossible de trouver des propositions pour cette demande

    -
    Traitement manuel obligatoire !
    - {% endif %} -

    - Retour à la liste des demandes -

    -{% endblock %} diff --git a/petitscours/templates/petitscours/traitement_demande_success.html b/petitscours/templates/petitscours/traitement_demande_success.html deleted file mode 100644 index 9d2ded41..00000000 --- a/petitscours/templates/petitscours/traitement_demande_success.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "petitscours/base_title.html" %} - -{% block realcontent %} -

    Traitement de la demande de petits cours {{ demande.id }}

    -
    Demande {{ demande.id }} de {{ demande.name }} {% if redo %}re{% endif %}traitée avec succès !
    - Retour à la liste des demandes -{% endblock %} diff --git a/petitscours/tests/__init__.py b/petitscours/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/petitscours/tests/test_views.py b/petitscours/tests/test_views.py deleted file mode 100644 index 9367c258..00000000 --- a/petitscours/tests/test_views.py +++ /dev/null @@ -1,326 +0,0 @@ -import json -from unittest import mock - -from django.contrib.auth import get_user_model -from django.test import TestCase -from django.urls import reverse - -from gestioncof.tests.mixins import ViewTestCaseMixin - -from .utils import ( - create_petitcours_ability, - create_petitcours_demande, - create_petitcours_subject, -) - -User = get_user_model() - - -class PetitCoursDemandeListViewTestCase(ViewTestCaseMixin, TestCase): - url_name = "petits-cours-demandes-list" - url_expected = "/gestion/petitcours/demandes" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - def setUp(self): - super().setUp() - self.demande1 = create_petitcours_demande() - self.demande2 = create_petitcours_demande() - self.demande3 = create_petitcours_demande() - - def test_get(self): - resp = self.client.get(self.url) - self.assertEqual(resp.status_code, 200) - self.assertEqual(len(resp.context["object_list"]), 3) - - -class PetitCoursDemandeDetailListViewTestCase(ViewTestCaseMixin, TestCase): - url_name = "petits-cours-demande-details" - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - @property - def url_kwargs(self): - return {"pk": self.demande.pk} - - @property - def url_expected(self): - return "/gestion/petitcours/demandes/{}".format(self.demande.pk) - - def setUp(self): - super().setUp() - self.demande = create_petitcours_demande() - - def test_get(self): - resp = self.client.get(self.url) - self.assertEqual(resp.status_code, 200) - - -class PetitCoursInscriptionViewTestCase(ViewTestCaseMixin, TestCase): - url_name = "petits-cours-inscription" - url_expected = "/gestion/petitcours/inscription" - - http_methods = ["GET", "POST"] - - auth_user = "member" - # Also forbidden for "user". Test below. - auth_forbidden = [None] - - def setUp(self): - super().setUp() - self.user = self.users["member"] - self.cofprofile = self.user.profile - - self.subject1 = create_petitcours_subject(name="Matière 1") - self.subject2 = create_petitcours_subject(name="Matière 2") - - def test_get_forbidden_user_not_cof(self): - self.client.force_login( - self.users["user"], backend="django.contrib.auth.backends.ModelBackend" - ) - resp = self.client.get(self.url) - self.assertRedirects(resp, reverse("cof-denied")) - - def test_get(self): - resp = self.client.get(self.url) - self.assertEqual(resp.status_code, 200) - - @property - def base_post_data(self): - return { - "petitcoursability_set-TOTAL_FORMS": "3", - "petitcoursability_set-INITIAL_FORMS": "0", - "petitcoursability_set-MIN_NUM_FORMS": "0", - "petitcoursability_set-MAX_NUM_FORMS": "1000", - "remarques": "", - } - - def test_post(self): - data = dict( - self.base_post_data, - **{ - "petitcoursability_set-TOTAL_FORMS": "2", - "petitcoursability_set-0-id": "", - "petitcoursability_set-0-user": "", - "petitcoursability_set-0-matiere": str(self.subject1.pk), - "petitcoursability_set-0-niveau": "college", - "petitcoursability_set-0-agrege": "1", - # "petitcoursability_set-0-DELETE": "1", - "petitcoursability_set-1-id": "", - "petitcoursability_set-1-user": "", - "petitcoursability_set-1-matiere": str(self.subject2.pk), - "petitcoursability_set-1-niveau": "lycee", - # "petitcoursability_set-1-agrege": "1", - # "petitcoursability_set-1-DELETE": "1", - # "receive_proposals": "1", - "remarques": "Une remarque", - }, - ) - resp = self.client.post(self.url, data) - - self.assertEqual(resp.status_code, 200) - self.cofprofile.refresh_from_db() - self.assertEqual(self.cofprofile.petits_cours_accept, False) - self.assertEqual(self.cofprofile.petits_cours_remarques, "Une remarque") - self.assertEqual(self.user.petitcoursability_set.count(), 2) - ability1 = self.user.petitcoursability_set.get(matiere=self.subject1) - self.assertEqual(ability1.niveau, "college") - self.assertTrue(ability1.agrege) - ability2 = self.user.petitcoursability_set.get(matiere=self.subject2) - self.assertEqual(ability2.niveau, "lycee") - self.assertFalse(ability2.agrege) - - def test_post_delete(self): - ability1 = create_petitcours_ability(user=self.user) - ability2 = create_petitcours_ability(user=self.user) - - data = dict( - self.base_post_data, - **{ - "petitcoursability_set-INITIAL_FORMS": "2", - "petitcoursability_set-TOTAL_FORMS": "2", - "petitcoursability_set-0-id": str(ability1.pk), - "petitcoursability_set-0-user": "", - "petitcoursability_set-0-matiere": str(self.subject1.pk), - "petitcoursability_set-0-niveau": "college", - "petitcoursability_set-0-agrege": "1", - "petitcoursability_set-0-DELETE": "1", - "petitcoursability_set-1-id": str(ability2.pk), - "petitcoursability_set-1-user": str(self.user.pk), - "petitcoursability_set-1-matiere": str(self.subject2.pk), - "petitcoursability_set-1-niveau": "lycee", - # "petitcoursability_set-1-agrege": "1", - "petitcoursability_set-1-DELETE": "1", - }, - ) - resp = self.client.post(self.url, data) - - self.assertEqual(resp.status_code, 200) - self.assertFalse(self.user.petitcoursability_set.all()) - - -class PetitCoursTraitementViewTestCase(ViewTestCaseMixin, TestCase): - url_name = "petits-cours-demande-traitement" - - http_methods = ["GET", "POST"] - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - @property - def url_kwargs(self): - return {"demande_id": self.demande.pk} - - @property - def url_expected(self): - return "/gestion/petitcours/demandes/{}/traitement".format(self.demande.pk) - - def setUp(self): - super().setUp() - self.user = self.users["member"] - self.user.profile.petits_cours_accept = True - self.user.profile.save() - self.subject = create_petitcours_subject() - self.demande = create_petitcours_demande(niveau="college") - self.demande.matieres.add(self.subject) - - def test_get(self): - resp = self.client.get(self.url) - self.assertEqual(resp.status_code, 200) - - def test_get_with_match(self): - create_petitcours_ability( - user=self.user, matiere=self.subject, niveau="college" - ) - - resp = self.client.get(self.url) - - self.assertEqual(resp.status_code, 200) - self.assertListEqual( - list(resp.context["proposals"]), [(self.subject, [self.user])] - ) - self.assertEqual( - resp.context["attribdata"], json.dumps([(self.subject.id, [self.user.id])]) - ) - - def test_post_with_match(self): - create_petitcours_ability( - user=self.user, matiere=self.subject, niveau="college" - ) - - data = { - "attribdata": json.dumps([(self.subject.pk, [self.user.pk])]), - "extra": "", - } - resp = self.client.post(self.url, data) - - self.assertEqual(resp.status_code, 200) - self.demande.refresh_from_db() - self.assertTrue(self.demande.traitee) - self.assertEqual(self.demande.traitee_par, self.users["staff"]) - self.assertIsNotNone(self.demande.processed) - - -class PetitCoursRetraitementViewTestCase(ViewTestCaseMixin, TestCase): - url_name = "petits-cours-demande-retraitement" - - http_methods = ["GET", "POST"] - - auth_user = "staff" - auth_forbidden = [None, "user", "member"] - - @property - def url_kwargs(self): - return {"demande_id": self.demande.pk} - - @property - def url_expected(self): - return "/gestion/petitcours/demandes/{}/retraitement".format(self.demande.pk) - - def setUp(self): - super().setUp() - self.demande = create_petitcours_demande() - - def test_get(self): - resp = self.client.get(self.url) - self.assertEqual(resp.status_code, 200) - - -class PetitCoursDemandeViewTestCase(ViewTestCaseMixin, TestCase): - url_name = "petits-cours-demande" - url_expected = "/gestion/petitcours/demande" - - http_methods = ["GET", "POST"] - - auth_user = None - auth_forbidden = [] - - def setUp(self): - super().setUp() - self.subject1 = create_petitcours_subject() - self.subject2 = create_petitcours_subject() - - def test_get(self): - resp = self.client.get(self.url) - self.assertEqual(resp.status_code, 200) - - @mock.patch("hcaptcha.fields.hCaptchaField.clean") - def test_post(self, mock_clean): - data = { - "name": "Le nom", - "email": "lemail@mail.net", - "phone": "0123456789", - "quand": "matin, midi et soir", - "freq": "tous les jours", - "lieu": "partout", - "matieres": [str(self.subject1.pk), str(self.subject2.pk)], - "agrege_requis": "1", - "niveau": "lycee", - "remarques": "no comment", - "h-captcha-response": 1, - } - resp = self.client.post(self.url, data) - - self.assertEqual(resp.status_code, 200) - self.assertTrue(resp.context["success"], msg=str(resp.context["form"].errors)) - - -class PetitCoursDemandeRawViewTestCase(ViewTestCaseMixin, TestCase): - url_name = "petits-cours-demande-raw" - url_expected = "/gestion/petitcours/demande-raw" - - http_methods = ["GET", "POST"] - - auth_user = None - auth_forbidden = [] - - def setUp(self): - super().setUp() - self.subject1 = create_petitcours_subject() - self.subject2 = create_petitcours_subject() - - def test_get(self): - resp = self.client.get(self.url) - self.assertEqual(resp.status_code, 200) - - @mock.patch("hcaptcha.fields.hCaptchaField.clean") - def test_post(self, mock_clean): - data = { - "name": "Le nom", - "email": "lemail@mail.net", - "phone": "0123456789", - "quand": "matin, midi et soir", - "freq": "tous les jours", - "lieu": "partout", - "matieres": [str(self.subject1.pk), str(self.subject2.pk)], - "agrege_requis": "1", - "niveau": "lycee", - "remarques": "no comment", - "h-captcha-response": 1, - } - resp = self.client.post(self.url, data) - - self.assertEqual(resp.status_code, 200) - self.assertTrue(resp.context["success"], msg=str(resp.context["form"].errors)) diff --git a/petitscours/tests/utils.py b/petitscours/tests/utils.py deleted file mode 100644 index 131f14dc..00000000 --- a/petitscours/tests/utils.py +++ /dev/null @@ -1,27 +0,0 @@ -from gestioncof.tests.utils import create_user -from petitscours.models import ( - PetitCoursAbility, - PetitCoursAttributionCounter, - PetitCoursDemande, - PetitCoursSubject, -) - - -def create_petitcours_ability(**kwargs): - if "user" not in kwargs: - kwargs["user"] = create_user("toto") - if "matiere" not in kwargs: - kwargs["matiere"] = create_petitcours_subject() - if "niveau" not in kwargs: - kwargs["niveau"] = "college" - ability = PetitCoursAbility.objects.create(**kwargs) - PetitCoursAttributionCounter.get_uptodate(ability.user, ability.matiere) - return ability - - -def create_petitcours_demande(**kwargs): - return PetitCoursDemande.objects.create(**kwargs) - - -def create_petitcours_subject(**kwargs): - return PetitCoursSubject.objects.create(**kwargs) diff --git a/petitscours/urls.py b/petitscours/urls.py deleted file mode 100644 index b4bf1538..00000000 --- a/petitscours/urls.py +++ /dev/null @@ -1,37 +0,0 @@ -from django.urls import path - -from gestioncof.decorators import buro_required -from petitscours import views -from petitscours.views import DemandeDetailView, DemandeListView - -urlpatterns = [ - path("inscription", views.inscription, name="petits-cours-inscription"), - path("demande", views.demande, name="petits-cours-demande"), - path( - "demande-raw", - views.demande, - kwargs={"raw": True}, - name="petits-cours-demande-raw", - ), - path( - "demandes", - buro_required(DemandeListView.as_view()), - name="petits-cours-demandes-list", - ), - path( - "demandes/", - buro_required(DemandeDetailView.as_view()), - name="petits-cours-demande-details", - ), - path( - "demandes//traitement", - views.traitement, - name="petits-cours-demande-traitement", - ), - path( - "demandes//retraitement", - views.traitement, - kwargs={"redo": True}, - name="petits-cours-demande-retraitement", - ), -] diff --git a/petitscours/views.py b/petitscours/views.py deleted file mode 100644 index 61230239..00000000 --- a/petitscours/views.py +++ /dev/null @@ -1,315 +0,0 @@ -import json - -from django.conf import settings -from django.contrib import messages -from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User -from django.core import mail -from django.db import transaction -from django.shortcuts import get_object_or_404, redirect, render -from django.template import loader -from django.utils import timezone -from django.views.decorators.csrf import csrf_exempt -from django.views.generic import DetailView, ListView - -from gestioncof.decorators import buro_required -from gestioncof.models import CofProfile -from petitscours.forms import DemandeForm, MatieresFormSet -from petitscours.models import ( - PetitCoursAbility, - PetitCoursAttribution, - PetitCoursAttributionCounter, - PetitCoursDemande, -) - - -class DemandeListView(ListView): - queryset = PetitCoursDemande.objects.prefetch_related("matieres").order_by( - "traitee", "-id" - ) - template_name = "petitscours/demande_list.html" - paginate_by = 20 - - -class DemandeDetailView(DetailView): - model = PetitCoursDemande - queryset = PetitCoursDemande.objects.prefetch_related( - "petitcoursattribution_set", "matieres" - ) - template_name = "petitscours/demande_detail.html" - context_object_name = "demande" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - obj = self.object - context["attributions"] = obj.petitcoursattribution_set.all() - return context - - -@buro_required -def traitement(request, demande_id, redo=False): - demande = get_object_or_404(PetitCoursDemande, id=demande_id) - if demande.niveau == "other": - return _traitement_other(request, demande, redo) - if request.method == "POST": - return _traitement_post(request, demande) - proposals, unsatisfied = demande.get_proposals(redo=redo, max_candidates=3) - return _finalize_traitement(request, demande, proposals, unsatisfied, redo) - - -def _finalize_traitement( - request, demande, proposals, unsatisfied, redo=False, errors=None -): - attribdata = [ - (matiere.id, [user.id for user in users]) - for matiere, users in proposals.items() - ] - proposed_for = {} - for matiere, users in proposals.items(): - for user in users: - proposed_for.setdefault(user, []).append(matiere) - - proposed_mails = _generate_eleve_email(demande, proposed_for) - mainmail = ( - "Cours particuliers ENS", - loader.render_to_string( - "petitscours/mails/demandeur.txt", - context={ - "proposals": proposals.items(), - "unsatisfied": unsatisfied, - "extra": '", - }, - ), - ) - if errors is not None: - for error in errors: - messages.error(request, error) - return render( - request, - "petitscours/traitement_demande.html", - { - "demande": demande, - "unsatisfied": unsatisfied, - "proposals": proposals.items(), - "proposed_for": proposed_for.items(), - "proposed_mails": proposed_mails, - "mainmail": mainmail, - "attribdata": json.dumps(attribdata), - "redo": redo, - }, - ) - - -def _generate_eleve_email(demande, proposed_for): - subject = "Petits cours ENS par le COF" - return [ - ( - user, - ( - subject, - loader.render_to_string( - "petitscours/mails/eleve.txt", - context={"demande": demande, "matieres": matieres}, - ), - ), - ) - for user, matieres in proposed_for.items() - ] - - -def _traitement_other_preparing(request, demande): - redo = "redo" in request.POST - unsatisfied = [] - proposals = {} - errors = [] - for matiere, candidates in demande.get_candidates(redo): - if candidates: - candidates = dict( - [(candidate.user.id, candidate.user) for candidate in candidates] - ) - proposals[matiere] = [] - for choice_id in range(min(3, len(candidates))): - choice = int( - request.POST["proposal-{:d}-{:d}".format(matiere.id, choice_id)] - ) - if choice == -1: - continue - if choice not in candidates: - errors.append( - "Choix invalide pour la proposition {:d}" - "en {!s}".format(choice_id + 1, matiere) - ) - continue - user = candidates[choice] - if user in proposals[matiere]: - errors.append( - "La proposition {:d} en {!s} est un doublon".format( - choice_id + 1, matiere - ) - ) - continue - proposals[matiere].append(user) - if not proposals[matiere]: - errors.append("Aucune proposition pour {!s}".format(matiere)) - elif len(proposals[matiere]) < 3: - errors.append( - "Seulement {:d} proposition{:s} pour {!s}".format( - len(proposals[matiere]), - "s" if len(proposals[matiere]) > 1 else "", - matiere, - ) - ) - else: - unsatisfied.append(matiere) - return _finalize_traitement(request, demande, proposals, unsatisfied, errors=errors) - - -def _traitement_other(request, demande, redo): - if request.method == "POST": - if "preparing" in request.POST: - return _traitement_other_preparing(request, demande) - else: - return _traitement_post(request, demande) - proposals, unsatisfied = demande.get_proposals(redo=redo) - return render( - request, - "petitscours/traitement_demande_autre_niveau.html", - { - "demande": demande, - "unsatisfied": unsatisfied, - "proposals": proposals.items(), - }, - ) - - -def _traitement_post(request, demande): - proposals = {} - proposed_for = {} - unsatisfied = [] - extra = request.POST["extra"].strip() - redo = "redo" in request.POST - attribdata = request.POST["attribdata"] - attribdata = dict(json.loads(attribdata)) - for matiere in demande.matieres.all(): - if matiere.id not in attribdata: - unsatisfied.append(matiere) - else: - proposals[matiere] = [] - for user_id in attribdata[matiere.id]: - user = User.objects.get(pk=user_id) - proposals[matiere].append(user) - if user not in proposed_for: - proposed_for[user] = [matiere] - else: - proposed_for[user].append(matiere) - proposed_mails = _generate_eleve_email(demande, proposed_for) - mainmail_object = "Cours particuliers ENS" - mainmail_body = loader.render_to_string( - "petitscours/mails/demandeur.txt", - context={ - "proposals": proposals.items(), - "unsatisfied": unsatisfied, - "extra": extra, - }, - ) - frommail = settings.MAIL_DATA["petits_cours"]["FROM"] - bccaddress = settings.MAIL_DATA["petits_cours"]["BCC"] - replyto = settings.MAIL_DATA["petits_cours"]["REPLYTO"] - mails_to_send = [] - for user, (mail_object, body) in proposed_mails: - msg = mail.EmailMessage( - mail_object, - body, - frommail, - [user.email], - [bccaddress], - headers={"Reply-To": replyto}, - ) - mails_to_send.append(msg) - mails_to_send.append( - mail.EmailMessage( - mainmail_object, - mainmail_body, - frommail, - [demande.email], - [bccaddress], - headers={"Reply-To": replyto}, - ) - ) - connection = mail.get_connection(fail_silently=False) - connection.send_messages(mails_to_send) - with transaction.atomic(): - for matiere, users in proposals.items(): - for rank, user in enumerate(users): - # TODO(AD): Prefer PetitCoursAttributionCounter.get_uptodate() - counter = PetitCoursAttributionCounter.objects.get( - user=user, matiere=matiere - ) - counter.count += 1 - counter.save() - attrib = PetitCoursAttribution( - user=user, matiere=matiere, demande=demande, rank=rank + 1 - ) - attrib.save() - demande.traitee = True - demande.traitee_par = request.user - demande.processed = timezone.now() - demande.save() - return render( - request, - "petitscours/traitement_demande_success.html", - {"demande": demande, "redo": redo}, - ) - - -@login_required -def inscription(request): - profile, created = CofProfile.objects.get_or_create(user=request.user) - if not profile.is_cof: - return redirect("cof-denied") - success = False - if request.method == "POST": - formset = MatieresFormSet(request.POST, instance=request.user) - if formset.is_valid(): - formset.save() - profile.petits_cours_accept = "receive_proposals" in request.POST - profile.petits_cours_remarques = request.POST["remarques"] - profile.save() - with transaction.atomic(): - abilities = PetitCoursAbility.objects.filter(user=request.user).all() - for ability in abilities: - PetitCoursAttributionCounter.get_uptodate( - ability.user, ability.matiere - ) - success = True - formset = MatieresFormSet(instance=request.user) - else: - formset = MatieresFormSet(instance=request.user) - return render( - request, - "petitscours/inscription.html", - { - "formset": formset, - "success": success, - "receive_proposals": profile.petits_cours_accept, - "remarques": profile.petits_cours_remarques, - }, - ) - - -@csrf_exempt -def demande(request, *, raw: bool = False): - success = False - if request.method == "POST": - form = DemandeForm(request.POST) - if form.is_valid(): - form.save() - success = True - else: - form = DemandeForm() - template_name = "petitscours/demande.html" - if raw: - template_name = "petitscours/demande_raw.html" - return render(request, template_name, {"form": form, "success": success}) diff --git a/requirements.txt b/requirements.txt index c5685b03..f495227f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,11 @@ Django==2.2.* -Pillow==7.2.0 -authens==0.1b0 channels==1.1.* configparser==3.5.0 django-autocomplete-light==3.3.* django-bootstrap-form==3.3 -django-cas-ng==3.6.* +# django-cas-ng==3.6.* django-cors-headers==2.2.0 django-djconfig==0.8.0 -django-hCaptcha==0.1.0 django-js-reverse==0.9.1 django-widget-tweaks==1.4.1 icalendar==4.0.7 diff --git a/shell.nix b/shell.nix index 1dba6da6..769864e9 100644 --- a/shell.nix +++ b/shell.nix @@ -1,29 +1,11 @@ -{ pkgs ? import { }, ... }: +{ pkgs ? import { } +, ... +}: let - python = pkgs.python38; - - django-types = python.pkgs.buildPythonPackage rec { - pname = "django-types"; - version = "0.17.0"; - - format = "pyproject"; - - src = pkgs.fetchPypi { - inherit pname version; - hash = "sha256-wcQqt4h2xXxyg0LVqwYHJas3H8jcg7uFuuC+BoRqrXA="; - }; - - nativeBuildInputs = with python.pkgs; [ poetry-core ]; - - # setup.cfg tries to pull in nonexistent LICENSE.txt file - # postPatch = "rm setup.cfg"; - - # propagatedBuildInputs = [ django typing-extensions ]; - }; + python = pkgs.python39; in - pkgs.mkShell { shellHook = '' export DJANGO_SETTINGS_MODULE=gestioasso.settings.local @@ -34,11 +16,14 @@ pkgs.mkShell { pip install -r requirements-devel.txt | grep -v 'Requirement already satisfied:' ''; - packages = [ python django-types ] ++ (with python.pkgs; [ - pip - virtualenv - python-ldap - ]); + packages = + [ python ] + ++ (with python.pkgs; [ + django-types + pip + virtualenv + python-ldap + ]); allowSubstitutes = false; }