Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Limiting a Django form's ManyToManyField queryset in a formtools wizard based on selection on previous form

I'm using a SessionWizardView from django-formtools to construct a two-form wizard. The challenge I'm facing is that I need to reference the input from the first form to limit the available querysets on the second form.

To make it more interesting, I'm using crispy forms for layout and the queryset needs to be limited by a method on a related item.

Here's the (much simplified) gist of where I'm at:

Models

class Product(models.Model):
    # pk, name, etc....
    catalogitem = ForeignKey("myapp.CatalogItem")
    colors = ManyToManyField("myapp.Colors")

class Colors(models.Model):
    # pk, name, etc....

class CatalogItem(models.Model):
    # Colors are stored within CatalogVariants, which I've left
    # as a blackbox in this example, since they are retrieved as
    # a queryset on this model with this method:

    # pk, name, etc....

    def get_colors(self):
        # Returns a queryset of color objects.

Views

ProductFormWizard(SessionWizardView):
    form_list = [
        productFormWizard_Step1,
        productFormWizard_Step2,
    ]

    def get_context_data(self, **kwargs):
       # ...
       pass

    def get_form_initial(self, step):
        initial = {}
        # ...
        return self.initial_dict.get(step, initial)

    def process_step(self, form):
        if self.steps.step1 == 1:
            pass
        return self.get_form_step_data(form)

    def done(self, form_list, **kwargs):
        return render(self.request, 'done.html', {
            'form_data': [form.cleaned_data for form in form_list],
        })

Forms

productFormWizard_Step1(forms.ModelForm):
    # Defines a form where the user selects a CatalogProduct.
    model = Product

productFormWizard_Step2(forms.ModelForm):
    """
    Defines a form where the user chooses colors based on 
    the CatalogProduct they selected in the previous step.
    """
    model = Product

Based on research via the Googles and some SO questions (none of which were =directly= related), I'm assuming I need to set the .queryset property on the colors field, but I'm not exactly sure where to do that. Two thoughts:

  • I would guess it goes in .get_form_initial() somehow, but I'm at a loss as to the best way to achieve that.
  • Alternatively, the appropriate code might go into the productFormWizard.get_context_data() method somehow.

Within .get_form_initial(), I can do something like this:

if step == '1':
    itemID = self.storage.get_step_data('0').data.get('0-pfProduct', "")
    if itemID:
        obj = CatalogItem.objects.get(id=itemID)
        initial['colors'] = obj.get_get_colors()

However, this just selects the available related items... it doesn't limit the list.

Additional Info

Python == 3.5.3
Django == 1.10.6
django-crispy-forms == 1.6.1
django-formtools == 2.0
like image 323
Andrew Marconi Avatar asked Mar 12 '17 19:03

Andrew Marconi


1 Answers

The solution is to override the .get_form() method on the View:

def get_form(self, step=None, data=None, files=None):

  form = super(bzProductFormWizard, self).get_form(step, data, files)

    if step == '1':
        past_data = self.get_cleaned_data_for_step('0')
        product = past_data['product']
        form.fields['colors'].queryset = ... #CUSTOM QUERYSET

    return form
like image 111
Andrew Marconi Avatar answered Oct 21 '22 16:10

Andrew Marconi