We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
502 Bad Gateway on Projects > Versions page
The Projects > Versions page for our project is throwing a 502 Bad Gateway error. It's happening at the following URL: 

https://readthedocs.org/projects/htcondor/versions

I thought this might be a transient error but it's been happening consistently for the last day or so. All the other pages I've tried seem to work fine.

It's worth noting a similar ticket  #5441  was filed a couple of days ago for a 502 error on the Import a Repository page. This seems to be working correctly now.

## Details

* Read the Docs project URL: https://htcondor.readthedocs.io
* Build URL (if applicable): 
* Read the Docs username (if applicable): htcondor

## Expected Result

A list of the different versions of our docs, dropdown menus to connect them to different branches, etc.

## Actual Result

Server Error

502 - Web server received an invalid response while acting as a gateway or proxy server.

There is a problem with the page you are looking for, and it cannot be displayed. When the Web server (while acting as a gateway or proxy) contacted the upstream content server, it received an invalid response from the content server.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `readthedocs/core/permissions.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 """Objects for User permission checks."""
4 
5 from readthedocs.core.utils.extend import SettingsOverrideObject
6 
7 
8 class AdminPermissionBase:
9 
10     @classmethod
11     def is_admin(cls, user, project):
12         return user in project.users.all() or user.is_superuser
13 
14     @classmethod
15     def is_member(cls, user, obj):
16         return user in obj.users.all() or user.is_superuser
17 
18 
19 class AdminPermission(SettingsOverrideObject):
20     _default_class = AdminPermissionBase
21     _override_setting = 'ADMIN_PERMISSION'
22
```

Path: `readthedocs/projects/views/public.py`
Content:
```
1 """Public project views."""
2 
3 import json
4 import logging
5 import mimetypes
6 import operator
7 import os
8 from collections import OrderedDict
9 
10 import requests
11 from django.conf import settings
12 from django.contrib import messages
13 from django.contrib.auth.models import User
14 from django.core.cache import cache
15 from django.core.files.storage import get_storage_class
16 from django.http import HttpResponse, HttpResponseRedirect
17 from django.shortcuts import get_object_or_404, render
18 from django.urls import reverse
19 from django.views.decorators.cache import never_cache
20 from django.views.generic import DetailView, ListView
21 from taggit.models import Tag
22 
23 from readthedocs.builds.constants import LATEST
24 from readthedocs.builds.models import Version
25 from readthedocs.builds.views import BuildTriggerMixin
26 from readthedocs.projects.models import Project
27 from readthedocs.projects.templatetags.projects_tags import sort_version_aware
28 
29 from .base import ProjectOnboardMixin
30 
31 
32 log = logging.getLogger(__name__)
33 search_log = logging.getLogger(__name__ + '.search')
34 mimetypes.add_type('application/epub+zip', '.epub')
35 storage = get_storage_class()()
36 
37 
38 class ProjectIndex(ListView):
39 
40     """List view of public :py:class:`Project` instances."""
41 
42     model = Project
43 
44     def get_queryset(self):
45         queryset = Project.objects.public(self.request.user)
46         queryset = queryset.exclude(users__profile__banned=True)
47 
48         if self.kwargs.get('tag'):
49             self.tag = get_object_or_404(Tag, slug=self.kwargs.get('tag'))
50             queryset = queryset.filter(tags__slug__in=[self.tag.slug])
51         else:
52             self.tag = None
53 
54         if self.kwargs.get('username'):
55             self.user = get_object_or_404(
56                 User,
57                 username=self.kwargs.get('username'),
58             )
59             queryset = queryset.filter(user=self.user)
60         else:
61             self.user = None
62 
63         return queryset
64 
65     def get_context_data(self, **kwargs):
66         context = super().get_context_data(**kwargs)
67         context['person'] = self.user
68         context['tag'] = self.tag
69         return context
70 
71 
72 class ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView):
73 
74     """Display project onboard steps."""
75 
76     model = Project
77     slug_url_kwarg = 'project_slug'
78 
79     def get_queryset(self):
80         return Project.objects.protected(self.request.user)
81 
82     def get_context_data(self, **kwargs):
83         context = super().get_context_data(**kwargs)
84 
85         project = self.get_object()
86         context['versions'] = Version.objects.public(
87             user=self.request.user,
88             project=project,
89         )
90 
91         protocol = 'http'
92         if self.request.is_secure():
93             protocol = 'https'
94 
95         version_slug = project.get_default_version()
96 
97         context['badge_url'] = '{}://{}{}?version={}'.format(
98             protocol,
99             settings.PRODUCTION_DOMAIN,
100             reverse('project_badge', args=[project.slug]),
101             project.get_default_version(),
102         )
103         context['site_url'] = '{url}?badge={version}'.format(
104             url=project.get_docs_url(version_slug),
105             version=version_slug,
106         )
107 
108         return context
109 
110 
111 @never_cache
112 def project_badge(request, project_slug):
113     """Return a sweet badge for the project."""
114     style = request.GET.get('style', 'flat')
115     if style not in (
116             'flat',
117             'plastic',
118             'flat-square',
119             'for-the-badge',
120             'social',
121     ):
122         style = 'flat'
123 
124     # Get the local path to the badge files
125     badge_path = os.path.join(
126         os.path.dirname(__file__),
127         '..',
128         'static',
129         'projects',
130         'badges',
131         '%s-' + style + '.svg',
132     )
133 
134     version_slug = request.GET.get('version', LATEST)
135     file_path = badge_path % 'unknown'
136 
137     version = Version.objects.public(request.user).filter(
138         project__slug=project_slug,
139         slug=version_slug,
140     ).first()
141 
142     if version:
143         last_build = version.builds.filter(
144             type='html',
145             state='finished',
146         ).order_by('-date').first()
147         if last_build:
148             if last_build.success:
149                 file_path = badge_path % 'passing'
150             else:
151                 file_path = badge_path % 'failing'
152 
153     try:
154         with open(file_path) as fd:
155             return HttpResponse(
156                 fd.read(),
157                 content_type='image/svg+xml',
158             )
159     except (IOError, OSError):
160         log.exception(
161             'Failed to read local filesystem while serving a docs badge',
162         )
163         return HttpResponse(status=503)
164 
165 
166 def project_downloads(request, project_slug):
167     """A detail view for a project with various downloads."""
168     project = get_object_or_404(
169         Project.objects.protected(request.user),
170         slug=project_slug,
171     )
172     versions = Version.objects.public(user=request.user, project=project)
173     versions = sort_version_aware(versions)
174     version_data = OrderedDict()
175     for version in versions:
176         data = version.get_downloads()
177         # Don't show ones that have no downloads.
178         if data:
179             version_data[version] = data
180 
181     return render(
182         request,
183         'projects/project_downloads.html',
184         {
185             'project': project,
186             'version_data': version_data,
187             'versions': versions,
188         },
189     )
190 
191 
192 def project_download_media(request, project_slug, type_, version_slug):
193     """
194     Download a specific piece of media.
195 
196     Perform an auth check if serving in private mode.
197 
198     .. warning:: This is linked directly from the HTML pages.
199                  It should only care about the Version permissions,
200                  not the actual Project permissions.
201     """
202     version = get_object_or_404(
203         Version.objects.public(user=request.user),
204         project__slug=project_slug,
205         slug=version_slug,
206     )
207     privacy_level = getattr(settings, 'DEFAULT_PRIVACY_LEVEL', 'public')
208     if privacy_level == 'public' or settings.DEBUG:
209         storage_path = version.project.get_storage_path(
210             type_=type_, version_slug=version_slug
211         )
212         if storage.exists(storage_path):
213             return HttpResponseRedirect(storage.url(storage_path))
214 
215         media_path = os.path.join(
216             settings.MEDIA_URL,
217             type_,
218             project_slug,
219             version_slug,
220             '%s.%s' % (project_slug, type_.replace('htmlzip', 'zip')),
221         )
222         return HttpResponseRedirect(media_path)
223 
224     # Get relative media path
225     path = (
226         version.project.get_production_media_path(
227             type_=type_,
228             version_slug=version_slug,
229         ).replace(settings.PRODUCTION_ROOT, '/prod_artifacts')
230     )
231     content_type, encoding = mimetypes.guess_type(path)
232     content_type = content_type or 'application/octet-stream'
233     response = HttpResponse(content_type=content_type)
234     if encoding:
235         response['Content-Encoding'] = encoding
236     response['X-Accel-Redirect'] = path
237     # Include version in filename; this fixes a long-standing bug
238     filename = '{}-{}.{}'.format(
239         project_slug,
240         version_slug,
241         path.split('.')[-1],
242     )
243     response['Content-Disposition'] = 'filename=%s' % filename
244     return response
245 
246 
247 def project_versions(request, project_slug):
248     """
249     Project version list view.
250 
251     Shows the available versions and lets the user choose which ones to build.
252     """
253     project = get_object_or_404(
254         Project.objects.protected(request.user),
255         slug=project_slug,
256     )
257 
258     versions = Version.objects.public(
259         user=request.user,
260         project=project,
261         only_active=False,
262     )
263     active_versions = versions.filter(active=True)
264     inactive_versions = versions.filter(active=False)
265 
266     # If there's a wiped query string, check the string against the versions
267     # list and display a success message. Deleting directories doesn't know how
268     # to fail.  :)
269     wiped = request.GET.get('wipe', '')
270     wiped_version = versions.filter(slug=wiped)
271     if wiped and wiped_version.count():
272         messages.success(request, 'Version wiped: ' + wiped)
273 
274     return render(
275         request,
276         'projects/project_version_list.html',
277         {
278             'inactive_versions': inactive_versions,
279             'active_versions': active_versions,
280             'project': project,
281         },
282     )
283 
284 
285 def project_analytics(request, project_slug):
286     """Have a analytics API placeholder."""
287     project = get_object_or_404(
288         Project.objects.protected(request.user),
289         slug=project_slug,
290     )
291     analytics_cache = cache.get('analytics:%s' % project_slug)
292     if analytics_cache:
293         analytics = json.loads(analytics_cache)
294     else:
295         try:
296             resp = requests.get(
297                 '{host}/api/v1/index/1/heatmap/'.format(
298                     host=settings.GROK_API_HOST,
299                 ),
300                 params={'project': project.slug, 'days': 7, 'compare': True},
301             )
302             analytics = resp.json()
303             cache.set('analytics:%s' % project_slug, resp.content, 1800)
304         except requests.exceptions.RequestException:
305             analytics = None
306 
307     if analytics:
308         page_list = list(
309             reversed(
310                 sorted(
311                     list(analytics['page'].items()),
312                     key=operator.itemgetter(1),
313                 ),
314             ),
315         )
316         version_list = list(
317             reversed(
318                 sorted(
319                     list(analytics['version'].items()),
320                     key=operator.itemgetter(1),
321                 ),
322             ),
323         )
324     else:
325         page_list = []
326         version_list = []
327 
328     full = request.GET.get('full')
329     if not full:
330         page_list = page_list[:20]
331         version_list = version_list[:20]
332 
333     return render(
334         request,
335         'projects/project_analytics.html',
336         {
337             'project': project,
338             'analytics': analytics,
339             'page_list': page_list,
340             'version_list': version_list,
341             'full': full,
342         },
343     )
344 
345 
346 def project_embed(request, project_slug):
347     """Have a content API placeholder."""
348     project = get_object_or_404(
349         Project.objects.protected(request.user),
350         slug=project_slug,
351     )
352     version = project.versions.get(slug=LATEST)
353     files = version.imported_files.filter(
354         name__endswith='.html',
355     ).order_by('path')
356 
357     return render(
358         request,
359         'projects/project_embed.html',
360         {
361             'project': project,
362             'files': files,
363             'settings': {
364                 'PUBLIC_API_URL': settings.PUBLIC_API_URL,
365                 'URI': request.build_absolute_uri(location='/').rstrip('/'),
366             },
367         },
368     )
369
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/readthedocs/core/permissions.py b/readthedocs/core/permissions.py
--- a/readthedocs/core/permissions.py
+++ b/readthedocs/core/permissions.py
@@ -9,6 +9,8 @@
 
     @classmethod
     def is_admin(cls, user, project):
+        # This explicitly uses "user in project.users.all" so that
+        # users on projects can be cached using prefetch_related or prefetch_related_objects
         return user in project.users.all() or user.is_superuser
 
     @classmethod
diff --git a/readthedocs/projects/views/public.py b/readthedocs/projects/views/public.py
--- a/readthedocs/projects/views/public.py
+++ b/readthedocs/projects/views/public.py
@@ -13,6 +13,7 @@
 from django.contrib.auth.models import User
 from django.core.cache import cache
 from django.core.files.storage import get_storage_class
+from django.db.models import prefetch_related_objects
 from django.http import HttpResponse, HttpResponseRedirect
 from django.shortcuts import get_object_or_404, render
 from django.urls import reverse
@@ -271,6 +272,9 @@
     if wiped and wiped_version.count():
         messages.success(request, 'Version wiped: ' + wiped)
 
+    # Optimize project permission checks
+    prefetch_related_objects([project], 'users')
+
     return render(
         request,
         'projects/project_version_list.html',

{"golden_diff": "diff --git a/readthedocs/core/permissions.py b/readthedocs/core/permissions.py\n--- a/readthedocs/core/permissions.py\n+++ b/readthedocs/core/permissions.py\n@@ -9,6 +9,8 @@\n \n     @classmethod\n     def is_admin(cls, user, project):\n+        # This explicitly uses \"user in project.users.all\" so that\n+        # users on projects can be cached using prefetch_related or prefetch_related_objects\n         return user in project.users.all() or user.is_superuser\n \n     @classmethod\ndiff --git a/readthedocs/projects/views/public.py b/readthedocs/projects/views/public.py\n--- a/readthedocs/projects/views/public.py\n+++ b/readthedocs/projects/views/public.py\n@@ -13,6 +13,7 @@\n from django.contrib.auth.models import User\n from django.core.cache import cache\n from django.core.files.storage import get_storage_class\n+from django.db.models import prefetch_related_objects\n from django.http import HttpResponse, HttpResponseRedirect\n from django.shortcuts import get_object_or_404, render\n from django.urls import reverse\n@@ -271,6 +272,9 @@\n     if wiped and wiped_version.count():\n         messages.success(request, 'Version wiped: ' + wiped)\n \n+    # Optimize project permission checks\n+    prefetch_related_objects([project], 'users')\n+\n     return render(\n         request,\n         'projects/project_version_list.html',\n", "issue": "502 Bad Gateway on Projects > Versions page\nThe Projects > Versions page for our project is throwing a 502 Bad Gateway error. It's happening at the following URL: \r\n\r\nhttps://readthedocs.org/projects/htcondor/versions\r\n\r\nI thought this might be a transient error but it's been happening consistently for the last day or so. All the other pages I've tried seem to work fine.\r\n\r\nIt's worth noting a similar ticket  #5441  was filed a couple of days ago for a 502 error on the Import a Repository page. This seems to be working correctly now.\r\n\r\n## Details\r\n\r\n* Read the Docs project URL: https://htcondor.readthedocs.io\r\n* Build URL (if applicable): \r\n* Read the Docs username (if applicable): htcondor\r\n\r\n## Expected Result\r\n\r\nA list of the different versions of our docs, dropdown menus to connect them to different branches, etc.\r\n\r\n## Actual Result\r\n\r\nServer Error\r\n\r\n502 - Web server received an invalid response while acting as a gateway or proxy server.\r\n\r\nThere is a problem with the page you are looking for, and it cannot be displayed. When the Web server (while acting as a gateway or proxy) contacted the upstream content server, it received an invalid response from the content server.\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\"Objects for User permission checks.\"\"\"\n\nfrom readthedocs.core.utils.extend import SettingsOverrideObject\n\n\nclass AdminPermissionBase:\n\n    @classmethod\n    def is_admin(cls, user, project):\n        return user in project.users.all() or user.is_superuser\n\n    @classmethod\n    def is_member(cls, user, obj):\n        return user in obj.users.all() or user.is_superuser\n\n\nclass AdminPermission(SettingsOverrideObject):\n    _default_class = AdminPermissionBase\n    _override_setting = 'ADMIN_PERMISSION'\n", "path": "readthedocs/core/permissions.py"}, {"content": "\"\"\"Public project views.\"\"\"\n\nimport json\nimport logging\nimport mimetypes\nimport operator\nimport os\nfrom collections import OrderedDict\n\nimport requests\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.contrib.auth.models import User\nfrom django.core.cache import cache\nfrom django.core.files.storage import get_storage_class\nfrom django.http import HttpResponse, HttpResponseRedirect\nfrom django.shortcuts import get_object_or_404, render\nfrom django.urls import reverse\nfrom django.views.decorators.cache import never_cache\nfrom django.views.generic import DetailView, ListView\nfrom taggit.models import Tag\n\nfrom readthedocs.builds.constants import LATEST\nfrom readthedocs.builds.models import Version\nfrom readthedocs.builds.views import BuildTriggerMixin\nfrom readthedocs.projects.models import Project\nfrom readthedocs.projects.templatetags.projects_tags import sort_version_aware\n\nfrom .base import ProjectOnboardMixin\n\n\nlog = logging.getLogger(__name__)\nsearch_log = logging.getLogger(__name__ + '.search')\nmimetypes.add_type('application/epub+zip', '.epub')\nstorage = get_storage_class()()\n\n\nclass ProjectIndex(ListView):\n\n    \"\"\"List view of public :py:class:`Project` instances.\"\"\"\n\n    model = Project\n\n    def get_queryset(self):\n        queryset = Project.objects.public(self.request.user)\n        queryset = queryset.exclude(users__profile__banned=True)\n\n        if self.kwargs.get('tag'):\n            self.tag = get_object_or_404(Tag, slug=self.kwargs.get('tag'))\n            queryset = queryset.filter(tags__slug__in=[self.tag.slug])\n        else:\n            self.tag = None\n\n        if self.kwargs.get('username'):\n            self.user = get_object_or_404(\n                User,\n                username=self.kwargs.get('username'),\n            )\n            queryset = queryset.filter(user=self.user)\n        else:\n            self.user = None\n\n        return queryset\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context['person'] = self.user\n        context['tag'] = self.tag\n        return context\n\n\nclass ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView):\n\n    \"\"\"Display project onboard steps.\"\"\"\n\n    model = Project\n    slug_url_kwarg = 'project_slug'\n\n    def get_queryset(self):\n        return Project.objects.protected(self.request.user)\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        project = self.get_object()\n        context['versions'] = Version.objects.public(\n            user=self.request.user,\n            project=project,\n        )\n\n        protocol = 'http'\n        if self.request.is_secure():\n            protocol = 'https'\n\n        version_slug = project.get_default_version()\n\n        context['badge_url'] = '{}://{}{}?version={}'.format(\n            protocol,\n            settings.PRODUCTION_DOMAIN,\n            reverse('project_badge', args=[project.slug]),\n            project.get_default_version(),\n        )\n        context['site_url'] = '{url}?badge={version}'.format(\n            url=project.get_docs_url(version_slug),\n            version=version_slug,\n        )\n\n        return context\n\n\n@never_cache\ndef project_badge(request, project_slug):\n    \"\"\"Return a sweet badge for the project.\"\"\"\n    style = request.GET.get('style', 'flat')\n    if style not in (\n            'flat',\n            'plastic',\n            'flat-square',\n            'for-the-badge',\n            'social',\n    ):\n        style = 'flat'\n\n    # Get the local path to the badge files\n    badge_path = os.path.join(\n        os.path.dirname(__file__),\n        '..',\n        'static',\n        'projects',\n        'badges',\n        '%s-' + style + '.svg',\n    )\n\n    version_slug = request.GET.get('version', LATEST)\n    file_path = badge_path % 'unknown'\n\n    version = Version.objects.public(request.user).filter(\n        project__slug=project_slug,\n        slug=version_slug,\n    ).first()\n\n    if version:\n        last_build = version.builds.filter(\n            type='html',\n            state='finished',\n        ).order_by('-date').first()\n        if last_build:\n            if last_build.success:\n                file_path = badge_path % 'passing'\n            else:\n                file_path = badge_path % 'failing'\n\n    try:\n        with open(file_path) as fd:\n            return HttpResponse(\n                fd.read(),\n                content_type='image/svg+xml',\n            )\n    except (IOError, OSError):\n        log.exception(\n            'Failed to read local filesystem while serving a docs badge',\n        )\n        return HttpResponse(status=503)\n\n\ndef project_downloads(request, project_slug):\n    \"\"\"A detail view for a project with various downloads.\"\"\"\n    project = get_object_or_404(\n        Project.objects.protected(request.user),\n        slug=project_slug,\n    )\n    versions = Version.objects.public(user=request.user, project=project)\n    versions = sort_version_aware(versions)\n    version_data = OrderedDict()\n    for version in versions:\n        data = version.get_downloads()\n        # Don't show ones that have no downloads.\n        if data:\n            version_data[version] = data\n\n    return render(\n        request,\n        'projects/project_downloads.html',\n        {\n            'project': project,\n            'version_data': version_data,\n            'versions': versions,\n        },\n    )\n\n\ndef project_download_media(request, project_slug, type_, version_slug):\n    \"\"\"\n    Download a specific piece of media.\n\n    Perform an auth check if serving in private mode.\n\n    .. warning:: This is linked directly from the HTML pages.\n                 It should only care about the Version permissions,\n                 not the actual Project permissions.\n    \"\"\"\n    version = get_object_or_404(\n        Version.objects.public(user=request.user),\n        project__slug=project_slug,\n        slug=version_slug,\n    )\n    privacy_level = getattr(settings, 'DEFAULT_PRIVACY_LEVEL', 'public')\n    if privacy_level == 'public' or settings.DEBUG:\n        storage_path = version.project.get_storage_path(\n            type_=type_, version_slug=version_slug\n        )\n        if storage.exists(storage_path):\n            return HttpResponseRedirect(storage.url(storage_path))\n\n        media_path = os.path.join(\n            settings.MEDIA_URL,\n            type_,\n            project_slug,\n            version_slug,\n            '%s.%s' % (project_slug, type_.replace('htmlzip', 'zip')),\n        )\n        return HttpResponseRedirect(media_path)\n\n    # Get relative media path\n    path = (\n        version.project.get_production_media_path(\n            type_=type_,\n            version_slug=version_slug,\n        ).replace(settings.PRODUCTION_ROOT, '/prod_artifacts')\n    )\n    content_type, encoding = mimetypes.guess_type(path)\n    content_type = content_type or 'application/octet-stream'\n    response = HttpResponse(content_type=content_type)\n    if encoding:\n        response['Content-Encoding'] = encoding\n    response['X-Accel-Redirect'] = path\n    # Include version in filename; this fixes a long-standing bug\n    filename = '{}-{}.{}'.format(\n        project_slug,\n        version_slug,\n        path.split('.')[-1],\n    )\n    response['Content-Disposition'] = 'filename=%s' % filename\n    return response\n\n\ndef project_versions(request, project_slug):\n    \"\"\"\n    Project version list view.\n\n    Shows the available versions and lets the user choose which ones to build.\n    \"\"\"\n    project = get_object_or_404(\n        Project.objects.protected(request.user),\n        slug=project_slug,\n    )\n\n    versions = Version.objects.public(\n        user=request.user,\n        project=project,\n        only_active=False,\n    )\n    active_versions = versions.filter(active=True)\n    inactive_versions = versions.filter(active=False)\n\n    # If there's a wiped query string, check the string against the versions\n    # list and display a success message. Deleting directories doesn't know how\n    # to fail.  :)\n    wiped = request.GET.get('wipe', '')\n    wiped_version = versions.filter(slug=wiped)\n    if wiped and wiped_version.count():\n        messages.success(request, 'Version wiped: ' + wiped)\n\n    return render(\n        request,\n        'projects/project_version_list.html',\n        {\n            'inactive_versions': inactive_versions,\n            'active_versions': active_versions,\n            'project': project,\n        },\n    )\n\n\ndef project_analytics(request, project_slug):\n    \"\"\"Have a analytics API placeholder.\"\"\"\n    project = get_object_or_404(\n        Project.objects.protected(request.user),\n        slug=project_slug,\n    )\n    analytics_cache = cache.get('analytics:%s' % project_slug)\n    if analytics_cache:\n        analytics = json.loads(analytics_cache)\n    else:\n        try:\n            resp = requests.get(\n                '{host}/api/v1/index/1/heatmap/'.format(\n                    host=settings.GROK_API_HOST,\n                ),\n                params={'project': project.slug, 'days': 7, 'compare': True},\n            )\n            analytics = resp.json()\n            cache.set('analytics:%s' % project_slug, resp.content, 1800)\n        except requests.exceptions.RequestException:\n            analytics = None\n\n    if analytics:\n        page_list = list(\n            reversed(\n                sorted(\n                    list(analytics['page'].items()),\n                    key=operator.itemgetter(1),\n                ),\n            ),\n        )\n        version_list = list(\n            reversed(\n                sorted(\n                    list(analytics['version'].items()),\n                    key=operator.itemgetter(1),\n                ),\n            ),\n        )\n    else:\n        page_list = []\n        version_list = []\n\n    full = request.GET.get('full')\n    if not full:\n        page_list = page_list[:20]\n        version_list = version_list[:20]\n\n    return render(\n        request,\n        'projects/project_analytics.html',\n        {\n            'project': project,\n            'analytics': analytics,\n            'page_list': page_list,\n            'version_list': version_list,\n            'full': full,\n        },\n    )\n\n\ndef project_embed(request, project_slug):\n    \"\"\"Have a content API placeholder.\"\"\"\n    project = get_object_or_404(\n        Project.objects.protected(request.user),\n        slug=project_slug,\n    )\n    version = project.versions.get(slug=LATEST)\n    files = version.imported_files.filter(\n        name__endswith='.html',\n    ).order_by('path')\n\n    return render(\n        request,\n        'projects/project_embed.html',\n        {\n            'project': project,\n            'files': files,\n            'settings': {\n                'PUBLIC_API_URL': settings.PUBLIC_API_URL,\n                'URI': request.build_absolute_uri(location='/').rstrip('/'),\n            },\n        },\n    )\n", "path": "readthedocs/projects/views/public.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\"Objects for User permission checks.\"\"\"\n\nfrom readthedocs.core.utils.extend import SettingsOverrideObject\n\n\nclass AdminPermissionBase:\n\n    @classmethod\n    def is_admin(cls, user, project):\n        # This explicitly uses \"user in project.users.all\" so that\n        # users on projects can be cached using prefetch_related or prefetch_related_objects\n        return user in project.users.all() or user.is_superuser\n\n    @classmethod\n    def is_member(cls, user, obj):\n        return user in obj.users.all() or user.is_superuser\n\n\nclass AdminPermission(SettingsOverrideObject):\n    _default_class = AdminPermissionBase\n    _override_setting = 'ADMIN_PERMISSION'\n", "path": "readthedocs/core/permissions.py"}, {"content": "\"\"\"Public project views.\"\"\"\n\nimport json\nimport logging\nimport mimetypes\nimport operator\nimport os\nfrom collections import OrderedDict\n\nimport requests\nfrom django.conf import settings\nfrom django.contrib import messages\nfrom django.contrib.auth.models import User\nfrom django.core.cache import cache\nfrom django.core.files.storage import get_storage_class\nfrom django.db.models import prefetch_related_objects\nfrom django.http import HttpResponse, HttpResponseRedirect\nfrom django.shortcuts import get_object_or_404, render\nfrom django.urls import reverse\nfrom django.views.decorators.cache import never_cache\nfrom django.views.generic import DetailView, ListView\nfrom taggit.models import Tag\n\nfrom readthedocs.builds.constants import LATEST\nfrom readthedocs.builds.models import Version\nfrom readthedocs.builds.views import BuildTriggerMixin\nfrom readthedocs.projects.models import Project\nfrom readthedocs.projects.templatetags.projects_tags import sort_version_aware\n\nfrom .base import ProjectOnboardMixin\n\n\nlog = logging.getLogger(__name__)\nsearch_log = logging.getLogger(__name__ + '.search')\nmimetypes.add_type('application/epub+zip', '.epub')\nstorage = get_storage_class()()\n\n\nclass ProjectIndex(ListView):\n\n    \"\"\"List view of public :py:class:`Project` instances.\"\"\"\n\n    model = Project\n\n    def get_queryset(self):\n        queryset = Project.objects.public(self.request.user)\n        queryset = queryset.exclude(users__profile__banned=True)\n\n        if self.kwargs.get('tag'):\n            self.tag = get_object_or_404(Tag, slug=self.kwargs.get('tag'))\n            queryset = queryset.filter(tags__slug__in=[self.tag.slug])\n        else:\n            self.tag = None\n\n        if self.kwargs.get('username'):\n            self.user = get_object_or_404(\n                User,\n                username=self.kwargs.get('username'),\n            )\n            queryset = queryset.filter(user=self.user)\n        else:\n            self.user = None\n\n        return queryset\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context['person'] = self.user\n        context['tag'] = self.tag\n        return context\n\n\nclass ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView):\n\n    \"\"\"Display project onboard steps.\"\"\"\n\n    model = Project\n    slug_url_kwarg = 'project_slug'\n\n    def get_queryset(self):\n        return Project.objects.protected(self.request.user)\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        project = self.get_object()\n        context['versions'] = Version.objects.public(\n            user=self.request.user,\n            project=project,\n        )\n\n        protocol = 'http'\n        if self.request.is_secure():\n            protocol = 'https'\n\n        version_slug = project.get_default_version()\n\n        context['badge_url'] = '{}://{}{}?version={}'.format(\n            protocol,\n            settings.PRODUCTION_DOMAIN,\n            reverse('project_badge', args=[project.slug]),\n            project.get_default_version(),\n        )\n        context['site_url'] = '{url}?badge={version}'.format(\n            url=project.get_docs_url(version_slug),\n            version=version_slug,\n        )\n\n        return context\n\n\n@never_cache\ndef project_badge(request, project_slug):\n    \"\"\"Return a sweet badge for the project.\"\"\"\n    style = request.GET.get('style', 'flat')\n    if style not in (\n            'flat',\n            'plastic',\n            'flat-square',\n            'for-the-badge',\n            'social',\n    ):\n        style = 'flat'\n\n    # Get the local path to the badge files\n    badge_path = os.path.join(\n        os.path.dirname(__file__),\n        '..',\n        'static',\n        'projects',\n        'badges',\n        '%s-' + style + '.svg',\n    )\n\n    version_slug = request.GET.get('version', LATEST)\n    file_path = badge_path % 'unknown'\n\n    version = Version.objects.public(request.user).filter(\n        project__slug=project_slug,\n        slug=version_slug,\n    ).first()\n\n    if version:\n        last_build = version.builds.filter(\n            type='html',\n            state='finished',\n        ).order_by('-date').first()\n        if last_build:\n            if last_build.success:\n                file_path = badge_path % 'passing'\n            else:\n                file_path = badge_path % 'failing'\n\n    try:\n        with open(file_path) as fd:\n            return HttpResponse(\n                fd.read(),\n                content_type='image/svg+xml',\n            )\n    except (IOError, OSError):\n        log.exception(\n            'Failed to read local filesystem while serving a docs badge',\n        )\n        return HttpResponse(status=503)\n\n\ndef project_downloads(request, project_slug):\n    \"\"\"A detail view for a project with various downloads.\"\"\"\n    project = get_object_or_404(\n        Project.objects.protected(request.user),\n        slug=project_slug,\n    )\n    versions = Version.objects.public(user=request.user, project=project)\n    versions = sort_version_aware(versions)\n    version_data = OrderedDict()\n    for version in versions:\n        data = version.get_downloads()\n        # Don't show ones that have no downloads.\n        if data:\n            version_data[version] = data\n\n    return render(\n        request,\n        'projects/project_downloads.html',\n        {\n            'project': project,\n            'version_data': version_data,\n            'versions': versions,\n        },\n    )\n\n\ndef project_download_media(request, project_slug, type_, version_slug):\n    \"\"\"\n    Download a specific piece of media.\n\n    Perform an auth check if serving in private mode.\n\n    .. warning:: This is linked directly from the HTML pages.\n                 It should only care about the Version permissions,\n                 not the actual Project permissions.\n    \"\"\"\n    version = get_object_or_404(\n        Version.objects.public(user=request.user),\n        project__slug=project_slug,\n        slug=version_slug,\n    )\n    privacy_level = getattr(settings, 'DEFAULT_PRIVACY_LEVEL', 'public')\n    if privacy_level == 'public' or settings.DEBUG:\n        storage_path = version.project.get_storage_path(\n            type_=type_, version_slug=version_slug\n        )\n        if storage.exists(storage_path):\n            return HttpResponseRedirect(storage.url(storage_path))\n\n        media_path = os.path.join(\n            settings.MEDIA_URL,\n            type_,\n            project_slug,\n            version_slug,\n            '%s.%s' % (project_slug, type_.replace('htmlzip', 'zip')),\n        )\n        return HttpResponseRedirect(media_path)\n\n    # Get relative media path\n    path = (\n        version.project.get_production_media_path(\n            type_=type_,\n            version_slug=version_slug,\n        ).replace(settings.PRODUCTION_ROOT, '/prod_artifacts')\n    )\n    content_type, encoding = mimetypes.guess_type(path)\n    content_type = content_type or 'application/octet-stream'\n    response = HttpResponse(content_type=content_type)\n    if encoding:\n        response['Content-Encoding'] = encoding\n    response['X-Accel-Redirect'] = path\n    # Include version in filename; this fixes a long-standing bug\n    filename = '{}-{}.{}'.format(\n        project_slug,\n        version_slug,\n        path.split('.')[-1],\n    )\n    response['Content-Disposition'] = 'filename=%s' % filename\n    return response\n\n\ndef project_versions(request, project_slug):\n    \"\"\"\n    Project version list view.\n\n    Shows the available versions and lets the user choose which ones to build.\n    \"\"\"\n    project = get_object_or_404(\n        Project.objects.protected(request.user),\n        slug=project_slug,\n    )\n\n    versions = Version.objects.public(\n        user=request.user,\n        project=project,\n        only_active=False,\n    )\n    active_versions = versions.filter(active=True)\n    inactive_versions = versions.filter(active=False)\n\n    # If there's a wiped query string, check the string against the versions\n    # list and display a success message. Deleting directories doesn't know how\n    # to fail.  :)\n    wiped = request.GET.get('wipe', '')\n    wiped_version = versions.filter(slug=wiped)\n    if wiped and wiped_version.count():\n        messages.success(request, 'Version wiped: ' + wiped)\n\n    # Optimize project permission checks\n    prefetch_related_objects([project], 'users')\n\n    return render(\n        request,\n        'projects/project_version_list.html',\n        {\n            'inactive_versions': inactive_versions,\n            'active_versions': active_versions,\n            'project': project,\n        },\n    )\n\n\ndef project_analytics(request, project_slug):\n    \"\"\"Have a analytics API placeholder.\"\"\"\n    project = get_object_or_404(\n        Project.objects.protected(request.user),\n        slug=project_slug,\n    )\n    analytics_cache = cache.get('analytics:%s' % project_slug)\n    if analytics_cache:\n        analytics = json.loads(analytics_cache)\n    else:\n        try:\n            resp = requests.get(\n                '{host}/api/v1/index/1/heatmap/'.format(\n                    host=settings.GROK_API_HOST,\n                ),\n                params={'project': project.slug, 'days': 7, 'compare': True},\n            )\n            analytics = resp.json()\n            cache.set('analytics:%s' % project_slug, resp.content, 1800)\n        except requests.exceptions.RequestException:\n            analytics = None\n\n    if analytics:\n        page_list = list(\n            reversed(\n                sorted(\n                    list(analytics['page'].items()),\n                    key=operator.itemgetter(1),\n                ),\n            ),\n        )\n        version_list = list(\n            reversed(\n                sorted(\n                    list(analytics['version'].items()),\n                    key=operator.itemgetter(1),\n                ),\n            ),\n        )\n    else:\n        page_list = []\n        version_list = []\n\n    full = request.GET.get('full')\n    if not full:\n        page_list = page_list[:20]\n        version_list = version_list[:20]\n\n    return render(\n        request,\n        'projects/project_analytics.html',\n        {\n            'project': project,\n            'analytics': analytics,\n            'page_list': page_list,\n            'version_list': version_list,\n            'full': full,\n        },\n    )\n\n\ndef project_embed(request, project_slug):\n    \"\"\"Have a content API placeholder.\"\"\"\n    project = get_object_or_404(\n        Project.objects.protected(request.user),\n        slug=project_slug,\n    )\n    version = project.versions.get(slug=LATEST)\n    files = version.imported_files.filter(\n        name__endswith='.html',\n    ).order_by('path')\n\n    return render(\n        request,\n        'projects/project_embed.html',\n        {\n            'project': project,\n            'files': files,\n            'settings': {\n                'PUBLIC_API_URL': settings.PUBLIC_API_URL,\n                'URI': request.build_absolute_uri(location='/').rstrip('/'),\n            },\n        },\n    )\n", "path": "readthedocs/projects/views/public.py"}]}