首页 > 解决方案 > How to query the database in Django forms?

问题描述

I have a model:

class Registration(models.Model):
    student_name = models.CharField(max_length=50)
    season = models.ForeignKey(Season, on_delete=models.CASCADE)
    subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
    address = models.TextField()

    class Meta:
        unique_together = (('student_name', 'selected_season', 'selected_subject'),)

I want to create a form for this model where the user will be given the option to select the subject based on the season that they selected. I have models for them as well:

class Subject(models.Model):
    subject_name = models.CharField(max_length=50)
    ...

class Season(models.Model):
    season = models.CharField(max_length=2, primary_key=True)
    subject = models.ManyToManyField(Subject)
    ...

I dont know how to query the Form. Should It be a ModelForm or a regular Form? How to query the database in the form?

标签: djangodjango-modelsdjango-forms

解决方案


You can only know which season was selected when the form is submitted, so there's no simple direct way to implement this (note that this is a HTTP limitation, not a django one). IOW you'll need either a "wizard" process or front-end scripting.

The "wizard" solution is: one first form where the user selects the season, user submits the form, your code selects the related subjects and displays a second form with subjects choices, user selects subjects and submits for final validation (nb: this is usually done within a single view, using a form hidden field to keep track of the current step and which season was selected in first step). This is garanteed to work (if correctly implemented of course xD), but not really user friendly.

Second solution is to use front-end scripting.

In it's simplest form, when the user selects the season, you use js to hide other seasons subjects (or you first hide all subjects and only display relevant ones when the season is selected). This can be done rather simply by grouping all subjects for a given season in a same fieldset (or whatever other container tag) with an id matching the season's one, or by having a distinct html "select" (with same name but different ids) per season. Of course you can also have all subjects in one single html select (or whatever), keep a front-side (js) mapping of seasons=>subjects, and update your select or whatever from this mapping.

This solution can be made to work (in "degraded" mode) without JS (you just have to make sure the subjects selector(s) are visible and active by default). You'll have to implement a custom validation backend-side (cf django's forms doc for this) to make sure the subject matches the season anyway (never trust front-side validation, it's only user-friendly sugar), so in the worst case the form will just not validate.

Now if you have a LOT of seasons and subjects, you may want to prefer doing an ajax query when the user selects the season and populate the subjects selector from this query's result (to avoid initially rendering a huge list of options), but then you can't make it work without JS anymore (whether this is an issue or not depends on your requirements).

EDIT

If i do follow the form wizard option, I need to create 2 forms, the first consisting of just the Season.

Well, you could do it with one single form (passing an argument to specify what should be done) but using two forms is much simpler indeed.

The 2nd form will consist of the rest of the options (except for seasons), am I right? So should the 2nd form be a modelform?

Well, that's the point of modelforms, isn't it ?

How do I put a queryset in the ModelChoiceField in modelform? I googled but could't find anything –</p>

In your form's __init__, you update self.fields["subject"].queryset with your filtered queryset.

IMPORTANT: do NOT try to touch self.fieldname - this is the class-level field definition, so changing it would totally unexpected results in production (been here, done that, and that was quite some fun to debug xD).

Now this being said, for your use case, I reckon you'd be better with the simple js filtering solution - it's the easiest to set up - on the backend it's totally transparent, and the front-end part is rather simple using jQuery -, it's much more user-friendly, and it's garanteed to work.


推荐阅读