3 votes

Comment construire un formulaire Django qui possède un ModelChoiceField ?

J'essaie d'écrire un test pour mon formulaire qui utilise un ModelChoiceField personnalisé :

from django.forms import ModelChoiceField
class CycleModelChoiceField(ModelChoiceField):
    def label_from_instance(self, cycle):
        return str(cycle.begin_date)

Ce que l'utilisateur sélectionne dans ce champ doit être transmis à deux autres champs (DateField et radio ChoiceField) en remplaçant la fonction clean() méthode. C'est une logique compliquée que je dois tester.

Voici donc ce que j'ai essayé dans mon test jusqu'à présent :

self.client.login(username='user', password='1234')
response = self.client.get(reverse('my_form'), follow=True)
cyc = list(CycleDate.objects.all())[0]
form_data = {'type_of_input': '0', 'cycle': cyc,'usage_type': 'E',}
form = EnergyUsageForm(data=form_data, user=response.context['user'])

Mais le form.is_valid() retourne false et form.errors dit :

{'cycle': [u'Select a valid choice. That choice is not one of the available choices.']}

Il doit y avoir un problème dans la construction de mon formulaire. 'cycle': cyc ne fonctionne clairement pas comme prévu. J'ai également essayé 'cycle': '0' y 'cycle': '1' .

Quelle est la manière correcte de construire un formulaire comme celui-ci ?

EDITAR: Je devrais expliquer quels sont les choix possibles. Il n'y a qu'un seul CycleDate dans la base de données et un seul choix. Après avoir exécuté les lignes de mon test dans le shell, j'ai tapé form.fields['cycle'].choices.choice(cyc) qui renvoie (1, '2015-05-01') . Ce qui est étrange, c'est que form.fields['cycle'].queryset renvoie à [] . Peut-être le problème est-il lié à cela ?

EDIT2: Voici mon formulaire avec cette méthode de nettoyage compliquée (lisez : désordonnée et terrible et honteuse) :

class EnergyUsageForm(forms.Form):
    # Override init so that we can pass the user as a parameter.
    # Then put the cycle form inside init so that it can access the current user variable
    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user', None)
        super(EnergyUsageForm, self).__init__(*args, **kwargs)

        # Get the last 12 cycle dates for the current user
        td = datetime.date.today
        cycle_dates = CycleDate.objects.filter(cycle_ref=Account.objects.get(holder__user=user).cycle,
                                               begin_date__lte=td).order_by('begin_date')
        self.fields['cycle'] = CycleModelChoiceField(queryset = cycle_dates,
                                                      required = False,
                                                      widget = forms.Select(attrs={"onChange":'changeCalendar()'}),
                                                      label = "Choose a billing cycle")

    type_of_input = forms.ChoiceField(required=False,
                                    widget=forms.Select(attrs={"onChange": "switchInput()"}),
                                    choices=INPUT,
                                    initial='0',
                                    label="Choose a way to display usage", )

    end_date = forms.DateField(widget=forms.TextInput(attrs=
                                {
                                    'class':'datepicker'
                                }), 
                                label="Choose start date",
                                help_text='Choose a beginning date for displaying usage',
                                required=True,
                                initial=datetime.date.today,)

    period = forms.ChoiceField(required=True,
                                widget=forms.RadioSelect, 
                                choices=DISPLAY_PERIOD, 
                                initial='01',
                                label="Choose period to display",)

    usage_type = forms.ChoiceField(required=True,
                                widget=forms.RadioSelect,
                                choices=USAGE_TYPE,
                                initial='E',
                                label="Choose type of usage to display",)

    def clean_end_date(self):
        data = self.cleaned_data['end_date']

        if datetime.date.today() < data:
            raise forms.ValidationError("Don't choose a future date")
        # Always return the cleaned data, whether you have changed it or
        # not.
        return data

    def clean(self):
        cleaned_data = super(EnergyUsageForm, self).clean()
        selection = cleaned_data['type_of_input']
        # Check if the user wants to use cycle_dates instead
        if selection == '0':
            # Set the end_date and period
            cleaned_data['end_date'] = cleaned_data['cycle'].begin_date #MUST BE CHANGED TO END_DATE LATER
            cleaned_data['period'] = cleaned_data['cycle'].duration
        return cleaned_data

EDIT3 J'ai corrigé une coquille dans mon test, et voici aussi la méthode setUp de mon test :

client = Client()
    def setUp(self):
        user = User.objects.create_user(username = 'user', password = '1234')
        user.save()
        profile = UserProfile.objects.create(user = user)
        profile.save()
        account = Account(number=1, first_name='test',
                          last_name='User',
                          active=True,
                          holder=profile,
                          holder_verification_key=1)

        account.save()

        the_cycle = Cycle.objects.create(name = 'test cycle')
        the_cycle.save()
        cd = CycleDate.objects.create(begin_date = datetime.date(2015, 5, 1),
                                 end_date = datetime.date.today(),
                                 cycle_ref = the_cycle)
        cd.save()

EDIT4 : En plus de tout ce désordre, je reçois maintenant KeyError: 'cycle' chaque fois que j'appelle form.is_valid() . Probablement dû au fait que la méthode clean() essaie d'accéder à cleaned_data['cycle'] lorsque le champ cycle a une sélection invalide.

1voto

Jerzyk Points 1744
self.client.login(username='user', password='1234')
response = self.client.get(reverse('my_form'), follow=True)
cyc = list(CycleDate.objects.all())[0]
form_data = {'type_of_input': '0', 'cycle': cyc,'usage_type': 'E',}
form = EnergyUsageForm(data=form_data, user=response.context['user'])

surtout la ligne

cyc = list(CycleDate.objects.all())[0]
  • vous obtenez d'abord un itérateur de tous les CycleDates (ok, plus ou moins ok...)
  • puis vous lisez tout en mémoire (pourquoi !)
  • et enfin... vous sélectionnez le premier élément de la liste (bien sûr, vous obtiendrez une exception si la liste est vide...)

pourquoi pas :

cyc = CycleDate.objects.first()
if cyc:
    # cycle is a ModelChoice - in html it stores primary key!
    form_data = {'type_of_input': '0', 'cycle': cyc.pk, 'usage_type': 'E'}
    form = EnergyUsageForm(data=form_data, user=response.context['user'])

L'init de EnergyUsageForm :

def __init__(self, *args, **kwargs):
    user = kwargs.pop('user', None)
    super(EnergyUsageForm, self).__init__(*args, **kwargs)

    # today is a function!
    td = datetime.date.today()
    account = Account.objects.get(holder__user=user)
    cycle_dates = CycleDate.objects.filter(cycle_ref=account.cycle,
                                           begin_date__lte=td)
                                   .order_by('begin_date')

    self.fields['cycle'] = CycleModelChoiceField(queryset=cycle_dates,
                                                 required=False,
                                                 widget=forms.Select(attrs={"onChange":'changeCalendar()'}),
                                                 label = "Choose a billing cycle")

0voto

Mac Chaffee Points 75

Comme l'a souligné DanielRoseman, la réponse est d'utiliser l'id de l'instance.

La manière correcte de construire un formulaire avec un ModelChoiceField est donc la suivante :

my_instance = MyModelName.objects.get(whatever instance you need)
form_data = {'my_regular_choice_field': '0', 'my_model_choice_field': my_instance.id}
form = MyFormName(data=form_data)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X