{"action":"created","comment":{"url":"https://api.github.com/repos/django/django/pulls/comments/31383622","id":31383622,"diff_hunk":"@@ -0,0 +1,694 @@\n+===========================================\n+Advanced customization of class-based views\n+===========================================\n+\n+.. caution::\n+\n+    This is an advanced topic. A working knowledge of :doc:`Django's\n+    class-based views<index>` is advised before exploring these\n+    techniques.\n+\n+This section is meant to provide the following:\n+\n+* a number of ready-to-use customized class-based view examples\n+\n+* a walk-through of the process of creation and design of provided examples\n+\n+* a discussion of pro's and con's of different approaches when customizing\n+  class-based views\n+\n+* a specific focus on a few areas of class-based generic view hierarchy that\n+  can be complex and / or daunting to tweak\n+\n+There are two possible uses for customized class-based generic views: they may\n+serve as parent classes for future inheritance, or they may be used as \"Leaf\n+Classes\" to serve actual views in your application.\n+\n+In the former case, the general approach should be to override all of the\n+methods where functionality needs to be added by the custom class. The\n+resulting class should be as consistent with existing generic hierarchy as\n+possible. This allows for maximum flexibility in future reuse and further\n+customization by the \"Leaf Classes\".\n+\n+In the latter case, when using a class as actual view, you may find it useful\n+to use an almost exactly opposite approach:\n+\n+  * flatten and simplify the hierarchy when possible\n+\n+  * reduce the number of methods called when class is used\n+\n+  * prefer to methods directly instead of indirectly through intermediary methods\n+\n+The idea being that the operation of the view should be explicit and easy to\n+follow, to debug and modify (albeit at the cost of allowing less flexibility\n+for future reuse through inheritance).\n+\n+In this section, we will mostly focus on the latter approach -- customizing\n+classes to be used as actual views.\n+\n+Combined detail / list view\n+===========================\n+\n+It's a common pattern to show details of an object while at the same time\n+listing related objects (for example, a blog post with a list of comments).\n+\n+The first step when combining multiple views is to consider what we need to\n+reuse from each view.\n+\n+In this case, we need the Detail view to get an object based on the ``pk``\n+argument and insert the object into context. All of this is handled in\n+:meth:`SingleObjectMixin.get_context_data <django.views.generic.detail.SingleObjectMixin.get_context_data>`.\n+This method relies on the ``object`` instance variable.\n+\n+We also need to insert the list object into the context and to\n+handle pagination -- all of this is similarly handled in ``get_context_data()``\n+and relies on the ``object_list`` instance variable.\n+\n+Looking at ``get_context_data()`` implementations we can see that the logic\n+creates the context and adds it to the context of the super class (this will be\n+covered in more detail later). In other words, all of the required contexts are\n+combined automatically as long as we create the needed instance objects.\n+\n+Therefore, we have to set up the instance variables in ``get()`` method, call\n+``get_context_data()`` on the super class and render context to response.\n+\n+Note that we're skipping the ``allow_empty`` logic in ``ListView.get()``\n+because it makes no sense to disallow empty listings for this view.\n+\n+We also need to decide what we should inherit from. We have the following\n+options:\n+\n+* ``SingleObjectMixin``, ``MultipleObjectMixin`` and ``View``\n+  (to provide view rendering methods).\n+\n+* ``DetailView`` and ``ListView``.\n+\n+* One of the views above and one of the Mixins for the other class.\n+\n+The two first options are both good, but the 3rd option is problematic because\n+it makes it appear that we are using one of the view classes preferentially\n+where in fact we just need the view methods available from either class. We'll\n+use the first option in our example.\n+\n+We'll demonstrate this with the ``Author`` model we used in the\n+:doc:`generic class-based views introduction<generic-display>`.\n+\n+.. snippet::\n+    :filename: views.py\n+\n+    from django.views.generic import TemplateView\n+    from django.views.generic.detail import SingleObjectMixin\n+    from django.views.generic.list import MultipleObjectMixin\n+    from books.models import Author\n+\n+    class AuthorDetail(SingleObjectMixin, MultipleObjectMixin, TemplateView):\n+        \"\"\"Combined detail / list view.\"\"\"\n+        model = Author\n+        paginate_by = 2\n+        template_name = \"author.html\"\n+\n+        def get(self, request, *args, **kwargs):\n+            self.object = self.get_object()\n+            self.object_list = self.object.books.all()\n+            return self.render_to_response(self.get_context_data())\n+\n+We'll also have a ``Book`` model defined, linked to ``Author``:\n+\n+.. snippet::\n+    :filename: models.py\n+\n+    from django import models\n+\n+    class Book(models.Model):\n+        name = models.CharField(max_length=50, unique=True)\n+        author = models.ForeignKey(Author, related_name=\"books\")\n+\n+Note that when using ``context_object_name`` property or an automatically added\n+context variable with the name based on model name, we need to be mindful of\n+the inheritance order: in this case we'll use the ``author`` context variable\n+which is set up in ``SingleObjectMixin.get_context_data()`` so we have to put\n+``SingleObjectMixin`` first.\n+\n+.. code-block:: html+django\n+\n+    <h2>Author {{ author }}</h2>\n+\n+    <ol>\n+      {% for book in page_obj %}\n+        <li>{{ book.name }}</li>\n+      {% endfor %}\n+    </ol>\n+\n+    <div class=\"pagination\">\n+        <span class=\"step-links\">\n+            {% if page_obj.has_previous %}\n+                <a href=\"?page={{ page_obj.previous_page_number }}\">previous</a>\n+            {% endif %}\n+\n+            <span class=\"current\">\n+                Page {{ page_obj.number }} of {{ paginator.num_pages }}.\n+            </span>\n+\n+            {% if page_obj.has_next %}\n+                <a href=\"?page={{ page_obj.next_page_number }}\">next</a>\n+            {% endif %}\n+        </span>\n+    </div>\n+\n+Combined Detail and Form Views\n+==============================\n+\n+As with the last example, let's look at what we need to reuse:\n+``get_context_data()`` methods from both classes, and the ``post()`` method\n+from ``FormView``.\n+\n+Unlike the last example, there is no clash between ``get()`` methods:\n+``FormView``'s method does not need to do any special setup, therefore we can\n+simply skip it by putting ``SingleObjectMixin`` first in the order of inheritance.\n+\n+As such, we don't need to override any methods at all! However, we may\n+sometimes wish to override ``form_valid()`` to do some processing of the form\n+(but would have to do the same in a stand-alone ``FormView``):\n+\n+.. snippet::\n+    :filename: views.py\n+\n+    from django.views.generic import FormView\n+    from django.shortcuts import redirect\n+    from django import forms\n+\n+    class AuthorInterestForm(forms.Form):\n+        message = forms.CharField()\n+\n+    class AuthorDetail2(SingleObjectMixin, FormView):\n+        model = Author\n+        form_class = AuthorInterestForm\n+        template_name = \"author2.html\"\n+\n+        def form_valid(self, form):\n+            # Here, we would have some logic based on user's form inputs\n+            return redirect('author2', pk=self.get_object().pk)\n+\n+The template can use the ``author`` context variable and display the form:\n+\n+.. code-block:: html+django\n+\n+    <h2>Author {{ author.name }}</h2>\n+\n+    <form action=\"\" method=\"post\" accept-charset=\"utf-8\">{% csrf_token %}\n+        <table border=\"0\" cellpadding=\"2\" cellspacing=\"2\">\n+            {{ form.as_table }}\n+        </table>\n+    <p><input type=\"submit\" value=\"Submit\" /></p>\n+    </form>\n+\n+Combined List View with a Form\n+==============================\n+\n+This combined view is very similar to the last example. We need\n+``MultipleObjectMixin`` to get the listing object into context and we need\n+``FormView`` for ``post()`` method logic and adding the form to context. Since\n+``FormView`` already provides a ``get()`` method, we don't need any additional\n+classes:\n+\n+.. snippet::\n+    :filename: views.py\n+\n+    class BooksFormView(MultipleObjectMixin, FormView):\n+        model = Book\n+        form_class = AuthorInterestForm\n+        paginate_by = 2\n+        template_name = \"books.html\"\n+\n+        def form_valid(self, form):\n+            # Here, we would have some logic based on user's form inputs\n+            return redirect('books-form')\n+\n+The template is also straightforward (pagination links could also be added as\n+in the first example):\n+\n+.. code-block:: html+django\n+\n+    <h2>Books</h2>\n+\n+    <ol>\n+      {% for book in page_obj %}\n+        <li>{{ book.name }}</li>\n+      {% endfor %}\n+    </ol>\n+\n+\n+    <form action=\"\" method=\"post\" accept-charset=\"utf-8\">{% csrf_token %}\n+        <table border=\"0\" cellpadding=\"2\" cellspacing=\"2\">\n+            {{ form.as_table }}\n+        </table>\n+    <p><input type=\"submit\" value=\"Submit\" /></p>\n+    </form>\n+\n+A View with Arbitrary Number of Forms\n+=====================================\n+\n+The following view will display an arbitrary number of forms where the user\n+will only submit one of the forms, which is going to be validated while the\n+other forms will be loaded in initial state. This may be useful, for example,\n+when you want to show a search box on a page that already has a form.\n+\n+This should not be confused with an implementation where multiple django forms\n+are within a single ``form`` tag and are all processed on submission -- this is\n+trivially implemented because there's no need to track which form was used and\n+to special-case it.\n+\n+We'll need to override the ``get()`` and ``post()`` methods to load initial\n+forms into the context and to determine which form was used. Note that we are\n+only instantiating and validating the form that was used and ignoring the rest.\n+\n+The ``get()`` method is a good example of flattening and simplifying the method\n+hierarchy: we only need to get the forms into the context, so we can ignore\n+``get_context_data()`` and all form-related methods and render the dictionary\n+of forms directly to response.\n+\n+In ``post()`` we're getting the name of the form, instantiating it, and passing\n+both the name and form to ``form_valid()`` or ``form_invalid()`` methods.\n+\n+In ``form_valid()`` we may wish to do custom processing based on the form name.\n+Note that we've also flattened the method hierarchy by redirecting directly the\n+the success url.\n+\n+The ``form_invalid()`` method requires a bit of explanation: it's very similar\n+to the ``get()`` method but we need to create fresh instances of all forms\n+except for the submitted one, which is inserted into context as a bound form.\n+\n+.. snippet::\n+    :filename: views.py\n+\n+    class CommentForm(forms.Form):\n+        comment = forms.CharField()\n+\n+    class NFormsView(FormView):\n+        form_classes = dict(\n+                            comment_form = CommentForm,\n+                            author_form = AuthorInterestForm,\n+                            )\n+        template_name = \"two-forms.html\"\n+\n+        def get(self, request, *args, **kwargs):\n+            context = {n: cls() for n, cls in self.form_classes.items()}\n+            return self.render_to_response(context)\n+\n+        def post(self, request, *args, **kwargs):\n+            for name, cls in self.form_classes.items():\n+                if name in request.POST:\n+                    form = self.get_form(cls)\n+                    break\n+\n+            if form.is_valid():\n+                return self.form_valid(name, form)\n+            else:\n+                return self.form_invalid(name, form)\n+\n+        def form_valid(self, name, form):\n+            # Here, we would have some logic based on user's form inputs\n+            return redirect('two-forms')\n+\n+        def form_invalid(self, name, form):\n+            context = {n: cls() for n, cls in self.form_classes.items()}\n+            context.update({name: form})\n+            return self.render_to_response(context)\n+\n+The key change in the template is that each form's ``input`` tag needs to\n+specify the form's name:\n+\n+.. code-block:: html+django\n+\n+    <h2>Author form</h2>\n+\n+    <form action=\"\" method=\"post\" accept-charset=\"utf-8\">{% csrf_token %}\n+        <table border=\"0\" cellpadding=\"2\" cellspacing=\"2\">\n+            {{ author_form.as_table }}\n+        </table>\n+    <p><input type=\"submit\" name=\"author_form\" value=\"Submit\" /></p>\n+    </form>\n+\n+    <h2>Comment form</h2>\n+\n+    <form action=\"\" method=\"post\" accept-charset=\"utf-8\">{% csrf_token %}\n+        <table border=\"0\" cellpadding=\"2\" cellspacing=\"2\">\n+            {{ comment_form.as_table }}\n+        </table>\n+    <p><input type=\"submit\" name=\"comment_form\" value=\"Submit\" /></p>\n+    </form>\n+\n+Create / Update View\n+====================\n+\n+If we look at the ``CreateView`` and ``UpdateView``, there are only two methods\n+that are different: ``get()`` and ``post()``, where ``CreateView``'s methods\n+set the ``object`` property to ``None`` instead of using ``get_object()``\n+method.\n+\n+If we made the choice to customize ``CreateView``, we'd have to override both\n+methods since they explicitly set the value; with ``UpdateView`` the change can\n+be simpler because both methods delegate to ``get_object()``.\n+\n+Looking at ``get_object()`` we can see that a view missing ``pk`` and ``slug``\n+(as a ``CreateView`` will not have these) arguments will raise an\n+``AttributeError``.\n+\n+Therefore, all we have to do is return ``None`` on ``AttributeError`` in\n+``get_object()``:\n+\n+.. snippet::\n+    :filename: views.py\n+\n+    from django.core.urlresolvers import reverse_lazy\n+    from django.views.generic import UpdateView\n+\n+    class CreateUpdateBook(UpdateView):\n+        model = Book\n+        fields = [\"name\", \"author\"]\n+        success_url = reverse_lazy(\"books-form\")\n+        template_name = \"update-book.html\"\n+\n+        def get_object(self):\n+            try:\n+                return super(CreateUpdateBook, self).get_object()\n+            except AttributeError:\n+                return None\n+\n+In the template, you may check for the type of view by checking the\n+``form.instance`` variable:\n+\n+.. code-block:: html+django\n+\n+    <h2>Book form</h2>\n+\n+    <p>\n+    {% if form.instance %} edit {% else %} create {% endif %} a book\n+    </p>\n+\n+    <form action=\"\" method=\"post\" accept-charset=\"utf-8\">{% csrf_token %}\n+        <table border=\"0\" cellpadding=\"2\" cellspacing=\"2\">\n+            {{ form.as_table }}\n+        </table>\n+    <p><input type=\"submit\" name=\"author_form\" value=\"Submit\" /></p>\n+    </form>\n+\n+Combined Detail, List and Form View\n+===================================","path":"docs/topics/class-based-views/advanced.txt","position":397,"original_position":397,"commit_id":"0aa1bd4e8ee833c0508d223b82afd631cdd722a7","original_commit_id":"0aa1bd4e8ee833c0508d223b82afd631cdd722a7","user":{"login":"mjtamlyn","id":389581,"avatar_url":"https://avatars.githubusercontent.com/u/389581?v=3","gravatar_id":"","url":"https://api.github.com/users/mjtamlyn","html_url":"https://github.com/mjtamlyn","followers_url":"https://api.github.com/users/mjtamlyn/followers","following_url":"https://api.github.com/users/mjtamlyn/following{/other_user}","gists_url":"https://api.github.com/users/mjtamlyn/gists{/gist_id}","starred_url":"https://api.github.com/users/mjtamlyn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mjtamlyn/subscriptions","organizations_url":"https://api.github.com/users/mjtamlyn/orgs","repos_url":"https://api.github.com/users/mjtamlyn/repos","events_url":"https://api.github.com/users/mjtamlyn/events{/privacy}","received_events_url":"https://api.github.com/users/mjtamlyn/received_events","type":"User","site_admin":false},"body":"This is way out the territory of a generic view.","created_at":"2015-05-30T19:20:21Z","updated_at":"2015-05-30T19:20:21Z","html_url":"https://github.com/django/django/pull/4595#discussion_r31383622","pull_request_url":"https://api.github.com/repos/django/django/pulls/4595","_links":{"self":{"href":"https://api.github.com/repos/django/django/pulls/comments/31383622"},"html":{"href":"https://github.com/django/django/pull/4595#discussion_r31383622"},"pull_request":{"href":"https://api.github.com/repos/django/django/pulls/4595"}}},"pull_request":{"url":"https://api.github.com/repos/django/django/pulls/4595","id":34562208,"html_url":"https://github.com/django/django/pull/4595","diff_url":"https://github.com/django/django/pull/4595.diff","patch_url":"https://github.com/django/django/pull/4595.patch","issue_url":"https://api.github.com/repos/django/django/issues/4595","number":4595,"state":"closed","locked":false,"title":"Fixed #24734 -- Added CBGV advanced docs","user":{"login":"akulakov","id":243874,"avatar_url":"https://avatars.githubusercontent.com/u/243874?v=3","gravatar_id":"","url":"https://api.github.com/users/akulakov","html_url":"https://github.com/akulakov","followers_url":"https://api.github.com/users/akulakov/followers","following_url":"https://api.github.com/users/akulakov/following{/other_user}","gists_url":"https://api.github.com/users/akulakov/gists{/gist_id}","starred_url":"https://api.github.com/users/akulakov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akulakov/subscriptions","organizations_url":"https://api.github.com/users/akulakov/orgs","repos_url":"https://api.github.com/users/akulakov/repos","events_url":"https://api.github.com/users/akulakov/events{/privacy}","received_events_url":"https://api.github.com/users/akulakov/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-05-01T19:51:15Z","updated_at":"2015-05-30T19:20:21Z","closed_at":"2015-05-30T19:06:26Z","merged_at":null,"merge_commit_sha":"e9f3680d0d13e97393089b2be3b7109d7f543262","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/django/django/pulls/4595/commits","review_comments_url":"https://api.github.com/repos/django/django/pulls/4595/comments","review_comment_url":"https://api.github.com/repos/django/django/pulls/comments{/number}","comments_url":"https://api.github.com/repos/django/django/issues/4595/comments","statuses_url":"https://api.github.com/repos/django/django/statuses/0aa1bd4e8ee833c0508d223b82afd631cdd722a7","head":{"label":"akulakov:#24734-cbgv-advanced-docs","ref":"#24734-cbgv-advanced-docs","sha":"0aa1bd4e8ee833c0508d223b82afd631cdd722a7","user":{"login":"akulakov","id":243874,"avatar_url":"https://avatars.githubusercontent.com/u/243874?v=3","gravatar_id":"","url":"https://api.github.com/users/akulakov","html_url":"https://github.com/akulakov","followers_url":"https://api.github.com/users/akulakov/followers","following_url":"https://api.github.com/users/akulakov/following{/other_user}","gists_url":"https://api.github.com/users/akulakov/gists{/gist_id}","starred_url":"https://api.github.com/users/akulakov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akulakov/subscriptions","organizations_url":"https://api.github.com/users/akulakov/orgs","repos_url":"https://api.github.com/users/akulakov/repos","events_url":"https://api.github.com/users/akulakov/events{/privacy}","received_events_url":"https://api.github.com/users/akulakov/received_events","type":"User","site_admin":false},"repo":{"id":29978799,"name":"django-1","full_name":"akulakov/django-1","owner":{"login":"akulakov","id":243874,"avatar_url":"https://avatars.githubusercontent.com/u/243874?v=3","gravatar_id":"","url":"https://api.github.com/users/akulakov","html_url":"https://github.com/akulakov","followers_url":"https://api.github.com/users/akulakov/followers","following_url":"https://api.github.com/users/akulakov/following{/other_user}","gists_url":"https://api.github.com/users/akulakov/gists{/gist_id}","starred_url":"https://api.github.com/users/akulakov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akulakov/subscriptions","organizations_url":"https://api.github.com/users/akulakov/orgs","repos_url":"https://api.github.com/users/akulakov/repos","events_url":"https://api.github.com/users/akulakov/events{/privacy}","received_events_url":"https://api.github.com/users/akulakov/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/akulakov/django-1","description":"The Web framework for perfectionists with deadlines.","fork":true,"url":"https://api.github.com/repos/akulakov/django-1","forks_url":"https://api.github.com/repos/akulakov/django-1/forks","keys_url":"https://api.github.com/repos/akulakov/django-1/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akulakov/django-1/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akulakov/django-1/teams","hooks_url":"https://api.github.com/repos/akulakov/django-1/hooks","issue_events_url":"https://api.github.com/repos/akulakov/django-1/issues/events{/number}","events_url":"https://api.github.com/repos/akulakov/django-1/events","assignees_url":"https://api.github.com/repos/akulakov/django-1/assignees{/user}","branches_url":"https://api.github.com/repos/akulakov/django-1/branches{/branch}","tags_url":"https://api.github.com/repos/akulakov/django-1/tags","blobs_url":"https://api.github.com/repos/akulakov/django-1/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akulakov/django-1/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akulakov/django-1/git/refs{/sha}","trees_url":"https://api.github.com/repos/akulakov/django-1/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akulakov/django-1/statuses/{sha}","languages_url":"https://api.github.com/repos/akulakov/django-1/languages","stargazers_url":"https://api.github.com/repos/akulakov/django-1/stargazers","contributors_url":"https://api.github.com/repos/akulakov/django-1/contributors","subscribers_url":"https://api.github.com/repos/akulakov/django-1/subscribers","subscription_url":"https://api.github.com/repos/akulakov/django-1/subscription","commits_url":"https://api.github.com/repos/akulakov/django-1/commits{/sha}","git_commits_url":"https://api.github.com/repos/akulakov/django-1/git/commits{/sha}","comments_url":"https://api.github.com/repos/akulakov/django-1/comments{/number}","issue_comment_url":"https://api.github.com/repos/akulakov/django-1/issues/comments{/number}","contents_url":"https://api.github.com/repos/akulakov/django-1/contents/{+path}","compare_url":"https://api.github.com/repos/akulakov/django-1/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akulakov/django-1/merges","archive_url":"https://api.github.com/repos/akulakov/django-1/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akulakov/django-1/downloads","issues_url":"https://api.github.com/repos/akulakov/django-1/issues{/number}","pulls_url":"https://api.github.com/repos/akulakov/django-1/pulls{/number}","milestones_url":"https://api.github.com/repos/akulakov/django-1/milestones{/number}","notifications_url":"https://api.github.com/repos/akulakov/django-1/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akulakov/django-1/labels{/name}","releases_url":"https://api.github.com/repos/akulakov/django-1/releases{/id}","created_at":"2015-01-28T17:17:41Z","updated_at":"2015-02-05T14:59:48Z","pushed_at":"2015-05-29T21:33:06Z","git_url":"git://github.com/akulakov/django-1.git","ssh_url":"git@github.com:akulakov/django-1.git","clone_url":"https://github.com/akulakov/django-1.git","svn_url":"https://github.com/akulakov/django-1","homepage":"https://www.djangoproject.com/","size":136617,"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":"3baebf52aa0850de3ebadcbe1eda7e6e9a2c6c44","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-05-30T17:41:33Z","pushed_at":"2015-05-30T16:20:16Z","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":303736,"stargazers_count":14430,"watchers_count":14430,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":5743,"mirror_url":null,"open_issues_count":67,"forks":5743,"open_issues":67,"watchers":14430,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/django/django/pulls/4595"},"html":{"href":"https://github.com/django/django/pull/4595"},"issue":{"href":"https://api.github.com/repos/django/django/issues/4595"},"comments":{"href":"https://api.github.com/repos/django/django/issues/4595/comments"},"review_comments":{"href":"https://api.github.com/repos/django/django/pulls/4595/comments"},"review_comment":{"href":"https://api.github.com/repos/django/django/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/django/django/pulls/4595/commits"},"statuses":{"href":"https://api.github.com/repos/django/django/statuses/0aa1bd4e8ee833c0508d223b82afd631cdd722a7"}}}}