pythondjangodrop-down-menudynamic-list

How do I show dynamic and dependable list of values of model field on Front End using Django Forms?


I have a Detail Model which has a ForeignKey() of User Model. I want to show the list of subject (which is a field of Detail Model) associated with a single user, at the Front End in Template. It should be a dropdown menu and user should be able to select the subject from the list and submit the form.

How should I accomplish it?

Below is my models.py

class Detail(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    subject = models.CharField(max_length=50)
    skype_session_attendance = models.FloatField()
    internal_course_marks = models.FloatField()
    programming_lab_activity = models.FloatField()
    mid_term_marks = models.FloatField()
    final_term_marks = models.FloatField()

    def __str__(self):
        return f'{self.subject, (self.user.username)} Details'

and below is my views.py:

def performanceCalculator(request):
    if request.method == 'POST':
        performance_form = PerformanceCalculatorForm(request.POST, user=request.user)

        if performance_form.is_valid():
            sub = performance_form.cleaned_data['subject']

            detail = Detail.objects.all().filter(user=request.user, subject=sub).first()

            result = fuzz_algo(detail.skype_session_attendance,
                detail.internal_course_marks, detail.programming_lab_activity,
                detail.mid_term_marks, detail.final_term_marks)

            messages.success(request, result)

            return redirect('performance_calculator')
    else:
        performance_form = PerformanceCalculatorForm(user=request.user)

    context = {
        'performance_form': performance_form,        
    }

    return render(request, 'users/performance_calculator.html', context)

and below is forms.py:

class PerformanceCalculatorForm(forms.Form):
    subject = # what should I put here in order to make a dropdown list?

    class Meta:
        fields = ['subject']

Below is the updated code of PerformanceCalculatorForm in views.py:

class PerformanceCalculatorForm(forms.Form):
    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user')
        super(PerformanceCalculatorForm, self).__init__(*args, **kwargs)
        self.fields['subject'].queryset = Detail.objects.filter(user=user)

    subject = forms.ModelChoiceField(queryset=None)

    class Meta:
        fields = ['subject']

Solution

  • You don't need to put it here

    class PerformanceCalculatorForm(forms.Form):
        subject = # what should I put here in order to make a dropdown list?
    
        class Meta:
            fields = ['subject']
    

    There are snippets on https://docs.djangoproject.com/en/3.0/ref/models/fields/#choices

    Instead, do it on your models.py

    class Detail(models.Model):
        #create your choice tuple
        SUBJECT_CHOICES = [
            #(actual value on database, human readable text)
            ('math','Math'),
            ('algebra1', 'Algebra I'),
            ('calculus3','Calculus III'),
        ]
    
        user = models.ForeignKey(User, on_delete=models.CASCADE)
                                                  #here add the choices
        subject = models.CharField(max_length=50, choices=SUBJECT_CHOICES)
        skype_session_attendance = models.FloatField()
        internal_course_marks = models.FloatField()
        programming_lab_activity = models.FloatField()
        mid_term_marks = models.FloatField()
        final_term_marks = models.FloatField()
    
        def __str__(self):
            return f'{self.subject, (self.user.username)} Details'
    

    By passing a tuple to the choices a values, it will be replaced on the render with a select box.


    UPDATE related to adding choices dinamically to Model field: A better and practical way would be having a function that returns each subjects and append it to your choices list.

    In your models.py leave it like before.

    class Detail(models.Model):
        user = models.ForeignKey(User, on_delete=models.CASCADE)
        subject = models.CharField(max_length=50)
        skype_session_attendance = models.FloatField()
        internal_course_marks = models.FloatField()
        programming_lab_activity = models.FloatField()
        mid_term_marks = models.FloatField()
        final_term_marks = models.FloatField()
    
        def __str__(self):
            return f'{self.subject, (self.user.username)} Details'
    

    For the issue that you commented, you can just return the __str__ to whatever to you like but separate it using a special character, then this will be seen on the POST request and using split() to get your subject out of the queryset object.

    At the __str__ of your model:

        return f'{self.user.username}-{self.subject}'
    

    now in your views.py use split() to the the part that you want to match

    def performanceCalculator(request):
        if request.method == 'POST':
            performance_form = PerformanceCalculatorForm(request.POST, user=request.user)
    
            if performance_form.is_valid():
                sub = performance_form.cleaned_data['subject']
                sub = str(sub).split('-')[1] #user-subject will get the subject part
    
                detail = Detail.objects.all().filter(user=request.user, subject=sub).first()
    
                result = fuzz_algo(detail.skype_session_attendance,
                    detail.internal_course_marks, detail.programming_lab_activity,
                    detail.mid_term_marks, detail.final_term_marks)
    
                messages.success(request, result)
    
                return redirect('performance_calculator')
        else:
            performance_form = PerformanceCalculatorForm(user=request.user)
    
        context = {
            'performance_form': performance_form,        
        }
    
        return render(request, 'users/performance_calculator.html', context)