Fix D213 using docformatter
prospector \ --profile-path=/home/humitos/rtfd/code/readthedocs.org \ --profile=prospector --die-on-tool-error \ | \ grep -e "^[a-z] \ > \ list-files.txt for x in `cat list-files.txt`; do \ docformatter \ --wrap-summaries=80 \ --wrap-descriptions=80 \ --pre-summary-newline \ --no-blank \ --in-place readthedocs/$x\ ; donemore-gsoc
parent
63a96d7d61
commit
4d10f2e287
|
@ -16,7 +16,8 @@ __all__ = ['VersionManager']
|
|||
|
||||
class VersionManagerBase(models.Manager):
|
||||
|
||||
"""Version manager for manager only queries
|
||||
"""
|
||||
Version manager for manager only queries.
|
||||
|
||||
For queries not suitable for the :py:cls:`VersionQuerySet`, such as create
|
||||
queries.
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
"""Classes to copy files between build and web servers
|
||||
"""
|
||||
Classes to copy files between build and web servers.
|
||||
|
||||
"Syncers" copy files from the local machine, while "Pullers" copy files to
|
||||
the local machine.
|
||||
"Syncers" copy files from the local machine, while "Pullers" copy files to the
|
||||
local machine.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Contains logic for handling version slugs.
|
||||
"""
|
||||
Contains logic for handling version slugs.
|
||||
|
||||
Handling slugs for versions is not too straightforward. We need to allow some
|
||||
characters which are uncommon in usual slugs. They are dots and underscores.
|
||||
|
@ -32,8 +33,8 @@ def get_fields_with_model(cls):
|
|||
"""
|
||||
Replace deprecated function of the same name in Model._meta.
|
||||
|
||||
This replaces deprecated function (as of Django 1.10) in
|
||||
Model._meta as prescrived in the Django docs.
|
||||
This replaces deprecated function (as of Django 1.10) in Model._meta as
|
||||
prescrived in the Django docs.
|
||||
https://docs.djangoproject.com/en/1.11/ref/models/meta/#migrating-from-the-old-api
|
||||
"""
|
||||
return [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Django admin interface for core models"""
|
||||
"""Django admin interface for core models."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from datetime import datetime, timedelta
|
||||
|
@ -22,7 +22,7 @@ class UserProjectInline(admin.TabularInline):
|
|||
|
||||
class UserProjectFilter(admin.SimpleListFilter):
|
||||
|
||||
"""Filter users based on project properties"""
|
||||
"""Filter users based on project properties."""
|
||||
|
||||
parameter_name = 'project_state'
|
||||
title = _('user projects')
|
||||
|
@ -39,7 +39,8 @@ class UserProjectFilter(admin.SimpleListFilter):
|
|||
)
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
"""Add filters to queryset filter
|
||||
"""
|
||||
Add filters to queryset filter.
|
||||
|
||||
``PROJECT_ACTIVE`` and ``PROJECT_BUILT`` look for versions on projects,
|
||||
``PROJECT_RECENT`` looks for projects with builds in the last year
|
||||
|
|
|
@ -31,10 +31,11 @@ SINGLE_VERSION_URLCONF = getattr(
|
|||
|
||||
class SubdomainMiddleware(object):
|
||||
|
||||
"""Middleware to display docs for non-dashboard domains"""
|
||||
"""Middleware to display docs for non-dashboard domains."""
|
||||
|
||||
def process_request(self, request):
|
||||
"""Process requests for unhandled domains
|
||||
"""
|
||||
Process requests for unhandled domains.
|
||||
|
||||
If the request is not for our ``PUBLIC_DOMAIN``, or if ``PUBLIC_DOMAIN``
|
||||
is not set and the request is for a subdomain on ``PRODUCTION_DOMAIN``,
|
||||
|
@ -132,22 +133,22 @@ class SubdomainMiddleware(object):
|
|||
|
||||
class SingleVersionMiddleware(object):
|
||||
|
||||
"""Reset urlconf for requests for 'single_version' docs.
|
||||
|
||||
In settings.MIDDLEWARE_CLASSES, SingleVersionMiddleware must follow
|
||||
after SubdomainMiddleware.
|
||||
"""
|
||||
Reset urlconf for requests for 'single_version' docs.
|
||||
|
||||
In settings.MIDDLEWARE_CLASSES, SingleVersionMiddleware must follow after
|
||||
SubdomainMiddleware.
|
||||
"""
|
||||
|
||||
def _get_slug(self, request):
|
||||
"""Get slug from URLs requesting docs.
|
||||
"""
|
||||
Get slug from URLs requesting docs.
|
||||
|
||||
If URL is like '/docs/<project_name>/', we split path
|
||||
and pull out slug.
|
||||
|
||||
If URL is subdomain or CNAME, we simply read request.slug, which is
|
||||
set by SubdomainMiddleware.
|
||||
|
||||
"""
|
||||
slug = None
|
||||
if hasattr(request, 'slug'):
|
||||
|
@ -187,16 +188,16 @@ class SingleVersionMiddleware(object):
|
|||
class ProxyMiddleware(object):
|
||||
|
||||
"""
|
||||
Middleware that sets REMOTE_ADDR based on HTTP_X_FORWARDED_FOR, if the
|
||||
Middleware that sets REMOTE_ADDR based on HTTP_X_FORWARDED_FOR, if the.
|
||||
|
||||
latter is set. This is useful if you're sitting behind a reverse proxy that
|
||||
causes each request's REMOTE_ADDR to be set to 127.0.0.1.
|
||||
Note that this does NOT validate HTTP_X_FORWARDED_FOR. If you're not behind
|
||||
a reverse proxy that sets HTTP_X_FORWARDED_FOR automatically, do not use
|
||||
this middleware. Anybody can spoof the value of HTTP_X_FORWARDED_FOR, and
|
||||
because this sets REMOTE_ADDR based on HTTP_X_FORWARDED_FOR, that means
|
||||
anybody can "fake" their IP address. Only use this when you can absolutely
|
||||
trust the value of HTTP_X_FORWARDED_FOR.
|
||||
causes each request's REMOTE_ADDR to be set to 127.0.0.1. Note that this
|
||||
does NOT validate HTTP_X_FORWARDED_FOR. If you're not behind a reverse proxy
|
||||
that sets HTTP_X_FORWARDED_FOR automatically, do not use this middleware.
|
||||
Anybody can spoof the value of HTTP_X_FORWARDED_FOR, and because this sets
|
||||
REMOTE_ADDR based on HTTP_X_FORWARDED_FOR, that means anybody can "fake"
|
||||
their IP address. Only use this when you can absolutely trust the value of
|
||||
HTTP_X_FORWARDED_FOR.
|
||||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""URL resolver for documentation"""
|
||||
"""URL resolver for documentation."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from builtins import object
|
||||
|
@ -54,7 +54,7 @@ class ResolverBase(object):
|
|||
def base_resolve_path(self, project_slug, filename, version_slug=None,
|
||||
language=None, private=False, single_version=None,
|
||||
subproject_slug=None, subdomain=None, cname=None):
|
||||
"""Resolve a with nothing smart, just filling in the blanks"""
|
||||
"""Resolve a with nothing smart, just filling in the blanks."""
|
||||
# Only support `/docs/project' URLs outside our normal environment. Normally
|
||||
# the path should always have a subdomain or CNAME domain
|
||||
# pylint: disable=unused-argument
|
||||
|
@ -80,7 +80,7 @@ class ResolverBase(object):
|
|||
def resolve_path(self, project, filename='', version_slug=None,
|
||||
language=None, single_version=None, subdomain=None,
|
||||
cname=None, private=None):
|
||||
"""Resolve a URL with a subset of fields defined"""
|
||||
"""Resolve a URL with a subset of fields defined."""
|
||||
relation = project.superprojects.first()
|
||||
cname = cname or project.domains.filter(canonical=True).first()
|
||||
main_language_project = project.main_language_project
|
||||
|
@ -145,7 +145,8 @@ class ResolverBase(object):
|
|||
)
|
||||
|
||||
def _get_canonical_project(self, project):
|
||||
"""Get canonical project in the case of subproject or translations
|
||||
"""
|
||||
Get canonical project in the case of subproject or translations.
|
||||
|
||||
:type project: Project
|
||||
:rtype: Project
|
||||
|
@ -159,7 +160,7 @@ class ResolverBase(object):
|
|||
return project
|
||||
|
||||
def _get_project_subdomain(self, project):
|
||||
"""Determine canonical project domain as subdomain"""
|
||||
"""Determine canonical project domain as subdomain."""
|
||||
public_domain = getattr(settings, 'PUBLIC_DOMAIN', None)
|
||||
if self._use_subdomain():
|
||||
project = self._get_canonical_project(project)
|
||||
|
@ -177,9 +178,10 @@ class ResolverBase(object):
|
|||
|
||||
def _fix_filename(self, project, filename):
|
||||
"""
|
||||
Force filenames that might be HTML file paths into proper URL's
|
||||
Force filenames that might be HTML file paths into proper URL's.
|
||||
|
||||
This basically means stripping / and .html endings and then re-adding them properly.
|
||||
This basically means stripping / and .html endings and then re-adding
|
||||
them properly.
|
||||
"""
|
||||
# Bail out on non-html files
|
||||
if '.' in filename and '.html' not in filename:
|
||||
|
@ -203,7 +205,7 @@ class ResolverBase(object):
|
|||
return path
|
||||
|
||||
def _use_subdomain(self):
|
||||
"""Make decision about whether to use a subdomain to serve docs"""
|
||||
"""Make decision about whether to use a subdomain to serve docs."""
|
||||
use_subdomain = getattr(settings, 'USE_SUBDOMAIN', False)
|
||||
public_domain = getattr(settings, 'PUBLIC_DOMAIN', None)
|
||||
return use_subdomain and public_domain is not None
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Class based settings for complex settings inheritance"""
|
||||
"""Class based settings for complex settings inheritance."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from builtins import object
|
||||
|
@ -8,11 +8,12 @@ import sys
|
|||
|
||||
class Settings(object):
|
||||
|
||||
"""Class-based settings wrapper"""
|
||||
"""Class-based settings wrapper."""
|
||||
|
||||
@classmethod
|
||||
def load_settings(cls, module_name):
|
||||
"""Export class variables and properties to module namespace
|
||||
"""
|
||||
Export class variables and properties to module namespace.
|
||||
|
||||
This will export and class variable that is all upper case and doesn't
|
||||
begin with ``_``. These members will be set as attributes on the module
|
||||
|
|
|
@ -50,7 +50,6 @@ Example layout
|
|||
ja/
|
||||
|
||||
fabric -> rtd-builds/fabric/en/latest/ # single version
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
@ -121,9 +120,8 @@ class Symlink(object):
|
|||
"""
|
||||
Create proper symlinks in the right order.
|
||||
|
||||
Since we have a small nest of directories and symlinks,
|
||||
the ordering of these calls matter,
|
||||
so we provide this helper to make life easier.
|
||||
Since we have a small nest of directories and symlinks, the ordering of
|
||||
these calls matter, so we provide this helper to make life easier.
|
||||
"""
|
||||
# Outside of the web root
|
||||
self.symlink_cnames()
|
||||
|
@ -138,7 +136,8 @@ class Symlink(object):
|
|||
self.symlink_versions()
|
||||
|
||||
def symlink_cnames(self, domain=None):
|
||||
"""Symlink project CNAME domains
|
||||
"""
|
||||
Symlink project CNAME domains.
|
||||
|
||||
Link from HOME/$CNAME_ROOT/<cname> ->
|
||||
HOME/$WEB_ROOT/<project>
|
||||
|
@ -164,13 +163,14 @@ class Symlink(object):
|
|||
run(['ln', '-nsf', self.project.doc_path, project_cname_symlink])
|
||||
|
||||
def remove_symlink_cname(self, domain):
|
||||
"""Remove CNAME symlink"""
|
||||
"""Remove CNAME symlink."""
|
||||
self._log(u"Removing symlink for CNAME {0}".format(domain.domain))
|
||||
symlink = os.path.join(self.CNAME_ROOT, domain.domain)
|
||||
os.unlink(symlink)
|
||||
|
||||
def symlink_subprojects(self):
|
||||
"""Symlink project subprojects
|
||||
"""
|
||||
Symlink project subprojects.
|
||||
|
||||
Link from $WEB_ROOT/projects/<project> ->
|
||||
$WEB_ROOT/<project>
|
||||
|
@ -213,7 +213,8 @@ class Symlink(object):
|
|||
os.unlink(os.path.join(self.subproject_root, subproj))
|
||||
|
||||
def symlink_translations(self):
|
||||
"""Symlink project translations
|
||||
"""
|
||||
Symlink project translations.
|
||||
|
||||
Link from $WEB_ROOT/<project>/<language>/ ->
|
||||
$WEB_ROOT/<translation>/<language>/
|
||||
|
@ -247,7 +248,8 @@ class Symlink(object):
|
|||
shutil.rmtree(to_delete)
|
||||
|
||||
def symlink_single_version(self):
|
||||
"""Symlink project single version
|
||||
"""
|
||||
Symlink project single version.
|
||||
|
||||
Link from $WEB_ROOT/<project> ->
|
||||
HOME/user_builds/<project>/rtd-builds/latest/
|
||||
|
@ -268,7 +270,8 @@ class Symlink(object):
|
|||
run(['ln', '-nsf', docs_dir, symlink])
|
||||
|
||||
def symlink_versions(self):
|
||||
"""Symlink project's versions
|
||||
"""
|
||||
Symlink project's versions.
|
||||
|
||||
Link from $WEB_ROOT/<project>/<language>/<version>/ ->
|
||||
HOME/user_builds/<project>/rtd-builds/<version>
|
||||
|
@ -295,7 +298,7 @@ class Symlink(object):
|
|||
os.unlink(os.path.join(version_dir, old_ver))
|
||||
|
||||
def get_default_version(self):
|
||||
"""Look up project default version, return None if not found"""
|
||||
"""Look up project default version, return None if not found."""
|
||||
default_version = self.project.get_default_version()
|
||||
try:
|
||||
return self.get_version_queryset().get(slug=default_version)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Basic tasks"""
|
||||
"""Basic tasks."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
|
@ -19,7 +19,8 @@ EMAIL_TIME_LIMIT = 30
|
|||
@app.task(queue='web', time_limit=EMAIL_TIME_LIMIT)
|
||||
def send_email_task(recipient, subject, template, template_html,
|
||||
context=None, from_email=None, **kwargs):
|
||||
"""Send multipart email
|
||||
"""
|
||||
Send multipart email.
|
||||
|
||||
recipient
|
||||
Email recipient address
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Common utilty functions"""
|
||||
"""Common utilty functions."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
@ -77,7 +77,8 @@ def cname_to_slug(host):
|
|||
|
||||
|
||||
def trigger_build(project, version=None, record=True, force=False, basic=False):
|
||||
"""Trigger build for project and version
|
||||
"""
|
||||
Trigger build for project and version.
|
||||
|
||||
If project has a ``build_queue``, execute task on this build queue. Queue
|
||||
will be prefixed with ``build-`` to unify build queue names.
|
||||
|
@ -135,7 +136,8 @@ def trigger_build(project, version=None, record=True, force=False, basic=False):
|
|||
|
||||
def send_email(recipient, subject, template, template_html, context=None,
|
||||
request=None, from_email=None, **kwargs): # pylint: disable=unused-argument
|
||||
"""Alter context passed in and call email send task
|
||||
"""
|
||||
Alter context passed in and call email send task.
|
||||
|
||||
.. seealso::
|
||||
|
||||
|
@ -152,7 +154,8 @@ def send_email(recipient, subject, template, template_html, context=None,
|
|||
|
||||
|
||||
def slugify(value, *args, **kwargs):
|
||||
"""Add a DNS safe option to slugify
|
||||
"""
|
||||
Add a DNS safe option to slugify.
|
||||
|
||||
:param dns_safe: Remove underscores from slug as well
|
||||
"""
|
||||
|
@ -170,9 +173,9 @@ def safe_makedirs(directory_name):
|
|||
"""
|
||||
Safely create a directory.
|
||||
|
||||
Makedirs has an issue where it has a race condition around
|
||||
checking for a directory and then creating it.
|
||||
This catches the exception in the case where the dir already exists.
|
||||
Makedirs has an issue where it has a race condition around checking for a
|
||||
directory and then creating it. This catches the exception in the case where
|
||||
the dir already exists.
|
||||
"""
|
||||
try:
|
||||
os.makedirs(directory_name)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Patterns for extending Read the Docs"""
|
||||
"""Patterns for extending Read the Docs."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import inspect
|
||||
|
@ -9,7 +9,8 @@ import six
|
|||
|
||||
|
||||
def get_override_class(proxy_class, default_class=None):
|
||||
"""Determine which class to use in an override class
|
||||
"""
|
||||
Determine which class to use in an override class.
|
||||
|
||||
The `proxy_class` is the main class that is used, and `default_class` is the
|
||||
default class that this proxy class will instantiate. If `default_class` is
|
||||
|
@ -33,7 +34,8 @@ def get_override_class(proxy_class, default_class=None):
|
|||
|
||||
class SettingsOverrideMeta(type):
|
||||
|
||||
"""Meta class for passing along classmethod class to the underlying class"""
|
||||
"""Meta class for passing along classmethod class to the underlying
|
||||
class."""
|
||||
|
||||
def __getattr__(cls, attr): # noqa: pep8 false positive
|
||||
proxy_class = get_override_class(cls, getattr(cls, '_default_class'))
|
||||
|
@ -42,7 +44,8 @@ class SettingsOverrideMeta(type):
|
|||
|
||||
class SettingsOverrideObject(six.with_metaclass(SettingsOverrideMeta, object)):
|
||||
|
||||
"""Base class for creating class that can be overridden
|
||||
"""
|
||||
Base class for creating class that can be overridden.
|
||||
|
||||
This is used for extension points in the code, where we want to extend a
|
||||
class without monkey patching it. This class will proxy classmethod calls
|
||||
|
@ -68,7 +71,8 @@ class SettingsOverrideObject(six.with_metaclass(SettingsOverrideMeta, object)):
|
|||
_override_setting = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""Set up wrapped object
|
||||
"""
|
||||
Set up wrapped object.
|
||||
|
||||
Create an instance of the underlying target class and return instead of
|
||||
this class.
|
||||
|
|
|
@ -107,7 +107,8 @@ def _build_url(url, projects, branches):
|
|||
"""
|
||||
Map a URL onto specific projects to build that are linked to that URL.
|
||||
|
||||
Check each of the ``branches`` to see if they are active and should be built.
|
||||
Check each of the ``branches`` to see if they are active and should be
|
||||
built.
|
||||
"""
|
||||
ret = ""
|
||||
all_built = {}
|
||||
|
@ -152,7 +153,7 @@ def _build_url(url, projects, branches):
|
|||
@csrf_exempt
|
||||
def github_build(request): # noqa: D205
|
||||
"""
|
||||
GitHub webhook consumer
|
||||
GitHub webhook consumer.
|
||||
|
||||
.. warning:: **DEPRECATED**
|
||||
Use :py:cls:`readthedocs.restapi.views.integrations.GitHubWebhookView`
|
||||
|
@ -206,7 +207,8 @@ def github_build(request): # noqa: D205
|
|||
|
||||
@csrf_exempt
|
||||
def gitlab_build(request): # noqa: D205
|
||||
"""GitLab webhook consumer
|
||||
"""
|
||||
GitLab webhook consumer.
|
||||
|
||||
.. warning:: **DEPRECATED**
|
||||
Use :py:cls:`readthedocs.restapi.views.integrations.GitLabWebhookView`
|
||||
|
@ -239,7 +241,8 @@ def gitlab_build(request): # noqa: D205
|
|||
|
||||
@csrf_exempt
|
||||
def bitbucket_build(request):
|
||||
"""Consume webhooks from multiple versions of Bitbucket's API
|
||||
"""
|
||||
Consume webhooks from multiple versions of Bitbucket's API.
|
||||
|
||||
.. warning:: **DEPRECATED**
|
||||
Use :py:cls:`readthedocs.restapi.views.integrations.BitbucketWebhookView`
|
||||
|
@ -307,11 +310,13 @@ def bitbucket_build(request):
|
|||
|
||||
@csrf_exempt
|
||||
def generic_build(request, project_id_or_slug=None):
|
||||
"""Generic webhook build endpoint
|
||||
"""
|
||||
Generic webhook build endpoint.
|
||||
|
||||
.. warning:: **DEPRECATED**
|
||||
Use :py:cls:`readthedocs.restapi.views.integrations.GenericWebhookView`
|
||||
instead of this view function
|
||||
|
||||
Use :py:cls:`readthedocs.restapi.views.integrations.GenericWebhookView`
|
||||
instead of this view function
|
||||
"""
|
||||
try:
|
||||
project = Project.objects.get(pk=project_id_or_slug)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""MkDocs_ backend for building docs.
|
||||
"""
|
||||
MkDocs_ backend for building docs.
|
||||
|
||||
.. _MkDocs: http://www.mkdocs.org/
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import os
|
||||
|
@ -22,10 +22,10 @@ OVERRIDE_TEMPLATE_DIR = '%s/readthedocs/templates/mkdocs/overrides' % settings.S
|
|||
|
||||
|
||||
def get_absolute_media_url():
|
||||
"""Get the fully qualified media URL from settings.
|
||||
"""
|
||||
Get the fully qualified media URL from settings.
|
||||
|
||||
Mkdocs needs a full domain because it tries to link to local media files.
|
||||
|
||||
"""
|
||||
media_url = settings.MEDIA_URL
|
||||
|
||||
|
@ -38,7 +38,7 @@ def get_absolute_media_url():
|
|||
|
||||
class BaseMkdocs(BaseBuilder):
|
||||
|
||||
"""Mkdocs builder"""
|
||||
"""Mkdocs builder."""
|
||||
|
||||
use_theme = True
|
||||
|
||||
|
@ -50,10 +50,10 @@ class BaseMkdocs(BaseBuilder):
|
|||
self.root_path = self.version.project.checkout_path(self.version.slug)
|
||||
|
||||
def load_yaml_config(self):
|
||||
"""Load a YAML config.
|
||||
"""
|
||||
Load a YAML config.
|
||||
|
||||
Raise BuildEnvironmentError if failed due to syntax errors.
|
||||
|
||||
"""
|
||||
try:
|
||||
return yaml.safe_load(
|
||||
|
@ -74,7 +74,7 @@ class BaseMkdocs(BaseBuilder):
|
|||
note,))
|
||||
|
||||
def append_conf(self, **__):
|
||||
"""Set mkdocs config values"""
|
||||
"""Set mkdocs config values."""
|
||||
# Pull mkdocs config data
|
||||
user_config = self.load_yaml_config()
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Documentation Builder Environments"""
|
||||
"""Documentation Builder Environments."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from builtins import str
|
||||
|
@ -45,7 +45,8 @@ __all__ = (
|
|||
|
||||
class BuildCommand(BuildCommandResultMixin):
|
||||
|
||||
"""Wrap command execution for execution in build environments
|
||||
"""
|
||||
Wrap command execution for execution in build environments.
|
||||
|
||||
This wraps subprocess commands with some logic to handle exceptions,
|
||||
logging, and setting up the env for the build command.
|
||||
|
@ -101,7 +102,8 @@ class BuildCommand(BuildCommandResultMixin):
|
|||
return '\n'.join([self.get_command(), output])
|
||||
|
||||
def run(self):
|
||||
"""Set up subprocess and execute command
|
||||
"""
|
||||
Set up subprocess and execute command.
|
||||
|
||||
:param cmd_input: input to pass to command in STDIN
|
||||
:type cmd_input: str
|
||||
|
@ -170,13 +172,13 @@ class BuildCommand(BuildCommandResultMixin):
|
|||
self.end_time = datetime.utcnow()
|
||||
|
||||
def get_command(self):
|
||||
"""Flatten command"""
|
||||
"""Flatten command."""
|
||||
if hasattr(self.command, '__iter__') and not isinstance(self.command, str):
|
||||
return ' '.join(self.command)
|
||||
return self.command
|
||||
|
||||
def save(self):
|
||||
"""Save this command and result via the API"""
|
||||
"""Save this command and result via the API."""
|
||||
data = {
|
||||
'build': self.build_env.build.get('id'),
|
||||
'command': self.get_command(),
|
||||
|
@ -191,13 +193,15 @@ class BuildCommand(BuildCommandResultMixin):
|
|||
|
||||
class DockerBuildCommand(BuildCommand):
|
||||
|
||||
"""Create a docker container and run a command inside the container
|
||||
"""
|
||||
Create a docker container and run a command inside the container.
|
||||
|
||||
Build command to execute in docker container
|
||||
"""
|
||||
|
||||
def run(self):
|
||||
"""Execute command in existing Docker container
|
||||
"""
|
||||
Execute command in existing Docker container.
|
||||
|
||||
:param cmd_input: input to pass to command in STDIN
|
||||
:type cmd_input: str
|
||||
|
@ -241,7 +245,8 @@ class DockerBuildCommand(BuildCommand):
|
|||
self.end_time = datetime.utcnow()
|
||||
|
||||
def get_wrapped_command(self):
|
||||
"""Escape special bash characters in command to wrap in shell
|
||||
"""
|
||||
Escape special bash characters in command to wrap in shell.
|
||||
|
||||
In order to set the current working path inside a docker container, we
|
||||
need to wrap the command in a shell call manually. Some characters will
|
||||
|
@ -264,7 +269,8 @@ class DockerBuildCommand(BuildCommand):
|
|||
|
||||
class BuildEnvironment(object):
|
||||
|
||||
"""Base build environment
|
||||
"""
|
||||
Base build environment.
|
||||
|
||||
Base class for wrapping command execution for build steps. This provides a
|
||||
context for command execution and reporting, and eventually performs updates
|
||||
|
@ -320,7 +326,8 @@ class BuildEnvironment(object):
|
|||
return ret
|
||||
|
||||
def handle_exception(self, exc_type, exc_value, _):
|
||||
"""Exception handling for __enter__ and __exit__
|
||||
"""
|
||||
Exception handling for __enter__ and __exit__
|
||||
|
||||
This reports on the exception we're handling and special cases
|
||||
subclasses of BuildEnvironmentException. For
|
||||
|
@ -340,11 +347,12 @@ class BuildEnvironment(object):
|
|||
return True
|
||||
|
||||
def run(self, *cmd, **kwargs):
|
||||
"""Shortcut to run command from environment"""
|
||||
"""Shortcut to run command from environment."""
|
||||
return self.run_command_class(cls=self.command_class, cmd=cmd, **kwargs)
|
||||
|
||||
def run_command_class(self, cls, cmd, **kwargs):
|
||||
"""Run command from this environment
|
||||
"""
|
||||
Run command from this environment.
|
||||
|
||||
Use ``cls`` to instantiate a command
|
||||
|
||||
|
@ -383,13 +391,14 @@ class BuildEnvironment(object):
|
|||
|
||||
@property
|
||||
def successful(self):
|
||||
"""Is build completed, without top level failures or failing commands"""
|
||||
"""Is build completed, without top level failures or failing
|
||||
commands."""
|
||||
return (self.done and self.failure is None and
|
||||
all(cmd.successful for cmd in self.commands))
|
||||
|
||||
@property
|
||||
def failed(self):
|
||||
"""Is build completed, but has top level failure or failing commands"""
|
||||
"""Is build completed, but has top level failure or failing commands."""
|
||||
return (self.done and (
|
||||
self.failure is not None or
|
||||
any(cmd.failed for cmd in self.commands)
|
||||
|
@ -397,12 +406,13 @@ class BuildEnvironment(object):
|
|||
|
||||
@property
|
||||
def done(self):
|
||||
"""Is build in finished state"""
|
||||
"""Is build in finished state."""
|
||||
return (self.build is not None and
|
||||
self.build['state'] == BUILD_STATE_FINISHED)
|
||||
|
||||
def update_build(self, state=None):
|
||||
"""Record a build by hitting the API
|
||||
"""
|
||||
Record a build by hitting the API.
|
||||
|
||||
This step is skipped if we aren't recording the build. To avoid
|
||||
recording successful builds yet (for instance, running setup commands
|
||||
|
@ -488,7 +498,7 @@ class BuildEnvironment(object):
|
|||
|
||||
class LocalEnvironment(BuildEnvironment):
|
||||
|
||||
"""Local execution environment"""
|
||||
"""Local execution environment."""
|
||||
|
||||
command_class = BuildCommand
|
||||
|
||||
|
@ -496,7 +506,7 @@ class LocalEnvironment(BuildEnvironment):
|
|||
class DockerEnvironment(BuildEnvironment):
|
||||
|
||||
"""
|
||||
Docker build environment, uses docker to contain builds
|
||||
Docker build environment, uses docker to contain builds.
|
||||
|
||||
If :py:data:`settings.DOCKER_ENABLE` is true, build documentation inside a
|
||||
docker container, instead of the host system, using this build environment
|
||||
|
@ -532,7 +542,7 @@ class DockerEnvironment(BuildEnvironment):
|
|||
self.container_time_limit = self.project.container_time_limit
|
||||
|
||||
def __enter__(self):
|
||||
"""Start of environment context"""
|
||||
"""Start of environment context."""
|
||||
log.info('Creating container')
|
||||
try:
|
||||
# Test for existing container. We remove any stale containers that
|
||||
|
@ -579,7 +589,7 @@ class DockerEnvironment(BuildEnvironment):
|
|||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
"""End of environment context"""
|
||||
"""End of environment context."""
|
||||
try:
|
||||
# Update buildenv state given any container error states first
|
||||
self.update_build_from_container_state()
|
||||
|
@ -624,7 +634,7 @@ class DockerEnvironment(BuildEnvironment):
|
|||
return ret
|
||||
|
||||
def get_client(self):
|
||||
"""Create Docker client connection"""
|
||||
"""Create Docker client connection."""
|
||||
try:
|
||||
if self.client is None:
|
||||
self.client = Client(
|
||||
|
@ -652,14 +662,14 @@ class DockerEnvironment(BuildEnvironment):
|
|||
|
||||
@property
|
||||
def container_id(self):
|
||||
"""Return id of container if it is valid"""
|
||||
"""Return id of container if it is valid."""
|
||||
if self.container_name:
|
||||
return self.container_name
|
||||
elif self.container:
|
||||
return self.container.get('Id')
|
||||
|
||||
def container_state(self):
|
||||
"""Get container state"""
|
||||
"""Get container state."""
|
||||
client = self.get_client()
|
||||
try:
|
||||
info = client.inspect_container(self.container_id)
|
||||
|
@ -668,7 +678,8 @@ class DockerEnvironment(BuildEnvironment):
|
|||
return None
|
||||
|
||||
def update_build_from_container_state(self):
|
||||
"""Update buildenv state from container state
|
||||
"""
|
||||
Update buildenv state from container state.
|
||||
|
||||
In the case of the parent command exiting before the exec commands
|
||||
finish and the container is destroyed, or in the case of OOM on the
|
||||
|
@ -689,7 +700,7 @@ class DockerEnvironment(BuildEnvironment):
|
|||
.format(state.get('Error'))))
|
||||
|
||||
def create_container(self):
|
||||
"""Create docker container"""
|
||||
"""Create docker container."""
|
||||
client = self.get_client()
|
||||
image = self.container_image
|
||||
if self.project.container_image:
|
||||
|
|
|
@ -76,7 +76,8 @@ class PythonEnvironment(object):
|
|||
)
|
||||
|
||||
def venv_bin(self, filename=None):
|
||||
"""Return path to the virtualenv bin path, or a specific binary
|
||||
"""
|
||||
Return path to the virtualenv bin path, or a specific binary.
|
||||
|
||||
:param filename: If specified, add this filename to the path return
|
||||
:returns: Path to virtualenv bin or filename in virtualenv bin
|
||||
|
@ -89,10 +90,10 @@ class PythonEnvironment(object):
|
|||
|
||||
class Virtualenv(PythonEnvironment):
|
||||
|
||||
"""A virtualenv_ environment.
|
||||
"""
|
||||
A virtualenv_ environment.
|
||||
|
||||
.. _virtualenv: https://virtualenv.pypa.io/
|
||||
|
||||
"""
|
||||
|
||||
def venv_path(self):
|
||||
|
@ -203,10 +204,10 @@ class Virtualenv(PythonEnvironment):
|
|||
|
||||
class Conda(PythonEnvironment):
|
||||
|
||||
"""A Conda_ environment.
|
||||
"""
|
||||
A Conda_ environment.
|
||||
|
||||
.. _Conda: https://conda.io/docs/
|
||||
|
||||
"""
|
||||
|
||||
def venv_path(self):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""A Django app for Gold Membership.
|
||||
"""
|
||||
A Django app for Gold Membership.
|
||||
|
||||
Gold Membership is Read the Docs' program for recurring, monthly donations.
|
||||
|
||||
"""
|
||||
default_app_config = 'readthedocs.gold.apps.GoldAppConfig'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Gold subscription forms"""
|
||||
"""Gold subscription forms."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from builtins import object
|
||||
|
@ -12,7 +12,8 @@ from .models import LEVEL_CHOICES, GoldUser
|
|||
|
||||
class GoldSubscriptionForm(StripeResourceMixin, StripeModelForm):
|
||||
|
||||
"""Gold subscription payment form
|
||||
"""
|
||||
Gold subscription payment form.
|
||||
|
||||
This extends the common base form for handling Stripe subscriptions. Credit
|
||||
card fields for card number, expiry, and CVV are extended from
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Gold subscription views"""
|
||||
"""Gold subscription views."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
|
@ -22,7 +22,7 @@ from .models import GoldUser
|
|||
|
||||
class GoldSubscriptionMixin(SuccessMessageMixin, StripeMixin, LoginRequiredMixin):
|
||||
|
||||
"""Gold subscription mixin for view classes"""
|
||||
"""Gold subscription mixin for view classes."""
|
||||
|
||||
model = GoldUser
|
||||
form_class = GoldSubscriptionForm
|
||||
|
@ -34,7 +34,7 @@ class GoldSubscriptionMixin(SuccessMessageMixin, StripeMixin, LoginRequiredMixin
|
|||
return None
|
||||
|
||||
def get_form(self, data=None, files=None, **kwargs):
|
||||
"""Pass in copy of POST data to avoid read only QueryDicts"""
|
||||
"""Pass in copy of POST data to avoid read only QueryDicts."""
|
||||
kwargs['customer'] = self.request.user
|
||||
return super(GoldSubscriptionMixin, self).get_form(data, files, **kwargs)
|
||||
|
||||
|
@ -57,7 +57,8 @@ class GoldSubscriptionMixin(SuccessMessageMixin, StripeMixin, LoginRequiredMixin
|
|||
class DetailGoldSubscription(GoldSubscriptionMixin, DetailView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""GET handling for this view
|
||||
"""
|
||||
GET handling for this view.
|
||||
|
||||
If there is a gold subscription instance, then we show the normal detail
|
||||
page, otherwise show the registration form
|
||||
|
@ -74,7 +75,8 @@ class UpdateGoldSubscription(GoldSubscriptionMixin, UpdateView):
|
|||
|
||||
class DeleteGoldSubscription(GoldSubscriptionMixin, DeleteView):
|
||||
|
||||
"""Delete Gold subscription view
|
||||
"""
|
||||
Delete Gold subscription view.
|
||||
|
||||
On object deletion, the corresponding Stripe customer is deleted as well.
|
||||
Deletion is triggered on subscription deletion using a signal, ensuring the
|
||||
|
@ -84,7 +86,7 @@ class DeleteGoldSubscription(GoldSubscriptionMixin, DeleteView):
|
|||
success_message = _('Your subscription has been cancelled')
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Add success message to delete post"""
|
||||
"""Add success message to delete post."""
|
||||
resp = super(DeleteGoldSubscription, self).post(request, *args, **kwargs)
|
||||
success_message = self.get_success_message({})
|
||||
if success_message:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Integration admin models"""
|
||||
"""Integration admin models."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from django.contrib import admin
|
||||
|
@ -30,7 +30,8 @@ def pretty_json_field(field, description, include_styles=False):
|
|||
|
||||
class HttpExchangeAdmin(admin.ModelAdmin):
|
||||
|
||||
"""Admin model for HttpExchange
|
||||
"""
|
||||
Admin model for HttpExchange.
|
||||
|
||||
This adds some read-only display to the admin model.
|
||||
"""
|
||||
|
@ -78,7 +79,8 @@ class HttpExchangeAdmin(admin.ModelAdmin):
|
|||
|
||||
class IntegrationAdmin(admin.ModelAdmin):
|
||||
|
||||
"""Admin model for Integration
|
||||
"""
|
||||
Admin model for Integration.
|
||||
|
||||
Because of some problems using JSONField with admin model inlines, this
|
||||
instead just links to the queryset.
|
||||
|
@ -88,7 +90,8 @@ class IntegrationAdmin(admin.ModelAdmin):
|
|||
readonly_fields = ['exchanges']
|
||||
|
||||
def exchanges(self, obj):
|
||||
"""Manually make an inline-ish block
|
||||
"""
|
||||
Manually make an inline-ish block.
|
||||
|
||||
JSONField doesn't do well with fieldsets for whatever reason. This is
|
||||
just to link to the exchanges.
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
"""Integration utility functions"""
|
||||
"""Integration utility functions."""
|
||||
|
||||
|
||||
def normalize_request_payload(request):
|
||||
"""Normalize the request body, hopefully to JSON
|
||||
"""
|
||||
Normalize the request body, hopefully to JSON.
|
||||
|
||||
This will attempt to return a JSON body, backing down to a string body next.
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Extensions to Django messages to support notifications to users.
|
||||
"""
|
||||
Extensions to Django messages to support notifications to users.
|
||||
|
||||
Notifications are important communications to users that need to be as visible
|
||||
as possible. We support different backends to make notifications visible in
|
||||
|
@ -10,7 +11,6 @@ on the site.
|
|||
|
||||
.. _`django-messages-extends`: https://github.com
|
||||
/AliLozano/django-messages-extends/
|
||||
|
||||
"""
|
||||
from .notification import Notification
|
||||
from .backends import send_notification
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
"""Pluggable backends for the delivery of notifications.
|
||||
"""
|
||||
Pluggable backends for the delivery of notifications.
|
||||
|
||||
Delivery of notifications to users depends on a list of backends configured in
|
||||
Django settings. For example, they might be e-mailed to users as well as
|
||||
displayed on the site.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
@ -19,7 +19,8 @@ from .constants import LEVEL_MAPPING, REQUIREMENT, HTML
|
|||
|
||||
|
||||
def send_notification(request, notification):
|
||||
"""Send notifications through all backends defined by settings
|
||||
"""
|
||||
Send notifications through all backends defined by settings.
|
||||
|
||||
Backends should be listed in the settings ``NOTIFICATION_BACKENDS``, which
|
||||
should be a list of class paths to be loaded, using the standard Django
|
||||
|
@ -42,7 +43,8 @@ class Backend(object):
|
|||
|
||||
class EmailBackend(Backend):
|
||||
|
||||
"""Send templated notification emails through our standard email backend
|
||||
"""
|
||||
Send templated notification emails through our standard email backend.
|
||||
|
||||
The content body is first rendered from an on-disk template, then passed
|
||||
into the standard email templates as a string.
|
||||
|
@ -66,7 +68,8 @@ class EmailBackend(Backend):
|
|||
|
||||
class SiteBackend(Backend):
|
||||
|
||||
"""Add messages through Django messages application
|
||||
"""
|
||||
Add messages through Django messages application.
|
||||
|
||||
This uses persistent messageing levels provided by :py:mod:`message_extends`
|
||||
and stores persistent messages in the database.
|
||||
|
|
|
@ -6,7 +6,8 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
class SendNotificationForm(forms.Form):
|
||||
|
||||
"""Send notification form
|
||||
"""
|
||||
Send notification form.
|
||||
|
||||
Used for sending a notification to a list of users from admin pages
|
||||
|
||||
|
@ -33,7 +34,7 @@ class SendNotificationForm(forms.Form):
|
|||
in self.notification_classes]
|
||||
|
||||
def clean_source(self):
|
||||
"""Get the source class from the class name"""
|
||||
"""Get the source class from the class name."""
|
||||
source = self.cleaned_data['source']
|
||||
classes = dict((cls.name, cls) for cls in self.notification_classes)
|
||||
return classes.get(source, None)
|
||||
|
|
|
@ -13,14 +13,14 @@ from . import constants
|
|||
|
||||
class Notification(object):
|
||||
|
||||
"""An unsent notification linked to an object.
|
||||
"""
|
||||
An unsent notification linked to an object.
|
||||
|
||||
This class provides an interface to construct notification messages by
|
||||
rendering Django templates. The ``Notification`` itself is not expected
|
||||
to be persisted by the backends.
|
||||
|
||||
Call .send() to send the notification.
|
||||
|
||||
"""
|
||||
|
||||
name = None
|
||||
|
@ -75,7 +75,8 @@ class Notification(object):
|
|||
)
|
||||
|
||||
def send(self):
|
||||
"""Trigger notification send through all notification backends
|
||||
"""
|
||||
Trigger notification send through all notification backends.
|
||||
|
||||
In order to limit which backends a notification will send out from,
|
||||
override this method and duplicate the logic from
|
||||
|
|
|
@ -10,7 +10,8 @@ from messages_extends.constants import PERSISTENT_MESSAGE_LEVELS
|
|||
|
||||
class FallbackUniqueStorage(FallbackStorage):
|
||||
|
||||
"""Persistent message fallback storage, but only stores unique notifications
|
||||
"""
|
||||
Persistent message fallback storage, but only stores unique notifications.
|
||||
|
||||
This loops through all backends to find messages to store, but will skip
|
||||
this step if the message already exists for the user in the database.
|
||||
|
|
|
@ -9,7 +9,8 @@ from .forms import SendNotificationForm
|
|||
|
||||
class SendNotificationView(FormView):
|
||||
|
||||
"""Form view for sending notifications to users from admin pages
|
||||
"""
|
||||
Form view for sending notifications to users from admin pages.
|
||||
|
||||
Accepts the following additional parameters:
|
||||
|
||||
|
@ -28,7 +29,8 @@ class SendNotificationView(FormView):
|
|||
notification_classes = []
|
||||
|
||||
def get_form_kwargs(self):
|
||||
"""Override form kwargs based on input fields
|
||||
"""
|
||||
Override form kwargs based on input fields.
|
||||
|
||||
The admin posts to this view initially, so detect the send button on
|
||||
form post variables. Drop additional fields if we see the send button.
|
||||
|
@ -41,14 +43,14 @@ class SendNotificationView(FormView):
|
|||
return kwargs
|
||||
|
||||
def get_initial(self):
|
||||
"""Add selected ids to initial form data"""
|
||||
"""Add selected ids to initial form data."""
|
||||
initial = super(SendNotificationView, self).get_initial()
|
||||
initial['_selected_action'] = self.request.POST.getlist(
|
||||
admin.ACTION_CHECKBOX_NAME)
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
"""If form is valid, send notification to recipients"""
|
||||
"""If form is valid, send notification to recipients."""
|
||||
count = 0
|
||||
notification_cls = form.cleaned_data['source']
|
||||
for obj in self.get_queryset().all():
|
||||
|
@ -65,7 +67,8 @@ class SendNotificationView(FormView):
|
|||
return HttpResponseRedirect(self.request.get_full_path())
|
||||
|
||||
def get_object_recipients(self, obj):
|
||||
"""Iterate over queryset objects and return User objects
|
||||
"""
|
||||
Iterate over queryset objects and return User objects.
|
||||
|
||||
This allows for non-User querysets to pass back a list of Users to send
|
||||
to. By default, assume we're working with :py:class:`User` objects and
|
||||
|
@ -85,7 +88,7 @@ class SendNotificationView(FormView):
|
|||
return self.kwargs.get('queryset')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Return queryset in context"""
|
||||
"""Return queryset in context."""
|
||||
context = super(SendNotificationView, self).get_context_data(**kwargs)
|
||||
recipients = []
|
||||
for obj in self.get_queryset().all():
|
||||
|
@ -96,7 +99,8 @@ class SendNotificationView(FormView):
|
|||
|
||||
def message_user(self, message, level=messages.INFO, extra_tags='',
|
||||
fail_silently=False):
|
||||
"""Implementation of :py:meth:`django.contrib.admin.options.ModelAdmin.message_user`
|
||||
"""
|
||||
Implementation of :py:meth:`django.contrib.admin.options.ModelAdmin.message_user`
|
||||
|
||||
Send message through messages framework
|
||||
"""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""OAuth utility functions"""
|
||||
"""OAuth utility functions."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from builtins import str
|
||||
|
@ -26,7 +26,7 @@ log = logging.getLogger(__name__)
|
|||
|
||||
class BitbucketService(Service):
|
||||
|
||||
"""Provider service for Bitbucket"""
|
||||
"""Provider service for Bitbucket."""
|
||||
|
||||
adapter = BitbucketOAuth2Adapter
|
||||
# TODO replace this with a less naive check
|
||||
|
@ -34,12 +34,12 @@ class BitbucketService(Service):
|
|||
https_url_pattern = re.compile(r'^https:\/\/[^@]+@bitbucket.org/')
|
||||
|
||||
def sync(self):
|
||||
"""Sync repositories and teams from Bitbucket API"""
|
||||
"""Sync repositories and teams from Bitbucket API."""
|
||||
self.sync_repositories()
|
||||
self.sync_teams()
|
||||
|
||||
def sync_repositories(self):
|
||||
"""Sync repositories from Bitbucket API"""
|
||||
"""Sync repositories from Bitbucket API."""
|
||||
# Get user repos
|
||||
try:
|
||||
repos = self.paginate(
|
||||
|
@ -71,7 +71,7 @@ class BitbucketService(Service):
|
|||
pass
|
||||
|
||||
def sync_teams(self):
|
||||
"""Sync Bitbucket teams and team repositories"""
|
||||
"""Sync Bitbucket teams and team repositories."""
|
||||
try:
|
||||
teams = self.paginate(
|
||||
'https://api.bitbucket.org/2.0/teams/?role=member'
|
||||
|
@ -89,7 +89,8 @@ class BitbucketService(Service):
|
|||
|
||||
def create_repository(self, fields, privacy=DEFAULT_PRIVACY_LEVEL,
|
||||
organization=None):
|
||||
"""Update or create a repository from Bitbucket API response
|
||||
"""
|
||||
Update or create a repository from Bitbucket API response.
|
||||
|
||||
.. note::
|
||||
The :py:data:`admin` property is not set during creation, as
|
||||
|
@ -145,7 +146,8 @@ class BitbucketService(Service):
|
|||
fields['name'])
|
||||
|
||||
def create_organization(self, fields):
|
||||
"""Update or create remote organization from Bitbucket API response
|
||||
"""
|
||||
Update or create remote organization from Bitbucket API response.
|
||||
|
||||
:param fields: dictionary response of data from API
|
||||
:rtype: RemoteOrganization
|
||||
|
@ -171,7 +173,7 @@ class BitbucketService(Service):
|
|||
return response.json().get('values', [])
|
||||
|
||||
def get_webhook_data(self, project, integration):
|
||||
"""Get webhook JSON data to post to the API"""
|
||||
"""Get webhook JSON data to post to the API."""
|
||||
return json.dumps({
|
||||
'description': 'Read the Docs ({domain})'.format(domain=settings.PRODUCTION_DOMAIN),
|
||||
'url': 'https://{domain}{path}'.format(
|
||||
|
@ -187,7 +189,8 @@ class BitbucketService(Service):
|
|||
})
|
||||
|
||||
def setup_webhook(self, project):
|
||||
"""Set up Bitbucket project webhook for project
|
||||
"""
|
||||
Set up Bitbucket project webhook for project.
|
||||
|
||||
:param project: project to set up webhook for
|
||||
:type project: Project
|
||||
|
@ -231,7 +234,8 @@ class BitbucketService(Service):
|
|||
return (False, resp)
|
||||
|
||||
def update_webhook(self, project, integration):
|
||||
"""Update webhook integration
|
||||
"""
|
||||
Update webhook integration.
|
||||
|
||||
:param project: project to set up webhook for
|
||||
:type project: Project
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""OAuth utility functions"""
|
||||
"""OAuth utility functions."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from builtins import str
|
||||
|
@ -27,19 +27,19 @@ log = logging.getLogger(__name__)
|
|||
|
||||
class GitHubService(Service):
|
||||
|
||||
"""Provider service for GitHub"""
|
||||
"""Provider service for GitHub."""
|
||||
|
||||
adapter = GitHubOAuth2Adapter
|
||||
# TODO replace this with a less naive check
|
||||
url_pattern = re.compile(r'github\.com')
|
||||
|
||||
def sync(self):
|
||||
"""Sync repositories and organizations"""
|
||||
"""Sync repositories and organizations."""
|
||||
self.sync_repositories()
|
||||
self.sync_organizations()
|
||||
|
||||
def sync_repositories(self):
|
||||
"""Sync repositories from GitHub API"""
|
||||
"""Sync repositories from GitHub API."""
|
||||
repos = self.paginate('https://api.github.com/user/repos?per_page=100')
|
||||
try:
|
||||
for repo in repos:
|
||||
|
@ -51,7 +51,7 @@ class GitHubService(Service):
|
|||
'try reconnecting your account')
|
||||
|
||||
def sync_organizations(self):
|
||||
"""Sync organizations from GitHub API"""
|
||||
"""Sync organizations from GitHub API."""
|
||||
try:
|
||||
orgs = self.paginate('https://api.github.com/user/orgs')
|
||||
for org in orgs:
|
||||
|
@ -72,7 +72,8 @@ class GitHubService(Service):
|
|||
|
||||
def create_repository(self, fields, privacy=DEFAULT_PRIVACY_LEVEL,
|
||||
organization=None):
|
||||
"""Update or create a repository from GitHub API response
|
||||
"""
|
||||
Update or create a repository from GitHub API response.
|
||||
|
||||
:param fields: dictionary of response data from API
|
||||
:param privacy: privacy level to support
|
||||
|
@ -122,7 +123,8 @@ class GitHubService(Service):
|
|||
fields['name'])
|
||||
|
||||
def create_organization(self, fields):
|
||||
"""Update or create remote organization from GitHub API response
|
||||
"""
|
||||
Update or create remote organization from GitHub API response.
|
||||
|
||||
:param fields: dictionary response of data from API
|
||||
:rtype: RemoteOrganization
|
||||
|
@ -155,7 +157,7 @@ class GitHubService(Service):
|
|||
return response.json()
|
||||
|
||||
def get_webhook_data(self, project, integration):
|
||||
"""Get webhook JSON data to post to the API"""
|
||||
"""Get webhook JSON data to post to the API."""
|
||||
return json.dumps({
|
||||
'name': 'web',
|
||||
'active': True,
|
||||
|
@ -174,7 +176,8 @@ class GitHubService(Service):
|
|||
})
|
||||
|
||||
def setup_webhook(self, project):
|
||||
"""Set up GitHub project webhook for project
|
||||
"""
|
||||
Set up GitHub project webhook for project.
|
||||
|
||||
:param project: project to set up webhook for
|
||||
:type project: Project
|
||||
|
@ -221,7 +224,8 @@ class GitHubService(Service):
|
|||
return (False, resp)
|
||||
|
||||
def update_webhook(self, project, integration):
|
||||
"""Update webhook integration
|
||||
"""
|
||||
Update webhook integration.
|
||||
|
||||
:param project: project to set up webhook for
|
||||
:type project: Project
|
||||
|
@ -265,7 +269,7 @@ class GitHubService(Service):
|
|||
|
||||
@classmethod
|
||||
def get_token_for_project(cls, project, force_local=False):
|
||||
"""Get access token for project by iterating over project users"""
|
||||
"""Get access token for project by iterating over project users."""
|
||||
# TODO why does this only target GitHub?
|
||||
if not getattr(settings, 'ALLOW_PRIVATE_REPOS', False):
|
||||
return None
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Payment forms"""
|
||||
"""Payment forms."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from builtins import str
|
||||
|
@ -17,7 +17,7 @@ log = logging.getLogger(__name__)
|
|||
|
||||
class StripeResourceMixin(object):
|
||||
|
||||
"""Stripe actions for resources, available as a Form mixin class"""
|
||||
"""Stripe actions for resources, available as a Form mixin class."""
|
||||
|
||||
def ensure_stripe_resource(self, resource, attrs):
|
||||
try:
|
||||
|
@ -59,7 +59,8 @@ class StripeResourceMixin(object):
|
|||
|
||||
class StripeModelForm(forms.ModelForm):
|
||||
|
||||
"""Payment form base for Stripe interaction
|
||||
"""
|
||||
Payment form base for Stripe interaction.
|
||||
|
||||
Use this as a base class for payment forms. It includes the necessary fields
|
||||
for card input and manipulates the Knockout field data bindings correctly.
|
||||
|
@ -114,7 +115,8 @@ class StripeModelForm(forms.ModelForm):
|
|||
super(StripeModelForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def validate_stripe(self):
|
||||
"""Run validation against Stripe
|
||||
"""
|
||||
Run validation against Stripe.
|
||||
|
||||
This is what will create several objects using the Stripe API. We need
|
||||
to actually create the objects, as that is what will provide us with
|
||||
|
@ -133,12 +135,12 @@ class StripeModelForm(forms.ModelForm):
|
|||
return data
|
||||
|
||||
def clean(self):
|
||||
"""Clean form to add Stripe objects via API during validation phase
|
||||
"""
|
||||
Clean form to add Stripe objects via API during validation phase.
|
||||
|
||||
This will handle ensuring a customer and subscription exist and will
|
||||
raise any issues as validation errors. This is required because part
|
||||
of Stripe's validation happens on the API call to establish a
|
||||
subscription.
|
||||
raise any issues as validation errors. This is required because part of
|
||||
Stripe's validation happens on the API call to establish a subscription.
|
||||
"""
|
||||
cleaned_data = super(StripeModelForm, self).clean()
|
||||
|
||||
|
@ -171,7 +173,8 @@ class StripeModelForm(forms.ModelForm):
|
|||
return cleaned_data
|
||||
|
||||
def clear_card_data(self):
|
||||
"""Clear card data on validation errors
|
||||
"""
|
||||
Clear card data on validation errors.
|
||||
|
||||
This requires the form was created by passing in a mutable QueryDict
|
||||
instance, see :py:class:`readthedocs.payments.mixin.StripeMixin`
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Payment view mixin classes"""
|
||||
"""Payment view mixin classes."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from builtins import object
|
||||
|
@ -7,7 +7,7 @@ from django.conf import settings
|
|||
|
||||
class StripeMixin(object):
|
||||
|
||||
"""Adds Stripe publishable key to the context data"""
|
||||
"""Adds Stripe publishable key to the context data."""
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(StripeMixin, self).get_context_data(**kwargs)
|
||||
|
@ -15,7 +15,8 @@ class StripeMixin(object):
|
|||
return context
|
||||
|
||||
def get_form(self, data=None, files=None, **kwargs):
|
||||
"""Pass in copy of POST data to avoid read only QueryDicts on form
|
||||
"""
|
||||
Pass in copy of POST data to avoid read only QueryDicts on form.
|
||||
|
||||
This is used to be able to reset some important credit card fields if
|
||||
card validation fails. In this case, the Stripe token was valid, but the
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Payment utility functions
|
||||
"""
|
||||
Payment utility functions.
|
||||
|
||||
These are mostly one-off functions. Define the bulk of Stripe operations on
|
||||
:py:class:`readthedocs.payments.forms.StripeResourceMixin`.
|
||||
|
@ -12,7 +13,7 @@ stripe.api_key = getattr(settings, 'STRIPE_SECRET', None)
|
|||
|
||||
|
||||
def delete_customer(customer_id):
|
||||
"""Delete customer from Stripe, cancelling subscriptions"""
|
||||
"""Delete customer from Stripe, cancelling subscriptions."""
|
||||
try:
|
||||
customer = stripe.Customer.retrieve(customer_id)
|
||||
return customer.delete()
|
||||
|
@ -21,7 +22,7 @@ def delete_customer(customer_id):
|
|||
|
||||
|
||||
def cancel_subscription(customer_id, subscription_id):
|
||||
"""Cancel Stripe subscription, if it exists"""
|
||||
"""Cancel Stripe subscription, if it exists."""
|
||||
try:
|
||||
customer = stripe.Customer.retrieve(customer_id)
|
||||
if hasattr(customer, 'subscriptions'):
|
||||
|
|
|
@ -74,7 +74,8 @@ class DomainInline(admin.TabularInline):
|
|||
|
||||
class ProjectOwnerBannedFilter(admin.SimpleListFilter):
|
||||
|
||||
"""Filter for projects with banned owners
|
||||
"""
|
||||
Filter for projects with banned owners.
|
||||
|
||||
There are problems adding `users__profile__banned` to the `list_filter`
|
||||
attribute, so we'll create a basic filter to capture banned owners.
|
||||
|
@ -98,7 +99,7 @@ class ProjectOwnerBannedFilter(admin.SimpleListFilter):
|
|||
|
||||
class ProjectAdmin(GuardedModelAdmin):
|
||||
|
||||
"""Project model admin view"""
|
||||
"""Project model admin view."""
|
||||
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
list_display = ('name', 'repo', 'repo_type', 'allow_comments', 'featured', 'theme')
|
||||
|
@ -121,7 +122,8 @@ class ProjectAdmin(GuardedModelAdmin):
|
|||
send_owner_email.short_description = 'Notify project owners'
|
||||
|
||||
def ban_owner(self, request, queryset):
|
||||
"""Ban project owner
|
||||
"""
|
||||
Ban project owner.
|
||||
|
||||
This will only ban single owners, because a malicious user could add a
|
||||
user as a co-owner of the project. We don't want to induce and
|
||||
|
@ -146,7 +148,8 @@ class ProjectAdmin(GuardedModelAdmin):
|
|||
ban_owner.short_description = 'Ban project owner'
|
||||
|
||||
def delete_selected_and_artifacts(self, request, queryset):
|
||||
"""Remove HTML/etc artifacts from application instances
|
||||
"""
|
||||
Remove HTML/etc artifacts from application instances.
|
||||
|
||||
Prior to the query delete, broadcast tasks to delete HTML artifacts from
|
||||
application instances.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Project exceptions"""
|
||||
"""Project exceptions."""
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
|
@ -8,7 +8,7 @@ from readthedocs.doc_builder.exceptions import BuildEnvironmentError
|
|||
|
||||
class ProjectConfigurationError(BuildEnvironmentError):
|
||||
|
||||
"""Error raised trying to configure a project for build"""
|
||||
"""Error raised trying to configure a project for build."""
|
||||
|
||||
NOT_FOUND = _(
|
||||
'A configuration file was not found. '
|
||||
|
@ -38,7 +38,8 @@ class RepositoryError(BuildEnvironmentError):
|
|||
|
||||
class ProjectSpamError(Exception):
|
||||
|
||||
"""Error raised when a project field has detected spam
|
||||
"""
|
||||
Error raised when a project field has detected spam.
|
||||
|
||||
This error is not raised to users, we use this for banning users in the
|
||||
background.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Project models"""
|
||||
"""Project models."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
@ -43,7 +43,8 @@ log = logging.getLogger(__name__)
|
|||
@python_2_unicode_compatible
|
||||
class ProjectRelationship(models.Model):
|
||||
|
||||
"""Project to project relationship
|
||||
"""
|
||||
Project to project relationship.
|
||||
|
||||
This is used for subprojects
|
||||
"""
|
||||
|
@ -72,7 +73,7 @@ class ProjectRelationship(models.Model):
|
|||
@python_2_unicode_compatible
|
||||
class Project(models.Model):
|
||||
|
||||
"""Project model"""
|
||||
"""Project model."""
|
||||
|
||||
# Auto fields
|
||||
pub_date = models.DateTimeField(_('Publication date'), auto_now_add=True)
|
||||
|
@ -343,7 +344,8 @@ class Project(models.Model):
|
|||
return reverse('projects_detail', args=[self.slug])
|
||||
|
||||
def get_docs_url(self, version_slug=None, lang_slug=None, private=None):
|
||||
"""Return a URL for the docs
|
||||
"""
|
||||
Return a URL for the docs.
|
||||
|
||||
Always use http for now, to avoid content warnings.
|
||||
"""
|
||||
|
@ -360,7 +362,8 @@ class Project(models.Model):
|
|||
return self.get_docs_url()
|
||||
|
||||
def get_subproject_urls(self):
|
||||
"""List subproject URLs
|
||||
"""
|
||||
List subproject URLs.
|
||||
|
||||
This is used in search result linking
|
||||
"""
|
||||
|
@ -375,7 +378,8 @@ class Project(models.Model):
|
|||
|
||||
def get_production_media_path(self, type_, version_slug, include_file=True):
|
||||
"""
|
||||
This is used to see if these files exist so we can offer them for download.
|
||||
This is used to see if these files exist so we can offer them for
|
||||
download.
|
||||
|
||||
:param type_: Media content type, ie - 'pdf', 'zip'
|
||||
:param version_slug: Project version slug for lookup
|
||||
|
@ -409,7 +413,7 @@ class Project(models.Model):
|
|||
return path
|
||||
|
||||
def subdomain(self):
|
||||
"""Get project subdomain from resolver"""
|
||||
"""Get project subdomain from resolver."""
|
||||
return resolve_domain(self)
|
||||
|
||||
def get_downloads(self):
|
||||
|
@ -440,7 +444,7 @@ class Project(models.Model):
|
|||
|
||||
@property
|
||||
def pip_cache_path(self):
|
||||
"""Path to pip cache"""
|
||||
"""Path to pip cache."""
|
||||
if getattr(settings, 'GLOBAL_PIP_CACHE', False):
|
||||
return settings.GLOBAL_PIP_CACHE
|
||||
return os.path.join(self.doc_path, '.cache', 'pip')
|
||||
|
@ -449,7 +453,7 @@ class Project(models.Model):
|
|||
# Paths for symlinks in project doc_path.
|
||||
#
|
||||
def translations_symlink_path(self, language=None):
|
||||
"""Path in the doc_path that we symlink translations"""
|
||||
"""Path in the doc_path that we symlink translations."""
|
||||
if not language:
|
||||
language = self.language
|
||||
return os.path.join(self.doc_path, 'translations', language)
|
||||
|
@ -459,7 +463,7 @@ class Project(models.Model):
|
|||
#
|
||||
|
||||
def full_doc_path(self, version=LATEST):
|
||||
"""The path to the documentation root in the project"""
|
||||
"""The path to the documentation root in the project."""
|
||||
doc_base = self.checkout_path(version)
|
||||
for possible_path in ['docs', 'doc', 'Doc']:
|
||||
if os.path.exists(os.path.join(doc_base, '%s' % possible_path)):
|
||||
|
@ -468,19 +472,19 @@ class Project(models.Model):
|
|||
return doc_base
|
||||
|
||||
def artifact_path(self, type_, version=LATEST):
|
||||
"""The path to the build html docs in the project"""
|
||||
"""The path to the build html docs in the project."""
|
||||
return os.path.join(self.doc_path, "artifacts", version, type_)
|
||||
|
||||
def full_build_path(self, version=LATEST):
|
||||
"""The path to the build html docs in the project"""
|
||||
"""The path to the build html docs in the project."""
|
||||
return os.path.join(self.conf_dir(version), "_build", "html")
|
||||
|
||||
def full_latex_path(self, version=LATEST):
|
||||
"""The path to the build LaTeX docs in the project"""
|
||||
"""The path to the build LaTeX docs in the project."""
|
||||
return os.path.join(self.conf_dir(version), "_build", "latex")
|
||||
|
||||
def full_epub_path(self, version=LATEST):
|
||||
"""The path to the build epub docs in the project"""
|
||||
"""The path to the build epub docs in the project."""
|
||||
return os.path.join(self.conf_dir(version), "_build", "epub")
|
||||
|
||||
# There is currently no support for building man/dash formats, but we keep
|
||||
|
@ -488,34 +492,34 @@ class Project(models.Model):
|
|||
# legacy builds.
|
||||
|
||||
def full_man_path(self, version=LATEST):
|
||||
"""The path to the build man docs in the project"""
|
||||
"""The path to the build man docs in the project."""
|
||||
return os.path.join(self.conf_dir(version), "_build", "man")
|
||||
|
||||
def full_dash_path(self, version=LATEST):
|
||||
"""The path to the build dash docs in the project"""
|
||||
"""The path to the build dash docs in the project."""
|
||||
return os.path.join(self.conf_dir(version), "_build", "dash")
|
||||
|
||||
def full_json_path(self, version=LATEST):
|
||||
"""The path to the build json docs in the project"""
|
||||
"""The path to the build json docs in the project."""
|
||||
if 'sphinx' in self.documentation_type:
|
||||
return os.path.join(self.conf_dir(version), "_build", "json")
|
||||
elif 'mkdocs' in self.documentation_type:
|
||||
return os.path.join(self.checkout_path(version), "_build", "json")
|
||||
|
||||
def full_singlehtml_path(self, version=LATEST):
|
||||
"""The path to the build singlehtml docs in the project"""
|
||||
"""The path to the build singlehtml docs in the project."""
|
||||
return os.path.join(self.conf_dir(version), "_build", "singlehtml")
|
||||
|
||||
def rtd_build_path(self, version=LATEST):
|
||||
"""The destination path where the built docs are copied"""
|
||||
"""The destination path where the built docs are copied."""
|
||||
return os.path.join(self.doc_path, 'rtd-builds', version)
|
||||
|
||||
def static_metadata_path(self):
|
||||
"""The path to the static metadata JSON settings file"""
|
||||
"""The path to the static metadata JSON settings file."""
|
||||
return os.path.join(self.doc_path, 'metadata.json')
|
||||
|
||||
def conf_file(self, version=LATEST):
|
||||
"""Find a ``conf.py`` file in the project checkout"""
|
||||
"""Find a ``conf.py`` file in the project checkout."""
|
||||
if self.conf_py_file:
|
||||
conf_path = os.path.join(self.checkout_path(version), self.conf_py_file)
|
||||
if os.path.exists(conf_path):
|
||||
|
@ -542,12 +546,12 @@ class Project(models.Model):
|
|||
|
||||
@property
|
||||
def is_type_sphinx(self):
|
||||
"""Is project type Sphinx"""
|
||||
"""Is project type Sphinx."""
|
||||
return 'sphinx' in self.documentation_type
|
||||
|
||||
@property
|
||||
def is_type_mkdocs(self):
|
||||
"""Is project type Mkdocs"""
|
||||
"""Is project type Mkdocs."""
|
||||
return 'mkdocs' in self.documentation_type
|
||||
|
||||
@property
|
||||
|
@ -603,7 +607,8 @@ class Project(models.Model):
|
|||
return Lock(self, version, timeout, polling_interval)
|
||||
|
||||
def find(self, filename, version):
|
||||
"""Find files inside the project's ``doc`` path
|
||||
"""
|
||||
Find files inside the project's ``doc`` path.
|
||||
|
||||
:param filename: Filename to search for in project checkout
|
||||
:param version: Version instance to set version checkout path
|
||||
|
@ -615,7 +620,8 @@ class Project(models.Model):
|
|||
return matches
|
||||
|
||||
def full_find(self, filename, version):
|
||||
"""Find files inside a project's checkout path
|
||||
"""
|
||||
Find files inside a project's checkout path.
|
||||
|
||||
:param filename: Filename to search for in project checkout
|
||||
:param version: Version instance to set version checkout path
|
||||
|
@ -628,10 +634,9 @@ class Project(models.Model):
|
|||
|
||||
def get_latest_build(self, finished=True):
|
||||
"""
|
||||
Get latest build for project
|
||||
Get latest build for project.
|
||||
|
||||
finished
|
||||
Return only builds that are in a finished state
|
||||
finished -- Return only builds that are in a finished state
|
||||
"""
|
||||
kwargs = {'type': 'html'}
|
||||
if finished:
|
||||
|
@ -664,7 +669,8 @@ class Project(models.Model):
|
|||
return sort_version_aware(versions)
|
||||
|
||||
def all_active_versions(self):
|
||||
"""Get queryset with all active versions
|
||||
"""
|
||||
Get queryset with all active versions.
|
||||
|
||||
.. note::
|
||||
This is a temporary workaround for activate_versions filtering out
|
||||
|
@ -675,7 +681,8 @@ class Project(models.Model):
|
|||
return self.versions.filter(active=True)
|
||||
|
||||
def supported_versions(self):
|
||||
"""Get the list of supported versions
|
||||
"""
|
||||
Get the list of supported versions.
|
||||
|
||||
:returns: List of version strings.
|
||||
"""
|
||||
|
@ -693,7 +700,8 @@ class Project(models.Model):
|
|||
return self.versions.filter(slug=STABLE).first()
|
||||
|
||||
def update_stable_version(self):
|
||||
"""Returns the version that was promoted to be the new stable version
|
||||
"""
|
||||
Returns the version that was promoted to be the new stable version.
|
||||
|
||||
Return ``None`` if no update was mode or if there is no version on the
|
||||
project that can be considered stable.
|
||||
|
@ -749,7 +757,7 @@ class Project(models.Model):
|
|||
return LATEST
|
||||
|
||||
def get_default_branch(self):
|
||||
"""Get the version representing 'latest'"""
|
||||
"""Get the version representing 'latest'."""
|
||||
if self.default_branch:
|
||||
return self.default_branch
|
||||
return self.vcs_repo().fallback_branch
|
||||
|
@ -776,7 +784,8 @@ class Project(models.Model):
|
|||
return queue
|
||||
|
||||
def add_node(self, content_hash, page, version, commit):
|
||||
"""Add comment node
|
||||
"""
|
||||
Add comment node.
|
||||
|
||||
:param content_hash: Hash of node content
|
||||
:param page: Doc page for node
|
||||
|
@ -804,7 +813,8 @@ class Project(models.Model):
|
|||
return True # ie, it's True that a new node was created.
|
||||
|
||||
def add_comment(self, version_slug, page, content_hash, commit, user, text):
|
||||
"""Add comment to node
|
||||
"""
|
||||
Add comment to node.
|
||||
|
||||
:param version_slug: Version slug to use for node lookup
|
||||
:param page: Page to attach comment to
|
||||
|
@ -827,7 +837,8 @@ class Project(models.Model):
|
|||
return Feature.objects.for_project(self)
|
||||
|
||||
def has_feature(self, feature_id):
|
||||
"""Does project have existing feature flag
|
||||
"""
|
||||
Does project have existing feature flag.
|
||||
|
||||
If the feature has a historical True value before the feature was added,
|
||||
we consider the project to have the flag. This is used for deprecating a
|
||||
|
@ -836,7 +847,8 @@ class Project(models.Model):
|
|||
return self.features.filter(feature_id=feature_id).exists()
|
||||
|
||||
def get_feature_value(self, feature, positive, negative):
|
||||
"""Look up project feature, return corresponding value
|
||||
"""
|
||||
Look up project feature, return corresponding value.
|
||||
|
||||
If a project has a feature, return ``positive``, otherwise return
|
||||
``negative``
|
||||
|
@ -846,7 +858,8 @@ class Project(models.Model):
|
|||
|
||||
class APIProject(Project):
|
||||
|
||||
"""Project proxy model for API data deserialization
|
||||
"""
|
||||
Project proxy model for API data deserialization.
|
||||
|
||||
This replaces the pattern where API data was deserialized into a mocked
|
||||
:py:cls:`Project` object. This pattern was confusing, as it was not explicit
|
||||
|
@ -885,7 +898,8 @@ class APIProject(Project):
|
|||
@python_2_unicode_compatible
|
||||
class ImportedFile(models.Model):
|
||||
|
||||
"""Imported files model
|
||||
"""
|
||||
Imported files model.
|
||||
|
||||
This tracks files that are output from documentation builds, useful for
|
||||
things like CDN invalidation.
|
||||
|
@ -986,7 +1000,8 @@ class Domain(models.Model):
|
|||
@python_2_unicode_compatible
|
||||
class Feature(models.Model):
|
||||
|
||||
"""Project feature flags
|
||||
"""
|
||||
Project feature flags.
|
||||
|
||||
Features should generally be added here as choices, however features may
|
||||
also be added dynamically from a signal in other packages. Features can be
|
||||
|
@ -1041,7 +1056,8 @@ class Feature(models.Model):
|
|||
)
|
||||
|
||||
def get_feature_display(self):
|
||||
"""Implement display name field for fake ChoiceField
|
||||
"""
|
||||
Implement display name field for fake ChoiceField.
|
||||
|
||||
Because the field is not a ChoiceField here, we need to manually
|
||||
implement this behavior.
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Project search indexes
|
||||
Project search indexes.
|
||||
|
||||
.. deprecated::
|
||||
Read the Docs no longer uses Haystack in production and the core team does
|
||||
not maintain this code. Use at your own risk, this may go away soon.
|
||||
|
||||
Read the Docs no longer uses Haystack in production and the core team does not
|
||||
maintain this code. Use at your own risk, this may go away soon.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
@ -26,7 +27,7 @@ log = logging.getLogger(__name__)
|
|||
|
||||
class ProjectIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
|
||||
"""Project search index"""
|
||||
"""Project search index."""
|
||||
|
||||
text = CharField(document=True, use_template=True)
|
||||
author = CharField()
|
||||
|
@ -45,14 +46,14 @@ class ProjectIndex(indexes.SearchIndex, indexes.Indexable):
|
|||
return Project
|
||||
|
||||
def index_queryset(self, using=None):
|
||||
"""Used when the entire index for model is updated"""
|
||||
"""Used when the entire index for model is updated."""
|
||||
return self.get_model().objects.public()
|
||||
|
||||
|
||||
# TODO Should prob make a common subclass for this and FileIndex
|
||||
class ImportedFileIndex(indexes.SearchIndex, indexes.Indexable):
|
||||
|
||||
"""Search index for imported files"""
|
||||
"""Search index for imported files."""
|
||||
|
||||
text = CharField(document=True)
|
||||
author = CharField()
|
||||
|
@ -71,7 +72,8 @@ class ImportedFileIndex(indexes.SearchIndex, indexes.Indexable):
|
|||
return obj.get_absolute_url()
|
||||
|
||||
def prepare_text(self, obj):
|
||||
"""Prepare the text of the html file
|
||||
"""
|
||||
Prepare the text of the html file.
|
||||
|
||||
This only works on machines that have the html files for the projects
|
||||
checked out.
|
||||
|
@ -107,6 +109,6 @@ class ImportedFileIndex(indexes.SearchIndex, indexes.Indexable):
|
|||
return ImportedFile
|
||||
|
||||
def index_queryset(self, using=None):
|
||||
"""Used when the entire index for model is updated"""
|
||||
"""Used when the entire index for model is updated."""
|
||||
return (self.get_model().objects
|
||||
.filter(project__privacy_level=constants.PUBLIC))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Tasks related to projects
|
||||
"""
|
||||
Tasks related to projects.
|
||||
|
||||
This includes fetching repository code, cleaning ``conf.py`` files, and
|
||||
rebuilding documentation.
|
||||
|
@ -186,7 +187,8 @@ class UpdateDocsTask(Task):
|
|||
return True
|
||||
|
||||
def run_setup(self, record=True):
|
||||
"""Run setup in the local environment.
|
||||
"""
|
||||
Run setup in the local environment.
|
||||
|
||||
Return True if successful.
|
||||
"""
|
||||
|
@ -236,11 +238,11 @@ class UpdateDocsTask(Task):
|
|||
return True
|
||||
|
||||
def run_build(self, docker=False, record=True):
|
||||
"""Build the docs in an environment.
|
||||
"""
|
||||
Build the docs in an environment.
|
||||
|
||||
If `docker` is True, or Docker is enabled by the settings.DOCKER_ENABLE
|
||||
setting, then build in a Docker environment. Otherwise build locally.
|
||||
|
||||
"""
|
||||
env_vars = self.get_env_vars()
|
||||
|
||||
|
@ -294,13 +296,13 @@ class UpdateDocsTask(Task):
|
|||
|
||||
@staticmethod
|
||||
def get_project(project_pk):
|
||||
"""Get project from API"""
|
||||
"""Get project from API."""
|
||||
project_data = api_v2.project(project_pk).get()
|
||||
return APIProject(**project_data)
|
||||
|
||||
@staticmethod
|
||||
def get_version(project, version_pk):
|
||||
"""Ensure we're using a sane version"""
|
||||
"""Ensure we're using a sane version."""
|
||||
if version_pk:
|
||||
version_data = api_v2.version(version_pk).get()
|
||||
else:
|
||||
|
@ -312,7 +314,7 @@ class UpdateDocsTask(Task):
|
|||
@staticmethod
|
||||
def get_build(build_pk):
|
||||
"""
|
||||
Retrieve build object from API
|
||||
Retrieve build object from API.
|
||||
|
||||
:param build_pk: Build primary key
|
||||
"""
|
||||
|
@ -369,7 +371,7 @@ class UpdateDocsTask(Task):
|
|||
|
||||
def update_documentation_type(self):
|
||||
"""
|
||||
Force Sphinx for 'auto' documentation type
|
||||
Force Sphinx for 'auto' documentation type.
|
||||
|
||||
This used to determine the type and automatically set the documentation
|
||||
type to Sphinx for rST and Mkdocs for markdown. It now just forces
|
||||
|
@ -383,7 +385,8 @@ class UpdateDocsTask(Task):
|
|||
|
||||
def update_app_instances(self, html=False, localmedia=False, search=False,
|
||||
pdf=False, epub=False):
|
||||
"""Update application instances with build artifacts
|
||||
"""
|
||||
Update application instances with build artifacts.
|
||||
|
||||
This triggers updates across application instances for html, pdf, epub,
|
||||
downloads, and search. Tasks are broadcast to all web servers from here.
|
||||
|
@ -439,7 +442,8 @@ class UpdateDocsTask(Task):
|
|||
self.python_env.install_package()
|
||||
|
||||
def build_docs(self):
|
||||
"""Wrapper to all build functions
|
||||
"""
|
||||
Wrapper to all build functions.
|
||||
|
||||
Executes the necessary builds for this task and returns whether the
|
||||
build was successful or not.
|
||||
|
@ -465,7 +469,7 @@ class UpdateDocsTask(Task):
|
|||
return outcomes
|
||||
|
||||
def build_docs_html(self):
|
||||
"""Build HTML docs"""
|
||||
"""Build HTML docs."""
|
||||
html_builder = get_builder_class(self.project.documentation_type)(
|
||||
build_env=self.build_env,
|
||||
python_env=self.python_env,
|
||||
|
@ -489,7 +493,7 @@ class UpdateDocsTask(Task):
|
|||
return success
|
||||
|
||||
def build_docs_search(self):
|
||||
"""Build search data with separate build"""
|
||||
"""Build search data with separate build."""
|
||||
if self.build_search:
|
||||
if self.project.is_type_mkdocs:
|
||||
return self.build_docs_class('mkdocs_json')
|
||||
|
@ -498,7 +502,7 @@ class UpdateDocsTask(Task):
|
|||
return False
|
||||
|
||||
def build_docs_localmedia(self):
|
||||
"""Get local media files with separate build"""
|
||||
"""Get local media files with separate build."""
|
||||
if 'htmlzip' not in self.config.formats:
|
||||
return False
|
||||
|
||||
|
@ -508,7 +512,7 @@ class UpdateDocsTask(Task):
|
|||
return False
|
||||
|
||||
def build_docs_pdf(self):
|
||||
"""Build PDF docs"""
|
||||
"""Build PDF docs."""
|
||||
if ('pdf' not in self.config.formats or
|
||||
self.project.slug in HTML_ONLY or
|
||||
not self.project.is_type_sphinx):
|
||||
|
@ -516,7 +520,7 @@ class UpdateDocsTask(Task):
|
|||
return self.build_docs_class('sphinx_pdf')
|
||||
|
||||
def build_docs_epub(self):
|
||||
"""Build ePub docs"""
|
||||
"""Build ePub docs."""
|
||||
if ('epub' not in self.config.formats or
|
||||
self.project.slug in HTML_ONLY or
|
||||
not self.project.is_type_sphinx):
|
||||
|
@ -524,7 +528,8 @@ class UpdateDocsTask(Task):
|
|||
return self.build_docs_class('sphinx_epub')
|
||||
|
||||
def build_docs_class(self, builder_class):
|
||||
"""Build docs with additional doc backends
|
||||
"""
|
||||
Build docs with additional doc backends.
|
||||
|
||||
These steps are not necessarily required for the build to halt, so we
|
||||
only raise a warning exception here. A hard error will halt the build
|
||||
|
@ -536,14 +541,14 @@ class UpdateDocsTask(Task):
|
|||
return success
|
||||
|
||||
def send_notifications(self):
|
||||
"""Send notifications on build failure"""
|
||||
"""Send notifications on build failure."""
|
||||
send_notifications.delay(self.version.pk, build_pk=self.build['id'])
|
||||
|
||||
|
||||
@app.task()
|
||||
def update_imported_docs(version_pk):
|
||||
"""
|
||||
Check out or update the given project's repository
|
||||
Check out or update the given project's repository.
|
||||
|
||||
:param version_pk: Version id to update
|
||||
"""
|
||||
|
@ -626,10 +631,11 @@ def update_imported_docs(version_pk):
|
|||
@app.task(queue='web')
|
||||
def sync_files(project_pk, version_pk, hostname=None, html=False,
|
||||
localmedia=False, search=False, pdf=False, epub=False):
|
||||
"""Sync build artifacts to application instances
|
||||
"""
|
||||
Sync build artifacts to application instances.
|
||||
|
||||
This task broadcasts from a build instance on build completion and
|
||||
performs synchronization of build artifacts on each application instance.
|
||||
This task broadcasts from a build instance on build completion and performs
|
||||
synchronization of build artifacts on each application instance.
|
||||
"""
|
||||
# Clean up unused artifacts
|
||||
if not pdf:
|
||||
|
@ -658,7 +664,8 @@ def sync_files(project_pk, version_pk, hostname=None, html=False,
|
|||
@app.task(queue='web')
|
||||
def move_files(version_pk, hostname, html=False, localmedia=False, search=False,
|
||||
pdf=False, epub=False):
|
||||
"""Task to move built documentation to web servers
|
||||
"""
|
||||
Task to move built documentation to web servers.
|
||||
|
||||
:param version_pk: Version id to sync files for
|
||||
:param hostname: Hostname to sync to
|
||||
|
@ -723,7 +730,8 @@ def move_files(version_pk, hostname, html=False, localmedia=False, search=False,
|
|||
|
||||
@app.task(queue='web')
|
||||
def update_search(version_pk, commit, delete_non_commit_files=True):
|
||||
"""Task to update search indexes
|
||||
"""
|
||||
Task to update search indexes.
|
||||
|
||||
:param version_pk: Version id to update
|
||||
:param commit: Commit that updated index
|
||||
|
@ -814,7 +822,8 @@ def fileify(version_pk, commit):
|
|||
|
||||
|
||||
def _manage_imported_files(version, path, commit):
|
||||
"""Update imported files for version
|
||||
"""
|
||||
Update imported files for version.
|
||||
|
||||
:param version: Version instance
|
||||
:param path: Path to search
|
||||
|
@ -869,7 +878,8 @@ def send_notifications(version_pk, build_pk):
|
|||
|
||||
|
||||
def email_notification(version, build, email):
|
||||
"""Send email notifications for build failure
|
||||
"""
|
||||
Send email notifications for build failure.
|
||||
|
||||
:param version: :py:class:`Version` instance that failed
|
||||
:param build: :py:class:`Build` instance that failed
|
||||
|
@ -903,7 +913,8 @@ def email_notification(version, build, email):
|
|||
|
||||
|
||||
def webhook_notification(version, build, hook_url):
|
||||
"""Send webhook notification for project webhook
|
||||
"""
|
||||
Send webhook notification for project webhook.
|
||||
|
||||
:param version: Version instance to send hook for
|
||||
:param build: Build instance that failed
|
||||
|
@ -928,7 +939,8 @@ def webhook_notification(version, build, hook_url):
|
|||
|
||||
@app.task(queue='web')
|
||||
def update_static_metadata(project_pk, path=None):
|
||||
"""Update static metadata JSON file
|
||||
"""
|
||||
Update static metadata JSON file.
|
||||
|
||||
Metadata settings include the following project settings:
|
||||
|
||||
|
@ -979,8 +991,8 @@ def remove_dir(path):
|
|||
"""
|
||||
Remove a directory on the build/celery server.
|
||||
|
||||
This is mainly a wrapper around shutil.rmtree so that app servers
|
||||
can kill things on the build server.
|
||||
This is mainly a wrapper around shutil.rmtree so that app servers can kill
|
||||
things on the build server.
|
||||
"""
|
||||
log.info("Removing %s", path)
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
|
@ -988,7 +1000,7 @@ def remove_dir(path):
|
|||
|
||||
@app.task()
|
||||
def clear_artifacts(version_pk):
|
||||
"""Remove artifacts from the web servers"""
|
||||
"""Remove artifacts from the web servers."""
|
||||
version = Version.objects.get(pk=version_pk)
|
||||
clear_pdf_artifacts(version)
|
||||
clear_epub_artifacts(version)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Utility functions used by projects"""
|
||||
"""Utility functions used by projects."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
@ -32,7 +32,8 @@ def version_from_slug(slug, version):
|
|||
|
||||
|
||||
def find_file(filename):
|
||||
"""Recursively find matching file from the current working path
|
||||
"""
|
||||
Recursively find matching file from the current working path.
|
||||
|
||||
:param file: Filename to match
|
||||
:returns: A list of matching filenames.
|
||||
|
@ -45,7 +46,8 @@ def find_file(filename):
|
|||
|
||||
|
||||
def run(*commands):
|
||||
"""Run one or more commands
|
||||
"""
|
||||
Run one or more commands.
|
||||
|
||||
Each argument in `commands` can be passed as a string or as a list. Passing
|
||||
as a list is the preferred method, as space escaping is more explicit and it
|
||||
|
@ -106,7 +108,8 @@ def run(*commands):
|
|||
|
||||
|
||||
def safe_write(filename, contents):
|
||||
"""Normalize and write to filename
|
||||
"""
|
||||
Normalize and write to filename.
|
||||
|
||||
Write ``contents`` to the given ``filename``. If the filename's
|
||||
directory does not exist, it is created. Contents are written as UTF-8,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Mixin classes for project views"""
|
||||
"""Mixin classes for project views."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from builtins import object
|
||||
|
@ -9,7 +9,8 @@ from readthedocs.projects.models import Project
|
|||
|
||||
class ProjectRelationMixin(object):
|
||||
|
||||
"""Mixin class for constructing model views for project dashboard
|
||||
"""
|
||||
Mixin class for constructing model views for project dashboard.
|
||||
|
||||
This mixin class is used for model views on models that have a relation
|
||||
to the :py:cls:`Project` model.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Project views for authenticated users"""
|
||||
"""Project views for authenticated users."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
|
@ -52,7 +52,7 @@ class PrivateViewMixin(LoginRequiredMixin):
|
|||
|
||||
class ProjectDashboard(PrivateViewMixin, ListView):
|
||||
|
||||
"""Project dashboard"""
|
||||
"""Project dashboard."""
|
||||
|
||||
model = Project
|
||||
template_name = 'projects/project_dashboard.html'
|
||||
|
@ -75,7 +75,8 @@ class ProjectDashboard(PrivateViewMixin, ListView):
|
|||
|
||||
@login_required
|
||||
def project_manage(__, project_slug):
|
||||
"""Project management view
|
||||
"""
|
||||
Project management view.
|
||||
|
||||
Where you will have links to edit the projects' configuration, edit the
|
||||
files associated with that project, etc.
|
||||
|
@ -131,7 +132,8 @@ class ProjectAdvancedUpdate(ProjectSpamMixin, PrivateViewMixin, UpdateView):
|
|||
|
||||
@login_required
|
||||
def project_versions(request, project_slug):
|
||||
"""Project versions view
|
||||
"""
|
||||
Project versions view.
|
||||
|
||||
Shows the available versions and lets the user choose which ones he would
|
||||
like to have built.
|
||||
|
@ -161,7 +163,7 @@ def project_versions(request, project_slug):
|
|||
|
||||
@login_required
|
||||
def project_version_detail(request, project_slug, version_slug):
|
||||
"""Project version detail page"""
|
||||
"""Project version detail page."""
|
||||
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),
|
||||
|
@ -189,7 +191,8 @@ def project_version_detail(request, project_slug, version_slug):
|
|||
|
||||
@login_required
|
||||
def project_delete(request, project_slug):
|
||||
"""Project delete confirmation view
|
||||
"""
|
||||
Project delete confirmation view.
|
||||
|
||||
Make a project as deleted on POST, otherwise show a form asking for
|
||||
confirmation of delete.
|
||||
|
@ -213,14 +216,14 @@ def project_delete(request, project_slug):
|
|||
|
||||
class ImportWizardView(ProjectSpamMixin, PrivateViewMixin, SessionWizardView):
|
||||
|
||||
"""Project import wizard"""
|
||||
"""Project import wizard."""
|
||||
|
||||
form_list = [('basics', ProjectBasicsForm),
|
||||
('extra', ProjectExtraForm)]
|
||||
condition_dict = {'extra': lambda self: self.is_advanced()}
|
||||
|
||||
def get_form_kwargs(self, step=None):
|
||||
"""Get args to pass into form instantiation"""
|
||||
"""Get args to pass into form instantiation."""
|
||||
kwargs = {}
|
||||
kwargs['user'] = self.request.user
|
||||
if step == 'basics':
|
||||
|
@ -228,11 +231,12 @@ class ImportWizardView(ProjectSpamMixin, PrivateViewMixin, SessionWizardView):
|
|||
return kwargs
|
||||
|
||||
def get_template_names(self):
|
||||
"""Return template names based on step name"""
|
||||
"""Return template names based on step name."""
|
||||
return 'projects/import_{0}.html'.format(self.steps.current)
|
||||
|
||||
def done(self, form_list, **kwargs):
|
||||
"""Save form data as object instance
|
||||
"""
|
||||
Save form data as object instance.
|
||||
|
||||
Don't save form data directly, instead bypass documentation building and
|
||||
other side effects for now, by signalling a save without commit. Then,
|
||||
|
@ -260,14 +264,14 @@ class ImportWizardView(ProjectSpamMixin, PrivateViewMixin, SessionWizardView):
|
|||
args=[project.slug]))
|
||||
|
||||
def is_advanced(self):
|
||||
"""Determine if the user selected the `show advanced` field"""
|
||||
"""Determine if the user selected the `show advanced` field."""
|
||||
data = self.get_cleaned_data_for_step('basics') or {}
|
||||
return data.get('advanced', True)
|
||||
|
||||
|
||||
class ImportDemoView(PrivateViewMixin, View):
|
||||
|
||||
"""View to pass request on to import form to import demo project"""
|
||||
"""View to pass request on to import form to import demo project."""
|
||||
|
||||
form_class = ProjectBasicsForm
|
||||
request = None
|
||||
|
@ -275,7 +279,7 @@ class ImportDemoView(PrivateViewMixin, View):
|
|||
kwargs = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Process link request as a form post to the project import form"""
|
||||
"""Process link request as a form post to the project import form."""
|
||||
self.request = request
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
@ -305,7 +309,7 @@ class ImportDemoView(PrivateViewMixin, View):
|
|||
args=[project.slug]))
|
||||
|
||||
def get_form_data(self):
|
||||
"""Get form data to post to import form"""
|
||||
"""Get form data to post to import form."""
|
||||
return {
|
||||
'name': '{0}-demo'.format(self.request.user.username),
|
||||
'repo_type': 'git',
|
||||
|
@ -313,13 +317,14 @@ class ImportDemoView(PrivateViewMixin, View):
|
|||
}
|
||||
|
||||
def get_form_kwargs(self):
|
||||
"""Form kwargs passed in during instantiation"""
|
||||
"""Form kwargs passed in during instantiation."""
|
||||
return {'user': self.request.user}
|
||||
|
||||
|
||||
class ImportView(PrivateViewMixin, TemplateView):
|
||||
|
||||
"""On GET, show the source an import view, on POST, mock out a wizard
|
||||
"""
|
||||
On GET, show the source an import view, on POST, mock out a wizard.
|
||||
|
||||
If we are accepting POST data, use the fields to seed the initial data in
|
||||
:py:class:`ImportWizardView`. The import templates will redirect the form to
|
||||
|
@ -330,7 +335,8 @@ class ImportView(PrivateViewMixin, TemplateView):
|
|||
wizard_class = ImportWizardView
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Display list of repositories to import
|
||||
"""
|
||||
Display list of repositories to import.
|
||||
|
||||
Adds a warning to the listing if any of the accounts connected for the
|
||||
user are not supported accounts.
|
||||
|
@ -377,7 +383,7 @@ class ImportView(PrivateViewMixin, TemplateView):
|
|||
|
||||
@login_required
|
||||
def edit_alias(request, project_slug, alias_id=None):
|
||||
"""Edit project alias form view"""
|
||||
"""Edit project alias form view."""
|
||||
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)
|
||||
|
@ -455,7 +461,7 @@ class ProjectRelationshipDelete(ProjectRelationshipMixin, DeleteView):
|
|||
|
||||
@login_required
|
||||
def project_users(request, project_slug):
|
||||
"""Project users view and form view"""
|
||||
"""Project users view and form view."""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
|
||||
|
@ -490,7 +496,7 @@ def project_users_delete(request, project_slug):
|
|||
|
||||
@login_required
|
||||
def project_notifications(request, project_slug):
|
||||
"""Project notification view and form view"""
|
||||
"""Project notification view and form view."""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
|
||||
|
@ -538,7 +544,7 @@ def project_comments_settings(request, project_slug):
|
|||
|
||||
@login_required
|
||||
def project_notifications_delete(request, project_slug):
|
||||
"""Project notifications delete confirmation view"""
|
||||
"""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),
|
||||
|
@ -556,7 +562,7 @@ def project_notifications_delete(request, project_slug):
|
|||
|
||||
@login_required
|
||||
def project_translations(request, project_slug):
|
||||
"""Project translations view and form view"""
|
||||
"""Project translations view and form view."""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
form = TranslationForm(data=request.POST or None, parent=project)
|
||||
|
@ -587,7 +593,7 @@ def project_translations_delete(request, project_slug, child_slug):
|
|||
|
||||
@login_required
|
||||
def project_redirects(request, project_slug):
|
||||
"""Project redirects view and form view"""
|
||||
"""Project redirects view and form view."""
|
||||
project = get_object_or_404(Project.objects.for_admin_user(request.user),
|
||||
slug=project_slug)
|
||||
|
||||
|
@ -609,7 +615,7 @@ def project_redirects(request, project_slug):
|
|||
|
||||
@login_required
|
||||
def project_redirects_delete(request, project_slug):
|
||||
"""Project redirect delete view"""
|
||||
"""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),
|
||||
|
@ -626,7 +632,8 @@ def project_redirects_delete(request, project_slug):
|
|||
|
||||
@login_required
|
||||
def project_version_delete_html(request, project_slug, version_slug):
|
||||
"""Project version 'delete' HTML
|
||||
"""
|
||||
Project version 'delete' HTML.
|
||||
|
||||
This marks a version as not built
|
||||
"""
|
||||
|
@ -672,7 +679,7 @@ class DomainDelete(DomainMixin, DeleteView):
|
|||
|
||||
class IntegrationMixin(ProjectAdminMixin, PrivateViewMixin):
|
||||
|
||||
"""Project external service mixin for listing webhook objects"""
|
||||
"""Project external service mixin for listing webhook objects."""
|
||||
|
||||
model = Integration
|
||||
integration_url_field = 'integration_pk'
|
||||
|
@ -689,7 +696,7 @@ class IntegrationMixin(ProjectAdminMixin, PrivateViewMixin):
|
|||
return self.model.objects.filter(project=self.project)
|
||||
|
||||
def get_integration(self):
|
||||
"""Return project integration determined by url kwarg"""
|
||||
"""Return project integration determined by url kwarg."""
|
||||
if self.integration_url_field not in self.kwargs:
|
||||
return None
|
||||
return get_object_or_404(
|
||||
|
@ -765,7 +772,8 @@ class IntegrationExchangeDetail(IntegrationMixin, DetailView):
|
|||
|
||||
class IntegrationWebhookSync(IntegrationMixin, GenericView):
|
||||
|
||||
"""Resync a project webhook
|
||||
"""
|
||||
Resync a project webhook.
|
||||
|
||||
The signal will add a success/failure message on the request.
|
||||
"""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Public project views"""
|
||||
"""Public project views."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from collections import OrderedDict
|
||||
|
@ -38,7 +38,7 @@ mimetypes.add_type("application/epub+zip", ".epub")
|
|||
|
||||
class ProjectIndex(ListView):
|
||||
|
||||
"""List view of public :py:class:`Project` instances"""
|
||||
"""List view of public :py:class:`Project` instances."""
|
||||
|
||||
model = Project
|
||||
|
||||
|
@ -70,7 +70,7 @@ project_index = ProjectIndex.as_view()
|
|||
|
||||
class ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView):
|
||||
|
||||
"""Display project onboard steps"""
|
||||
"""Display project onboard steps."""
|
||||
|
||||
model = Project
|
||||
slug_url_kwarg = 'project_slug'
|
||||
|
@ -106,7 +106,7 @@ class ProjectDetailView(BuildTriggerMixin, ProjectOnboardMixin, DetailView):
|
|||
|
||||
@never_cache
|
||||
def project_badge(request, project_slug):
|
||||
"""Return a sweet badge for the project"""
|
||||
"""Return a sweet badge for the project."""
|
||||
badge_path = "projects/badges/%s.svg"
|
||||
version_slug = request.GET.get('version', LATEST)
|
||||
try:
|
||||
|
@ -128,7 +128,7 @@ def project_badge(request, project_slug):
|
|||
|
||||
|
||||
def project_downloads(request, project_slug):
|
||||
"""A detail view for a project with various dataz"""
|
||||
"""A detail view for a project with various dataz."""
|
||||
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()
|
||||
|
@ -158,7 +158,6 @@ def project_download_media(request, project_slug, type_, version_slug):
|
|||
.. warning:: This is linked directly from the HTML pages.
|
||||
It should only care about the Version permissions,
|
||||
not the actual Project permissions.
|
||||
|
||||
"""
|
||||
version = get_object_or_404(
|
||||
Version.objects.public(user=request.user),
|
||||
|
@ -189,7 +188,7 @@ def project_download_media(request, project_slug, type_, version_slug):
|
|||
|
||||
|
||||
def search_autocomplete(request):
|
||||
"""Return a json list of project names"""
|
||||
"""Return a json list of project names."""
|
||||
if 'term' in request.GET:
|
||||
term = request.GET['term']
|
||||
else:
|
||||
|
@ -208,7 +207,7 @@ def search_autocomplete(request):
|
|||
|
||||
|
||||
def version_autocomplete(request, project_slug):
|
||||
"""Return a json list of version names"""
|
||||
"""Return a json list of version names."""
|
||||
queryset = Project.objects.public(request.user)
|
||||
get_object_or_404(queryset, slug=project_slug)
|
||||
versions = Version.objects.public(request.user)
|
||||
|
@ -247,7 +246,7 @@ def version_filter_autocomplete(request, project_slug):
|
|||
|
||||
|
||||
def file_autocomplete(request, project_slug):
|
||||
"""Return a json list of file names"""
|
||||
"""Return a json list of file names."""
|
||||
if 'term' in request.GET:
|
||||
term = request.GET['term']
|
||||
else:
|
||||
|
@ -266,7 +265,7 @@ def file_autocomplete(request, project_slug):
|
|||
|
||||
|
||||
def elastic_project_search(request, project_slug):
|
||||
"""Use elastic search to search in a project"""
|
||||
"""Use elastic search to search in a project."""
|
||||
queryset = Project.objects.protected(request.user)
|
||||
project = get_object_or_404(queryset, slug=project_slug)
|
||||
version_slug = request.GET.get('version', LATEST)
|
||||
|
@ -340,7 +339,8 @@ def elastic_project_search(request, project_slug):
|
|||
|
||||
|
||||
def project_versions(request, project_slug):
|
||||
"""Project version list view
|
||||
"""
|
||||
Project version list view.
|
||||
|
||||
Shows the available versions and lets the user choose which ones to build.
|
||||
"""
|
||||
|
@ -371,7 +371,7 @@ def project_versions(request, project_slug):
|
|||
|
||||
|
||||
def project_analytics(request, project_slug):
|
||||
"""Have a analytics API placeholder"""
|
||||
"""Have a analytics API placeholder."""
|
||||
project = get_object_or_404(Project.objects.protected(request.user),
|
||||
slug=project_slug)
|
||||
analytics_cache = cache.get('analytics:%s' % project_slug)
|
||||
|
@ -416,7 +416,7 @@ def project_analytics(request, project_slug):
|
|||
|
||||
|
||||
def project_embed(request, project_slug):
|
||||
"""Have a content API placeholder"""
|
||||
"""Have a content API placeholder."""
|
||||
project = get_object_or_404(Project.objects.protected(request.user),
|
||||
slug=project_slug)
|
||||
version = project.versions.get(slug=LATEST)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
"""Redirection view support.
|
||||
"""
|
||||
Redirection view support.
|
||||
|
||||
This module allows for parsing a URL path, looking up redirects associated
|
||||
with it in the database, and generating a redirect response.
|
||||
|
||||
These are not used directly as views; they are instead included into 404
|
||||
handlers, so that redirects only take effect if no other view matches.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from django.http import HttpResponseRedirect
|
||||
|
@ -20,7 +20,8 @@ log = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def project_and_path_from_request(request, path):
|
||||
"""Parse the project from a request path.
|
||||
"""
|
||||
Parse the project from a request path.
|
||||
|
||||
Return a tuple (project, path) where `project` is a projects.Project if
|
||||
a matching project exists, and `path` is the unmatched remainder of the
|
||||
|
@ -28,7 +29,6 @@ def project_and_path_from_request(request, path):
|
|||
|
||||
If the path does not match, or no matching project is found, then `project`
|
||||
will be ``None``.
|
||||
|
||||
"""
|
||||
if hasattr(request, 'slug'):
|
||||
project_slug = request.slug
|
||||
|
|
|
@ -62,12 +62,13 @@ class APIPermission(permissions.IsAuthenticatedOrReadOnly):
|
|||
|
||||
class APIRestrictedPermission(permissions.BasePermission):
|
||||
|
||||
"""Allow admin write, authenticated and anonymous read only
|
||||
"""
|
||||
Allow admin write, authenticated and anonymous read only.
|
||||
|
||||
This differs from :py:class:`APIPermission` by not allowing for authenticated
|
||||
POSTs. This permission is endpoints like ``/api/v2/build/``, which are used
|
||||
by admin users to coordinate build instance creation, but only should be
|
||||
readable by end users.
|
||||
This differs from :py:class:`APIPermission` by not allowing for
|
||||
authenticated POSTs. This permission is endpoints like ``/api/v2/build/``,
|
||||
which are used by admin users to coordinate build instance creation, but
|
||||
only should be readable by end users.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
|
|
|
@ -28,7 +28,8 @@ class ProjectSerializer(serializers.ModelSerializer):
|
|||
|
||||
class ProjectAdminSerializer(ProjectSerializer):
|
||||
|
||||
"""Project serializer for admin only access
|
||||
"""
|
||||
Project serializer for admin only access.
|
||||
|
||||
Includes special internal fields that don't need to be exposed through the
|
||||
general API, mostly for fields used in the build process
|
||||
|
@ -77,7 +78,7 @@ class VersionSerializer(serializers.ModelSerializer):
|
|||
|
||||
class VersionAdminSerializer(VersionSerializer):
|
||||
|
||||
"""Version serializer that returns admin project data"""
|
||||
"""Version serializer that returns admin project data."""
|
||||
|
||||
project = ProjectAdminSerializer()
|
||||
|
||||
|
@ -93,7 +94,7 @@ class BuildCommandSerializer(serializers.ModelSerializer):
|
|||
|
||||
class BuildSerializer(serializers.ModelSerializer):
|
||||
|
||||
"""Build serializer for user display, doesn't display internal fields"""
|
||||
"""Build serializer for user display, doesn't display internal fields."""
|
||||
|
||||
commands = BuildCommandSerializer(many=True, read_only=True)
|
||||
state_display = serializers.ReadOnlyField(source='get_state_display')
|
||||
|
@ -105,7 +106,7 @@ class BuildSerializer(serializers.ModelSerializer):
|
|||
|
||||
class BuildAdminSerializer(BuildSerializer):
|
||||
|
||||
"""Build serializer for display to admin users and build instances"""
|
||||
"""Build serializer for display to admin users and build instances."""
|
||||
|
||||
class Meta(BuildSerializer.Meta):
|
||||
exclude = ()
|
||||
|
@ -142,7 +143,7 @@ class RemoteOrganizationSerializer(serializers.ModelSerializer):
|
|||
|
||||
class RemoteRepositorySerializer(serializers.ModelSerializer):
|
||||
|
||||
"""Remote service repository serializer"""
|
||||
"""Remote service repository serializer."""
|
||||
|
||||
organization = RemoteOrganizationSerializer()
|
||||
matches = serializers.SerializerMethodField()
|
||||
|
|
|
@ -78,7 +78,8 @@ def delete_versions(project, version_data):
|
|||
|
||||
def index_search_request(version, page_list, commit, project_scale, page_scale,
|
||||
section=True, delete=True):
|
||||
"""Update search indexes with build output JSON
|
||||
"""
|
||||
Update search indexes with build output JSON.
|
||||
|
||||
In order to keep sub-projects all indexed on the same shard, indexes will be
|
||||
updated using the parent project's slug as the routing value.
|
||||
|
|
|
@ -39,7 +39,7 @@ class WebhookMixin(object):
|
|||
integration_type = None
|
||||
|
||||
def post(self, request, project_slug):
|
||||
"""Set up webhook post view with request and project objects"""
|
||||
"""Set up webhook post view with request and project objects."""
|
||||
self.request = request
|
||||
self.project = None
|
||||
try:
|
||||
|
@ -57,7 +57,7 @@ class WebhookMixin(object):
|
|||
return Project.objects.get(**kwargs)
|
||||
|
||||
def finalize_response(self, req, *args, **kwargs):
|
||||
"""If the project was set on POST, store an HTTP exchange"""
|
||||
"""If the project was set on POST, store an HTTP exchange."""
|
||||
resp = super(WebhookMixin, self).finalize_response(req, *args, **kwargs)
|
||||
if hasattr(self, 'project') and self.project:
|
||||
HttpExchange.objects.from_exchange(
|
||||
|
@ -69,15 +69,16 @@ class WebhookMixin(object):
|
|||
return resp
|
||||
|
||||
def get_data(self):
|
||||
"""Normalize posted data"""
|
||||
"""Normalize posted data."""
|
||||
return normalize_request_payload(self.request)
|
||||
|
||||
def handle_webhook(self):
|
||||
"""Handle webhook payload"""
|
||||
"""Handle webhook payload."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_integration(self):
|
||||
"""Get or create an inbound webhook to track webhook requests
|
||||
"""
|
||||
Get or create an inbound webhook to track webhook requests.
|
||||
|
||||
We shouldn't need this, but to support legacy webhooks, we can't assume
|
||||
that a webhook has ever been created on our side. Most providers don't
|
||||
|
@ -97,7 +98,8 @@ class WebhookMixin(object):
|
|||
return integration
|
||||
|
||||
def get_response_push(self, project, branches):
|
||||
"""Build branches on push events and return API response
|
||||
"""
|
||||
Build branches on push events and return API response.
|
||||
|
||||
Return a JSON response with the following::
|
||||
|
||||
|
@ -124,7 +126,8 @@ class WebhookMixin(object):
|
|||
|
||||
class GitHubWebhookView(WebhookMixin, APIView):
|
||||
|
||||
"""Webhook consumer for GitHub
|
||||
"""
|
||||
Webhook consumer for GitHub.
|
||||
|
||||
Accepts webhook events from GitHub, 'push' events trigger builds. Expects the
|
||||
webhook event type will be included in HTTP header ``X-GitHub-Event``, and
|
||||
|
@ -164,7 +167,8 @@ class GitHubWebhookView(WebhookMixin, APIView):
|
|||
|
||||
class GitLabWebhookView(WebhookMixin, APIView):
|
||||
|
||||
"""Webhook consumer for GitLab
|
||||
"""
|
||||
Webhook consumer for GitLab.
|
||||
|
||||
Accepts webhook events from GitLab, 'push' events trigger builds.
|
||||
|
||||
|
@ -195,7 +199,8 @@ class GitLabWebhookView(WebhookMixin, APIView):
|
|||
|
||||
class BitbucketWebhookView(WebhookMixin, APIView):
|
||||
|
||||
"""Webhook consumer for Bitbucket
|
||||
"""
|
||||
Webhook consumer for Bitbucket.
|
||||
|
||||
Accepts webhook events from Bitbucket, 'repo:push' events trigger builds.
|
||||
|
||||
|
@ -236,7 +241,8 @@ class BitbucketWebhookView(WebhookMixin, APIView):
|
|||
|
||||
class IsAuthenticatedOrHasToken(permissions.IsAuthenticated):
|
||||
|
||||
"""Allow authenticated users and requests with token auth through
|
||||
"""
|
||||
Allow authenticated users and requests with token auth through.
|
||||
|
||||
This does not check for instance-level permissions, as the check uses
|
||||
methods from the view to determine if the token matches.
|
||||
|
@ -250,7 +256,8 @@ class IsAuthenticatedOrHasToken(permissions.IsAuthenticated):
|
|||
|
||||
class APIWebhookView(WebhookMixin, APIView):
|
||||
|
||||
"""API webhook consumer
|
||||
"""
|
||||
API webhook consumer.
|
||||
|
||||
Expects the following JSON::
|
||||
|
||||
|
@ -263,7 +270,8 @@ class APIWebhookView(WebhookMixin, APIView):
|
|||
permission_classes = [IsAuthenticatedOrHasToken]
|
||||
|
||||
def get_project(self, **kwargs):
|
||||
"""Get authenticated user projects, or token authed projects
|
||||
"""
|
||||
Get authenticated user projects, or token authed projects.
|
||||
|
||||
Allow for a user to either be authed to receive a project, or require
|
||||
the integration token to be specified as a POST argument.
|
||||
|
@ -304,7 +312,8 @@ class APIWebhookView(WebhookMixin, APIView):
|
|||
|
||||
class WebhookView(APIView):
|
||||
|
||||
"""This is the main webhook view for webhooks with an ID
|
||||
"""
|
||||
This is the main webhook view for webhooks with an ID.
|
||||
|
||||
The handling of each view is handed off to another view. This should only
|
||||
ever get webhook requests for established webhooks on our side. The other
|
||||
|
@ -320,7 +329,7 @@ class WebhookView(APIView):
|
|||
}
|
||||
|
||||
def post(self, request, project_slug, integration_pk):
|
||||
"""Set up webhook post view with request and project objects"""
|
||||
"""Set up webhook post view with request and project objects."""
|
||||
integration = get_object_or_404(
|
||||
Integration,
|
||||
project__slug=project_slug,
|
||||
|
|
|
@ -34,7 +34,8 @@ log = logging.getLogger(__name__)
|
|||
|
||||
class UserSelectViewSet(viewsets.ModelViewSet):
|
||||
|
||||
"""View set that varies serializer class based on request user credentials
|
||||
"""
|
||||
View set that varies serializer class based on request user credentials.
|
||||
|
||||
Viewsets using this class should have an attribute `admin_serializer_class`,
|
||||
which is a serializer that might have more fields that only admin/staff
|
||||
|
@ -50,13 +51,15 @@ class UserSelectViewSet(viewsets.ModelViewSet):
|
|||
return self.serializer_class
|
||||
|
||||
def get_queryset(self):
|
||||
"""Use our API manager method to determine authorization on queryset"""
|
||||
"""Use our API manager method to determine authorization on queryset."""
|
||||
return self.model.objects.api(self.request.user)
|
||||
|
||||
|
||||
class ProjectViewSet(UserSelectViewSet):
|
||||
|
||||
"""List, filter, etc. Projects."""
|
||||
"""
|
||||
List, filter, etc. Projects.
|
||||
"""
|
||||
|
||||
permission_classes = [APIPermission]
|
||||
renderer_classes = (JSONRenderer,)
|
||||
|
@ -131,7 +134,8 @@ class ProjectViewSet(UserSelectViewSet):
|
|||
@decorators.detail_route(permission_classes=[permissions.IsAdminUser], methods=['post'])
|
||||
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.
|
||||
"""
|
||||
|
|
|
@ -21,7 +21,7 @@ log = logging.getLogger(__name__)
|
|||
@decorators.permission_classes((permissions.IsAdminUser,))
|
||||
@decorators.renderer_classes((JSONRenderer,))
|
||||
def index_search(request):
|
||||
"""Add things to the search index"""
|
||||
"""Add things to the search index."""
|
||||
data = request.data['data']
|
||||
version_pk = data['version_pk']
|
||||
commit = data.get('commit')
|
||||
|
@ -41,7 +41,7 @@ def index_search(request):
|
|||
@decorators.permission_classes((permissions.AllowAny,))
|
||||
@decorators.renderer_classes((JSONRenderer,))
|
||||
def search(request):
|
||||
"""Perform search, supplement links by resolving project domains"""
|
||||
"""Perform search, supplement links by resolving project domains."""
|
||||
project_slug = request.GET.get('project', None)
|
||||
version_slug = request.GET.get('version', LATEST)
|
||||
query = request.GET.get('q', None)
|
||||
|
@ -100,7 +100,8 @@ def project_search(request):
|
|||
@decorators.permission_classes((permissions.AllowAny,))
|
||||
@decorators.renderer_classes((JSONRenderer,))
|
||||
def section_search(request):
|
||||
"""Section search
|
||||
"""
|
||||
Section search.
|
||||
|
||||
Queries with query ``q`` across all documents and projects. Queries can be
|
||||
limited to a single project or version by using the ``project`` and
|
||||
|
@ -129,7 +130,6 @@ def section_search(request):
|
|||
Example::
|
||||
|
||||
GET /api/v2/search/section/?q=virtualenv&project=django
|
||||
|
||||
"""
|
||||
query = request.GET.get('q', None)
|
||||
if not query:
|
||||
|
|
|
@ -37,20 +37,22 @@ class RTDTestCase(TestCase):
|
|||
@patch('readthedocs.projects.views.private.trigger_build', lambda x, basic: None)
|
||||
class MockBuildTestCase(TestCase):
|
||||
|
||||
"""Mock build triggers for test cases"""
|
||||
"""Mock build triggers for test cases."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class RequestFactoryTestMixin(object):
|
||||
|
||||
"""Adds helper methods for testing with :py:class:`RequestFactory`
|
||||
"""
|
||||
Adds helper methods for testing with :py:class:`RequestFactory`
|
||||
|
||||
This handles setting up authentication, messages, and session handling
|
||||
"""
|
||||
|
||||
def request(self, *args, **kwargs):
|
||||
"""Perform request from factory
|
||||
"""
|
||||
Perform request from factory.
|
||||
|
||||
:param method: Request method as string
|
||||
:returns: Request instance
|
||||
|
@ -88,7 +90,7 @@ class RequestFactoryTestMixin(object):
|
|||
|
||||
class WizardTestCase(RequestFactoryTestMixin, TestCase):
|
||||
|
||||
"""Test case for testing wizard forms"""
|
||||
"""Test case for testing wizard forms."""
|
||||
|
||||
step_data = OrderedDict({})
|
||||
url = None
|
||||
|
@ -97,7 +99,8 @@ class WizardTestCase(RequestFactoryTestMixin, TestCase):
|
|||
|
||||
@patch('readthedocs.projects.views.private.trigger_build', lambda x, basic: None)
|
||||
def post_step(self, step, **kwargs):
|
||||
"""Post step form data to `url`, using supplementary `kwargs`
|
||||
"""
|
||||
Post step form data to `url`, using supplementary `kwargs`
|
||||
|
||||
Use data from kwargs to build dict to pass into form
|
||||
"""
|
||||
|
@ -121,7 +124,7 @@ class WizardTestCase(RequestFactoryTestMixin, TestCase):
|
|||
# We use camelCase on purpose here to conform with unittest's naming
|
||||
# conventions.
|
||||
def assertWizardResponse(self, response, step=None): # noqa
|
||||
"""Assert successful wizard response"""
|
||||
"""Assert successful wizard response."""
|
||||
# This is the last form
|
||||
if step is None:
|
||||
try:
|
||||
|
@ -149,7 +152,8 @@ class WizardTestCase(RequestFactoryTestMixin, TestCase):
|
|||
# We use camelCase on purpose here to conform with unittest's naming
|
||||
# conventions.
|
||||
def assertWizardFailure(self, response, field, match=None): # noqa
|
||||
"""Assert field threw a validation error
|
||||
"""
|
||||
Assert field threw a validation error.
|
||||
|
||||
response
|
||||
Client response object
|
||||
|
|
|
@ -15,7 +15,7 @@ from readthedocs.search.signals import (before_project_search,
|
|||
|
||||
|
||||
def search_project(request, query, language=None):
|
||||
"""Search index for projects matching query"""
|
||||
"""Search index for projects matching query."""
|
||||
body = {
|
||||
"query": {
|
||||
"bool": {
|
||||
|
@ -50,7 +50,8 @@ def search_project(request, query, language=None):
|
|||
|
||||
|
||||
def search_file(request, query, project_slug=None, version_slug=LATEST, taxonomy=None):
|
||||
"""Search index for files matching query
|
||||
"""
|
||||
Search index for files matching query.
|
||||
|
||||
Raises a 404 error on missing project
|
||||
|
||||
|
@ -163,7 +164,8 @@ def search_file(request, query, project_slug=None, version_slug=LATEST, taxonomy
|
|||
|
||||
def search_section(request, query, project_slug=None, version_slug=LATEST,
|
||||
path=None):
|
||||
"""Search for a section of content
|
||||
"""
|
||||
Search for a section of content.
|
||||
|
||||
When you search, you will have a ``project`` facet, which includes the
|
||||
number of matching sections per project. When you search inside a project,
|
||||
|
|
|
@ -18,7 +18,7 @@ log = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def process_mkdocs_json(version, build_dir=True):
|
||||
"""Given a version object, return a list of page dicts from disk content"""
|
||||
"""Given a version object, return a list of page dicts from disk content."""
|
||||
if build_dir:
|
||||
full_path = version.project.full_json_path(version.slug)
|
||||
else:
|
||||
|
@ -215,7 +215,8 @@ def parse_sphinx_sections(content):
|
|||
|
||||
|
||||
def parse_mkdocs_sections(content):
|
||||
"""Generate a list of sections from mkdocs-style html.
|
||||
"""
|
||||
Generate a list of sections from mkdocs-style html.
|
||||
|
||||
May raise a ValueError
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue