Reputation: 468
I'm building a calendar on Django. When a user clicks a day on the calendar, he's redirected to a CreateEventForm where he can create an event happening on the day he clicked (selected_date). To save the user the hassle of having to enter the date he selected, I used get_form_kwargs() to send the selected_date to the form and prefill the start_time and end-time fields. In this way, he only needs to enter the hours and minutes for the start_time and end_time of the event.
My problem
Currently, the entire "%Y-%m-%dT%H:%M" format is displayed like so:
I would like to display only the %H:%M. But I want to make sure that the datetime object still contains the date; except that it wouldn't be displayed to the end user, like so:
I've tried to format the datetime object using form widgets, but it doesn't seem to work. When I format the field to '%H:%M', the field displays like so:
Forms
class EventForm(ModelForm):
class Meta:
model = Event
widgets = {
'start_time': DateInput(attrs={'type': 'datetime-local'}, format='%H:%M'),
'end_time': DateInput(attrs={'type': 'datetime-local'}, format='%H:%M'),
}
fields = '__all__'
def __init__(self, selected_date, *args, **kwargs):
super(EventForm, self).__init__(*args, **kwargs)
self.fields['start_time'].initial = selected_date
self.fields['end_time'].initial = selected_date
self.fields['start_time'].input_formats = ('%H:%M',)
self.fields['end_time'].input_formats = ('%H:%M',)
Models
class Event(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
start_time = models.DateTimeField()
end_time = models.DateTimeField()
def get_absolute_url(self):
return reverse('cal:calendar')
Views
class EventCreateView(generic.CreateView):
model = Event
template_name = 'cal/event.html'
form_class = EventForm
def get_form_kwargs(self):
kwargs = super(EventCreateView, self).get_form_kwargs()
selected_date_str = self.request.GET.get('date', None)
selected_date = datetime.strptime(selected_date_str, '%Y-%m-%d')
kwargs.update({'selected_date': selected_date})
return kwargs
class EventUpdateView(generic.UpdateView):
model = Event
template_name = 'cal/event.html'
form_class = EventForm
Template
{% extends 'cal/base.html' %}
{% load crispy_forms_tags %}
{% block title %}
Event
{% endblock %}
{% block content %}
<div class="clearfix">
<a class="btn btn-info left" href="{% url 'cal:calendar' %}"> Calendar </a>
</div>
<div class="container">
<form method="post">
<div class="form-group">
{% csrf_token %}
{% if form.instance.pk %}
<h3 class="center">{{ form.instance.start_time }}</h3>
{% else %}
<h3 class="center">{{selected_date}}</h3>
{% endif %}
<div class="col-4">{{ form.title | as_crispy_field }}</div>
<div class="col-4">{{ form.description | as_crispy_field }}</div>
<div class="col-4">{{ form.start_time | as_crispy_field }}</div>
<div class="col-4">{{ form.end_time | as_crispy_field }}</div>
{% if form.instance.pk %}
<a href="{% url 'cal:event_delete' pk=event.pk %}" class="btn btn-danger"> Delete </a>
{% endif %}
<button type="submit" class="btn btn-info"> Submit </button>
</div>
</form>
{% endblock %}
Upvotes: 2
Views: 1809
Reputation: 4149
clean
method to handle creating datetime
objectsDateTimeInput
widgetselected_date
class EventForm(ModelForm):
start_time = forms.DateTimeField(required=True, input_formats=['%H:%M'], localize=True, widget=forms.DateTimeInput)
end_time = forms.DateTimeField(required=True, input_formats=['%H:%M'], localize=True, widget=forms.DateTimeInput)
selected_date = forms.DateField(required=True)
class Meta:
model = Event
fields = ['title', 'description', 'start_time', 'end_time', 'selected_date']
def __init__(self, selected_date, *args, **kwargs):
super(EventForm, self).__init__(*args, **kwargs)
self.fields['selected_date'].initial = selected_date
def clean(self):
end_time = self.cleaned_data['end_time']
end_time = end_time.replace(year = self.cleaned_data['selected_date'].year)
end_time = end_time.replace(month = self.cleaned_data['selected_date'].month)
end_time = end_time.replace(day = self.cleaned_data['selected_date'].day)
start_time = self.cleaned_data['start_time']
start_time = start_time.replace(year = self.cleaned_data['selected_date'].year)
start_time = start_time.replace(month = self.cleaned_data['selected_date'].month)
start_time = start_time.replace(day = self.cleaned_data['selected_date'].day)
return self.cleaned_data.update({'end_time': end_time, 'start_time': start_time})
To post that selected_date
to your EventCreateView
(which happens when the user submits the form from your template), add the following to your template:
<form>
...
<input id="selected_date" type="hidden" name="selected_date" value="{{ selected_date }}">
...
</form>
In the same way that the title
and description
are automatically extracted from the request and saved to the form, the selected_date
will be extracted by Django. This happens because the view you're using automatically gets the POST data and passes it to your form class. Thus, the value of selected_date
from your hidden
field will be available to you in your clean()
method, where we then use it to create the full datetime
objects and pass them back to the form framework, which will save them for you.
The More on hidden elements can be found here.
N.B.: This requires that you pass the selected_date
back during your POST request, after the user has filled out the end_time
and start_time
. This is because Django's form framework will automatically fetch that value if the string matches. (If you need help with that, post your template code.)
My changes rely on the implementation details of Django's forms. The docs for the clean
method state:
The form subclass’s clean() method can perform validation that requires access to multiple form fields. This is where you might put in checks such as “if field A is supplied, field B must contain a valid email address”. This method can return a completely different dictionary if it wishes, which will be used as the cleaned_data.
Upvotes: 3