← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:mypy-next-cancel-url into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:mypy-next-cancel-url into launchpad:master.

Commit message:
Avoid substitution issue with next_url and cancel_url

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/437116

Launchpad's form views are allowed to define `next_url` and `cancel_url` to indicate where the form should redirect to after a successful submission or if the user chooses to cancel.  Some do this by assigning to those attributes (especially if the URL is computed based on the submitted form), while some do this by defining those attributes as properties (especially if there are multiple form actions that use the same URLs).  There's no obvious way to settle on one approach or the other here, but until now it hasn't mattered.

Unfortunately, `mypy` detects a technical difficulty here.  If a base class declares something as a plain attribute, then a subclass that redefines that as a property without also providing a setter (which wouldn't really make sense here) violates the Liskov substitution principle, because assigning to that attribute is allowed for instances of the base class but forbidden for instances of the subclass.

However, if we change the base class to make those attributes be read-only properties (which is all that the general form view infrastructure needs), and then redefine them as plain class attributes (e.g. with `next_url = None`) in all the subclasses that need to assign to them at some point, then the type checker is happier and everything still works.  This slightly increases the friction of writing views that assign to those attributes, but for many cases of new views properties will be just fine, and it's lightweight enough not to be too big a problem anyway.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:mypy-next-cancel-url into launchpad:master.
diff --git a/lib/lp/answers/browser/faq.py b/lib/lp/answers/browser/faq.py
index 732bd2d..e8cf8c7 100644
--- a/lib/lp/answers/browser/faq.py
+++ b/lib/lp/answers/browser/faq.py
@@ -69,6 +69,7 @@ class FAQEditView(LaunchpadEditFormView):
     schema = IFAQ
     label = _("Edit FAQ")
     field_names = ["title", "keywords", "content"]
+    next_url = None
 
     @property
     def page_title(self):
diff --git a/lib/lp/answers/browser/faqtarget.py b/lib/lp/answers/browser/faqtarget.py
index 7f37282..093ed63 100644
--- a/lib/lp/answers/browser/faqtarget.py
+++ b/lib/lp/answers/browser/faqtarget.py
@@ -35,6 +35,7 @@ class FAQCreateView(LaunchpadFormView):
     schema = IFAQ
     label = _("Create a new FAQ")
     field_names = ["title", "keywords", "content"]
+    next_url = None
 
     custom_widget_keywords = TokensTextWidget
 
diff --git a/lib/lp/answers/browser/question.py b/lib/lp/answers/browser/question.py
index 65bbd94..6d3ff60 100644
--- a/lib/lp/answers/browser/question.py
+++ b/lib/lp/answers/browser/question.py
@@ -309,6 +309,7 @@ class QuestionSetView(LaunchpadFormView):
 
     schema = IAnswersFrontPageSearchForm
     custom_widget_scope = ProjectScopeWidget
+    next_url = None
 
     page_title = "Launchpad Answers"
     label = "Questions and Answers"
@@ -964,6 +965,7 @@ class QuestionWorkflowView(LaunchpadFormView, LinkFAQMixin):
     """
 
     schema = IQuestionAddMessageForm
+    next_url = None
 
     # Do not autofocus the message widget.
     initial_focus_widget = None
@@ -1364,6 +1366,7 @@ class QuestionCreateFAQView(LinkFAQMixin, LaunchpadFormView):
     """View to create a new FAQ."""
 
     schema = IFAQ
+    next_url = None
     label = _("Create a new FAQ")
 
     @property
diff --git a/lib/lp/answers/browser/questiontarget.py b/lib/lp/answers/browser/questiontarget.py
index 8682cf9..ed993fb 100644
--- a/lib/lp/answers/browser/questiontarget.py
+++ b/lib/lp/answers/browser/questiontarget.py
@@ -746,6 +746,7 @@ class ManageAnswerContactView(UserSupportLanguagesMixin, LaunchpadFormView):
 
     label = page_title
     custom_widget_answer_contact_teams = LabeledMultiCheckBoxWidget
+    next_url = None
 
     def setUpFields(self):
         """See `LaunchpadFormView`."""
diff --git a/lib/lp/app/browser/launchpadform.py b/lib/lp/app/browser/launchpadform.py
index 126eb3f..3578008 100644
--- a/lib/lp/app/browser/launchpadform.py
+++ b/lib/lp/app/browser/launchpadform.py
@@ -67,10 +67,15 @@ class LaunchpadFormView(LaunchpadView):
     field_names = None  # type: Optional[List[str]]
 
     # The next URL to redirect to on successful form submission
-    next_url = None  # type: Optional[str]
+    @property
+    def next_url(self) -> Optional[str]:
+        return None
+
     # The cancel URL is rendered as a Cancel link in the form
     # macro if set in a derived class.
-    cancel_url = None  # type: Optional[str]
+    @property
+    def cancel_url(self) -> Optional[str]:
+        return None
 
     # The name of the widget that will receive initial focus in the form.
     # By default, the first widget will receive focus.  Set this to None
diff --git a/lib/lp/app/doc/launchpadform.rst b/lib/lp/app/doc/launchpadform.rst
index 996ec23..a138ca8 100644
--- a/lib/lp/app/doc/launchpadform.rst
+++ b/lib/lp/app/doc/launchpadform.rst
@@ -396,6 +396,7 @@ Form Rendering
     >>> class RenderFormTest(LaunchpadFormView):
     ...     schema = IFormTest
     ...     field_names = ["displayname"]
+    ...     next_url = None
     ...
     ...     def template(self):
     ...         return "Content that comes from a ZCML registered template."
diff --git a/lib/lp/blueprints/browser/specification.py b/lib/lp/blueprints/browser/specification.py
index 1186390..92f61af 100644
--- a/lib/lp/blueprints/browser/specification.py
+++ b/lib/lp/blueprints/browser/specification.py
@@ -867,6 +867,7 @@ class SpecificationEditView(LaunchpadEditFormView):
 
     schema = SpecificationEditSchema
     field_names = ["name", "title", "specurl", "summary", "whiteboard"]
+    next_url = None
     label = "Edit specification"
     custom_widget_summary = CustomWidgetFactory(TextAreaWidget, height=5)
     custom_widget_whiteboard = CustomWidgetFactory(TextAreaWidget, height=10)
@@ -1002,6 +1003,7 @@ class SpecificationGoalProposeView(LaunchpadEditFormView):
     label = "Target to a distribution series"
     field_names = ["distroseries", "whiteboard"]
     custom_widget_whiteboard = CustomWidgetFactory(TextAreaWidget, height=5)
+    next_url = None
 
     @property
     def initial_values(self):
@@ -1123,6 +1125,7 @@ class SpecificationRetargetingView(LaunchpadFormView):
 class SpecificationSupersedingView(LaunchpadFormView):
     schema = ISpecification
     field_names = ["superseded_by"]
+    next_url = None
     label = _("Mark blueprint superseded")
 
     @property
@@ -1386,6 +1389,7 @@ class SpecificationSprintAddView(LaunchpadFormView):
     schema = ISprintSpecification
     label = _("Propose specification for meeting agenda")
     field_names = ["sprint"]
+    next_url = None
     # ISprintSpecification.sprint is a read-only field, so we need to set
     # for_input to True here to ensure it's rendered as an input widget.
     for_input = True
@@ -1657,6 +1661,7 @@ class SpecificationLinkBranchView(LaunchpadFormView):
 class SpecificationSetView(AppFrontPageSearchView, HasSpecificationsView):
     """View for the Blueprints index page."""
 
+    next_url = None
     label = "Blueprints"
 
     @property
diff --git a/lib/lp/blueprints/browser/specificationdependency.py b/lib/lp/blueprints/browser/specificationdependency.py
index cdf6ed7..513ac61 100644
--- a/lib/lp/blueprints/browser/specificationdependency.py
+++ b/lib/lp/blueprints/browser/specificationdependency.py
@@ -75,6 +75,7 @@ class SpecificationDependencyRemoveView(LaunchpadFormView):
     schema = ISpecificationDependencyRemoval
     label = "Remove a dependency"
     field_names = ["dependency"]
+    next_url = None
     for_input = True
 
     @action("Continue", name="continue")
diff --git a/lib/lp/blueprints/browser/sprint.py b/lib/lp/blueprints/browser/sprint.py
index 047084a..9d66d8b 100644
--- a/lib/lp/blueprints/browser/sprint.py
+++ b/lib/lp/blueprints/browser/sprint.py
@@ -403,6 +403,7 @@ class SprintDeleteView(LaunchpadFormView):
 
     schema = ISprint
     field_names = []  # type: List[str]
+    next_url = None
 
     @property
     def label(self):
diff --git a/lib/lp/bugs/browser/bugalsoaffects.py b/lib/lp/bugs/browser/bugalsoaffects.py
index 0ec2af3..642dd90 100644
--- a/lib/lp/bugs/browser/bugalsoaffects.py
+++ b/lib/lp/bugs/browser/bugalsoaffects.py
@@ -207,6 +207,8 @@ class BugTaskCreationStep(AlsoAffectsStep):
     registered.
     """
 
+    next_url = None
+
     custom_widget_bug_url = CustomWidgetFactory(
         StrippedTextWidget, displayWidth=62
     )
@@ -820,6 +822,7 @@ class BugAlsoAffectsProductWithProductCreationView(
         StrippedTextWidget, displayWidth=62
     )
     custom_widget_existing_product = LaunchpadRadioWidget
+    next_url = None
     existing_products = None
     MAX_PRODUCTS_TO_DISPLAY = 10
     licenses = [License.DONT_KNOW]
diff --git a/lib/lp/bugs/browser/bugattachment.py b/lib/lp/bugs/browser/bugattachment.py
index ce004b8..0588b51 100644
--- a/lib/lp/bugs/browser/bugattachment.py
+++ b/lib/lp/bugs/browser/bugattachment.py
@@ -106,6 +106,8 @@ class BugAttachmentEditView(LaunchpadFormView, BugAttachmentContentCheck):
 
     schema = IBugAttachmentEditForm
     field_names = ["title", "patch", "contenttype"]
+    next_url = None
+    cancel_url = None
 
     def __init__(self, context, request):
         LaunchpadFormView.__init__(self, context, request)
@@ -206,6 +208,8 @@ class BugAttachmentPatchConfirmationView(LaunchpadFormView):
     """
 
     schema = IBugAttachmentIsPatchConfirmationForm
+    next_url = None
+    cancel_url = None
 
     custom_widget_patch = LaunchpadBooleanRadioWidget
 
diff --git a/lib/lp/bugs/browser/buglinktarget.py b/lib/lp/bugs/browser/buglinktarget.py
index 9dda500..693a9eb 100644
--- a/lib/lp/bugs/browser/buglinktarget.py
+++ b/lib/lp/bugs/browser/buglinktarget.py
@@ -39,6 +39,7 @@ class BugLinkView(LaunchpadFormView):
     label = _("Link a bug report")
     schema = IBugLinkForm
     page_title = label
+    next_url = None
 
     focused_element_id = "bug"
 
@@ -139,6 +140,7 @@ class BugsUnlinkView(LaunchpadFormView):
     schema = IUnlinkBugsForm
     custom_widget_bugs = LabeledMultiCheckBoxWidget
     page_title = label
+    next_url = None
 
     @property
     def cancel_url(self):
diff --git a/lib/lp/bugs/browser/bugmessage.py b/lib/lp/bugs/browser/bugmessage.py
index 30cff9e..45761fc 100644
--- a/lib/lp/bugs/browser/bugmessage.py
+++ b/lib/lp/bugs/browser/bugmessage.py
@@ -25,6 +25,7 @@ class BugMessageAddFormView(LaunchpadFormView, BugAttachmentContentCheck):
     """Browser view class for adding a bug comment/attachment."""
 
     schema = IBugMessageAddForm
+    next_url = None
     initial_focus_widget = None
 
     custom_widget_comment = CustomWidgetFactory(
diff --git a/lib/lp/bugs/browser/bugtarget.py b/lib/lp/bugs/browser/bugtarget.py
index 5ae70f2..ed93035 100644
--- a/lib/lp/bugs/browser/bugtarget.py
+++ b/lib/lp/bugs/browser/bugtarget.py
@@ -207,12 +207,12 @@ class FileBugViewBase(LaunchpadFormView):
     """Base class for views related to filing a bug."""
 
     schema = IBug
-
     custom_widget_information_type = LaunchpadRadioWidgetWithDescription
     custom_widget_comment = CustomWidgetFactory(
         TextAreaWidget, cssClass="comment-text"
     )
     custom_widget_packagename = FileBugSourcePackageNameWidget
+    next_url = None
 
     extra_data_token = None
 
@@ -1350,6 +1350,7 @@ class OfficialBugTagsManageView(LaunchpadEditFormView):
 
     schema = IOfficialBugTagTargetPublic
     custom_widget_official_bug_tags = LargeBugTagsWidget
+    next_url = None
 
     label = "Manage official bug tags"
 
diff --git a/lib/lp/bugs/browser/bugtracker.py b/lib/lp/bugs/browser/bugtracker.py
index 7460e9b..0c02abc 100644
--- a/lib/lp/bugs/browser/bugtracker.py
+++ b/lib/lp/bugs/browser/bugtracker.py
@@ -112,6 +112,7 @@ class BugTrackerAddView(LaunchpadFormView):
         "summary",
         "contactdetails",
     ]
+    next_url = None
 
     def setUpWidgets(self, context=None):
         # We only show those bug tracker types for which there can be
@@ -251,7 +252,6 @@ BUG_TRACKER_ACTIVE_VOCABULARY = SimpleVocabulary.fromItems(
 class BugTrackerEditView(LaunchpadEditFormView):
 
     schema = IBugTracker
-
     custom_widget_summary = CustomWidgetFactory(
         TextAreaWidget, width=30, height=5
     )
@@ -259,6 +259,7 @@ class BugTrackerEditView(LaunchpadEditFormView):
     custom_widget_active = CustomWidgetFactory(
         LaunchpadRadioWidget, orientation="vertical"
     )
+    next_url = None
 
     @property
     def page_title(self):
diff --git a/lib/lp/buildmaster/browser/builder.py b/lib/lp/buildmaster/browser/builder.py
index 1c24da5..2d3343a 100644
--- a/lib/lp/buildmaster/browser/builder.py
+++ b/lib/lp/buildmaster/browser/builder.py
@@ -422,6 +422,7 @@ class BuilderSetAddView(LaunchpadFormView):
     """View class for adding new Builders."""
 
     schema = IBuilder
+    next_url = None
 
     label = "Register a new build machine"
 
diff --git a/lib/lp/charms/browser/charmrecipe.py b/lib/lp/charms/browser/charmrecipe.py
index f80aebb..fefba73 100644
--- a/lib/lp/charms/browser/charmrecipe.py
+++ b/lib/lp/charms/browser/charmrecipe.py
@@ -299,6 +299,8 @@ def log_oops(error, request):
 
 
 class CharmRecipeAuthorizeMixin:
+    next_url = None
+
     def requestAuthorization(self, recipe):
         try:
             self.next_url = CharmRecipeAuthorizeView.requestAuthorization(
@@ -629,6 +631,7 @@ class CharmRecipeDeleteView(BaseCharmRecipeEditView):
     page_title = "Delete"
 
     field_names = []
+    next_url = None
 
     @action("Delete charm recipe", name="delete")
     def delete_action(self, action, data):
@@ -644,6 +647,7 @@ class CharmRecipeRequestBuildsView(LaunchpadFormView):
     def label(self):
         return "Request builds for %s" % self.context.name
 
+    next_url = None
     page_title = "Request builds"
 
     class schema(Interface):
diff --git a/lib/lp/code/browser/branch.py b/lib/lp/code/browser/branch.py
index 0b4add6..c12a290 100644
--- a/lib/lp/code/browser/branch.py
+++ b/lib/lp/code/browser/branch.py
@@ -645,8 +645,8 @@ class BranchView(
 class BranchRescanView(LaunchpadEditFormView):
 
     schema = Interface
-
     field_names = []
+    next_url = None
 
     @action("Rescan", name="rescan")
     def rescan(self, action, data):
@@ -966,6 +966,7 @@ class BranchDeletionView(LaunchpadFormView):
 
     schema = IBranch
     field_names = []
+    next_url = None
 
     @property
     def page_title(self):
@@ -1301,6 +1302,7 @@ class RegisterBranchMergeProposalView(LaunchpadFormView):
     """The view to register new branch merge proposals."""
 
     schema = RegisterProposalSchema
+    next_url = None
     for_input = True
 
     custom_widget_target_branch = TargetBranchWidget
diff --git a/lib/lp/code/browser/branchmergeproposal.py b/lib/lp/code/browser/branchmergeproposal.py
index a789afb..019b22c 100644
--- a/lib/lp/code/browser/branchmergeproposal.py
+++ b/lib/lp/code/browser/branchmergeproposal.py
@@ -627,6 +627,7 @@ class BranchMergeProposalView(
     """A basic view used for the index page."""
 
     schema = ClaimButton
+    next_url = None
 
     def initialize(self):
         super().initialize()
@@ -887,8 +888,8 @@ class BranchMergeProposalView(
 
 class BranchMergeProposalRescanView(LaunchpadEditFormView):
     schema = Interface
-
     field_names = []
+    next_url = None
 
     @action("Rescan", name="rescan")
     def rescan(self, action, data):
@@ -1039,8 +1040,8 @@ class BranchMergeProposalVoteView(LaunchpadView):
 class BranchMergeProposalScheduleUpdateDiffView(LaunchpadEditFormView):
 
     schema = Interface
-
     field_names = []
+    next_url = None
 
     @action("Update", name="update")
     def update(self, action, data):
@@ -1118,6 +1119,9 @@ class MergeProposalEditView(
 ):
     """A base class for merge proposal edit views."""
 
+    next_url = None
+    cancel_url = None
+
     def initialize(self):
         # Record next_url and cancel url now
         self.next_url = canonical_url(self.context)
@@ -1143,6 +1147,8 @@ class BranchMergeProposalResubmitView(
     """The view to resubmit a proposal to merge."""
 
     schema = ResubmitSchema
+    next_url = None
+    cancel_url = None
     for_input = True
     page_title = label = "Resubmit proposal to merge"
 
@@ -1277,6 +1283,7 @@ class BranchMergeProposalDeleteView(MergeProposalEditView):
 
     schema = IBranchMergeProposal
     field_names = []
+    next_url = None
     page_title = label = "Delete proposal to merge branch"
 
     def initialize(self):
diff --git a/lib/lp/code/browser/codeimport.py b/lib/lp/code/browser/codeimport.py
index 7856269..e856781 100644
--- a/lib/lp/code/browser/codeimport.py
+++ b/lib/lp/code/browser/codeimport.py
@@ -345,6 +345,7 @@ class CodeImportNewView(CodeImportBaseView, CodeImportNameValidationMixin):
     """The view to request a new code import."""
 
     schema = NewCodeImportForm
+    next_url = None
     for_input = True
 
     custom_widget_rcs_type = LaunchpadRadioWidget
@@ -635,6 +636,8 @@ class CodeImportEditView(CodeImportBaseView):
     """
 
     schema = EditCodeImportForm
+    next_url = None
+    cancel_url = None
 
     # Need this to render the context to prepopulate the form fields.
     # Added here as the base class isn't LaunchpadEditFormView.
diff --git a/lib/lp/code/browser/gitref.py b/lib/lp/code/browser/gitref.py
index df8c704..33f630a 100644
--- a/lib/lp/code/browser/gitref.py
+++ b/lib/lp/code/browser/gitref.py
@@ -333,6 +333,7 @@ class GitRefRegisterMergeProposalView(LaunchpadFormView):
     """The view to register new Git merge proposals."""
 
     schema = GitRefRegisterMergeProposalSchema
+    next_url = None
     for_input = True
 
     custom_widget_target_git_ref = CustomWidgetFactory(
diff --git a/lib/lp/code/browser/gitrepository.py b/lib/lp/code/browser/gitrepository.py
index c3724f0..17056f8 100644
--- a/lib/lp/code/browser/gitrepository.py
+++ b/lib/lp/code/browser/gitrepository.py
@@ -552,8 +552,8 @@ class GitRepositoryView(
 class GitRepositoryForkView(LaunchpadEditFormView):
 
     schema = Interface
-
     field_names = []
+    next_url = None
 
     def initialize(self):
         if not getFeatureFlag(GIT_REPOSITORY_FORK_ENABLED):
@@ -596,8 +596,8 @@ class GitRepositoryForkView(LaunchpadEditFormView):
 class GitRepositoryRescanView(LaunchpadEditFormView):
 
     schema = Interface
-
     field_names = []
+    next_url = None
 
     @action("Rescan", name="rescan")
     def rescan(self, action, data):
@@ -1019,6 +1019,8 @@ class GitRulePatternField(UniqueField):
 class GitRepositoryPermissionsView(LaunchpadFormView):
     """A view to manage repository permissions."""
 
+    next_url = None
+
     heads_prefix = "refs/heads/"
     tags_prefix = "refs/tags/"
 
@@ -1552,6 +1554,7 @@ class GitRepositoryDeletionView(LaunchpadFormView):
 
     schema = IGitRepository
     field_names = []
+    next_url = None
 
     @property
     def page_title(self):
diff --git a/lib/lp/code/browser/sourcepackagerecipe.py b/lib/lp/code/browser/sourcepackagerecipe.py
index 002520d..baed5b7 100644
--- a/lib/lp/code/browser/sourcepackagerecipe.py
+++ b/lib/lp/code/browser/sourcepackagerecipe.py
@@ -445,6 +445,8 @@ class SourcePackageRecipeRequestBuildsHtmlView(
 ):
     """Supports HTML form recipe build requests."""
 
+    next_url = None
+
     @property
     def title(self):
         return "Request builds for %s" % self.context.name
@@ -536,6 +538,8 @@ class SourcePackageRecipeRequestDailyBuildView(LaunchpadFormView):
     This view works for both ajax and html form requests.
     """
 
+    next_url = None
+
     # Attributes for the html version
     page_title = "Build now"
 
@@ -782,6 +786,7 @@ class SourcePackageRecipeAddView(
     title = label = "Create a new source package recipe"
 
     schema = ISourcePackageAddSchema
+    next_url = None
     custom_widget_distroseries = LabeledMultiCheckBoxWidget
     custom_widget_owner = RecipeOwnerWidget
     custom_widget_use_ppa = LaunchpadRadioWidget
@@ -959,6 +964,7 @@ class SourcePackageRecipeEditView(
     label = title
 
     schema = ISourcePackageEditSchema
+    next_url = None
     custom_widget_distroseries = LabeledMultiCheckBoxWidget
 
     def setUpFields(self):
diff --git a/lib/lp/coop/answersbugs/browser.py b/lib/lp/coop/answersbugs/browser.py
index 879a801..70c5abd 100644
--- a/lib/lp/coop/answersbugs/browser.py
+++ b/lib/lp/coop/answersbugs/browser.py
@@ -16,8 +16,8 @@ class QuestionMakeBugView(LaunchpadFormView):
     """Browser class for adding a bug from a question."""
 
     schema = IBug
-
     field_names = ["title", "description"]
+    next_url = None
 
     def initialize(self):
         """Initialize the view when a Bug may be reported for the Question."""
diff --git a/lib/lp/oci/browser/ocirecipe.py b/lib/lp/oci/browser/ocirecipe.py
index 1e5e06e..7fe9c3d 100644
--- a/lib/lp/oci/browser/ocirecipe.py
+++ b/lib/lp/oci/browser/ocirecipe.py
@@ -403,6 +403,8 @@ class InvisibleCredentialsWidget(DisplayWidget):
 class OCIRecipeEditPushRulesView(LaunchpadFormView):
     """View for +ocirecipe-edit-push-rules.pt."""
 
+    next_url = None
+
     class schema(Interface):
         """Schema for editing push rules."""
 
@@ -794,6 +796,8 @@ class OCIRecipeEditPushRulesView(LaunchpadFormView):
 class OCIRecipeRequestBuildsView(LaunchpadFormView):
     """A view for requesting builds of an OCI recipe."""
 
+    next_url = None
+
     @property
     def label(self):
         return "Request builds for %s" % self.context.name
@@ -986,6 +990,7 @@ class OCIRecipeAddView(
         "build_daily",
     )
     custom_widget_git_ref = GitRefWidget
+    next_url = None
 
     def initialize(self):
         super().initialize()
@@ -1133,6 +1138,7 @@ class OCIRecipeAddView(
 class BaseOCIRecipeEditView(LaunchpadEditFormView):
 
     schema = IOCIRecipeEditSchema
+    next_url = None
 
     @property
     def private(self):
@@ -1345,6 +1351,7 @@ class OCIRecipeDeleteView(BaseOCIRecipeEditView):
     page_title = "Delete"
 
     field_names = ()
+    next_url = None
 
     @action("Delete OCI recipe", name="delete")
     def delete_action(self, action, data):
diff --git a/lib/lp/registry/browser/announcement.py b/lib/lp/registry/browser/announcement.py
index ef7957e..6260fdf 100644
--- a/lib/lp/registry/browser/announcement.py
+++ b/lib/lp/registry/browser/announcement.py
@@ -131,6 +131,7 @@ class AnnouncementAddView(LaunchpadFormView):
     """A view for creating a new Announcement."""
 
     schema = AddAnnouncementForm
+    next_url = None
     label = "Make an announcement"
     page_title = label
 
@@ -162,6 +163,7 @@ class AnnouncementEditView(AnnouncementFormMixin, LaunchpadFormView):
     """A view which allows you to edit the announcement."""
 
     schema = AddAnnouncementForm
+    next_url = None
     field_names = [
         "title",
         "summary",
@@ -203,6 +205,7 @@ class AnnouncementRetargetView(AnnouncementFormMixin, LaunchpadFormView):
 
     schema = AnnouncementRetargetForm
     field_names = ["target"]
+    next_url = None
     page_title = "Move announcement"
 
     def validate(self, data):
@@ -242,6 +245,7 @@ class AnnouncementPublishView(AnnouncementFormMixin, LaunchpadFormView):
 
     schema = AddAnnouncementForm
     field_names = ["publication_date"]
+    next_url = None
     page_title = "Publish announcement"
 
     custom_widget_publication_date = AnnouncementDateWidget
@@ -257,6 +261,7 @@ class AnnouncementRetractView(AnnouncementFormMixin, LaunchpadFormView):
     """A view to unpublish an announcement."""
 
     schema = IAnnouncement
+    next_url = None
     page_title = "Retract announcement"
 
     @action(_("Retract"), name="retract")
@@ -269,6 +274,7 @@ class AnnouncementDeleteView(AnnouncementFormMixin, LaunchpadFormView):
     """A view to delete an annoucement."""
 
     schema = IAnnouncement
+    next_url = None
     page_title = "Delete announcement"
 
     @action(_("Delete"), name="delete", validator="validate_cancel")
diff --git a/lib/lp/registry/browser/codeofconduct.py b/lib/lp/registry/browser/codeofconduct.py
index 3c47070..531e8c2 100644
--- a/lib/lp/registry/browser/codeofconduct.py
+++ b/lib/lp/registry/browser/codeofconduct.py
@@ -179,6 +179,7 @@ class AffirmCodeOfConductView(LaunchpadFormView):
         )
 
     field_names = ["affirmed"]
+    next_url = None
 
     @property
     def page_title(self):
@@ -206,6 +207,7 @@ class SignedCodeOfConductAddView(LaunchpadFormView):
 
     schema = ISignedCodeOfConduct
     field_names = ["signedcode"]
+    next_url = None
 
     @property
     def page_title(self):
diff --git a/lib/lp/registry/browser/distribution.py b/lib/lp/registry/browser/distribution.py
index 7180941..ba5d9e9 100644
--- a/lib/lp/registry/browser/distribution.py
+++ b/lib/lp/registry/browser/distribution.py
@@ -1052,6 +1052,7 @@ class DistributionAddView(
     ]
     custom_widget_require_virtualized = CheckBoxWidget
     custom_widget_processors = LabeledMultiCheckBoxWidget
+    next_url = None
 
     @property
     def page_title(self):
@@ -1227,11 +1228,11 @@ class DistributionAdminView(LaunchpadEditFormView):
         "redirect_default_traversal",
         "information_type",
     ]
-
     custom_widget_information_type = CustomWidgetFactory(
         LaunchpadRadioWidgetWithDescription,
         vocabulary=InformationTypeVocabulary(types=PILLAR_INFORMATION_TYPES),
     )
+    next_url = None
 
     @property
     def label(self):
@@ -1602,6 +1603,7 @@ class DistributionPublisherConfigView(LaunchpadFormView):
 
     schema = IPublisherConfig
     field_names = ["root_dir", "base_url", "copy_base_url"]
+    next_url = None
 
     @property
     def label(self):
diff --git a/lib/lp/registry/browser/distributionmirror.py b/lib/lp/registry/browser/distributionmirror.py
index 262784e..0876547 100644
--- a/lib/lp/registry/browser/distributionmirror.py
+++ b/lib/lp/registry/browser/distributionmirror.py
@@ -178,6 +178,7 @@ class DistributionMirrorDeleteView(LaunchpadFormView):
 
     schema = IDistributionMirror
     field_names = []
+    next_url = None
 
     @property
     def label(self):
@@ -231,6 +232,7 @@ class DistributionMirrorAddView(LaunchpadFormView):
         "content",
         "official_candidate",
     ]
+    next_url = None
     invariant_context = None
 
     @property
@@ -273,6 +275,7 @@ class DistributionMirrorReviewView(LaunchpadEditFormView):
 
     schema = IDistributionMirror
     field_names = ["status", "whiteboard"]
+    next_url = None
 
     @property
     def label(self):
@@ -316,6 +319,7 @@ class DistributionMirrorEditView(LaunchpadEditFormView):
         "content",
         "official_candidate",
     ]
+    next_url = None
 
     @property
     def label(self):
@@ -342,6 +346,7 @@ class DistributionMirrorResubmitView(LaunchpadEditFormView):
 
     schema = IDistributionMirror
     field_names = []
+    next_url = None
 
     @property
     def label(self):
diff --git a/lib/lp/registry/browser/distroseries.py b/lib/lp/registry/browser/distroseries.py
index f1a25fe..b9e1af2 100644
--- a/lib/lp/registry/browser/distroseries.py
+++ b/lib/lp/registry/browser/distroseries.py
@@ -614,6 +614,7 @@ class DistroSeriesEditView(LaunchpadEditFormView, SeriesStatusMixin):
     schema = IDistroSeries
     field_names = ["display_name", "title", "summary", "description"]
     custom_widget_status = LaunchpadDropdownWidget
+    next_url = None
 
     @property
     def label(self):
@@ -672,6 +673,7 @@ class DistroSeriesAdminView(LaunchpadEditFormView, SeriesStatusMixin):
         "inherit_overrides_from_parents",
     ]
     custom_widget_status = LaunchpadDropdownWidget
+    next_url = None
 
     @property
     def label(self):
@@ -745,6 +747,7 @@ class DistroSeriesAddView(LaunchpadFormView):
         "display_name",
         "summary",
     ]
+    next_url = None
 
     help_links = {
         "name": "/+help-registry/distribution-add-series.html#codename",
@@ -952,6 +955,7 @@ class DistroSeriesDifferenceBaseView(
         header="Select person being sponsored",
         show_assign_me_button=False,
     )
+    next_url = None
 
     # Differences type to display. Can be overrided by sublasses.
     differences_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
diff --git a/lib/lp/registry/browser/featuredproject.py b/lib/lp/registry/browser/featuredproject.py
index 4152919..6eb67f9 100644
--- a/lib/lp/registry/browser/featuredproject.py
+++ b/lib/lp/registry/browser/featuredproject.py
@@ -48,6 +48,7 @@ class FeaturedProjectsView(LaunchpadFormView):
 
     schema = FeaturedProjectForm
     custom_widget_remove = LabeledMultiCheckBoxWidget
+    next_url = None
 
     @action(_("Update featured project list"), name="update")
     def update_action(self, action, data):
diff --git a/lib/lp/registry/browser/karma.py b/lib/lp/registry/browser/karma.py
index 19daccc..b9c2a09 100644
--- a/lib/lp/registry/browser/karma.py
+++ b/lib/lp/registry/browser/karma.py
@@ -42,6 +42,7 @@ class KarmaActionEditView(LaunchpadEditFormView):
 
     schema = IKarmaAction
     field_names = ["name", "category", "points", "title", "summary"]
+    next_url = None
 
     @property
     def label(self):
diff --git a/lib/lp/registry/browser/milestone.py b/lib/lp/registry/browser/milestone.py
index e6926a3..8462a5c 100644
--- a/lib/lp/registry/browser/milestone.py
+++ b/lib/lp/registry/browser/milestone.py
@@ -478,8 +478,8 @@ class MilestoneAddView(MilestoneTagBase, LaunchpadFormView):
     schema = IMilestone
     field_names = ["name", "code_name", "dateexpected", "summary"]
     label = "Register a new milestone"
-
     custom_widget_dateexpected = DateWidget
+    next_url = None
 
     @action(_("Register Milestone"), name="register")
     def register_action(self, action, data):
@@ -516,8 +516,8 @@ class MilestoneEditView(MilestoneTagBase, LaunchpadEditFormView):
 
     schema = IMilestone
     label = "Modify milestone details"
-
     custom_widget_dateexpected = DateWidget
+    next_url = None
 
     @property
     def cancel_url(self):
@@ -582,6 +582,7 @@ class MilestoneDeleteView(LaunchpadFormView, RegistryDeleteViewMixin):
 
     schema = IMilestone
     field_names = []
+    next_url = None
 
     @property
     def cancel_url(self):
@@ -644,6 +645,7 @@ class MilestoneTagView(
     """A View for listing bugtasks and specification for milestone tags."""
 
     schema = ISearchMilestoneTagsForm
+    next_url = None
 
     def __init__(self, context, request):
         """See `LaunchpadView`.
diff --git a/lib/lp/registry/browser/ociproject.py b/lib/lp/registry/browser/ociproject.py
index e5d00c7..fe0817e 100644
--- a/lib/lp/registry/browser/ociproject.py
+++ b/lib/lp/registry/browser/ociproject.py
@@ -111,6 +111,7 @@ class OCIProjectAddView(LaunchpadFormView):
 
     schema = IOCIProjectName
     field_names = ["name"]
+    next_url = None
 
     def initialize(self):
         if not getFeatureFlag(
diff --git a/lib/lp/registry/browser/peoplemerge.py b/lib/lp/registry/browser/peoplemerge.py
index 63f7bd9..1a2d1f3 100644
--- a/lib/lp/registry/browser/peoplemerge.py
+++ b/lib/lp/registry/browser/peoplemerge.py
@@ -112,6 +112,7 @@ class ValidatingMergeView(LaunchpadFormView):
 class AdminMergeBaseView(ValidatingMergeView):
     """Base view for the pages where admins can merge people/teams."""
 
+    next_url = None
     page_title = "Merge Launchpad accounts"
     # Both subclasses share the same template so we need to define these
     # variables (which are used in the template) here rather than on
@@ -476,6 +477,7 @@ class RequestPeopleMergeView(ValidatingMergeView):
     label = "Merge Launchpad accounts"
     page_title = label
     schema = IRequestPeopleMerge
+    next_url = None
 
     @property
     def cancel_url(self):
diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
index 8145cc0..ed88065 100644
--- a/lib/lp/registry/browser/person.py
+++ b/lib/lp/registry/browser/person.py
@@ -1087,6 +1087,7 @@ class PersonDeactivateAccountView(LaunchpadFormView):
     custom_widget_comment = CustomWidgetFactory(
         TextAreaWidget, height=5, width=60
     )
+    next_url = None
 
     def validate(self, data):
         """See `LaunchpadFormView`."""
@@ -2888,6 +2889,7 @@ class PersonEditEmailsView(LaunchpadFormView):
     """
 
     schema = IEmailAddress
+    next_url = None
 
     custom_widget_VALIDATED_SELECTED = CustomWidgetFactory(
         LaunchpadRadioWidget, orientation="vertical"
@@ -3275,6 +3277,7 @@ class PersonEditMailingListsView(LaunchpadFormView):
     """A view for editing a person's mailing list subscriptions."""
 
     schema = IEmailAddress
+    next_url = None
 
     custom_widget_mailing_list_auto_subscribe_policy = (
         LaunchpadRadioWidgetWithDescription
@@ -4004,6 +4007,7 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
         )
 
     schema = Interface
+    next_url = None
 
     def initialize(self):
         if not user_can_edit_credentials_for_owner(self.context, self.user):
@@ -4679,6 +4683,7 @@ class EmailToPersonView(LaunchpadFormView):
     schema = IEmailToPerson
     field_names = ["subject", "message"]
     custom_widget_subject = CustomWidgetFactory(TextWidget, displayWidth=60)
+    next_url = None
 
     def initialize(self):
         """See `ILaunchpadFormView`."""
diff --git a/lib/lp/registry/browser/poll.py b/lib/lp/registry/browser/poll.py
index b182a91..f13348f 100644
--- a/lib/lp/registry/browser/poll.py
+++ b/lib/lp/registry/browser/poll.py
@@ -419,6 +419,7 @@ class PollAddView(LaunchpadFormView):
         "dateopens",
         "datecloses",
     ]
+    next_url = None
     invariant_context = None
 
     page_title = "New poll"
@@ -459,6 +460,7 @@ class PollEditView(LaunchpadEditFormView):
         "dateopens",
         "datecloses",
     ]
+    next_url = None
 
     @property
     def cancel_url(self):
@@ -479,6 +481,7 @@ class PollOptionEditView(LaunchpadEditFormView):
     page_title = "Edit option"
     field_names = ["name", "title"]
     custom_widget_title = CustomWidgetFactory(TextWidget, displayWidth=30)
+    next_url = None
 
     @property
     def cancel_url(self):
@@ -499,6 +502,7 @@ class PollOptionAddView(LaunchpadFormView):
     page_title = "New option"
     field_names = ["name", "title"]
     custom_widget_title = CustomWidgetFactory(TextWidget, displayWidth=30)
+    next_url = None
 
     @property
     def cancel_url(self):
diff --git a/lib/lp/registry/browser/product.py b/lib/lp/registry/browser/product.py
index f90e27a..5f90da9 100644
--- a/lib/lp/registry/browser/product.py
+++ b/lib/lp/registry/browser/product.py
@@ -2601,6 +2601,7 @@ class ProjectAddStepTwo(StepView, ProductLicenseMixin, ReturnToReferrerMixin):
         "owner",
     ]
     schema = IProduct
+    next_url = None
     step_name = "projectaddstep2"
     template = ViewPageTemplateFile("../templates/product-new.pt")
     page_title = ProjectAddStepOne.page_title
diff --git a/lib/lp/registry/browser/productrelease.py b/lib/lp/registry/browser/productrelease.py
index 3bf5b32..99ae3db 100644
--- a/lib/lp/registry/browser/productrelease.py
+++ b/lib/lp/registry/browser/productrelease.py
@@ -99,7 +99,6 @@ class ProductReleaseAddViewBase(LaunchpadFormView):
     """
 
     schema = IProductRelease
-
     custom_widget_datereleased = DateTimeWidget
     custom_widget_release_notes = CustomWidgetFactory(
         TextAreaWidget, height=7, width=62
@@ -107,6 +106,7 @@ class ProductReleaseAddViewBase(LaunchpadFormView):
     custom_widget_changelog = CustomWidgetFactory(
         TextAreaWidget, height=7, width=62
     )
+    next_url = None
 
     def _prependKeepMilestoneActiveField(self):
         keep_milestone_active_checkbox = FormFields(
@@ -235,7 +235,6 @@ class ProductReleaseEditView(LaunchpadEditFormView):
         "release_notes",
         "changelog",
     ]
-
     custom_widget_datereleased = DateTimeWidget
     custom_widget_release_notes = CustomWidgetFactory(
         TextAreaWidget, height=7, width=62
@@ -243,6 +242,7 @@ class ProductReleaseEditView(LaunchpadEditFormView):
     custom_widget_changelog = CustomWidgetFactory(
         TextAreaWidget, height=7, width=62
     )
+    next_url = None
 
     @property
     def label(self):
@@ -279,10 +279,10 @@ class ProductReleaseAddDownloadFileView(LaunchpadFormView):
     """A view for adding a file to an `IProductRelease`."""
 
     schema = IProductReleaseFileAddForm
-
     custom_widget_description = CustomWidgetFactory(
         TextWidget, displayWidth=60
     )
+    next_url = None
 
     @property
     def label(self):
@@ -365,6 +365,7 @@ class ProductReleaseDeleteView(LaunchpadFormView, RegistryDeleteViewMixin):
 
     schema = IProductRelease
     field_names = []
+    next_url = None
 
     @property
     def label(self):
diff --git a/lib/lp/registry/browser/productseries.py b/lib/lp/registry/browser/productseries.py
index 9b05317..807d373 100644
--- a/lib/lp/registry/browser/productseries.py
+++ b/lib/lp/registry/browser/productseries.py
@@ -712,6 +712,7 @@ class ProductSeriesDeleteView(RegistryDeleteViewMixin, LaunchpadEditFormView):
 
     schema = IProductSeries
     field_names = []
+    next_url = None
 
     @property
     def label(self):
@@ -846,6 +847,7 @@ class ProductSeriesReviewView(LaunchpadEditFormView):
     schema = IProductSeries
     field_names = ["product", "name"]
     custom_widget_name = CustomWidgetFactory(TextWidget, displayWidth=20)
+    next_url = None
 
     @property
     def label(self):
diff --git a/lib/lp/registry/browser/sourcepackage.py b/lib/lp/registry/browser/sourcepackage.py
index 461a963..9fb232e 100644
--- a/lib/lp/registry/browser/sourcepackage.py
+++ b/lib/lp/registry/browser/sourcepackage.py
@@ -595,6 +595,7 @@ class SourcePackageAssociationPortletView(LaunchpadFormView):
     custom_widget_upstream = CustomWidgetFactory(
         LaunchpadRadioWidget, orientation="vertical"
     )
+    next_url = None
     product_suggestions = None
     initial_focus_widget = None
     max_suggestions = 9
diff --git a/lib/lp/registry/browser/team.py b/lib/lp/registry/browser/team.py
index 8de222c..386c325 100644
--- a/lib/lp/registry/browser/team.py
+++ b/lib/lp/registry/browser/team.py
@@ -622,6 +622,7 @@ class TeamMailingListConfigurationView(MailingListTeamBaseView):
         TextAreaWidget, width=72, height=10
     )
     page_title = label
+    next_url = None
 
     def __init__(self, context, request):
         """Set feedback messages for users who want to edit the mailing list.
@@ -958,6 +959,7 @@ class TeamMailingListModerationView(MailingListTeamBaseView):
     """A view for moderating the held messages of a mailing list."""
 
     schema = Interface
+    next_url = None
     label = "Mailing list moderation"
 
     def __init__(self, context, request):
@@ -1070,7 +1072,6 @@ class TeamAddView(TeamFormMixin, HasRenewalPolicyMixin, LaunchpadFormView):
     invariant_context = None
     page_title = "Register a new team in Launchpad"
     label = page_title
-
     custom_widget_teamowner = HiddenUserWidget
     custom_widget_renewal_policy = CustomWidgetFactory(
         LaunchpadRadioWidget, orientation="vertical"
@@ -1081,6 +1082,7 @@ class TeamAddView(TeamFormMixin, HasRenewalPolicyMixin, LaunchpadFormView):
     custom_widget_defaultrenewalperiod = CustomWidgetFactory(
         IntWidget, widget_class="field subordinate"
     )
+    next_url = None
 
     def setUpFields(self):
         """See `LaunchpadViewForm`.
@@ -1165,6 +1167,7 @@ class SimpleTeamAddView(TeamAddView):
 
 class ProposedTeamMembersEditView(LaunchpadFormView):
     schema = Interface
+    next_url = None
     label = "Proposed team members"
 
     @action("Save changes", name="save")
@@ -2097,6 +2100,7 @@ class TeamAddMyTeamsView(LaunchpadFormView):
 
     page_title = "Propose/add one of your teams to another one"
     custom_widget_teams = LabeledMultiCheckBoxWidget
+    next_url = None
 
     def initialize(self):
         context = self.context
diff --git a/lib/lp/services/oauth/browser/__init__.py b/lib/lp/services/oauth/browser/__init__.py
index 2a67f72..3b30e33 100644
--- a/lib/lp/services/oauth/browser/__init__.py
+++ b/lib/lp/services/oauth/browser/__init__.py
@@ -182,6 +182,7 @@ class OAuthAuthorizeTokenView(LaunchpadFormView, JSONTokenMixin):
     page_title = label
     schema = IOAuthRequestToken
     field_names = []
+    next_url = None
     token = None
 
     @property
diff --git a/lib/lp/services/webhooks/browser.py b/lib/lp/services/webhooks/browser.py
index fefbf51..73b9f35 100644
--- a/lib/lp/services/webhooks/browser.py
+++ b/lib/lp/services/webhooks/browser.py
@@ -112,6 +112,7 @@ class WebhookAddView(LaunchpadFormView):
 
     schema = WebhookEditSchema
     custom_widget_event_types = LabeledMultiCheckBoxWidget
+    next_url = None
 
     @property
     def inside_breadcrumb(self):
@@ -184,6 +185,7 @@ class WebhookView(LaunchpadEditFormView):
 class WebhookDeleteView(LaunchpadFormView):
 
     schema = Interface
+    next_url = None
 
     page_title = label = "Delete webhook"
 
diff --git a/lib/lp/snappy/browser/snap.py b/lib/lp/snappy/browser/snap.py
index 80819c0..9917601 100644
--- a/lib/lp/snappy/browser/snap.py
+++ b/lib/lp/snappy/browser/snap.py
@@ -399,6 +399,8 @@ def builds_and_requests_for_snap(snap):
 class SnapRequestBuildsView(LaunchpadFormView):
     """A view for requesting builds of a snap package."""
 
+    next_url = None
+
     @property
     def label(self):
         return "Request builds for %s" % self.context.name
@@ -527,6 +529,8 @@ def log_oops(error, request):
 
 
 class SnapAuthorizeMixin:
+    next_url = None
+
     def requestAuthorization(self, snap):
         try:
             self.next_url = SnapAuthorizeView.requestAuthorization(
@@ -552,6 +556,7 @@ class SnapAddView(
     page_title = label = "Create a new snap package"
 
     schema = ISnapEditSchema
+    next_url = None
 
     custom_widget_vcs = LaunchpadRadioWidget
     custom_widget_git_ref = CustomWidgetFactory(
@@ -770,6 +775,7 @@ class BaseSnapEditView(
 ):
 
     schema = ISnapEditSchema
+    next_url = None
 
     @property
     def cancel_url(self):
@@ -1152,6 +1158,7 @@ class SnapDeleteView(BaseSnapEditView):
     page_title = "Delete"
 
     field_names = []
+    next_url = None
 
     @action("Delete snap package", name="delete")
     def delete_action(self, action, data):
diff --git a/lib/lp/soyuz/browser/archive.py b/lib/lp/soyuz/browser/archive.py
index 6bbca05..943ef77 100644
--- a/lib/lp/soyuz/browser/archive.py
+++ b/lib/lp/soyuz/browser/archive.py
@@ -1179,6 +1179,7 @@ class ArchiveSourceSelectionFormView(ArchiveSourcePackageListViewBase):
     """Base class to implement a source selection widget for PPAs."""
 
     custom_widget_selected_sources = LabeledMultiCheckBoxWidget
+    next_url = None
 
     selectable_sources = True
 
@@ -1745,7 +1746,6 @@ class ArchiveEditDependenciesView(ArchiveViewBase, LaunchpadFormView):
     """Archive dependencies view class."""
 
     schema = IArchiveEditDependenciesForm
-
     custom_widget_selected_dependencies = CustomWidgetFactory(
         PlainMultiCheckBoxWidget,
         cssClass="line-through-when-checked ppa-dependencies",
@@ -1756,6 +1756,8 @@ class ArchiveEditDependenciesView(ArchiveViewBase, LaunchpadFormView):
     custom_widget_primary_components = CustomWidgetFactory(
         LaunchpadRadioWidget, cssClass="highlight-selected"
     )
+    next_url = None
+    cancel_url = None
 
     label = "Edit PPA dependencies"
     page_title = label
@@ -2117,6 +2119,7 @@ class ArchiveActivateView(LaunchpadFormView):
     field_names = ("name", "displayname", "description")
     custom_widget_description = CustomWidgetFactory(TextAreaWidget, height=3)
     custom_widget_name = CustomWidgetFactory(PPANameWidget, label="URL")
+    next_url = None
     label = "Activate a Personal Package Archive"
     page_title = "Activate PPA"
 
@@ -2234,6 +2237,7 @@ class BaseArchiveEditView(LaunchpadEditFormView, ArchiveViewBase):
 
     schema = IArchive
     field_names = []
+    next_url = None
 
     @action(_("Save"), name="save", validator="validate_save")
     def save_action(self, action, data):
diff --git a/lib/lp/soyuz/browser/archivesubscription.py b/lib/lp/soyuz/browser/archivesubscription.py
index de0bb9a..425b07b 100644
--- a/lib/lp/soyuz/browser/archivesubscription.py
+++ b/lib/lp/soyuz/browser/archivesubscription.py
@@ -132,6 +132,7 @@ class ArchiveSubscribersView(LaunchpadFormView):
     custom_widget_subscriber = CustomWidgetFactory(
         PersonPickerWidget, header="Select the subscriber"
     )
+    next_url = None
 
     @property
     def label(self):
diff --git a/lib/lp/soyuz/browser/build.py b/lib/lp/soyuz/browser/build.py
index 27d3b5b..81af820 100644
--- a/lib/lp/soyuz/browser/build.py
+++ b/lib/lp/soyuz/browser/build.py
@@ -342,6 +342,8 @@ class BuildRescoringView(LaunchpadFormView):
     """View class for build rescoring."""
 
     schema = IBuildRescoreForm
+    next_url = None
+    cancel_url = None
 
     @property
     def label(self):
diff --git a/lib/lp/soyuz/browser/distroarchseries.py b/lib/lp/soyuz/browser/distroarchseries.py
index d3dae16..8415df0 100644
--- a/lib/lp/soyuz/browser/distroarchseries.py
+++ b/lib/lp/soyuz/browser/distroarchseries.py
@@ -100,6 +100,7 @@ class DistroArchSeriesAddView(LaunchpadFormView):
 
     schema = DistroArchSeriesAddSchema
     field_names = ["architecturetag", "processor", "official"]
+    next_url = None
 
     @property
     def label(self):
diff --git a/lib/lp/soyuz/browser/livefs.py b/lib/lp/soyuz/browser/livefs.py
index 0fea4d9..b5c1cfc 100644
--- a/lib/lp/soyuz/browser/livefs.py
+++ b/lib/lp/soyuz/browser/livefs.py
@@ -227,6 +227,7 @@ class LiveFSAddView(LiveFSMetadataValidatorMixin, LaunchpadFormView):
     schema = ILiveFSEditSchema
     field_names = ["owner", "name", "distro_series", "metadata"]
     custom_widget_distro_series = LaunchpadRadioWidget
+    next_url = None
 
     def initialize(self):
         """See `LaunchpadView`."""
@@ -281,6 +282,7 @@ class LiveFSAddView(LiveFSMetadataValidatorMixin, LaunchpadFormView):
 class BaseLiveFSEditView(LaunchpadEditFormView):
 
     schema = ILiveFSEditSchema
+    next_url = None
 
     @property
     def cancel_url(self):
@@ -383,6 +385,7 @@ class LiveFSDeleteView(BaseLiveFSEditView):
     label = title
 
     field_names = []
+    next_url = None
 
     @property
     def has_builds(self):
diff --git a/lib/lp/translations/browser/distroseries.py b/lib/lp/translations/browser/distroseries.py
index 563d9b0..c9e12a3 100644
--- a/lib/lp/translations/browser/distroseries.py
+++ b/lib/lp/translations/browser/distroseries.py
@@ -58,6 +58,7 @@ class DistroSeriesLanguagePackView(LaunchpadEditFormView):
     """Browser view to manage used language packs."""
 
     schema = IDistroSeries
+    next_url = None
     label = "Language packs"
     page_title = "Language packs"
 
diff --git a/lib/lp/translations/browser/hastranslationimports.py b/lib/lp/translations/browser/hastranslationimports.py
index c1f7623..7d14f94 100644
--- a/lib/lp/translations/browser/hastranslationimports.py
+++ b/lib/lp/translations/browser/hastranslationimports.py
@@ -46,7 +46,6 @@ class HasTranslationImportsView(LaunchpadFormView):
 
     schema = IHasTranslationImports
     field_names = []
-
     custom_widget_filter_target = CustomWidgetFactory(
         DropdownWidget, cssClass="inlined-widget"
     )
@@ -59,6 +58,7 @@ class HasTranslationImportsView(LaunchpadFormView):
     custom_widget_status = CustomWidgetFactory(
         DropdownWidget, cssClass="inlined-widget"
     )
+    next_url = None
 
     translation_import_queue_macros = ViewPageTemplateFile(
         "../templates/translation-import-queue-macros.pt"
diff --git a/lib/lp/translations/browser/person.py b/lib/lp/translations/browser/person.py
index 12e0c34..f7a5037 100644
--- a/lib/lp/translations/browser/person.py
+++ b/lib/lp/translations/browser/person.py
@@ -454,6 +454,7 @@ class PersonTranslationRelicensingView(LaunchpadFormView):
         LaunchpadRadioWidget, orientation="vertical"
     )
     custom_widget_back_to = CustomWidgetFactory(TextWidget, visible=False)
+    next_url = None
 
     page_title = "Licensing"
 
diff --git a/lib/lp/translations/browser/translationgroup.py b/lib/lp/translations/browser/translationgroup.py
index 2aa2d50..4135fdf 100644
--- a/lib/lp/translations/browser/translationgroup.py
+++ b/lib/lp/translations/browser/translationgroup.py
@@ -190,6 +190,7 @@ class TranslationGroupAddView(LaunchpadFormView):
 
     schema = ITranslationGroup
     field_names = ["name", "title", "summary", "translation_guide_url"]
+    next_url = None
     label = "Create a new translation group"
     page_title = label