# Multiple forms/actions in single view

**URL:** <https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957>\
**Category:** Forms & APIs\
**Created:** [March 27, 2025, 11:05pm UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957 "2025-03-27T23:05:03Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![anders0nmat](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anders0nmat/32/27786_2.png) [@anders0nmat](https://forum.djangoproject.com/u/anders0nmat)\
**Post date:** [March 27, 2025, 11:05pm UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/1 "2025-03-27T23:05:03Z")

</div>

Hello,

I am building a ledger-like app where users can buy products with their accounts. Users also need to transfer money into their accounts, so a custom transaction is also possible. Because this is infrequent, it is part of the account-update page.

In the spirit of progressive enhancement, the account-update-page (and all other pages for that matter) should also be usable without JavaScript, which means, all interactions must go through forms. On the other hand, if JavaScript is available, some forms should be submitted without page refreshes via `fetch()`. The aforementioned account page might look like this:

```html
<h2>User Settings</h2>
<form method="POST">
  {{ form }}
  <button type="submit">Save</button>
</form>

<h2>Deposit</h2>
<form method="POST">
  <input type="hidden" name="action" value="deposit">
  {{ deposit_form }}
  <button type="submit">Confirm</button>
</form>

```

The associated view:

```py
class AccountUpdate(UpdateView):
    model = Account
  
    def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
        return super().get_context_data(**kwargs) | {
            'deposit_form': TransactionForm(),
        }

# url: /accounts/<pk>/
class AccountView(View):
    def get(self, request, *args, **kwargs):
        view = AccountUpdate.as_view()
        return view(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        extra_content = None
        if 'action' in request.POST:
          form = TransactionForm(self.request.POST)
          if form.is_valid():
              return custom_transaction(request)
          
          extra_context = {f'deposit_form': form}

        view = AccountUpdate.as_view(extra_context=extra_context)
        return view(request, *args, **kwargs)

# url: /transaction/custom 
def custom_transaction(request: HttpRequest):
    try:		
        if request.content_type == 'application/json':
            data = loads(request.body)
        else:
            data = request.POST

        form = TransactionForm(data)
        if not form.is_valid():
            raise ValidationError('')

        # Processing form
        
        if request.accepts('text/html'):
            return HttpResponseRedirect(reverse('account_view', args=[account.pk]))
        else:
            return JsonResponse({'transaction_id': new_transaction.pk})
    except ValidationError:
        return HttpResponseBadRequest("Invalid form submission: " + form.errors.as_text())

```

The idea being that if the submitted transaction is valid, you get redirected (to itself); if it is invalid, you get the same page but with form errors.

Now, with JS enabled, the form should instead be submitted via `fetch()` with a JSON body (no particular reason other than the ergonomics of using it in JS). Because both modes of operation still need the same processing behind the scenes, I use the “API”-function `custom_transaction` in both cases.

Generally I wonder if this is the best approach. Specifically:

- The logic to distinguish between an “API”-Call and a form submission
- The double-validation of the transaction form in the HTML-case (once on `/accounts/<pk>` to see if I need to display the template again, and then on `/transaction/custom` to actually process the request)
- The logic to distinguish between a “legitimate” account update request and a deposit request

**Which brings me to my questions:**

- **What is the best way to handle multiple actions on a single view?**
- **How can/should I design views that are used by both regular and `fetch()`-submitted forms?**

For the first question, I can think of a few alternatives:

1. Use a separate url for every action (e.g. `/accounts/<pk>` for update, `/accounts/<pk>/deposit` for deposit transactions, etc.)
2. Use distinguishing hidden fields like in the example and detect them in the `post()`-handler, delegate accordingly
3. Use distinguishing submit buttons (e.g. `<button type="submit" name="deposit">`) and detect their presence in `post()`
4. Use form prefixes and detect them somehow

My main problem with these approaches is

- For 1, that the form errors are not redirected to the page they originated from  
(think of a user submitting an invalid deposit, landing on the same page under a different domain and from there submitting an account update)
- For 2, 3, 4 the required nontivial logic to distinguish forms and insert them, if invalid, back into the template, but branch to an API-route if valid.

Both main strategies feel like unintended (one might say bad) design.

I’d appreciate some guidance.

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [March 27, 2025, 11:10pm UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/2 "2025-03-27T23:10:56Z")

</div>

> [@anders0nmat](#):
>
> How can/should I design views that are used by both regular and `fetch()`-submitted forms?

Separate views. I see no benefit in trying to have one view handle the different sets of actions.

---

<div class="post-metadata">

**Author:** ![white-seolpyo](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/white-seolpyo/32/22386_2.png) [@white-seolpyo](https://forum.djangoproject.com/u/white-seolpyo)\
**Post date:** [March 28, 2025, 5:46am UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/3 "2025-03-28T05:46:39Z")

</div>

If the field names used in two different models are different, this is how it can be implemented.

```auto
# views.py
context['formA'] = {a form}
context['formB'] = {b form}

```

```auto
# template
<form>
{{ formA }}
{{ formB }}
</form>

```

```auto
# views.py
a = {a form}(request.POST)
b = {b form}(request.POST)
if a.is_valid() and b.is_valid(): {valid action}
else: {invalid action}

```

---

<div class="post-metadata">

**Author:** ![anders0nmat](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anders0nmat/32/27786_2.png) [@anders0nmat](https://forum.djangoproject.com/u/anders0nmat)\
**Post date:** [March 28, 2025, 2:11pm UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/4 "2025-03-28T14:11:39Z")

</div>

But that’s the thing (with question 2): The action performed is the same, as the underlying form is the same in both cases. However, for `fetch()` the view should return an error (400, 404, whatever) whereas a regular submitted form should return 200 with the form errors in HTML.

Currently I rely on the client expecting a JSON response (`Accept: application/json`) to differentiate these two response behaviors.

Question 1 is more about different actions triggered from the same page. My problem here is mainly that form errors cannot be easily shown in context (=within the same page) without sacrificing either a) complexity in the view `post()` or b) continuity with the page URL (the page showing form errors is served under a different URL than the one where the form was entered)

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [March 28, 2025, 2:21pm UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/5 "2025-03-28T14:21:32Z")

</div>

I may not be following what you’re trying to describe here, but I can’t reconcile these two statements:

> [@anders0nmat](#):
>
> The action performed is the same, as the underlying form is the same in both cases.

and

> [@anders0nmat](#):
>
> However, for `fetch()` the view should return an error (400, 404, whatever) whereas a regular submitted form should return 200 with the form errors in HTML.

The action is _not_ the same.

Now, some of the functionality performed within the view might be the same - in which case that code could be factored out into a different function.

But fundamentally, these are two different views with different behavior. I do not see a benefit with trying to wedge these behaviors into a single view with a common url.

This also includes the situation in the very first code block, where you have two completely different forms being submitted. I find no value in trying to have each of those forms posting to the same URL, and trying to make a distinction within that view between the two.

URLs and views are “cheap”. Let Django do as much work as possible for you - don’t write code that you don’t need to write.

---

<div class="post-metadata">

**Author:** ![philgyford](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/philgyford/32/14295_2.png) [@philgyford](https://forum.djangoproject.com/u/philgyford)\
**Post date:** [March 28, 2025, 4:53pm UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/6 "2025-03-28T16:53:19Z")

</div>

Just to support Ken’s point of view here - I find myself that I tend to think of a view being equal to “a page”, so it should not only render a particular template, but also do _all_ of the form processing and requests for that “page”. view == page makes some intuitive sense.

And when it’s a simple page, with a single simple form, that’s fine.

But as soon as it gets more complicated like this, you (and I) have to remember that “a page” can use multiple views. And that’s often better.

One view to render its GET request. Maybe one each to process the POST for each of its forms. Another for each JS-submitted form.

Splitting the views up so they each have a single clear purpose can make things simpler than one, or two, sprawling views with many conditionals.

---

<div class="post-metadata">

**Author:** ![anders0nmat](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anders0nmat/32/27786_2.png) [@anders0nmat](https://forum.djangoproject.com/u/anders0nmat)\
**Post date:** [March 29, 2025, 8:33am UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/7 "2025-03-29T08:33:45Z")

</div>

Okay, thank you for this advice.

If I got it right, my solution should have all forms _displayed_ with the view, but those with other actions _processed_ by other views under a different url.

If that is the case, how would you handle form errors to those additional actions?

---

<div class="post-metadata">

**Author:** ![anders0nmat](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anders0nmat/32/27786_2.png) [@anders0nmat](https://forum.djangoproject.com/u/anders0nmat)\
**Post date:** [March 29, 2025, 8:46am UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/8 "2025-03-29T08:46:01Z")

</div>

This is kind of what i currently do; Granted, somewhat convoluted.  
When using multiple views for one page, there still needs to be one „entrypoint“ that delegates the request to the correct view, right?

So with that idea, I‘d have one view evaluating the correct view to present/call:

```py
def delegate(request):
    form = TransactionForm(request.POST)
    if form.is_valid():
        return transaction_view(request)
    # Now how do i distinguish between `form` really being invalid
    # Or just another form being submitted?
    return AccountUpdate.as_view()(request)

```

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [March 29, 2025, 12:04pm UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/9 "2025-03-29T12:04:59Z")

</div>

> [@anders0nmat](#):
>
> If that is the case, how would you handle form errors to those additional actions?

In your “mental model”, separate the concept of a “page” from a “view” as @philgyford points out.

Think of the views as handling actions or events. Decide what needs to happen for each case.

If I’m understanding what all you’re trying to do here, it looks like you’ve got eight distinct events.

- View 1 - returns a page:
  - First form submitted via http post, form is correct.
  - First form submitted via http post, form is incorrect.

- View 2 - returns json or html:
  - First form submitted via ajax, form is correct.
  - First form submitted via ajax, form is incorrect.

- View 3 - returns a page:
  - Second form submitted via http post, form is correct.
  - Second form submitted via http post, form is incorrect.

- View 4 - returns json or html:
  - Second form submitted via ajax, form is correct.
  - Second form submitted via ajax, form is incorrect.

If this is a correct summary, then you need to identify what needs to be returned for each of those 8 cases.

**After** you have that figured out, _then_ you can (if it appears to be desirable) factor out the common code that might be useful as a “utility function”.

---

<div class="post-metadata">

**Author:** ![philgyford](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/philgyford/32/14295_2.png) [@philgyford](https://forum.djangoproject.com/u/philgyford)\
**Post date:** [March 29, 2025, 2:52pm UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/10 "2025-03-29T14:52:43Z")

</div>

> [@anders0nmat](#):
>
> When using multiple views for one page, there still needs to be one „entrypoint“ that delegates the request to the correct view, right?

No. As Ken says, different views for the different events or actions.

To try and avoid thinking in terms of a “page”, maybe you should try coming at the idea from URLs and events.

- GET `/accounts/<pk>/`: `AccountView` renders `account.html` containing the two forms. One form submits to `/account/settings/` the other to `/account/deposit/`
- POST to `/accounts/<pk>/settings/`: `SettingsAccountView` processes the settings form.
  - If errors, renders `account.html`
  - If success, redirects to `/accounts/<pk>/`

- POST to `/accounts/<pk>/deposit/`: `DepositAccountView` processes the deposit form.
  - If errors, renders `account.html`
  - If success, redirects to `/accounts/<pk>/`

- Follow a similar pattern for the AJAX-submitted forms

I’m not saying that’s the only or best way to do it, but it’s one way.

---

<div class="post-metadata">

**Author:** ![anders0nmat](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anders0nmat/32/27786_2.png) [@anders0nmat](https://forum.djangoproject.com/u/anders0nmat)\
**Post date:** [March 29, 2025, 7:32pm UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/11 "2025-03-29T19:32:48Z")

</div>

This clears it up for me, thank you. My first draft was quite similar in structure, but somewhere along the way it seems I lost track 😅.

Just so i don’t start overthinking a non-issue again, in the case of, lets say, a form error where `account.html` should be rendered, is it sensible to call out to the regular `GET` view for that, passing the form (somehow?) to the context?  
I am asking because my context data for `accounts.html` is a bit more than just some forms, and it feels like a lot of code duplication to have that in multiple views. Or would the context data be a candidate for a separate function in that case? (I mean, both are possible, but what is the “right” way to go about it / how would you decide which way to go?)

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [March 29, 2025, 11:01pm UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/12 "2025-03-29T23:01:12Z")

</div>

> [@anders0nmat](#):
>
> Or would the context data be a candidate for a separate function in that case?

Yes, this.

> [@anders0nmat](#):
>
> it feels like a lot of code duplication to have that in multiple views.

As a starting point, there’s nothing wrong with this.

> [@anders0nmat](#):
>
> how would you decide which way to go?

I would actually start by duplicating the code as necessary. Get the views working as needed. Then, once you have functional code, look to see what’s identical between the views, and _then_ decide what can be factored out into other functions.

There’s a lot to be said for actually seeing the replicated code to identify what is in common, and what is different between the different views.

---

<div class="post-metadata">

**Author:** ![anders0nmat](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/anders0nmat/32/27786_2.png) [@anders0nmat](https://forum.djangoproject.com/u/anders0nmat)\
**Post date:** [March 29, 2025, 11:49pm UTC](https://forum.djangoproject.com/t/multiple-forms-actions-in-single-view/39957/13 "2025-03-29T23:49:56Z")

</div>

Okay, got it.

Thank you a lot for your help (and patience)
