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.)
Related
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)
How can I get the Article instance in my model form's clean method? I tried too access self.instance but it is None. How do I get the previous field values?
model
class Article(models.Model):
name = models.CharField(max_length=25)
value = models.CharField(max_length=25)
forms
class ArticleForm(forms.ModelForm)
class Meta:
model = Article
fields = '__all__'
def clean(self):
cleaned_data = super().clean()
get_instance = self.instance
print(get_instance) and I get None
views
def test(request)
form = ArticleForm({'name':'test', 'value':'test'})
if form.is_valid():
print(1)
else:
print(form.errors)
You get None because you didn't instantiate the form with an instance.
form = ArticleForm({'name':'test', 'value':'test'})
If you instantiate the form with an instance, then you can access it with self.instance in the clean method.
article = Article.objects.get(pk=1)
form = ArticleForm({'name':'test', 'value':'test'}, instance=article)
However, note that cleaning the form alters the model instance. If you want the original values, you should refetch the instance from the database, e.g. original_instance = Art
def clean(self):
cleaned_data = super().clean()
if self.instance is not None and self.instance.pk is not None:
original_instance = Article.objects.get(pk=self.instance.pk)
else:
original_instance = None
...
If you only want to know which fields changed, and don't care about their original values, it would be simpler to use the changed_data attribute.
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)
I am working with flask and need django form like class so that in flask view i can simply instantiate a class and check its validity.Something like this.
class StringField(object):
def __init__(self, value=None, null=False):
self.value = value.strip() if value is not None else value
self.nullable = nullable
def clean(self):
if self.null:
if self.value in ('', None):
return self.value
else:
if self.value in ('', None):
raise Exception(
"Value can not be null or blank"
)
try:
self.value = str(self.value)
except:
raise Exception(
"Value is neithe string nor can be coerced into one"
)
class MyForm(Form):
username = StringField(null=True)
in my views i want do this
mf = MyForm(data_dict)
if mf.is_valid():
# do something...
Problem is:
how to get all fields like username, email etc in constructor of our main Form class (one which gets inherited), so that i can apply some validation to its attributes as these fields can be variable in number
Django's docs contains a lot of information regarding forms, start here.
For example:
from django import forms
# your form:
class UserForm(forms.Form):
username = forms.CharField(max_length=100)
email = forms.EmailField(null=True, blank=True)
# and your view:
def user_view(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = Userorm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = UserForm()
return render(request, 'user.html', {'form': form})
See the link above for more info.
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.