I've made a Model Form for a model I have, I cannot directly save data to model because I have a custom format date field which can only be made in forms.py with forms.DateField(input_formats=settings.DATE_INPUT_FORMATS) but for some reason the data I'm parsing through the views is not saving in the model.
views.py
def applications_view(request):
if request.user.is_superuser:
applications = LeaveApplication.objects.all()
if request.method == "POST":
id = request.POST.get('applications_id')
user = request.POST.get('applications_user')
reason = request.POST.get('applications_reason')
from_date = request.POST.get('applications_from_date')
to_date = request.POST.get('applications_to_date')
if request.POST.get('approve'):
something = LeaveList(user=user, reason=reason, from_date=from_date,
to_date=to_date, leave_status='Approve')
leave_list_form = LeaveListForm(request.POST, instance=something)
if leave_list_form.is_valid():
leave_list_form.save()
elif request.POST.get('deny'):
something = LeaveList(user=user, reason=reason, from_date=from_date,
to_date=to_date, leave_status='Denied')
leave_list_form = LeaveListForm(request.POST, instance=something)
if leave_list_form.is_valid():
leave_list_form.save()
context = {
'applications': applications
}
return render(request, 'home/applications.html', context)
forms.py
class LeaveListForm(forms.ModelForm):
from_date = forms.DateField(input_formats=settings.DATE_INPUT_FORMATS)
to_date = forms.DateField(input_formats=settings.DATE_INPUT_FORMATS)
class Meta:
model = LeaveList
fields = [
'user',
'reason',
'from_date',
'to_date',
'leave_status'
]
I'm not rendering form with context on the template because I'm looping over data from another model which I'm fetching again with a button with the help of a hidden type input which is rendered with every loop.
<table style="width:100%">
<tr>
<th>Request ID</th>
<th>Employee</th>
<th>Reason for Leave</th>
<th>From Date</th>
<th>To Date</th>
<th>Approve/Deny</th>
</tr>
<tr>
<td>{{ applications.id }}</td>
<td>{{ applications.user }}</td>
<td>{{ applications.reason }}</td>
<td>{{ applications.from_date }}</td>
<td>{{ applications.to_date }}</td>
<td>
<form method="POST">
{% csrf_token %}
<input type="hidden" id="applications_id" name="applications_id" value="{{ applications.id }}">
<input type="hidden" id="applications_user" name="applications_user" value="{{ applications.user }}">
<input type="hidden" id="applications_reason" name="applications_reason" value="{{ applications.reason }}">
<input type="hidden" id="applications_from_date" name="applications_from_date" value="{{ applications.from_date }}">
<input type="hidden" id="applications_to_date" name="applications_to_date" value="{{ applications.to_date }}">
<input class="appproveButton appproveButtonYes" type="submit" name="approve" value="Approve">
<input type="submit" class="appproveButton appproveButtonNo"name="deny" value="Deny">
</form>
</td>
</tr>
</table>
Since you are already using your form why even handle getting the data? Simply pass the POST data to the form and let it handle it. Also it seems you don't want to get the leave_status from the form and instead pass a different key in the form for that, If that is the case just remove them from your form and handle it yourself:
from django.shortcuts import redirect
def applications_view(request):
if request.user.is_superuser:
applications = LeaveApplication.objects.all()
if request.method == "POST":
form = LeaveListForm(request.POST)
if form.is_valid():
if request.POST.get('approve'):
leave_status = 'Approve'
elif request.POST.get('deny'):
leave_status = 'Denied'
else:
leave_status = 'SOME_DEFAULT_VALUE'
form.instance.leave_status = leave_status
form.save()
return redirect('<SOME_URL_NAME>') # Redirect after successful form save!
context = {
'applications': applications,
}
return render(request, 'home/applications.html', context)
Now since you use a different view to render the form:
def other_view(request, *args, **kwargs):
form = LeaveListForm()
# rest of view
context = {..., 'form': LeaveListForm()}
return render(request, 'some_template.html', context)
In your form:
class LeaveListForm(forms.ModelForm):
from_date = forms.DateField(input_formats=settings.DATE_INPUT_FORMATS) # Make sure the input formats are correct
to_date = forms.DateField(input_formats=settings.DATE_INPUT_FORMATS)
class Meta:
model = LeaveList
fields = [
'user',
'reason',
'from_date',
'to_date',
] # Removed leave_status from here
Also it appears that you are directly rendering the form yourself by writing html. Even if you want to do that you need to work with the form in mind and set the names of the inputs to what the form would expect! Either simply leave the form rendering to django or correct your input tags names. I would suggest leaving the form rendering to django:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="approve" value="Approve">
<input type="submit" name="deny" value="Deny">
</form>
If you don't like the way Django renders forms and want to customize it there are lots of great packages out there to help you do that, for e.g. django-widget-tweaks and django-crispy-forms.
Related
I have a Django form that asks for id number. Hence, when the user clicks on submit, that id number is passed as a parameter to the endpoint.
This URL path('verify/nin/', views.post_nin, name='post_nin') contains the form and asks for the id number while I am submitting the data to this URL to
path('nin/<str:nin>',
views.nin_verification_vw, name="nin_verification")
So I expect to be redirected to http://127.0.0.1:8000/api/nin/15374020766 but instead it is redirecting me to http://127.0.0.1:8000/api/nin/%3Cinput%20type=%22text%22%20name=%22nin%22%20required%20id=%22id_nin%22%3E?nin=15374020766&csrfmiddlewaretoken=u5UmwDW4KRUIvYWXAa64J8g1dTPoJ3yDqtoCuKjboIE2TNxI3tPbjPmCK6FztVwW
How do I avoid the unnecessary parameters?
Here is my forms.py:
class NINPostForm(forms.Form):
"""Form for a user to verify NIN"""
nin = forms.CharField(required=True, help_text='e.g. 123xxxxxxxx')
# check if the nin is a valid one
def clean_nin(self):
nin = self.cleaned_data['nin']
regex = re.compile("^[0-9]{11}$")
if not regex.match(nin):
raise forms.ValidationError("NIN is incorrect.")
return nin
Here is my views.py:
def post_nin(request):
submitted = False
if request.method == 'POST':
form = NINPostForm(request.POST)
if form.is_valid():
cd = form.cleaned_data['nin']
return HttpResponseRedirect('/verify/nin?submitted=True')
else:
form = NINPostForm()
context = {
'form': form,
# 'cd': cd,
}
return render(request, 'ninform.html', context)
And here is my HTML template:
<form action="{% url 'nin_verification' form.nin %}" method="POST">
<table>
{{ form.as_table }}
<tr>
<td><input type="submit" value="Submit"></td>
</tr>
</table>
{% csrf_token %}
</form>
first import redirect : from django.shortcuts import redirect
Change your view to :
<form action="{% url 'post_nin' %}" method="POST">
<table>
{{ form.as_table }}
<tr>
<td><input type="submit" value="Submit"></td>
</tr>
</table>
{% csrf_token %}
</form>
You were passing the whole field instead of a String by using form.nin in your form action you should use your post_nin view to parse the nin field so...
Change your view to :
def post_nin(request):
submitted = False # Don't understand this part
if request.method == 'POST':
form = NINPostForm(request.POST)
if form.is_valid():
nin = form.cleaned_data['nin']
return redirect('nin_verification', nin=nin)
else:
form = NINPostForm()
context = {
'form': form,
}
return render(request, 'ninform.html', context)
`
In my formset I would like to check each reading against a target if the reading is larger than the target do not save to db. For some reason I can't get this to work correctly because it still allows me to save. Any help would be greatly appreciated!
all in views.py
#custom formset validation
def get_dim_target(target):
dim = Dimension.objects.values_list('target', flat=True).filter(id=target)
return dim
#custom formset validation
class BaseInspecitonFormSet(BaseFormSet):
def insp_clean(self):
if any(self.errors):
return
reading = []
for form in self.forms:
dim_id = form.cleanded_data['dimension_id']
reading = form.cleaned_data['reading']
target = get_dim_target(dim_id)
if reading > target:
raise forms.ValidationError("Reading larger than target")
reading.append(reading)
#formset
def update_inspection_vals(request, dim_id=None):
dims_data = Dimension.objects.filter(id=dim_id)
can_delete = False
dims = Dimension.objects.get(pk=dim_id)
sheet_data = Sheet.objects.get(pk=dims.sheet_id)
serial_sample_number = Inspection_vals.objects.filter(dimension_id=24).values_list('serial_number', flat=True)[0]
target = Dimension.objects.filter(id=24).values_list('target', flat=True)[0]
title_head = 'Inspect-%s' % dims.description
if dims.ref_dim_id == 1:
inspection_inline_formset = inlineformset_factory(Dimension, Inspection_vals, can_delete=False, extra=0, fields=('reading',), widgets={
'reading': forms.TextInput(attrs={'cols': 80, 'rows': 20})
})
if request.method == "POST":
formset = inspection_inline_formset(request.POST, request.FILES, instance=dims)
if formset.is_valid():
new_instance = formset.save(commit=False)
for n_i in new_instance:
n_i.created_at = datetime.datetime.now()
n_i.updated_at = datetime.datetime.now()
n_i.save()
else:
form_errors = formset.errors
formset.non_form_errors()
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
else:
formset = inspection_inline_formset(instance=dims, queryset=Inspection_vals.objects.filter(dimension_id=dim_id).order_by('serial_number'))
return render(request, 'app/inspection_vals.html',
{
'formset': formset,
'dim_data': dims_data,
'title':title_head,
'dim_description': dims.description,
'dim_target': dims.target,
'work_order': sheet_data.work_order,
'customer_name': sheet_data.customer_name,
'serial_sample_number': serial_sample_number,
})
inspection_val.html
<h1>Inspeciton Values</h1>
<div class="well">
<form method="post">
{% csrf_token %}
<table>
{{ formset.management_form }}
{% for x in formset.forms %}
<tr>
<td>
Sample Number {{ forloop.counter0|add:serial_sample_number }}
</td>
<td>
{{ x }}
{{ x.errors }}
</td>
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit Values" class="btn-primary" />
</form>
</div>
</div>
The Django docs on custom formset validation show that you should create a clean method.
You have named your method insp_clean, so Django will never call it. Rename the method to clean.
The form, that is described below, is not valid. And expression {{ search_id_form.errors }} doesn't show anything.
Django template:
<form method="POST">
{% csrf_token %}
{{ search_id_form.idtype.label_tag }}
{{ search_id_form.idtype }}
{{ search_id_form.index.label_tag }}
{{ search_id_form.index }}<br>
<input type="submit" name="id_search_button" value="Submit">
</form>
Python class:
class IDSearchForm(forms.Form):
idtype = forms.ChoiceField(
choices=[('idx', 'Our Database ID'), ('uprot', 'UniProt'), ('ncbi', 'NCBI')],
initial='idx',
widget=forms.RadioSelect,
label="Which identifier to use:"
)
index = forms.CharField(label="Identifier:")
View:
def search(request):
if request.method == 'POST':
# handling other forms ...
# find a toxin by id
if 'id_search_button' in request.POST:
search_id_form = IDSearchForm()
if search_id_form.is_valid():
idtype = search_id_form.cleaned_data['idtype']
index = search_id_form.cleaned_data['index']
return render(request, 'ctxdb/result_ctx.html', {
# here I try to use predefined object to pass to renderer (for debugging)
'ctx': get_object_or_404(CTX, idx='21')
})
# handling other forms ...
# other forms
search_id_form = IDSearchForm()
# other forms
return render(request, 'ctxdb/search.html', {
# other forms
'search_id_form': search_id_form,
# other forms
})
In the view function I handle four different forms on single page. Other forms work correctly. What is the problem here?
When calling .is_valid, you need to pass the data to search_id_form which you are not doing.
Change
search_id_form = IDSearchForm()
to
search_id_form = IDSearchForm(request.POST)
I've made some attempted to use formwizard but there wasn't much documentation about it so I decided to stay with the basic. I've successfully obtained and display the data from the first form to the second form and added some checkbox next to the data to allow user to choose whether to overwrite or ignore the duplicate data found in the backend process. The problem I have is the second form doesn't know how retrieve the data of the first form after hitting "Confirm" button. The form2.html template invalidated the data completely since it called itself again by the form action after submitting the data. Is there a way to solve this or a better approach to this?
forms.py
class NameForm (forms.Form):
first_name = forms.CharField (required = False)
last_name = forms.CharField (required = False)
class CheckBox (forms.Form):
overwrite = forms.BooleanField (required = False)
views.py
def form1 (request):
NameFormSet = formset_factory (NameForm, formset = BaseNodeFormSet, extra = 2, max_num = 5)
if request.method == 'POST':
name_formset = NameFormSet (request.POST, prefix = 'nameform')
if name_formset.is_valid ():
data = name_formset.cleaned_data
request.session ['data'] = data
context = {'data': data}
return render (request, 'nameform/form2.html', context)
else:
name_formset = NameFormSet (prefix = 'nameform')
context = {......}
return render (request, 'nameform/form1.html', context)
def form2 (request):
request.session ['data'] = data
CheckBoxFormSet = formset_factory (CheckBox, extra = 2, max_num = 5)
if request.method == 'POST':
checkbox_formset = CheckBoxFormSet (request.POST, prefix = 'checkbox')
if checkbox_formset.is_valid ():
data = checkbox_formset.cleaned_data
context = {'data': data}
return render (request, 'nameform/success.html', context)
else:
checkbox_formset = CheckBoxFormSet (prefix = 'checkbox')
return HttpResponse ('No overwrite data.')
form2.html
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
{% load staticfiles %}
<link rel="stylesheet" type="text/css" href="{% static 'nodeform/style.css' %}" >
<title>User Information</title>
</head>
<body>
<h1>User Information:</h1>
<form action="form2" method="POST">
{{ checkbox_formset.management_form }}
<div id="tablefont">
<table id="table01">
<tr>
<th>First Name</th>
<th>Last Name</th>
<th class="center">Overwrite</th>
</tr>
{% for info in data %}
<tr>
<td>{{ info.first_name }}</td>
<td>{{ info.last_name }}</td>
<td class="center"><input id="id_checkbox-{{ forloop.counter0 }}-overwrite" name="checkbox-{{ forloop.counter0 }}-overwrite" type="checkbox" /></td>
</tr>
{% endfor %}
</table>
</div>
<br>
<p><input type="submit" value="Confirm">
<a href="{% url 'form1' %}">
<button type="button">Cancel</button></a></p>
</form>
</body>
</html>
Personally I find that class based views always make life simpler, and in this case specifically there is the Form Wizard views which use the session or a cookie. Check out the docs; https://django-formtools.readthedocs.io/en/latest/wizard.html
I've just finished writing something very similar to this;
from django.contrib.formtools.wizard.views import SessionWizardView
class SignupWizard(SessionWizardView):
template_name = 'wizard_form.html'
form_list = [Form1, Form2, Form3]
model = MyModel
def get_form_initial(self, step, **kwargs):
initial = self.initial_dict.get(step, {})
current_step = self.storage.current_step
if step or current_step in ['0', 0]:
initial.update({'field': 'data'})
elif step or current_step in ['1', 1]:
initial.update({'field': 'data'})
return initial
def get_context_data(self, form, **kwargs):
"""
Supply extra content data for each stage.
e.g. stage page title
"""
previous_data = None
if self.steps.current == self.steps.last:
previous_data = self.get_all_cleaned_data()
context = super(SignupWizard, self).get_context_data(form, **kwargs)
context.update(
{
'review_data': previous_data,
}
)
return context
def done(self, form_list, **kwargs):
"""
Final page, this is rendered after the post method to the final form.
"""
form_data_dict = self.get_all_cleaned_data()
mymodelform = form_list[0].save()
return HttpResponseRedirect()
Essentially there my first form is a ModelForm for MyModel and I use other standard forms to collect other information. So at the end, grab the first form from the list and save that to create the MyModel instance.
Equally as you can see you can grab self.get_all_cleaned_data() to get all of the data entered in to the forms through to whole process to handle the data yourself.
The template for something this would be along the lines of;
{% extends 'base_wizard.html' %}
{% load i18n %}
{% block form %}
{% for error in wizard.form.non_field_errors %}
{{ error }}
{% endfor %}
{{ wizard.form.media }}
{{ wizard.management_form }}
{% wizard.form.as_p %}
<div class="controls">
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">
{% trans "Previous step" %}
</button>
{% endif %}
<button type="submit" name="submit">
{% trans "Next step" %}
</button>
</div>
{% endblock form %}
How do I perform a Django form validation for an unknown number of fields specified by the user? In my case, form allows a user to create a music album with any number of tracks and associate it to an artist. The view asks for how many tracks there are and then generates a form with that many input fields.
Form:
class NumberOfTracks(forms.Form):
track_no = forms.IntegerField()
class CustomAlbumAdmin(forms.Form):
artist = forms.CharField(max_length=150)
album = forms.CharField(max_length=150)
track_no = forms.IntegerField()
track = forms.CharField(max_length=150)
View:
def album_admin(request):
if request.GET.get('track_no'):
number_of_tracks = request.GET.get('track_no')
artists = Artist.objects.all()
return render(request, 'customadmin/album_admin1.html', {
'number_of_tracks': number_of_tracks,
'tracks': range(1, int(number_of_tracks) + 1),
'artists': artists,
})
elif request.method == 'POST':
form = CustomAlbumAdmin(request.POST)
print form
artist = request.POST['artist']
album = request.POST['album']
all_tracks = request.POST.getlist('track')
create_album = CreateAlbum(artist=artist, album=album, tracks=all_tracks)
create_album.save_album()
create_album.save_tracks()
form = NumberOfTracks()
return render(request, 'customadmin/no_tracks1.html', {
'form': form,
})
else:
form = NumberOfTracks()
return render(request, 'customadmin/no_tracks1.html', {
'form': form,
})
(Just so it's clear, I used if form.is_valid() and form.cleaned_data but to get this to work thus far, I've had to bypass that in favor of getting the raw POST data)
Part of what's confusing me is that I've customized my form template to add a number input fields with name="track" depending on user input (EX: create an album with 13 tracks). When I go into my view to print form = CustomAlbumAdmin(request.POST) it gives a very simple table based on my form: one artist, one album, one track, and one track_no so validating against this will of course return False unless I have an album with just one track.
Here's the template:
{% extends 'base.html' %}
{% block content %}
<form action="/customadmin/album1/" method="POST">{% csrf_token %}
<select name="artist">
{% for entry in artists %}
<option value="{{ entry.name }}">{{ entry.name }}</option>
{% endfor %}
</select>
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.album.errors }}
<label for="id_album">Album:</label>
<input id="id_album" maxlength="150" name="album" type="text" />
</div>
<div class="fieldWrapper">
{{ form.track.errors }}
<input type="hidden" name="number_of_tracks" value="{{ number_of_tracks }}">
{% for e in tracks %}
<label for="id_track">Track No. {{ forloop.counter }}</label>
<input id="id_track_{{ forloop.counter }}" maxlength="150" name="track" type="text" /></br>
{% endfor %}
</div>
<p><input type="submit" value="Save album" /></p>
</form>
{% endblock %}
The one way I was thinking of approaching this was to create a custom clean_track method that takes a list of all the tracks entered as I've done in the view with all_tracks = request.POST.getlist('track') but not sure how to do that.
A related question I have is if I can customize validation based on POST data. The first way I approached this was to generate incremented inputs with name="track_1", name="track_2", etc.,. and then trying to validate based on that. However, I wouldn't be able to use request.POST.getlist('track') in that case.
It might be a better approach to use formsets instead.
class AlbumForm(forms.Form):
artist = forms.CharField(max_length=150)
name = forms.CharField(max_length=150)
class TrackForm(forms.Form):
track_no = forms.IntegerField()
name = forms.CharField(max_length=150)
# In view
from django.forms.formsets import formset_factory
TrackFormSet = formset_factory(TrackForm)
if request.method == 'POST':
track_formset = TrackFormSet(request.POST)
album_form = AlbumForm(request.POST)
if track_formset.is_valid() and album_form.is_valid():
save_album(album_form, track_formset)
else:
track_formset = TrackFormSet()
album_form = AlbumForm()
And in save_album you can just iterate through track_formset.ordered_forms to get each form in the formset:
for form in track_formset.ordered_forms:
data = form.cleaned_data
# Do what you want with the data
This can be even more powerful if you use model formsets because you can set a foreign key in the track model that points to the album model, and Django can save them automatically for you.