After a valid form, how do Django's FormView, CreateView or UpdateView, and DeleteView each decide which URL to redirect to?
answer
- three different get_success_url() versions
- string formatting with the object
- model's own canonical URL
- compute it before the row is gone
basics
~10 sFormView needs success_url or raises ImproperlyConfigured. CreateView and UpdateView format success_url with the saved object's fields, else call its get_absolute_url(). DeleteView formats success_url with no fallback, computed before deleting.
solid answer
~30 sAll of them redirect to `get_success_url()`, but each mixin implements it differently. `FormMixin` returns `str(self.success_url)` and raises `ImproperlyConfigured` if it is empty. `ModelFormMixin` formats `success_url` with `self.object.__dict__`, so `'/proposals/{id}/'` works, and without one it calls `self.object.get_absolute_url()`. `DeletionMixin`, which `DeleteView` uses, also formats `success_url` but has no fallback, since the object is being removed; `BaseDeleteView.form_valid()` computes the URL before calling `delete()` because deletion sets the instance's pk to `None`. For anything dynamic, such as returning to the proposal's conference, override `get_success_url()`.
code
python · 23 linesfrom django.urls import reverse
from django.views.generic.edit import DeleteView, UpdateView
from .models import Proposal
class ProposalUpdateView(UpdateView):
model = Proposal
fields = ['title', 'abstract']
# no success_url: redirects to self.object.get_absolute_url()
class ProposalDeleteView(DeleteView):
model = Proposal
success_url = '/conferences/{conference_id}/proposals/' # attname, not 'conference'
class ProposalWithdrawView(DeleteView):
model = Proposal
def get_success_url(self):
# runs before self.object.delete(), so the foreign key is still set
return reverse('proposal-list', kwargs={'conference_id': self.object.conference_id})go deeper
Recall that editing views redirect to get_success_url(), and that model views fall back to the model's get_absolute_url().
Explain the three implementations: FormMixin requires success_url, ModelFormMixin formats it with the object or falls back, DeletionMixin formats it with no fallback.
Point out the ordering in DeleteView, the attname rule for placeholders, and that any user-supplied next target must be validated before redirecting.
Standardise on get_absolute_url() as the canonical object URL so templates, the admin and views agree, and keep success URL overrides rare and reviewed.
## One hook, three implementations Every editing generic ends a successful submission with `HttpResponseRedirect(self.get_success_url())`. What `get_success_url()` does depends on which mixin supplies it: | View | Mixin providing it | With `success_url` set | Without it | |---|---|---|---| | `FormView` | `FormMixin` | `str(success_url)` | `ImproperlyConfigured` | | `CreateView`, `UpdateView` | `ModelFormMixin` | `success_url.format(**self.object.__dict__)` | `self.object.get_absolute_url()`, else `ImproperlyConfigured` | | `DeleteView` | `DeletionMixin` | `success_url.format(**self.object.__dict__)` | `ImproperlyConfigured` | ## FormView A plain form has no object to derive a URL from, so `FormMixin.get_success_url()` simply returns `success_url` as a string. The `str()` call exists because `success_url` is often a lazy object built at import time. If the attribute is empty the view raises `ImproperlyConfigured("No URL to redirect to. Provide a success_url.")`. ## CreateView and UpdateView By the time `get_success_url()` runs, `form_valid()` has saved the form into `self.object`, so the URL can depend on the row: - **Placeholders.** `success_url = '/proposals/{id}/'` is formatted with the instance's `__dict__`. That dict holds the database column attribute names, so a foreign key is available as `{conference_id}`, not `{conference}`; `{conference}` raises a `KeyError`. - **Fallback.** With no `success_url`, the view calls `self.object.get_absolute_url()`. Defining that method on the model gives every create and update page a sensible default: after submitting a proposal, show the proposal. - **No fallback left.** If the model has no `get_absolute_url()`, the resulting `AttributeError` is turned into `ImproperlyConfigured`. ## DeleteView `DeleteView` puts `DeletionMixin` first in its bases, so its `get_success_url()` wins. It formats `success_url` the same way but has no `get_absolute_url()` fallback: the object's own page will not exist after the delete. Order matters here: 1. `BaseDeleteView.post()` fetches `self.object` and validates the confirmation form. 2. `form_valid()` calls `get_success_url()` **first**. 3. Only then does it call `self.object.delete()` and return the redirect. After `Model.delete()`, Django sets the instance's primary key attribute to `None`, so a URL built afterwards from `{id}` would read `None`. Computing it first keeps placeholders like `/conferences/{conference_id}/proposals/` usable. ## Overriding get_success_url() Override the method when the target depends on more than the object's own fields: - returning to the list for the proposal's conference, using the object's foreign key and the URL name for that list; - sending reviewers and speakers to different pages based on `self.request.user`; - honouring a `next` parameter, which must be validated against allowed hosts before use, otherwise it is an open redirect. Keep the override short and return a string. If you need a URL name reversed at class-definition time, that is the job of lazy reversing, a routing topic in its own right. ## Checklist - `FormView`: always set `success_url` or override the method. - Model views: prefer `get_absolute_url()` on the model, override only for exceptions. - `DeleteView`: always set `success_url` or override; never rely on the deleted object's page.
- Why does success_url = '/conferences/{conference}/' raise KeyError on a Django CreateView?`ModelFormMixin` formats the string with `self.object.__dict__`, which stores a foreign key under its column attribute, `conference_id`. The related object is not a key in that dict, so the placeholder must be `{conference_id}`.
- Why does DeleteView compute its success URL before deleting the object?`Model.delete()` sets the instance's primary key attribute to `None`, and the row is gone. `BaseDeleteView.form_valid()` therefore calls `get_success_url()` first, so placeholders and overrides can still read the object's fields.
A delete redirect is like writing down a guest's room number before they check out: once they have left, the front desk record is cleared and the number is gone.
saying these in an interview costs you the question
- DeleteView falls back to the object's get_absolute_url() after deleting
- FormView redirects to the current URL when success_url is missing
- success_url placeholders can use the related object name like {conference}
- The success URL is computed after the object has been deleted
- CreateView needs success_url even when the model has get_absolute_url()