Django testing form.is_valid() - python

My view have optional parameter in url. I pass this parameter (<int:jobNr>/None)to form in get_form_kwargs
class AddHoursView(LoginRequiredMixin, generic.CreateView):
form_class = AddHoursForm
def get_form_kwargs(self):
# pass "jobNr" keyword argument from current url to form
kwargs = super(AddHoursView, self).get_form_kwargs()
kwargs[JOB_PARAM] = self.kwargs.get(JOB_PARAM)
return kwargs
def form_valid(self, form):
self.object = form.save(commit = False)
# If url/jobNR take jobNr from URL, if not take from form
try:
jobNr = self.kwargs[JOB_PARAM]
except KeyError:
jobNr = form.cleaned_data[JOB_WORKER].jobNr
job = models.Job.objects.get(jobNr = jobNr)
jobWorker = models.JobWorker.objects.get_or_create(job = job,
user = self.request.user)
self.object.jobWorker = jobWorker[0]
self.object.save()
return HttpResponseRedirect(reverse('worktime:myjobs'))
In form __init__ If url /foo/jobNr/ create self.jobNr with url parameter. If url /foo/ create new field.
class AddHoursForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
#Add jobworker field to from Worktime model if any jobNr pass in url
#When in url will be parameter. Job foreignkey will be set by automat.
self.jobNr = kwargs.pop(JOB_PARAM, None)
super(AddHoursForm, self).__init__(*args, **kwargs)
if not self.jobNr:
self.fields[JOB_WORKER] = forms.ModelChoiceField(queryset=Job.objects.all())
def clean(self):
cleaned_data = super().clean()
date = cleaned_data.get('date')
if self.jobNr:
jobDate = Job.objects.get(jobNr=self.jobNr).start
elif not cleaned_data.get(JOB_WORKER).start:
raise forms.ValidationError("Dat work don't start yet")
else:
jobDate = cleaned_data.get(JOB_WORKER).start
if date<jobDate:
raise forms.ValidationError("Wrong date")
return cleaned_data
And now i want to code tests for a form. This is what i tried
def test_no_jobNr_in_url_and_no_date(self):
job = Job.objects.create(jobNr = 1, street = 'a', city = 'a',
zip = 'a')
form_data = {'description': 'description', 'hours': 20}
form = AddHoursForm(form_data)
# Override field jobWorker after __init__ form
form.fields['jobWorker'] = job
self.assertFalse(form.is_valid())
And this is what i get AttributeError: 'Job' object has no attribute 'disabled' After 5h i need help. I have no idea anymore.

you can't assign job to a form field (that's what form.fields['jobWorker'] is). If you want to assign an initial value, you should do:
form.fields['jobWorker'].initial = job
But I think you want to pass job as the chosen model, you should just do that in data:
form_data = {'description': 'description', 'hours': 20, 'jobWorker': job.pk}
form = AddHoursForm(form_data)
self.assertFalse(form.is_valid)

Related

Adjust number of fields of the form based on the argument in the view

I have a form with 4 fields.
class data_collect_form(forms.Form):
data_entry1 = forms.IntegerField(widget = forms.NumberInput(attrs={"class":"form-control","initial":0}))
data_entry2 = forms.IntegerField(widget = forms.NumberInput(attrs={"class":"form-control","initial":0})
data_entry3 = forms.IntegerField(widget = forms.NumberInput(attrs={"class":"form-control","initial":0})
data_entry4 = forms.IntegerField(widget = forms.NumberInput(attrs={"class":"form-control","initial":0}))
Based on the argument "number_of_fields" in the view, I would like to delete last fields of this form.
Here is the view:
def add_submission(request, number_of_fields ):
#delete number of fields of data_collect_form according to number_of_fields argument
if request.method == 'POST':
form2 = data_collect_form(request.POST)
else:
form2 = data_collect_form()
return render(request, 'main/second.html',{"form2":form2})
I am sure it should be possible with the __init__ method, but I do not have understanding on writing it correctly.
should be pretty straight forward like this:
number_of_fields = 3
form2 = data_collect_form(number_of_fields)
init for class data_collect_form
def __init__(self, number_of_fields, *args, **kwargs):
super(data_collect_form, self).__init__(*args, **kwargs)
if number_of_fields < 4:
self.fields.pop('data_entry4')
Django Forms: pass parameter to form
Removing a fields from a dynamic ModelForm

Django: 'UtilisateurUpdateView' object has no attribute 'object'

I develop a Django project with inlineformset nested using FormView.
I first develop my form using CreateView/UpdateView and it works but when I use FormView I got an error
'UtilisateurUpdateView' object has no attribute 'object'
Why I can get access 'object' when I use UpdateView but not with FormView ?
I have read that it could come from override method but here doesn't seems to be the case ?
forms.py
NAME = Thesaurus.options_list(2,'fr')
ACCESS = Thesaurus.options_list(3,'fr')
ApplicationFormset = inlineformset_factory(
UtilisateurProjet, Application, #Utilisateur, Application,
fields=('app_app_nom','app_dro'),
widgets={
'app_app_nom': forms.Select(choices=NAME),
'app_dro': forms.Select(choices=ACCESS)
},
extra=3,
can_delete=True,
)
class UtilisateurProjetUpdateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
super(UtilisateurProjetUpdateForm, self).__init__(*args, **kwargs)
self.fields["pro_ide"] = forms.ModelChoiceField(queryset = PROJETS, label = "Nom projet", widget = forms.HiddenInput(), initial = Projet.objects.get(pro_ide=self.request['projet']))
self.fields["uti_ide"] = forms.ModelChoiceField(queryset = UTILISATEURS, label = "Nom, prénom de l'utilisateur", widget = forms.Select, initial = Utilisateur.objects.get(uti_ide=self.request['utilisateur']))
class Meta:
model = UtilisateurProjet
fields = ('pro_ide','uti_ide',)
views.py
class UtilisateurUpdateView(FormView):
template_name = 'project/utilisateurprojet_form.html'
form_class = UtilisateurProjetUpdateForm
def get_form_kwargs(self):
kwargs = super(UtilisateurUpdateView, self).get_form_kwargs()
kwargs['request'] = dict(utilisateur = self.kwargs['pk'], projet = self.kwargs['projet'])
# kwargs['request'] = self.request
# print('projet',self.kwargs['projet'])
# print('utilisateur',self.kwargs['pk'])
return kwargs
def get_context_data(self, **kwargs):
data = super(UtilisateurUpdateView,self).get_context_data(**kwargs)
# print('projet',self.kwargs['projet'])
# print('utilisateur',self.kwargs['pk'])
instance = UtilisateurProjet.objects.get(pro_ide=self.kwargs['projet'],uti_ide=self.kwargs['pk'])
data['projet'] = Projet.objects.get(pro_ide=self.kwargs['projet'])
data['utilisateur_app'] = Utilisateur.objects.get(uti_ide=self.kwargs['pk'])
if self.request.POST:
data["utilisateur"] = self.request.user.username
data["user_profil"] = self.request.session.get('user_profil')
data["application"] = ApplicationFormset(self.request.POST, instance=instance)
else:
data["application"] = ApplicationFormset(instance=instance)
return data
def form_valid(self, form):
# Setting commit to False will return the object without saving it to the Database.
self.object = form.save(commit=False)
context = self.get_context_data()
application = context["application"]
user_profil = context["user_profil"]
if user_profil == 'Investigateur':
self.object.uti_val = 0 # demande modifiée par investigateur -> repasse à non validée + envoie de mail
elif user_profil == 'Moniteur':
self.object.uti_val = 1 # demande validée par moniteur -> validée + envoie de mail
self.object.uti_val_dat = timezone.now()
else:
self.object.uti_val = 0
# After doing our own changes to the object, we can save it.
self.object.save()
if application.is_valid():
# Not sure what is happening here, but this statement does nothing by itself.
# form.instance = self.object
application.save()
return super().form_valid(form)
def get_success_url(self):
return reverse("project:index")
FormView and DetailView are different classes from different packages: django.views.generic.edit.FormView and django.views.generic.detail.DetailView respectively.
From the docs of DetailView:
While this view is executing, self.object will contain the object that
the view is operating upon.
FormView doesn't have an object property, because it doesn't necessarily work with an object.
However, since you're using ModelForms, you should be able to access the Form's object after calling form.save():
def form_valid(self, form):
# Setting commit to False will return the object without saving it to the Database.
self.object = form.save(commit=False)
context = self.get_context_data()
application = context["application"]
user_profil = context["user_profil"]
self.object.uti_val = 1
self.object.uti_val_dat = timezone.now()
else:
self.object.uti_val = 0
# After doing our own changes to the object, we can save it.
self.object.save()
if application.is_valid():
# Not sure what is happening here, but this statement does nothing by itself.
form.instance = self.object
application.save()
return super().form_valid(form)

Changing BoundField value after initializing and validating form

I am trying to manually change the name of a form field after it initialized in a CBV. You could do this after initializing:
form = self.get_form() # it is initialized here
form.cleaned_data['name'] = form.instance.name + ' (new)'
But I need to change the value after validating, so form.cleaned_data is no longer involved. I need to directly change the value of the BoundField. How can I do that? Or how can I do what I am trying to do in any other way?
Here is some cody of my view and form class:
class MyView(CBV):
def form_valid(self, form):
if copy:
form.instance.pk = None
name = form.instance.name + ' (new)'
form.instance.name = name
# it does work this way
data = form.data.copy()
data['name'] = name
form.data = data
self.object = form.save()
else:
pass
return self.render_to_response(self.get_context_data(form=form))
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = []
def __init__(self, *args, **kwargs):
self.copy = kwargs.get('copy', False)
super(MyForm, self).__init__(*args, **kwargs)
def clean_name(self):
# (1) doing this has no effect in the displayed form included in the response of a POST request
# (2) I don't always want to do this, only when a form is valid
cleaned_data = self.cleaned_data['name']
if self.copy:
cleaned_data['name'] += ' (new)'
return cleaned_data
You can override clean method. After inherit form, you can modify your cleaned_data after validation.
def clean(self)
# It will validate data
self.cleaned_data = super().clean()
# do what you want
self.cleaned_data['name'] = 'blah...'
return self.cleaned_data
Or you can use specific field, in this situcation, name.
def clean_name(self):
data = self.cleaned_data.get('name', '')
if not data:
raise forms.ValidationError("You must enter a name")
if not data.endswith('( new)'):
return data += '( new)'
return data
ADD
You can see the validation actually already DONE after super().clean().
You can check print self.is_valid() then you can see it's True. Or just add debugger in that line to check validation is already done
def clean(self):
# It will validate data
self.cleaned_data = super().clean()
# Check validation is already done here
# Or debugging it
# from IPython import embed; embed()
print(self.is_valid())
# do what you want
return self.cleaned_data
You should save the form first, with commit=False, and then change it in the instance.
def form_valid(self, form):
if copy:
instance = form.save(commit=False)
instance.pk = None
name = instance.name + ' (new)'
instance.save()
return...
(Also, you should always redirect after a successful post, not render a template.)

Django: Pass URL parameter to context_data in generic.CreateView

I have created two url paths.
path('addhours/<int:jobNr>/', views.AddHoursView.as_view(), name='addhours'),
path('addhours/', views.AddHoursView.as_view(), name='addhours'),
And a CreateView for these paths.
class AddHoursView(generic.CreateView):
template_name = "worktime/addhours.html"
form_class = AddHoursForm
success_url = reverse_lazy('worktime:myjobs')
def get_form_kwargs(self):
# pass "jobNr" keyword argument from the current url to form
kwargs = super(AddHoursView, self).get_form_kwargs()
kwargs['jobNr'] = self.kwargs.pop(JOB_PARAM, None)
return kwargs
And a form, where the number of fields depends on if the parameter exists in the URL.
class AddHoursForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
#Add jobworker field to from Worktime model if any jobNr pass in url
#When in url will be parameter. Job foreignkey will be set by automat.
self.jobNr = kwargs.pop('jobNr', None)
super(AddHoursForm, self).__init__(*args, **kwargs)
if not self.jobNr:
self.fields['jobWorker'] = forms.ModelChoiceField(queryset=Job.objects.all())
class Meta:
model = WorkTime
fields = ['date', 'hours', 'description']
widgets = {
'date': forms.SelectDateWidget(empty_label=("Choose Year",
"Choose Month",
"Choose Day"))
}
Now I want to have in AddHoursView context["jobNr"] = "bar" where the URL is /url/bar
In AddHoursView's def context_data(self, **kwargs) I tried:
self.request.GET gives me empty QueryDict
self.kwargs['jobNr'] give me a key error
I need access to the url parameter if it exist, to pass this jobNr to template, so override form_valid is not what I need.
At the moment you are popping the value from self.kwargs, which removes it. That means it is no longer there when you try to access it in get_context_data. Since the argument is optional, you can use get() instead.
Change the method to:
def get_form_kwargs(self):
kwargs = super(AddHoursView, self).get_form_kwargs()
kwargs['jobNr'] = self.kwargs.get('JOB_PARAM')
return kwargs
Then in get_context_data you can do:
def get_context_data(self, **kwargs):
kwargs = super(AddHoursView, self).get_context_data(**kwargs)
kwargs['jobNr'] = self.kwargs.get('JOB_PARAM')
return kwargs

Validate form A's data by usuing form B's data

So I have these two forms. I would like to be able to access the data in form env_form when I am checking add_uRG for my other form. Is it possible to do this? My env form is a very common form through out my app so I would like to keep it separate instead of including it on every form.
class env_form(forms.Form):
env = forms.ChoiceField(choices=ENV, required=True)
class add_uRG(forms.Form):
user = forms.CharField(max_length=50)
group = forms.CharField(required=True)
role = forms.CharField(required=True)
def clean_user(self):
post_user = self.cleaned_data['user']
post_env = self.cleaned_data['env']
c = User.objects.filter(user__contains=post_user, env__contains=post_env ).count()
if (c == 0):
raise forms.ValidationError(u"User Not Found.")
else:
user_info = User.objects.filter(user__contains=post_user).values('password').distinct().count()
user_env = User.objects.filter(user__contains=post_user).values('env').distinct().count()
if not (user_env == user_info):
raise forms.ValidationError(u'User is using same password')
return(post_user)
If I'm reading your question correctly, I think that form inheritance could be used here.
class env_form(forms.Form):
env = forms.ChoiceField(choices=ENV, required=True)
class add_uRG(env_form):
user = forms.CharField(max_length=50)
group = forms.CharField(required=True)
role = forms.CharField(required=True)
def clean_user(self):
post_user = self.cleaned_data['user']
post_env = self.cleaned_data['env']
c = User.objects.filter(user__contains=post_user, env__contains=post_env ).count()
if (c == 0):
raise forms.ValidationError(u"User Not Found.")
else:
user_info = User.objects.filter(user__contains=post_user).values('password').distinct().count()
user_env = User.objects.filter(user__contains=post_user).values('env').distinct().count()
if not (user_env == user_info):
raise forms.ValidationError(u'User is using same password')
return(post_user)
def __init__(self, *args, **kwargs):
super(env_form, self).__init__(*args, **kwargs)
class SomeOtherForm(env_form):
some_field = forms.CharField()
def __init__(self,*args,**kwargs):
super(env_form, self).__init__(*args,**kwargs)
You could assign a property on your add_uRG form after you validate the env_form, or you could pass the value to the add_uRG form as a parameter.
#forms.py
class AdduRGForm(forms.Form):
def __init__(self, *args, **kwargs):
super(AdduRGForm, self).__init__(*args, **kwargs)
self.env = None
#my_view.py
def my_view(request):
env_form = env_form(request.POST or None)
if request.POST:
if env_form.is_valid():
add_uRG_form = AdduRGForm(request.POST or None)
add_uRG_form.env = env_form.cleaned_data.get('env')
if add_uRG_form.is_valid():
#do something else
return render_to_response('template.html',
{'env_form' : env_form, 'add_uRG_form' : add_uRG_form})
I'm not exactly sure of your workflow, so if these forms don't exist in the same view, like if you're processing the env_form first, and then going to another view and needing the value someone previously selected, you could pass in the request to env_form, and set the selection in session, which you could pick up again in the second form using the method I outlined.

Categories

Resources