A lot of styling/autolinting
parent
bf5119f45a
commit
dc96c6d4a7
|
@ -1,28 +1,30 @@
|
|||
"""API resources"""
|
||||
from __future__ import absolute_import
|
||||
from builtins import object
|
||||
import logging
|
||||
# -*- coding: utf-8 -*-
|
||||
"""API resources."""
|
||||
from __future__ import (
|
||||
absolute_import, division, print_function, unicode_literals)
|
||||
|
||||
import json
|
||||
import logging
|
||||
from builtins import object
|
||||
|
||||
import redis
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf.urls import url
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.cache import cache
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from tastypie import fields
|
||||
from tastypie.authorization import DjangoAuthorization
|
||||
from tastypie.constants import ALL_WITH_RELATIONS, ALL
|
||||
from tastypie.constants import ALL, ALL_WITH_RELATIONS
|
||||
from tastypie.http import HttpApplicationError, HttpCreated
|
||||
from tastypie.resources import ModelResource
|
||||
from tastypie.http import HttpCreated, HttpApplicationError
|
||||
from tastypie.utils import dict_strip_unicode_keys, trailing_slash
|
||||
|
||||
from readthedocs.builds.constants import LATEST
|
||||
from readthedocs.builds.models import Version
|
||||
from readthedocs.core.utils import trigger_build
|
||||
from readthedocs.projects.models import Project, ImportedFile
|
||||
from readthedocs.projects.models import ImportedFile, Project
|
||||
|
||||
from .utils import SearchMixin, PostAuthentication
|
||||
from .utils import PostAuthentication, SearchMixin
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -41,8 +43,8 @@ class ProjectResource(ModelResource, SearchMixin):
|
|||
authorization = DjangoAuthorization()
|
||||
excludes = ['path', 'featured', 'programming_language']
|
||||
filtering = {
|
||||
"users": ALL_WITH_RELATIONS,
|
||||
"slug": ALL_WITH_RELATIONS,
|
||||
'users': ALL_WITH_RELATIONS,
|
||||
'slug': ALL_WITH_RELATIONS,
|
||||
}
|
||||
|
||||
def get_object_list(self, request):
|
||||
|
@ -63,28 +65,32 @@ class ProjectResource(ModelResource, SearchMixin):
|
|||
If a new resource is created, return ``HttpCreated`` (201 Created).
|
||||
"""
|
||||
deserialized = self.deserialize(
|
||||
request, request.body,
|
||||
format=request.META.get('CONTENT_TYPE', 'application/json')
|
||||
request,
|
||||
request.body,
|
||||
format=request.META.get('CONTENT_TYPE', 'application/json'),
|
||||
)
|
||||
|
||||
# Force this in an ugly way, at least should do "reverse"
|
||||
deserialized["users"] = ["/api/v1/user/%s/" % request.user.id]
|
||||
bundle = self.build_bundle(data=dict_strip_unicode_keys(deserialized), request=request)
|
||||
deserialized['users'] = ['/api/v1/user/%s/' % request.user.id]
|
||||
bundle = self.build_bundle(
|
||||
data=dict_strip_unicode_keys(deserialized), request=request)
|
||||
self.is_valid(bundle)
|
||||
updated_bundle = self.obj_create(bundle, request=request)
|
||||
return HttpCreated(location=self.get_resource_uri(updated_bundle))
|
||||
|
||||
def sync_versions(self, request, **kwargs):
|
||||
"""
|
||||
Sync the version data in the repo (on the build server) with what we have in the database.
|
||||
Sync the version data in the repo (on the build server) with what we
|
||||
have in the database.
|
||||
|
||||
Returns the identifiers for the versions that have been deleted.
|
||||
"""
|
||||
project = get_object_or_404(Project, pk=kwargs['pk'])
|
||||
try:
|
||||
post_data = self.deserialize(
|
||||
request, request.body,
|
||||
format=request.META.get('CONTENT_TYPE', 'application/json')
|
||||
request,
|
||||
request.body,
|
||||
format=request.META.get('CONTENT_TYPE', 'application/json'),
|
||||
)
|
||||
data = json.loads(post_data)
|
||||
self.method_check(request, allowed=['post'])
|
||||
|
@ -104,17 +110,20 @@ class ProjectResource(ModelResource, SearchMixin):
|
|||
|
||||
def prepend_urls(self):
|
||||
return [
|
||||
url(r"^(?P<resource_name>%s)/schema/$" % self._meta.resource_name,
|
||||
self.wrap_view('get_schema'), name="api_get_schema"),
|
||||
url(r"^(?P<resource_name>%s)/search%s$" % (
|
||||
self._meta.resource_name, trailing_slash()),
|
||||
self.wrap_view('get_search'), name="api_get_search"),
|
||||
url(r"^(?P<resource_name>%s)/(?P<pk>\d+)/sync_versions%s$" % (
|
||||
self._meta.resource_name, trailing_slash()),
|
||||
self.wrap_view('sync_versions'), name="api_sync_versions"),
|
||||
url((r"^(?P<resource_name>%s)/(?P<slug>[a-z-_]+)/$")
|
||||
% self._meta.resource_name, self.wrap_view('dispatch_detail'),
|
||||
name="api_dispatch_detail"),
|
||||
url(
|
||||
r'^(?P<resource_name>%s)/schema/$' % self._meta.resource_name,
|
||||
self.wrap_view('get_schema'), name='api_get_schema'),
|
||||
url(
|
||||
r'^(?P<resource_name>%s)/search%s$' %
|
||||
(self._meta.resource_name, trailing_slash()),
|
||||
self.wrap_view('get_search'), name='api_get_search'),
|
||||
url(
|
||||
r'^(?P<resource_name>%s)/(?P<pk>\d+)/sync_versions%s$' %
|
||||
(self._meta.resource_name, trailing_slash()),
|
||||
self.wrap_view('sync_versions'), name='api_sync_versions'),
|
||||
url((r'^(?P<resource_name>%s)/(?P<slug>[a-z-_]+)/$') %
|
||||
self._meta.resource_name, self.wrap_view('dispatch_detail'),
|
||||
name='api_dispatch_detail'),
|
||||
]
|
||||
|
||||
|
||||
|
@ -131,9 +140,9 @@ class VersionResource(ModelResource):
|
|||
authentication = PostAuthentication()
|
||||
authorization = DjangoAuthorization()
|
||||
filtering = {
|
||||
"project": ALL_WITH_RELATIONS,
|
||||
"slug": ALL_WITH_RELATIONS,
|
||||
"active": ALL,
|
||||
'project': ALL_WITH_RELATIONS,
|
||||
'slug': ALL_WITH_RELATIONS,
|
||||
'active': ALL,
|
||||
}
|
||||
|
||||
def get_object_list(self, request):
|
||||
|
@ -149,19 +158,19 @@ class VersionResource(ModelResource):
|
|||
|
||||
def prepend_urls(self):
|
||||
return [
|
||||
url(r"^(?P<resource_name>%s)/schema/$"
|
||||
% self._meta.resource_name,
|
||||
self.wrap_view('get_schema'),
|
||||
name="api_get_schema"),
|
||||
url(r"^(?P<resource_name>%s)/(?P<project__slug>[a-z-_]+[a-z0-9-_]+)/$" # noqa
|
||||
url(
|
||||
r'^(?P<resource_name>%s)/schema/$' % self._meta.resource_name,
|
||||
self.wrap_view('get_schema'), name='api_get_schema'),
|
||||
url(
|
||||
r'^(?P<resource_name>%s)/(?P<project__slug>[a-z-_]+[a-z0-9-_]+)/$' # noqa
|
||||
% self._meta.resource_name,
|
||||
self.wrap_view('dispatch_list'),
|
||||
name="api_version_list"),
|
||||
url((r"^(?P<resource_name>%s)/(?P<project_slug>[a-z-_]+[a-z0-9-_]+)/(?P"
|
||||
r"<version_slug>[a-z0-9-_.]+)/build/$")
|
||||
% self._meta.resource_name,
|
||||
self.wrap_view('build_version'),
|
||||
name="api_version_build_slug"),
|
||||
name='api_version_list'),
|
||||
url((
|
||||
r'^(?P<resource_name>%s)/(?P<project_slug>[a-z-_]+[a-z0-9-_]+)/(?P'
|
||||
r'<version_slug>[a-z0-9-_.]+)/build/$') %
|
||||
self._meta.resource_name, self.wrap_view('build_version'),
|
||||
name='api_version_build_slug'),
|
||||
]
|
||||
|
||||
|
||||
|
@ -182,18 +191,17 @@ class FileResource(ModelResource, SearchMixin):
|
|||
|
||||
def prepend_urls(self):
|
||||
return [
|
||||
url(r"^(?P<resource_name>%s)/schema/$" %
|
||||
self._meta.resource_name,
|
||||
self.wrap_view('get_schema'),
|
||||
name="api_get_schema"),
|
||||
url(r"^(?P<resource_name>%s)/search%s$" %
|
||||
url(
|
||||
r'^(?P<resource_name>%s)/schema/$' % self._meta.resource_name,
|
||||
self.wrap_view('get_schema'), name='api_get_schema'),
|
||||
url(
|
||||
r'^(?P<resource_name>%s)/search%s$' %
|
||||
(self._meta.resource_name, trailing_slash()),
|
||||
self.wrap_view('get_search'),
|
||||
name="api_get_search"),
|
||||
url(r"^(?P<resource_name>%s)/anchor%s$" %
|
||||
self.wrap_view('get_search'), name='api_get_search'),
|
||||
url(
|
||||
r'^(?P<resource_name>%s)/anchor%s$' %
|
||||
(self._meta.resource_name, trailing_slash()),
|
||||
self.wrap_view('get_anchor'),
|
||||
name="api_get_anchor"),
|
||||
self.wrap_view('get_anchor'), name='api_get_anchor'),
|
||||
]
|
||||
|
||||
def get_anchor(self, request, **__):
|
||||
|
@ -204,12 +212,14 @@ class FileResource(ModelResource, SearchMixin):
|
|||
query = request.GET.get('q', '')
|
||||
try:
|
||||
redis_client = cache.get_client(None)
|
||||
redis_data = redis_client.keys("*redirects:v4*%s*" % query)
|
||||
redis_data = redis_client.keys('*redirects:v4*%s*' % query)
|
||||
except (AttributeError, redis.exceptions.ConnectionError):
|
||||
redis_data = []
|
||||
# -2 because http:
|
||||
urls = [''.join(data.split(':')[6:]) for data in redis_data
|
||||
if 'http://' in data]
|
||||
urls = [
|
||||
''.join(data.split(':')[6:]) for data in redis_data
|
||||
if 'http://' in data
|
||||
]
|
||||
object_list = {'objects': urls}
|
||||
|
||||
self.log_throttled_access(request)
|
||||
|
@ -230,12 +240,11 @@ class UserResource(ModelResource):
|
|||
|
||||
def prepend_urls(self):
|
||||
return [
|
||||
url(r"^(?P<resource_name>%s)/schema/$" %
|
||||
self._meta.resource_name,
|
||||
self.wrap_view('get_schema'),
|
||||
name="api_get_schema"),
|
||||
url(r"^(?P<resource_name>%s)/(?P<username>[a-z-_]+)/$" %
|
||||
self._meta.resource_name,
|
||||
self.wrap_view('dispatch_detail'),
|
||||
name="api_dispatch_detail"),
|
||||
url(
|
||||
r'^(?P<resource_name>%s)/schema/$' % self._meta.resource_name,
|
||||
self.wrap_view('get_schema'), name='api_get_schema'),
|
||||
url(
|
||||
r'^(?P<resource_name>%s)/(?P<username>[a-z-_]+)/$' %
|
||||
self._meta.resource_name, self.wrap_view('dispatch_detail'),
|
||||
name='api_dispatch_detail'),
|
||||
]
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Views for the bookmarks app."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.views.generic import ListView, View
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from __future__ import (
|
||||
absolute_import, division, print_function, unicode_literals)
|
||||
|
||||
import json
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import (
|
||||
HttpResponse, HttpResponseBadRequest, HttpResponseRedirect)
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import ListView, View
|
||||
|
||||
from readthedocs.bookmarks.models import Bookmark
|
||||
from readthedocs.projects.models import Project
|
||||
|
||||
|
||||
# These views are CSRF exempt because of Django's CSRF middleware failing here
|
||||
# https://github.com/django/django/blob/stable/1.6.x/django/middleware/csrf.py#L135-L159
|
||||
# We don't have a valid referrer because we're on a subdomain
|
||||
|
@ -25,24 +28,23 @@ class BookmarkExistsView(View):
|
|||
|
||||
@method_decorator(csrf_exempt)
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super(BookmarkExistsView, self).dispatch(request, *args, **kwargs)
|
||||
return super(BookmarkExistsView,
|
||||
self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get(self, request):
|
||||
return HttpResponse(
|
||||
content=json.dumps(
|
||||
{'error': 'You must POST!'}
|
||||
),
|
||||
content=json.dumps({'error': 'You must POST!'}),
|
||||
content_type='application/json',
|
||||
status=405
|
||||
status=405,
|
||||
)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""
|
||||
Returns:
|
||||
|
||||
200 response with exists = True in json if bookmark exists.
|
||||
404 with exists = False in json if no matching bookmark is found.
|
||||
400 if json data is missing any one of: project, version, page.
|
||||
- 200 response with exists = True in json if bookmark exists.
|
||||
- 404 with exists = False in json if no matching bookmark is found.
|
||||
- 400 if json data is missing any one of: project, version, page.
|
||||
"""
|
||||
post_json = json.loads(request.body)
|
||||
try:
|
||||
|
@ -51,31 +53,31 @@ class BookmarkExistsView(View):
|
|||
page = post_json['page']
|
||||
except KeyError:
|
||||
return HttpResponseBadRequest(
|
||||
content=json.dumps({'error': 'Invalid parameters'})
|
||||
content=json.dumps({'error': 'Invalid parameters'}),
|
||||
)
|
||||
try:
|
||||
Bookmark.objects.get(
|
||||
project__slug=project,
|
||||
version__slug=version,
|
||||
page=page
|
||||
page=page,
|
||||
)
|
||||
except ObjectDoesNotExist:
|
||||
return HttpResponse(
|
||||
content=json.dumps({'exists': False}),
|
||||
status=404,
|
||||
content_type="application/json"
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
return HttpResponse(
|
||||
content=json.dumps({'exists': True}),
|
||||
status=200,
|
||||
content_type="application/json"
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
|
||||
class BookmarkListView(ListView):
|
||||
|
||||
"""Displays all of a logged-in user's bookmarks"""
|
||||
"""Displays all of a logged-in user's bookmarks."""
|
||||
|
||||
model = Bookmark
|
||||
|
||||
|
@ -89,7 +91,7 @@ class BookmarkListView(ListView):
|
|||
|
||||
class BookmarkAddView(View):
|
||||
|
||||
"""Adds bookmarks in response to POST requests"""
|
||||
"""Adds bookmarks in response to POST requests."""
|
||||
|
||||
@method_decorator(login_required)
|
||||
@method_decorator(csrf_exempt)
|
||||
|
@ -98,11 +100,9 @@ class BookmarkAddView(View):
|
|||
|
||||
def get(self, request):
|
||||
return HttpResponse(
|
||||
content=json.dumps(
|
||||
{'error': 'You must POST!'}
|
||||
),
|
||||
content=json.dumps({'error': 'You must POST!'}),
|
||||
content_type='application/json',
|
||||
status=405
|
||||
status=405,
|
||||
)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
@ -119,7 +119,7 @@ class BookmarkAddView(View):
|
|||
url = post_json['url']
|
||||
except KeyError:
|
||||
return HttpResponseBadRequest(
|
||||
content=json.dumps({'error': "Invalid parameters"})
|
||||
content=json.dumps({'error': 'Invalid parameters'}),
|
||||
)
|
||||
|
||||
try:
|
||||
|
@ -128,8 +128,7 @@ class BookmarkAddView(View):
|
|||
except ObjectDoesNotExist:
|
||||
return HttpResponseBadRequest(
|
||||
content=json.dumps(
|
||||
{'error': "Project or Version does not exist"}
|
||||
)
|
||||
{'error': 'Project or Version does not exist'}),
|
||||
)
|
||||
|
||||
Bookmark.objects.get_or_create(
|
||||
|
@ -142,7 +141,7 @@ class BookmarkAddView(View):
|
|||
return HttpResponse(
|
||||
json.dumps({'added': True}),
|
||||
status=201,
|
||||
content_type='application/json'
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
|
||||
|
@ -157,7 +156,8 @@ class BookmarkRemoveView(View):
|
|||
@method_decorator(login_required)
|
||||
@method_decorator(csrf_exempt)
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super(BookmarkRemoveView, self).dispatch(request, *args, **kwargs)
|
||||
return super(BookmarkRemoveView,
|
||||
self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return render(request, 'bookmarks/bookmark_delete.html')
|
||||
|
@ -180,7 +180,7 @@ class BookmarkRemoveView(View):
|
|||
page = post_json['page']
|
||||
except KeyError:
|
||||
return HttpResponseBadRequest(
|
||||
json.dumps({'error': "Invalid parameters"})
|
||||
json.dumps({'error': 'Invalid parameters'}),
|
||||
)
|
||||
|
||||
bookmark = get_object_or_404(
|
||||
|
@ -189,12 +189,12 @@ class BookmarkRemoveView(View):
|
|||
url=url,
|
||||
project=project,
|
||||
version=version,
|
||||
page=page
|
||||
page=page,
|
||||
)
|
||||
bookmark.delete()
|
||||
|
||||
return HttpResponse(
|
||||
json.dumps({'removed': True}),
|
||||
status=200,
|
||||
content_type="application/json"
|
||||
content_type='application/json',
|
||||
)
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Views for comments app."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import (
|
||||
absolute_import, division, print_function, unicode_literals)
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import render
|
||||
from django.utils.decorators import method_decorator
|
||||
from rest_framework import permissions, status
|
||||
from rest_framework.decorators import (
|
||||
api_view,
|
||||
authentication_classes,
|
||||
permission_classes,
|
||||
renderer_classes,
|
||||
detail_route
|
||||
)
|
||||
api_view, authentication_classes, detail_route, permission_classes,
|
||||
renderer_classes)
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.response import Response
|
||||
|
@ -19,13 +18,14 @@ from rest_framework.viewsets import ModelViewSet
|
|||
from sphinx.websupport import WebSupport
|
||||
|
||||
from readthedocs.comments.models import (
|
||||
DocumentComment, DocumentNode, NodeSnapshot, DocumentCommentSerializer,
|
||||
DocumentNodeSerializer, ModerationActionSerializer)
|
||||
DocumentComment, DocumentCommentSerializer, DocumentNode,
|
||||
DocumentNodeSerializer, ModerationActionSerializer, NodeSnapshot)
|
||||
from readthedocs.projects.models import Project
|
||||
from readthedocs.restapi.permissions import CommentModeratorOrReadOnly
|
||||
|
||||
from .backend import DjangoStorage
|
||||
from .session import UnsafeSessionAuthentication
|
||||
|
||||
storage = DjangoStorage()
|
||||
|
||||
support = WebSupport(
|
||||
|
@ -36,11 +36,11 @@ support = WebSupport(
|
|||
docroot='websupport',
|
||||
)
|
||||
|
||||
|
||||
########
|
||||
# called by javascript
|
||||
########
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([permissions.IsAuthenticatedOrReadOnly])
|
||||
@renderer_classes((JSONRenderer,))
|
||||
|
@ -56,7 +56,7 @@ def get_options(request): # pylint: disable=unused-argument
|
|||
@renderer_classes((JSONRenderer,))
|
||||
def get_metadata(request):
|
||||
"""
|
||||
Check for get_metadata
|
||||
Check for get_metadata.
|
||||
|
||||
GET: page
|
||||
"""
|
||||
|
@ -84,6 +84,7 @@ def attach_comment(request):
|
|||
# Normal Views
|
||||
#######
|
||||
|
||||
|
||||
def build(request): # pylint: disable=unused-argument
|
||||
support.build()
|
||||
|
||||
|
@ -93,6 +94,7 @@ def serve_file(request, file): # pylint: disable=redefined-builtin
|
|||
|
||||
return render(request, 'doc.html', {'document': document})
|
||||
|
||||
|
||||
######
|
||||
# Called by Builder
|
||||
######
|
||||
|
@ -147,8 +149,9 @@ def update_node(request):
|
|||
node.update_hash(new_hash, commit)
|
||||
return Response(DocumentNodeSerializer(node).data)
|
||||
except KeyError:
|
||||
return Response("You must include new_hash and commit in POST payload to this view.",
|
||||
status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
'You must include new_hash and commit in POST payload to this view.',
|
||||
status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class CommentViewSet(ModelViewSet):
|
||||
|
@ -156,16 +159,21 @@ class CommentViewSet(ModelViewSet):
|
|||
"""Viewset for Comment model."""
|
||||
|
||||
serializer_class = DocumentCommentSerializer
|
||||
permission_classes = [CommentModeratorOrReadOnly, permissions.IsAuthenticatedOrReadOnly]
|
||||
permission_classes = [
|
||||
CommentModeratorOrReadOnly,
|
||||
permissions.IsAuthenticatedOrReadOnly,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
qp = self.request.query_params
|
||||
if qp.get('node'):
|
||||
try:
|
||||
node = DocumentNode.objects.from_hash(version_slug=qp['version'],
|
||||
page=qp['document_page'],
|
||||
node_hash=qp['node'],
|
||||
project_slug=qp['project'])
|
||||
node = DocumentNode.objects.from_hash(
|
||||
version_slug=qp['version'],
|
||||
page=qp['document_page'],
|
||||
node_hash=qp['node'],
|
||||
project_slug=qp['project'],
|
||||
)
|
||||
queryset = DocumentComment.objects.filter(node=node)
|
||||
|
||||
except KeyError:
|
||||
|
@ -175,7 +183,8 @@ class CommentViewSet(ModelViewSet):
|
|||
except DocumentNode.DoesNotExist:
|
||||
queryset = DocumentComment.objects.none()
|
||||
elif qp.get('project'):
|
||||
queryset = DocumentComment.objects.filter(node__project__slug=qp['project'])
|
||||
queryset = DocumentComment.objects.filter(
|
||||
node__project__slug=qp['project'])
|
||||
|
||||
else:
|
||||
queryset = DocumentComment.objects.all()
|
||||
|
@ -184,16 +193,19 @@ class CommentViewSet(ModelViewSet):
|
|||
@method_decorator(login_required)
|
||||
def create(self, request, *args, **kwargs):
|
||||
project = Project.objects.get(slug=request.data['project'])
|
||||
comment = project.add_comment(version_slug=request.data['version'],
|
||||
page=request.data['document_page'],
|
||||
content_hash=request.data['node'],
|
||||
commit=request.data['commit'],
|
||||
user=request.user,
|
||||
text=request.data['text'])
|
||||
comment = project.add_comment(
|
||||
version_slug=request.data['version'],
|
||||
page=request.data['document_page'],
|
||||
content_hash=request.data['node'],
|
||||
commit=request.data['commit'],
|
||||
user=request.user,
|
||||
text=request.data['text'],
|
||||
)
|
||||
|
||||
serializer = self.get_serializer(comment)
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
|
||||
@detail_route(methods=['put'])
|
||||
def moderate(self, request, pk): # pylint: disable=unused-argument
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Core views, including the main homepage,
|
||||
|
||||
|
@ -10,7 +11,6 @@ from past.utils import old_div
|
|||
import os
|
||||
import logging
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponseRedirect, Http404
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
|
@ -37,7 +37,7 @@ class HomepageView(TemplateView):
|
|||
template_name = 'homepage.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Add latest builds and featured projects"""
|
||||
"""Add latest builds and featured projects."""
|
||||
context = super(HomepageView, self).get_context_data(**kwargs)
|
||||
latest = []
|
||||
latest_builds = (
|
||||
|
@ -47,7 +47,7 @@ class HomepageView(TemplateView):
|
|||
success=True,
|
||||
)
|
||||
.order_by('-date')
|
||||
)[:100]
|
||||
)[:100] # yapf: disable
|
||||
for build in latest_builds:
|
||||
if (build.project not in latest and len(latest) < 10):
|
||||
latest.append(build.project)
|
||||
|
@ -64,7 +64,8 @@ class SupportView(TemplateView):
|
|||
support_email = getattr(settings, 'SUPPORT_EMAIL', None)
|
||||
if not support_email:
|
||||
support_email = 'support@{domain}'.format(
|
||||
domain=getattr(settings, 'PRODUCTION_DOMAIN', 'readthedocs.org'))
|
||||
domain=getattr(
|
||||
settings, 'PRODUCTION_DOMAIN', 'readthedocs.org'))
|
||||
|
||||
context['support_email'] = support_email
|
||||
return context
|
||||
|
@ -83,10 +84,13 @@ def random_page(request, project_slug=None): # pylint: disable=unused-argument
|
|||
|
||||
@csrf_exempt
|
||||
def wipe_version(request, project_slug, version_slug):
|
||||
version = get_object_or_404(Version, project__slug=project_slug,
|
||||
slug=version_slug)
|
||||
version = get_object_or_404(
|
||||
Version,
|
||||
project__slug=project_slug,
|
||||
slug=version_slug,
|
||||
)
|
||||
if request.user not in version.project.users.all():
|
||||
raise Http404("You must own this project to wipe it.")
|
||||
raise Http404('You must own this project to wipe it.')
|
||||
|
||||
if request.method == 'POST':
|
||||
del_dirs = [
|
||||
|
@ -97,8 +101,9 @@ def wipe_version(request, project_slug, version_slug):
|
|||
for del_dir in del_dirs:
|
||||
broadcast(type='build', task=remove_dir, args=[del_dir])
|
||||
return redirect('project_version_list', project_slug)
|
||||
return render(request, 'wipe_version.html',
|
||||
{'version': version, 'project': version.project})
|
||||
return render(
|
||||
request, 'wipe_version.html',
|
||||
{'version': version, 'project': version.project})
|
||||
|
||||
|
||||
def divide_by_zero(request): # pylint: disable=unused-argument
|
||||
|
@ -106,14 +111,14 @@ def divide_by_zero(request): # pylint: disable=unused-argument
|
|||
|
||||
|
||||
def server_error_500(request, template_name='500.html'):
|
||||
"""A simple 500 handler so we get media"""
|
||||
"""A simple 500 handler so we get media."""
|
||||
r = render(request, template_name)
|
||||
r.status_code = 500
|
||||
return r
|
||||
|
||||
|
||||
def server_error_404(request, exception, template_name='404.html'): # pylint: disable=unused-argument # noqa
|
||||
"""A simple 404 handler so we get media"""
|
||||
"""A simple 404 handler so we get media."""
|
||||
response = get_redirect_response(request, path=request.get_full_path())
|
||||
if response:
|
||||
return response
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Doc serving from Python.
|
||||
|
||||
|
@ -24,23 +25,25 @@ PYTHON_MEDIA (False) - Set this to True to serve docs & media from Python
|
|||
SERVE_DOCS (['private']) - The list of ['private', 'public'] docs to serve.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import (
|
||||
absolute_import, division, print_function, unicode_literals)
|
||||
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
from functools import wraps
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse, HttpResponseRedirect, Http404
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.views.static import serve
|
||||
|
||||
from readthedocs.builds.models import Version
|
||||
from readthedocs.projects import constants
|
||||
from readthedocs.projects.models import Project, ProjectRelationship
|
||||
from readthedocs.core.permissions import AdminPermission
|
||||
from readthedocs.core.resolver import resolve, resolve_path
|
||||
from readthedocs.core.symlink import PrivateSymlink, PublicSymlink
|
||||
|
||||
import mimetypes
|
||||
import os
|
||||
import logging
|
||||
from functools import wraps
|
||||
from readthedocs.projects import constants
|
||||
from readthedocs.projects.models import Project, ProjectRelationship
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -53,8 +56,10 @@ def map_subproject_slug(view_func):
|
|||
|
||||
.. warning:: Does not take into account any kind of privacy settings.
|
||||
"""
|
||||
|
||||
@wraps(view_func)
|
||||
def inner_view(request, subproject=None, subproject_slug=None, *args, **kwargs):
|
||||
def inner_view(
|
||||
request, subproject=None, subproject_slug=None, *args, **kwargs):
|
||||
if subproject is None and subproject_slug:
|
||||
try:
|
||||
subproject = Project.objects.get(slug=subproject_slug)
|
||||
|
@ -69,6 +74,7 @@ def map_subproject_slug(view_func):
|
|||
except (ProjectRelationship.DoesNotExist, KeyError):
|
||||
raise Http404
|
||||
return view_func(request, subproject=subproject, *args, **kwargs)
|
||||
|
||||
return inner_view
|
||||
|
||||
|
||||
|
@ -80,6 +86,7 @@ def map_project_slug(view_func):
|
|||
|
||||
.. warning:: Does not take into account any kind of privacy settings.
|
||||
"""
|
||||
|
||||
@wraps(view_func)
|
||||
def inner_view(request, project=None, project_slug=None, *args, **kwargs):
|
||||
if project is None:
|
||||
|
@ -90,13 +97,14 @@ def map_project_slug(view_func):
|
|||
except Project.DoesNotExist:
|
||||
raise Http404('Project does not exist.')
|
||||
return view_func(request, project=project, *args, **kwargs)
|
||||
|
||||
return inner_view
|
||||
|
||||
|
||||
@map_project_slug
|
||||
@map_subproject_slug
|
||||
def redirect_project_slug(request, project, subproject): # pylint: disable=unused-argument
|
||||
"""Handle / -> /en/latest/ directs on subdomains"""
|
||||
"""Handle / -> /en/latest/ directs on subdomains."""
|
||||
return HttpResponseRedirect(resolve(subproject or project))
|
||||
|
||||
|
||||
|
@ -104,7 +112,8 @@ def redirect_project_slug(request, project, subproject): # pylint: disable=unus
|
|||
@map_subproject_slug
|
||||
def redirect_page_with_filename(request, project, subproject, filename): # pylint: disable=unused-argument # noqa
|
||||
"""Redirect /page/file.html to /en/latest/file.html."""
|
||||
return HttpResponseRedirect(resolve(subproject or project, filename=filename))
|
||||
return HttpResponseRedirect(
|
||||
resolve(subproject or project, filename=filename))
|
||||
|
||||
|
||||
def _serve_401(request, project):
|
||||
|
@ -121,15 +130,16 @@ def _serve_file(request, filename, basepath):
|
|||
return serve(request, filename, basepath)
|
||||
else:
|
||||
# Serve from Nginx
|
||||
content_type, encoding = mimetypes.guess_type(os.path.join(basepath, filename))
|
||||
content_type, encoding = mimetypes.guess_type(
|
||||
os.path.join(basepath, filename))
|
||||
content_type = content_type or 'application/octet-stream'
|
||||
response = HttpResponse(content_type=content_type)
|
||||
if encoding:
|
||||
response["Content-Encoding"] = encoding
|
||||
response['Content-Encoding'] = encoding
|
||||
try:
|
||||
response['X-Accel-Redirect'] = os.path.join(
|
||||
basepath[len(settings.SITE_ROOT):],
|
||||
filename
|
||||
filename,
|
||||
)
|
||||
except UnicodeEncodeError:
|
||||
raise Http404
|
||||
|
@ -139,9 +149,11 @@ def _serve_file(request, filename, basepath):
|
|||
|
||||
@map_project_slug
|
||||
@map_subproject_slug
|
||||
def serve_docs(request, project, subproject,
|
||||
lang_slug=None, version_slug=None, filename=''):
|
||||
"""Exists to map existing proj, lang, version, filename views to the file format."""
|
||||
def serve_docs(
|
||||
request, project, subproject, lang_slug=None, version_slug=None,
|
||||
filename=''):
|
||||
"""Exists to map existing proj, lang, version, filename views to the file
|
||||
format."""
|
||||
if not version_slug:
|
||||
version_slug = project.get_default_version()
|
||||
try:
|
||||
|
@ -153,18 +165,20 @@ def serve_docs(request, project, subproject,
|
|||
raise Http404('Version does not exist.')
|
||||
filename = resolve_path(
|
||||
subproject or project, # Resolve the subproject if it exists
|
||||
version_slug=version_slug, language=lang_slug, filename=filename,
|
||||
version_slug=version_slug,
|
||||
language=lang_slug,
|
||||
filename=filename,
|
||||
subdomain=True, # subdomain will make it a "full" path without a URL prefix
|
||||
)
|
||||
if (
|
||||
version.privacy_level == constants.PRIVATE and
|
||||
not AdminPermission.is_member(user=request.user, obj=project)
|
||||
):
|
||||
if (version.privacy_level == constants.PRIVATE and
|
||||
not AdminPermission.is_member(user=request.user, obj=project)):
|
||||
return _serve_401(request, project)
|
||||
return _serve_symlink_docs(request,
|
||||
filename=filename,
|
||||
project=project,
|
||||
privacy_level=version.privacy_level)
|
||||
return _serve_symlink_docs(
|
||||
request,
|
||||
filename=filename,
|
||||
project=project,
|
||||
privacy_level=version.privacy_level,
|
||||
)
|
||||
|
||||
|
||||
@map_project_slug
|
||||
|
@ -184,7 +198,7 @@ def _serve_symlink_docs(request, project, privacy_level, filename=''):
|
|||
|
||||
serve_docs = getattr(settings, 'SERVE_DOCS', [constants.PRIVATE])
|
||||
|
||||
if (settings.DEBUG or constants.PUBLIC in serve_docs) and privacy_level != constants.PRIVATE:
|
||||
if (settings.DEBUG or constants.PUBLIC in serve_docs) and privacy_level != constants.PRIVATE: # yapf: disable
|
||||
public_symlink = PublicSymlink(project)
|
||||
basepath = public_symlink.project_root
|
||||
if os.path.exists(os.path.join(basepath, filename)):
|
||||
|
@ -192,7 +206,7 @@ def _serve_symlink_docs(request, project, privacy_level, filename=''):
|
|||
else:
|
||||
files_tried.append(os.path.join(basepath, filename))
|
||||
|
||||
if (settings.DEBUG or constants.PRIVATE in serve_docs) and privacy_level == constants.PRIVATE:
|
||||
if (settings.DEBUG or constants.PRIVATE in serve_docs) and privacy_level == constants.PRIVATE: # yapf: disable
|
||||
|
||||
# Handle private
|
||||
private_symlink = PrivateSymlink(project)
|
||||
|
@ -203,4 +217,5 @@ def _serve_symlink_docs(request, project, privacy_level, filename=''):
|
|||
else:
|
||||
files_tried.append(os.path.join(basepath, filename))
|
||||
|
||||
raise Http404('File not found. Tried these files: %s' % ','.join(files_tried))
|
||||
raise Http404(
|
||||
'File not found. Tried these files: %s' % ','.join(files_tried))
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Gold subscription views."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from __future__ import (
|
||||
absolute_import, division, print_function, unicode_literals)
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from vanilla import DeleteView, UpdateView, DetailView
|
||||
from vanilla import DeleteView, DetailView, UpdateView
|
||||
|
||||
from readthedocs.core.mixins import LoginRequiredMixin
|
||||
from readthedocs.projects.models import Project, Domain
|
||||
from readthedocs.payments.mixins import StripeMixin
|
||||
from readthedocs.projects.models import Domain, Project
|
||||
|
||||
from .forms import GoldSubscriptionForm, GoldProjectForm
|
||||
from .forms import GoldProjectForm, GoldSubscriptionForm
|
||||
from .models import GoldUser
|
||||
|
||||
|
||||
class GoldSubscriptionMixin(SuccessMessageMixin, StripeMixin, LoginRequiredMixin):
|
||||
class GoldSubscriptionMixin(SuccessMessageMixin, StripeMixin,
|
||||
LoginRequiredMixin):
|
||||
|
||||
"""Gold subscription mixin for view classes."""
|
||||
|
||||
|
@ -41,8 +45,7 @@ class GoldSubscriptionMixin(SuccessMessageMixin, StripeMixin, LoginRequiredMixin
|
|||
return reverse_lazy('gold_detail')
|
||||
|
||||
def get_template_names(self):
|
||||
return ('gold/subscription{0}.html'
|
||||
.format(self.template_name_suffix))
|
||||
return ('gold/subscription{0}.html'.format(self.template_name_suffix))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GoldSubscriptionMixin, self).get_context_data(**kwargs)
|
||||
|
@ -50,6 +53,7 @@ class GoldSubscriptionMixin(SuccessMessageMixin, StripeMixin, LoginRequiredMixin
|
|||
context['domains'] = domains
|
||||
return context
|
||||
|
||||
|
||||
# Subscription Views
|
||||
|
||||
|
||||
|
@ -99,7 +103,8 @@ def projects(request):
|
|||
gold_projects = gold_user.projects.all()
|
||||
|
||||
if request.method == 'POST':
|
||||
form = GoldProjectForm(data=request.POST, user=gold_user, projects=gold_projects)
|
||||
form = GoldProjectForm(
|
||||
data=request.POST, user=gold_user, projects=gold_projects)
|
||||
if form.is_valid():
|
||||
to_add = Project.objects.get(slug=form.cleaned_data['project'])
|
||||
gold_user.projects.add(to_add)
|
||||
|
@ -107,15 +112,14 @@ def projects(request):
|
|||
else:
|
||||
form = GoldProjectForm()
|
||||
|
||||
return render(request,
|
||||
'gold/projects.html',
|
||||
{
|
||||
'form': form,
|
||||
'gold_user': gold_user,
|
||||
'publishable': settings.STRIPE_PUBLISHABLE,
|
||||
'user': request.user,
|
||||
'projects': gold_projects
|
||||
})
|
||||
return render(
|
||||
request, 'gold/projects.html', {
|
||||
'form': form,
|
||||
'gold_user': gold_user,
|
||||
'publishable': settings.STRIPE_PUBLISHABLE,
|
||||
'user': request.user,
|
||||
'projects': gold_projects,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Views for creating, editing and viewing site-specific user profiles."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import (
|
||||
absolute_import, division, print_function, unicode_literals)
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import logout
|
||||
|
@ -15,9 +17,10 @@ from django.template.context import RequestContext
|
|||
from readthedocs.core.forms import UserDeleteForm
|
||||
|
||||
|
||||
def create_profile(request, form_class, success_url=None,
|
||||
template_name='profiles/private/create_profile.html',
|
||||
extra_context=None):
|
||||
def create_profile(
|
||||
request, form_class, success_url=None,
|
||||
template_name='profiles/private/create_profile.html',
|
||||
extra_context=None):
|
||||
"""
|
||||
Create a profile for the current user, if one doesn't already exist.
|
||||
|
||||
|
@ -63,7 +66,6 @@ def create_profile(request, form_class, success_url=None,
|
|||
|
||||
``template_name`` keyword argument, or
|
||||
:template:`profiles/create_profile.html`.
|
||||
|
||||
"""
|
||||
try:
|
||||
profile_obj = request.user.profile
|
||||
|
@ -81,8 +83,9 @@ def create_profile(request, form_class, success_url=None,
|
|||
#
|
||||
|
||||
if success_url is None:
|
||||
success_url = reverse('profiles_profile_detail',
|
||||
kwargs={'username': request.user.username})
|
||||
success_url = reverse(
|
||||
'profiles_profile_detail',
|
||||
kwargs={'username': request.user.username})
|
||||
if request.method == 'POST':
|
||||
form = form_class(data=request.POST, files=request.FILES)
|
||||
if form.is_valid():
|
||||
|
@ -103,12 +106,14 @@ def create_profile(request, form_class, success_url=None,
|
|||
|
||||
context.update({'form': form})
|
||||
return render(request, template_name, context=context)
|
||||
|
||||
|
||||
create_profile = login_required(create_profile)
|
||||
|
||||
|
||||
def edit_profile(request, form_class, success_url=None,
|
||||
template_name='profiles/private/edit_profile.html',
|
||||
extra_context=None):
|
||||
def edit_profile(
|
||||
request, form_class, success_url=None,
|
||||
template_name='profiles/private/edit_profile.html', extra_context=None):
|
||||
"""
|
||||
Edit the current user's profile.
|
||||
|
||||
|
@ -153,7 +158,6 @@ def edit_profile(request, form_class, success_url=None,
|
|||
|
||||
``template_name`` keyword argument or
|
||||
:template:`profiles/edit_profile.html`.
|
||||
|
||||
"""
|
||||
try:
|
||||
profile_obj = request.user.profile
|
||||
|
@ -161,10 +165,12 @@ def edit_profile(request, form_class, success_url=None,
|
|||
return HttpResponseRedirect(reverse('profiles_profile_create'))
|
||||
|
||||
if success_url is None:
|
||||
success_url = reverse('profiles_profile_detail',
|
||||
kwargs={'username': request.user.username})
|
||||
success_url = reverse(
|
||||
'profiles_profile_detail',
|
||||
kwargs={'username': request.user.username})
|
||||
if request.method == 'POST':
|
||||
form = form_class(data=request.POST, files=request.FILES, instance=profile_obj)
|
||||
form = form_class(
|
||||
data=request.POST, files=request.FILES, instance=profile_obj)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return HttpResponseRedirect(success_url)
|
||||
|
@ -183,6 +189,8 @@ def edit_profile(request, form_class, success_url=None,
|
|||
'user': profile_obj.user,
|
||||
})
|
||||
return render(request, template_name, context=context)
|
||||
|
||||
|
||||
edit_profile = login_required(edit_profile)
|
||||
|
||||
|
||||
|
@ -205,9 +213,10 @@ def delete_account(request):
|
|||
return render(request, template_name, {'form': form})
|
||||
|
||||
|
||||
def profile_detail(request, username, public_profile_field=None,
|
||||
template_name='profiles/public/profile_detail.html',
|
||||
extra_context=None):
|
||||
def profile_detail(
|
||||
request, username, public_profile_field=None,
|
||||
template_name='profiles/public/profile_detail.html',
|
||||
extra_context=None):
|
||||
"""
|
||||
Detail view of a user's profile.
|
||||
|
||||
|
@ -252,7 +261,6 @@ def profile_detail(request, username, public_profile_field=None,
|
|||
|
||||
``template_name`` keyword argument or
|
||||
:template:`profiles/profile_detail.html`.
|
||||
|
||||
"""
|
||||
user = get_object_or_404(User, username=username)
|
||||
try:
|
||||
|
|
|
@ -1,46 +1,44 @@
|
|||
"""Project views for authenticated users."""
|
||||
"""Project views for authenticated users"""
|
||||
|
||||
from __future__ import (
|
||||
absolute_import, division, print_function, unicode_literals)
|
||||
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import (HttpResponseRedirect, HttpResponseNotAllowed,
|
||||
Http404, HttpResponseBadRequest)
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.views.generic import View, TemplateView, ListView
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.http import (
|
||||
Http404, HttpResponseBadRequest, HttpResponseNotAllowed,
|
||||
HttpResponseRedirect)
|
||||
from django.middleware.csrf import get_token
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import ListView, TemplateView, View
|
||||
from formtools.wizard.views import SessionWizardView
|
||||
from allauth.socialaccount.models import SocialAccount
|
||||
|
||||
from vanilla import CreateView, DeleteView, UpdateView, DetailView, GenericView
|
||||
from vanilla import CreateView, DeleteView, DetailView, GenericView, UpdateView
|
||||
|
||||
from readthedocs.bookmarks.models import Bookmark
|
||||
from readthedocs.builds.models import Version
|
||||
from readthedocs.builds.forms import AliasForm, VersionForm
|
||||
from readthedocs.builds.models import VersionAlias
|
||||
from readthedocs.core.utils import trigger_build, broadcast
|
||||
from readthedocs.core.mixins import ListViewWithForm
|
||||
from readthedocs.builds.models import Version, VersionAlias
|
||||
from readthedocs.core.mixins import ListViewWithForm, LoginRequiredMixin
|
||||
from readthedocs.core.utils import broadcast, trigger_build
|
||||
from readthedocs.integrations.models import HttpExchange, Integration
|
||||
from readthedocs.projects.forms import (
|
||||
ProjectBasicsForm, ProjectExtraForm, ProjectAdvancedForm,
|
||||
UpdateProjectForm, ProjectRelationshipForm,
|
||||
build_versions_form, UserForm, EmailHookForm, TranslationForm,
|
||||
RedirectForm, WebHookForm, DomainForm, IntegrationForm,
|
||||
ProjectAdvertisingForm)
|
||||
from readthedocs.projects.models import (
|
||||
Project, ProjectRelationship, EmailHook, WebHook, Domain)
|
||||
from readthedocs.projects.views.base import ProjectAdminMixin, ProjectSpamMixin
|
||||
from readthedocs.projects import tasks
|
||||
from readthedocs.oauth.services import registry
|
||||
from readthedocs.oauth.utils import attach_webhook, update_webhook
|
||||
|
||||
from readthedocs.core.mixins import LoginRequiredMixin
|
||||
from readthedocs.projects import tasks
|
||||
from readthedocs.projects.forms import (
|
||||
DomainForm, EmailHookForm, IntegrationForm, ProjectAdvancedForm,
|
||||
ProjectAdvertisingForm, ProjectBasicsForm, ProjectExtraForm,
|
||||
ProjectRelationshipForm, RedirectForm, TranslationForm, UpdateProjectForm,
|
||||
UserForm, WebHookForm, build_versions_form)
|
||||
from readthedocs.projects.models import (
|
||||
Domain, EmailHook, Project, ProjectRelationship, WebHook)
|
||||
from readthedocs.projects.signals import project_import
|
||||
from readthedocs.projects.views.base import ProjectAdminMixin, ProjectSpamMixin
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -82,18 +80,18 @@ def project_manage(__, project_slug):
|
|||
|
||||
Now redirects to the normal /projects/<slug> view.
|
||||
"""
|
||||
return HttpResponseRedirect(reverse('projects_detail',
|
||||
args=[project_slug]))
|
||||
return HttpResponseRedirect(reverse('projects_detail', args=[project_slug]))
|
||||
|
||||
|
||||
@login_required
|
||||
def project_comments_moderation(request, project_slug):
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
return render(
|
||||
request,
|
||||
'projects/project_comments_moderation.html',
|
||||
{'project': project})
|
||||
{'project': project},
|
||||
)
|
||||
|
||||
|
||||
class ProjectUpdate(ProjectSpamMixin, PrivateViewMixin, UpdateView):
|
||||
|
@ -137,8 +135,8 @@ def project_versions(request, project_slug):
|
|||
Shows the available versions and lets the user choose which ones he would
|
||||
like to have built.
|
||||
"""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
|
||||
if not project.is_imported:
|
||||
raise Http404
|
||||
|
@ -153,17 +151,19 @@ def project_versions(request, project_slug):
|
|||
project_dashboard = reverse('projects_detail', args=[project.slug])
|
||||
return HttpResponseRedirect(project_dashboard)
|
||||
|
||||
return render(request,
|
||||
'projects/project_versions.html',
|
||||
{'form': form, 'project': project})
|
||||
return render(
|
||||
request, 'projects/project_versions.html',
|
||||
{'form': form, 'project': project})
|
||||
|
||||
|
||||
@login_required
|
||||
def project_version_detail(request, project_slug, version_slug):
|
||||
"""Project version detail page."""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
version = get_object_or_404(
|
||||
Version.objects.public(user=request.user, project=project, only_active=False),
|
||||
Version.objects.public(
|
||||
user=request.user, project=project, only_active=False),
|
||||
slug=version_slug)
|
||||
|
||||
form = VersionForm(request.POST or None, instance=version)
|
||||
|
@ -173,14 +173,15 @@ def project_version_detail(request, project_slug, version_slug):
|
|||
if form.has_changed():
|
||||
if 'active' in form.changed_data and version.active is False:
|
||||
log.info('Removing files for version %s', version.slug)
|
||||
broadcast(type='app', task=tasks.clear_artifacts, args=[version.pk])
|
||||
broadcast(
|
||||
type='app', task=tasks.clear_artifacts, args=[version.pk])
|
||||
version.built = False
|
||||
version.save()
|
||||
url = reverse('project_version_list', args=[project.slug])
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
return render(request,
|
||||
'projects/project_version_detail.html',
|
||||
return render(
|
||||
request, 'projects/project_version_detail.html',
|
||||
{'form': form, 'project': project, 'version': version})
|
||||
|
||||
|
||||
|
@ -192,8 +193,8 @@ def project_delete(request, project_slug):
|
|||
Make a project as deleted on POST, otherwise show a form asking for
|
||||
confirmation of delete.
|
||||
"""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
|
||||
if request.method == 'POST':
|
||||
broadcast(type='app', task=tasks.remove_dir, args=[project.doc_path])
|
||||
|
@ -202,17 +203,14 @@ def project_delete(request, project_slug):
|
|||
project_dashboard = reverse('projects_dashboard')
|
||||
return HttpResponseRedirect(project_dashboard)
|
||||
|
||||
return render(request,
|
||||
'projects/project_delete.html',
|
||||
{'project': project})
|
||||
return render(request, 'projects/project_delete.html', {'project': project})
|
||||
|
||||
|
||||
class ImportWizardView(ProjectSpamMixin, PrivateViewMixin, SessionWizardView):
|
||||
|
||||
"""Project import wizard."""
|
||||
|
||||
form_list = [('basics', ProjectBasicsForm),
|
||||
('extra', ProjectExtraForm)]
|
||||
form_list = [('basics', ProjectBasicsForm), ('extra', ProjectExtraForm),]
|
||||
condition_dict = {'extra': lambda self: self.is_advanced()}
|
||||
|
||||
def get_form_kwargs(self, step=None):
|
||||
|
@ -253,8 +251,8 @@ class ImportWizardView(ProjectSpamMixin, PrivateViewMixin, SessionWizardView):
|
|||
project.save()
|
||||
project_import.send(sender=project, request=self.request)
|
||||
trigger_build(project, basic=basic_only)
|
||||
return HttpResponseRedirect(reverse('projects_detail',
|
||||
args=[project.slug]))
|
||||
return HttpResponseRedirect(
|
||||
reverse('projects_detail', args=[project.slug]))
|
||||
|
||||
def is_advanced(self):
|
||||
"""Determine if the user selected the `show advanced` field."""
|
||||
|
@ -278,8 +276,8 @@ class ImportDemoView(PrivateViewMixin, View):
|
|||
self.kwargs = kwargs
|
||||
|
||||
data = self.get_form_data()
|
||||
project = (Project.objects.for_admin_user(request.user)
|
||||
.filter(repo=data['repo']).first())
|
||||
project = Project.objects.for_admin_user(
|
||||
request.user).filter(repo=data['repo']).first()
|
||||
if project is not None:
|
||||
messages.success(
|
||||
request, _('The demo project is already imported!'))
|
||||
|
@ -295,11 +293,13 @@ class ImportDemoView(PrivateViewMixin, View):
|
|||
else:
|
||||
for (__, msg) in list(form.errors.items()):
|
||||
log.error(msg)
|
||||
messages.error(request,
|
||||
_('There was a problem adding the demo project'))
|
||||
messages.error(
|
||||
request,
|
||||
_('There was a problem adding the demo project'),
|
||||
)
|
||||
return HttpResponseRedirect(reverse('projects_dashboard'))
|
||||
return HttpResponseRedirect(reverse('projects_detail',
|
||||
args=[project.slug]))
|
||||
return HttpResponseRedirect(
|
||||
reverse('projects_detail', args=[project.slug]))
|
||||
|
||||
def get_form_data(self):
|
||||
"""Get form data to post to import form."""
|
||||
|
@ -337,19 +337,23 @@ class ImportView(PrivateViewMixin, TemplateView):
|
|||
deprecated_accounts = (
|
||||
SocialAccount.objects
|
||||
.filter(user=self.request.user)
|
||||
.exclude(provider__in=[service.adapter.provider_id
|
||||
for service in registry])
|
||||
)
|
||||
.exclude(
|
||||
provider__in=[
|
||||
service.adapter.provider_id for service in registry
|
||||
])
|
||||
) # yapf: disable
|
||||
for account in deprecated_accounts:
|
||||
provider_account = account.get_provider_account()
|
||||
messages.error(
|
||||
request,
|
||||
mark_safe(
|
||||
(_('There is a problem with your {service} account, '
|
||||
'try reconnecting your account on your '
|
||||
'<a href="{url}">connected services page</a>.')
|
||||
.format(service=provider_account.get_brand()['name'],
|
||||
url=reverse('socialaccount_connections'))))
|
||||
mark_safe((
|
||||
_(
|
||||
'There is a problem with your {service} account, '
|
||||
'try reconnecting your account on your '
|
||||
'<a href="{url}">connected services page</a>.').format(
|
||||
service=provider_account.get_brand()['name'],
|
||||
url=reverse('socialaccount_connections'))
|
||||
)) # yapf: disable
|
||||
)
|
||||
return super(ImportView, self).get(request, *args, **kwargs)
|
||||
|
||||
|
@ -367,17 +371,16 @@ class ImportView(PrivateViewMixin, TemplateView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super(ImportView, self).get_context_data(**kwargs)
|
||||
context['view_csrf_token'] = get_token(self.request)
|
||||
context['has_connected_accounts'] = (SocialAccount
|
||||
.objects
|
||||
.filter(user=self.request.user)
|
||||
.exists())
|
||||
context['has_connected_accounts'] = SocialAccount.objects.filter(
|
||||
user=self.request.user).exists()
|
||||
return context
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_alias(request, project_slug, alias_id=None):
|
||||
"""Edit project alias form view."""
|
||||
proj = get_object_or_404(Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
proj = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
if alias_id:
|
||||
alias = proj.aliases.get(pk=alias_id)
|
||||
form = AliasForm(instance=alias, data=request.POST or None)
|
||||
|
@ -387,9 +390,11 @@ def edit_alias(request, project_slug, alias_id=None):
|
|||
if request.method == 'POST' and form.is_valid():
|
||||
alias = form.save()
|
||||
return HttpResponseRedirect(alias.project.get_absolute_url())
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'projects/alias_edit.html',
|
||||
{'form': form})
|
||||
{'form': form},
|
||||
)
|
||||
|
||||
|
||||
class AliasList(PrivateViewMixin, ListView):
|
||||
|
@ -417,11 +422,15 @@ class ProjectRelationshipMixin(ProjectAdminMixin, PrivateViewMixin):
|
|||
|
||||
def get_form(self, data=None, files=None, **kwargs):
|
||||
kwargs['user'] = self.request.user
|
||||
return super(ProjectRelationshipMixin, self).get_form(data, files, **kwargs)
|
||||
return super(ProjectRelationshipMixin,
|
||||
self).get_form(data, files, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
broadcast(type='app', task=tasks.symlink_subproject,
|
||||
args=[self.get_project().pk])
|
||||
broadcast(
|
||||
type='app',
|
||||
task=tasks.symlink_subproject,
|
||||
args=[self.get_project().pk],
|
||||
)
|
||||
return super(ProjectRelationshipMixin, self).form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
|
@ -453,8 +462,8 @@ class ProjectRelationshipDelete(ProjectRelationshipMixin, DeleteView):
|
|||
@login_required
|
||||
def project_users(request, project_slug):
|
||||
"""Project users view and form view."""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
|
||||
form = UserForm(data=request.POST or None, project=project)
|
||||
|
||||
|
@ -465,17 +474,21 @@ def project_users(request, project_slug):
|
|||
|
||||
users = project.users.all()
|
||||
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'projects/project_users.html',
|
||||
{'form': form, 'project': project, 'users': users})
|
||||
{'form': form, 'project': project, 'users': users},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def project_users_delete(request, project_slug):
|
||||
if request.method != 'POST':
|
||||
return HttpResponseNotAllowed('Only POST is allowed')
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
user = get_object_or_404(User.objects.all(), username=request.POST.get('username'))
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
user = get_object_or_404(
|
||||
User.objects.all(), username=request.POST.get('username'))
|
||||
if user == request.user:
|
||||
raise Http404
|
||||
project.users.remove(user)
|
||||
|
@ -486,8 +499,8 @@ def project_users_delete(request, project_slug):
|
|||
@login_required
|
||||
def project_notifications(request, project_slug):
|
||||
"""Project notification view and form view."""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
|
||||
email_form = EmailHookForm(data=request.POST or None, project=project)
|
||||
webhook_form = WebHookForm(data=request.POST or None, project=project)
|
||||
|
@ -497,14 +510,17 @@ def project_notifications(request, project_slug):
|
|||
email_form.save()
|
||||
if webhook_form.is_valid():
|
||||
webhook_form.save()
|
||||
project_dashboard = reverse('projects_notifications',
|
||||
args=[project.slug])
|
||||
project_dashboard = reverse(
|
||||
'projects_notifications',
|
||||
args=[project.slug],
|
||||
)
|
||||
return HttpResponseRedirect(project_dashboard)
|
||||
|
||||
emails = project.emailhook_notifications.all()
|
||||
urls = project.webhook_notifications.all()
|
||||
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'projects/project_notifications.html',
|
||||
{
|
||||
'email_form': email_form,
|
||||
|
@ -512,19 +528,22 @@ def project_notifications(request, project_slug):
|
|||
'project': project,
|
||||
'emails': emails,
|
||||
'urls': urls,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def project_comments_settings(request, project_slug):
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'projects/project_comments_settings.html',
|
||||
{
|
||||
'project': project,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -532,13 +551,15 @@ def project_notifications_delete(request, project_slug):
|
|||
"""Project notifications delete confirmation view."""
|
||||
if request.method != 'POST':
|
||||
return HttpResponseNotAllowed('Only POST is allowed')
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
try:
|
||||
project.emailhook_notifications.get(email=request.POST.get('email')).delete()
|
||||
project.emailhook_notifications.get(
|
||||
email=request.POST.get('email')).delete()
|
||||
except EmailHook.DoesNotExist:
|
||||
try:
|
||||
project.webhook_notifications.get(url=request.POST.get('email')).delete()
|
||||
project.webhook_notifications.get(
|
||||
url=request.POST.get('email')).delete()
|
||||
except WebHook.DoesNotExist:
|
||||
raise Http404
|
||||
project_dashboard = reverse('projects_notifications', args=[project.slug])
|
||||
|
@ -548,27 +569,41 @@ def project_notifications_delete(request, project_slug):
|
|||
@login_required
|
||||
def project_translations(request, project_slug):
|
||||
"""Project translations view and form view."""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
form = TranslationForm(data=request.POST or None, parent=project)
|
||||
|
||||
if request.method == 'POST' and form.is_valid():
|
||||
form.save()
|
||||
project_dashboard = reverse('projects_translations',
|
||||
args=[project.slug])
|
||||
project_dashboard = reverse(
|
||||
'projects_translations',
|
||||
args=[project.slug],
|
||||
)
|
||||
return HttpResponseRedirect(project_dashboard)
|
||||
|
||||
lang_projects = project.translations.all()
|
||||
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'projects/project_translations.html',
|
||||
{'form': form, 'project': project, 'lang_projects': lang_projects})
|
||||
{
|
||||
'form': form,
|
||||
'project': project,
|
||||
'lang_projects': lang_projects,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def project_translations_delete(request, project_slug, child_slug):
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
subproj = get_object_or_404(Project.objects.for_admin_user(request.user), slug=child_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug,
|
||||
)
|
||||
subproj = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user),
|
||||
slug=child_slug,
|
||||
)
|
||||
project.translations.remove(subproj)
|
||||
project_dashboard = reverse('projects_translations', args=[project.slug])
|
||||
return HttpResponseRedirect(project_dashboard)
|
||||
|
@ -577,8 +612,8 @@ def project_translations_delete(request, project_slug, child_slug):
|
|||
@login_required
|
||||
def project_redirects(request, project_slug):
|
||||
"""Project redirects view and form view."""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
|
||||
form = RedirectForm(data=request.POST or None, project=project)
|
||||
|
||||
|
@ -589,8 +624,8 @@ def project_redirects(request, project_slug):
|
|||
|
||||
redirects = project.redirects.all()
|
||||
|
||||
return render(request,
|
||||
'projects/project_redirects.html',
|
||||
return render(
|
||||
request, 'projects/project_redirects.html',
|
||||
{'form': form, 'project': project, 'redirects': redirects})
|
||||
|
||||
|
||||
|
@ -599,16 +634,16 @@ def project_redirects_delete(request, project_slug):
|
|||
"""Project redirect delete view."""
|
||||
if request.method != 'POST':
|
||||
return HttpResponseNotAllowed('Only POST is allowed')
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
redirect = get_object_or_404(project.redirects,
|
||||
pk=request.POST.get('id_pk'))
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
redirect = get_object_or_404(
|
||||
project.redirects, pk=request.POST.get('id_pk'))
|
||||
if redirect.project == project:
|
||||
redirect.delete()
|
||||
else:
|
||||
raise Http404
|
||||
return HttpResponseRedirect(reverse('projects_redirects',
|
||||
args=[project.slug]))
|
||||
return HttpResponseRedirect(
|
||||
reverse('projects_redirects', args=[project.slug]))
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -618,9 +653,11 @@ def project_version_delete_html(request, project_slug, version_slug):
|
|||
|
||||
This marks a version as not built
|
||||
"""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.for_admin_user(request.user), slug=project_slug)
|
||||
version = get_object_or_404(
|
||||
Version.objects.public(user=request.user, project=project, only_active=False),
|
||||
Version.objects.public(
|
||||
user=request.user, project=project, only_active=False),
|
||||
slug=version_slug)
|
||||
|
||||
if not version.active:
|
||||
|
@ -628,7 +665,8 @@ def project_version_delete_html(request, project_slug, version_slug):
|
|||
version.save()
|
||||
broadcast(type='app', task=tasks.clear_artifacts, args=[version.pk])
|
||||
else:
|
||||
return HttpResponseBadRequest("Can't delete HTML for an active version.")
|
||||
return HttpResponseBadRequest(
|
||||
"Can't delete HTML for an active version.")
|
||||
return HttpResponseRedirect(
|
||||
reverse('project_version_list', kwargs={'project_slug': project_slug}))
|
||||
|
||||
|
@ -707,7 +745,7 @@ class IntegrationCreate(IntegrationMixin, CreateView):
|
|||
kwargs={
|
||||
'project_slug': self.get_project().slug,
|
||||
'integration_pk': self.object.id,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
@ -726,8 +764,9 @@ class IntegrationDetail(IntegrationMixin, DetailView):
|
|||
return self.template_name
|
||||
integration_type = self.get_integration().integration_type
|
||||
suffix = self.SUFFIX_MAP.get(integration_type, integration_type)
|
||||
return ('projects/integration_{0}{1}.html'
|
||||
.format(suffix, self.template_name_suffix))
|
||||
return (
|
||||
'projects/integration_{0}{1}.html'
|
||||
.format(suffix, self.template_name_suffix))
|
||||
|
||||
|
||||
class IntegrationDelete(IntegrationMixin, DeleteView):
|
||||
|
@ -743,9 +782,7 @@ class IntegrationExchangeDetail(IntegrationMixin, DetailView):
|
|||
template_name = 'projects/integration_exchange_detail.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(
|
||||
integrations=self.get_integration()
|
||||
)
|
||||
return self.model.objects.filter(integrations=self.get_integration())
|
||||
|
||||
def get_object(self):
|
||||
return DetailView.get_object(self)
|
||||
|
|
|
@ -1,38 +1,40 @@
|
|||
"""Public project views."""
|
||||
"""Public project views"""
|
||||
|
||||
from __future__ import (
|
||||
absolute_import, division, print_function, unicode_literals)
|
||||
|
||||
from __future__ import absolute_import
|
||||
from collections import OrderedDict
|
||||
import operator
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
import operator
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.cache import cache
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.staticfiles.templatetags.staticfiles import static
|
||||
from django.http import HttpResponse, HttpResponseRedirect, Http404
|
||||
from django.core.cache import cache
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.generic import ListView, DetailView
|
||||
|
||||
from django.views.generic import DetailView, ListView
|
||||
from taggit.models import Tag
|
||||
import requests
|
||||
|
||||
from .base import ProjectOnboardMixin
|
||||
from readthedocs.builds.constants import LATEST
|
||||
from readthedocs.builds.models import Version
|
||||
from readthedocs.builds.views import BuildTriggerMixin
|
||||
from readthedocs.projects.models import Project, ImportedFile
|
||||
from readthedocs.projects.models import ImportedFile, Project
|
||||
from readthedocs.search.indexes import PageIndex
|
||||
from readthedocs.search.views import LOG_TEMPLATE
|
||||
|
||||
from .base import ProjectOnboardMixin
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
search_log = logging.getLogger(__name__ + '.search')
|
||||
mimetypes.add_type("application/epub+zip", ".epub")
|
||||
mimetypes.add_type('application/epub+zip', '.epub')
|
||||
|
||||
|
||||
class ProjectIndex(ListView):
|
||||
|
@ -51,7 +53,8 @@ class ProjectIndex(ListView):
|
|||
self.tag = None
|
||||
|
||||
if self.kwargs.get('username'):
|
||||
self.user = get_object_or_404(User, username=self.kwargs.get('username'))
|
||||
self.user = get_object_or_404(
|
||||
User, username=self.kwargs.get('username'))
|
||||
queryset = queryset.filter(user=self.user)
|
||||
else:
|
||||
self.user = None
|
||||
|
@ -64,6 +67,7 @@ class ProjectIndex(ListView):
|
|||
context['tag'] = self.tag
|
||||
return context
|
||||
|
||||
|
||||
project_index = ProjectIndex.as_view()
|
||||
|
||||
|
||||
|
@ -90,15 +94,16 @@ class ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView):
|
|||
|
||||
version_slug = project.get_default_version()
|
||||
|
||||
context['badge_url'] = "%s://%s%s?version=%s" % (
|
||||
context['badge_url'] = '%s://%s%s?version=%s' % (
|
||||
protocol,
|
||||
settings.PRODUCTION_DOMAIN,
|
||||
reverse('project_badge', args=[project.slug]),
|
||||
project.get_default_version(),
|
||||
)
|
||||
context['site_url'] = "{url}?badge={version}".format(
|
||||
context['site_url'] = '{url}?badge={version}'.format(
|
||||
url=project.get_docs_url(version_slug),
|
||||
version=version_slug)
|
||||
version=version_slug,
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
|
@ -106,29 +111,31 @@ class ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView):
|
|||
@never_cache
|
||||
def project_badge(request, project_slug):
|
||||
"""Return a sweet badge for the project."""
|
||||
badge_path = "projects/badges/%s.svg"
|
||||
badge_path = 'projects/badges/%s.svg'
|
||||
version_slug = request.GET.get('version', LATEST)
|
||||
try:
|
||||
version = Version.objects.public(request.user).get(
|
||||
project__slug=project_slug, slug=version_slug)
|
||||
except Version.DoesNotExist:
|
||||
url = static(badge_path % "unknown")
|
||||
url = static(badge_path % 'unknown')
|
||||
return HttpResponseRedirect(url)
|
||||
version_builds = version.builds.filter(type='html', state='finished').order_by('-date')
|
||||
version_builds = version.builds.filter(type='html',
|
||||
state='finished').order_by('-date')
|
||||
if not version_builds.exists():
|
||||
url = static(badge_path % "unknown")
|
||||
url = static(badge_path % 'unknown')
|
||||
return HttpResponseRedirect(url)
|
||||
last_build = version_builds[0]
|
||||
if last_build.success:
|
||||
url = static(badge_path % "passing")
|
||||
url = static(badge_path % 'passing')
|
||||
else:
|
||||
url = static(badge_path % "failing")
|
||||
url = static(badge_path % 'failing')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
def project_downloads(request, project_slug):
|
||||
"""A detail view for a project with various dataz."""
|
||||
project = get_object_or_404(Project.objects.protected(request.user), slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.protected(request.user), slug=project_slug)
|
||||
versions = Version.objects.public(user=request.user, project=project)
|
||||
version_data = OrderedDict()
|
||||
for version in versions:
|
||||
|
@ -137,13 +144,15 @@ def project_downloads(request, project_slug):
|
|||
if data:
|
||||
version_data[version] = data
|
||||
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'projects/project_downloads.html',
|
||||
{
|
||||
'project': project,
|
||||
'version_data': version_data,
|
||||
'versions': versions,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def project_download_media(request, project_slug, type_, version_slug):
|
||||
|
@ -163,23 +172,25 @@ def project_download_media(request, project_slug, type_, version_slug):
|
|||
)
|
||||
privacy_level = getattr(settings, 'DEFAULT_PRIVACY_LEVEL', 'public')
|
||||
if privacy_level == 'public' or settings.DEBUG:
|
||||
path = os.path.join(settings.MEDIA_URL, type_, project_slug, version_slug,
|
||||
'%s.%s' % (project_slug, type_.replace('htmlzip', 'zip')))
|
||||
path = os.path.join(
|
||||
settings.MEDIA_URL, type_, project_slug, version_slug,
|
||||
'%s.%s' % (project_slug, type_.replace('htmlzip', 'zip')))
|
||||
return HttpResponseRedirect(path)
|
||||
else:
|
||||
# Get relative media path
|
||||
path = (version.project
|
||||
.get_production_media_path(
|
||||
type_=type_, version_slug=version_slug)
|
||||
.replace(settings.PRODUCTION_ROOT, '/prod_artifacts'))
|
||||
path = (
|
||||
version.project.get_production_media_path(
|
||||
type_=type_, version_slug=version_slug)
|
||||
.replace(settings.PRODUCTION_ROOT, '/prod_artifacts'))
|
||||
content_type, encoding = mimetypes.guess_type(path)
|
||||
content_type = content_type or 'application/octet-stream'
|
||||
response = HttpResponse(content_type=content_type)
|
||||
if encoding:
|
||||
response["Content-Encoding"] = encoding
|
||||
response['Content-Encoding'] = encoding
|
||||
response['X-Accel-Redirect'] = path
|
||||
# Include version in filename; this fixes a long-standing bug
|
||||
filename = "%s-%s.%s" % (project_slug, version_slug, path.split('.')[-1])
|
||||
filename = '%s-%s.%s' % (
|
||||
project_slug, version_slug, path.split('.')[-1])
|
||||
response['Content-Disposition'] = 'filename=%s' % filename
|
||||
return response
|
||||
|
||||
|
@ -190,7 +201,8 @@ def search_autocomplete(request):
|
|||
term = request.GET['term']
|
||||
else:
|
||||
raise Http404
|
||||
queryset = (Project.objects.public(request.user).filter(name__icontains=term)[:20])
|
||||
queryset = Project.objects.public(
|
||||
request.user).filter(name__icontains=term)[:20]
|
||||
|
||||
ret_list = []
|
||||
for project in queryset:
|
||||
|
@ -231,12 +243,14 @@ def version_filter_autocomplete(request, project_slug):
|
|||
json_response = json.dumps(list(names))
|
||||
return HttpResponse(json_response, content_type='text/javascript')
|
||||
elif resp_format == 'html':
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'core/version_list.html',
|
||||
{
|
||||
'project': project,
|
||||
'versions': versions,
|
||||
})
|
||||
},
|
||||
)
|
||||
return HttpResponse(status=400)
|
||||
|
||||
|
||||
|
@ -246,7 +260,8 @@ def file_autocomplete(request, project_slug):
|
|||
term = request.GET['term']
|
||||
else:
|
||||
raise Http404
|
||||
queryset = ImportedFile.objects.filter(project__slug=project_slug, path__icontains=term)[:20]
|
||||
queryset = ImportedFile.objects.filter(
|
||||
project__slug=project_slug, path__icontains=term)[:20]
|
||||
|
||||
ret_list = []
|
||||
for filename in queryset:
|
||||
|
@ -269,43 +284,44 @@ def elastic_project_search(request, project_slug):
|
|||
user = ''
|
||||
if request.user.is_authenticated():
|
||||
user = request.user
|
||||
log.info(LOG_TEMPLATE.format(
|
||||
user=user,
|
||||
project=project or '',
|
||||
type='inproject',
|
||||
version=version_slug or '',
|
||||
language='',
|
||||
msg=query or '',
|
||||
))
|
||||
log.info(
|
||||
LOG_TEMPLATE.format(
|
||||
user=user,
|
||||
project=project or '',
|
||||
type='inproject',
|
||||
version=version_slug or '',
|
||||
language='',
|
||||
msg=query or '',
|
||||
))
|
||||
|
||||
if query:
|
||||
|
||||
kwargs = {}
|
||||
body = {
|
||||
"query": {
|
||||
"bool": {
|
||||
"should": [
|
||||
{"match": {"title": {"query": query, "boost": 10}}},
|
||||
{"match": {"headers": {"query": query, "boost": 5}}},
|
||||
{"match": {"content": {"query": query}}},
|
||||
'query': {
|
||||
'bool': {
|
||||
'should': [
|
||||
{'match': {'title': {'query': query, 'boost': 10}}},
|
||||
{'match': {'headers': {'query': query, 'boost': 5}}},
|
||||
{'match': {'content': {'query': query}}},
|
||||
]
|
||||
}
|
||||
},
|
||||
"highlight": {
|
||||
"fields": {
|
||||
"title": {},
|
||||
"headers": {},
|
||||
"content": {},
|
||||
'highlight': {
|
||||
'fields': {
|
||||
'title': {},
|
||||
'headers': {},
|
||||
'content': {},
|
||||
}
|
||||
},
|
||||
"fields": ["title", "project", "version", "path"],
|
||||
"filter": {
|
||||
"and": [
|
||||
{"term": {"project": project_slug}},
|
||||
{"term": {"version": version_slug}},
|
||||
'fields': ['title', 'project', 'version', 'path'],
|
||||
'filter': {
|
||||
'and': [
|
||||
{'term': {'project': project_slug}},
|
||||
{'term': {'version': version_slug}},
|
||||
]
|
||||
},
|
||||
"size": 50 # TODO: Support pagination.
|
||||
'size': 50, # TODO: Support pagination.
|
||||
}
|
||||
|
||||
# Add routing to optimize search by hitting the right shard.
|
||||
|
@ -322,13 +338,15 @@ def elastic_project_search(request, project_slug):
|
|||
if isinstance(val, list):
|
||||
results['hits']['hits'][num]['fields'][key] = val[0]
|
||||
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'search/elastic_project_search.html',
|
||||
{
|
||||
'project': project,
|
||||
'query': query,
|
||||
'results': results,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def project_versions(request, project_slug):
|
||||
|
@ -337,10 +355,11 @@ def project_versions(request, project_slug):
|
|||
|
||||
Shows the available versions and lets the user choose which ones to build.
|
||||
"""
|
||||
project = get_object_or_404(Project.objects.protected(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.protected(request.user), slug=project_slug)
|
||||
|
||||
versions = Version.objects.public(user=request.user, project=project, only_active=False)
|
||||
versions = Version.objects.public(
|
||||
user=request.user, project=project, only_active=False)
|
||||
active_versions = versions.filter(active=True)
|
||||
inactive_versions = versions.filter(active=False)
|
||||
|
||||
|
@ -352,38 +371,46 @@ def project_versions(request, project_slug):
|
|||
if wiped and wiped_version.count():
|
||||
messages.success(request, 'Version wiped: ' + wiped)
|
||||
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'projects/project_version_list.html',
|
||||
{
|
||||
'inactive_versions': inactive_versions,
|
||||
'active_versions': active_versions,
|
||||
'project': project,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def project_analytics(request, project_slug):
|
||||
"""Have a analytics API placeholder."""
|
||||
project = get_object_or_404(Project.objects.protected(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.protected(request.user), slug=project_slug)
|
||||
analytics_cache = cache.get('analytics:%s' % project_slug)
|
||||
if analytics_cache:
|
||||
analytics = json.loads(analytics_cache)
|
||||
else:
|
||||
try:
|
||||
resp = requests.get(
|
||||
'{host}/api/v1/index/1/heatmap/'.format(host=settings.GROK_API_HOST),
|
||||
params={'project': project.slug, 'days': 7, 'compare': True}
|
||||
)
|
||||
'{host}/api/v1/index/1/heatmap/'.format(
|
||||
host=settings.GROK_API_HOST),
|
||||
params={'project': project.slug, 'days': 7, 'compare': True})
|
||||
analytics = resp.json()
|
||||
cache.set('analytics:%s' % project_slug, resp.content, 1800)
|
||||
except requests.exceptions.RequestException:
|
||||
analytics = None
|
||||
|
||||
if analytics:
|
||||
page_list = list(reversed(sorted(list(analytics['page'].items()),
|
||||
key=operator.itemgetter(1))))
|
||||
version_list = list(reversed(sorted(list(analytics['version'].items()),
|
||||
key=operator.itemgetter(1))))
|
||||
page_list = list(
|
||||
reversed(
|
||||
sorted(
|
||||
list(analytics['page'].items()),
|
||||
key=operator.itemgetter(1))))
|
||||
version_list = list(
|
||||
reversed(
|
||||
sorted(
|
||||
list(analytics['version'].items()),
|
||||
key=operator.itemgetter(1))))
|
||||
else:
|
||||
page_list = []
|
||||
version_list = []
|
||||
|
@ -393,7 +420,8 @@ def project_analytics(request, project_slug):
|
|||
page_list = page_list[:20]
|
||||
version_list = version_list[:20]
|
||||
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'projects/project_analytics.html',
|
||||
{
|
||||
'project': project,
|
||||
|
@ -401,23 +429,26 @@ def project_analytics(request, project_slug):
|
|||
'page_list': page_list,
|
||||
'version_list': version_list,
|
||||
'full': full,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def project_embed(request, project_slug):
|
||||
"""Have a content API placeholder."""
|
||||
project = get_object_or_404(Project.objects.protected(request.user),
|
||||
slug=project_slug)
|
||||
project = get_object_or_404(
|
||||
Project.objects.protected(request.user), slug=project_slug)
|
||||
version = project.versions.get(slug=LATEST)
|
||||
files = version.imported_files.filter(name__endswith='.html').order_by('path')
|
||||
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'projects/project_embed.html',
|
||||
{
|
||||
'project': project,
|
||||
'files': files,
|
||||
'settings': {
|
||||
'PUBLIC_API_URL': settings.PUBLIC_API_URL,
|
||||
'URI': request.build_absolute_uri(location='/').rstrip('/')
|
||||
}
|
||||
})
|
||||
'URI': request.build_absolute_uri(location='/').rstrip('/'),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Search views."""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from pprint import pprint
|
||||
from __future__ import (
|
||||
absolute_import, division, print_function, unicode_literals)
|
||||
|
||||
import collections
|
||||
import logging
|
||||
from pprint import pprint
|
||||
|
||||
from django.conf import settings
|
||||
from django.shortcuts import render
|
||||
|
@ -11,26 +13,33 @@ from django.shortcuts import render
|
|||
from readthedocs.builds.constants import LATEST
|
||||
from readthedocs.search import lib as search_lib
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
LOG_TEMPLATE = u"(Elastic Search) [{user}:{type}] [{project}:{version}:{language}] {msg}"
|
||||
|
||||
LOG_TEMPLATE = u'(Elastic Search) [{user}:{type}] [{project}:{version}:{language}] {msg}'
|
||||
|
||||
UserInput = collections.namedtuple(
|
||||
'UserInput', ('query', 'type', 'project', 'version', 'taxonomy', 'language'))
|
||||
'UserInput',
|
||||
(
|
||||
'query',
|
||||
'type',
|
||||
'project',
|
||||
'version',
|
||||
'taxonomy',
|
||||
'language',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def elastic_search(request):
|
||||
"""Use Elasticsearch for global search"""
|
||||
"""Use Elasticsearch for global search."""
|
||||
user_input = UserInput(
|
||||
query=request.GET.get('q'),
|
||||
type=request.GET.get('type', 'project'),
|
||||
project=request.GET.get('project'),
|
||||
version=request.GET.get('version', LATEST),
|
||||
taxonomy=request.GET.get('taxonomy'),
|
||||
language=request.GET.get('language')
|
||||
language=request.GET.get('language'),
|
||||
)
|
||||
results = ""
|
||||
results = ''
|
||||
|
||||
facets = {}
|
||||
|
||||
|
@ -65,21 +74,23 @@ def elastic_search(request):
|
|||
user = ''
|
||||
if request.user.is_authenticated():
|
||||
user = request.user
|
||||
log.info(LOG_TEMPLATE.format(
|
||||
user=user,
|
||||
project=user_input.project or '',
|
||||
type=user_input.type or '',
|
||||
version=user_input.version or '',
|
||||
language=user_input.language or '',
|
||||
msg=user_input.query or '',
|
||||
))
|
||||
log.info(
|
||||
LOG_TEMPLATE.format(
|
||||
user=user,
|
||||
project=user_input.project or '',
|
||||
type=user_input.type or '',
|
||||
version=user_input.version or '',
|
||||
language=user_input.language or '',
|
||||
msg=user_input.query or '',
|
||||
))
|
||||
|
||||
template_vars = user_input._asdict()
|
||||
template_vars.update({
|
||||
'results': results,
|
||||
'facets': facets,
|
||||
})
|
||||
return render(request,
|
||||
return render(
|
||||
request,
|
||||
'search/elastic_search.html',
|
||||
template_vars,
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue