表单向导中的Django模型表单集未返回

时间:2018-07-17 18:45:10

标签: python-3.x django-forms django-formwizard

在表单向导的不同步骤上有多个表单集。我遇到了一个表单集无法渲染的问题。具体来说,它返回错误:

'NoneType' object does not support item assignment

我已确定错误是我正在尝试根据表单向导中的上一步修改初始表单集数据,但是该数据没有传递给表单向导。相反,它将传递None,这将导致向导呈现常规表单而不是表单集。

查看代码,您可以看到两个表单集。一种格式collaborators可以很好地工作。另一个jobs已损坏。

我找不到理由让表单集传递None初始数据。除了(无法成功地)追溯以确定如何生成数据变量外,我还尝试了collaborators步骤中放置表单,表单集类等的多种组合。

使用这种方法,我已经注意到,问题似乎与步骤有关。如果我将jobs中的所有表单集数据放在我的FORMS排序字典中到collaborators上,则表单集将呈现。但是,关闭与jobs步骤相关联的所有其他功能不会突然允许表单集呈现。

希望这有点愚蠢。预先感谢您的帮助!

以下是相关代码:

app / views.py

FORMS = [
    ('info', project_forms.NewProjectNameForm),
    ('incomesources', project_forms.NewProjectSourcesForm),
    ('collaborators', forms.modelformset_factory(
        models.UserCollaborator,
        form=project_forms.NewProjectCollaboratorsForm)),
    ('jobs', forms.modelformset_factory(
        models.Job,
        form=project_forms.NewProjectJobForm,
        formset=project_forms.JobFormset)),
    ('checkingaccount', project_forms.NewProjectWithdrawalBankForm),
    ('review', project_forms.NewProjectReviewForm),

TEMPLATES = {
    'info': 'project/name.html',
    'incomesources': 'project/sources.html',
    'collaborators': 'project/collaborators.html',
    'jobs': 'project/jobs.html',
    'checkingaccount': 'project/checkingaccount.html',
    'review': 'project/review.html',
}

TEMPLATE_NAMES = {
    'info': "Basic Info",
    'incomesources': "Income Sources",
    'collaborators': "Collaborators",
    'jobs': "Jobs",
    'checkingaccount': "Checking Account",
    'review': "Review",

class ProjectWizard(NamedUrlSessionWizardView):
    def get_template_names(self):
        """
        Gets form template names to be rendered in page title.

        :return: Template Name
        """
        return [TEMPLATES[self.steps.current]]

    def get_collaborators_as_users(self):
        collaborators = []
        collaborators_data = self.storage.get_step_data('collaborators')
        if collaborators_data:
            total_emails = int(collaborators_data.get('collaborators-TOTAL_FORMS'))
            for email in range(total_emails):
                # Gather a list of users.
                collaborator_key = 'collaborators-' + str(email) + '-email'
                email = collaborators_data.get(collaborator_key)
                collaborators.append(User.objects.get(email__iexact=email))
        return collaborators

    def get_owner_collaborators_as_names(self, collaborators):
        collaborators_names = [self.request.user.get_full_name() + ' (Owner)']
        for collaborator in collaborators:
            if not collaborator.get_full_name():
                collaborators_names.append(collaborator.email)
            else:
                collaborators_names.append(collaborator.get_full_name())
        return collaborators_names

    def get_context_data(self, form, **kwargs):
        """
        Overrides class method.

        :param form: Current Form
        :param kwargs: Derived from class
        :return: Context is returned
        """
        context = super(ProjectWizard, self).get_context_data(form=form, **kwargs)
        step = self.steps.current
        if step == 'incomesources':
            youtube_credentials = models.YouTubeCredentials.objects.filter(user_id=self.request.user.id)
            context.update(
                {'youtube_credentials': youtube_credentials})
        elif step == 'jobs':
            collaborators = self.get_collaborators_as_users()
            collaborators_names = self.get_owner_collaborators_as_names(collaborators)
            context.update({'collaborator_names': collaborators_names})

        elif step == 'checkingaccount':
            accounts = StripeCredentials.objects.filter(id=self.request.user.id)
            if accounts.exists():
                context.update(({
                    'accounts_exist': True,
                    'stripe_public_key': settings.STRIPE_PUBLIC_KEY,
                }))
            else:
                context.update(({'accounts_exist': False}))
        elif step == 'review':
            # Get raw step data
            step_info = self.storage.get_step_data('info')
            step_income_sources = self.storage.get_step_data('incomesources')
            step_jobs = self.storage.get_step_data('jobs')
            step_checking_account = self.storage.get_step_data('checkingaccount')
            # Process collaborator objects to names
            collaborators = self.get_collaborators_as_users()
            collaborators_names = self.get_owner_collaborators_as_names(collaborators)
            # Process jobs
            total_jobs = step_jobs['jobs-TOTAL_FORMS']
            jobs = []
            for job in range(int(total_jobs)):
                # Gather a list of users.
                job_key = 'jobs-' + str(job) + '-job'
                job = step_jobs.get(job_key)
                jobs.append(job)
            print(step_checking_account)
            context.update({
                'project_name': step_info['info-name'],
                'video_id': step_income_sources['incomesources-value'],
                'collaborators_names': collaborators_names,
            })

        context.update(
            {'page_title': "New Project – " + TEMPLATE_NAMES[self.steps.current]})
        return context

    def get_form(self, step=None, data=None, files=None):
        """
        Overrides class method.

        Constructs the form for a given `step`. If no `step` is defined, the
        current step will be determined automatically.

        The form will be initialized using the `data` argument to prefill the
        new form. If needed, instance or queryset (for `ModelForm` or
        `ModelFormSet`) will be added too.
        """
        if step is None:
            step = self.steps.current
        form_class = self.form_list[step]
        # Prepare the kwargs for the form instance.
        kwargs = self.get_form_kwargs(step)

        if self.request.method == 'GET':
            if step == 'collaborators':
                assert data != None, "Formset not rendering. Data returned as: {}".format(data)
            if step == 'jobs':
                assert data != None, "Formset not rendering. Data returned as: {}".format(data)
                # Define number of forms to be registered based on previous step.
                collaborators_data = self.storage.get_step_data('collaborators')
                data['jobs-TOTAL_FORMS'] = str(int(collaborators_data['collaborators-TOTAL_FORMS']) + 1)
        kwargs.update({
            'data': data,
            'files': files,
            'prefix': self.get_form_prefix(step, form_class),
            'initial': self.get_form_initial(step),
        })
        if issubclass(form_class, (forms.ModelForm, forms.models.BaseInlineFormSet)):
            # If the form is based on ModelForm or InlineFormSet,
            # add instance if available and not previously set.
            kwargs.setdefault('instance', self.get_form_instance(step))
        elif issubclass(form_class, forms.models.BaseModelFormSet):
            # If the form is based on ModelFormSet, add queryset if available
            # and not previous set.
            kwargs.setdefault('queryset', self.get_form_instance(step))
        return form_class(**kwargs)

    def post(self, *args, **kwargs):
        """
        This method handles POST requests.

        The wizard will render either the current step (if form validation
        wasn't successful), the next step (if the current step was stored
        successful) or the done view (if no more steps are available)
        """
        # Look for a wizard_goto_step element in the posted data which
        # contains a valid step name. If one was found, render the requested
        # form. (This makes stepping back a lot easier).
        wizard_goto_step = self.request.POST.get('wizard_goto_step', None)
        if wizard_goto_step and wizard_goto_step in self.get_form_list():
            return self.render_goto_step(wizard_goto_step)

        # Check if form was refreshed
        management_form = ManagementForm(self.request.POST, prefix=self.prefix)
        if not management_form.is_valid():
            raise ValidationError(
                _('ManagementForm data is missing or has been tampered.'),
                code='missing_management_form',
            )

        form_current_step = management_form.cleaned_data['current_step']
        if (form_current_step != self.steps.current and
                self.storage.current_step is not None):
            # form refreshed, change current step
            self.storage.current_step = form_current_step

        # get the form for the current step
        form = self.get_form(data=self.request.POST, files=self.request.FILES)

        # and try to validate
        if form.is_valid():
            # if the form is valid, store the cleaned data and files.
            self.storage.set_step_data(self.steps.current, self.process_step(form))
            self.storage.set_step_files(self.steps.current, self.process_step_files(form))

            # Interact with Stripe
            if self.steps.current == 'checkingaccount':
                stripe.api_key = settings.STRIPE_SECRET_KEY
                stripe_token = self.request.POST.get('stripe_token')
                email = self.request.user.email
                description = 'Customer for ' + email
                customer = stripe.Customer.create(
                    description=description,
                    source=stripe_token
                )
                customer_id = customer.id
                stripe_credentials, created = StripeCredentials.objects.get_or_create(
                    owner_id=self.request.user)
                if created:
                    stripe_credentials.save()

            # check if the current step is the last step
            if self.steps.current == self.steps.last:
                # no more steps, render done view
                return self.render_done(form, **kwargs)
            else:
                # proceed to the next step
                return self.render_next_step(form)
        return self.render(form)

app / urls.py

project_wizard = project_views.ProjectWizard.as_view(project_views.FORMS,
    url_name='project_step', done_step_name='finished')

urlpatterns = [
    path('new/<step>', project_wizard, name='project_step'),
    path('new', project_wizard, name='new_project'),
]

1 个答案:

答案 0 :(得分:0)

从我重写的类中读取注释:

if issubclass(form_class, (forms.ModelForm, forms.models.BaseInlineFormSet)):
        # If the form is based on ModelForm or InlineFormSet,
        # add instance if available and not previously set.
        kwargs.setdefault('instance', self.get_form_instance(step))
    elif issubclass(form_class, forms.models.BaseModelFormSet):
        # If the form is based on ModelFormSet, add queryset if available
        # and not previous set.
        kwargs.setdefault('queryset', self.get_form_instance(step))

我相信这些注释意味着数据将只通过一次...”。如果有可用的查询集,请添加查询集,而不是以前的查询集。

也就是说,我确切地知道了何时调用表单集: app / views.py

...    
form_class = self.form_list[step]
...

这将返回可修改的数据,这些数据最终会传递到前端。我根据以前的表单集的总表单修改了总表单,最终能够达到我想要的结果。