Django Form DateInput with widget in update: loosing the initial value - python

I need a DateInput field in a ModelForm with the default HTML datepicker (I'm not using 3rd party libraries).
Since the DateInput is rendered with <input type = "text"> by default, the datepicker is missing (it comes for free with <input type = "date">)
I've found some examples explaining how to change the input type by handling widget parameters (below the code I've done so far)
The issue
I have the datepicker working correctly but in "update mode" when passing initial date value to the form (see view part), the date remains empty in the HTML.
I've tried to find the cause and it seems that the 'type': 'date' part in the widget customization is clearing the initial value is some way; in fact, removing it, the initial value date is displayed again, but I loose the datepicker of course.
In the view the date is passed with a valid value
I also found another similar unanswered question where the field was declared as
class DateInput(forms.DateInput):
input_type = 'date'
date_effet = forms.DateField(widget=forms.DateInput(format='%d-%m-%Y'), label='Date effet')
the problem still remains
My code
model.py
class TimesheetItem(models.Model):
date = models.DateField()
description = models.CharField(max_length=100)
# many other fields here
form.py
class TimesheetItemForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# init is used for other fields initialization and crispy forms
class Meta:
model = TimesheetItem
fields = ['date', 'description']
widgets = {
'date': forms.DateInput(
format=('%d/%m/%Y'),
attrs={'class': 'form-control',
'placeholder': 'Select a date',
'type': 'date' # <--- IF I REMOVE THIS LINE, THE INITIAL VALUE IS DISPLAYED
}),
}
 view.py
def edit(request, uuid):
try:
timesheet_entry_item = TimesheetItem.objects.get(uuid=uuid)
if request.method == 'POST':
form = TimesheetItemForm(
data=request.POST,
instance=timesheet_entry_item
)
if form.is_valid():
pass # save the form
else:
form = TimesheetItemForm(initial={
'date': timesheet_entry_item.date, # <--- the date here has a valid value
'description': timesheet_entry_item.description
})
return render(request, 'template.html', {'form': form})
except ObjectDoesNotExist:
raise Http404("error")
Thanks for any help
M.

I managed to make it work. Following the cause of the issue, I hope it can be useful to others.
The HTML <input type='date'> element wants a date in the format YYYY-mm-dd; in fact an example of working HTML must be like this:
<input type="date" name="date" value="2020-03-31"
class="form-control dateinput form-control"
placeholder="Select a date" required="" id="id_date">
Since by default the form.DateInput produces the element <input type='text'>, it expects a date in the local format: let's say '31/03/2020'.
Forcing the 'type': 'date' and local format format=('%d/%m/%Y') or not passing a format at all, it ignores the value passed since the <input type='date'> wants format=('%Y-%m-%d')
At last the correct instruction was:
widgets = {
'date': forms.DateInput(
format=('%Y-%m-%d'),
attrs={'class': 'form-control',
'placeholder': 'Select a date',
'type': 'date'
}),
}

Recently, I coded:
date = models.DateTimeField() in models.py
widgets = {'date': NumberInput(attrs={'type': 'date'})} in forms.py
form = <ModelForm>(instance=<model_instance>, data=request.POST) in views.py
and ran into the same problem. I figured it out by changing to:
widgets = {'date': DateInput(attrs={'type': 'date'})} in forms.py.
Maybe how to pre-fill the form matters in views.py. Another difference is , I imported DateInput from django.forms.widgets, not django.forms.

Related

Form shows wrong date format while changing element in Django

I need the date format dd-mm-yyyy and I changed it everywhere. The date in the list of elements shows correctly. The database also has a requirement to enter the date in this format.
However, when I try to modify an existing element, the form shows a date in the format yyyy-mm-dd and I have to change the date to dd-mm-yyyy each time to make changes to the element, otherwise an error occurs. How can I make the date displayed in the format dd-mm-yyyy by default when editing an element?
forms.py
class CaseRequestForm(forms.ModelForm):
class Meta:
model = CaseRequest
fields = ('name', 'datebirth')
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'datebirth': forms.TextInput(attrs={'class': 'form-control'}),
}
Models.py
class CaseRequest(models.Model):
name = models.CharField('Put full name',max_length=255)
datebirth = models.DateField('Put date in dd.mm.yy format')
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('caserequest')
#property
def case_exists(self):
return Case.objects.filter(name=self.name, datebirth=self.datebirth).exists()
Views.py
class UpdateCaseRequestView(UpdateView):
model = CaseRequest
form_class = CaseRequestForm
template_name = 'add_caserequest.html'
update_caserequest.py
<div>
<form method="POST">
{% csrf_token %}
{{form.as_p}}
<button class="btn btn-secondary">Send</button>
</form>
</div>
In settings.py I already added
DATE_INPUT_FORMATS = ["%d.%m.%Y"]
USE_L10N = False
The default widget for the DateField is DateInput not TextInput, that's for CharField.
You can override its format in the widget itself so:
from django.forms import DateInput
class CaseRequestForm(forms.ModelForm):
class Meta:
model = CaseRequest
fields = ('name', 'datebirth')
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'datebirth': DateInput(attrs={'class': 'form-control'}, format='%d-%m-%Y'),
}
Now, the date input field in the form will use the dd-mm-yyyy format in the front-end.
Also provide %d-%m-%y in the DATE_INPUT_FORMATS since both are valid date formats so:
DATE_INPUT_FORMATS = ["%d.%m.%Y", "%d-%m-%Y"]
USE_L10N = False

How to display a variable in Django forms?

I am trying to use the value of my database to display it in the form. So if a user already filled a value before, they will see it when they access again to the page.
So there is my HTML file :
<div class="form-group row">
<label class="col-4 mt-1 text-right">Modèle</label>
<div class="col-4">
{{ form.model }}
</div>
</div>
My views.py file :
def get_car(request, identifiant):
if request.method == "POST":
form = CarForm(request.POST)
if form.is_valid():
car_instance = Car(
identifiant,
form.cleaned_data["model"]
)
car_instance.save()
return HttpResponseRedirect("/forms/car/{}".format(identifiant))
else:
form = CarForm()
form.identifiant = identifiant
return render(request, "forms/car.html", {"form": form})
My models.py file :
class Car(models.Model):
model = models.CharField(max_length=16)
(primary key is automatically created)
And my forms.py file :
class CarForm(forms.Form):
model = forms.CharField(label="Modèle", max_length=32, widget=forms.TextInput({ "value": Car.objects.get(pk=1).model}))
We can see in the forms file that I gave a value to pk, so the code will search the value from the user whose pk = 1 in my database. My question is how to give the pk value from the active user ?
with form = CarForm() you create an empty form instance. No wonder the form shows up empty.
I would seriously recommend you to switch to class-based views (UpdateView and CreateView) as they created exactly for this type of usage and are super easy to implement.
Anyway, all you need to do here is to generate the form instance with the object that you plan to update.
I'm gonna guess that identifiant is a car ID(?)
car = Car.objects.get(id=identifiant)
form = CarForm(instance=car)
I would also suggest you to use a ModelForm instead of a standard form for your Car since you are collecting just the single field "model" in the "Car" model.
class CarForm(forms.ModelForm):
class Meta:
fields = ['model']
widgets = {
'model' : forms.TextInput(),
}
labels = {
'model' : 'Modèle'
}
You can add other attributes this way. You can add anything you want from there really. For example:
class CarForm(forms.ModelForm):
class Meta:
fields = ['model']
widgets = {
'model' : forms.TextInput(attrs={
'placeholder': '12345678',
'class' : 'shadow-sm',
'onClick' : "myFunction(this);"
}),
}
labels = {
'model' : 'Modèle'
}
Anything you put there will be rendered in the field. So if you add css classes you can put them all in the same line. For example
'class' : 'shadow-sm mx-3 bold-p text-responsive'

Django: Initialize date input dynamically

I'm building a simple web app where users can log new entries that have a name (CharField) and a date.
By default, the date is set to the current date:
class EntryForm(forms.Form):
entry_name = forms.CharField(label='New Entry', max_length=100)
entry_date = forms.DateField(initial=datetime.date.today, widget=forms.widgets.DateInput(attrs={'type': 'date'}))
If users select a different date and add an entry with that date, I want the selected date to persist as new initial value when the page reloads.
I know there are a lot of related questions on setting initial values dynamically, but unfortunately, I still could achieve setting an initial value dynamically.
My view with the form looks like this:
#login_required
def index(request):
if request.method == 'POST':
form = EntryForm(request.POST)
if form.is_valid():
# get the label name and strip and convert to lower before saving it
entry_name = form.cleaned_data['entry_name'].strip().lower()
entry_date = form.cleaned_data['entry_date']
entry = Entry.objects.create(name=entry_name, date=entry_date, owner=request.user)
return HttpResponseRedirect(reverse('app:index'))
else:
form = EntryForm()
# other, unrelated stuff stuff ...
context = {
'form': form,
}
return render(request, 'app/index.html', context)
Even setting the initial value of a form field to a fixed value inside the view's else branch didn't work. I tried EntryForm(initial={'entry_name': 'test'}) (also for entry_date) without success. Also form.fields['entry_name'].initial = 'test', which didn't work either. In both cases, the from's entry_name remained empty when reloading the page.
What's the problem? How can I set the initial value in the form (neither for name nor for date)?
Is it somehow because the form is still unbounded?
If setting the initial value in the view worked, I think I could simply set it when the date is entered and it should stay when the page is reloaded since I pass the form (with the adjusted initial value) in the context dict when rerendering the page.
Edit: This is how I render my form in the template:
<div class="input-group">
<input type="text" class="form-control" id="{{ form.entry_name.id_for_label }}" name="{{ form.entry_name.html_name }}" aria-label="new entry field">
{{ form.entry_date }}
<div class="input-group-append">
<button type="submit" class="btn btn-primary">Add</button>
</div>
</div>
Oh, I think I didn't test correctly. In fact, setting
form = EntryForm(initial={'entry_date': '2020-12-12'})
inside my view does work fine and the date gets initialized as configured.
I guess, I just tried passing a datetime.date before or tested with initializing entry_name, but that didn't work because of how I render entry_name in the template.
To persist the previously entered date, I added an optional argument init_date to my index view, which is set according to the previously entered date:
def index(request, init_date=datetime.date.today()):
if request.method == 'POST':
form = EntryForm(request.POST)
if form.is_valid():
entry_name = form.cleaned_data['entry_name'].strip().lower()
entry_date = form.cleaned_data['entry_date']
entry = Entry.objects.create(name=entry_name, date=entry_date, owner=request.user)
return HttpResponseRedirect(reverse('app:index_with_date', args=[entry_date]))
else:
form = EntryForm(initial={'entry_date': init_date})
For that, I added another URL pattern:
path('add/<entry_name>/<yyyymmdd:entry_date>/', views.add_entry, name='add_entry_with_date'),
With the following date converter:
class DateConverter:
regex = '\d{4}-\d{2}-\d{2}'
def to_python(self, value):
return datetime.datetime.strptime(value, '%Y-%m-%d')
def to_url(self, value):
return value
register_converter(DateConverter, 'yyyymmdd')

Django model DateField format in modelformset_factory

I'm trying to make a modelform_factory for my model named Book but the DateField is behaving strangely.
class Book(models.Model):
name = models.CharField(max_length = 50)
date = models.DateField(max_length = 50)
I have a function in my views that returns this:
modelform = modelformset_factory(Book, fields = (
'name',
'date',
),
can_delete=True,
widgets = {
'date': forms.DateInput(format='%d/%m/%Y'),
})
In my template this modelform will render with a datepicker:
$('.datepicker').datepicker({ format: 'dd/mm/yyyy' });
So far so good, and everything is working as expected to this point. But when I post my form to my view again, and save the modelformset, the dd/mm/yyyy date is wrongly converted to yyyy-mm-dd.
if request.method == 'POST':
formset = modelform(request.POST, request.FILES, queryset=Book.objects.filter(name=book))
if formset.is_valid():
formset.save() # This first deletes all instances that are checked for deletion
f = formset.save(commit=False) # This filters the formset to only include filled in forms
for form in f:
form.save()
return HttpResponse("success")
Does someone has any clue how I can format this dd/mm/yyyy date to correctly enter my database?
You probably need to reformat the date to match what the field requires (yyyy-mm-dd). You'll need to change the value via jQuery after using the datapicker tool which is using the dd/mm/yyyy format.
Somthing like:
$("#dateField").on('change', function(){
var arr = $("#dateField").val().split("/");
var newDate = arr[2]+"-"+arr[1]+"-"+arr[0];
$("#dateField").val(newDate);
});
Then, you'll need to parse that string to a date type in your view before saving it with parse_date:
from datetime import datetime
date = request.POST['dateField']
pDate= parse_date(date)
form.date = pDate
form.save()
In the source:
https://docs.djangoproject.com/en/2.1/_modules/django/db/models/fields/#DateField
https://docs.djangoproject.com/en/2.1/ref/utils/#module-django.utils.dateparse

Required and Optional Field in Django Formset

I created a formset to replicate over two input fields, first name and last name. I wanted to make the first set of "first name" and "last name" field to be required while the second, and third set as optional. In another word, at least one of the three set input box must be filled out. Is there a way to do this? Do I have to make 2 separate form/formset to accommodate this?
The reason for my question is I wanted to have 3 sets of first name and last name field. If a user only fill out one of the 3 sets and click Submit, Django will throw a Key Error because the other 2 sets aren't filled in.
forms.py
from django import forms
class NameForm (forms.Form):
first_name = forms.CharField (max_length = 20, required = False)
last_name = forms.CharField (max_length = 20, required = False)
views.py
from django.shortcuts import render
from django.forms.formsets import formset_factory
from nameform.forms import NameForm
from nameform.addName import webform
# Create your views here.
def addname (request):
NameFormSet = formset_factory (NameForm, extra = 2, max_num = 5) # Set maximum to avoid default of 1000 forms.
if request.method == 'POST':
# Django will become valid even if an empty form is submitted. Adding initial data causes unbound form and
# trigger formset.errors
formset = NameFormSet (request.POST, initial = [{'first_name': 'John', 'last_name': 'Doe'}])
if formset.is_valid ():
location = request.POST ['site']
data = formset.cleaned_data
for form in data:
firstname = form ['first_name']
lastname = form ['last_name']
webform (firstname, lastname, location)
context = {'data': data, 'location': location}
return render (request, 'nameform/success.html', context)
else:
formset = NameFormSet ()
return render (request, 'nameform/addname.html', {'formset': formset})
Here are the steps that I've tried:
If I removed the "required = False", it would make every form in the formset required. KeyError will be flagged if the user only filled out the first set and leave the second set blank.
Making this formset as optional like above will also throw a KeyError if user submitted a blank form.
Django formset in this case works in a peculiar way - forms without data have empty cleaned_data but treated as valid. So you have to manually check existence of form data:
# at imports section of views.py
from django.forms.util import ErrorList
# at addname()
actual_data = []
for form in data:
firstname = form.get('first_name')
lastname = form.get('last_name')
if firstname and lastname:
webform (firstname, lastname, location)
actual_data.append(form)
if actual_data:
context = {'data': actual_data, 'location': location}
return render(request, 'nameform/success.html', context)
formset._non_form_errors = ErrorList(['Enter at least one person name.'])
you need to inherit from BaseFormSet and override "clean" method to implement manual validation .
the django docs has an example : https://docs.djangoproject.com/en/1.7/topics/forms/formsets/#custom-formset-validation

Categories