Working with Django Forms....hand over Drop-Down list ?

Hi there, it’s me again. I am struggeling with a Django project since the Easter-term…i keep switching between “hooray” and “f-c- y–” while doing it :slight_smile:

Right now i have completed (as far as such a project is ever completed) the displaying functions of my database…NOW i have to deal with the “C” in CRUD…

So let’s say i have THIS in my forms.py:

class ExaminationForm(ModelForm):
    class Meta:
        model = Examinations
        fields = ['purpose', 'result', 'date', 'examiner', 'valid_thru', 'comment']
        widgets = {
            'purpose': TextInput(attrs={
                'class': "form-control",
                'style': 'max-width: 400px;',
                'placeholder': 'Untersuchung auf'
            }),
            'result': TextInput(attrs={
                'class': "form-control",
                'style': 'max-width: 400px;',
                'placeholder': 'Begutachtungsergebnis'
            }),
            'date': DateInput(attrs={
                'class': "form-control",
                'style': 'max-width: 400px;',
                'placeholder': 'Untersuchungsdatum'
            }),
            'examiner': TextInput(attrs={
                'class': "form-control",
                'style': 'max-width: 400px;',
                'placeholder': 'Untersucher'
            }),
            'valid_thru': DateInput(attrs={
                'class': "form-control",
                'style': 'max-width: 400px;',
                'placeholder': 'gültig bis'
            }),
            'comment': Textarea(attrs={
                'class': "form-control",
                'style': 'max-width: 400px;',
                'placeholder': 'Kommentar',
                'rows': '3'
            })
        }

Everything is working as intended, but i would like to hand over also some drop-down values that should be valid ONLY (no free-text entry allowed) for some of the fields. the drop down values are stored in another django app in the same project. is there a simple way to configure single Form-Fields to display the existing drop down values in template ?

1 Like

Yes. You would use a ChoiceField for that field in the form, with a callable for the choices attribute to populate it with the appropriate values.

1 Like

Hi there once more Ken and thx for the hint, took me a while but i managed it. In my case, ModelChoiceFields are the solution as i am populating a models table with this form. But i didn#t tell you that I’m afraid :stuck_out_tongue:

So, right now i have this model in models.py:

class Examinations(models.Model):
    purpose = models.CharField(max_length=100)
    result = models.CharField(max_length=100)
    date = models.DateField()
    examiner = models.CharField(max_length=50)
    valid_thru = models.DateField(null=True, blank=True)
    comment = models.CharField(max_length=300, null=True, blank=True)
    employee = models.ForeignKey(Employees, on_delete=models.CASCADE)

    def __str__(self):
        return self.purpose

    class Meta:
        verbose_name_plural = 'Begutachtungen'
        ordering = ['date']

This in my forms.py:

class ExaminationForm(ModelForm):
    class Meta:
        model = Examinations
        fields = '__all__'

    purpose = forms.ModelChoiceField(queryset=Tauglichkeit.objects.all(), empty_label="Untersuchung auf",
                                     widget=forms.Select(attrs={
                                         'class': "form-select text-center fw-bold",
                                         'style': 'max-width: auto;',
                                     }))
    result = forms.ModelChoiceField(queryset=Ergebnis.objects.all(), empty_label="Begutachtungsergebnis",
                                    widget=forms.Select(attrs={
                                        'class': "form-select text-center fw-bold",
                                        'style': 'max-width: auto;',
                                    }))
    date = forms.DateField(label='Untersuchungsdatum:', widget=DateInput(attrs={
        'class': "form-control text-center fw-bold",
        'style': 'max-width: auto;',
        'placeholder': 'Untersuchungsdatum',
        'type': 'text',
        'onfocus': "(this.type = 'date')"
    }))

    valid_thru = forms.DateField(label='gültig bis:', required=False, widget=DateInput(attrs={
        'class': "form-control text-center fw-bold",
        'style': 'max-width: auto;',
        'placeholder': 'gültig bis',
        'type': 'text',
        'onfocus': "(this.type = 'date')"
    }))

    examiner = forms.CharField(widget=TextInput(attrs={
        'class': "form-control text-center fw-bold",
        'style': 'max-width: auto;',
        'placeholder': 'Untersucher'
    }))

    comment = forms.CharField(required=False, widget=Textarea(attrs={
        'class': "form-control fw-bold",
        'style': 'max-width: auto;',
        'placeholder': 'Kommentar',
        'rows': '3'
    }))

    employee = forms.ModelChoiceField(queryset=Employees.objects.all(), empty_label="Mitarbeiter",
                                      widget=forms.Select(attrs={
                                          'class': "form-select text-center fw-bold",
                                          'style': 'max-width: auto;',
                                      }))

and this in my views.py:

@access_group("examiner")
def examinations(request):

    context = {
        'special_header': "Begutachtungen - BA90/5",
        'names':  Employees.objects.all(),
        }

    if request.method == 'GET':
        form = ExaminationForm()

    else:

        form = ExaminationForm(request.POST)

        if 'id' in request.POST and request.POST['id'] == 'Datensatz auswählen...':
            context['error'] = 'Bitte wählen Sie zuerst einen Mitarbeiter aus'

        elif 'id' in request.POST:

            emp_id = request.POST['id']
            last_name = Employees.objects.get(id=emp_id).last_name
            first_name = Employees.objects.get(id=emp_id).first_name
            rank = Employees.objects.get(id=emp_id).title_rank

            context['entries'] = Employees.objects.filter(id=emp_id)
            context['id'] = Employees.objects.get(id=emp_id).id
            context['prefix_title'] = Employees.objects.get(id=emp_id).prefix_title
            context['last_name'] = last_name
            context['first_name'] = first_name
            context['examinations'] = Examinations.objects.filter(employee_id=emp_id).order_by('-date', 'purpose')
            context['special_header'] = 'Begutachtungen - ' + last_name + ', ' + first_name

        elif form.is_valid():

            print(request.POST['employee_number'])
            form.save()
            context['success'] = 'Neuer Begutachtungseintrag gespeichert!'

        else:

            form = ExaminationForm()
            context['error'] = 'Es ist ein Fehler aufgetreten, die eingegebenen Daten wurden NICHT gespeichert!'

    context['examination_form'] = form
    return render(request, 'examination.html', context)

rendered result looks like this:

Filling out and saving works as intended with two little problems:
i want to put a default value other than ‘blank’ in the saved cleaned form-data, if there is no entry in “comment” and i want to get rid of the form field for “employee”, as the employee has to be selected by user already to REACH this form. So actually, wenn submitting the form, additionally the user-id from already chosen employee is handed over together with the form-data. (that’s the “print” command for to check, if this works…)

So alltogehter i want to do something like, but of course, it is not working THIS way, because this would be too simple i think :wink:

        elif form.is_valid():

            print(request.POST['employee_number'])
            if form.comment = '':
                form.comment = '---'
            form.employee = request.POST['employee_number']
            form.save()
            context['success'] = 'Neuer Begutachtungseintrag gespeichert!'

Hope i could sufficently tell what i intend…

To see if I understand what you’re asking -

The first is:

and the second is:

Assuming I’m correct (and assuming you’ll correct me if I’m wrong…)

The comment field can be handled a couple different ways, but my gut-hunch suggestion would be to create a clean_comment method on the form to check for a blank entry, and substitute your desired value there.

(Other ways to do this includes creating a subclass of that field with a custom to_python method, creating a copy of request.POST and altering it before binding the data to the form, altering the form.instance, or saving with commit=False, updating the field, and saving the object.)

Likewise you have multiple options for handling the employee number. To start with, you have every option above for altering that field.

The questions become, “How does the view have access to that value?” and “How concerned are you about that value being changed?”

If you’re editing an existing model such that you’re updating this row and not creating it, it’s really simple - just don’t include that field in the form.

If you’re creating a new instance, then somehow you need to pass the number through the form on the get such that it is submitted on the post. How you do this may depend upon your security risk evaluation for this system.

The easiest (and therefore least secure) method would be to include that field in the form as a hidden field. (Django does this a lot with model forms and formsets). (It needs to be hidden and not disabled, since a disabled field is not submitted back to the view on a post.)

(Note: By “least secure”, what I mean is that the user could use the browser’s developer tools to edit that field in the page - whether or not that creates a potential problem is not something I can evaluate.)

Solved the first mentioned problem with

    def clean_comment(self):
        if self.cleaned_data['comment'] == '':
            comment = '---'
        return comment

in my forms.py, now i try to deal with the second :slight_smile:

I don’t think that’s going to work if there is data in the comment field of the form…

you are right, as always :see_no_evil:
now it works also WITH text in comment:

    def clean_comment(self):
        if self.cleaned_data['comment'] == '':
            comment = '❌'
        else:
            comment = self.cleaned_data['comment']
        return comment

In my first approach “comment” variable was not returned in all cases…I see

Concerning the second problem: The HTML-template that’s hosting the form is activated by a choice from an employee list. the employees-id is within the POST data. So i would like to skip the form field for the foreign-key field “employee” and simply assing the already chosen employee-id to it. But i get a knot in my brains while thinking about, because the above postet solution for my FIRST problem obviously doesn’t help…I have a modelForm code in forms.py and a logic in views.py. Now, how could i set the existing employee.id in forms as a default? Most likely, the solution for this looks totally different, but right now, i have no idea (and i am dealing with this for a while now…)

Trying to ensure I understand you correctly, you have “view 1” that renders a form containing a choice field. When that form is submitted, you’re saving whatever data was submitted and then redirecting you to “view 2” that renders this second form. It’s this second form that when it is posted, you want to use the employee id that was originally selected in “view 1”.

Is that a correct summary?

Just to expose you to some “idiomatic Python”, there are a couple other different ways to write your clean method. (It’s not that your code is in any way “wrong” or “not the best” - I’m only trying to expose you to some Python language features or coding patterns you may not be familiar with.)

Since an empty string is “falsey”, here’s one option:

def clean_comment(self):
    return self.cleaned_data['comment'] or '❌'

or

def clean_comment(self):
    comment = self.cleaned_data['comment'] if self.cleaned_data['comment'] else '❌'

Your summary is nearly correct, but its always same view-function…iun the meantime (with some sleep before some further thinking) i solved the problem: (as form is not displayed before employee is chosen, there will never reached the “form_is_valid” part with matching the “valid” conditiion without having a posted ‘id’ prior to it…). So i get a prefilled Form-Field for employee that i simply not display in template…

        if 'id' in request.POST:
            context = examination_context(request)
            form = ExaminationForm({'employee': request.POST['id']})

        elif form.is_valid():
            form.save()
            context = examination_context(request)
            context['success'] = 'Neuer Begutachtungseintrag gespeichert!'
            # Formular wieder leeren:
            form = ExaminationForm()