{"action":"opened","number":1867,"pull_request":{"url":"https://api.github.com/repos/torchbox/wagtail/pulls/1867","id":48820927,"html_url":"https://github.com/torchbox/wagtail/pull/1867","diff_url":"https://github.com/torchbox/wagtail/pull/1867.diff","patch_url":"https://github.com/torchbox/wagtail/pull/1867.patch","issue_url":"https://api.github.com/repos/torchbox/wagtail/issues/1867","number":1867,"state":"open","locked":false,"title":"Allow defining a custom form for Page validation","user":{"login":"timheap","id":132147,"avatar_url":"https://avatars.githubusercontent.com/u/132147?v=3","gravatar_id":"","url":"https://api.github.com/users/timheap","html_url":"https://github.com/timheap","followers_url":"https://api.github.com/users/timheap/followers","following_url":"https://api.github.com/users/timheap/following{/other_user}","gists_url":"https://api.github.com/users/timheap/gists{/gist_id}","starred_url":"https://api.github.com/users/timheap/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timheap/subscriptions","organizations_url":"https://api.github.com/users/timheap/orgs","repos_url":"https://api.github.com/users/timheap/repos","events_url":"https://api.github.com/users/timheap/events{/privacy}","received_events_url":"https://api.github.com/users/timheap/received_events","type":"User","site_admin":false},"body":"Developers can create their own form class, with custom validation logic, by subclassing `wagtail.wagtailadmin.forms.WagtailAdminPageForm` and setting the `MyPage.base_form_class` attribute:\r\n\r\n```python\r\nfrom wagtail.wagtailadmin.forms import WagtailAdminPageForm\r\nfrom wagtail.wagtailcore.models import Page\r\n\r\nclass EventPageForm(WagtailAdminPageForm):\r\n    def clean(self):\r\n        cleaned_data = super(EventPageForm, self).clean()\r\n\r\n        start_date = cleaned_data.get('start_date')\r\n        end_date = cleaned_data.get('end_date')\r\n        if start_date and end_date:\r\n            if start_date < end_date:\r\n                msg = 'Start date must be before end date'\r\n                self.add_error('start_date', msg)\r\n                self.add_error('start_date', msg)\r\n            elif start_date == end_date\r\n                cleaned_data['end_date'] = None\r\n\r\n        return cleaned_data\r\n\r\nclass EventPage(Page):\r\n    start_date = models.DateField()\r\n    end_date = models.DateField()\r\n\r\n    content_panels = Page.content_panels + [\r\n        FieldPanel('start_date'),\r\n        FieldPanel('end_date'),\r\n    ]\r\n    base_form_class = EventPageForm\r\n```\r\n\r\nThe `Page` form validation logic has been moved to `WagtailAdminPageForm.clean()`, out of the\r\n`wagtail.wagtailadmin.views.pages.validate_page_data()` function. There may be an issue for developers who have made their own `Page.edit_handler`, which will not set the correct form class for validating pages.\r\n\r\nConstructing a `TabbedInterface` for `Page`s is now done by the `Page.get_edit_handler()` function, which behaves the same as the code embedded in `wagtail.wagtailadmin.views.pages.get_page_edit_handler()`. Because it is a function on `Page`, developers can easily override it site wide using a custom `Page` base class.","created_at":"2015-10-27T02:08:13Z","updated_at":"2015-10-27T02:08:13Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bbb2b41de25e151c33771d13d7f4cf47a35e6f84","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/torchbox/wagtail/pulls/1867/commits","review_comments_url":"https://api.github.com/repos/torchbox/wagtail/pulls/1867/comments","review_comment_url":"https://api.github.com/repos/torchbox/wagtail/pulls/comments{/number}","comments_url":"https://api.github.com/repos/torchbox/wagtail/issues/1867/comments","statuses_url":"https://api.github.com/repos/torchbox/wagtail/statuses/d2773d393329be1accc809d571b370ab08b40c30","head":{"label":"takeflight:refactor/page-edit-handler","ref":"refactor/page-edit-handler","sha":"d2773d393329be1accc809d571b370ab08b40c30","user":{"login":"takeflight","id":7663899,"avatar_url":"https://avatars.githubusercontent.com/u/7663899?v=3","gravatar_id":"","url":"https://api.github.com/users/takeflight","html_url":"https://github.com/takeflight","followers_url":"https://api.github.com/users/takeflight/followers","following_url":"https://api.github.com/users/takeflight/following{/other_user}","gists_url":"https://api.github.com/users/takeflight/gists{/gist_id}","starred_url":"https://api.github.com/users/takeflight/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/takeflight/subscriptions","organizations_url":"https://api.github.com/users/takeflight/orgs","repos_url":"https://api.github.com/users/takeflight/repos","events_url":"https://api.github.com/users/takeflight/events{/privacy}","received_events_url":"https://api.github.com/users/takeflight/received_events","type":"Organization","site_admin":false},"repo":{"id":20045486,"name":"wagtail","full_name":"takeflight/wagtail","owner":{"login":"takeflight","id":7663899,"avatar_url":"https://avatars.githubusercontent.com/u/7663899?v=3","gravatar_id":"","url":"https://api.github.com/users/takeflight","html_url":"https://github.com/takeflight","followers_url":"https://api.github.com/users/takeflight/followers","following_url":"https://api.github.com/users/takeflight/following{/other_user}","gists_url":"https://api.github.com/users/takeflight/gists{/gist_id}","starred_url":"https://api.github.com/users/takeflight/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/takeflight/subscriptions","organizations_url":"https://api.github.com/users/takeflight/orgs","repos_url":"https://api.github.com/users/takeflight/repos","events_url":"https://api.github.com/users/takeflight/events{/privacy}","received_events_url":"https://api.github.com/users/takeflight/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/takeflight/wagtail","description":"A new Django content management system","fork":true,"url":"https://api.github.com/repos/takeflight/wagtail","forks_url":"https://api.github.com/repos/takeflight/wagtail/forks","keys_url":"https://api.github.com/repos/takeflight/wagtail/keys{/key_id}","collaborators_url":"https://api.github.com/repos/takeflight/wagtail/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/takeflight/wagtail/teams","hooks_url":"https://api.github.com/repos/takeflight/wagtail/hooks","issue_events_url":"https://api.github.com/repos/takeflight/wagtail/issues/events{/number}","events_url":"https://api.github.com/repos/takeflight/wagtail/events","assignees_url":"https://api.github.com/repos/takeflight/wagtail/assignees{/user}","branches_url":"https://api.github.com/repos/takeflight/wagtail/branches{/branch}","tags_url":"https://api.github.com/repos/takeflight/wagtail/tags","blobs_url":"https://api.github.com/repos/takeflight/wagtail/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/takeflight/wagtail/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/takeflight/wagtail/git/refs{/sha}","trees_url":"https://api.github.com/repos/takeflight/wagtail/git/trees{/sha}","statuses_url":"https://api.github.com/repos/takeflight/wagtail/statuses/{sha}","languages_url":"https://api.github.com/repos/takeflight/wagtail/languages","stargazers_url":"https://api.github.com/repos/takeflight/wagtail/stargazers","contributors_url":"https://api.github.com/repos/takeflight/wagtail/contributors","subscribers_url":"https://api.github.com/repos/takeflight/wagtail/subscribers","subscription_url":"https://api.github.com/repos/takeflight/wagtail/subscription","commits_url":"https://api.github.com/repos/takeflight/wagtail/commits{/sha}","git_commits_url":"https://api.github.com/repos/takeflight/wagtail/git/commits{/sha}","comments_url":"https://api.github.com/repos/takeflight/wagtail/comments{/number}","issue_comment_url":"https://api.github.com/repos/takeflight/wagtail/issues/comments{/number}","contents_url":"https://api.github.com/repos/takeflight/wagtail/contents/{+path}","compare_url":"https://api.github.com/repos/takeflight/wagtail/compare/{base}...{head}","merges_url":"https://api.github.com/repos/takeflight/wagtail/merges","archive_url":"https://api.github.com/repos/takeflight/wagtail/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/takeflight/wagtail/downloads","issues_url":"https://api.github.com/repos/takeflight/wagtail/issues{/number}","pulls_url":"https://api.github.com/repos/takeflight/wagtail/pulls{/number}","milestones_url":"https://api.github.com/repos/takeflight/wagtail/milestones{/number}","notifications_url":"https://api.github.com/repos/takeflight/wagtail/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/takeflight/wagtail/labels{/name}","releases_url":"https://api.github.com/repos/takeflight/wagtail/releases{/id}","created_at":"2014-05-22T02:47:56Z","updated_at":"2015-04-23T01:24:37Z","pushed_at":"2015-10-27T02:07:41Z","git_url":"git://github.com/takeflight/wagtail.git","ssh_url":"git@github.com:takeflight/wagtail.git","clone_url":"https://github.com/takeflight/wagtail.git","svn_url":"https://github.com/takeflight/wagtail","homepage":"wagtail.io","size":20846,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"torchbox:master","ref":"master","sha":"13c8e4da583b1178974c48d822fbaa411de1f755","user":{"login":"torchbox","id":84015,"avatar_url":"https://avatars.githubusercontent.com/u/84015?v=3","gravatar_id":"","url":"https://api.github.com/users/torchbox","html_url":"https://github.com/torchbox","followers_url":"https://api.github.com/users/torchbox/followers","following_url":"https://api.github.com/users/torchbox/following{/other_user}","gists_url":"https://api.github.com/users/torchbox/gists{/gist_id}","starred_url":"https://api.github.com/users/torchbox/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/torchbox/subscriptions","organizations_url":"https://api.github.com/users/torchbox/orgs","repos_url":"https://api.github.com/users/torchbox/repos","events_url":"https://api.github.com/users/torchbox/events{/privacy}","received_events_url":"https://api.github.com/users/torchbox/received_events","type":"Organization","site_admin":false},"repo":{"id":16479108,"name":"wagtail","full_name":"torchbox/wagtail","owner":{"login":"torchbox","id":84015,"avatar_url":"https://avatars.githubusercontent.com/u/84015?v=3","gravatar_id":"","url":"https://api.github.com/users/torchbox","html_url":"https://github.com/torchbox","followers_url":"https://api.github.com/users/torchbox/followers","following_url":"https://api.github.com/users/torchbox/following{/other_user}","gists_url":"https://api.github.com/users/torchbox/gists{/gist_id}","starred_url":"https://api.github.com/users/torchbox/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/torchbox/subscriptions","organizations_url":"https://api.github.com/users/torchbox/orgs","repos_url":"https://api.github.com/users/torchbox/repos","events_url":"https://api.github.com/users/torchbox/events{/privacy}","received_events_url":"https://api.github.com/users/torchbox/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/torchbox/wagtail","description":"A Django content management system focused on flexibility and user experience","fork":false,"url":"https://api.github.com/repos/torchbox/wagtail","forks_url":"https://api.github.com/repos/torchbox/wagtail/forks","keys_url":"https://api.github.com/repos/torchbox/wagtail/keys{/key_id}","collaborators_url":"https://api.github.com/repos/torchbox/wagtail/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/torchbox/wagtail/teams","hooks_url":"https://api.github.com/repos/torchbox/wagtail/hooks","issue_events_url":"https://api.github.com/repos/torchbox/wagtail/issues/events{/number}","events_url":"https://api.github.com/repos/torchbox/wagtail/events","assignees_url":"https://api.github.com/repos/torchbox/wagtail/assignees{/user}","branches_url":"https://api.github.com/repos/torchbox/wagtail/branches{/branch}","tags_url":"https://api.github.com/repos/torchbox/wagtail/tags","blobs_url":"https://api.github.com/repos/torchbox/wagtail/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/torchbox/wagtail/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/torchbox/wagtail/git/refs{/sha}","trees_url":"https://api.github.com/repos/torchbox/wagtail/git/trees{/sha}","statuses_url":"https://api.github.com/repos/torchbox/wagtail/statuses/{sha}","languages_url":"https://api.github.com/repos/torchbox/wagtail/languages","stargazers_url":"https://api.github.com/repos/torchbox/wagtail/stargazers","contributors_url":"https://api.github.com/repos/torchbox/wagtail/contributors","subscribers_url":"https://api.github.com/repos/torchbox/wagtail/subscribers","subscription_url":"https://api.github.com/repos/torchbox/wagtail/subscription","commits_url":"https://api.github.com/repos/torchbox/wagtail/commits{/sha}","git_commits_url":"https://api.github.com/repos/torchbox/wagtail/git/commits{/sha}","comments_url":"https://api.github.com/repos/torchbox/wagtail/comments{/number}","issue_comment_url":"https://api.github.com/repos/torchbox/wagtail/issues/comments{/number}","contents_url":"https://api.github.com/repos/torchbox/wagtail/contents/{+path}","compare_url":"https://api.github.com/repos/torchbox/wagtail/compare/{base}...{head}","merges_url":"https://api.github.com/repos/torchbox/wagtail/merges","archive_url":"https://api.github.com/repos/torchbox/wagtail/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/torchbox/wagtail/downloads","issues_url":"https://api.github.com/repos/torchbox/wagtail/issues{/number}","pulls_url":"https://api.github.com/repos/torchbox/wagtail/pulls{/number}","milestones_url":"https://api.github.com/repos/torchbox/wagtail/milestones{/number}","notifications_url":"https://api.github.com/repos/torchbox/wagtail/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/torchbox/wagtail/labels{/name}","releases_url":"https://api.github.com/repos/torchbox/wagtail/releases{/id}","created_at":"2014-02-03T12:41:59Z","updated_at":"2015-10-27T01:23:34Z","pushed_at":"2015-10-27T02:08:14Z","git_url":"git://github.com/torchbox/wagtail.git","ssh_url":"git@github.com:torchbox/wagtail.git","clone_url":"https://github.com/torchbox/wagtail.git","svn_url":"https://github.com/torchbox/wagtail","homepage":"wagtail.io","size":40403,"stargazers_count":3031,"watchers_count":3031,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":477,"mirror_url":null,"open_issues_count":323,"forks":477,"open_issues":323,"watchers":3031,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/torchbox/wagtail/pulls/1867"},"html":{"href":"https://github.com/torchbox/wagtail/pull/1867"},"issue":{"href":"https://api.github.com/repos/torchbox/wagtail/issues/1867"},"comments":{"href":"https://api.github.com/repos/torchbox/wagtail/issues/1867/comments"},"review_comments":{"href":"https://api.github.com/repos/torchbox/wagtail/pulls/1867/comments"},"review_comment":{"href":"https://api.github.com/repos/torchbox/wagtail/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/torchbox/wagtail/pulls/1867/commits"},"statuses":{"href":"https://api.github.com/repos/torchbox/wagtail/statuses/d2773d393329be1accc809d571b370ab08b40c30"}},"merged":false,"mergeable":true,"mergeable_state":"clean","merged_by":null,"comments":0,"review_comments":0,"commits":1,"additions":302,"deletions":191,"changed_files":8}}