{"action":"created","comment":{"url":"https://api.github.com/repos/django/django/pulls/comments/31968501","id":31968501,"diff_hunk":"@@ -0,0 +1,147 @@\n+from django.contrib.auth import models\n+from django.contrib.auth.mixins import (\n+    LoginRequiredMixin, PermissionRequiredMixin, UserPassesTestMixin,\n+)\n+from django.contrib.auth.models import AnonymousUser\n+from django.core.exceptions import PermissionDenied\n+from django.http import HttpResponse\n+from django.test import RequestFactory, TestCase\n+from django.views.generic import View\n+\n+from .test_views import AuthViewsTestCase\n+\n+\n+class EmptyResponseView(View):\n+    def get(self, request, *args, **kwargs):\n+        return HttpResponse()\n+\n+\n+class UserTestAlwaysTrueView(UserPassesTestMixin, EmptyResponseView):\n+    def test_func(self, user):\n+        return True\n+\n+\n+class UserTestAlwaysFalseView(UserPassesTestMixin, EmptyResponseView):\n+    def test_func(self, user):\n+        return False\n+\n+\n+class UserPassesTestTestCase(TestCase):\n+    @classmethod\n+    def setUpTestData(cls):\n+        cls.factory = RequestFactory()\n+\n+    def test_redirect(self, view=None, url='/accounts/login/?next=/rand'):\n+        if not view:\n+            view = UserTestAlwaysFalseView.as_view()\n+        request = self.factory.get('/rand')\n+        request.user = AnonymousUser()\n+        response = view(request)\n+        self.assertEqual(response.status_code, 302)\n+        self.assertEqual(response.url, url)\n+\n+    def test_custom_redirect_url(self):\n+        class AView(UserTestAlwaysFalseView):\n+            login_url = '/login/'\n+\n+        self.test_redirect(AView.as_view(), '/login/?next=/rand')\n+\n+    def test_custom_redirect_parameter(self):\n+        class AView(UserTestAlwaysFalseView):\n+            redirect_field_name = 'goto'\n+\n+        self.test_redirect(AView.as_view(), '/accounts/login/?goto=/rand')\n+\n+    def test_no_redirect_parameter(self):\n+        class AView(UserTestAlwaysFalseView):\n+            redirect_field_name = None\n+\n+        self.test_redirect(AView.as_view(), '/accounts/login/')\n+\n+    def test_raise_exception(self):\n+        class AView(UserTestAlwaysFalseView):\n+            raise_exception = True\n+\n+        request = self.factory.get('/rand')\n+        request.user = AnonymousUser()\n+        self.assertRaises(PermissionDenied, AView.as_view(), request)\n+\n+    def test_user_passes(self):\n+        view = UserTestAlwaysTrueView.as_view()\n+        request = self.factory.get('/rand')\n+        request.user = AnonymousUser()\n+        response = view(request)\n+        self.assertEqual(response.status_code, 200)\n+\n+\n+class LoginRequiredMixinTestCase(AuthViewsTestCase):\n+    @classmethod\n+    def setUpTestData(cls):\n+        cls.user = models.User.objects.create(username='joe', password='qwerty')\n+        cls.factory = RequestFactory()\n+\n+    def test_login_required(self):\n+        \"\"\"\n+        Check that login_required works on a simple view wrapped in a\n+        login_required decorator.\n+        \"\"\"\n+        class AView(LoginRequiredMixin, EmptyResponseView):\n+            pass\n+\n+        view = AView.as_view()\n+\n+        request = self.factory.get('/rand')\n+        request.user = AnonymousUser()\n+        response = view(request)\n+        self.assertEqual(response.status_code, 302)\n+        self.assertEqual('/accounts/login/?next=/rand', response.url)\n+        request = self.factory.get('/rand')\n+        request.user = self.user\n+        response = view(request)\n+        self.assertEqual(response.status_code, 200)\n+\n+\n+class PermissionsRequiredMixinTest(TestCase):\n+    @classmethod","path":"tests/auth_tests/test_mixins.py","position":105,"original_position":105,"commit_id":"8626d0cca1d23e8b4bfb0a71e43aee9b81d6d286","original_commit_id":"8626d0cca1d23e8b4bfb0a71e43aee9b81d6d286","user":{"login":"MarkusH","id":475613,"avatar_url":"https://avatars.githubusercontent.com/u/475613?v=3","gravatar_id":"","url":"https://api.github.com/users/MarkusH","html_url":"https://github.com/MarkusH","followers_url":"https://api.github.com/users/MarkusH/followers","following_url":"https://api.github.com/users/MarkusH/following{/other_user}","gists_url":"https://api.github.com/users/MarkusH/gists{/gist_id}","starred_url":"https://api.github.com/users/MarkusH/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MarkusH/subscriptions","organizations_url":"https://api.github.com/users/MarkusH/orgs","repos_url":"https://api.github.com/users/MarkusH/repos","events_url":"https://api.github.com/users/MarkusH/events{/privacy}","received_events_url":"https://api.github.com/users/MarkusH/received_events","type":"User","site_admin":false},"body":"As above.","created_at":"2015-06-08T22:55:04Z","updated_at":"2015-06-08T22:55:04Z","html_url":"https://github.com/django/django/pull/4749#discussion_r31968501","pull_request_url":"https://api.github.com/repos/django/django/pulls/4749","_links":{"self":{"href":"https://api.github.com/repos/django/django/pulls/comments/31968501"},"html":{"href":"https://github.com/django/django/pull/4749#discussion_r31968501"},"pull_request":{"href":"https://api.github.com/repos/django/django/pulls/4749"}}},"pull_request":{"url":"https://api.github.com/repos/django/django/pulls/4749","id":36918102,"html_url":"https://github.com/django/django/pull/4749","diff_url":"https://github.com/django/django/pull/4749.diff","patch_url":"https://github.com/django/django/pull/4749.patch","issue_url":"https://api.github.com/repos/django/django/issues/4749","number":4749,"state":"open","locked":false,"title":"Fixed #24914 -- Added basic permission mixins to contrib.auth","user":{"login":"raphaelm","id":64280,"avatar_url":"https://avatars.githubusercontent.com/u/64280?v=3","gravatar_id":"","url":"https://api.github.com/users/raphaelm","html_url":"https://github.com/raphaelm","followers_url":"https://api.github.com/users/raphaelm/followers","following_url":"https://api.github.com/users/raphaelm/following{/other_user}","gists_url":"https://api.github.com/users/raphaelm/gists{/gist_id}","starred_url":"https://api.github.com/users/raphaelm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raphaelm/subscriptions","organizations_url":"https://api.github.com/users/raphaelm/orgs","repos_url":"https://api.github.com/users/raphaelm/repos","events_url":"https://api.github.com/users/raphaelm/events{/privacy}","received_events_url":"https://api.github.com/users/raphaelm/received_events","type":"User","site_admin":false},"body":"https://code.djangoproject.com/ticket/24914\r\n\r\nAn initial version of implementation and tests is in the pull request, documentation is yet to be done. Comments on the code are very welcome.\r\n\r\nThis proposal provides all the and only the functionality of all three authentication decorators from contrib.auth.\r\n\r\nWhile it is slightly differently implemented, it tries to stay compatible with the widely-used 3rd-party package django-braces in terms of parameter and method names. django-braces does have more functionality than the decorators have, but implementing this functionality in django itself is outside the scope of this ticket.","created_at":"2015-06-04T09:46:49Z","updated_at":"2015-06-08T22:55:04Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c9f5cd35fd09c746cef1553882788230857eddc8","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/django/django/pulls/4749/commits","review_comments_url":"https://api.github.com/repos/django/django/pulls/4749/comments","review_comment_url":"https://api.github.com/repos/django/django/pulls/comments{/number}","comments_url":"https://api.github.com/repos/django/django/issues/4749/comments","statuses_url":"https://api.github.com/repos/django/django/statuses/8626d0cca1d23e8b4bfb0a71e43aee9b81d6d286","head":{"label":"raphaelm:ticket_24914","ref":"ticket_24914","sha":"8626d0cca1d23e8b4bfb0a71e43aee9b81d6d286","user":{"login":"raphaelm","id":64280,"avatar_url":"https://avatars.githubusercontent.com/u/64280?v=3","gravatar_id":"","url":"https://api.github.com/users/raphaelm","html_url":"https://github.com/raphaelm","followers_url":"https://api.github.com/users/raphaelm/followers","following_url":"https://api.github.com/users/raphaelm/following{/other_user}","gists_url":"https://api.github.com/users/raphaelm/gists{/gist_id}","starred_url":"https://api.github.com/users/raphaelm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raphaelm/subscriptions","organizations_url":"https://api.github.com/users/raphaelm/orgs","repos_url":"https://api.github.com/users/raphaelm/repos","events_url":"https://api.github.com/users/raphaelm/events{/privacy}","received_events_url":"https://api.github.com/users/raphaelm/received_events","type":"User","site_admin":false},"repo":{"id":24692186,"name":"django","full_name":"raphaelm/django","owner":{"login":"raphaelm","id":64280,"avatar_url":"https://avatars.githubusercontent.com/u/64280?v=3","gravatar_id":"","url":"https://api.github.com/users/raphaelm","html_url":"https://github.com/raphaelm","followers_url":"https://api.github.com/users/raphaelm/followers","following_url":"https://api.github.com/users/raphaelm/following{/other_user}","gists_url":"https://api.github.com/users/raphaelm/gists{/gist_id}","starred_url":"https://api.github.com/users/raphaelm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/raphaelm/subscriptions","organizations_url":"https://api.github.com/users/raphaelm/orgs","repos_url":"https://api.github.com/users/raphaelm/repos","events_url":"https://api.github.com/users/raphaelm/events{/privacy}","received_events_url":"https://api.github.com/users/raphaelm/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/raphaelm/django","description":"The Web framework for perfectionists with deadlines.","fork":true,"url":"https://api.github.com/repos/raphaelm/django","forks_url":"https://api.github.com/repos/raphaelm/django/forks","keys_url":"https://api.github.com/repos/raphaelm/django/keys{/key_id}","collaborators_url":"https://api.github.com/repos/raphaelm/django/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/raphaelm/django/teams","hooks_url":"https://api.github.com/repos/raphaelm/django/hooks","issue_events_url":"https://api.github.com/repos/raphaelm/django/issues/events{/number}","events_url":"https://api.github.com/repos/raphaelm/django/events","assignees_url":"https://api.github.com/repos/raphaelm/django/assignees{/user}","branches_url":"https://api.github.com/repos/raphaelm/django/branches{/branch}","tags_url":"https://api.github.com/repos/raphaelm/django/tags","blobs_url":"https://api.github.com/repos/raphaelm/django/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/raphaelm/django/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/raphaelm/django/git/refs{/sha}","trees_url":"https://api.github.com/repos/raphaelm/django/git/trees{/sha}","statuses_url":"https://api.github.com/repos/raphaelm/django/statuses/{sha}","languages_url":"https://api.github.com/repos/raphaelm/django/languages","stargazers_url":"https://api.github.com/repos/raphaelm/django/stargazers","contributors_url":"https://api.github.com/repos/raphaelm/django/contributors","subscribers_url":"https://api.github.com/repos/raphaelm/django/subscribers","subscription_url":"https://api.github.com/repos/raphaelm/django/subscription","commits_url":"https://api.github.com/repos/raphaelm/django/commits{/sha}","git_commits_url":"https://api.github.com/repos/raphaelm/django/git/commits{/sha}","comments_url":"https://api.github.com/repos/raphaelm/django/comments{/number}","issue_comment_url":"https://api.github.com/repos/raphaelm/django/issues/comments{/number}","contents_url":"https://api.github.com/repos/raphaelm/django/contents/{+path}","compare_url":"https://api.github.com/repos/raphaelm/django/compare/{base}...{head}","merges_url":"https://api.github.com/repos/raphaelm/django/merges","archive_url":"https://api.github.com/repos/raphaelm/django/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/raphaelm/django/downloads","issues_url":"https://api.github.com/repos/raphaelm/django/issues{/number}","pulls_url":"https://api.github.com/repos/raphaelm/django/pulls{/number}","milestones_url":"https://api.github.com/repos/raphaelm/django/milestones{/number}","notifications_url":"https://api.github.com/repos/raphaelm/django/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/raphaelm/django/labels{/name}","releases_url":"https://api.github.com/repos/raphaelm/django/releases{/id}","created_at":"2014-10-01T19:27:33Z","updated_at":"2015-06-04T08:17:39Z","pushed_at":"2015-06-08T22:46:30Z","git_url":"git://github.com/raphaelm/django.git","ssh_url":"git@github.com:raphaelm/django.git","clone_url":"https://github.com/raphaelm/django.git","svn_url":"https://github.com/raphaelm/django","homepage":"https://www.djangoproject.com/","size":135020,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"django:master","ref":"master","sha":"9f825b9e28547b1310526c9be9fca9e6dbcd57e7","user":{"login":"django","id":27804,"avatar_url":"https://avatars.githubusercontent.com/u/27804?v=3","gravatar_id":"","url":"https://api.github.com/users/django","html_url":"https://github.com/django","followers_url":"https://api.github.com/users/django/followers","following_url":"https://api.github.com/users/django/following{/other_user}","gists_url":"https://api.github.com/users/django/gists{/gist_id}","starred_url":"https://api.github.com/users/django/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/django/subscriptions","organizations_url":"https://api.github.com/users/django/orgs","repos_url":"https://api.github.com/users/django/repos","events_url":"https://api.github.com/users/django/events{/privacy}","received_events_url":"https://api.github.com/users/django/received_events","type":"Organization","site_admin":false},"repo":{"id":4164482,"name":"django","full_name":"django/django","owner":{"login":"django","id":27804,"avatar_url":"https://avatars.githubusercontent.com/u/27804?v=3","gravatar_id":"","url":"https://api.github.com/users/django","html_url":"https://github.com/django","followers_url":"https://api.github.com/users/django/followers","following_url":"https://api.github.com/users/django/following{/other_user}","gists_url":"https://api.github.com/users/django/gists{/gist_id}","starred_url":"https://api.github.com/users/django/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/django/subscriptions","organizations_url":"https://api.github.com/users/django/orgs","repos_url":"https://api.github.com/users/django/repos","events_url":"https://api.github.com/users/django/events{/privacy}","received_events_url":"https://api.github.com/users/django/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/django/django","description":"The Web framework for perfectionists with deadlines.","fork":false,"url":"https://api.github.com/repos/django/django","forks_url":"https://api.github.com/repos/django/django/forks","keys_url":"https://api.github.com/repos/django/django/keys{/key_id}","collaborators_url":"https://api.github.com/repos/django/django/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/django/django/teams","hooks_url":"https://api.github.com/repos/django/django/hooks","issue_events_url":"https://api.github.com/repos/django/django/issues/events{/number}","events_url":"https://api.github.com/repos/django/django/events","assignees_url":"https://api.github.com/repos/django/django/assignees{/user}","branches_url":"https://api.github.com/repos/django/django/branches{/branch}","tags_url":"https://api.github.com/repos/django/django/tags","blobs_url":"https://api.github.com/repos/django/django/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/django/django/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/django/django/git/refs{/sha}","trees_url":"https://api.github.com/repos/django/django/git/trees{/sha}","statuses_url":"https://api.github.com/repos/django/django/statuses/{sha}","languages_url":"https://api.github.com/repos/django/django/languages","stargazers_url":"https://api.github.com/repos/django/django/stargazers","contributors_url":"https://api.github.com/repos/django/django/contributors","subscribers_url":"https://api.github.com/repos/django/django/subscribers","subscription_url":"https://api.github.com/repos/django/django/subscription","commits_url":"https://api.github.com/repos/django/django/commits{/sha}","git_commits_url":"https://api.github.com/repos/django/django/git/commits{/sha}","comments_url":"https://api.github.com/repos/django/django/comments{/number}","issue_comment_url":"https://api.github.com/repos/django/django/issues/comments{/number}","contents_url":"https://api.github.com/repos/django/django/contents/{+path}","compare_url":"https://api.github.com/repos/django/django/compare/{base}...{head}","merges_url":"https://api.github.com/repos/django/django/merges","archive_url":"https://api.github.com/repos/django/django/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/django/django/downloads","issues_url":"https://api.github.com/repos/django/django/issues{/number}","pulls_url":"https://api.github.com/repos/django/django/pulls{/number}","milestones_url":"https://api.github.com/repos/django/django/milestones{/number}","notifications_url":"https://api.github.com/repos/django/django/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/django/django/labels{/name}","releases_url":"https://api.github.com/repos/django/django/releases{/id}","created_at":"2012-04-28T02:47:18Z","updated_at":"2015-06-08T22:30:26Z","pushed_at":"2015-06-08T22:46:35Z","git_url":"git://github.com/django/django.git","ssh_url":"git@github.com:django/django.git","clone_url":"https://github.com/django/django.git","svn_url":"https://github.com/django/django","homepage":"https://www.djangoproject.com/","size":292544,"stargazers_count":14553,"watchers_count":14553,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":5814,"mirror_url":null,"open_issues_count":91,"forks":5814,"open_issues":91,"watchers":14553,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/django/django/pulls/4749"},"html":{"href":"https://github.com/django/django/pull/4749"},"issue":{"href":"https://api.github.com/repos/django/django/issues/4749"},"comments":{"href":"https://api.github.com/repos/django/django/issues/4749/comments"},"review_comments":{"href":"https://api.github.com/repos/django/django/pulls/4749/comments"},"review_comment":{"href":"https://api.github.com/repos/django/django/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/django/django/pulls/4749/commits"},"statuses":{"href":"https://api.github.com/repos/django/django/statuses/8626d0cca1d23e8b4bfb0a71e43aee9b81d6d286"}}}}