IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Django Python Discussion :

Un contrôle anti-doublon sur un formulaire qui ne s'applique pas toujours


Sujet :

Django Python

  1. #1
    Membre régulier
    Homme Profil pro
    Administrateur de base de données
    Inscrit en
    Décembre 2011
    Messages
    257
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Administrateur de base de données
    Secteur : Santé

    Informations forums :
    Inscription : Décembre 2011
    Messages : 257
    Points : 76
    Points
    76
    Par défaut Un contrôle anti-doublon sur un formulaire qui ne s'applique pas toujours
    Bonjour,

    Je développe un projet Django qui est actuellement en test.

    Lors d'un test, l'utilisateur a réussi à saisir 2 fois le même formulaire alors même qu'un contrôle anti-doublon existe à la validation du formulaire (méthode clean)
    De mon côté, je n'ai pas réussi à reproduire ce problème et je suis bien bloqué par le contrôle à la validation

    je ne comprends pas comment c'est possible ?

    models.py :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
    class Entree(models.Model):
     
        asp_ent_cle = models.AutoField(primary_key=True)
        asp_ent_loc = models.CharField("Site concerned by the operation", max_length=10, null=True, blank=True)
        med_num = models.CharField("Trial batch number", max_length=3, null=True, blank=True,)
        asp_ent_dat = models.DateField("Entry date", null=True, blank=True)
        asp_ent_pro_pay = models.CharField("Country of treatment origin in case of entry", max_length=10, null=True, blank=True)
        asp_ent_pro_sit = models.CharField("Processing source site in case of entry", max_length=10, null=True, blank=True)
        opr_nom = models.CharField("Input operator", max_length=10, null=True, blank=True)
        opr_dat = models.DateField("Entry date", null=True, blank=True)
        log = HistoricalRecords()
     
        class Meta:
     
            db_table = 'pha_asp_ent'
            verbose_name_plural = 'Entries'
            ordering = ['asp_ent_cle']  
            permissions = [
                ('can_manage_drugs','Can manage trial drugs'),
            ]
    form.py :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    class EditForm(forms.ModelForm):
    
        # surcharge méthode constructeur (__init__) pour avoir accès aux variables de sessions
        # https://stackoverflow.com/questions/3778148/django-form-validation-including-the-use-of-session-data
        def __init__(self, request, *args, **kwargs):
            super(EditForm, self).__init__(*args, **kwargs)
            self.request = request
            self.language = request.session.get('language')
            self.user = request.user.id # id de l'utilisateur
            self.user_pays = request.session.get('user_pays') # id pays de l'utilisateur
            self.user_site_type = request.session.get('user_site_type') # type de site de l'utilisateur ('International','National','Site')
            self.user_site = request.session.get('user_site') # code lettre du site de l'utilisateur '(eg TR pour CHU Treichville)'
    
            #  Listes déroulantes conditionnées sur le profil utilisateur connecté
            SITE_CONCERNE = Site.options_list_site_concerne(self.user,self.user_pays,self.user_site_type,self.language)
            # Liste de tous les traitements mentionnés en sortie avec destination = SiteConnecté et non entrés dans la pharmacie du site connecté
            MEDICAMENTS = Entree.medicaments_entree(self.user_site)
            PAYS = Pays.options_list(self.user_pays,self.user_site_type,self.language)
            SITE_PROVENANCE = Site.options_list_site_provenance(self.user_site_type,self.user_pays,self.language)
    
            # si utilisateur connecté = International
            if self.user_site_type == 'International':
                self.fields["asp_ent_loc"] = forms.ChoiceField(label = _("Site concerned by the operation"), widget=forms.Select, choices=SITE_CONCERNE)
            elif self.user_site_type == 'National':
                self.fields["asp_ent_loc"] = forms.ChoiceField(label = _("Site concerned by the operation"), widget=forms.Select, choices=SITE_CONCERNE, initial = self.user_site)
            else:
                self.fields["asp_ent_loc"] = forms.ChoiceField(label = _("Site concerned by the operation"), widget=forms.Select, choices=SITE_CONCERNE, initial = self.user_site, disabled=True)
    
            # si utilisateur connecté = Site
            if self.user_site_type == 'Site':
                self.fields["med_num"] = forms.ChoiceField(label = _("Trial batch number"), widget=forms.Select, choices=MEDICAMENTS)
            else:
                self.fields["med_num"] = forms.CharField(label = _("Trial batch number"), required=True)
            # empêche l'autocomplétion
            self.fields['med_num'].widget.attrs.update({
                'autocomplete': 'off'
            })
            self.fields["asp_ent_dat"] = forms.DateField(
                    label = _("Entry date"),
                    required = True,
                    # initial = timezone.now(),  
                    # initial = datetime.datetime.now(),  
                    validators=[validate_date],
                )
            # empêche l'autocomplétion
            self.fields['asp_ent_dat'].widget.attrs.update({
                'autocomplete': 'off'
            })
            # Si utilisateur connecté = Site
            if self.user_site_type == 'Site':
                self.fields["asp_ent_pro_pay"] = forms.ChoiceField(label = _("Country of treatment origin in case of entry"), widget=forms.Select, choices=PAYS, initial = Pays.pays_abreviation(self.user_pays), disabled=True)
            else:
                self.fields["asp_ent_pro_pay"] = forms.ChoiceField(label = _("Country of treatment origin in case of entry"), widget=forms.Select, choices=PAYS)
            self.fields["asp_ent_pro_sit"] = forms.ChoiceField(label = _("Processing source site in case of entry"), widget=forms.Select, choices=SITE_PROVENANCE,)
    
        def clean(self):
            cleaned_data = super(EditForm, self).clean()
            # controle anti-doublon (profil International et National car liste déroulante conditionnée pour les profils sites)
            entrees = Entree.objects.all()
            if self.user_site_type != 'Site':
                doublon = str(self.data.get('asp_ent_loc')) +'/'+ str(self.data.get('med_num')) +'/'+ str(self.data.get('asp_ent_dat'))
                if len([d for d in entrees if d.asp_ent_loc+'/'+d.med_num+'/'+str(d.asp_ent_dat) == doublon]) > 0:
                    raise ValidationError(_('This entry already exist'))
    
        class Meta:
            model = Entree
            fields = ('asp_ent_loc','med_num','asp_ent_dat','asp_ent_pro_pay','asp_ent_pro_sit',)
    
    
        def clean_med_num(self):
            data = self.cleaned_data['med_num']
    
            # contrôle longueur numéro de lot saisie
            if len(data) != 3:
                raise forms.ValidationError(_("Error on Batch number format (3 letters)"))
    
            # contrôle existance numéro de lot dans la table médicament (fournie par statisticien)
            if not Medicament.objects.filter(med_num = data).exists():
                raise ValidationError(_('Batch number does not exist'))
    
            return data
    
        def clean_asp_ent_dat(self):
            data = self.cleaned_data['asp_ent_dat']
            # try:
            #     entrydate = datetime.datetime.strptime(str(data), "%Y-%m-%d")
            # except expression as identifier:
            #     pass
            # else:
            #     entrydate = datetime.datetime.strptime(str(data), "%d/%m/%Y")
            entrydate = datetime.datetime.strptime(str(data), "%Y-%m-%d")
            currentdate = datetime.datetime.now()
            if entrydate > currentdate:
                raise forms.ValidationError(_("Please control entry date"))
            return data
    
        def clean_asp_ent_pro_sit(self):
            data = self.cleaned_data['asp_ent_pro_sit']
            if data == '' or data == 'None':
                raise forms.ValidationError(_("This field is required"))
            return data

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 242
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 242
    Points : 36 699
    Points
    36 699
    Par défaut
    Salut,

    Citation Envoyé par OZ1977 Voir le message
    je ne comprends pas comment c'est possible ?
    Si vous concaténez vos champs en construisant des chaînes de caractères différemment, ce n'est peut être pas la cause... mais çà ne rend pas le test lisible.

    Vous avez 3 solutions pour rendre les enregistrements uniques:
    • unique_together pour déclarer l’agrégat de champs qui rendent l'enregistrement "unique".
    • clean: effectivement mais plutôt que de tout récupérer et faire le tri, un .get avec les champs qui rendent l'enregistrement unique suffit. Il suffit de gérer l'exception qui est levée s'il n'existe pas.
    • dans le browser avec une validation faite via du JavaScript (sportif mais efficace)


    Personnellement, j'applique toujours les 2 premiers (après avoir vérifié qu'unique_together se traduit bien en contrainte sur la table côté base de données car le modèle de données a généralement une durée de vie supérieure à l'application).

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #3
    Membre régulier
    Homme Profil pro
    Administrateur de base de données
    Inscrit en
    Décembre 2011
    Messages
    257
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Administrateur de base de données
    Secteur : Santé

    Informations forums :
    Inscription : Décembre 2011
    Messages : 257
    Points : 76
    Points
    76
    Par défaut
    merci pour votre réponse

    je vais regarder vos propositions
    mais même si ma solution n'est pas la plus propre, je n'avais jamais réussi à enregistrer un doublon durant mes tests

    visiblement, l'utilisateur a été déconnecté à la fin de la première saisie et a été déconnecté (il ne sais pas pourquoi)
    il se serait reconnecté puis aurait re-saisi le même enregistrement et là curieusement le contrôle anti-doublon ne s'est pas appliqué...???


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
     
        unique_together = ['asp_ent_loc', 'med_num','asp_ent_dat']
    mais j'utilise un .get sur les élements de formulaire saisis et qu'il n'y a pas d'enregistrement correspondant, j'aurais un erreur

  4. #4
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 242
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 242
    Points : 36 699
    Points
    36 699
    Par défaut
    Salut,

    Citation Envoyé par OZ1977 Voir le message
    il se serait reconnecté puis aurait re-saisi le même enregistrement et là curieusement le contrôle anti-doublon ne s'est pas appliqué...???
    Votre contrôle anti doublon a failli...

    Et désolé mais si vous codez çà avec:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
         doublon = str(self.data.get('asp_ent_loc')) +'/'+ str(self.data.get('med_num')) +'/'+ str(self.data.get('asp_ent_dat'))
        if len([d for d in entrees if d.asp_ent_loc+'/'+d.med_num+'/'+str(d.asp_ent_dat) == doublon]) > 0:
    c'est juste illisible. D'autant que c'est testé à condition que self.user_site_type != 'Site'.

    Je ne dis pas que ce que vous avez fait ne devrait pas marcher. Je dis seulement qu'interdire, c'est des barrières claires et lisibles, ... avec un gardien qui tire sur tout ce qui dépasse.

    Si vous ne vous appliquez pas à ce genre de discipline, je ne vais pas essayer de comprendre pourquoi çà ne fonctionne pas.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  5. #5
    Membre régulier
    Homme Profil pro
    Administrateur de base de données
    Inscrit en
    Décembre 2011
    Messages
    257
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Administrateur de base de données
    Secteur : Santé

    Informations forums :
    Inscription : Décembre 2011
    Messages : 257
    Points : 76
    Points
    76
    Par défaut
    oui, je comprends

    je vais essayer d'améliorer la lisibilité

    j'utilisais mon premier test if self.user_site_type != 'Site' car j'ai 3 profils utilisateurs (Site, National et International qui ont des formulaires de saisie légèrement différents

    notamment, les profils 'Site' ont une liste déroulante conditionnée qui empêche de pouvoir saisir 2 fois la même entrée
    mais je peux faire sauter ce contrôle

    ce qui donne le code suivant qui fonctionne

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    def clean(self):
            cleaned_data = super(EditForm, self).clean()
            cle1 = self.data.get('asp_ent_loc')
            cle2 = self.data.get('med_num')
            cle3 = self.data.get('asp_ent_dat')
     
            if Entree.objects.filter(Q(asp_ent_loc = cle1) & Q(med_num = cle2) & Q(asp_ent_dat= cle3)).exists():
                    raise ValidationError(_('This entry already exist'))

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. contrôle des doublons sur plusieurs champs
    Par christy1 dans le forum Modélisation
    Réponses: 3
    Dernier message: 09/12/2011, 15h13
  2. [CSS 2] Style qui ne s'applique pas sur un l'id d'un formulaire
    Par alexgille dans le forum Mise en page CSS
    Réponses: 2
    Dernier message: 02/07/2010, 11h00
  3. Contrôles de validation sur un formulaire ASP.Net
    Par acta49xf dans le forum ASP.NET
    Réponses: 1
    Dernier message: 05/05/2010, 18h45
  4. Réponses: 1
    Dernier message: 03/07/2008, 20h36
  5. Contrôle anti-spam sur un formulaire
    Par Oberown dans le forum Langage
    Réponses: 6
    Dernier message: 30/07/2006, 14h31

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo