I'm struggling with my Django application, to the point of asking my first question on StackOverflow.
To be short, I have a form where the user (a farmer) allows him to add a plant on a culture.
It'd be handy if instead of a boring select box, the farmer could just write down a few letters and every related results pop on the screen. The farmer would pick-up the plant and proceed to the next step. Since he had 330 different seeds, it's not just a fancy functionality.
I'm able to build a "simple" WizardForm, I already have the search engine and my field is populated with a ModelChoiceField()... I feel like I'm so close yet so far :(
I have also considered that WizardForm might not be the right approach for what I'm doing. But I feel like I'm just missing something.
Do any of you have any suggestion on it?
Below, you can read a few extracts from my code. I will try to clean-up the mess and provide you a readable code.
models.py
'''
From the model, the only field that interests this question is the second one, id_graine (graine means seed).
'''
class Lot(models.Model):
id = models.AutoField(primary_key = True)
id_graine = models.ForeignKey('Graine', on_delete = models.PROTECT, related_name = 'id_graine', null = True, blank = True)
nom_culture_incubation = models.ForeignKey('Culture', on_delete = models.PROTECT, related_name = 'nom_culture_incubation', null = True, blank = True)
nom_culture = models.ForeignKey('Culture', on_delete = models.PROTECT, related_name = 'nom_culture', null = True, blank = True)
etat_lot = models.CharField('état', choices = EtatLotsChoix.choices, max_length = 50, null = True, blank = True)
quantite_lot = models.PositiveSmallIntegerField('quantité', null = True, blank = True)
semis_date = models.DateField('date de semis', null = True, blank = True)
phase_lunaire_semis = models.CharField('phase lunaire de semis', max_length = 4, blank = True)
constellation_semis = models.CharField('constellations de semis', max_length = 7, blank = True)
germination_date = models.DateField('date de germination', null = True, blank = True)
plantaison_date = models.DateField('date de plantaison', null = True, blank = True)
phase_lunaire_plantation = models.CharField('phase lunaire de plantation', choices = PhasesLunairesChoix.choices, max_length = 4, blank = True)
constellation_plantation = models.CharField('constellations de plantation', choices = ConstellationChoix.choices, max_length = 7, blank = True)
culture_introduction_date = models.DateField('date d\'introduction', null = True, blank = True)
floraison_date = models.DateField('date de floraison', null = True, blank = True)
recolte_date = models.DateField('date de récolte', null = True, blank = True)
def __int__(self):
return self.id
class Meta:
verbose_name = 'Lot'
verbose_name_plural = 'Lots'
views.py
class AjoutLotWizard(SessionWizardView):
template_name = 'wizardforms/ajout_lot_seme.html'
def get_context_data(self, form, **kwargs):
'''
I told you that I was able to provide choices through a ModelChoiceField. But this approach
didn't pay off well. I also had a successful attempt by providing a context to the template
with those informations.
'''
if self.request.GET.get('search_value') != None:
search_value = self.request.GET.get('search_value')
search_results = Graine.objects.filter(espece_graine__contains = search_value)
context = super(AjoutLotWizard, self).get_context_data(form = form, **kwargs)
if (self.steps.current == '0') & (self.request.GET.get('search_value') != None):
context.update({'search_results': search_results})
return context
else:
return context
Within views.py, I also tried to pass data with get_form_kwargs(), get_form_step_data(), overwrite get() with a custom attribute.
The approach with get_form_kwargs was promising once I was able to store my values in variables inside unfortunately, I couldn't manipulate them as I wanted within the WizardView.
forms.py
class AddLotSemeStep1(forms.ModelForm):
'''
This file might not be accurate. I did a mess on that part by rewriting __init__().
But from what I remember, there wasn't more within the form when I use get_context_data
to provide choices anyway with the template.
'''
class Meta:
model = Lot
fields = ['id_graine']
labels = {
'id_graine': 'Graine'
}
widgets = {
'id_graine': forms.RadioSelect()
}
template.html
{% load static %}
{% load i18n %}
{% block content %}
{% include 'components/_base.html' %}
<main class="wizard-form-main">
<h2>Ajout de lot semé - le 1</h2>
<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
<form action="" method="GET" name="searchbar">
<input type="text" name="search_value">
<button type="submit">Rechercher</button>
</form>
<form action = "" method = "POST" name="wizardform">
{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form %}
{{ wizard.form.management_form }}
{{ wizard.form }}
{% else %}
{% for result in search_results %}
<p>
<label>
<input type="radio" name="chope_id" value="{{result.id}}">
{{result.espece_graine}} {{result.variete_graine}}, ({{result.provenance}} {{result.annee_de_recolte|cut:".0"}})
</label>
<input type="submit" value="Ajouter cette graine">
</p>
{% endfor %}
{% endif %}
</table>
{% if wizard.steps.prev %}
<!--<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.first }}">{% trans "first step" %}</button>-->
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button>
{% endif %}
<input type = "submit" value = "étape suivante">
</form>
</main>
{% endblock %}
Thank you for your attention if you managed to read all of it !
Hello fellow that browsed toward this page. If you were also looking for a multi-page form with specific step like mine who has a search bar, you can do like so.
I will just deposit my raw code in here. It should be helpful already. Once my Django app is finished, I'll take the time to explain.
views.py
def ajout_lot(request, nom_culture):
# Initialization
template_name = 'forms/ajout_lot.html'
context = {
}
instance = Lot()
model = Lot
form_list = [AddLotStep1, AddLotStep2, AddLotStep3]
fields = ['etat_lot', 'id_graine', 'quantite_lot', 'seme_date', 'plante_date', 'culture_introduction_date', 'phase_lunaire_seme', 'phase_lunaire_plante', 'constellation_seme', 'constellation_plante', 'nom_culture', 'nom_culture_incubation']
form = {
'step': 1,
'count': len(form_list)
}
form.update({
'form_fields': form_list[form['step'] -1]
})
context.update({'form': form})
# Handling GET requests
if request.GET.get('search_value') != None:
search_value = request.GET.get('search_value')
search_results = Graine.objects.filter(
Q(espece_graine__contains = search_value) |
Q(variete_graine__contains = search_value)
)
form.update({'step': 1})
context.update({
'form': form,
'search_results': search_results
})
# Navigation throught steps
if request.POST.get('next_step') != None:
for field in fields:
if request.POST.get(field) != None:
request.session.update({field: request.POST.get(field)})
else:
pass
form.update({
'step': int(request.POST.get('next_step'))
})
form.update({
'form_fields': form_list[form['step'] -1]()
})
context.update({'form': form})
elif request.POST.get('previous_step') != None:
form.update({
'step': int(request.POST.get('previous_step'))
})
form.update({
'form_fields': form_list[form['step'] -1]()
})
context.update({'form': form})
# step 3
if (request.POST.get('etat_lot') != None) and (len(request.POST) < 6):
form_list[form['step'] -1] = AddLotStep3(request.POST or None, etat_lot = request.POST.get('etat_lot'))
form.update({
'form_fields': form_list[form['step'] -1],
'step': 3
})
context.update({
'form': form,
'lunar_calendar': 'lunar_calendar'
})
# last step
elif request.POST.get('confirmation_step') != None:
for field in fields:
if request.POST.get(field) != None:
request.session.update({field: request.POST.get(field)})
else:
pass
request.session.update({'nom_culture': nom_culture})
# Now, every data from forms have been stored within request.session dictionnary.
# Transfering data to a dictionnary, becausee it'd be bad practice to process data from there
'''
From data we have :
- data_front that will display a summary of the form for the user
- data_back that will save data in the database
Once the user is satisfied with the summary, he can save data. Otherwise, he starts again from zero.
'''
data = {key: value for key, value in request.session.items() if key in fields}
for key, value in data.items():
if key == 'id_graine':
data.update({key: Graine.objects.get(id = value)})
elif key == 'nom_culture':
data.update({key: Culture.objects.get(nom = value)})
labels = []
for datum in data:
labels.append(getattr(model, datum).field.verbose_name)
data_front = {label: value for label, value in zip(labels, data.values())}
context.update({'data_front': data_front})
elif request.POST.get('recommencer') != None:
# Since we restart the form, we better have to clean request.session before
for field in fields:
try:
del request.session[field]
except KeyError:
pass
return redirect('ajout_lot', nom_culture = nom_culture)
elif request.POST.get('sauvegarder') != None:
data = {key: value for key, value in request.session.items() if key in fields}
for key, value in data.items():
if key == 'id_graine':
data.update({key: Graine.objects.get(id = value)})
elif (key == 'quantite_lot') and (value == ''):
data.update({key: None})
elif key == 'nom_culture':
data.update({key: Culture.objects.get(nom = value)})
elif (key.endswith('date')) and (key != None):
data.update({key: dt.strptime(value, '%d-%m-%Y').strftime('%Y-%m-%d')})
data_back = {}
for key, value in data.items():
if key == 'nom_culture':
data_back.update({
key: value,
'nom_culture_incubation': value
})
elif (key == 'seme_date') or (key == 'plante_date'):
data_back.update({
key: value,
'culture_introduction_date': value
})
else:
data_back.update({key: value})
for key, value in data_back.items():
setattr(instance, key, value)
instance.save()
# Once data are saved, we can clean request.session
for field in fields:
try:
del request.session[field]
except KeyError:
pass
context.update({
'sauvegarder': 'sauvegarder',
'nom_culture': nom_culture
})
return render(request, template_name, context)
forms.py
class AddLotStep1(forms.ModelForm):
class Meta:
model = Lot
fields = ['id_graine']
labels = {
'id_graine': 'Choisissez la graine du lot'
}
class AddLotStep2(forms.ModelForm):
class Meta:
model = Lot
fields = ['quantite_lot']
labels = {
'quantite_lot': 'Indiquez la quantité'
}
widgets = {
'quantite_lot': forms.TextInput
}
class AddLotStep3(forms.Form):
etat_lot = forms.ChoiceField(
label = 'Le lot a-t-il été semé ou planté ?',
choices = [
('Semé', 'Semé'),
('Planté', 'Planté')
],
widget = forms.RadioSelect(),
required = False
)
def __init__(self, *args, **kwargs):
etat_lot = kwargs.pop('etat_lot', None)
super(AddLotStep3, self).__init__(*args, **kwargs)
if etat_lot == 'Semé':
self.fields['seme_date'] = forms.DateField(
label = 'Date de semaison',
widget = DatePicker(),
required = False
)
self.fields['phase_lunaire_seme'] = forms.ChoiceField(
label = 'Phase lunaire',
choices = PhasesLunairesChoix.choices,
required = False
)
self.fields['constellation_seme'] = forms.ChoiceField(
label = 'Constellation',
choices = ConstellationChoix.choices,
required = False
)
elif etat_lot == 'Planté':
self.fields['plante_date'] = forms.DateField(
label = 'Date de plantaison',
widget = DatePicker(),
required = False
)
self.fields['phase_lunaire_plante'] = forms.ChoiceField(
label = 'Phase lunaire',
choices = PhasesLunairesChoix.choices,
required = False
)
self.fields['constellation_plante'] = forms.ChoiceField(
label = 'Constellation',
choices = ConstellationChoix.choices,
required = False
)
app/urls.py
path('etat_jardin/<str:nom_culture>/ajout_lot', views.ajout_lot, name = 'ajout_lot')
template
{% block base %}
{% include 'components/_base.html' %}
{% endblock %}
{% block content %}
{% if sauvegarder %}
<p>Les données ont bien été sauvegardées</p>
<a href="{% url 'ajout_lot' nom_culture %}">Ajouter un nouveau lot</a>
<a href="{% url 'etat_jardin_detail' nom_culture %}">Revenir à {{nom_culture}}</a>
{% elif data_front %}
<p>Récapitulatif</p>
{% for label, value in data_front.items %}
{% if label == "Graine" %}
<p>{{label}} : {{value.espece_graine}} {{value.variete_graine}}</p>
{% else %}
<p>{{label}} : {{value|default:"Aucune données enregistrées"}}</p>
{% endif %}
{% endfor %}
<form action="" method="POST">{% csrf_token %}
<button name="recommencer" type="submit">Je me suis trompé, recommencer</button>
<button name="sauvegarder" type="submit">Je confirme le lot, sauvegarder</button>
</form>
{% elif form %}
{% if form.step == 1 %}
<p>Étape {{ form.step }}/{{ form.count }}</p>
<p>Choisissez la graine du lot</p>
<form action="" method="GET" name="barre de recherche">
<input type="text" name="search_value">
<button type="submit" value="1">Valider</button>
</form>
<form action="" method="POST">{% csrf_token %}
{% for result in search_results %}
<p>
<label>
<input type="radio" name="id_graine" value="{{result.id}}">
{{result.espece_graine}} {{result.variete_graine}}, ({{result.provenance}} {{result.annee_de_recolte|cut:".0"}})
</label>
</p>
{% endfor %}
<button name="next_step" type="submit" value="{{ form.step|add:'1' }}">Suivant</button>
</form>
{% elif form.step == form.count %}
<p>Étape {{ form.step }}/{{ form.count }}</p>
{% if lunar_calendar %}
{% include 'components/_lunarCalendar.html' %}
{% endif %}
<form action="" method="POST">{% csrf_token %}
{{ form.form_fields }}
<button name="previous_step" type="submit" value="{{ form.step|add:'-1' }}">Précédent</button>
<button name="confirmation_step" type="submit">Résumer</button>
</form>
{% else %}
<p>Étape {{ form.step }}/{{ form.count }}</p>
<form action="" method="POST">{% csrf_token %}
{{ form.form_fields }}
<button name="previous_step" type="submit" value="{{ form.step|add:'-1' }}">Précédent</button>
<button name="next_step" type="submit" value="{{ form.step|add:'1' }}">Suivant</button>
</form>
{% endif %}
{% endif %}
{% endblock %}