Django: Cleaning and validating FORMS that depend on each other - django

The django docs cover cleaning and validating FIELDS that depend on each other, but I can't find anything that covers forms that depend on each other.
I have a single HTML form with which contains both a standard django form and a django formset. Proper validation of each form in the formset is entirely conditional based on a value from the main form (e.g. check a box on the main form, and a specific field on each form in the formset suddenly becomes required).
My intuition is to "simply" pass the entire main form into the formset validation call, like so:
def my_view(request):
MyFormSet = formset_factory(MyForm, extra=2, can_order=True)
if request.method == 'POST':
form = MainForm(request.POST)
formset = MyFormSet(request.POST)
if form.is_valid() and formset.is_valid(form): # <-- ?!?!
# The formset is now validated based on the form
However, to make that work, I believe I would have to override both the formset is_valid() along with the underlying form is_valid() and clean() method. So, it gets pretty messy pretty quick.
Is there a better way to do this?

I investigated doing something like this once, and this tutorial http://yergler.net/blog/2009/09/27/nested-formsets-with-django/ was fairly helpful.
Another way to do this is:
def my_view(request):
MyFormSet = formset_factory(MyForm, extra=2, can_order=True)
if request.method == 'POST':
form = MainForm(request.POST)
formset = MyFormSet(request.POST, other_form = form)
if form.is_valid() and formset.is_valid(): # <-- ?!?!
# The formset is now validated based on the form
Then
class MyFormSet(...):
def __init__(self, *args, **kwargs):
if kwargs.has_key('other_form'):
self.myformforlater = kwargs.pop('other_form')
Super(MyFormSet, self).__init__(*args, **kwargs)
This way you only have to override the init method, and you have access to the outer form from any validation step.

Here's the code I ended up with, using Ted's answer (django 1.3):
class BaseMyFormSet(BaseFormSet):
main_form = None
def __init__(self, *args, **kwargs):
# Save the main form until validation
if kwargs.has_key('main_form'):
self.main_form = kwargs.pop('main_form')
super(BaseMyFormSet, self).__init__(*args, **kwargs)
def clean(self):
if any(self.errors):
# Don't bother validating the formset unless each
# form is valid on its own
return
checkbox = self.main_form.cleaned_data['my_checkbox']
if checkbox:
for form in self.forms:
# Do some extra validation
def my_view(request):
MyFormSet = formset_factory(MyForm, extra=2, can_order=True,
formset=BaseMyFormSet)
if request.method == 'POST':
form = MainForm(request.POST)
formset = MyFormSet(request.POST, main_form=form)
if form.is_valid() and formset.is_valid():
# The formset is now validated based on the form

Related

How i can add correctly data to an existing choicefield and save it in database: Django

I want to add some choices to an exiting fieldchoice from the database,
I have did that in my views.py:
def operation(request):
if request.method == 'GET':
form = FormOperation(instance=request.user, )
var = Metry.objects.filter(user=request.user).last().profile.name
varr = Metry.objects.filter(user=request.user).last().profile.category
form.fields['dite'].choices.append((varr, var))
print(form.fields['dite'].choices)
else:
if request.user.is_authenticated:
form = FormOperation(request.POST, )
if form.is_valid():
form.save()
return render(request, 'pages/operation.html', {'form': form})
models.py:
dite = models.CharField(null = True, max_length=60,choices = CHOICES)
forms.py:
class FormOperation(forms.ModelForm):
class Meta:
model = Operation
exclude = ("user",)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
after "append" the choice , As a test I have did a "print" to see the choice and it's normal i can see it in my terminal, but not in the page browser of my django application indeed ,i can see just the first choices without considering what i have append in my views.py,...
Any help will be appreciated.
You can use list extend method. (Make sure your CHOICES is a LIST)
new_choices[('abc', 'def'),]
CHOICES.extend(new_choices)
(Note: It will not override the existing value in select field, and will create another choice with same value)

Django - How do I get the files submitted to a form?

In my Django project, I have this view:
def create_format(request, **kwargs):
if request.method == 'POST':
form = FormatUpdateForm(request.POST)
if form.is_valid():
business_type = int(request.POST.get('business_type'))
... more fields ...
constitutive_act_copy = clean_str(request.POST.get('constitutive_act_copy'))
return HttpResponseRedirect(reverse('formats:formats'))
elif request.method == 'GET':
form = FormatUpdateForm()
return render(request, 'format_form.html', context={'form': form, 'autocomplete': SearchableData.objects.all()})
I have many fields, some of which are FileFields. I want to create an object with the data obtained from the form, and I can do that for all fields except the FileFields. What is the correct way to obtain the Files uploaded in the form and use them as attributes when creating an object?

Adding custom parameter to django modelform

I'm having troubles adding a custom parameter to a modelform (in this case, the currrent user from request.user)
I used to do this on previous projects, and I can't manage to make it work on a new project on Django 1.10.4.
I got this error :
TypeError at /realestateprogram/edition_appartement/19/
__init__() got an unexpected keyword argument 'user'
This is my view :
apartment = get_object_or_404(Apartment, pk=apartment_id)
if request.method == 'POST':
form = ApartmentForm(request.POST, request.FILES, instance=apartment, user=request.user)
if form.is_valid():
form.save()
return redirect('list_realestateprogram_apartment', realestateprogram_id=apartment.realestateprogram.id)
else:
print form.errors
else:
form = ApartmentForm(instance=apartment, user=request.user)
and this is my form :
class ApartmentForm(forms.ModelForm):
class Meta:
model = Apartment
exclude = ('realestateprogram',)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(ApartmentForm, self).__init__(*args, **kwargs)
Thanks in advance if you can help me
Try to pass all the values with keyword arguments:
form = ApartmentForm(data=request.POST, files=request.FILES, instance=apartment, user=request.user)
If you look at the class based generic views, that's how they do it. I would also suggest that you look at Form handling with class-based views.

Why clean method in my form will not clean?

I wrote a clean method on my form and it is not actually doing the validation.
class Property1Form(forms.ModelForm):
class Meta:
model = Property1
fields = ['unit','propertytype','is_true','date','followup_date','quantity','description']
def __init__(self, *args, **kwargs):
super(Property1Form, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance:
self.fields['unit'].required = False
self.fields['unit'].widget.attrs['disabled'] = 'disabled'
def clean(self):
form_data = self.cleaned_data
if Property1.objects.filter(unit=form_data['unit'], propertytype=form_data['propertytype'] ).count() > 0:
self._errors["propertytype"] = ["Propertytype already exists for unit"] # Will raise a error message
del form_data['propertytype']
Same validation does work for me on model level but on model level I am getting 500 error
ValidationError at
/unit/property/new/6/http://127.0.0.1:8000/unit/property_details/6/
{'all': [u'Same property cant be assigned more then ones']}
So trying to have same validation on the form side as well.
UPDATE:
view method
def property_new(request,pk,uri):
unit = get_object_or_404(Unit, pk=pk)
title = 'property'
uri = _get_redirect_url(request, uri)
if request.method == "POST":
form = Property1Form(request.POST)
form.fields['unit'] = unit
if form.is_valid():
properties = form.save(commit=False)
properties.unit = unit
properties.save()
messages.add_message(request, messages.SUCCESS, str(properties.unit) + "-SUCCESS Object created sucssefully")
return redirect(uri)
else:
form = Property1Form(initial={'unit': unit})
return render(request, 'object_edit.html', {'form': form, 'title':title, 'extend': EXTEND})
You have set required=False and disabled the unit field. That means that the browser will not submit any values for the unit field.
Therefore form.cleaned_data['unit'] is None, so the if statement in your clean method is always False.
As I suggested on your other question, I think it's a bad idea to set required=False and disabled the unit field. If you don't want the user to edit the field, don't include it in the form.

Pass logged user to model field django

I don't know how to get the username from the current user.
I have a edit form rendered with djano-crispy-forms:
class RecepcionForm(forms.ModelForm):
fecha_recepcion = forms.DateField(widget=DateInput())
def __init__(self,*args,**kwargs):
super(RecepcionForm,self).__init__(*args,**kwargs)
self.helper = FormHelper(self)
self.helper.layout = Layout(
Field('id_proveedor',
'anio',
'mes',
'usuario',
readonly = True
),
Fieldset('',
'fecha_recepcion',
'num_archivos',
Submit('save','Grabar')
)
)
class Meta:
model = DetalleRecepcion
my views.py:
#login_required(login_url='/login/')
def RecepcionView(request):
idp = request.GET.get('i')
anio = request.GET.get('a')
mes = request.GET.get('m')
if request.method == 'POST':
r = DetalleRecepcion.objects.get(id_proveedor=idp,anio=anio,mes=mes)
form = RecepcionForm(request.POST, instance=r)
if form.is_valid():
form.save()
return HttpResponseRedirect('/monitor/')
else:
r = DetalleRecepcion.objects.get(id_proveedor=idp,anio=anio,mes=mes)
form = RecepcionForm(instance=r)
return render_to_response('recepcion.html',
{'form':form},
context_instance=RequestContext(request))
I need to fill the field usuario with the logged username.
I tried with form = request.user.username before the save of the form.
I am confused of this have to be done passed the value in the form definition or in the view.
If is possible to overwrite the retrieved value from the database and fill the field with the username in the form class.
Another question
How can I change the widget type in the form. The field id_proveedor is a foreign key and is rendered as a drop down box (select widget), but I need to show the value displayed in a label where the can't edit the value.
I tried with the readonly propertie, but the user is not capable to write in the select box, but is capable to select from the drop down.
How can change the widget or how can I disabled the drop dwon function from the select box
Thanks in advance
You can always pass whatever arguments or keyword arguments you need to a form class, you just have to remove them from the *args or **kwargs that are passed on when calling super(), otherwise Django will throw an exception because it's receiving an arg or kwarg it's not expecting:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user') # notice the .pop()
super(MyForm, self).__init__(*args, **kwargs)
# views.py
def my_view(request):
# assuming the user is logged in
form = MyForm(user=request.user)
I came across the same as your problem and found a solution just now. I do not know whether this is the best solution or maybe I will have problem later.
def add_trip_event(request):
#form = Trip_EventForm()
#return render(request, 'trips/add_trip_event.html', {'form': form})
if request.method == "POST":
form = Trip_EventForm(request.POST, request.FILES)
if form.is_valid():
post = form.save(commit=False)
post.trip_owner = Owner.objects.get(owner=request.user)
post.pub_date = timezone.now()
post.view = 0
post.save()
form.save_m2m()
return HttpResponseRedirect(reverse('trips:index'))
else:
form = Trip_EventForm()
return render(request, 'trips/add_trip_event.html', {'form': form})