From 9ddf4d0c6da4338d5d6ec8318b30bd6ab7d913a3 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Sun, 7 Feb 2021 00:41:22 +0100 Subject: [PATCH] black --- avisstage/admin.py | 13 +- avisstage/api.py | 69 +- avisstage/apps.py | 3 +- avisstage/decorators.py | 2 + avisstage/documents.py | 71 +- avisstage/forms.py | 125 +++- .../management/commands/nettoie_lieux.py | 51 +- .../management/commands/nettoie_stages.py | 40 +- .../management/commands/supprime_lieu.py | 33 +- .../management/commands/termine_scolarite.py | 3 +- avisstage/migrations/0001_initial.py | 686 ++++++++++++++++-- .../migrations/0002_auto_20171002_2243.py | 26 +- .../migrations/0003_auto_20210117_1208.py | 350 ++++++++- .../migrations/0004_allauth_to_authens.py | 59 +- .../migrations/0005_normalien_en_scolarite.py | 6 +- .../migrations/0006_auto_20210131_1954.py | 24 +- avisstage/models.py | 159 ++-- avisstage/statics.py | 595 +++++++-------- avisstage/templatetags/avisstage_tags.py | 15 +- avisstage/tests.py | 513 +++++++------ avisstage/urls.py | 92 ++- avisstage/utils.py | 14 +- avisstage/views.py | 324 ++++++--- avisstage/views_search.py | 221 +++--- avisstage/widgets.py | 24 +- 25 files changed, 2360 insertions(+), 1158 deletions(-) diff --git a/avisstage/admin.py b/avisstage/admin.py index c57f678..52a6c2d 100644 --- a/avisstage/admin.py +++ b/avisstage/admin.py @@ -5,29 +5,36 @@ from avisstage.models import * import authens.models as authmod + class NormalienInline(admin.StackedInline): model = Normalien inline_classes = ("collapse open",) + class UserAdmin(UserAdmin): - inlines = (NormalienInline, ) + inlines = (NormalienInline,) + class AvisLieuInline(admin.StackedInline): model = AvisLieu inline_classes = ("collapse open",) extra = 0 + class AvisStageInline(admin.StackedInline): model = AvisStage inline_classes = ("collapse open",) extra = 0 - + + class StageAdmin(admin.ModelAdmin): inlines = (AvisLieuInline, AvisStageInline) + class StageMatiereAdmin(admin.ModelAdmin): model = StageMatiere - prepopulated_fields = {"slug": ('nom',)} + prepopulated_fields = {"slug": ("nom",)} + admin.site.unregister(User) admin.site.register(User, UserAdmin) diff --git a/avisstage/api.py b/avisstage/api.py index a1e4c0e..61e555f 100644 --- a/avisstage/api.py +++ b/avisstage/api.py @@ -10,23 +10,25 @@ from django.urls import reverse from .models import Lieu, Stage, Normalien, StageMatiere from .utils import approximate_distance + class EnScolariteAuthentication(SessionAuthentication): def is_authenticated(self, request, **kwargs): if super().is_authenticated(request, **kwargs): return request.user.profil.en_scolarite return False + # API principale pour les lieux class LieuResource(ModelResource): - #stages = fields.ToManyField("avisstage.api.StageResource", + # stages = fields.ToManyField("avisstage.api.StageResource", # "stages", use_in="detail", full=True) - + class Meta: queryset = Lieu.objects.all() resource_name = "lieu" fields = ["nom", "ville", "pays", "coord", "type_lieu", "id"] - - #login_required + + # login_required authentication = SessionAuthentication() # Filtres personnalisés @@ -37,43 +39,44 @@ class LieuResource(ModelResource): # Trouver les lieux à proximités d'un point donné if "lng" in filters and "lat" in filters: - lat = float(filters['lat']) - lng = float(filters['lng']) - pt = geos.Point((lng,lat), srid=4326) + lat = float(filters["lat"]) + lng = float(filters["lng"]) + pt = geos.Point((lng, lat), srid=4326) self.reference_point = pt - orm_filters['coord__distance_lte'] = (pt, 10000) + orm_filters["coord__distance_lte"] = (pt, 10000) # Filtrer les lieux qui ont déjà des stages if "has_stage" in filters: - orm_filters['stages__public'] = True - + orm_filters["stages__public"] = True + return orm_filters # Custom apply filters pour ajouter le "distinct" def apply_filters(self, request, applicable_filters): return self.get_object_list(request).filter(**applicable_filters).distinct() - + # Ajout d'informations def dehydrate(self, bundle): bundle = super(LieuResource, self).dehydrate(bundle) - + obj = bundle.obj - bundle.data['coord'] = {'lat': float(obj.coord.y), - 'lng': float(obj.coord.x)} + bundle.data["coord"] = {"lat": float(obj.coord.y), "lng": float(obj.coord.x)} # Distance au point recherché if "lat" in bundle.request.GET and "lng" in bundle.request.GET: - bundle.data['distance'] = approximate_distance( - self.reference_point, bundle.obj.coord) - + bundle.data["distance"] = approximate_distance( + self.reference_point, bundle.obj.coord + ) + # Autres infos utiles bundle.data["pays_nom"] = obj.get_pays_display() bundle.data["type_lieu_nom"] = obj.type_lieu_fancy # TODO use annotate? - bundle.data["num_stages"] = obj.stages.filter(public=True).count() + bundle.data["num_stages"] = obj.stages.filter(public=True).count() return bundle + # API sur un stage class StageResource(ModelResource): class Meta: @@ -81,7 +84,7 @@ class StageResource(ModelResource): resource_name = "stage" fields = ["sujet", "date_debut", "date_fin", "matieres", "id"] - #login_required + # login_required authentication = EnScolariteAuthentication() # Filtres personnalisés @@ -92,9 +95,9 @@ class StageResource(ModelResource): # Récupération des stages à un lieu donné if "lieux" in filters: - flieux = map(int, filters['lieux'].split(',')) - orm_filters['lieux__id__in'] = flieux - + flieux = map(int, filters["lieux"].split(",")) + orm_filters["lieux__id__in"] = flieux + return orm_filters # Informations à ajouter @@ -103,23 +106,27 @@ class StageResource(ModelResource): obj = bundle.obj # Affichage des manytomany en condensé - bundle.data['auteur'] = obj.auteur.nom - bundle.data['thematiques'] = list(obj.thematiques.all().values_list("name", flat=True)) - bundle.data['matieres'] = list(obj.matieres.all().values_list("nom", flat=True)) - + bundle.data["auteur"] = obj.auteur.nom + bundle.data["thematiques"] = list( + obj.thematiques.all().values_list("name", flat=True) + ) + bundle.data["matieres"] = list(obj.matieres.all().values_list("nom", flat=True)) + # Adresse de la fiche de stage - bundle.data['url'] = reverse("avisstage:stage", kwargs={"pk": obj.id}); + bundle.data["url"] = reverse("avisstage:stage", kwargs={"pk": obj.id}) return bundle + # Auteurs des fiches (TODO supprimer ?) class AuteurResource(ModelResource): - stages = fields.ToManyField("avisstage.api.StageResource", - "stages", use_in="detail") - + stages = fields.ToManyField( + "avisstage.api.StageResource", "stages", use_in="detail" + ) + class Meta: queryset = Normalien.objects.all() resource_name = "profil" fields = ["id", "nom", "stages"] - #login_required + # login_required authentication = EnScolariteAuthentication() diff --git a/avisstage/apps.py b/avisstage/apps.py index 4fa2dd9..f046aa9 100644 --- a/avisstage/apps.py +++ b/avisstage/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig + class AvisstageConfig(AppConfig): - name = 'avisstage' + name = "avisstage" diff --git a/avisstage/decorators.py b/avisstage/decorators.py index d5f7dd6..8437240 100644 --- a/avisstage/decorators.py +++ b/avisstage/decorators.py @@ -3,10 +3,12 @@ from functools import wraps from django.urls import reverse from django.shortcuts import redirect + def en_scolarite_required(view_func): @wraps(view_func) def _wrapped_view(request, *args, **kwargs): if request.user.profil.en_scolarite: return view_func(request, *args, **kwargs) return redirect(reverse("avisstage:403-archicubes")) + return _wrapped_view diff --git a/avisstage/documents.py b/avisstage/documents.py index b2fcf50..4acfbc9 100644 --- a/avisstage/documents.py +++ b/avisstage/documents.py @@ -6,47 +6,56 @@ from .statics import PAYS_OPTIONS PAYS_DICT = dict(PAYS_OPTIONS) -stage = Index('stages') -stage.settings( - number_of_shards=1, - number_of_replicas=0 -) - +stage = Index("stages") +stage.settings(number_of_shards=1, number_of_replicas=0) + text_analyzer = analyzer( - 'default', + "default", tokenizer="standard", - filter=['lowercase', 'standard', 'asciifolding', - token_filter("frstop", type="stop", stopwords="_french_"), - token_filter("frsnow", type="snowball", language="French")]) + filter=[ + "lowercase", + "standard", + "asciifolding", + token_filter("frstop", type="stop", stopwords="_french_"), + token_filter("frsnow", type="snowball", language="French"), + ], +) stage.analyzer(text_analyzer) + @stage.doc_type class StageDocument(DocType): - lieux = fields.ObjectField(properties={ - 'nom': fields.StringField(), - 'ville': fields.StringField(), - 'pays': fields.StringField(), - }) - auteur = fields.ObjectField(properties={ - 'nom': fields.StringField(), - }) + lieux = fields.ObjectField( + properties={ + "nom": fields.StringField(), + "ville": fields.StringField(), + "pays": fields.StringField(), + } + ) + auteur = fields.ObjectField( + properties={ + "nom": fields.StringField(), + } + ) thematiques = fields.StringField() matieres = fields.StringField() - + class Meta: model = Stage fields = [ - 'sujet', - 'encadrants', - 'type_stage', - 'niveau_scol', - 'structure', - 'date_debut', - 'date_fin' + "sujet", + "encadrants", + "type_stage", + "niveau_scol", + "structure", + "date_debut", + "date_fin", ] def prepare_thematiques(self, instance): - return ", ".join(instance.thematiques.all().values_list("name", flat=True)).lower() + return ", ".join( + instance.thematiques.all().values_list("name", flat=True) + ).lower() def prepare_matieres(self, instance): return ", ".join(instance.matieres.all().values_list("nom", flat=True)).lower() @@ -65,11 +74,11 @@ class StageDocument(DocType): def prepare_sujet(self, instance): return instance.sujet.lower() - + # Hook pour l'affichage des noms de pays def prepare(self, instance): data = super(StageDocument, self).prepare(instance) - - for lieu in data['lieux']: - lieu['pays'] = PAYS_DICT[lieu['pays']].lower() + + for lieu in data["lieux"]: + lieu["pays"] = PAYS_DICT[lieu["pays"]].lower() return data diff --git a/avisstage/forms.py b/avisstage/forms.py index 7e52b3a..f8696f7 100644 --- a/avisstage/forms.py +++ b/avisstage/forms.py @@ -15,32 +15,52 @@ from .widgets import LatLonField class HTMLTrimmerForm(forms.ModelForm): def clean(self): # Suppression des espaces blanc avant et après le texte pour les champs html - leading_white = re.compile(r"^( \t\n)*(

( |[ \n\t]|)*

( \t\n)*)+?", re.IGNORECASE) - trailing_white = re.compile(r"(( \t\n)*

( |[ \n\t]|)*

)+?( \t\n)*$", re.IGNORECASE) + leading_white = re.compile( + r"^( \t\n)*(

( |[ \n\t]|)*

( \t\n)*)+?", re.IGNORECASE + ) + trailing_white = re.compile( + r"(( \t\n)*

( |[ \n\t]|)*

)+?( \t\n)*$", re.IGNORECASE + ) cleaned_data = super(HTMLTrimmerForm, self).clean() - + for (fname, fval) in cleaned_data.items(): # Heuristique : les champs commençant par "avis_" sont des champs html if fname[:5] == "avis_": - cleaned_data[fname] = leading_white.sub("", trailing_white.sub("", fval)) - + cleaned_data[fname] = leading_white.sub( + "", trailing_white.sub("", fval) + ) + return cleaned_data + # Infos sur un stage class StageForm(forms.ModelForm): - date_widget = forms.DateInput(attrs={"class":"datepicker", - "placeholder":"JJ/MM/AAAA"}) - date_debut = forms.DateField(label="Date de début", - input_formats=["%d/%m/%Y"], widget=date_widget) - date_fin = forms.DateField(label="Date de fin", - input_formats=["%d/%m/%Y"], widget=date_widget) + date_widget = forms.DateInput( + attrs={"class": "datepicker", "placeholder": "JJ/MM/AAAA"} + ) + date_debut = forms.DateField( + label="Date de début", input_formats=["%d/%m/%Y"], widget=date_widget + ) + date_fin = forms.DateField( + label="Date de fin", input_formats=["%d/%m/%Y"], widget=date_widget + ) class Meta: model = Stage - fields = ['sujet', 'date_debut', 'date_fin', 'type_stage', 'niveau_scol', 'thematiques', 'matieres', 'structure', 'encadrants'] + fields = [ + "sujet", + "date_debut", + "date_fin", + "type_stage", + "niveau_scol", + "thematiques", + "matieres", + "structure", + "encadrants", + ] help_texts = { "thematiques": "Mettez une virgule pour valider votre thématique si la suggestion ne correspond pas ou si elle n'existe pas encore", - "structure": "Nom de l'équipe, du laboratoire, de la startup... (si le lieu ne suffit pas)" + "structure": "Nom de l'équipe, du laboratoire, de la startup... (si le lieu ne suffit pas)", } labels = { "date_debut": "Date de début", @@ -51,27 +71,36 @@ class StageForm(forms.ModelForm): if "request" in kwargs: self.request = kwargs.pop("request") super(StageForm, self).__init__(*args, **kwargs) - + def save(self, commit=True): # Lors de la création : attribution à l'utilisateur connecté - if self.instance.id is None and hasattr(self, 'request'): + if self.instance.id is None and hasattr(self, "request"): self.instance.auteur = self.request.user.profil - + # Date de modification self.instance.date_maj = timezone.now() self.instance.update_stats(False) - + stage = super(StageForm, self).save(commit=commit) return stage + # Sous-formulaire des avis sur le stage class AvisStageForm(HTMLTrimmerForm): class Meta: model = AvisStage - fields = ['chapo', 'avis_sujet', 'avis_ambiance', 'avis_admin', 'avis_prestage', 'les_plus', 'les_moins'] + fields = [ + "chapo", + "avis_sujet", + "avis_ambiance", + "avis_admin", + "avis_prestage", + "les_plus", + "les_moins", + ] help_texts = { - "chapo": "\"Trop long, pas lu\" : une accroche résumant ce que vous avez pensé de ce séjour", + "chapo": '"Trop long, pas lu" : une accroche résumant ce que vous avez pensé de ce séjour', "avis_ambiance": "Avez-vous passé un bon moment à ce travail ? Étiez-vous assez guidé⋅e ? Aviez-vous un bon contact avec vos encadrant⋅e⋅s ? Y avait-il une bonne ambiance dans l'équipe ?", "avis_sujet": "Quelle était votre mission ? Qu'en avez-vous retiré ? Le travail correspondait-il à vos attentes ? Était-ce à votre niveau, trop dur, trop facile ?", "avis_admin": "Avez-vous commencé votre travail à la date prévue ? Était-ce compliqué d'obtenir les documents nécessaires (visa, contrats, etc) ? L'administration de l'établissement vous a-t-elle aidé⋅e ? Étiez-vous rémunéré⋅e ?", @@ -80,48 +109,62 @@ class AvisStageForm(HTMLTrimmerForm): "les_moins": "Ce qui aurait pu être mieux", } + class AvisLieuForm(HTMLTrimmerForm): class Meta: model = AvisLieu - fields = ['lieu', 'chapo', 'avis_lieustage', 'avis_pratique', 'avis_tourisme', 'les_plus', 'les_moins'] + fields = [ + "lieu", + "chapo", + "avis_lieustage", + "avis_pratique", + "avis_tourisme", + "les_plus", + "les_moins", + ] help_texts = { - "chapo": "\"Trop long, pas lu\" : une accroche résumant ce que vous avez pensé de cet endroit", + "chapo": '"Trop long, pas lu" : une accroche résumant ce que vous avez pensé de cet endroit', "avis_lieustage": "Qu'avez-vous pensé des lieux où vous travailliez ? Les bâtiments étaient-ils modernes ? Était-il agréable d'y travailler ?", "avis_pratique": "Avez-vous eu du mal à trouver un logement ? Y-a-t-il des choses que vous avez apprises sur place qu'il vous aurait été utile de savoir avant de partir ?", "avis_tourisme": "Y-a-t-il des lieux à visiter dans cette zone ? Avez-vous pratiqué des activités sportives ? Est-il facile de faire des rencontres ?", "les_plus": "Les meilleures raisons de partir à cet endroit", "les_moins": "Ce qui vous a gêné ou manqué là-bas", } - widgets = { - "lieu": forms.HiddenInput(attrs={"class":"lieu-hidden"}) - } + widgets = {"lieu": forms.HiddenInput(attrs={"class": "lieu-hidden"})} + # Création d'un nouveau lieu class LieuForm(forms.ModelForm): coord = LatLonField() id = forms.IntegerField(widget=forms.widgets.HiddenInput(), required=False) - + class Meta: model = Lieu - fields = ['id', 'nom', 'type_lieu', 'ville', 'pays', 'coord'] + fields = ["id", "nom", "type_lieu", "ville", "pays", "coord"] + # Widget de feedback class FeedbackForm(forms.Form): objet = forms.CharField(label="Objet", required=True) - message = forms.CharField(label="Message", required=True, widget=forms.widgets.Textarea()) - + message = forms.CharField( + label="Message", required=True, widget=forms.widgets.Textarea() + ) + + # Nouvelle adresse mail class AdresseEmailForm(forms.Form): def __init__(self, _user, **kwargs): self._user = _user super().__init__(**kwargs) - email = forms.EmailField(widget=forms.widgets.EmailInput(attrs={"placeholder": "Nouvelle adresse"})) + + email = forms.EmailField( + widget=forms.widgets.EmailInput(attrs={"placeholder": "Nouvelle adresse"}) + ) def clean_email(self): email = self.cleaned_data["email"] if EmailAddress.objects.filter(user=self._user, email=email).exists(): - raise forms.ValidationError( - "Cette adresse est déjà associée à ce compte") + raise forms.ValidationError("Cette adresse est déjà associée à ce compte") return email @@ -131,19 +174,25 @@ def _unicode_ci_compare(s1, s2): recommended algorithm from Unicode Technical Report 36, section 2.11.2(B)(2). """ - return unicodedata.normalize('NFKC', s1).casefold() == unicodedata.normalize('NFKC', s2).casefold() + return ( + unicodedata.normalize("NFKC", s1).casefold() + == unicodedata.normalize("NFKC", s2).casefold() + ) + - # (Ré)initialisation du mot de passe class ReinitMdpForm(PasswordResetForm): def get_users(self, email): """Override default method to allow unusable passwords""" email_field_name = User.get_email_field_name() - active_users = User._default_manager.filter(**{ - '%s__iexact' % email_field_name: email, - 'is_active': True, - }) + active_users = User._default_manager.filter( + **{ + "%s__iexact" % email_field_name: email, + "is_active": True, + } + ) return ( - u for u in active_users + u + for u in active_users if _unicode_ci_compare(email, getattr(u, email_field_name)) ) diff --git a/avisstage/management/commands/nettoie_lieux.py b/avisstage/management/commands/nettoie_lieux.py index e7eb099..304e740 100644 --- a/avisstage/management/commands/nettoie_lieux.py +++ b/avisstage/management/commands/nettoie_lieux.py @@ -1,43 +1,60 @@ -#coding: utf-8 +# coding: utf-8 from django.core.management.base import BaseCommand, CommandError from django.db.models import Count from avisstage.models import Stage, Lieu + class Command(BaseCommand): - help = 'Nettoie les stages à plusieurs lieux identiques' + help = "Nettoie les stages à plusieurs lieux identiques" def add_arguments(self, parser): - parser.add_argument('min_lieu', nargs='?', default=0, type=int) + parser.add_argument("min_lieu", nargs="?", default=0, type=int) parser.add_argument( - '--apply', - action='store_true', + "--apply", + action="store_true", default=False, - help='Applies the modifications', + help="Applies the modifications", ) def handle(self, *args, **options): rundb = False - if options.get('apply', False): + if options.get("apply", False): rundb = True else: print("Les modifications ne seront pas appliquées") - - min_lieu = options.get('min_lieu', 0) - for lieu in Lieu.objects.filter(id__gte=min_lieu).order_by('-id'): - lproches = Lieu.objects.filter(id__lt=lieu.id, coord__distance_lte=(lieu.coord, 5)) + min_lieu = options.get("min_lieu", 0) + + for lieu in Lieu.objects.filter(id__gte=min_lieu).order_by("-id"): + lproches = Lieu.objects.filter( + id__lt=lieu.id, coord__distance_lte=(lieu.coord, 5) + ) if len(lproches) == 0: continue - print("Doublons possibles pour %s (id=%d, %d avis) :" % (lieu, lieu.id, lieu.avislieu_set.count())) + print( + "Doublons possibles pour %s (id=%d, %d avis) :" + % (lieu, lieu.id, lieu.avislieu_set.count()) + ) for plieu in lproches: - pprint = " > %s (id=%d, %d avis)" % (plieu, plieu.id, plieu.avislieu_set.count()) - if plieu.nom == lieu.nom and plieu.ville == lieu.ville and plieu.type_lieu == lieu.type_lieu: - print("%s %s" % (pprint, self.style.SUCCESS(u'-> Suppression'))) + pprint = " > %s (id=%d, %d avis)" % ( + plieu, + plieu.id, + plieu.avislieu_set.count(), + ) + if ( + plieu.nom == lieu.nom + and plieu.ville == lieu.ville + and plieu.type_lieu == lieu.type_lieu + ): + print("%s %s" % (pprint, self.style.SUCCESS(u"-> Suppression"))) if rundb: for avis in plieu.avislieu_set.all(): avis.lieu = lieu avis.save() plieu.delete() else: - print("%s %s" % (pprint, self.style.WARNING(u'-> À supprimer manuellement'))) - self.stdout.write(self.style.SUCCESS(u'Nettoyage des lieux effectué')) + print( + "%s %s" + % (pprint, self.style.WARNING(u"-> À supprimer manuellement")) + ) + self.stdout.write(self.style.SUCCESS(u"Nettoyage des lieux effectué")) diff --git a/avisstage/management/commands/nettoie_stages.py b/avisstage/management/commands/nettoie_stages.py index e679bb1..a76f83e 100644 --- a/avisstage/management/commands/nettoie_stages.py +++ b/avisstage/management/commands/nettoie_stages.py @@ -1,18 +1,19 @@ -#coding: utf-8 +# coding: utf-8 from django.core.management.base import BaseCommand, CommandError from django.db.models import Count from avisstage.models import Stage, Lieu + class Command(BaseCommand): - help = 'Nettoie les stages à plusieurs lieux identiques' + help = "Nettoie les stages à plusieurs lieux identiques" def add_arguments(self, parser): - parser.add_argument('min_stage', nargs='?', default=0, type=int) + parser.add_argument("min_stage", nargs="?", default=0, type=int) parser.add_argument( - '--apply', - action='store_true', + "--apply", + action="store_true", default=False, - help='Applies the modifications', + help="Applies the modifications", ) def handle(self, *args, **options): @@ -27,15 +28,16 @@ class Command(BaseCommand): return length rundb = False - if options.get('apply', False): + if options.get("apply", False): rundb = True else: print("Les modifications ne seront pas appliquées") - - min_stage = options.get('min_stage', 0) - for stage in Stage.objects.annotate(c=Count("lieux"))\ - .filter(c__gte=2, id__gte=min_stage): + min_stage = options.get("min_stage", 0) + + for stage in Stage.objects.annotate(c=Count("lieux")).filter( + c__gte=2, id__gte=min_stage + ): lieuset = {} todel = [] problems = [] @@ -54,13 +56,17 @@ class Command(BaseCommand): if len(todel) > 0: print("Doublons détectés dans %s" % (stage,)) for avis, alen in todel: - print(" > Suppression de l'avis sur %s de %d mots" % \ - (avis.lieu, alen)) + print( + " > Suppression de l'avis sur %s de %d mots" % (avis.lieu, alen) + ) if rundb: avis.delete() if len(problems) > 0: - self.stdout.write(self.style.WARNING("Réparation impossible de %s (id=%d)" % (stage, stage.id))) + self.stdout.write( + self.style.WARNING( + "Réparation impossible de %s (id=%d)" % (stage, stage.id) + ) + ) for avis, alen in problems: - print(" > Avis sur %s de %d mots" % \ - (avis.lieu, alen)) - self.stdout.write(self.style.SUCCESS(u'Nettoyage des stages effectué')) + print(" > Avis sur %s de %d mots" % (avis.lieu, alen)) + self.stdout.write(self.style.SUCCESS(u"Nettoyage des stages effectué")) diff --git a/avisstage/management/commands/supprime_lieu.py b/avisstage/management/commands/supprime_lieu.py index a596f3f..7c113e8 100644 --- a/avisstage/management/commands/supprime_lieu.py +++ b/avisstage/management/commands/supprime_lieu.py @@ -1,35 +1,42 @@ -#coding: utf-8 +# coding: utf-8 from django.core.management.base import BaseCommand, CommandError from django.db.models import Count from avisstage.models import Stage, Lieu + class Command(BaseCommand): - help = 'Nettoie les stages à plusieurs lieux identiques' + help = "Nettoie les stages à plusieurs lieux identiques" def add_arguments(self, parser): - parser.add_argument('del_lieu', type=int, help='Lieu à supprimer') - parser.add_argument('repl_lieu', type=int, help='Lieu le remplaçant') + parser.add_argument("del_lieu", type=int, help="Lieu à supprimer") + parser.add_argument("repl_lieu", type=int, help="Lieu le remplaçant") parser.add_argument( - '--apply', - action='store_true', + "--apply", + action="store_true", default=False, - help='Applies the modifications', + help="Applies the modifications", ) def handle(self, *args, **options): rundb = False - if options.get('apply', False): + if options.get("apply", False): rundb = True else: print("Les modifications ne seront pas appliquées") - plieu = Lieu.objects.get(id=options['del_lieu']) - lieu = Lieu.objects.get(id=options['repl_lieu']) - print("Suppression de %s (id=%d, %d avis)" % (plieu, plieu.id, plieu.avislieu_set.count())) - print("Remplacement par %s (id=%d, %d avis)" % (lieu, lieu.id, lieu.avislieu_set.count())) + plieu = Lieu.objects.get(id=options["del_lieu"]) + lieu = Lieu.objects.get(id=options["repl_lieu"]) + print( + "Suppression de %s (id=%d, %d avis)" + % (plieu, plieu.id, plieu.avislieu_set.count()) + ) + print( + "Remplacement par %s (id=%d, %d avis)" + % (lieu, lieu.id, lieu.avislieu_set.count()) + ) if rundb: for avis in plieu.avislieu_set.all(): avis.lieu = lieu avis.save() plieu.delete() - self.stdout.write(self.style.SUCCESS(u'Terminé')) + self.stdout.write(self.style.SUCCESS(u"Terminé")) diff --git a/avisstage/management/commands/termine_scolarite.py b/avisstage/management/commands/termine_scolarite.py index 6779604..aaf1e9e 100644 --- a/avisstage/management/commands/termine_scolarite.py +++ b/avisstage/management/commands/termine_scolarite.py @@ -4,6 +4,7 @@ from avisstage.models import Normalien from django.utils import timezone from datetime import timedelta + class Command(BaseCommand): help = 'Réinitialise les statuts "en scolarité" de tout le monde' @@ -13,4 +14,4 @@ class Command(BaseCommand): def handle(self, *args, **options): old_conn = timezone.now() - timedelta(days=365) Normalien.objects.all().update(last_cas_connect=t) - self.stdout.write(self.style.SUCCESS(u'Terminé')) + self.stdout.write(self.style.SUCCESS(u"Terminé")) diff --git a/avisstage/migrations/0001_initial.py b/avisstage/migrations/0001_initial.py index a4325e1..a083c8d 100644 --- a/avisstage/migrations/0001_initial.py +++ b/avisstage/migrations/0001_initial.py @@ -16,127 +16,663 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('taggit', '0002_auto_20150616_2121'), + ("taggit", "0002_auto_20150616_2121"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='AvisLieu', + name="AvisLieu", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('order', models.IntegerField(default=0, verbose_name='Ordre')), - ('chapo', models.TextField(blank=True, verbose_name='En quelques mots')), - ('avis_lieustage', tinymce.models.HTMLField(blank=True, verbose_name='Les lieux de travail')), - ('avis_pratique', tinymce.models.HTMLField(blank=True, verbose_name="S'installer - conseils pratiques")), - ('avis_tourisme', tinymce.models.HTMLField(blank=True, verbose_name='Dans les parages')), - ('les_plus', models.TextField(blank=True, verbose_name='Les plus du lieu')), - ('les_moins', models.TextField(blank=True, verbose_name='Les moins du lieu')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("order", models.IntegerField(default=0, verbose_name="Ordre")), + ( + "chapo", + models.TextField(blank=True, verbose_name="En quelques mots"), + ), + ( + "avis_lieustage", + tinymce.models.HTMLField( + blank=True, verbose_name="Les lieux de travail" + ), + ), + ( + "avis_pratique", + tinymce.models.HTMLField( + blank=True, verbose_name="S'installer - conseils pratiques" + ), + ), + ( + "avis_tourisme", + tinymce.models.HTMLField( + blank=True, verbose_name="Dans les parages" + ), + ), + ( + "les_plus", + models.TextField(blank=True, verbose_name="Les plus du lieu"), + ), + ( + "les_moins", + models.TextField(blank=True, verbose_name="Les moins du lieu"), + ), ], options={ - 'verbose_name': 'Avis sur un lieu de stage', - 'verbose_name_plural': 'Avis sur un lieu de stage', + "verbose_name": "Avis sur un lieu de stage", + "verbose_name_plural": "Avis sur un lieu de stage", }, ), migrations.CreateModel( - name='AvisStage', + name="AvisStage", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('chapo', models.TextField(blank=True, verbose_name='En quelques mots')), - ('avis_ambiance', tinymce.models.HTMLField(blank=True, verbose_name="L'ambiance de travail")), - ('avis_sujet', tinymce.models.HTMLField(blank=True, verbose_name='La mission')), - ('avis_admin', tinymce.models.HTMLField(blank=True, verbose_name='Formalit\xe9s et administration')), - ('les_plus', models.TextField(blank=True, verbose_name='Les plus de cette exp\xe9rience')), - ('les_moins', models.TextField(blank=True, verbose_name='Les moins de cette exp\xe9rience')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "chapo", + models.TextField(blank=True, verbose_name="En quelques mots"), + ), + ( + "avis_ambiance", + tinymce.models.HTMLField( + blank=True, verbose_name="L'ambiance de travail" + ), + ), + ( + "avis_sujet", + tinymce.models.HTMLField(blank=True, verbose_name="La mission"), + ), + ( + "avis_admin", + tinymce.models.HTMLField( + blank=True, verbose_name="Formalit\xe9s et administration" + ), + ), + ( + "les_plus", + models.TextField( + blank=True, verbose_name="Les plus de cette exp\xe9rience" + ), + ), + ( + "les_moins", + models.TextField( + blank=True, verbose_name="Les moins de cette exp\xe9rience" + ), + ), ], ), migrations.CreateModel( - name='Lieu', + name="Lieu", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('nom', models.CharField(max_length=250, verbose_name="Nom de l'institution d'accueil")), - ('type_lieu', models.CharField(choices=[(b'universite', 'Universit\xe9'), (b'entreprise', 'Entreprise'), (b'centrerecherche', 'Centre de recherche'), (b'administration', 'Administration'), (b'autre', 'Autre')], default='universite', max_length=15, verbose_name="Type de structure d'accueil")), - ('ville', models.CharField(max_length=200, verbose_name='Ville')), - ('pays', models.CharField(choices=[(b'AF', 'Afghanistan'), (b'AL', 'Albanie'), (b'AQ', 'Antarctique'), (b'DZ', 'Alg\xe9rie'), (b'AS', 'Samoa Am\xe9ricaines'), (b'AD', 'Andorre'), (b'AO', 'Angola'), (b'AG', 'Antigua-et-Barbuda'), (b'AZ', 'Azerba\xefdjan'), (b'AR', 'Argentine'), (b'AU', 'Australie'), (b'AT', 'Autriche'), (b'BS', 'Bahamas'), (b'BH', 'Bahre\xefn'), (b'BD', 'Bangladesh'), (b'AM', 'Arm\xe9nie'), (b'BB', 'Barbade'), (b'BE', 'Belgique'), (b'BM', 'Bermudes'), (b'BT', 'Bhoutan'), (b'BO', 'Bolivie'), (b'BA', 'Bosnie-Herz\xe9govine'), (b'BW', 'Botswana'), (b'BV', '\xcele Bouvet'), (b'BR', 'Br\xe9sil'), (b'BZ', 'Belize'), (b'IO', "Territoire Britannique de l'Oc\xe9an Indien"), (b'SB', '\xceles Salomon'), (b'VG', '\xceles Vierges Britanniques'), (b'BN', 'Brun\xe9i Darussalam'), (b'BG', 'Bulgarie'), (b'MM', 'Myanmar'), (b'BI', 'Burundi'), (b'BY', 'B\xe9larus'), (b'KH', 'Cambodge'), (b'CM', 'Cameroun'), (b'CA', 'Canada'), (b'CV', 'Cap-vert'), (b'KY', '\xceles Ca\xefmanes'), (b'CF', 'R\xe9publique Centrafricaine'), (b'LK', 'Sri Lanka'), (b'TD', 'Tchad'), (b'CL', 'Chili'), (b'CN', 'Chine'), (b'TW', 'Ta\xefwan'), (b'CX', '\xcele Christmas'), (b'CC', '\xceles Cocos (Keeling)'), (b'CO', 'Colombie'), (b'KM', 'Comores'), (b'YT', 'Mayotte'), (b'CG', 'R\xe9publique du Congo'), (b'CD', 'R\xe9publique D\xe9mocratique du Congo'), (b'CK', '\xceles Cook'), (b'CR', 'Costa Rica'), (b'HR', 'Croatie'), (b'CU', 'Cuba'), (b'CY', 'Chypre'), (b'CZ', 'R\xe9publique Tch\xe8que'), (b'BJ', 'B\xe9nin'), (b'DK', 'Danemark'), (b'DM', 'Dominique'), (b'DO', 'R\xe9publique Dominicaine'), (b'EC', '\xc9quateur'), (b'SV', 'El Salvador'), (b'GQ', 'Guin\xe9e \xc9quatoriale'), (b'ET', '\xc9thiopie'), (b'ER', '\xc9rythr\xe9e'), (b'EE', 'Estonie'), (b'FO', '\xceles F\xe9ro\xe9'), (b'FK', '\xceles (malvinas) Falkland'), (b'GS', 'G\xe9orgie du Sud et les \xceles Sandwich du Sud'), (b'FJ', 'Fidji'), (b'FI', 'Finlande'), (b'AX', '\xceles \xc5land'), (b'FR', 'France'), (b'GF', 'Guyane Fran\xe7aise'), (b'PF', 'Polyn\xe9sie Fran\xe7aise'), (b'TF', 'Terres Australes Fran\xe7aises'), (b'DJ', 'Djibouti'), (b'GA', 'Gabon'), (b'GE', 'G\xe9orgie'), (b'GM', 'Gambie'), (b'PS', 'Territoire Palestinien Occup\xe9'), (b'DE', 'Allemagne'), (b'GH', 'Ghana'), (b'GI', 'Gibraltar'), (b'KI', 'Kiribati'), (b'GR', 'Gr\xe8ce'), (b'GL', 'Groenland'), (b'GD', 'Grenade'), (b'GP', 'Guadeloupe'), (b'GU', 'Guam'), (b'GT', 'Guatemala'), (b'GN', 'Guin\xe9e'), (b'GY', 'Guyana'), (b'HT', 'Ha\xefti'), (b'HM', '\xceles Heard et Mcdonald'), (b'VA', 'Saint-Si\xe8ge (\xe9tat de la Cit\xe9 du Vatican)'), (b'HN', 'Honduras'), (b'HK', 'Hong-Kong'), (b'HU', 'Hongrie'), (b'IS', 'Islande'), (b'IN', 'Inde'), (b'ID', 'Indon\xe9sie'), (b'IR', "R\xe9publique Islamique d'Iran"), (b'IQ', 'Iraq'), (b'IE', 'Irlande'), (b'IL', 'Isra\xebl'), (b'IT', 'Italie'), (b'CI', "C\xf4te d'Ivoire"), (b'JM', 'Jama\xefque'), (b'JP', 'Japon'), (b'KZ', 'Kazakhstan'), (b'JO', 'Jordanie'), (b'KE', 'Kenya'), (b'KP', 'R\xe9publique Populaire D\xe9mocratique de Cor\xe9e'), (b'KR', 'R\xe9publique de Cor\xe9e'), (b'KW', 'Kowe\xeft'), (b'KG', 'Kirghizistan'), (b'LA', 'R\xe9publique D\xe9mocratique Populaire Lao'), (b'LB', 'Liban'), (b'LS', 'Lesotho'), (b'LV', 'Lettonie'), (b'LR', 'Lib\xe9ria'), (b'LY', 'Jamahiriya Arabe Libyenne'), (b'LI', 'Liechtenstein'), (b'LT', 'Lituanie'), (b'LU', 'Luxembourg'), (b'MO', 'Macao'), (b'MG', 'Madagascar'), (b'MW', 'Malawi'), (b'MY', 'Malaisie'), (b'MV', 'Maldives'), (b'ML', 'Mali'), (b'MT', 'Malte'), (b'MQ', 'Martinique'), (b'MR', 'Mauritanie'), (b'MU', 'Maurice'), (b'MX', 'Mexique'), (b'MC', 'Monaco'), (b'MN', 'Mongolie'), (b'MD', 'R\xe9publique de Moldova'), (b'MS', 'Montserrat'), (b'MA', 'Maroc'), (b'MZ', 'Mozambique'), (b'OM', 'Oman'), (b'NA', 'Namibie'), (b'NR', 'Nauru'), (b'NP', 'N\xe9pal'), (b'NL', 'Pays-Bas'), (b'AN', 'Antilles N\xe9erlandaises'), (b'AW', 'Aruba'), (b'NC', 'Nouvelle-Cal\xe9donie'), (b'VU', 'Vanuatu'), (b'NZ', 'Nouvelle-Z\xe9lande'), (b'NI', 'Nicaragua'), (b'NE', 'Niger'), (b'NG', 'Nig\xe9ria'), (b'NU', 'Niu\xe9'), (b'NF', '\xcele Norfolk'), (b'NO', 'Norv\xe8ge'), (b'MP', '\xceles Mariannes du Nord'), (b'UM', '\xceles Mineures \xc9loign\xe9es des \xc9tats-Unis'), (b'FM', '\xc9tats F\xe9d\xe9r\xe9s de Micron\xe9sie'), (b'MH', '\xceles Marshall'), (b'PW', 'Palaos'), (b'PK', 'Pakistan'), (b'PA', 'Panama'), (b'PG', 'Papouasie-Nouvelle-Guin\xe9e'), (b'PY', 'Paraguay'), (b'PE', 'P\xe9rou'), (b'PH', 'Philippines'), (b'PN', 'Pitcairn'), (b'PL', 'Pologne'), (b'PT', 'Portugal'), (b'GW', 'Guin\xe9e-Bissau'), (b'TL', 'Timor-Leste'), (b'PR', 'Porto Rico'), (b'QA', 'Qatar'), (b'RE', 'R\xe9union'), (b'RO', 'Roumanie'), (b'RU', 'F\xe9d\xe9ration de Russie'), (b'RW', 'Rwanda'), (b'SH', 'Sainte-H\xe9l\xe8ne'), (b'KN', 'Saint-Kitts-et-Nevis'), (b'AI', 'Anguilla'), (b'LC', 'Sainte-Lucie'), (b'PM', 'Saint-Pierre-et-Miquelon'), (b'VC', 'Saint-Vincent-et-les Grenadines'), (b'SM', 'Saint-Marin'), (b'ST', 'Sao Tom\xe9-et-Principe'), (b'SA', 'Arabie Saoudite'), (b'SN', 'S\xe9n\xe9gal'), (b'SC', 'Seychelles'), (b'SL', 'Sierra Leone'), (b'SG', 'Singapour'), (b'SK', 'Slovaquie'), (b'VN', 'Viet Nam'), (b'SI', 'Slov\xe9nie'), (b'SO', 'Somalie'), (b'ZA', 'Afrique du Sud'), (b'ZW', 'Zimbabwe'), (b'ES', 'Espagne'), (b'EH', 'Sahara Occidental'), (b'SD', 'Soudan'), (b'SR', 'Suriname'), (b'SJ', 'Svalbard et\xcele Jan Mayen'), (b'SZ', 'Swaziland'), (b'SE', 'Su\xe8de'), (b'CH', 'Suisse'), (b'SY', 'R\xe9publique Arabe Syrienne'), (b'TJ', 'Tadjikistan'), (b'TH', 'Tha\xeflande'), (b'TG', 'Togo'), (b'TK', 'Tokelau'), (b'TO', 'Tonga'), (b'TT', 'Trinit\xe9-et-Tobago'), (b'AE', '\xc9mirats Arabes Unis'), (b'TN', 'Tunisie'), (b'TR', 'Turquie'), (b'TM', 'Turkm\xe9nistan'), (b'TC', '\xceles Turks et Ca\xefques'), (b'TV', 'Tuvalu'), (b'UG', 'Ouganda'), (b'UA', 'Ukraine'), (b'MK', "L'ex-R\xe9publique Yougoslave de Mac\xe9doine"), (b'EG', '\xc9gypte'), (b'GB', 'Royaume-Uni'), (b'IM', '\xcele de Man'), (b'TZ', 'R\xe9publique-Unie de Tanzanie'), (b'US', '\xc9tats-Unis'), (b'VI', '\xceles Vierges des \xc9tats-Unis'), (b'BF', 'Burkina Faso'), (b'UY', 'Uruguay'), (b'UZ', 'Ouzb\xe9kistan'), (b'VE', 'Venezuela'), (b'WF', 'Wallis et Futuna'), (b'WS', 'Samoa'), (b'YE', 'Y\xe9men'), (b'CS', 'Serbie-et-Mont\xe9n\xe9gro'), (b'ZM', 'Zambie')], max_length=2, verbose_name='Pays')), - ('coord', django.contrib.gis.db.models.fields.PointField(geography=True, srid=4326, verbose_name='Coordonn\xe9es')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "nom", + models.CharField( + max_length=250, verbose_name="Nom de l'institution d'accueil" + ), + ), + ( + "type_lieu", + models.CharField( + choices=[ + (b"universite", "Universit\xe9"), + (b"entreprise", "Entreprise"), + (b"centrerecherche", "Centre de recherche"), + (b"administration", "Administration"), + (b"autre", "Autre"), + ], + default="universite", + max_length=15, + verbose_name="Type de structure d'accueil", + ), + ), + ("ville", models.CharField(max_length=200, verbose_name="Ville")), + ( + "pays", + models.CharField( + choices=[ + (b"AF", "Afghanistan"), + (b"AL", "Albanie"), + (b"AQ", "Antarctique"), + (b"DZ", "Alg\xe9rie"), + (b"AS", "Samoa Am\xe9ricaines"), + (b"AD", "Andorre"), + (b"AO", "Angola"), + (b"AG", "Antigua-et-Barbuda"), + (b"AZ", "Azerba\xefdjan"), + (b"AR", "Argentine"), + (b"AU", "Australie"), + (b"AT", "Autriche"), + (b"BS", "Bahamas"), + (b"BH", "Bahre\xefn"), + (b"BD", "Bangladesh"), + (b"AM", "Arm\xe9nie"), + (b"BB", "Barbade"), + (b"BE", "Belgique"), + (b"BM", "Bermudes"), + (b"BT", "Bhoutan"), + (b"BO", "Bolivie"), + (b"BA", "Bosnie-Herz\xe9govine"), + (b"BW", "Botswana"), + (b"BV", "\xcele Bouvet"), + (b"BR", "Br\xe9sil"), + (b"BZ", "Belize"), + (b"IO", "Territoire Britannique de l'Oc\xe9an Indien"), + (b"SB", "\xceles Salomon"), + (b"VG", "\xceles Vierges Britanniques"), + (b"BN", "Brun\xe9i Darussalam"), + (b"BG", "Bulgarie"), + (b"MM", "Myanmar"), + (b"BI", "Burundi"), + (b"BY", "B\xe9larus"), + (b"KH", "Cambodge"), + (b"CM", "Cameroun"), + (b"CA", "Canada"), + (b"CV", "Cap-vert"), + (b"KY", "\xceles Ca\xefmanes"), + (b"CF", "R\xe9publique Centrafricaine"), + (b"LK", "Sri Lanka"), + (b"TD", "Tchad"), + (b"CL", "Chili"), + (b"CN", "Chine"), + (b"TW", "Ta\xefwan"), + (b"CX", "\xcele Christmas"), + (b"CC", "\xceles Cocos (Keeling)"), + (b"CO", "Colombie"), + (b"KM", "Comores"), + (b"YT", "Mayotte"), + (b"CG", "R\xe9publique du Congo"), + (b"CD", "R\xe9publique D\xe9mocratique du Congo"), + (b"CK", "\xceles Cook"), + (b"CR", "Costa Rica"), + (b"HR", "Croatie"), + (b"CU", "Cuba"), + (b"CY", "Chypre"), + (b"CZ", "R\xe9publique Tch\xe8que"), + (b"BJ", "B\xe9nin"), + (b"DK", "Danemark"), + (b"DM", "Dominique"), + (b"DO", "R\xe9publique Dominicaine"), + (b"EC", "\xc9quateur"), + (b"SV", "El Salvador"), + (b"GQ", "Guin\xe9e \xc9quatoriale"), + (b"ET", "\xc9thiopie"), + (b"ER", "\xc9rythr\xe9e"), + (b"EE", "Estonie"), + (b"FO", "\xceles F\xe9ro\xe9"), + (b"FK", "\xceles (malvinas) Falkland"), + (b"GS", "G\xe9orgie du Sud et les \xceles Sandwich du Sud"), + (b"FJ", "Fidji"), + (b"FI", "Finlande"), + (b"AX", "\xceles \xc5land"), + (b"FR", "France"), + (b"GF", "Guyane Fran\xe7aise"), + (b"PF", "Polyn\xe9sie Fran\xe7aise"), + (b"TF", "Terres Australes Fran\xe7aises"), + (b"DJ", "Djibouti"), + (b"GA", "Gabon"), + (b"GE", "G\xe9orgie"), + (b"GM", "Gambie"), + (b"PS", "Territoire Palestinien Occup\xe9"), + (b"DE", "Allemagne"), + (b"GH", "Ghana"), + (b"GI", "Gibraltar"), + (b"KI", "Kiribati"), + (b"GR", "Gr\xe8ce"), + (b"GL", "Groenland"), + (b"GD", "Grenade"), + (b"GP", "Guadeloupe"), + (b"GU", "Guam"), + (b"GT", "Guatemala"), + (b"GN", "Guin\xe9e"), + (b"GY", "Guyana"), + (b"HT", "Ha\xefti"), + (b"HM", "\xceles Heard et Mcdonald"), + ( + b"VA", + "Saint-Si\xe8ge (\xe9tat de la Cit\xe9 du Vatican)", + ), + (b"HN", "Honduras"), + (b"HK", "Hong-Kong"), + (b"HU", "Hongrie"), + (b"IS", "Islande"), + (b"IN", "Inde"), + (b"ID", "Indon\xe9sie"), + (b"IR", "R\xe9publique Islamique d'Iran"), + (b"IQ", "Iraq"), + (b"IE", "Irlande"), + (b"IL", "Isra\xebl"), + (b"IT", "Italie"), + (b"CI", "C\xf4te d'Ivoire"), + (b"JM", "Jama\xefque"), + (b"JP", "Japon"), + (b"KZ", "Kazakhstan"), + (b"JO", "Jordanie"), + (b"KE", "Kenya"), + ( + b"KP", + "R\xe9publique Populaire D\xe9mocratique de Cor\xe9e", + ), + (b"KR", "R\xe9publique de Cor\xe9e"), + (b"KW", "Kowe\xeft"), + (b"KG", "Kirghizistan"), + (b"LA", "R\xe9publique D\xe9mocratique Populaire Lao"), + (b"LB", "Liban"), + (b"LS", "Lesotho"), + (b"LV", "Lettonie"), + (b"LR", "Lib\xe9ria"), + (b"LY", "Jamahiriya Arabe Libyenne"), + (b"LI", "Liechtenstein"), + (b"LT", "Lituanie"), + (b"LU", "Luxembourg"), + (b"MO", "Macao"), + (b"MG", "Madagascar"), + (b"MW", "Malawi"), + (b"MY", "Malaisie"), + (b"MV", "Maldives"), + (b"ML", "Mali"), + (b"MT", "Malte"), + (b"MQ", "Martinique"), + (b"MR", "Mauritanie"), + (b"MU", "Maurice"), + (b"MX", "Mexique"), + (b"MC", "Monaco"), + (b"MN", "Mongolie"), + (b"MD", "R\xe9publique de Moldova"), + (b"MS", "Montserrat"), + (b"MA", "Maroc"), + (b"MZ", "Mozambique"), + (b"OM", "Oman"), + (b"NA", "Namibie"), + (b"NR", "Nauru"), + (b"NP", "N\xe9pal"), + (b"NL", "Pays-Bas"), + (b"AN", "Antilles N\xe9erlandaises"), + (b"AW", "Aruba"), + (b"NC", "Nouvelle-Cal\xe9donie"), + (b"VU", "Vanuatu"), + (b"NZ", "Nouvelle-Z\xe9lande"), + (b"NI", "Nicaragua"), + (b"NE", "Niger"), + (b"NG", "Nig\xe9ria"), + (b"NU", "Niu\xe9"), + (b"NF", "\xcele Norfolk"), + (b"NO", "Norv\xe8ge"), + (b"MP", "\xceles Mariannes du Nord"), + ( + b"UM", + "\xceles Mineures \xc9loign\xe9es des \xc9tats-Unis", + ), + (b"FM", "\xc9tats F\xe9d\xe9r\xe9s de Micron\xe9sie"), + (b"MH", "\xceles Marshall"), + (b"PW", "Palaos"), + (b"PK", "Pakistan"), + (b"PA", "Panama"), + (b"PG", "Papouasie-Nouvelle-Guin\xe9e"), + (b"PY", "Paraguay"), + (b"PE", "P\xe9rou"), + (b"PH", "Philippines"), + (b"PN", "Pitcairn"), + (b"PL", "Pologne"), + (b"PT", "Portugal"), + (b"GW", "Guin\xe9e-Bissau"), + (b"TL", "Timor-Leste"), + (b"PR", "Porto Rico"), + (b"QA", "Qatar"), + (b"RE", "R\xe9union"), + (b"RO", "Roumanie"), + (b"RU", "F\xe9d\xe9ration de Russie"), + (b"RW", "Rwanda"), + (b"SH", "Sainte-H\xe9l\xe8ne"), + (b"KN", "Saint-Kitts-et-Nevis"), + (b"AI", "Anguilla"), + (b"LC", "Sainte-Lucie"), + (b"PM", "Saint-Pierre-et-Miquelon"), + (b"VC", "Saint-Vincent-et-les Grenadines"), + (b"SM", "Saint-Marin"), + (b"ST", "Sao Tom\xe9-et-Principe"), + (b"SA", "Arabie Saoudite"), + (b"SN", "S\xe9n\xe9gal"), + (b"SC", "Seychelles"), + (b"SL", "Sierra Leone"), + (b"SG", "Singapour"), + (b"SK", "Slovaquie"), + (b"VN", "Viet Nam"), + (b"SI", "Slov\xe9nie"), + (b"SO", "Somalie"), + (b"ZA", "Afrique du Sud"), + (b"ZW", "Zimbabwe"), + (b"ES", "Espagne"), + (b"EH", "Sahara Occidental"), + (b"SD", "Soudan"), + (b"SR", "Suriname"), + (b"SJ", "Svalbard et\xcele Jan Mayen"), + (b"SZ", "Swaziland"), + (b"SE", "Su\xe8de"), + (b"CH", "Suisse"), + (b"SY", "R\xe9publique Arabe Syrienne"), + (b"TJ", "Tadjikistan"), + (b"TH", "Tha\xeflande"), + (b"TG", "Togo"), + (b"TK", "Tokelau"), + (b"TO", "Tonga"), + (b"TT", "Trinit\xe9-et-Tobago"), + (b"AE", "\xc9mirats Arabes Unis"), + (b"TN", "Tunisie"), + (b"TR", "Turquie"), + (b"TM", "Turkm\xe9nistan"), + (b"TC", "\xceles Turks et Ca\xefques"), + (b"TV", "Tuvalu"), + (b"UG", "Ouganda"), + (b"UA", "Ukraine"), + (b"MK", "L'ex-R\xe9publique Yougoslave de Mac\xe9doine"), + (b"EG", "\xc9gypte"), + (b"GB", "Royaume-Uni"), + (b"IM", "\xcele de Man"), + (b"TZ", "R\xe9publique-Unie de Tanzanie"), + (b"US", "\xc9tats-Unis"), + (b"VI", "\xceles Vierges des \xc9tats-Unis"), + (b"BF", "Burkina Faso"), + (b"UY", "Uruguay"), + (b"UZ", "Ouzb\xe9kistan"), + (b"VE", "Venezuela"), + (b"WF", "Wallis et Futuna"), + (b"WS", "Samoa"), + (b"YE", "Y\xe9men"), + (b"CS", "Serbie-et-Mont\xe9n\xe9gro"), + (b"ZM", "Zambie"), + ], + max_length=2, + verbose_name="Pays", + ), + ), + ( + "coord", + django.contrib.gis.db.models.fields.PointField( + geography=True, srid=4326, verbose_name="Coordonn\xe9es" + ), + ), ], options={ - 'verbose_name': 'Lieu', - 'verbose_name_plural': 'Lieux', + "verbose_name": "Lieu", + "verbose_name_plural": "Lieux", }, ), migrations.CreateModel( - name='Normalien', + name="Normalien", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('nom', models.CharField(blank=True, max_length=255, verbose_name='Nom complet')), - ('promotion', models.CharField(blank=True, max_length=40, verbose_name='Promotion')), - ('mail', models.EmailField(blank=True, max_length=200, verbose_name='Adresse e-mail permanente')), - ('contactez_moi', models.BooleanField(default=True, verbose_name='Inviter les visiteurs \xe0 me contacter')), - ('bio', models.TextField(blank=True, default='', verbose_name='\xc0 propos de moi')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profil', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "nom", + models.CharField( + blank=True, max_length=255, verbose_name="Nom complet" + ), + ), + ( + "promotion", + models.CharField( + blank=True, max_length=40, verbose_name="Promotion" + ), + ), + ( + "mail", + models.EmailField( + blank=True, + max_length=200, + verbose_name="Adresse e-mail permanente", + ), + ), + ( + "contactez_moi", + models.BooleanField( + default=True, + verbose_name="Inviter les visiteurs \xe0 me contacter", + ), + ), + ( + "bio", + models.TextField( + blank=True, default="", verbose_name="\xc0 propos de moi" + ), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="profil", + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'verbose_name': 'Profil \xe9l\xe8ve', - 'verbose_name_plural': 'Profils \xe9l\xe8ves', + "verbose_name": "Profil \xe9l\xe8ve", + "verbose_name_plural": "Profils \xe9l\xe8ves", }, ), migrations.CreateModel( - name='Stage', + name="Stage", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('public', models.BooleanField(default=False, verbose_name='Visible publiquement')), - ('date_creation', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Cr\xe9\xe9 le')), - ('date_maj', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Mis \xe0 jour le')), - ('sujet', models.CharField(max_length=500, verbose_name='Sujet')), - ('date_debut', models.DateField(null=True, verbose_name='Date de d\xe9but')), - ('date_fin', models.DateField(null=True, verbose_name='Date de fin')), - ('type_stage', models.CharField(choices=[('Recherche :', ((b'recherche', 'Stage acad\xe9mique'), (b'recherche_autre', 'Stage non-acad\xe9mique'), (b'sejour_dri', 'S\xe9jour de recherche DRI'))), ('Stage sans vis\xe9e de recherche :', ((b'pro', 'Stage en entreprise'), (b'admin', 'Stage en admin./ONG/orga. internationale'))), ('Enseignement :', ((b'lectorat', 'Lectorat DRI'), (b'autre_teach', "Autre exp\xe9rience d'enseignement"))), (b'autre', 'Autre')], default='stage', max_length=31, verbose_name='Type')), - ('niveau_scol', models.CharField(blank=True, choices=[(b'L3', 'Licence 3'), (b'M1', 'Master 1'), (b'M2', 'Master 2'), (b'DOC', 'Pr\xe9-doctorat'), (b'CST', 'C\xe9sure'), (b'BLA', 'Ann\xe9e blanche'), (b'VAC', 'Vacances scolaires'), (b'MIT', 'Mi-temps en parall\xe8le des \xe9tudes'), (b'', 'Autre')], default='', max_length=3, verbose_name='Ann\xe9e de scolarit\xe9')), - ('encadrants', models.CharField(blank=True, max_length=500, verbose_name='Encadrant\u22c5e\u22c5s')), - ('structure', models.CharField(blank=True, max_length=500, verbose_name="Structure d'accueil")), - ('auteur', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stages', to='avisstage.Normalien')), - ('lieux', models.ManyToManyField(blank=True, related_name='stages', through='avisstage.AvisLieu', to='avisstage.Lieu')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "public", + models.BooleanField( + default=False, verbose_name="Visible publiquement" + ), + ), + ( + "date_creation", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="Cr\xe9\xe9 le" + ), + ), + ( + "date_maj", + models.DateTimeField( + default=django.utils.timezone.now, + verbose_name="Mis \xe0 jour le", + ), + ), + ("sujet", models.CharField(max_length=500, verbose_name="Sujet")), + ( + "date_debut", + models.DateField(null=True, verbose_name="Date de d\xe9but"), + ), + ("date_fin", models.DateField(null=True, verbose_name="Date de fin")), + ( + "type_stage", + models.CharField( + choices=[ + ( + "Recherche :", + ( + (b"recherche", "Stage acad\xe9mique"), + (b"recherche_autre", "Stage non-acad\xe9mique"), + (b"sejour_dri", "S\xe9jour de recherche DRI"), + ), + ), + ( + "Stage sans vis\xe9e de recherche :", + ( + (b"pro", "Stage en entreprise"), + ( + b"admin", + "Stage en admin./ONG/orga. internationale", + ), + ), + ), + ( + "Enseignement :", + ( + (b"lectorat", "Lectorat DRI"), + ( + b"autre_teach", + "Autre exp\xe9rience d'enseignement", + ), + ), + ), + (b"autre", "Autre"), + ], + default="stage", + max_length=31, + verbose_name="Type", + ), + ), + ( + "niveau_scol", + models.CharField( + blank=True, + choices=[ + (b"L3", "Licence 3"), + (b"M1", "Master 1"), + (b"M2", "Master 2"), + (b"DOC", "Pr\xe9-doctorat"), + (b"CST", "C\xe9sure"), + (b"BLA", "Ann\xe9e blanche"), + (b"VAC", "Vacances scolaires"), + (b"MIT", "Mi-temps en parall\xe8le des \xe9tudes"), + (b"", "Autre"), + ], + default="", + max_length=3, + verbose_name="Ann\xe9e de scolarit\xe9", + ), + ), + ( + "encadrants", + models.CharField( + blank=True, + max_length=500, + verbose_name="Encadrant\u22c5e\u22c5s", + ), + ), + ( + "structure", + models.CharField( + blank=True, max_length=500, verbose_name="Structure d'accueil" + ), + ), + ( + "auteur", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="stages", + to="avisstage.Normalien", + ), + ), + ( + "lieux", + models.ManyToManyField( + blank=True, + related_name="stages", + through="avisstage.AvisLieu", + to="avisstage.Lieu", + ), + ), ], options={ - 'verbose_name': 'Stage', + "verbose_name": "Stage", }, ), migrations.CreateModel( - name='StageMatiere', + name="StageMatiere", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('nom', models.CharField(max_length=30, verbose_name='Nom')), - ('slug', models.SlugField()), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("nom", models.CharField(max_length=30, verbose_name="Nom")), + ("slug", models.SlugField()), ], options={ - 'verbose_name': 'Mati\xe8re des stages', - 'verbose_name_plural': 'Mati\xe8res des stages', + "verbose_name": "Mati\xe8re des stages", + "verbose_name_plural": "Mati\xe8res des stages", }, ), migrations.AddField( - model_name='stage', - name='matieres', - field=models.ManyToManyField(related_name='stages', to='avisstage.StageMatiere', verbose_name='Mati\xe8re(s)'), + model_name="stage", + name="matieres", + field=models.ManyToManyField( + related_name="stages", + to="avisstage.StageMatiere", + verbose_name="Mati\xe8re(s)", + ), ), migrations.AddField( - model_name='stage', - name='thematiques', - field=taggit_autosuggest.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Th\xe9matiques'), + model_name="stage", + name="thematiques", + field=taggit_autosuggest.managers.TaggableManager( + blank=True, + help_text="A comma-separated list of tags.", + through="taggit.TaggedItem", + to="taggit.Tag", + verbose_name="Th\xe9matiques", + ), ), migrations.AddField( - model_name='avisstage', - name='stage', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='avis_stage', to='avisstage.Stage'), + model_name="avisstage", + name="stage", + field=models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="avis_stage", + to="avisstage.Stage", + ), ), migrations.AddField( - model_name='avislieu', - name='lieu', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='avisstage.Lieu'), + model_name="avislieu", + name="lieu", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="avisstage.Lieu" + ), ), migrations.AddField( - model_name='avislieu', - name='stage', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='avisstage.Stage'), + model_name="avislieu", + name="stage", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="avisstage.Stage" + ), ), ] diff --git a/avisstage/migrations/0002_auto_20171002_2243.py b/avisstage/migrations/0002_auto_20171002_2243.py index 59c0ea4..bdfefce 100644 --- a/avisstage/migrations/0002_auto_20171002_2243.py +++ b/avisstage/migrations/0002_auto_20171002_2243.py @@ -9,23 +9,29 @@ import tinymce.models class Migration(migrations.Migration): dependencies = [ - ('avisstage', '0001_initial'), + ("avisstage", "0001_initial"), ] operations = [ migrations.AddField( - model_name='avisstage', - name='avis_prestage', - field=tinymce.models.HTMLField(blank=True, default='', verbose_name='Avant le stage'), + model_name="avisstage", + name="avis_prestage", + field=tinymce.models.HTMLField( + blank=True, default="", verbose_name="Avant le stage" + ), ), migrations.AddField( - model_name='stage', - name='len_avis_lieux', - field=models.IntegerField(default=0, verbose_name='Longueur des avis de lieu'), + model_name="stage", + name="len_avis_lieux", + field=models.IntegerField( + default=0, verbose_name="Longueur des avis de lieu" + ), ), migrations.AddField( - model_name='stage', - name='len_avis_stage', - field=models.IntegerField(default=0, verbose_name='Longueur des avis de stage'), + model_name="stage", + name="len_avis_stage", + field=models.IntegerField( + default=0, verbose_name="Longueur des avis de stage" + ), ), ] diff --git a/avisstage/migrations/0003_auto_20210117_1208.py b/avisstage/migrations/0003_auto_20210117_1208.py index 307760c..4d13ea9 100644 --- a/avisstage/migrations/0003_auto_20210117_1208.py +++ b/avisstage/migrations/0003_auto_20210117_1208.py @@ -8,38 +8,350 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('avisstage', '0002_auto_20171002_2243'), + ("avisstage", "0002_auto_20171002_2243"), ] operations = [ migrations.AlterField( - model_name='lieu', - name='pays', - field=models.CharField(choices=[('AF', 'Afghanistan'), ('AL', 'Albanie'), ('AQ', 'Antarctique'), ('DZ', 'Algérie'), ('AS', 'Samoa Américaines'), ('AD', 'Andorre'), ('AO', 'Angola'), ('AG', 'Antigua-et-Barbuda'), ('AZ', 'Azerbaïdjan'), ('AR', 'Argentine'), ('AU', 'Australie'), ('AT', 'Autriche'), ('BS', 'Bahamas'), ('BH', 'Bahreïn'), ('BD', 'Bangladesh'), ('AM', 'Arménie'), ('BB', 'Barbade'), ('BE', 'Belgique'), ('BM', 'Bermudes'), ('BT', 'Bhoutan'), ('BO', 'Bolivie'), ('BA', 'Bosnie-Herzégovine'), ('BW', 'Botswana'), ('BV', 'Île Bouvet'), ('BR', 'Brésil'), ('BZ', 'Belize'), ('IO', "Territoire Britannique de l'Océan Indien"), ('SB', 'Îles Salomon'), ('VG', 'Îles Vierges Britanniques'), ('BN', 'Brunéi Darussalam'), ('BG', 'Bulgarie'), ('MM', 'Myanmar'), ('BI', 'Burundi'), ('BY', 'Bélarus'), ('KH', 'Cambodge'), ('CM', 'Cameroun'), ('CA', 'Canada'), ('CV', 'Cap-vert'), ('KY', 'Îles Caïmanes'), ('CF', 'République Centrafricaine'), ('LK', 'Sri Lanka'), ('TD', 'Tchad'), ('CL', 'Chili'), ('CN', 'Chine'), ('TW', 'Taïwan'), ('CX', 'Île Christmas'), ('CC', 'Îles Cocos (Keeling)'), ('CO', 'Colombie'), ('KM', 'Comores'), ('YT', 'Mayotte'), ('CG', 'République du Congo'), ('CD', 'République Démocratique du Congo'), ('CK', 'Îles Cook'), ('CR', 'Costa Rica'), ('HR', 'Croatie'), ('CU', 'Cuba'), ('CY', 'Chypre'), ('CZ', 'République Tchèque'), ('BJ', 'Bénin'), ('DK', 'Danemark'), ('DM', 'Dominique'), ('DO', 'République Dominicaine'), ('EC', 'Équateur'), ('SV', 'El Salvador'), ('GQ', 'Guinée Équatoriale'), ('ET', 'Éthiopie'), ('ER', 'Érythrée'), ('EE', 'Estonie'), ('FO', 'Îles Féroé'), ('FK', 'Îles (malvinas) Falkland'), ('GS', 'Géorgie du Sud et les Îles Sandwich du Sud'), ('FJ', 'Fidji'), ('FI', 'Finlande'), ('AX', 'Îles Åland'), ('FR', 'France'), ('GF', 'Guyane Française'), ('PF', 'Polynésie Française'), ('TF', 'Terres Australes Françaises'), ('DJ', 'Djibouti'), ('GA', 'Gabon'), ('GE', 'Géorgie'), ('GM', 'Gambie'), ('PS', 'Territoire Palestinien Occupé'), ('DE', 'Allemagne'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('KI', 'Kiribati'), ('GR', 'Grèce'), ('GL', 'Groenland'), ('GD', 'Grenade'), ('GP', 'Guadeloupe'), ('GU', 'Guam'), ('GT', 'Guatemala'), ('GN', 'Guinée'), ('GY', 'Guyana'), ('HT', 'Haïti'), ('HM', 'Îles Heard et Mcdonald'), ('VA', 'Saint-Siège (état de la Cité du Vatican)'), ('HN', 'Honduras'), ('HK', 'Hong-Kong'), ('HU', 'Hongrie'), ('IS', 'Islande'), ('IN', 'Inde'), ('ID', 'Indonésie'), ('IR', "République Islamique d'Iran"), ('IQ', 'Iraq'), ('IE', 'Irlande'), ('IL', 'Israël'), ('IT', 'Italie'), ('CI', "Côte d'Ivoire"), ('JM', 'Jamaïque'), ('JP', 'Japon'), ('KZ', 'Kazakhstan'), ('JO', 'Jordanie'), ('KE', 'Kenya'), ('KP', 'République Populaire Démocratique de Corée'), ('KR', 'République de Corée'), ('KW', 'Koweït'), ('KG', 'Kirghizistan'), ('LA', 'République Démocratique Populaire Lao'), ('LB', 'Liban'), ('LS', 'Lesotho'), ('LV', 'Lettonie'), ('LR', 'Libéria'), ('LY', 'Jamahiriya Arabe Libyenne'), ('LI', 'Liechtenstein'), ('LT', 'Lituanie'), ('LU', 'Luxembourg'), ('MO', 'Macao'), ('MG', 'Madagascar'), ('MW', 'Malawi'), ('MY', 'Malaisie'), ('MV', 'Maldives'), ('ML', 'Mali'), ('MT', 'Malte'), ('MQ', 'Martinique'), ('MR', 'Mauritanie'), ('MU', 'Maurice'), ('MX', 'Mexique'), ('MC', 'Monaco'), ('MN', 'Mongolie'), ('MD', 'République de Moldova'), ('MS', 'Montserrat'), ('MA', 'Maroc'), ('MZ', 'Mozambique'), ('OM', 'Oman'), ('NA', 'Namibie'), ('NR', 'Nauru'), ('NP', 'Népal'), ('NL', 'Pays-Bas'), ('AN', 'Antilles Néerlandaises'), ('AW', 'Aruba'), ('NC', 'Nouvelle-Calédonie'), ('VU', 'Vanuatu'), ('NZ', 'Nouvelle-Zélande'), ('NI', 'Nicaragua'), ('NE', 'Niger'), ('NG', 'Nigéria'), ('NU', 'Niué'), ('NF', 'Île Norfolk'), ('NO', 'Norvège'), ('MP', 'Îles Mariannes du Nord'), ('UM', 'Îles Mineures Éloignées des États-Unis'), ('FM', 'États Fédérés de Micronésie'), ('MH', 'Îles Marshall'), ('PW', 'Palaos'), ('PK', 'Pakistan'), ('PA', 'Panama'), ('PG', 'Papouasie-Nouvelle-Guinée'), ('PY', 'Paraguay'), ('PE', 'Pérou'), ('PH', 'Philippines'), ('PN', 'Pitcairn'), ('PL', 'Pologne'), ('PT', 'Portugal'), ('GW', 'Guinée-Bissau'), ('TL', 'Timor-Leste'), ('PR', 'Porto Rico'), ('QA', 'Qatar'), ('RE', 'Réunion'), ('RO', 'Roumanie'), ('RU', 'Fédération de Russie'), ('RW', 'Rwanda'), ('SH', 'Sainte-Hélène'), ('KN', 'Saint-Kitts-et-Nevis'), ('AI', 'Anguilla'), ('LC', 'Sainte-Lucie'), ('PM', 'Saint-Pierre-et-Miquelon'), ('VC', 'Saint-Vincent-et-les Grenadines'), ('SM', 'Saint-Marin'), ('ST', 'Sao Tomé-et-Principe'), ('SA', 'Arabie Saoudite'), ('SN', 'Sénégal'), ('SC', 'Seychelles'), ('SL', 'Sierra Leone'), ('SG', 'Singapour'), ('SK', 'Slovaquie'), ('VN', 'Viet Nam'), ('SI', 'Slovénie'), ('SO', 'Somalie'), ('ZA', 'Afrique du Sud'), ('ZW', 'Zimbabwe'), ('ES', 'Espagne'), ('EH', 'Sahara Occidental'), ('SD', 'Soudan'), ('SR', 'Suriname'), ('SJ', 'Svalbard etÎle Jan Mayen'), ('SZ', 'Swaziland'), ('SE', 'Suède'), ('CH', 'Suisse'), ('SY', 'République Arabe Syrienne'), ('TJ', 'Tadjikistan'), ('TH', 'Thaïlande'), ('TG', 'Togo'), ('TK', 'Tokelau'), ('TO', 'Tonga'), ('TT', 'Trinité-et-Tobago'), ('AE', 'Émirats Arabes Unis'), ('TN', 'Tunisie'), ('TR', 'Turquie'), ('TM', 'Turkménistan'), ('TC', 'Îles Turks et Caïques'), ('TV', 'Tuvalu'), ('UG', 'Ouganda'), ('UA', 'Ukraine'), ('MK', "L'ex-République Yougoslave de Macédoine"), ('EG', 'Égypte'), ('GB', 'Royaume-Uni'), ('IM', 'Île de Man'), ('TZ', 'République-Unie de Tanzanie'), ('US', 'États-Unis'), ('VI', 'Îles Vierges des États-Unis'), ('BF', 'Burkina Faso'), ('UY', 'Uruguay'), ('UZ', 'Ouzbékistan'), ('VE', 'Venezuela'), ('WF', 'Wallis et Futuna'), ('WS', 'Samoa'), ('YE', 'Yémen'), ('CS', 'Serbie-et-Monténégro'), ('ZM', 'Zambie')], max_length=2, verbose_name='Pays'), + model_name="lieu", + name="pays", + field=models.CharField( + choices=[ + ("AF", "Afghanistan"), + ("AL", "Albanie"), + ("AQ", "Antarctique"), + ("DZ", "Algérie"), + ("AS", "Samoa Américaines"), + ("AD", "Andorre"), + ("AO", "Angola"), + ("AG", "Antigua-et-Barbuda"), + ("AZ", "Azerbaïdjan"), + ("AR", "Argentine"), + ("AU", "Australie"), + ("AT", "Autriche"), + ("BS", "Bahamas"), + ("BH", "Bahreïn"), + ("BD", "Bangladesh"), + ("AM", "Arménie"), + ("BB", "Barbade"), + ("BE", "Belgique"), + ("BM", "Bermudes"), + ("BT", "Bhoutan"), + ("BO", "Bolivie"), + ("BA", "Bosnie-Herzégovine"), + ("BW", "Botswana"), + ("BV", "Île Bouvet"), + ("BR", "Brésil"), + ("BZ", "Belize"), + ("IO", "Territoire Britannique de l'Océan Indien"), + ("SB", "Îles Salomon"), + ("VG", "Îles Vierges Britanniques"), + ("BN", "Brunéi Darussalam"), + ("BG", "Bulgarie"), + ("MM", "Myanmar"), + ("BI", "Burundi"), + ("BY", "Bélarus"), + ("KH", "Cambodge"), + ("CM", "Cameroun"), + ("CA", "Canada"), + ("CV", "Cap-vert"), + ("KY", "Îles Caïmanes"), + ("CF", "République Centrafricaine"), + ("LK", "Sri Lanka"), + ("TD", "Tchad"), + ("CL", "Chili"), + ("CN", "Chine"), + ("TW", "Taïwan"), + ("CX", "Île Christmas"), + ("CC", "Îles Cocos (Keeling)"), + ("CO", "Colombie"), + ("KM", "Comores"), + ("YT", "Mayotte"), + ("CG", "République du Congo"), + ("CD", "République Démocratique du Congo"), + ("CK", "Îles Cook"), + ("CR", "Costa Rica"), + ("HR", "Croatie"), + ("CU", "Cuba"), + ("CY", "Chypre"), + ("CZ", "République Tchèque"), + ("BJ", "Bénin"), + ("DK", "Danemark"), + ("DM", "Dominique"), + ("DO", "République Dominicaine"), + ("EC", "Équateur"), + ("SV", "El Salvador"), + ("GQ", "Guinée Équatoriale"), + ("ET", "Éthiopie"), + ("ER", "Érythrée"), + ("EE", "Estonie"), + ("FO", "Îles Féroé"), + ("FK", "Îles (malvinas) Falkland"), + ("GS", "Géorgie du Sud et les Îles Sandwich du Sud"), + ("FJ", "Fidji"), + ("FI", "Finlande"), + ("AX", "Îles Åland"), + ("FR", "France"), + ("GF", "Guyane Française"), + ("PF", "Polynésie Française"), + ("TF", "Terres Australes Françaises"), + ("DJ", "Djibouti"), + ("GA", "Gabon"), + ("GE", "Géorgie"), + ("GM", "Gambie"), + ("PS", "Territoire Palestinien Occupé"), + ("DE", "Allemagne"), + ("GH", "Ghana"), + ("GI", "Gibraltar"), + ("KI", "Kiribati"), + ("GR", "Grèce"), + ("GL", "Groenland"), + ("GD", "Grenade"), + ("GP", "Guadeloupe"), + ("GU", "Guam"), + ("GT", "Guatemala"), + ("GN", "Guinée"), + ("GY", "Guyana"), + ("HT", "Haïti"), + ("HM", "Îles Heard et Mcdonald"), + ("VA", "Saint-Siège (état de la Cité du Vatican)"), + ("HN", "Honduras"), + ("HK", "Hong-Kong"), + ("HU", "Hongrie"), + ("IS", "Islande"), + ("IN", "Inde"), + ("ID", "Indonésie"), + ("IR", "République Islamique d'Iran"), + ("IQ", "Iraq"), + ("IE", "Irlande"), + ("IL", "Israël"), + ("IT", "Italie"), + ("CI", "Côte d'Ivoire"), + ("JM", "Jamaïque"), + ("JP", "Japon"), + ("KZ", "Kazakhstan"), + ("JO", "Jordanie"), + ("KE", "Kenya"), + ("KP", "République Populaire Démocratique de Corée"), + ("KR", "République de Corée"), + ("KW", "Koweït"), + ("KG", "Kirghizistan"), + ("LA", "République Démocratique Populaire Lao"), + ("LB", "Liban"), + ("LS", "Lesotho"), + ("LV", "Lettonie"), + ("LR", "Libéria"), + ("LY", "Jamahiriya Arabe Libyenne"), + ("LI", "Liechtenstein"), + ("LT", "Lituanie"), + ("LU", "Luxembourg"), + ("MO", "Macao"), + ("MG", "Madagascar"), + ("MW", "Malawi"), + ("MY", "Malaisie"), + ("MV", "Maldives"), + ("ML", "Mali"), + ("MT", "Malte"), + ("MQ", "Martinique"), + ("MR", "Mauritanie"), + ("MU", "Maurice"), + ("MX", "Mexique"), + ("MC", "Monaco"), + ("MN", "Mongolie"), + ("MD", "République de Moldova"), + ("MS", "Montserrat"), + ("MA", "Maroc"), + ("MZ", "Mozambique"), + ("OM", "Oman"), + ("NA", "Namibie"), + ("NR", "Nauru"), + ("NP", "Népal"), + ("NL", "Pays-Bas"), + ("AN", "Antilles Néerlandaises"), + ("AW", "Aruba"), + ("NC", "Nouvelle-Calédonie"), + ("VU", "Vanuatu"), + ("NZ", "Nouvelle-Zélande"), + ("NI", "Nicaragua"), + ("NE", "Niger"), + ("NG", "Nigéria"), + ("NU", "Niué"), + ("NF", "Île Norfolk"), + ("NO", "Norvège"), + ("MP", "Îles Mariannes du Nord"), + ("UM", "Îles Mineures Éloignées des États-Unis"), + ("FM", "États Fédérés de Micronésie"), + ("MH", "Îles Marshall"), + ("PW", "Palaos"), + ("PK", "Pakistan"), + ("PA", "Panama"), + ("PG", "Papouasie-Nouvelle-Guinée"), + ("PY", "Paraguay"), + ("PE", "Pérou"), + ("PH", "Philippines"), + ("PN", "Pitcairn"), + ("PL", "Pologne"), + ("PT", "Portugal"), + ("GW", "Guinée-Bissau"), + ("TL", "Timor-Leste"), + ("PR", "Porto Rico"), + ("QA", "Qatar"), + ("RE", "Réunion"), + ("RO", "Roumanie"), + ("RU", "Fédération de Russie"), + ("RW", "Rwanda"), + ("SH", "Sainte-Hélène"), + ("KN", "Saint-Kitts-et-Nevis"), + ("AI", "Anguilla"), + ("LC", "Sainte-Lucie"), + ("PM", "Saint-Pierre-et-Miquelon"), + ("VC", "Saint-Vincent-et-les Grenadines"), + ("SM", "Saint-Marin"), + ("ST", "Sao Tomé-et-Principe"), + ("SA", "Arabie Saoudite"), + ("SN", "Sénégal"), + ("SC", "Seychelles"), + ("SL", "Sierra Leone"), + ("SG", "Singapour"), + ("SK", "Slovaquie"), + ("VN", "Viet Nam"), + ("SI", "Slovénie"), + ("SO", "Somalie"), + ("ZA", "Afrique du Sud"), + ("ZW", "Zimbabwe"), + ("ES", "Espagne"), + ("EH", "Sahara Occidental"), + ("SD", "Soudan"), + ("SR", "Suriname"), + ("SJ", "Svalbard etÎle Jan Mayen"), + ("SZ", "Swaziland"), + ("SE", "Suède"), + ("CH", "Suisse"), + ("SY", "République Arabe Syrienne"), + ("TJ", "Tadjikistan"), + ("TH", "Thaïlande"), + ("TG", "Togo"), + ("TK", "Tokelau"), + ("TO", "Tonga"), + ("TT", "Trinité-et-Tobago"), + ("AE", "Émirats Arabes Unis"), + ("TN", "Tunisie"), + ("TR", "Turquie"), + ("TM", "Turkménistan"), + ("TC", "Îles Turks et Caïques"), + ("TV", "Tuvalu"), + ("UG", "Ouganda"), + ("UA", "Ukraine"), + ("MK", "L'ex-République Yougoslave de Macédoine"), + ("EG", "Égypte"), + ("GB", "Royaume-Uni"), + ("IM", "Île de Man"), + ("TZ", "République-Unie de Tanzanie"), + ("US", "États-Unis"), + ("VI", "Îles Vierges des États-Unis"), + ("BF", "Burkina Faso"), + ("UY", "Uruguay"), + ("UZ", "Ouzbékistan"), + ("VE", "Venezuela"), + ("WF", "Wallis et Futuna"), + ("WS", "Samoa"), + ("YE", "Yémen"), + ("CS", "Serbie-et-Monténégro"), + ("ZM", "Zambie"), + ], + max_length=2, + verbose_name="Pays", + ), ), migrations.AlterField( - model_name='lieu', - name='type_lieu', - field=models.CharField(choices=[('universite', 'Université'), ('entreprise', 'Entreprise'), ('centrerecherche', 'Centre de recherche'), ('administration', 'Administration'), ('autre', 'Autre')], default='universite', max_length=15, verbose_name="Type de structure d'accueil"), + model_name="lieu", + name="type_lieu", + field=models.CharField( + choices=[ + ("universite", "Université"), + ("entreprise", "Entreprise"), + ("centrerecherche", "Centre de recherche"), + ("administration", "Administration"), + ("autre", "Autre"), + ], + default="universite", + max_length=15, + verbose_name="Type de structure d'accueil", + ), ), migrations.AlterField( - model_name='normalien', - name='user', - field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='profil', to=settings.AUTH_USER_MODEL), + model_name="normalien", + name="user", + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="profil", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='stage', - name='auteur', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stages', to='avisstage.Normalien'), + model_name="stage", + name="auteur", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="stages", + to="avisstage.Normalien", + ), ), migrations.AlterField( - model_name='stage', - name='niveau_scol', - field=models.CharField(blank=True, choices=[('L3', 'Licence 3'), ('M1', 'Master 1'), ('M2', 'Master 2'), ('DOC', 'Pré-doctorat'), ('CST', 'Césure'), ('BLA', 'Année blanche'), ('VAC', 'Vacances scolaires'), ('MIT', 'Mi-temps en parallèle des études'), ('', 'Autre')], default='', max_length=3, verbose_name='Année de scolarité'), + model_name="stage", + name="niveau_scol", + field=models.CharField( + blank=True, + choices=[ + ("L3", "Licence 3"), + ("M1", "Master 1"), + ("M2", "Master 2"), + ("DOC", "Pré-doctorat"), + ("CST", "Césure"), + ("BLA", "Année blanche"), + ("VAC", "Vacances scolaires"), + ("MIT", "Mi-temps en parallèle des études"), + ("", "Autre"), + ], + default="", + max_length=3, + verbose_name="Année de scolarité", + ), ), migrations.AlterField( - model_name='stage', - name='type_stage', - field=models.CharField(choices=[('Recherche :', (('recherche', 'Stage académique'), ('recherche_autre', 'Stage non-académique'), ('sejour_dri', 'Séjour de recherche DRI'))), ('Stage sans visée de recherche :', (('pro', 'Stage en entreprise'), ('admin', 'Stage en admin./ONG/orga. internationale'))), ('Enseignement :', (('lectorat', 'Lectorat DRI'), ('autre_teach', "Autre expérience d'enseignement"))), ('autre', 'Autre')], default='stage', max_length=31, verbose_name='Type'), + model_name="stage", + name="type_stage", + field=models.CharField( + choices=[ + ( + "Recherche :", + ( + ("recherche", "Stage académique"), + ("recherche_autre", "Stage non-académique"), + ("sejour_dri", "Séjour de recherche DRI"), + ), + ), + ( + "Stage sans visée de recherche :", + ( + ("pro", "Stage en entreprise"), + ("admin", "Stage en admin./ONG/orga. internationale"), + ), + ), + ( + "Enseignement :", + ( + ("lectorat", "Lectorat DRI"), + ("autre_teach", "Autre expérience d'enseignement"), + ), + ), + ("autre", "Autre"), + ], + default="stage", + max_length=31, + verbose_name="Type", + ), ), ] diff --git a/avisstage/migrations/0004_allauth_to_authens.py b/avisstage/migrations/0004_allauth_to_authens.py index 9de7655..29f8f8b 100644 --- a/avisstage/migrations/0004_allauth_to_authens.py +++ b/avisstage/migrations/0004_allauth_to_authens.py @@ -2,18 +2,19 @@ from django.apps import apps as global_apps from django.db import migrations from django.utils import timezone - + + def forwards(apps, schema_editor): - User = apps.get_model('auth', 'User') - + User = apps.get_model("auth", "User") + try: - CASAccount = apps.get_model('authens', 'CASAccount') + CASAccount = apps.get_model("authens", "CASAccount") except LookupError: return try: - SocialAccount = apps.get_model('socialaccount', 'SocialAccount') - OldEmailAddress = apps.get_model('account', 'EmailAddress') + SocialAccount = apps.get_model("socialaccount", "SocialAccount") + OldEmailAddress = apps.get_model("account", "EmailAddress") except LookupError: # Allauth not installed # Simply create CAS accounts for every profile @@ -25,29 +26,26 @@ def forwards(apps, schema_editor): if ldap_info: entrance_year = ldap_info["entrance_year"] CASAccount.objects.create( - user=user, cas_login=user.username, - entrance_year=entrance_year + user=user, cas_login=user.username, entrance_year=entrance_year ) - + for user in User.objects.all(): migrate_user(user) return - - NewEmailAddress = apps.get_model('simple_email_confirmation', - 'EmailAddress') + NewEmailAddress = apps.get_model("simple_email_confirmation", "EmailAddress") from simple_email_confirmation.models import EmailAddressManager - + # Transfer from allauth to authens # Assumes usernames have the format @ # Assumes no clashing clipper accounts have ever been found - oldusers = ( - User.objects.all().prefetch_related( - "emailaddress_set", "socialaccount_set") + oldusers = User.objects.all().prefetch_related( + "emailaddress_set", "socialaccount_set" ) is_ens_mail = lambda mail: ( - mail is not None and (mail.endswith("ens.fr") or mail.endswith("ens.psl.eu"))) + mail is not None and (mail.endswith("ens.fr") or mail.endswith("ens.psl.eu")) + ) new_conns = [] new_mails = [] @@ -56,14 +54,14 @@ def forwards(apps, schema_editor): addresses = user.emailaddress_set.all() for addr in addresses: newaddr = NewEmailAddress( - user=user, email=addr.email, + user=user, + email=addr.email, set_at=timezone.now(), confirmed_at=(timezone.now() if addr.verified else None), key=EmailAddressManager().generate_key(), ) if addr.primary and user.email != addr.email: - print("Adresse principale inconsistante", - user.email, addr.email) + print("Adresse principale inconsistante", user.email, addr.email) new_mails.append(newaddr) # Create new CASAccount connexion @@ -78,30 +76,33 @@ def forwards(apps, schema_editor): print(user.username) continue entrance_year = saccount.extra_data.get( - "entrance_year", user.username.split("@")[1]) + "entrance_year", user.username.split("@")[1] + ) try: entrance_year = 2000 + int(entrance_year) except ValueError: print(entrance_year) continue - new_conns.append(CASAccount(user=user, cas_login=clipper, - entrance_year=int(entrance_year))) + new_conns.append( + CASAccount(user=user, cas_login=clipper, entrance_year=int(entrance_year)) + ) NewEmailAddress.objects.bulk_create(new_mails) CASAccount.objects.bulk_create(new_conns) + class Migration(migrations.Migration): operations = [ migrations.RunPython(forwards, migrations.RunPython.noop), ] dependencies = [ - ('avisstage', '0003_auto_20210117_1208'), - ('authens', '0002_old_cas_account'), + ("avisstage", "0003_auto_20210117_1208"), + ("authens", "0002_old_cas_account"), ] - if global_apps.is_installed('allauth'): - dependencies.append(('socialaccount', '0003_extra_data_default_dict')) + if global_apps.is_installed("allauth"): + dependencies.append(("socialaccount", "0003_extra_data_default_dict")) - if global_apps.is_installed('simple_email_confirmation'): - dependencies.append(('simple_email_confirmation', '0001_initial')) + if global_apps.is_installed("simple_email_confirmation"): + dependencies.append(("simple_email_confirmation", "0001_initial")) diff --git a/avisstage/migrations/0005_normalien_en_scolarite.py b/avisstage/migrations/0005_normalien_en_scolarite.py index da85767..9ec2a88 100644 --- a/avisstage/migrations/0005_normalien_en_scolarite.py +++ b/avisstage/migrations/0005_normalien_en_scolarite.py @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('avisstage', '0004_allauth_to_authens'), + ("avisstage", "0004_allauth_to_authens"), ] operations = [ migrations.AddField( - model_name='normalien', - name='en_scolarite', + model_name="normalien", + name="en_scolarite", field=models.BooleanField(blank=True, default=False), ), ] diff --git a/avisstage/migrations/0006_auto_20210131_1954.py b/avisstage/migrations/0006_auto_20210131_1954.py index 1ea3ac5..c940e17 100644 --- a/avisstage/migrations/0006_auto_20210131_1954.py +++ b/avisstage/migrations/0006_auto_20210131_1954.py @@ -7,26 +7,30 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('avisstage', '0005_normalien_en_scolarite'), + ("avisstage", "0005_normalien_en_scolarite"), ] operations = [ migrations.RemoveField( - model_name='normalien', - name='en_scolarite', + model_name="normalien", + name="en_scolarite", ), migrations.RemoveField( - model_name='normalien', - name='mail', + model_name="normalien", + name="mail", ), migrations.AddField( - model_name='normalien', - name='last_cas_login', + model_name="normalien", + name="last_cas_login", field=models.DateField(default=avisstage.models._default_cas_login), ), migrations.AlterField( - model_name='normalien', - name='contactez_moi', - field=models.BooleanField(default=True, help_text='Affiche votre adresse e-mail principale sur votre profil public', verbose_name='Inviter les visiteurs à me contacter'), + model_name="normalien", + name="contactez_moi", + field=models.BooleanField( + default=True, + help_text="Affiche votre adresse e-mail principale sur votre profil public", + verbose_name="Inviter les visiteurs à me contacter", + ), ), ] diff --git a/avisstage/models.py b/avisstage/models.py index 4b7a29f..05d7bae 100644 --- a/avisstage/models.py +++ b/avisstage/models.py @@ -20,27 +20,39 @@ from datetime import timedelta from .utils import choices_length, is_email_ens from .statics import ( - DEPARTEMENTS_DEFAUT, PAYS_OPTIONS, TYPE_LIEU_OPTIONS, TYPE_STAGE_OPTIONS, TYPE_LIEU_DICT, - TYPE_STAGE_DICT, NIVEAU_SCOL_OPTIONS, NIVEAU_SCOL_DICT + DEPARTEMENTS_DEFAUT, + PAYS_OPTIONS, + TYPE_LIEU_OPTIONS, + TYPE_STAGE_OPTIONS, + TYPE_LIEU_DICT, + TYPE_STAGE_DICT, + NIVEAU_SCOL_OPTIONS, + NIVEAU_SCOL_DICT, ) + def _default_cas_login(): - return (timezone.now()-timedelta(days=365)).date() + return (timezone.now() - timedelta(days=365)).date() + # # Profil Normalien (extension du modèle User) # + class Normalien(models.Model): - user = models.OneToOneField(User, related_name="profil", - on_delete=models.SET_NULL, null=True) + user = models.OneToOneField( + User, related_name="profil", on_delete=models.SET_NULL, null=True + ) # Infos spécifiques nom = models.CharField("Nom complet", max_length=255, blank=True) promotion = models.CharField("Promotion", max_length=40, blank=True) contactez_moi = models.BooleanField( "Inviter les visiteurs à me contacter", - default=True, help_text="Affiche votre adresse e-mail principale sur votre profil public") + default=True, + help_text="Affiche votre adresse e-mail principale sur votre profil public", + ) bio = models.TextField("À propos de moi", blank=True, default="") last_cas_login = models.DateField(default=_default_cas_login) @@ -53,12 +65,11 @@ class Normalien(models.Model): # Liste des stages publiés def stages_publics(self): - return self.stages.filter(public=True).order_by('-date_debut') + return self.stages.filter(public=True).order_by("-date_debut") def has_nonENS_email(self): return ( - self.user.email_address_set - .exclude(confirmed_at__isnull=True) + self.user.email_address_set.exclude(confirmed_at__isnull=True) .exclude(email__endswith="ens.fr") .exclude(email__endswith="ens.psl.eu") .exists() @@ -77,18 +88,20 @@ class Normalien(models.Model): def preferred_email(self): return self.user.email + # Hook à la création d'un nouvel utilisateur : information de base def create_basic_user_profile(sender, instance, created, **kwargs): if created: profil, created = Normalien.objects.get_or_create(user=instance) - + if not created and profil.promotion != "": return - + if "@" in instance.username: profil.promotion = instance.username.split("@")[1] profil.save() + post_save.connect(create_basic_user_profile, sender=User) # Hook d'authENS : information du CAS @@ -104,7 +117,7 @@ def handle_cas_connection(sender, instance, created, cas_login, attributes, **kw if len(dirs) < 4: print("HomeDirectory invalide", dirs) return - + year = dirs[2] departement = dirs[3] @@ -114,33 +127,33 @@ def handle_cas_connection(sender, instance, created, cas_login, attributes, **kw profil.nom = attributes.get("name", "") profil.save() + post_cas_connect.connect(handle_cas_connection, sender=User) # # Lieu de stage # + class Lieu(models.Model): # Général - nom = models.CharField("Nom de l'institution d'accueil", - max_length=250) - type_lieu = models.CharField("Type de structure d'accueil", - default="universite", - choices=TYPE_LIEU_OPTIONS, - max_length=choices_length(TYPE_LIEU_OPTIONS)) + nom = models.CharField("Nom de l'institution d'accueil", max_length=250) + type_lieu = models.CharField( + "Type de structure d'accueil", + default="universite", + choices=TYPE_LIEU_OPTIONS, + max_length=choices_length(TYPE_LIEU_OPTIONS), + ) # Infos géographiques - ville = models.CharField("Ville", - max_length=200) - pays = models.CharField("Pays", - choices=PAYS_OPTIONS, - max_length=choices_length(PAYS_OPTIONS)) - + ville = models.CharField("Ville", max_length=200) + pays = models.CharField( + "Pays", choices=PAYS_OPTIONS, max_length=choices_length(PAYS_OPTIONS) + ) + # Coordonnées - #objects = geomodels.GeoManager() # Requis par GeoDjango - coord = geomodels.PointField("Coordonnées", - geography=True, - srid = 4326) + # objects = geomodels.GeoManager() # Requis par GeoDjango + coord = geomodels.PointField("Coordonnées", geography=True, srid=4326) # Type du lieu en plus joli @property @@ -153,19 +166,21 @@ class Lieu(models.Model): def __str__(self): return "%s (%s)" % (self.nom, self.ville) - + class Meta: verbose_name = "Lieu" verbose_name_plural = "Lieux" - + + # # Matières des stages # + class StageMatiere(models.Model): nom = models.CharField("Nom", max_length=30) slug = models.SlugField() - + class Meta: verbose_name = "Matière des stages" verbose_name_plural = "Matières des stages" @@ -173,14 +188,17 @@ class StageMatiere(models.Model): def __str__(self): return self.nom + # # Un stage # + class Stage(models.Model): # Misc - auteur = models.ForeignKey(Normalien, related_name="stages", - on_delete=models.SET_NULL, null=True) + auteur = models.ForeignKey( + Normalien, related_name="stages", on_delete=models.SET_NULL, null=True + ) public = models.BooleanField("Visible publiquement", default=False) date_creation = models.DateTimeField("Créé le", default=timezone.now) date_maj = models.DateTimeField("Mis à jour le", default=timezone.now) @@ -193,29 +211,36 @@ class Stage(models.Model): date_debut = models.DateField("Date de début", null=True) date_fin = models.DateField("Date de fin", null=True) - type_stage = models.CharField("Type", - default="stage", - choices=TYPE_STAGE_OPTIONS, - max_length=choices_length(TYPE_STAGE_OPTIONS)) - niveau_scol = models.CharField("Année de scolarité", - default="", - choices=NIVEAU_SCOL_OPTIONS, - max_length=choices_length(NIVEAU_SCOL_OPTIONS), - blank=True) + type_stage = models.CharField( + "Type", + default="stage", + choices=TYPE_STAGE_OPTIONS, + max_length=choices_length(TYPE_STAGE_OPTIONS), + ) + niveau_scol = models.CharField( + "Année de scolarité", + default="", + choices=NIVEAU_SCOL_OPTIONS, + max_length=choices_length(NIVEAU_SCOL_OPTIONS), + blank=True, + ) thematiques = TaggableManager("Thématiques", blank=True) - matieres = models.ManyToManyField(StageMatiere, verbose_name="Matière(s)", related_name="stages") + matieres = models.ManyToManyField( + StageMatiere, verbose_name="Matière(s)", related_name="stages" + ) encadrants = models.CharField("Encadrant⋅e⋅s", max_length=500, blank=True) structure = models.CharField("Structure d'accueil", max_length=500, blank=True) # Avis - lieux = models.ManyToManyField(Lieu, related_name="stages", - through="AvisLieu", blank=True) + lieux = models.ManyToManyField( + Lieu, related_name="stages", through="AvisLieu", blank=True + ) # Affichage des avis ordonnés @property def avis_lieux(self): - return self.avislieu_set.order_by('order') + return self.avislieu_set.order_by("order") # Shortcut pour affichage rapide @property @@ -229,6 +254,7 @@ class Stage(models.Model): @property def type_stage_fancy(self): return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[0] + @property def type_stage_fem(self): return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[1] @@ -244,8 +270,8 @@ class Stage(models.Model): return self.lieux.all() def get_absolute_url(self): - return reverse('avisstage:stage', self) - + return reverse("avisstage:stage", self) + def __str__(self): return "%s (par %s)" % (self.sujet, self.auteur.user.username) @@ -259,26 +285,29 @@ class Stage(models.Model): length += len(obj.les_plus.split()) length += len(obj.les_moins.split()) return length - + if self.avis_stage: self.len_avis_stage = get_len(self.avis_stage) self.len_avis_lieux = 0 for avis in self.avislieu_set.all(): self.len_avis_lieux += get_len(avis) - + if save: self.save() - + class Meta: verbose_name = "Stage" + # # Les avis # + class AvisStage(models.Model): - stage = models.OneToOneField(Stage, related_name="avis_stage", - on_delete=models.CASCADE) + stage = models.OneToOneField( + Stage, related_name="avis_stage", on_delete=models.CASCADE + ) chapo = models.TextField("En quelques mots", blank=True) avis_ambiance = RichTextField("L'ambiance de travail", blank=True) @@ -290,14 +319,19 @@ class AvisStage(models.Model): les_moins = models.TextField("Les moins de cette expérience", blank=True) def __str__(self): - return "Avis sur {%s} par %s" % (self.stage.sujet, self.stage.auteur.user.username) + return "Avis sur {%s} par %s" % ( + self.stage.sujet, + self.stage.auteur.user.username, + ) # Liste des champs d'avis, couplés à leur nom (pour l'affichage) @property def avis_all(self): - fields = ['avis_sujet', 'avis_ambiance', 'avis_admin', 'avis_prestage'] - return [(AvisStage._meta.get_field(field).verbose_name, - getattr(self, field, '')) for field in fields] + fields = ["avis_sujet", "avis_ambiance", "avis_admin", "avis_prestage"] + return [ + (AvisStage._meta.get_field(field).verbose_name, getattr(self, field, "")) + for field in fields + ] class AvisLieu(models.Model): @@ -307,8 +341,7 @@ class AvisLieu(models.Model): chapo = models.TextField("En quelques mots", blank=True) avis_lieustage = RichTextField("Les lieux de travail", blank=True) - avis_pratique = RichTextField("S'installer - conseils pratiques", - blank=True) + avis_pratique = RichTextField("S'installer - conseils pratiques", blank=True) avis_tourisme = RichTextField("Dans les parages", blank=True) les_plus = models.TextField("Les plus du lieu", blank=True) @@ -324,6 +357,8 @@ class AvisLieu(models.Model): # Liste des champs d'avis, couplés à leur nom (pour l'affichage) @property def avis_all(self): - fields = ['avis_lieustage', 'avis_pratique', 'avis_tourisme'] - return [(AvisLieu._meta.get_field(field).verbose_name, - getattr(self, field, '')) for field in fields] + fields = ["avis_lieustage", "avis_pratique", "avis_tourisme"] + return [ + (AvisLieu._meta.get_field(field).verbose_name, getattr(self, field, "")) + for field in fields + ] diff --git a/avisstage/statics.py b/avisstage/statics.py index 25a5173..6a3c10f 100644 --- a/avisstage/statics.py +++ b/avisstage/statics.py @@ -1,67 +1,76 @@ # coding: utf-8 DEPARTEMENTS_DEFAUT = ( - ('phy', u'Physique'), - ('maths', u'Maths'), - ('bio', u'Biologie'), - ('chimie', u'Chimie'), - ('geol', u'Géosciences'), - ('dec', u'DEC'), - ('info', u'Informatique'), - ('litt', u'Littéraire'), - ('guests', u'Pensionnaires étrangers'), - ('pei', u'PEI'), + ("phy", u"Physique"), + ("maths", u"Maths"), + ("bio", u"Biologie"), + ("chimie", u"Chimie"), + ("geol", u"Géosciences"), + ("dec", u"DEC"), + ("info", u"Informatique"), + ("litt", u"Littéraire"), + ("guests", u"Pensionnaires étrangers"), + ("pei", u"PEI"), ) TYPE_STAGE_OPTIONS = ( - (u'Recherche :', ( - ('recherche', "Stage académique"), - ('recherche_autre', "Stage non-académique"), - ('sejour_dri', "Séjour de recherche DRI"), - )), - (u'Stage sans visée de recherche :', ( - ('pro', "Stage en entreprise"), - ('admin', "Stage en admin./ONG/orga. internationale"), - )), - (u'Enseignement :', ( - ('lectorat', "Lectorat DRI"), - ('autre_teach', "Autre expérience d'enseignement"), - )), - ('autre', "Autre"), + ( + u"Recherche :", + ( + ("recherche", "Stage académique"), + ("recherche_autre", "Stage non-académique"), + ("sejour_dri", "Séjour de recherche DRI"), + ), + ), + ( + u"Stage sans visée de recherche :", + ( + ("pro", "Stage en entreprise"), + ("admin", "Stage en admin./ONG/orga. internationale"), + ), + ), + ( + u"Enseignement :", + ( + ("lectorat", "Lectorat DRI"), + ("autre_teach", "Autre expérience d'enseignement"), + ), + ), + ("autre", "Autre"), ) # Dictionnaire des type de stage (et de leur genre, True=féminin) TYPE_STAGE_DICT = { - 'recherche': ("stage de recherche académique", False), - 'recherche_autre': ("stage de recherche non-académique", False), - 'sejour_dri': ("séjour de recherche DRI", False), - 'pro': ("stage en entreprise sans visée de recherche", False), - 'admin': ("stage en administration, ONG ou organisation internationale", False), - 'lectorat': ("lectorat DRI", False), - 'autre_teach': ("expérience de recherche", True), - 'autre': ("expérience", True), + "recherche": ("stage de recherche académique", False), + "recherche_autre": ("stage de recherche non-académique", False), + "sejour_dri": ("séjour de recherche DRI", False), + "pro": ("stage en entreprise sans visée de recherche", False), + "admin": ("stage en administration, ONG ou organisation internationale", False), + "lectorat": ("lectorat DRI", False), + "autre_teach": ("expérience de recherche", True), + "autre": ("expérience", True), } TYPE_LIEU_OPTIONS = ( - ('universite', "Université"), - ('entreprise', "Entreprise"), - ('centrerecherche', "Centre de recherche"), - ('administration', "Administration"), - ('autre', "Autre"), + ("universite", "Université"), + ("entreprise", "Entreprise"), + ("centrerecherche", "Centre de recherche"), + ("administration", "Administration"), + ("autre", "Autre"), ) # Place du stage dans le cursus NIVEAU_SCOL_OPTIONS = ( - ('L3', "Licence 3"), - ('M1', "Master 1"), - ('M2', "Master 2"), - ('DOC', "Pré-doctorat"), - ('CST', "Césure"), - ('BLA', "Année blanche"), - ('VAC', "Vacances scolaires"), - ('MIT', "Mi-temps en parallèle des études"), - ('', "Autre"), + ("L3", "Licence 3"), + ("M1", "Master 1"), + ("M2", "Master 2"), + ("DOC", "Pré-doctorat"), + ("CST", "Césure"), + ("BLA", "Année blanche"), + ("VAC", "Vacances scolaires"), + ("MIT", "Mi-temps en parallèle des études"), + ("", "Autre"), ) NIVEAU_SCOL_DICT = { @@ -74,257 +83,257 @@ NIVEAU_SCOL_DICT = { "VAC": "pendant des vacances scolaires", "MIT": "à mi-temps en parallèle des études", } - + # Dictionnaire des noms de lieux (et de leur genre, True=féminin) TYPE_LIEU_DICT = { - 'universite': ("université", True), - 'entreprise': ("entreprise", True), - 'centrerecherche': ("centre de recherche", False), - 'administration': ("administration", True), - 'autre': ("lieu", False), + "universite": ("université", True), + "entreprise": ("entreprise", True), + "centrerecherche": ("centre de recherche", False), + "administration": ("administration", True), + "autre": ("lieu", False), } PAYS_OPTIONS = ( - ("AF", "Afghanistan"), - ("AL", "Albanie"), - ("AQ", "Antarctique"), - ("DZ", "Algérie"), - ("AS", "Samoa Américaines"), - ("AD", "Andorre"), - ("AO", "Angola"), - ("AG", "Antigua-et-Barbuda"), - ("AZ", "Azerbaïdjan"), - ("AR", "Argentine"), - ("AU", "Australie"), - ("AT", "Autriche"), - ("BS", "Bahamas"), - ("BH", "Bahreïn"), - ("BD", "Bangladesh"), - ("AM", "Arménie"), - ("BB", "Barbade"), - ("BE", "Belgique"), - ("BM", "Bermudes"), - ("BT", "Bhoutan"), - ("BO", "Bolivie"), - ("BA", "Bosnie-Herzégovine"), - ("BW", "Botswana"), - ("BV", "Île Bouvet"), - ("BR", "Brésil"), - ("BZ", "Belize"), - ("IO", "Territoire Britannique de l'Océan Indien"), - ("SB", "Îles Salomon"), - ("VG", "Îles Vierges Britanniques"), - ("BN", "Brunéi Darussalam"), - ("BG", "Bulgarie"), - ("MM", "Myanmar"), - ("BI", "Burundi"), - ("BY", "Bélarus"), - ("KH", "Cambodge"), - ("CM", "Cameroun"), - ("CA", "Canada"), - ("CV", "Cap-vert"), - ("KY", "Îles Caïmanes"), - ("CF", "République Centrafricaine"), - ("LK", "Sri Lanka"), - ("TD", "Tchad"), - ("CL", "Chili"), - ("CN", "Chine"), - ("TW", "Taïwan"), - ("CX", "Île Christmas"), - ("CC", "Îles Cocos (Keeling)"), - ("CO", "Colombie"), - ("KM", "Comores"), - ("YT", "Mayotte"), - ("CG", "République du Congo"), - ("CD", "République Démocratique du Congo"), - ("CK", "Îles Cook"), - ("CR", "Costa Rica"), - ("HR", "Croatie"), - ("CU", "Cuba"), - ("CY", "Chypre"), - ("CZ", "République Tchèque"), - ("BJ", "Bénin"), - ("DK", "Danemark"), - ("DM", "Dominique"), - ("DO", "République Dominicaine"), - ("EC", "Équateur"), - ("SV", "El Salvador"), - ("GQ", "Guinée Équatoriale"), - ("ET", "Éthiopie"), - ("ER", "Érythrée"), - ("EE", "Estonie"), - ("FO", "Îles Féroé"), - ("FK", "Îles (malvinas) Falkland"), - ("GS", "Géorgie du Sud et les Îles Sandwich du Sud"), - ("FJ", "Fidji"), - ("FI", "Finlande"), - ("AX", "Îles Åland"), - ("FR", "France"), - ("GF", "Guyane Française"), - ("PF", "Polynésie Française"), - ("TF", "Terres Australes Françaises"), - ("DJ", "Djibouti"), - ("GA", "Gabon"), - ("GE", "Géorgie"), - ("GM", "Gambie"), - ("PS", "Territoire Palestinien Occupé"), - ("DE", "Allemagne"), - ("GH", "Ghana"), - ("GI", "Gibraltar"), - ("KI", "Kiribati"), - ("GR", "Grèce"), - ("GL", "Groenland"), - ("GD", "Grenade"), - ("GP", "Guadeloupe"), - ("GU", "Guam"), - ("GT", "Guatemala"), - ("GN", "Guinée"), - ("GY", "Guyana"), - ("HT", "Haïti"), - ("HM", "Îles Heard et Mcdonald"), - ("VA", "Saint-Siège (état de la Cité du Vatican)"), - ("HN", "Honduras"), - ("HK", "Hong-Kong"), - ("HU", "Hongrie"), - ("IS", "Islande"), - ("IN", "Inde"), - ("ID", "Indonésie"), - ("IR", "République Islamique d'Iran"), - ("IQ", "Iraq"), - ("IE", "Irlande"), - ("IL", "Israël"), - ("IT", "Italie"), - ("CI", "Côte d'Ivoire"), - ("JM", "Jamaïque"), - ("JP", "Japon"), - ("KZ", "Kazakhstan"), - ("JO", "Jordanie"), - ("KE", "Kenya"), - ("KP", "République Populaire Démocratique de Corée"), - ("KR", "République de Corée"), - ("KW", "Koweït"), - ("KG", "Kirghizistan"), - ("LA", "République Démocratique Populaire Lao"), - ("LB", "Liban"), - ("LS", "Lesotho"), - ("LV", "Lettonie"), - ("LR", "Libéria"), - ("LY", "Jamahiriya Arabe Libyenne"), - ("LI", "Liechtenstein"), - ("LT", "Lituanie"), - ("LU", "Luxembourg"), - ("MO", "Macao"), - ("MG", "Madagascar"), - ("MW", "Malawi"), - ("MY", "Malaisie"), - ("MV", "Maldives"), - ("ML", "Mali"), - ("MT", "Malte"), - ("MQ", "Martinique"), - ("MR", "Mauritanie"), - ("MU", "Maurice"), - ("MX", "Mexique"), - ("MC", "Monaco"), - ("MN", "Mongolie"), - ("MD", "République de Moldova"), - ("MS", "Montserrat"), - ("MA", "Maroc"), - ("MZ", "Mozambique"), - ("OM", "Oman"), - ("NA", "Namibie"), - ("NR", "Nauru"), - ("NP", "Népal"), - ("NL", "Pays-Bas"), - ("AN", "Antilles Néerlandaises"), - ("AW", "Aruba"), - ("NC", "Nouvelle-Calédonie"), - ("VU", "Vanuatu"), - ("NZ", "Nouvelle-Zélande"), - ("NI", "Nicaragua"), - ("NE", "Niger"), - ("NG", "Nigéria"), - ("NU", "Niué"), - ("NF", "Île Norfolk"), - ("NO", "Norvège"), - ("MP", "Îles Mariannes du Nord"), - ("UM", "Îles Mineures Éloignées des États-Unis"), - ("FM", "États Fédérés de Micronésie"), - ("MH", "Îles Marshall"), - ("PW", "Palaos"), - ("PK", "Pakistan"), - ("PA", "Panama"), - ("PG", "Papouasie-Nouvelle-Guinée"), - ("PY", "Paraguay"), - ("PE", "Pérou"), - ("PH", "Philippines"), - ("PN", "Pitcairn"), - ("PL", "Pologne"), - ("PT", "Portugal"), - ("GW", "Guinée-Bissau"), - ("TL", "Timor-Leste"), - ("PR", "Porto Rico"), - ("QA", "Qatar"), - ("RE", "Réunion"), - ("RO", "Roumanie"), - ("RU", "Fédération de Russie"), - ("RW", "Rwanda"), - ("SH", "Sainte-Hélène"), - ("KN", "Saint-Kitts-et-Nevis"), - ("AI", "Anguilla"), - ("LC", "Sainte-Lucie"), - ("PM", "Saint-Pierre-et-Miquelon"), - ("VC", "Saint-Vincent-et-les Grenadines"), - ("SM", "Saint-Marin"), - ("ST", "Sao Tomé-et-Principe"), - ("SA", "Arabie Saoudite"), - ("SN", "Sénégal"), - ("SC", "Seychelles"), - ("SL", "Sierra Leone"), - ("SG", "Singapour"), - ("SK", "Slovaquie"), - ("VN", "Viet Nam"), - ("SI", "Slovénie"), - ("SO", "Somalie"), - ("ZA", "Afrique du Sud"), - ("ZW", "Zimbabwe"), - ("ES", "Espagne"), - ("EH", "Sahara Occidental"), - ("SD", "Soudan"), - ("SR", "Suriname"), - ("SJ", "Svalbard etÎle Jan Mayen"), - ("SZ", "Swaziland"), - ("SE", "Suède"), - ("CH", "Suisse"), - ("SY", "République Arabe Syrienne"), - ("TJ", "Tadjikistan"), - ("TH", "Thaïlande"), - ("TG", "Togo"), - ("TK", "Tokelau"), - ("TO", "Tonga"), - ("TT", "Trinité-et-Tobago"), - ("AE", "Émirats Arabes Unis"), - ("TN", "Tunisie"), - ("TR", "Turquie"), - ("TM", "Turkménistan"), - ("TC", "Îles Turks et Caïques"), - ("TV", "Tuvalu"), - ("UG", "Ouganda"), - ("UA", "Ukraine"), - ("MK", "L'ex-République Yougoslave de Macédoine"), - ("EG", "Égypte"), - ("GB", "Royaume-Uni"), - ("IM", "Île de Man"), - ("TZ", "République-Unie de Tanzanie"), - ("US", "États-Unis"), - ("VI", "Îles Vierges des États-Unis"), - ("BF", "Burkina Faso"), - ("UY", "Uruguay"), - ("UZ", "Ouzbékistan"), - ("VE", "Venezuela"), - ("WF", "Wallis et Futuna"), - ("WS", "Samoa"), - ("YE", "Yémen"), - ("CS", "Serbie-et-Monténégro"), - ("ZM", "Zambie"), + ("AF", "Afghanistan"), + ("AL", "Albanie"), + ("AQ", "Antarctique"), + ("DZ", "Algérie"), + ("AS", "Samoa Américaines"), + ("AD", "Andorre"), + ("AO", "Angola"), + ("AG", "Antigua-et-Barbuda"), + ("AZ", "Azerbaïdjan"), + ("AR", "Argentine"), + ("AU", "Australie"), + ("AT", "Autriche"), + ("BS", "Bahamas"), + ("BH", "Bahreïn"), + ("BD", "Bangladesh"), + ("AM", "Arménie"), + ("BB", "Barbade"), + ("BE", "Belgique"), + ("BM", "Bermudes"), + ("BT", "Bhoutan"), + ("BO", "Bolivie"), + ("BA", "Bosnie-Herzégovine"), + ("BW", "Botswana"), + ("BV", "Île Bouvet"), + ("BR", "Brésil"), + ("BZ", "Belize"), + ("IO", "Territoire Britannique de l'Océan Indien"), + ("SB", "Îles Salomon"), + ("VG", "Îles Vierges Britanniques"), + ("BN", "Brunéi Darussalam"), + ("BG", "Bulgarie"), + ("MM", "Myanmar"), + ("BI", "Burundi"), + ("BY", "Bélarus"), + ("KH", "Cambodge"), + ("CM", "Cameroun"), + ("CA", "Canada"), + ("CV", "Cap-vert"), + ("KY", "Îles Caïmanes"), + ("CF", "République Centrafricaine"), + ("LK", "Sri Lanka"), + ("TD", "Tchad"), + ("CL", "Chili"), + ("CN", "Chine"), + ("TW", "Taïwan"), + ("CX", "Île Christmas"), + ("CC", "Îles Cocos (Keeling)"), + ("CO", "Colombie"), + ("KM", "Comores"), + ("YT", "Mayotte"), + ("CG", "République du Congo"), + ("CD", "République Démocratique du Congo"), + ("CK", "Îles Cook"), + ("CR", "Costa Rica"), + ("HR", "Croatie"), + ("CU", "Cuba"), + ("CY", "Chypre"), + ("CZ", "République Tchèque"), + ("BJ", "Bénin"), + ("DK", "Danemark"), + ("DM", "Dominique"), + ("DO", "République Dominicaine"), + ("EC", "Équateur"), + ("SV", "El Salvador"), + ("GQ", "Guinée Équatoriale"), + ("ET", "Éthiopie"), + ("ER", "Érythrée"), + ("EE", "Estonie"), + ("FO", "Îles Féroé"), + ("FK", "Îles (malvinas) Falkland"), + ("GS", "Géorgie du Sud et les Îles Sandwich du Sud"), + ("FJ", "Fidji"), + ("FI", "Finlande"), + ("AX", "Îles Åland"), + ("FR", "France"), + ("GF", "Guyane Française"), + ("PF", "Polynésie Française"), + ("TF", "Terres Australes Françaises"), + ("DJ", "Djibouti"), + ("GA", "Gabon"), + ("GE", "Géorgie"), + ("GM", "Gambie"), + ("PS", "Territoire Palestinien Occupé"), + ("DE", "Allemagne"), + ("GH", "Ghana"), + ("GI", "Gibraltar"), + ("KI", "Kiribati"), + ("GR", "Grèce"), + ("GL", "Groenland"), + ("GD", "Grenade"), + ("GP", "Guadeloupe"), + ("GU", "Guam"), + ("GT", "Guatemala"), + ("GN", "Guinée"), + ("GY", "Guyana"), + ("HT", "Haïti"), + ("HM", "Îles Heard et Mcdonald"), + ("VA", "Saint-Siège (état de la Cité du Vatican)"), + ("HN", "Honduras"), + ("HK", "Hong-Kong"), + ("HU", "Hongrie"), + ("IS", "Islande"), + ("IN", "Inde"), + ("ID", "Indonésie"), + ("IR", "République Islamique d'Iran"), + ("IQ", "Iraq"), + ("IE", "Irlande"), + ("IL", "Israël"), + ("IT", "Italie"), + ("CI", "Côte d'Ivoire"), + ("JM", "Jamaïque"), + ("JP", "Japon"), + ("KZ", "Kazakhstan"), + ("JO", "Jordanie"), + ("KE", "Kenya"), + ("KP", "République Populaire Démocratique de Corée"), + ("KR", "République de Corée"), + ("KW", "Koweït"), + ("KG", "Kirghizistan"), + ("LA", "République Démocratique Populaire Lao"), + ("LB", "Liban"), + ("LS", "Lesotho"), + ("LV", "Lettonie"), + ("LR", "Libéria"), + ("LY", "Jamahiriya Arabe Libyenne"), + ("LI", "Liechtenstein"), + ("LT", "Lituanie"), + ("LU", "Luxembourg"), + ("MO", "Macao"), + ("MG", "Madagascar"), + ("MW", "Malawi"), + ("MY", "Malaisie"), + ("MV", "Maldives"), + ("ML", "Mali"), + ("MT", "Malte"), + ("MQ", "Martinique"), + ("MR", "Mauritanie"), + ("MU", "Maurice"), + ("MX", "Mexique"), + ("MC", "Monaco"), + ("MN", "Mongolie"), + ("MD", "République de Moldova"), + ("MS", "Montserrat"), + ("MA", "Maroc"), + ("MZ", "Mozambique"), + ("OM", "Oman"), + ("NA", "Namibie"), + ("NR", "Nauru"), + ("NP", "Népal"), + ("NL", "Pays-Bas"), + ("AN", "Antilles Néerlandaises"), + ("AW", "Aruba"), + ("NC", "Nouvelle-Calédonie"), + ("VU", "Vanuatu"), + ("NZ", "Nouvelle-Zélande"), + ("NI", "Nicaragua"), + ("NE", "Niger"), + ("NG", "Nigéria"), + ("NU", "Niué"), + ("NF", "Île Norfolk"), + ("NO", "Norvège"), + ("MP", "Îles Mariannes du Nord"), + ("UM", "Îles Mineures Éloignées des États-Unis"), + ("FM", "États Fédérés de Micronésie"), + ("MH", "Îles Marshall"), + ("PW", "Palaos"), + ("PK", "Pakistan"), + ("PA", "Panama"), + ("PG", "Papouasie-Nouvelle-Guinée"), + ("PY", "Paraguay"), + ("PE", "Pérou"), + ("PH", "Philippines"), + ("PN", "Pitcairn"), + ("PL", "Pologne"), + ("PT", "Portugal"), + ("GW", "Guinée-Bissau"), + ("TL", "Timor-Leste"), + ("PR", "Porto Rico"), + ("QA", "Qatar"), + ("RE", "Réunion"), + ("RO", "Roumanie"), + ("RU", "Fédération de Russie"), + ("RW", "Rwanda"), + ("SH", "Sainte-Hélène"), + ("KN", "Saint-Kitts-et-Nevis"), + ("AI", "Anguilla"), + ("LC", "Sainte-Lucie"), + ("PM", "Saint-Pierre-et-Miquelon"), + ("VC", "Saint-Vincent-et-les Grenadines"), + ("SM", "Saint-Marin"), + ("ST", "Sao Tomé-et-Principe"), + ("SA", "Arabie Saoudite"), + ("SN", "Sénégal"), + ("SC", "Seychelles"), + ("SL", "Sierra Leone"), + ("SG", "Singapour"), + ("SK", "Slovaquie"), + ("VN", "Viet Nam"), + ("SI", "Slovénie"), + ("SO", "Somalie"), + ("ZA", "Afrique du Sud"), + ("ZW", "Zimbabwe"), + ("ES", "Espagne"), + ("EH", "Sahara Occidental"), + ("SD", "Soudan"), + ("SR", "Suriname"), + ("SJ", "Svalbard etÎle Jan Mayen"), + ("SZ", "Swaziland"), + ("SE", "Suède"), + ("CH", "Suisse"), + ("SY", "République Arabe Syrienne"), + ("TJ", "Tadjikistan"), + ("TH", "Thaïlande"), + ("TG", "Togo"), + ("TK", "Tokelau"), + ("TO", "Tonga"), + ("TT", "Trinité-et-Tobago"), + ("AE", "Émirats Arabes Unis"), + ("TN", "Tunisie"), + ("TR", "Turquie"), + ("TM", "Turkménistan"), + ("TC", "Îles Turks et Caïques"), + ("TV", "Tuvalu"), + ("UG", "Ouganda"), + ("UA", "Ukraine"), + ("MK", "L'ex-République Yougoslave de Macédoine"), + ("EG", "Égypte"), + ("GB", "Royaume-Uni"), + ("IM", "Île de Man"), + ("TZ", "République-Unie de Tanzanie"), + ("US", "États-Unis"), + ("VI", "Îles Vierges des États-Unis"), + ("BF", "Burkina Faso"), + ("UY", "Uruguay"), + ("UZ", "Ouzbékistan"), + ("VE", "Venezuela"), + ("WF", "Wallis et Futuna"), + ("WS", "Samoa"), + ("YE", "Yémen"), + ("CS", "Serbie-et-Monténégro"), + ("ZM", "Zambie"), ) diff --git a/avisstage/templatetags/avisstage_tags.py b/avisstage/templatetags/avisstage_tags.py index 7f9d03c..bba61d5 100644 --- a/avisstage/templatetags/avisstage_tags.py +++ b/avisstage/templatetags/avisstage_tags.py @@ -6,23 +6,27 @@ import re register = template.Library() -@register.inclusion_tag('avisstage/templatetags/widget_lieu.html') + +@register.inclusion_tag("avisstage/templatetags/widget_lieu.html") def lieu_widget(): form = LieuForm() return {"form": form} -@register.inclusion_tag('avisstage/templatetags/widget_feedback.html') + +@register.inclusion_tag("avisstage/templatetags/widget_feedback.html") def feedback_widget(): form = FeedbackForm() return {"form": form} + @register.filter def typonazisme(value): - value = re.sub(r'(\w)\s*([?!:])', u'\\1 \\2', value) - value = re.sub(r'(\w)\s*([,.])', u'\\1\\2', value) - value = re.sub(r'([?!:,.])(\w)', u'\\1 \\2', value) + value = re.sub(r"(\w)\s*([?!:])", u"\\1 \\2", value) + value = re.sub(r"(\w)\s*([,.])", u"\\1\\2", value) + value = re.sub(r"([?!:,.])(\w)", u"\\1 \\2", value) return value + @register.filter def avis_len(value): if value < 5: @@ -32,6 +36,7 @@ def avis_len(value): else: return "long" + @register.simple_tag def url_replace(request, field, value): dict_ = request.GET.copy() diff --git a/avisstage/tests.py b/avisstage/tests.py index 0ea4bc7..2c9b760 100644 --- a/avisstage/tests.py +++ b/avisstage/tests.py @@ -12,14 +12,15 @@ from unittest import mock from .models import User, Normalien, Lieu, Stage, StageMatiere, AvisLieu + class ExperiENSTestCase(TestCase): - + # Dummy database def setUp(self): - self.u_conscrit = User.objects.create_user('conscrit', - 'conscrit@ens.fr', - 'conscrit') + self.u_conscrit = User.objects.create_user( + "conscrit", "conscrit@ens.fr", "conscrit" + ) self.p_conscrit = self.u_conscrit.profil self.p_conscrit.nom = "Petit conscrit" self.p_conscrit.promotion = "Serpentard 2020" @@ -32,281 +33,313 @@ class ExperiENSTestCase(TestCase): entrance_year=2020, ) self.sa_conscrit.save() - - self.u_archi = User.objects.create_user('archicube', - 'archicube@ens.fr', - 'archicube') - self.p_archi = self.u_archi.profil - self.p_archi.nom="Vieil archicube" - self.p_archi.promotion="Gryffondor 2014" - self.p_archi.bio="Je suis un vieil archicube" - self.lieu1 = Lieu(nom="Beaux-Bâtons", type_lieu="universite", - ville="Brocéliande", pays="FR", - coord="POINT(-1.63971 48.116382)") + self.u_archi = User.objects.create_user( + "archicube", "archicube@ens.fr", "archicube" + ) + self.p_archi = self.u_archi.profil + self.p_archi.nom = "Vieil archicube" + self.p_archi.promotion = "Gryffondor 2014" + self.p_archi.bio = "Je suis un vieil archicube" + + self.lieu1 = Lieu( + nom="Beaux-Bâtons", + type_lieu="universite", + ville="Brocéliande", + pays="FR", + coord="POINT(-1.63971 48.116382)", + ) self.lieu1.save() - self.lieu2 = Lieu(nom="Durmstrang", type_lieu="universite", - ville="Edimbourg", pays="GB", - coord="POINT(56.32153 -1.259715)") + self.lieu2 = Lieu( + nom="Durmstrang", + type_lieu="universite", + ville="Edimbourg", + pays="GB", + coord="POINT(56.32153 -1.259715)", + ) self.lieu2.save() self.matiere1 = StageMatiere(nom="Arithmancie", slug="arithmancie") self.matiere1.save() self.matiere2 = StageMatiere(nom="Sortilège", slug="sortilege") self.matiere2.save() - - self.cstage1 = Stage(auteur=self.p_conscrit, sujet="Wingardium Leviosa", - date_debut=date(2020, 5, 10), - date_fin=date(2020, 8, 26), - type_stage="recherche", - niveau_scol="M1", public=True) + + self.cstage1 = Stage( + auteur=self.p_conscrit, + sujet="Wingardium Leviosa", + date_debut=date(2020, 5, 10), + date_fin=date(2020, 8, 26), + type_stage="recherche", + niveau_scol="M1", + public=True, + ) self.cstage1.save() self.cstage1.matieres.add(self.matiere1) - alieu1 = AvisLieu(stage=self.cstage1, lieu=self.lieu1, - chapo="Trop bien") + alieu1 = AvisLieu(stage=self.cstage1, lieu=self.lieu1, chapo="Trop bien") alieu1.save() - self.cstage2 = Stage(auteur=self.p_conscrit, sujet="Avada Kedavra", - date_debut=date(2021, 5, 10), - date_fin=date(2021, 8, 26), - type_stage="sejour_dri", - niveau_scol="M2", public=False) + self.cstage2 = Stage( + auteur=self.p_conscrit, + sujet="Avada Kedavra", + date_debut=date(2021, 5, 10), + date_fin=date(2021, 8, 26), + type_stage="sejour_dri", + niveau_scol="M2", + public=False, + ) self.cstage2.save() self.cstage2.matieres.add(self.matiere2) - alieu2 = AvisLieu(stage=self.cstage2, lieu=self.lieu2, - chapo="Trop nul") + alieu2 = AvisLieu(stage=self.cstage2, lieu=self.lieu2, chapo="Trop nul") alieu2.save() - - self.astage1 = Stage(auteur=self.p_archi, sujet="Alohomora", - date_debut=date(2014, 5, 10), - date_fin=date(2014, 8, 26), - type_stage="recherche", - niveau_scol="M2", public=True) + self.astage1 = Stage( + auteur=self.p_archi, + sujet="Alohomora", + date_debut=date(2014, 5, 10), + date_fin=date(2014, 8, 26), + type_stage="recherche", + niveau_scol="M2", + public=True, + ) self.astage1.save() self.astage1.matieres.add(self.matiere2) - alieu3 = AvisLieu(stage=self.astage1, lieu=self.lieu1, - chapo="Trop moyen") + alieu3 = AvisLieu(stage=self.astage1, lieu=self.lieu1, chapo="Trop moyen") alieu3.save() def assertRedirectToLogin(self, testurl): r = self.client.get(testurl) - return self.assertRedirects(r, settings.LOGIN_URL+"?next="+testurl) + return self.assertRedirects(r, settings.LOGIN_URL + "?next=" + testurl) def assertPageNotFound(self, testurl): r = self.client.get(testurl) self.assertEqual(r.status_code, 404) - - - - - """ ACCÈS PUBLIC """ + + class PublicViewsTest(ExperiENSTestCase): """ Vérifie que les fiches de stages ne sont pas visibles hors connexion """ - def test_stage_visibility_public(self): - self.assertRedirectToLogin(reverse('avisstage:stage', - kwargs={'pk':self.cstage1.id})) - - self.assertRedirectToLogin(reverse('avisstage:stage', - kwargs={'pk':self.cstage2.id})) - - self.assertRedirectToLogin(reverse('avisstage:stage', - kwargs={'pk':self.astage1.id})) + def test_stage_visibility_public(self): + self.assertRedirectToLogin( + reverse("avisstage:stage", kwargs={"pk": self.cstage1.id}) + ) + + self.assertRedirectToLogin( + reverse("avisstage:stage", kwargs={"pk": self.cstage2.id}) + ) + + self.assertRedirectToLogin( + reverse("avisstage:stage", kwargs={"pk": self.astage1.id}) + ) """ Vérifie que les profils de normaliens ne sont pas visibles hors connexion """ + def test_profil_visibility_public(self): - self.assertRedirectToLogin(reverse( - 'avisstage:profil', kwargs={'username': self.u_conscrit.username})) + self.assertRedirectToLogin( + reverse("avisstage:profil", kwargs={"username": self.u_conscrit.username}) + ) - self.assertRedirectToLogin(reverse( - 'avisstage:profil', kwargs={'username': self.u_archi.username})) + self.assertRedirectToLogin( + reverse("avisstage:profil", kwargs={"username": self.u_archi.username}) + ) - """ Vérifie que la recherche n'est pas accessible hors connexion """ + def test_pages_visibility_public(self): - self.assertRedirectToLogin(reverse('avisstage:recherche')) + self.assertRedirectToLogin(reverse("avisstage:recherche")) - self.assertRedirectToLogin(reverse('avisstage:recherche_resultats')) + self.assertRedirectToLogin(reverse("avisstage:recherche_resultats")) - self.assertRedirectToLogin(reverse('avisstage:stage_items')) + self.assertRedirectToLogin(reverse("avisstage:stage_items")) - self.assertRedirectToLogin(reverse('avisstage:feedback')) + self.assertRedirectToLogin(reverse("avisstage:feedback")) - self.assertRedirectToLogin(reverse('avisstage:moderation')) + self.assertRedirectToLogin(reverse("avisstage:moderation")) """ Vérifie que l'API n'est pas accessible hors connexion """ + def test_api_visibility_public(self): - testurl = reverse('avisstage:api_dispatch_list', - kwargs={"resource_name": "lieu", - "api_name": "v1"}) - r = self.client.get(testurl) - self.assertEqual(r.status_code, 401) - - testurl = reverse('avisstage:api_dispatch_list', - kwargs={"resource_name": "stage", - "api_name": "v1"}) - r = self.client.get(testurl) - self.assertEqual(r.status_code, 401) - - testurl = reverse('avisstage:api_dispatch_list', - kwargs={"resource_name": "profil", - "api_name": "v1"}) + testurl = reverse( + "avisstage:api_dispatch_list", + kwargs={"resource_name": "lieu", "api_name": "v1"}, + ) + r = self.client.get(testurl) + self.assertEqual(r.status_code, 401) + + testurl = reverse( + "avisstage:api_dispatch_list", + kwargs={"resource_name": "stage", "api_name": "v1"}, + ) + r = self.client.get(testurl) + self.assertEqual(r.status_code, 401) + + testurl = reverse( + "avisstage:api_dispatch_list", + kwargs={"resource_name": "profil", "api_name": "v1"}, + ) r = self.client.get(testurl) self.assertEqual(r.status_code, 401) - """ Vérifie que les pages d'édition ne sont pas accessible hors connexion """ + def test_edit_visibility_public(self): - self.assertRedirectToLogin(reverse( - 'avisstage:stage_edit', kwargs={'pk':self.cstage1.id})) - - self.assertRedirectToLogin(reverse( - 'avisstage:stage_edit', kwargs={'pk':self.astage1.id})) + self.assertRedirectToLogin( + reverse("avisstage:stage_edit", kwargs={"pk": self.cstage1.id}) + ) - self.assertRedirectToLogin(reverse( - 'avisstage:stage_publication', kwargs={'pk':self.cstage1.id})) - - self.assertRedirectToLogin(reverse( - 'avisstage:stage_publication', kwargs={'pk':self.astage1.id})) - - self.assertRedirectToLogin(reverse('avisstage:stage_ajout')) - - self.assertRedirectToLogin(reverse('avisstage:profil_edit')) + self.assertRedirectToLogin( + reverse("avisstage:stage_edit", kwargs={"pk": self.astage1.id}) + ) + self.assertRedirectToLogin( + reverse("avisstage:stage_publication", kwargs={"pk": self.cstage1.id}) + ) + self.assertRedirectToLogin( + reverse("avisstage:stage_publication", kwargs={"pk": self.astage1.id}) + ) + self.assertRedirectToLogin(reverse("avisstage:stage_ajout")) + self.assertRedirectToLogin(reverse("avisstage:profil_edit")) """ ACCÈS ARCHICUBE """ + + class ArchicubeViewsTest(ExperiENSTestCase): def setUp(self): super().setUp() # Connexion with password - self.client.login(username='archicube', password='archicube') + self.client.login(username="archicube", password="archicube") def assert403Archicubes(self, testurl): r = self.client.get(testurl) - return self.assertRedirects(r, reverse('avisstage:403-archicubes')) + return self.assertRedirects(r, reverse("avisstage:403-archicubes")) """ Vérifie que les seules fiches de stages visibles sont les siennes """ + def test_stage_visibility_archi(self): - self.assertPageNotFound(reverse('avisstage:stage', - kwargs={'pk':self.cstage1.id})) - - self.assertPageNotFound(reverse('avisstage:stage', - kwargs={'pk':self.cstage2.id})) - - testurl = reverse('avisstage:stage', - kwargs={'pk':self.astage1.id}) + self.assertPageNotFound( + reverse("avisstage:stage", kwargs={"pk": self.cstage1.id}) + ) + + self.assertPageNotFound( + reverse("avisstage:stage", kwargs={"pk": self.cstage2.id}) + ) + + testurl = reverse("avisstage:stage", kwargs={"pk": self.astage1.id}) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - """ Vérifie que le seul profil visible est le sien """ - def test_profil_visibility_archi(self): - self.assertPageNotFound(reverse( - 'avisstage:profil', kwargs={'username': self.u_conscrit.username})) - testurl = reverse('avisstage:profil', - kwargs={'username': self.u_archi.username}) + def test_profil_visibility_archi(self): + self.assertPageNotFound( + reverse("avisstage:profil", kwargs={"username": self.u_conscrit.username}) + ) + + testurl = reverse( + "avisstage:profil", kwargs={"username": self.u_archi.username} + ) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - """ Vérifie que la recherche n'est pas accessible """ + def test_pages_visibility_archi(self): - self.assert403Archicubes(reverse('avisstage:recherche')) - - self.assert403Archicubes(reverse('avisstage:recherche_resultats')) + self.assert403Archicubes(reverse("avisstage:recherche")) - self.assert403Archicubes(reverse('avisstage:stage_items')) + self.assert403Archicubes(reverse("avisstage:recherche_resultats")) - testurl = reverse('avisstage:feedback') - r = self.client.post(testurl, {"objet": "Contact", - "message": "Ceci est un texte"}) - self.assertRedirects(r, reverse('avisstage:index')) + self.assert403Archicubes(reverse("avisstage:stage_items")) - testurl = reverse('avisstage:moderation') + testurl = reverse("avisstage:feedback") + r = self.client.post( + testurl, {"objet": "Contact", "message": "Ceci est un texte"} + ) + self.assertRedirects(r, reverse("avisstage:index")) + + testurl = reverse("avisstage:moderation") r = self.client.get(testurl) - self.assertRedirects(r, reverse('admin:login')+"?next="+testurl) - + self.assertRedirects(r, reverse("admin:login") + "?next=" + testurl) """ Vérifie que la seule API accessible est celle des lieux """ + def test_api_visibility_archi(self): - testurl = reverse('avisstage:api_dispatch_list', - kwargs={"resource_name": "lieu", - "api_name": "v1"}) + testurl = reverse( + "avisstage:api_dispatch_list", + kwargs={"resource_name": "lieu", "api_name": "v1"}, + ) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - - testurl = reverse('avisstage:api_dispatch_list', - kwargs={"resource_name": "stage", - "api_name": "v1"}) - r = self.client.get(testurl) - self.assertEqual(r.status_code, 401) - - testurl = reverse('avisstage:api_dispatch_list', - kwargs={"resource_name": "profil", - "api_name": "v1"}) + + testurl = reverse( + "avisstage:api_dispatch_list", + kwargs={"resource_name": "stage", "api_name": "v1"}, + ) + r = self.client.get(testurl) + self.assertEqual(r.status_code, 401) + + testurl = reverse( + "avisstage:api_dispatch_list", + kwargs={"resource_name": "profil", "api_name": "v1"}, + ) r = self.client.get(testurl) self.assertEqual(r.status_code, 401) - """ Vérifie que le seul stage modifiable est le sien """ + def test_edit_visibility_archi(self): - testurl = reverse('avisstage:stage_edit', kwargs={'pk':self.cstage1.id}) + testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.cstage1.id}) r = self.client.get(testurl) self.assertEqual(r.status_code, 403) - testurl = reverse('avisstage:stage_edit', kwargs={'pk':self.astage1.id}) + testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.astage1.id}) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - testurl = reverse('avisstage:stage_publication', - kwargs={'pk':self.cstage1.id}) + testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.cstage1.id}) r = self.client.post(testurl, {"publier": True}) self.assertEqual(r.status_code, 403) - testurl = reverse('avisstage:stage_publication', - kwargs={'pk':self.astage1.id}) + testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.astage1.id}) r = self.client.post(testurl, {"publier": True}) - self.assertRedirects(r, reverse('avisstage:stage', - kwargs={"pk": self.astage1.id})) - - testurl = reverse('avisstage:stage_ajout') + self.assertRedirects( + r, reverse("avisstage:stage", kwargs={"pk": self.astage1.id}) + ) + + testurl = reverse("avisstage:stage_ajout") r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - - testurl = reverse('avisstage:profil_edit') + + testurl = reverse("avisstage:profil_edit") r = self.client.get(testurl) self.assertEqual(r.status_code, 200) @@ -320,7 +353,7 @@ class DeprecatedArchicubeViewsTest(ArchicubeViewsTest): fake_cas_client = FakeCASClient(cas_login="archicube", entrance_year=2012) mock_cas_client.return_value = fake_cas_client - + self.sa_archi = OldCASAccount( user=self.u_archi, cas_login="archicube", @@ -337,14 +370,14 @@ class DeprecatedArchicubeViewsTest(ArchicubeViewsTest): self.p_archi.save() # New connexion with password - self.client.login(username='archicube', password='archicube') - - + self.client.login(username="archicube", password="archicube") """ ACCÈS EN SCOLARITE """ + + class ScolariteViewsTest(ExperiENSTestCase): @mock.patch("authens.backends.get_cas_client") def setUp(self, mock_cas_client): @@ -352,16 +385,14 @@ class ScolariteViewsTest(ExperiENSTestCase): fake_cas_client = FakeCASClient(cas_login="vieuxcon", entrance_year=2017) mock_cas_client.return_value = fake_cas_client - + self.u_vieuxcon = User.objects.create_user( - 'vieuxcon', - 'vieuxcon@ens.fr', - 'vieuxcon' + "vieuxcon", "vieuxcon@ens.fr", "vieuxcon" ) self.p_vieuxcon = self.u_vieuxcon.profil - self.p_vieuxcon.nom="Vieux con" - self.p_vieuxcon.promotion="Poufsouffle 2017" - self.p_vieuxcon.bio="Je suis un vieux con encore en scolarité" + self.p_vieuxcon.nom = "Vieux con" + self.p_vieuxcon.promotion = "Poufsouffle 2017" + self.p_vieuxcon.bio = "Je suis un vieux con encore en scolarité" self.p_vieuxcon.save() self.sa_vieuxcon = CASAccount( @@ -370,16 +401,19 @@ class ScolariteViewsTest(ExperiENSTestCase): entrance_year=2017, ) self.sa_vieuxcon.save() - - self.vstage1 = Stage(auteur=self.p_vieuxcon, sujet="Oubliettes", - date_debut=date(2018, 5, 10), - date_fin=date(2018, 8, 26), - type_stage="recherche", - niveau_scol="M1", public=False) + + self.vstage1 = Stage( + auteur=self.p_vieuxcon, + sujet="Oubliettes", + date_debut=date(2018, 5, 10), + date_fin=date(2018, 8, 26), + type_stage="recherche", + niveau_scol="M1", + public=False, + ) self.vstage1.save() self.vstage1.matieres.add(self.matiere2) - alieu1 = AvisLieu(stage=self.vstage1, lieu=self.lieu2, - chapo="Pas si mal") + alieu1 = AvisLieu(stage=self.vstage1, lieu=self.lieu2, chapo="Pas si mal") alieu1.save() # Connexion through CAS @@ -389,134 +423,143 @@ class ScolariteViewsTest(ExperiENSTestCase): Vérifie que les seules fiches de stages visibles sont les siennes ou celles publiques """ + def test_stage_visibility_scolarite(self): - testurl = reverse('avisstage:stage', - kwargs={'pk':self.cstage1.id}) + testurl = reverse("avisstage:stage", kwargs={"pk": self.cstage1.id}) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - self.assertPageNotFound(reverse('avisstage:stage', - kwargs={'pk':self.cstage2.id})) - - testurl = reverse('avisstage:stage', - kwargs={'pk':self.vstage1.id}) + self.assertPageNotFound( + reverse("avisstage:stage", kwargs={"pk": self.cstage2.id}) + ) + + testurl = reverse("avisstage:stage", kwargs={"pk": self.vstage1.id}) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - """ Vérifie que tous les profils sont visibles """ + def test_profil_visibility_scolarite(self): - testurl = reverse('avisstage:profil', - kwargs={'username': self.u_conscrit.username}) + testurl = reverse( + "avisstage:profil", kwargs={"username": self.u_conscrit.username} + ) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - self.assertContains(r, "Wingardium Leviosa") # Public - self.assertNotContains(r, "Avada Kedavra") # Brouillon + self.assertContains(r, "Wingardium Leviosa") # Public + self.assertNotContains(r, "Avada Kedavra") # Brouillon - testurl = reverse('avisstage:profil', - kwargs={'username': self.u_archi.username}) + testurl = reverse( + "avisstage:profil", kwargs={"username": self.u_archi.username} + ) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - testurl = reverse('avisstage:profil', - kwargs={'username': self.u_vieuxcon.username}) + testurl = reverse( + "avisstage:profil", kwargs={"username": self.u_vieuxcon.username} + ) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - """ Vérifie que la recherche et les autres pages sont accessibles """ + def test_pages_visibility_scolarite(self): - testurl = reverse('avisstage:recherche') + testurl = reverse("avisstage:recherche") r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - testurl = reverse('avisstage:recherche_resultats') + testurl = reverse("avisstage:recherche_resultats") r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - self.assertContains(r, "Wingardium Leviosa") # Public - self.assertNotContains(r, "Avada Kedavra") # Brouillon + self.assertContains(r, "Wingardium Leviosa") # Public + self.assertNotContains(r, "Avada Kedavra") # Brouillon - testurl = reverse('avisstage:stage_items') + "?ids=" \ - + ";".join(("%d" % k.id) for k in [self.cstage1, - self.cstage2, - self.astage1]) + testurl = ( + reverse("avisstage:stage_items") + + "?ids=" + + ";".join( + ("%d" % k.id) for k in [self.cstage1, self.cstage2, self.astage1] + ) + ) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - self.assertContains(r, "Wingardium Leviosa") # Public - self.assertNotContains(r, "Avada Kedavra") # Brouillon + self.assertContains(r, "Wingardium Leviosa") # Public + self.assertNotContains(r, "Avada Kedavra") # Brouillon - testurl = reverse('avisstage:feedback') - r = self.client.post(testurl, {"objet": "Contact", - "message": "Ceci est un texte"}) - self.assertRedirects(r, reverse('avisstage:index')) + testurl = reverse("avisstage:feedback") + r = self.client.post( + testurl, {"objet": "Contact", "message": "Ceci est un texte"} + ) + self.assertRedirects(r, reverse("avisstage:index")) - testurl = reverse('avisstage:moderation') + testurl = reverse("avisstage:moderation") r = self.client.get(testurl) - self.assertRedirects(r, reverse('admin:login')+"?next="+testurl) - + self.assertRedirects(r, reverse("admin:login") + "?next=" + testurl) """ Vérifie que toutes les API sont accessibles et qu'elles ne montrent que les stages publics """ + def test_api_visibility_scolarite(self): - testurl = reverse('avisstage:api_dispatch_list', - kwargs={"resource_name": "lieu", - "api_name": "v1"}) - r = self.client.get(testurl) - self.assertEqual(r.status_code, 200) - - testurl = reverse('avisstage:api_dispatch_list', - kwargs={"resource_name": "stage", - "api_name": "v1"}) - r = self.client.get(testurl) - self.assertEqual(r.status_code, 200) - self.assertContains(r, "Wingardium Leviosa") # Public - self.assertNotContains(r, "Avada Kedavra") # Brouillon - - testurl = reverse('avisstage:api_dispatch_list', - kwargs={"resource_name": "profil", - "api_name": "v1"}) + testurl = reverse( + "avisstage:api_dispatch_list", + kwargs={"resource_name": "lieu", "api_name": "v1"}, + ) + r = self.client.get(testurl) + self.assertEqual(r.status_code, 200) + + testurl = reverse( + "avisstage:api_dispatch_list", + kwargs={"resource_name": "stage", "api_name": "v1"}, + ) + r = self.client.get(testurl) + self.assertEqual(r.status_code, 200) + self.assertContains(r, "Wingardium Leviosa") # Public + self.assertNotContains(r, "Avada Kedavra") # Brouillon + + testurl = reverse( + "avisstage:api_dispatch_list", + kwargs={"resource_name": "profil", "api_name": "v1"}, + ) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - """ Vérifie que le seul stage modifiable est le sien """ + def test_edit_visibility_scolarite(self): - testurl = reverse('avisstage:stage_edit', kwargs={'pk':self.cstage1.id}) + testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.cstage1.id}) r = self.client.get(testurl) self.assertEqual(r.status_code, 403) - testurl = reverse('avisstage:stage_edit', kwargs={'pk':self.astage1.id}) + testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.astage1.id}) r = self.client.get(testurl) self.assertEqual(r.status_code, 403) - - testurl = reverse('avisstage:stage_edit', kwargs={'pk':self.vstage1.id}) + + testurl = reverse("avisstage:stage_edit", kwargs={"pk": self.vstage1.id}) r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - testurl = reverse('avisstage:stage_publication', - kwargs={'pk':self.cstage1.id}) + testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.cstage1.id}) r = self.client.post(testurl, {"publier": True}) self.assertEqual(r.status_code, 403) - testurl = reverse('avisstage:stage_publication', - kwargs={'pk':self.vstage1.id}) + testurl = reverse("avisstage:stage_publication", kwargs={"pk": self.vstage1.id}) r = self.client.post(testurl, {"publier": True}) - self.assertRedirects(r, reverse('avisstage:stage', - kwargs={"pk": self.vstage1.id})) - - testurl = reverse('avisstage:stage_ajout') + self.assertRedirects( + r, reverse("avisstage:stage", kwargs={"pk": self.vstage1.id}) + ) + + testurl = reverse("avisstage:stage_ajout") r = self.client.get(testurl) self.assertEqual(r.status_code, 200) - - testurl = reverse('avisstage:profil_edit') + + testurl = reverse("avisstage:profil_edit") r = self.client.get(testurl) self.assertEqual(r.status_code, 200) diff --git a/avisstage/urls.py b/avisstage/urls.py index 93f0978..b7ce528 100644 --- a/avisstage/urls.py +++ b/avisstage/urls.py @@ -2,50 +2,62 @@ from django.urls import include, path from . import views, api from tastypie.api import Api -v1_api = Api(api_name='v1') +v1_api = Api(api_name="v1") v1_api.register(api.LieuResource()) v1_api.register(api.StageResource()) v1_api.register(api.AuteurResource()) app_name = "avisstage" urlpatterns = [ - path('', views.index, name='index'), - path('perso/', views.perso, name='perso'), - path('faq/', views.faq, name='faq'), - path('stage/nouveau/', views.manage_stage, name='stage_ajout'), - path('stage//', views.StageView.as_view(), name='stage'), - path('stage//edit/', views.manage_stage, name='stage_edit'), - path('stage//publication/', views.publier_stage, - name='stage_publication'), - path('403/archicubes/', views.archicubes_interdits, - name='403-archicubes'), - - path('lieu/save/', views.save_lieu, name='lieu_ajout'), - path('profil/show//', views.ProfilView.as_view(), - name='profil'), - path('profil/edit/', views.ProfilEdit.as_view(), name='profil_edit'), - path('profil/parametres/', views.MesParametres.as_view(), name='parametres'), - path('profil/emails//aconfirmer/', - views.AdresseAConfirmer.as_view(), name="emails_aconfirmer"), - path('profil/emails//supprime/', views.SupprimeAdresse.as_view(), - name="emails_supprime"), - path('profil/emails//reconfirme/', - views.ReConfirmeAdresse.as_view(), - name="emails_reconfirme"), - path('profil/emails//principal/', - views.RendAdressePrincipale.as_view(), name="emails_principal"), - path('profil/emails/confirme//', views.ConfirmeAdresse.as_view(), - name="emails_confirme"), - path('profil/mdp/demande/', - views.EnvoieLienMotDePasse.as_view(), name="mdp_demande"), - path('profil/mdp///', - views.DefinirMotDePasse.as_view(), name="mdp_edit"), - - path('recherche/', views.recherche, name='recherche'), - path('recherche/resultats/', views.recherche_resultats, - name='recherche_resultats'), - path('recherche/items/', views.stage_items, name='stage_items'), - path('feedback/', views.feedback, name='feedback'), - path('moderation/', views.statistiques, name='moderation'), - path('api/', include(v1_api.urls)), + path("", views.index, name="index"), + path("perso/", views.perso, name="perso"), + path("faq/", views.faq, name="faq"), + path("stage/nouveau/", views.manage_stage, name="stage_ajout"), + path("stage//", views.StageView.as_view(), name="stage"), + path("stage//edit/", views.manage_stage, name="stage_edit"), + path("stage//publication/", views.publier_stage, name="stage_publication"), + path("403/archicubes/", views.archicubes_interdits, name="403-archicubes"), + path("lieu/save/", views.save_lieu, name="lieu_ajout"), + path("profil/show//", views.ProfilView.as_view(), name="profil"), + path("profil/edit/", views.ProfilEdit.as_view(), name="profil_edit"), + path("profil/parametres/", views.MesParametres.as_view(), name="parametres"), + path( + "profil/emails//aconfirmer/", + views.AdresseAConfirmer.as_view(), + name="emails_aconfirmer", + ), + path( + "profil/emails//supprime/", + views.SupprimeAdresse.as_view(), + name="emails_supprime", + ), + path( + "profil/emails//reconfirme/", + views.ReConfirmeAdresse.as_view(), + name="emails_reconfirme", + ), + path( + "profil/emails//principal/", + views.RendAdressePrincipale.as_view(), + name="emails_principal", + ), + path( + "profil/emails/confirme//", + views.ConfirmeAdresse.as_view(), + name="emails_confirme", + ), + path( + "profil/mdp/demande/", views.EnvoieLienMotDePasse.as_view(), name="mdp_demande" + ), + path( + "profil/mdp///", + views.DefinirMotDePasse.as_view(), + name="mdp_edit", + ), + path("recherche/", views.recherche, name="recherche"), + path("recherche/resultats/", views.recherche_resultats, name="recherche_resultats"), + path("recherche/items/", views.stage_items, name="stage_items"), + path("feedback/", views.feedback, name="feedback"), + path("moderation/", views.statistiques, name="moderation"), + path("api/", include(v1_api.urls)), ] diff --git a/avisstage/utils.py b/avisstage/utils.py index 822f8ae..3ae2d28 100644 --- a/avisstage/utils.py +++ b/avisstage/utils.py @@ -1,21 +1,25 @@ from functools import reduce from math import cos, radians, sqrt -def choices_length (choices): - return reduce (lambda m, choice: max (m, len (choice[0])), choices, 0) + +def choices_length(choices): + return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0) + def en_scolarite(user): return user.profil.en_scolarite + def approximate_distance(a, b): lat_a = radians(a.y) lat_b = radians(b.y) dlon = radians(b.x - a.x) - dlon = dlon * cos((lat_a + lat_b)/2) - dlat = (lat_a - lat_b) - distance = 6371000 * sqrt(dlon*dlon + dlat*dlat) + dlon = dlon * cos((lat_a + lat_b) / 2) + dlat = lat_a - lat_b + distance = 6371000 * sqrt(dlon * dlon + dlat * dlat) return distance + def is_email_ens(mail, none=False): if mail is None: return none diff --git a/avisstage/views.py b/avisstage/views.py index 897b124..57fcaba 100644 --- a/avisstage/views.py +++ b/avisstage/views.py @@ -3,8 +3,14 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.views.generic import ( - DetailView, ListView, UpdateView, CreateView, TemplateView, DeleteView, - FormView, View + DetailView, + ListView, + UpdateView, + CreateView, + TemplateView, + DeleteView, + FormView, + View, ) from django.views.generic.detail import SingleObjectMixin from django import forms @@ -24,8 +30,13 @@ from simple_email_confirmation.models import EmailAddress from .models import Normalien, Stage, Lieu, AvisLieu, AvisStage from .forms import ( - StageForm, LieuForm, AvisStageForm, AvisLieuForm, FeedbackForm, AdresseEmailForm, - ReinitMdpForm + StageForm, + LieuForm, + AvisStageForm, + AvisLieuForm, + FeedbackForm, + AdresseEmailForm, + ReinitMdpForm, ) from .utils import en_scolarite @@ -40,8 +51,8 @@ import random, math # Page d'accueil def index(request): num_stages = Stage.objects.filter(public=True).count() - return render(request, 'avisstage/index.html', - {"num_stages": num_stages}) + return render(request, "avisstage/index.html", {"num_stages": num_stages}) + # Espace personnel @login_required @@ -53,40 +64,46 @@ def perso(request): profil, created = Normalien.objects.get_or_create(user=request.user) profil.save() - return render(request, 'avisstage/perso.html') + return render(request, "avisstage/perso.html") + # 403 Archicubes @login_required def archicubes_interdits(request): - return render(request, 'avisstage/403-archicubes.html') + return render(request, "avisstage/403-archicubes.html") + # Profil -#login_required +# login_required class ProfilView(LoginRequiredMixin, DetailView): model = Normalien - template_name = 'avisstage/detail/profil.html' + template_name = "avisstage/detail/profil.html" # Récupération du profil def get_object(self): - + # Restriction d'accès pour les archicubes - if (en_scolarite(self.request.user) or - self.kwargs.get('username') == self.request.user.username): + if ( + en_scolarite(self.request.user) + or self.kwargs.get("username") == self.request.user.username + ): return get_object_or_404( - Normalien, user__username=self.kwargs.get('username')) + Normalien, user__username=self.kwargs.get("username") + ) else: raise Http404 + # Stage -#login_required +# login_required class StageView(LoginRequiredMixin, DetailView): model = Stage - template_name = 'avisstage/detail/stage.html' + template_name = "avisstage/detail/stage.html" # Restriction aux stages publics ou personnels def get_queryset(self): filtre = Q(auteur__user_id=self.request.user.id) - + # Restriction d'accès pour les archicubes if en_scolarite(self.request.user): filtre |= Q(public=True) @@ -95,30 +112,33 @@ class StageView(LoginRequiredMixin, DetailView): def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) - context['MAPBOX_API_KEY'] = settings.MAPBOX_API_KEY + context["MAPBOX_API_KEY"] = settings.MAPBOX_API_KEY return context + # FAQ def faq(request): - return render(request, 'avisstage/faq.html') + return render(request, "avisstage/faq.html") + # # EDITION # # Profil -#login_required +# login_required class ProfilEdit(LoginRequiredMixin, UpdateView): model = Normalien - fields = ['nom', 'promotion', 'contactez_moi', 'bio'] - template_name = 'avisstage/formulaires/profil.html' + fields = ["nom", "promotion", "contactez_moi", "bio"] + template_name = "avisstage/formulaires/profil.html" # Limitation à son propre profil def get_object(self): return self.request.user.profil - + def get_success_url(self): - return reverse('avisstage:perso') + return reverse("avisstage:perso") + # Stage @login_required @@ -129,8 +149,9 @@ def manage_stage(request, pk=None): stage = Stage(auteur=request.user.profil) avis_stage = AvisStage(stage=stage) c_del = False - last_creation = Stage.objects.filter(auteur=request.user.profil)\ - .order_by("-date_creation")[:1] + last_creation = Stage.objects.filter(auteur=request.user.profil).order_by( + "-date_creation" + )[:1] if len(last_creation) != 0: last_maj = last_creation[0].date_creation else: @@ -144,57 +165,71 @@ def manage_stage(request, pk=None): # Formset pour les avis des lieux AvisLieuFormSet = forms.inlineformset_factory( - Stage, AvisLieu, form=AvisLieuForm, can_delete=c_del, extra=0) + Stage, AvisLieu, form=AvisLieuForm, can_delete=c_del, extra=0 + ) if request.method == "POST": # Lecture des données form = StageForm(request.POST, request=request, instance=stage, prefix="stage") - avis_stage_form = AvisStageForm(request.POST, - instance=avis_stage, prefix="avis") - avis_lieu_formset = AvisLieuFormSet(request.POST, instance=stage, - prefix="lieux") + avis_stage_form = AvisStageForm( + request.POST, instance=avis_stage, prefix="avis" + ) + avis_lieu_formset = AvisLieuFormSet( + request.POST, instance=stage, prefix="lieux" + ) # Validation et enregistrement - if (form.is_valid() and - avis_stage_form.is_valid() and - avis_lieu_formset.is_valid()): + if ( + form.is_valid() + and avis_stage_form.is_valid() + and avis_lieu_formset.is_valid() + ): stage = form.save() avis_stage_form.instance.stage = stage avis_stage_form.save() avis_lieu_formset.save() - #print(request.POST) + # print(request.POST) if "continuer" in request.POST: if pk is None: - return redirect(reverse('avisstage:stage_edit',kwargs={'pk':stage.id})) + return redirect( + reverse("avisstage:stage_edit", kwargs={"pk": stage.id}) + ) else: - return redirect(reverse('avisstage:stage', - kwargs={'pk':stage.id})) + return redirect(reverse("avisstage:stage", kwargs={"pk": stage.id})) else: form = StageForm(instance=stage, prefix="stage") avis_stage_form = AvisStageForm(instance=avis_stage, prefix="avis") avis_lieu_formset = AvisLieuFormSet(instance=stage, prefix="lieux") # Affichage du formulaire - return render(request, "avisstage/formulaires/stage.html", - {'form': form, 'avis_stage_form': avis_stage_form, - 'avis_lieu_formset': avis_lieu_formset, - 'creation': pk is None, "last_maj": last_maj, - 'GOOGLE_API_KEY': settings.GOOGLE_API_KEY, - 'MAPBOX_API_KEY': settings.MAPBOX_API_KEY}) + return render( + request, + "avisstage/formulaires/stage.html", + { + "form": form, + "avis_stage_form": avis_stage_form, + "avis_lieu_formset": avis_lieu_formset, + "creation": pk is None, + "last_maj": last_maj, + "GOOGLE_API_KEY": settings.GOOGLE_API_KEY, + "MAPBOX_API_KEY": settings.MAPBOX_API_KEY, + }, + ) + # Ajout d'un lieu de stage -#login_required +# login_required # Stage @login_required def save_lieu(request): normalien = request.user.profil - + if request.method == "POST": pk = request.POST.get("id", None) - #print(request.POST) + # print(request.POST) jitter = False - if pk is None or pk == '': + if pk is None or pk == "": lieu = Lieu() else: # Modification du lieu @@ -208,7 +243,7 @@ def save_lieu(request): lieu = Lieu() # Servira à bouger un peu le lieu jitter = True - + # Lecture des données form = LieuForm(request.POST, instance=lieu) @@ -217,51 +252,54 @@ def save_lieu(request): lieu = form.save(commit=False) if jitter: cdx, cdy = lieu.coord.get_coords() - ang = random.random() * 6.29; + ang = random.random() * 6.29 rad = (random.random() + 0.5) * 3e-4 - cdx += math.cos(ang) * rad; - cdy += math.sin(ang) * rad; + cdx += math.cos(ang) * rad + cdy += math.sin(ang) * rad lieu.coord.set_coords((cdx, cdy)) lieu.save() # Élimination des doublons if pk is None or pk == "": - olieux = Lieu.objects.filter(nom=lieu.nom, coord__distance_lte=(lieu.coord, 10)) + olieux = Lieu.objects.filter( + nom=lieu.nom, coord__distance_lte=(lieu.coord, 10) + ) for olieu in olieux: - if olieu.type_lieu == lieu.type_lieu and \ - olieu.ville == lieu.ville and \ - olieu.pays == lieu.pays: + if ( + olieu.type_lieu == lieu.type_lieu + and olieu.ville == lieu.ville + and olieu.pays == lieu.pays + ): return JsonResponse({"success": True, "id": olieu.id}) - + lieu.save() return JsonResponse({"success": True, "id": lieu.id}) else: - return JsonResponse({"success": False, - "errors": form.errors}) + return JsonResponse({"success": False, "errors": form.errors}) else: return JsonResponse({"erreur": "Aucune donnée POST"}) + class LieuAjout(LoginRequiredMixin, CreateView): model = Lieu form_class = LieuForm - template_name = 'avisstage/formulaires/lieu.html' + template_name = "avisstage/formulaires/lieu.html" # Retourne d'un JSON si requête AJAX def form_valid(self, form): if self.request.GET.get("format", "") == "json": self.object = form.save() - return JsonResponse({"success": True, - "id": self.object.id}) + return JsonResponse({"success": True, "id": self.object.id}) else: super(LieuAjout, self).form_valid(form) def form_invalid(self, form): if self.request.GET.get("format", "") == "json": - return JsonResponse({"success": False, - "errors": form.errors}) + return JsonResponse({"success": False, "errors": form.errors}) else: super(LieuAjout, self).form_valid(form) + # Passage d'un stage en mode public @login_required def publier_stage(request, pk): @@ -280,27 +318,28 @@ def publier_stage(request, pk): stage.public = False stage.save() - + return redirect(reverse("avisstage:stage", kwargs={"pk": pk})) + # # FEEDBACK # + @login_required def feedback(request): if request.method == "POST": form = FeedbackForm(request.POST) if form.is_valid(): - objet = form.cleaned_data['objet'] - header = "[From : %s <%s>]\n" % (request.user, - request.user.email) - message = header + form.cleaned_data['message'] + objet = form.cleaned_data["objet"] + header = "[From : %s <%s>]\n" % (request.user, request.user.email) + message = header + form.cleaned_data["message"] send_mail( - "[experiENS] "+ objet, + "[experiENS] " + objet, message, request.user.email, - ['robin.champenois@ens.fr'], + ["robin.champenois@ens.fr"], fail_silently=False, ) if request.GET.get("format", None) == "json": @@ -308,8 +347,7 @@ def feedback(request): return redirect(reverse("avisstage:index")) else: if request.GET.get("format", None) == "json": - return JsonResponse({"success": False, - "errors": form.errors}) + return JsonResponse({"success": False, "errors": form.errors}) else: form = FeedbackForm() raise Http404() @@ -319,62 +357,95 @@ def feedback(request): # STATISTIQUES # + @login_required @staff_member_required def statistiques(request): nstages = Stage.objects.count() npubstages = Stage.objects.filter(public=True).count() - nbymatiere_raw = Stage.objects.values('matieres__nom', 'public').annotate(scount=Count('matieres__nom')) + nbymatiere_raw = Stage.objects.values("matieres__nom", "public").annotate( + scount=Count("matieres__nom") + ) nbymatiere = defaultdict(dict) for npm in nbymatiere_raw: - nbymatiere[npm["matieres__nom"]]["publics" if npm["public"] else "drafts"] = npm["scount"] + nbymatiere[npm["matieres__nom"]][ + "publics" if npm["public"] else "drafts" + ] = npm["scount"] for mat, npm in nbymatiere.items(): npm["matiere"] = mat - nbymatiere = sorted(list(nbymatiere.values()), key=lambda npm: -npm.get("publics", 0)) - nbylength = [("Vide", Stage.objects.filter(len_avis_stage__lt=5).count(), - Stage.objects.filter(len_avis_lieux__lt=5).count()), - ("Court", Stage.objects.filter(len_avis_stage__lt=30, len_avis_stage__gt=4).count(), - Stage.objects.filter(len_avis_lieux__lt=30, len_avis_lieux__gt=4).count()), - ("Moyen", Stage.objects.filter(len_avis_stage__lt=100, len_avis_stage__gt=29).count(), - Stage.objects.filter(len_avis_lieux__lt=100, len_avis_lieux__gt=29).count()), - ("Long", Stage.objects.filter(len_avis_stage__gt=99).count(), - Stage.objects.filter(len_avis_lieux__gt=99).count())] + nbymatiere = sorted( + list(nbymatiere.values()), key=lambda npm: -npm.get("publics", 0) + ) + nbylength = [ + ( + "Vide", + Stage.objects.filter(len_avis_stage__lt=5).count(), + Stage.objects.filter(len_avis_lieux__lt=5).count(), + ), + ( + "Court", + Stage.objects.filter(len_avis_stage__lt=30, len_avis_stage__gt=4).count(), + Stage.objects.filter(len_avis_lieux__lt=30, len_avis_lieux__gt=4).count(), + ), + ( + "Moyen", + Stage.objects.filter(len_avis_stage__lt=100, len_avis_stage__gt=29).count(), + Stage.objects.filter(len_avis_lieux__lt=100, len_avis_lieux__gt=29).count(), + ), + ( + "Long", + Stage.objects.filter(len_avis_stage__gt=99).count(), + Stage.objects.filter(len_avis_lieux__gt=99).count(), + ), + ] nusers = Normalien.objects.count() nauts = Normalien.objects.filter(stages__isnull=False).distinct().count() - nbyaut = Counter(Normalien.objects.filter(stages__isnull=False).annotate(scount=Count('stages')).values_list('scount', flat="True")).items() + nbyaut = Counter( + Normalien.objects.filter(stages__isnull=False) + .annotate(scount=Count("stages")) + .values_list("scount", flat="True") + ).items() nlieux = Lieu.objects.filter(stages__isnull=False).distinct().count() - return render(request, 'avisstage/moderation/statistiques.html', - {'num_stages': nstages, - 'num_stages_pub': npubstages, - 'num_par_matiere': nbymatiere, - 'num_users': nusers, - 'num_auteurs': nauts, - 'num_par_auteur': nbyaut, - 'num_lieux_utiles': nlieux, - 'num_par_longueur': nbylength, - }) + return render( + request, + "avisstage/moderation/statistiques.html", + { + "num_stages": nstages, + "num_stages_pub": npubstages, + "num_par_matiere": nbymatiere, + "num_users": nusers, + "num_auteurs": nauts, + "num_par_auteur": nbyaut, + "num_lieux_utiles": nlieux, + "num_par_longueur": nbylength, + }, + ) + # # Compte # + class MesAdressesMixin(LoginRequiredMixin): slug_url_kwarg = "email" slug_field = "email" confirmed_only = False - + def get_queryset(self, *args, **kwargs): qs = self.request.user.email_address_set.all() if self.confirmed_only: qs = qs.filter(confirmed_at__isnull=False) return qs + def _send_confirm_mail(email, request): confirm_url = request.build_absolute_uri( - reverse("avisstage:emails_confirme", kwargs={"key": email.key})) + reverse("avisstage:emails_confirme", kwargs={"key": email.key}) + ) send_mail( "[ExperiENS] Confirmez votre adresse a-mail", -"""Bonjour, + """Bonjour, Vous venez d'ajouter cette adresse e-mail à votre compte ExperiENS. @@ -383,14 +454,18 @@ Pour la vérifier, merci de cliquer sur le lien suivant, ou de copier l'adresse {confirm_url} Cordialement, -L'équipe ExperiENS""".format(confirm_url=confirm_url), - 'experiens-nepasrepondre@eleves.ens.fr', +L'équipe ExperiENS""".format( + confirm_url=confirm_url + ), + "experiens-nepasrepondre@eleves.ens.fr", [email.email], fail_silently=False, ) - return redirect(reverse("avisstage:emails_aconfirmer", - kwargs={"email": email.email})) - + return redirect( + reverse("avisstage:emails_aconfirmer", kwargs={"email": email.email}) + ) + + class MesParametres(LoginRequiredMixin, FormView): model = EmailAddress template_name = "avisstage/compte/parametres.html" @@ -400,16 +475,18 @@ class MesParametres(LoginRequiredMixin, FormView): kwargs = super().get_form_kwargs(*args, **kwargs) kwargs["_user"] = self.request.user return kwargs - + def form_valid(self, form): new = EmailAddress.objects.create_unconfirmed( - form.cleaned_data["email"], self.request.user) + form.cleaned_data["email"], self.request.user + ) return _send_confirm_mail(new, self.request) - + + class RendAdressePrincipale(MesAdressesMixin, SingleObjectMixin, View): model = EmailAddress confirmed_only = True - + def post(self, *args, **kwargs): if not hasattr(self, "object"): self.object = self.get_object() @@ -417,10 +494,12 @@ class RendAdressePrincipale(MesAdressesMixin, SingleObjectMixin, View): self.request.user.save() return redirect(reverse("avisstage:parametres")) + class AdresseAConfirmer(MesAdressesMixin, DetailView): model = EmailAddress template_name = "avisstage/compte/aconfirmer.html" - + + class ReConfirmeAdresse(MesAdressesMixin, DetailView): model = EmailAddress @@ -430,18 +509,23 @@ class ReConfirmeAdresse(MesAdressesMixin, DetailView): return _send_confirm_mail(email, self.request) return redirect(reverse("avisstage:parametres")) + class ConfirmeAdresse(LoginRequiredMixin, View): def get(self, *args, **kwargs): try: - email = EmailAddress.objects.confirm(self.kwargs["key"], - self.request.user, True) + email = EmailAddress.objects.confirm( + self.kwargs["key"], self.request.user, True + ) except Exception as e: raise Http404() messages.add_message( - self.request, messages.SUCCESS, - "L'adresse email {email} a bien été confirmée".format(email=email.email)) + self.request, + messages.SUCCESS, + "L'adresse email {email} a bien été confirmée".format(email=email.email), + ) return redirect(reverse("avisstage:parametres")) + class SupprimeAdresse(MesAdressesMixin, DeleteView): model = EmailAddress template_name = "avisstage/compte/email_supprime.html" @@ -451,21 +535,26 @@ class SupprimeAdresse(MesAdressesMixin, DeleteView): qs = super().get_queryset(*args, **kwargs) return qs.exclude(email=self.request.user.email) + class EnvoieLienMotDePasse(LoginRequiredMixin, View): def post(self, *args, **kwargs): form = ReinitMdpForm({"email": self.request.user.email}) form.is_valid() form.save( - email_template_name = 'avisstage/mails/reinit_mdp.html', - from_email = 'experiens-nepasrepondre@eleves.ens.fr', - subject_template_name = 'avisstage/mails/reinit_mdp.txt', + email_template_name="avisstage/mails/reinit_mdp.html", + from_email="experiens-nepasrepondre@eleves.ens.fr", + subject_template_name="avisstage/mails/reinit_mdp.txt", ) messages.add_message( - self.request, messages.INFO, - "Un mail a été envoyé à {email}. Merci de vérifier vos indésirables si vous ne le recevez pas bientôt".format(email=self.request.user.email) + self.request, + messages.INFO, + "Un mail a été envoyé à {email}. Merci de vérifier vos indésirables si vous ne le recevez pas bientôt".format( + email=self.request.user.email + ), ) return redirect(reverse("avisstage:parametres")) + class DefinirMotDePasse(PasswordResetConfirmView): template_name = "avisstage/compte/edit_mdp.html" success_url = reverse_lazy("avisstage:perso") @@ -475,4 +564,3 @@ class DefinirMotDePasse(PasswordResetConfirmView): if self.request.user.is_authenticated and user != self.request.user: raise Http404("Ce token n'est pas valide pour votre compte") return user - diff --git a/avisstage/views_search.py b/avisstage/views_search.py index 12b9839..ca0ccf5 100644 --- a/avisstage/views_search.py +++ b/avisstage/views_search.py @@ -29,28 +29,36 @@ logger = logging.getLogger("recherche") # Recherche class SearchForm(forms.Form): generique = forms.CharField(required=False) - sujet = forms.CharField(label=u'À propos de', required=False) - contexte = forms.CharField(label=u'Contexte (lieu, encadrant⋅e⋅s, structure)', - required=False) - - apres_annee = forms.IntegerField(label=u'Après cette année', required=False) - avant_annee = forms.IntegerField(label=u'Avant cette année', required=False) + sujet = forms.CharField(label=u"À propos de", required=False) + contexte = forms.CharField( + label=u"Contexte (lieu, encadrant⋅e⋅s, structure)", required=False + ) - type_stage = forms.ChoiceField(label="Type de stage", choices=([('', u'')] - + list(TYPE_STAGE_OPTIONS)), - required=False) - niveau_scol = forms.ChoiceField(label="Année d'étude", choices=([('', u'')] - + list(NIVEAU_SCOL_OPTIONS)), - required=False) - - type_lieu = forms.ChoiceField(label="Type de lieu d'accueil", - choices=([('', u'')] - + list(TYPE_LIEU_OPTIONS)), - required=False) - tri = forms.ChoiceField(label=u'Tri par', - choices=[('pertinence', u'Pertinence'), - ('-date_maj',u'Dernière mise à jour')], - required=False, initial='pertinence') + apres_annee = forms.IntegerField(label=u"Après cette année", required=False) + avant_annee = forms.IntegerField(label=u"Avant cette année", required=False) + + type_stage = forms.ChoiceField( + label="Type de stage", + choices=([("", u"")] + list(TYPE_STAGE_OPTIONS)), + required=False, + ) + niveau_scol = forms.ChoiceField( + label="Année d'étude", + choices=([("", u"")] + list(NIVEAU_SCOL_OPTIONS)), + required=False, + ) + + type_lieu = forms.ChoiceField( + label="Type de lieu d'accueil", + choices=([("", u"")] + list(TYPE_LIEU_OPTIONS)), + required=False, + ) + tri = forms.ChoiceField( + label=u"Tri par", + choices=[("pertinence", u"Pertinence"), ("-date_maj", u"Dernière mise à jour")], + required=False, + initial="pertinence", + ) def cherche(**kwargs): @@ -58,9 +66,11 @@ def cherche(**kwargs): use_dsl = False def field_relevant(field, test_string=True): - return field in kwargs and \ - kwargs[field] is not None and \ - ((not test_string) or kwargs[field].strip() != '') + return ( + field in kwargs + and kwargs[field] is not None + and ((not test_string) or kwargs[field].strip() != "") + ) if USE_ELASTICSEARCH: dsl = StageDocument.search() @@ -71,28 +81,36 @@ def cherche(**kwargs): # Champ générique : recherche dans tous les champs if field_relevant("generique"): - #print("Filtre generique", kwargs['generique']) + # print("Filtre generique", kwargs['generique']) dsl = dsl.query( - "match", - _all={"query": kwargs["generique"], - "fuzziness": "auto"}) + "match", _all={"query": kwargs["generique"], "fuzziness": "auto"} + ) use_dsl = True # Sujet -> Recherche dan les noms de sujets et les thématiques if field_relevant("sujet"): - dsl = dsl.query("multi_match", - query = kwargs["sujet"], - fields = ['sujet^2', 'thematiques', 'matieres'], - fuzziness = "auto") + dsl = dsl.query( + "multi_match", + query=kwargs["sujet"], + fields=["sujet^2", "thematiques", "matieres"], + fuzziness="auto", + ) use_dsl = True # Contexte -> Encadrants, structure, lieu if field_relevant("contexte"): - dsl = dsl.query("multi_match", - query = kwargs["contexte"], - fields = ['encadrants', 'structure^2', - 'lieux.nom', 'lieux.pays', 'lieux.ville'], - fuzziness = "auto") + dsl = dsl.query( + "multi_match", + query=kwargs["contexte"], + fields=[ + "encadrants", + "structure^2", + "lieux.nom", + "lieux.pays", + "lieux.ville", + ], + fuzziness="auto", + ) use_dsl = True else: @@ -100,71 +118,74 @@ def cherche(**kwargs): # recherche en base de données if field_relevant("generique"): generique = kwargs["generique"] - filtres = (Q(sujet__icontains=generique) - | Q(thematiques__name__icontains=generique) - | Q(matieres__nom__icontains=generique) - | Q(lieux__nom__icontains=generique)) + filtres = ( + Q(sujet__icontains=generique) + | Q(thematiques__name__icontains=generique) + | Q(matieres__nom__icontains=generique) + | Q(lieux__nom__icontains=generique) + ) - # Autres champs -> non fonctionnels + # Autres champs -> non fonctionnels if field_relevant("sujet") or field_relevant("contexte"): raise NotImplementedError( - "ElasticSearch doit être activé pour ce type de recherche") + "ElasticSearch doit être activé pour ce type de recherche" + ) # # Filtres directs db # - + # Dates - if field_relevant('avant_annee', False): - dte = date(kwargs['avant_annee']+1, 1, 1) + if field_relevant("avant_annee", False): + dte = date(kwargs["avant_annee"] + 1, 1, 1) filtres &= Q(date_fin__lt=dte) - if field_relevant('apres_annee', False): - dte = date(kwargs['apres_annee'], 1, 1) + if field_relevant("apres_annee", False): + dte = date(kwargs["apres_annee"], 1, 1) filtres &= Q(date_debut__gte=dte) # Type de stage - if field_relevant('type_stage'): + if field_relevant("type_stage"): filtres &= Q(type_stage=kwargs["type_stage"]) - - if field_relevant('niveau_scol'): + + if field_relevant("niveau_scol"): filtres &= Q(niveau_scol=kwargs["niveau_scol"]) # Type de lieu - if field_relevant('type_lieu'): + if field_relevant("type_lieu"): filtres &= Q(lieux__type_lieu=kwargs["type_lieu"]) - # Application if USE_ELASTICSEARCH and use_dsl: filtres &= Q(id__in=[s.meta.id for s in dsl.scan()]) - #print(filtres) + # print(filtres) resultat = Stage.objects.filter(filtres) - tri = 'pertinence' + tri = "pertinence" if not use_dsl: - kwargs['tri'] = '-date_maj' - - if field_relevant('tri') and kwargs['tri'] in ['-date_maj']: - tri = kwargs['tri'] + kwargs["tri"] = "-date_maj" + + if field_relevant("tri") and kwargs["tri"] in ["-date_maj"]: + tri = kwargs["tri"] resultat = resultat.order_by(tri) return resultat, tri + @login_required @en_scolarite_required def recherche(request): form = SearchForm() - return render(request, 'avisstage/recherche/recherche.html', - {"form": form}) + return render(request, "avisstage/recherche/recherche.html", {"form": form}) + @login_required @en_scolarite_required def recherche_resultats(request): stages = [] - tri = '' - vue = 'vue-liste' + tri = "" + vue = "vue-liste" lieux = [] stageids = [] if request.method == "GET": @@ -174,17 +195,22 @@ def recherche_resultats(request): search_args = form.cleaned_data # Gestion du cache - lsearch_args = {key: val for key, val in search_args.items() - if val != "" and val is not None} + lsearch_args = { + key: val + for key, val in search_args.items() + if val != "" and val is not None + } cache_key = json.dumps(lsearch_args, sort_keys=True) cached = cache.get(cache_key) if cached is None: # Requête effective stages, tri = cherche(**search_args) - stageids = list(stages.values_list('id', flat=True)) - lieux = [[stageid, lieuid] for (stageid, lieuid) - in stages.values_list('id', 'lieux') - if lieuid is not None] + stageids = list(stages.values_list("id", flat=True)) + lieux = [ + [stageid, lieuid] + for (stageid, lieuid) in stages.values_list("id", "lieux") + if lieuid is not None + ] # Sauvegarde dans le cache to_cache = {"stages": stageids, "lieux": lieux, "tri": tri} @@ -205,42 +231,55 @@ def recherche_resultats(request): stageids = [] if cached is None: - stages = stages[max(0, stageids.start_index()-1): - stageids.end_index()] + stages = stages[ + max(0, stageids.start_index() - 1) : stageids.end_index() + ] else: - orderer = Case(*[When(pk=pk, then=pos) - for pos, pk in enumerate(stageids)]) + orderer = Case( + *[When(pk=pk, then=pos) for pos, pk in enumerate(stageids)] + ) stages = Stage.objects.filter(id__in=stageids).order_by(orderer) - stages = stages.prefetch_related('lieux', 'auteur', - 'matieres', 'thematiques') + stages = stages.prefetch_related( + "lieux", "auteur", "matieres", "thematiques" + ) else: form = SearchForm() if stages: - vue = 'vue-hybride' + vue = "vue-hybride" # Version JSON pour recherche dynamique if request.GET.get("format") == "json": - return JsonResponse({"stages": stages, "page": page, - "num_pages": paginator.num_pages}) - - template_name = 'avisstage/recherche/resultats.html' + return JsonResponse( + {"stages": stages, "page": page, "num_pages": paginator.num_pages} + ) + + template_name = "avisstage/recherche/resultats.html" if request.GET.get("format") == "raw": - template_name = 'avisstage/recherche/stage_items.html' - return render(request, template_name, - {"form": form, "stages": stages, "paginator": stageids, - "tri": tri, "vue": vue, "lieux": lieux, - "MAPBOX_API_KEY": settings.MAPBOX_API_KEY}) + template_name = "avisstage/recherche/stage_items.html" + return render( + request, + template_name, + { + "form": form, + "stages": stages, + "paginator": stageids, + "tri": tri, + "vue": vue, + "lieux": lieux, + "MAPBOX_API_KEY": settings.MAPBOX_API_KEY, + }, + ) + @login_required @en_scolarite_required def stage_items(request): try: - stageids = [int(a) for a in request.GET.get("ids", "").split(';')] + stageids = [int(a) for a in request.GET.get("ids", "").split(";")] except ValueError: return HttpResponseBadRequest("Paramètre incorrect") - stages = Stage.objects.filter(id__in=stageids)\ - .prefetch_related('lieux', 'auteur', - 'matieres', 'thematiques') - return render(request, 'avisstage/recherche/stage_items.html', - {"stages": stages}) + stages = Stage.objects.filter(id__in=stageids).prefetch_related( + "lieux", "auteur", "matieres", "thematiques" + ) + return render(request, "avisstage/recherche/stage_items.html", {"stages": stages}) diff --git a/avisstage/widgets.py b/avisstage/widgets.py index 020807f..e3487d6 100644 --- a/avisstage/widgets.py +++ b/avisstage/widgets.py @@ -1,14 +1,14 @@ from django import forms from django.core import validators + class LatLonWidget(forms.MultiWidget): """ A Widget that splits Point input into two latitude/longitude boxes. """ def __init__(self, attrs=None, date_format=None, time_format=None): - widgets = (forms.HiddenInput(attrs=attrs), - forms.HiddenInput(attrs=attrs)) + widgets = (forms.HiddenInput(attrs=attrs), forms.HiddenInput(attrs=attrs)) super(LatLonWidget, self).__init__(widgets, attrs) def decompress(self, value): @@ -23,13 +23,15 @@ class LatLonField(forms.MultiValueField): srid = 4326 default_error_messages = { - 'invalid_latitude' : (u'Entrez une latitude valide.'), - 'invalid_longitude' : (u'Entrez une longitude valide.'), + "invalid_latitude": (u"Entrez une latitude valide."), + "invalid_longitude": (u"Entrez une longitude valide."), } def __init__(self, *args, **kwargs): - fields = (forms.FloatField(min_value=-90, max_value=90), - forms.FloatField(min_value=-180, max_value=180)) + fields = ( + forms.FloatField(min_value=-90, max_value=90), + forms.FloatField(min_value=-180, max_value=180), + ) super(LatLonField, self).__init__(fields, *args, **kwargs) def compress(self, data_list): @@ -37,11 +39,11 @@ class LatLonField(forms.MultiValueField): # Raise a validation error if latitude or longitude is empty # (possible if LatLongField has required=False). if data_list[0] in validators.EMPTY_VALUES: - raise forms.ValidationError(self.error_messages['invalid_latitude']) + raise forms.ValidationError(self.error_messages["invalid_latitude"]) if data_list[1] in validators.EMPTY_VALUES: - raise forms.ValidationError(self.error_messages['invalid_longitude']) + raise forms.ValidationError(self.error_messages["invalid_longitude"]) # SRID=4326;POINT(1.12345789 1.123456789) - srid_str = 'SRID=%d'%self.srid - point_str = 'POINT(%f %f)'%tuple(reversed(data_list)) - return ';'.join([srid_str, point_str]) + srid_str = "SRID=%d" % self.srid + point_str = "POINT(%f %f)" % tuple(reversed(data_list)) + return ";".join([srid_str, point_str]) return None