From 23b20cdb8729597c7eae8235d3ff4b5da47fe2a7 Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Thu, 3 Jan 2019 16:42:58 +0100
Subject: [PATCH 01/42] Do not run py27 on tox
---
tox.ini | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/tox.ini b/tox.ini
index 4f96fee1936..ffac6f78c68 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,10 @@
[tox]
minversion=2.9.0
-envlist = py{27,36},lint,docs
+envlist = py36,lint,docs
skipsdist = True
[travis]
python =
- 2.7: py27
3.6: py36, codecov
[testenv]
From 678027c5f6c32ef156d91f13d9327905fed014dd Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Thu, 3 Jan 2019 16:43:31 +0100
Subject: [PATCH 02/42] Do not run py27 on Travis
---
.travis.yml | 1 -
1 file changed, 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 6743bf289bb..bce5b2f3742 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,5 @@
language: python
python:
- - 2.7
- 3.6
env:
- ES_VERSION=1.3.9 ES_DOWNLOAD_URL=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-${ES_VERSION}.tar.gz
From f7dea72b86f09e33014c1fcd7e113eaf875cad2a Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Thu, 3 Jan 2019 16:56:33 +0100
Subject: [PATCH 03/42] pyupgrade 1.11.0 on all code
pyupgrade --py3-plus --py3-only (*.py)
---
readthedocs/api/base.py | 12 ++--
readthedocs/api/utils.py | 2 +-
readthedocs/builds/forms.py | 4 +-
readthedocs/builds/managers.py | 2 +-
readthedocs/builds/migrations/0001_initial.py | 4 +-
readthedocs/builds/models.py | 18 +++---
readthedocs/builds/syncers.py | 4 +-
readthedocs/builds/version_slug.py | 4 +-
readthedocs/builds/views.py | 8 +--
readthedocs/config/config.py | 14 ++---
readthedocs/config/tests/test_config.py | 16 ++---
readthedocs/config/tests/test_parser.py | 18 +++---
readthedocs/config/tests/test_validation.py | 22 +++----
readthedocs/config/validation.py | 8 +--
readthedocs/core/adapters.py | 6 +-
readthedocs/core/forms.py | 10 +--
.../commands/import_github_language.py | 4 +-
readthedocs/core/middleware.py | 8 +--
readthedocs/core/mixins.py | 6 +-
readthedocs/core/permissions.py | 2 +-
readthedocs/core/resolver.py | 14 ++---
readthedocs/core/settings.py | 2 +-
readthedocs/core/static.py | 2 +-
readthedocs/core/symlink.py | 6 +-
readthedocs/core/tests/test_signals.py | 2 +-
readthedocs/core/utils/__init__.py | 2 +-
readthedocs/core/utils/tasks/public.py | 2 +-
readthedocs/core/utils/tasks/retrieve.py | 2 +-
readthedocs/core/views/__init__.py | 4 +-
readthedocs/core/views/hooks.py | 8 +--
readthedocs/core/views/serve.py | 2 +-
readthedocs/doc_builder/backends/mkdocs.py | 2 +-
readthedocs/doc_builder/backends/sphinx.py | 24 +++----
readthedocs/doc_builder/base.py | 2 +-
readthedocs/doc_builder/environments.py | 26 ++++----
readthedocs/doc_builder/exceptions.py | 2 +-
.../doc_builder/python_environments.py | 8 +--
readthedocs/gold/forms.py | 8 +--
readthedocs/gold/models.py | 2 +-
readthedocs/gold/views.py | 10 +--
readthedocs/integrations/admin.py | 6 +-
readthedocs/integrations/models.py | 30 ++++-----
readthedocs/notifications/backends.py | 2 +-
readthedocs/notifications/forms.py | 4 +-
readthedocs/notifications/notification.py | 6 +-
readthedocs/notifications/storages.py | 4 +-
readthedocs/notifications/views.py | 8 +--
.../oauth/migrations/0003_move_github.py | 4 +-
readthedocs/oauth/models.py | 2 +-
readthedocs/oauth/notifications.py | 4 +-
readthedocs/oauth/services/base.py | 2 +-
readthedocs/oauth/services/bitbucket.py | 4 +-
readthedocs/payments/forms.py | 6 +-
readthedocs/payments/mixins.py | 4 +-
readthedocs/projects/admin.py | 6 +-
readthedocs/projects/forms.py | 62 +++++++++----------
.../migrations/0007_migrate_canonical_data.py | 4 +-
.../migrations/0010_migrate_domain_data.py | 4 +-
.../migrations/0016_build-queue-name.py | 2 +-
readthedocs/projects/models.py | 26 ++++----
readthedocs/projects/tasks.py | 10 +--
readthedocs/projects/validators.py | 8 +--
readthedocs/projects/version_handling.py | 2 +-
readthedocs/projects/views/base.py | 12 ++--
readthedocs/projects/views/mixins.py | 4 +-
readthedocs/projects/views/private.py | 23 +++----
readthedocs/projects/views/public.py | 10 +--
readthedocs/redirects/models.py | 2 +-
readthedocs/restapi/permissions.py | 4 +-
readthedocs/restapi/serializers.py | 16 ++---
readthedocs/restapi/views/footer_views.py | 4 +-
readthedocs/restapi/views/integrations.py | 10 +--
readthedocs/rtd_tests/base.py | 14 ++---
readthedocs/rtd_tests/files/conf.py | 6 +-
.../fixtures/sample_repo/source/conf.py | 12 ++--
readthedocs/rtd_tests/mocks/environment.py | 2 +-
readthedocs/rtd_tests/mocks/mock_api.py | 6 +-
readthedocs/rtd_tests/tests/test_api.py | 47 +++++++-------
readthedocs/rtd_tests/tests/test_backend.py | 26 ++++----
.../rtd_tests/tests/test_build_config.py | 2 +-
readthedocs/rtd_tests/tests/test_celery.py | 4 +-
.../tests/test_config_integration.py | 2 +-
.../rtd_tests/tests/test_doc_building.py | 56 ++++++++---------
readthedocs/rtd_tests/tests/test_extend.py | 2 +-
.../rtd_tests/tests/test_integrations.py | 32 +++++-----
.../rtd_tests/tests/test_notifications.py | 4 +-
.../rtd_tests/tests/test_privacy_urls.py | 10 +--
readthedocs/rtd_tests/tests/test_project.py | 2 +-
.../rtd_tests/tests/test_project_forms.py | 8 +--
.../rtd_tests/tests/test_project_symlinks.py | 30 ++++-----
.../rtd_tests/tests/test_project_views.py | 15 +++--
readthedocs/rtd_tests/tests/test_resolver.py | 2 +-
readthedocs/rtd_tests/tests/test_urls.py | 2 +-
.../tests/test_version_commit_name.py | 30 ++++-----
readthedocs/search/indexes.py | 4 +-
readthedocs/search/parse_json.py | 6 +-
readthedocs/search/tests/test_views.py | 2 +-
readthedocs/search/utils.py | 4 +-
readthedocs/search/views.py | 2 +-
readthedocs/settings/base.py | 2 +-
readthedocs/settings/dev.py | 2 +-
readthedocs/vcs_support/backends/bzr.py | 4 +-
readthedocs/vcs_support/backends/git.py | 10 +--
readthedocs/vcs_support/backends/hg.py | 4 +-
readthedocs/vcs_support/backends/svn.py | 6 +-
readthedocs/vcs_support/base.py | 6 +-
readthedocs/vcs_support/utils.py | 4 +-
107 files changed, 479 insertions(+), 488 deletions(-)
diff --git a/readthedocs/api/base.py b/readthedocs/api/base.py
index 73b64e431cf..8e90c279b62 100644
--- a/readthedocs/api/base.py
+++ b/readthedocs/api/base.py
@@ -34,7 +34,7 @@ class ProjectResource(ModelResource):
users = fields.ToManyField('readthedocs.api.base.UserResource', 'users')
- class Meta(object):
+ class Meta:
include_absolute_url = True
allowed_methods = ['get', 'post', 'put']
queryset = Project.objects.api()
@@ -48,7 +48,7 @@ class Meta(object):
def get_object_list(self, request):
self._meta.queryset = Project.objects.api(user=request.user)
- return super(ProjectResource, self).get_object_list(request)
+ return super().get_object_list(request)
def dehydrate(self, bundle):
bundle.data['downloads'] = bundle.obj.get_downloads()
@@ -98,7 +98,7 @@ class VersionResource(ModelResource):
project = fields.ForeignKey(ProjectResource, 'project', full=True)
- class Meta(object):
+ class Meta:
allowed_methods = ['get', 'put', 'post']
always_return_data = True
queryset = Version.objects.api()
@@ -112,7 +112,7 @@ class Meta(object):
def get_object_list(self, request):
self._meta.queryset = Version.objects.api(user=request.user)
- return super(VersionResource, self).get_object_list(request)
+ return super().get_object_list(request)
def build_version(self, request, **kwargs):
project = get_object_or_404(Project, slug=kwargs['project_slug'])
@@ -145,7 +145,7 @@ class FileResource(ModelResource):
project = fields.ForeignKey(ProjectResource, 'project', full=True)
- class Meta(object):
+ class Meta:
allowed_methods = ['get', 'post']
queryset = ImportedFile.objects.all()
excludes = ['md5', 'slug']
@@ -190,7 +190,7 @@ class UserResource(ModelResource):
"""Read-only API resource for User model."""
- class Meta(object):
+ class Meta:
allowed_methods = ['get']
queryset = User.objects.all()
fields = ['username', 'id']
diff --git a/readthedocs/api/utils.py b/readthedocs/api/utils.py
index 1daa2deb963..c15cb02ec02 100644
--- a/readthedocs/api/utils.py
+++ b/readthedocs/api/utils.py
@@ -18,7 +18,7 @@ class PostAuthentication(BasicAuthentication):
"""Require HTTP Basic authentication for any method other than GET."""
def is_authenticated(self, request, **kwargs):
- val = super(PostAuthentication, self).is_authenticated(request,
+ val = super().is_authenticated(request,
**kwargs)
if request.method == "GET":
return True
diff --git a/readthedocs/builds/forms.py b/readthedocs/builds/forms.py
index 74fe0dba504..6e5e94b50ee 100644
--- a/readthedocs/builds/forms.py
+++ b/readthedocs/builds/forms.py
@@ -17,7 +17,7 @@
class VersionForm(forms.ModelForm):
- class Meta(object):
+ class Meta:
model = Version
fields = ['active', 'privacy_level', 'tags']
@@ -38,7 +38,7 @@ def _is_default_version(self):
return project.default_version == self.instance.slug
def save(self, commit=True):
- obj = super(VersionForm, self).save(commit=commit)
+ obj = super().save(commit=commit)
if obj.active and not obj.built and not obj.uploaded:
trigger_build(project=obj.project, version=obj)
return obj
diff --git a/readthedocs/builds/managers.py b/readthedocs/builds/managers.py
index 9ef1b2836e5..bd9316c3ef3 100644
--- a/readthedocs/builds/managers.py
+++ b/readthedocs/builds/managers.py
@@ -32,7 +32,7 @@ def from_queryset(cls, queryset_class, class_name=None):
VersionQuerySet,
VersionQuerySet._default_class # pylint: disable=protected-access
)
- return super(VersionManagerBase, cls).from_queryset(queryset_class, class_name)
+ return super().from_queryset(queryset_class, class_name)
def create_stable(self, **kwargs):
defaults = {
diff --git a/readthedocs/builds/migrations/0001_initial.py b/readthedocs/builds/migrations/0001_initial.py
index 32e6e0eb1fd..73ff3a648fe 100644
--- a/readthedocs/builds/migrations/0001_initial.py
+++ b/readthedocs/builds/migrations/0001_initial.py
@@ -77,10 +77,10 @@ class Migration(migrations.Migration):
),
migrations.AlterUniqueTogether(
name='version',
- unique_together=set([('project', 'slug')]),
+ unique_together={('project', 'slug')},
),
migrations.AlterIndexTogether(
name='build',
- index_together=set([('version', 'state', 'type')]),
+ index_together={('version', 'state', 'type')},
),
]
diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py
index 93678975232..b27b7e145af 100644
--- a/readthedocs/builds/models.py
+++ b/readthedocs/builds/models.py
@@ -114,7 +114,7 @@ class Version(models.Model):
objects = VersionManager.from_queryset(VersionQuerySet)()
- class Meta(object):
+ class Meta:
unique_together = [('project', 'slug')]
ordering = ['-verbose_name']
permissions = (
@@ -203,7 +203,7 @@ def get_absolute_url(self):
def save(self, *args, **kwargs): # pylint: disable=arguments-differ
"""Add permissions to the Version for all owners on save."""
from readthedocs.projects import tasks
- obj = super(Version, self).save(*args, **kwargs)
+ obj = super().save(*args, **kwargs)
for owner in self.project.users.all():
assign('view_version', owner, self)
broadcast(
@@ -216,7 +216,7 @@ def delete(self, *args, **kwargs): # pylint: disable=arguments-differ
broadcast(type='app', task=tasks.clear_artifacts, args=[self.get_artifact_paths()])
broadcast(
type='app', task=tasks.symlink_project, args=[self.project.pk])
- super(Version, self).delete(*args, **kwargs)
+ super().delete(*args, **kwargs)
@property
def identifier_friendly(self):
@@ -434,7 +434,7 @@ def __init__(self, *args, **kwargs):
del kwargs[key]
except KeyError:
pass
- super(APIVersion, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def save(self, *args, **kwargs):
return 0
@@ -479,13 +479,13 @@ class Build(models.Model):
CONFIG_KEY = '__config'
- class Meta(object):
+ class Meta:
ordering = ['-date']
get_latest_by = 'date'
index_together = [['version', 'state', 'type']]
def __init__(self, *args, **kwargs):
- super(Build, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self._config_changed = False
@property
@@ -549,7 +549,7 @@ def save(self, *args, **kwargs): # noqa
self._config and self._config == previous.config):
previous_pk = previous._config.get(self.CONFIG_KEY, previous.pk)
self._config = {self.CONFIG_KEY: previous_pk}
- super(Build, self).save(*args, **kwargs)
+ super().save(*args, **kwargs)
self._config_changed = False
def __str__(self):
@@ -571,7 +571,7 @@ def finished(self):
return self.state == BUILD_STATE_FINISHED
-class BuildCommandResultMixin(object):
+class BuildCommandResultMixin:
"""
Mixin for common command result methods/properties.
@@ -611,7 +611,7 @@ class BuildCommandResult(BuildCommandResultMixin, models.Model):
start_time = models.DateTimeField(_('Start time'))
end_time = models.DateTimeField(_('End time'))
- class Meta(object):
+ class Meta:
ordering = ['start_time']
get_latest_by = 'start_time'
diff --git a/readthedocs/builds/syncers.py b/readthedocs/builds/syncers.py
index 0ac0c6dedf6..cf5a9a90e07 100644
--- a/readthedocs/builds/syncers.py
+++ b/readthedocs/builds/syncers.py
@@ -22,7 +22,7 @@
log = logging.getLogger(__name__)
-class BaseSyncer(object):
+class BaseSyncer:
"""A base object for syncers and pullers"""
@@ -64,7 +64,7 @@ def copy(cls, path, target, is_file=False, **kwargs):
if app_servers:
log.info("Remote Copy %s to %s on %s", path, target, app_servers)
for server in app_servers:
- mkdir_cmd = ("ssh %s@%s mkdir -p %s" % (sync_user, server, target))
+ mkdir_cmd = ("ssh {}@{} mkdir -p {}".format(sync_user, server, target))
ret = os.system(mkdir_cmd)
if ret != 0:
log.debug("Copy error to app servers: cmd=%s", mkdir_cmd)
diff --git a/readthedocs/builds/version_slug.py b/readthedocs/builds/version_slug.py
index 61622369da7..7eb71f82798 100644
--- a/readthedocs/builds/version_slug.py
+++ b/readthedocs/builds/version_slug.py
@@ -72,7 +72,7 @@ def __init__(self, *args, **kwargs):
raise ValueError("missing 'populate_from' argument")
else:
self._populate_from = populate_from
- super(VersionSlugField, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def get_queryset(self, model_cls, slug_field):
# pylint: disable=protected-access
@@ -180,6 +180,6 @@ def pre_save(self, model_instance, add):
return value
def deconstruct(self):
- name, path, args, kwargs = super(VersionSlugField, self).deconstruct()
+ name, path, args, kwargs = super().deconstruct()
kwargs['populate_from'] = self._populate_from
return name, path, args, kwargs
diff --git a/readthedocs/builds/views.py b/readthedocs/builds/views.py
index 7cbe310fbb5..680bd271c29 100644
--- a/readthedocs/builds/views.py
+++ b/readthedocs/builds/views.py
@@ -33,7 +33,7 @@
log = logging.getLogger(__name__)
-class BuildBase(object):
+class BuildBase:
model = Build
def get_queryset(self):
@@ -49,7 +49,7 @@ def get_queryset(self):
return queryset
-class BuildTriggerMixin(object):
+class BuildTriggerMixin:
@method_decorator(login_required)
def post(self, request, project_slug):
@@ -85,7 +85,7 @@ def post(self, request, project_slug):
class BuildList(BuildBase, BuildTriggerMixin, ListView):
def get_context_data(self, **kwargs):
- context = super(BuildList, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
active_builds = self.get_queryset().exclude(state='finished'
).values('id')
@@ -104,7 +104,7 @@ class BuildDetail(BuildBase, DetailView):
pk_url_kwarg = 'build_pk'
def get_context_data(self, **kwargs):
- context = super(BuildDetail, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context['project'] = self.project
return context
diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py
index 4b7c48f5649..ac6b3db99a3 100644
--- a/readthedocs/config/config.py
+++ b/readthedocs/config/config.py
@@ -85,7 +85,7 @@ class ConfigError(Exception):
def __init__(self, message, code):
self.code = code
- super(ConfigError, self).__init__(message)
+ super().__init__(message)
class ConfigOptionNotSupportedError(ConfigError):
@@ -97,7 +97,7 @@ def __init__(self, configuration):
template = (
'The "{}" configuration option is not supported in this version'
)
- super(ConfigOptionNotSupportedError, self).__init__(
+ super().__init__(
template.format(self.configuration),
CONFIG_NOT_SUPPORTED
)
@@ -118,10 +118,10 @@ def __init__(self, key, code, error_message, source_file=None):
code=code,
error=error_message,
)
- super(InvalidConfig, self).__init__(message, code=code)
+ super().__init__(message, code=code)
-class BuildConfigBase(object):
+class BuildConfigBase:
"""
Config that handles the build of one particular documentation.
@@ -229,7 +229,7 @@ def validate(self):
@property
def python_interpreter(self):
ver = self.python_full_version
- return 'python{0}'.format(ver)
+ return 'python{}'.format(ver)
@property
def python_full_version(self):
@@ -481,7 +481,7 @@ def validate_python(self):
# Try to convert strings to an int first, to catch '2', then
# a float, to catch '2.7'
version = raw_python['version']
- if isinstance(version, six.string_types):
+ if isinstance(version, str):
try:
version = int(version)
except ValueError:
@@ -729,7 +729,7 @@ def validate_python(self):
python = {}
with self.catch_validation_error('python.version'):
version = self.pop_config('python.version', 3)
- if isinstance(version, six.string_types):
+ if isinstance(version, str):
try:
version = int(version)
except ValueError:
diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py
index f623881a615..29b776d0fb5 100644
--- a/readthedocs/config/tests/test_config.py
+++ b/readthedocs/config/tests/test_config.py
@@ -268,7 +268,7 @@ def test_python_pip_install_default():
assert build.python.install_with_pip is False
-class TestValidatePythonExtraRequirements(object):
+class TestValidatePythonExtraRequirements:
def test_it_defaults_to_list(self):
build = get_build_config({'python': {}}, get_env_config())
@@ -302,7 +302,7 @@ def test_it_uses_validate_string(self, validate_string):
validate_string.assert_any_call('tests')
-class TestValidateUseSystemSitePackages(object):
+class TestValidateUseSystemSitePackages:
def test_it_defaults_to_false(self):
build = get_build_config({'python': {}}, get_env_config())
@@ -330,7 +330,7 @@ def test_it_uses_validate_bool(self, validate_bool):
validate_bool.assert_any_call('to-validate')
-class TestValidateSetupPyInstall(object):
+class TestValidateSetupPyInstall:
def test_it_defaults_to_false(self):
build = get_build_config({'python': {}}, get_env_config())
@@ -358,7 +358,7 @@ def test_it_uses_validate_bool(self, validate_bool):
validate_bool.assert_any_call('to-validate')
-class TestValidatePythonVersion(object):
+class TestValidatePythonVersion:
def test_it_defaults_to_a_valid_version(self):
build = get_build_config({'python': {}}, get_env_config())
@@ -458,7 +458,7 @@ def test_it_respects_default_value(self, value):
assert build.python.version == value
-class TestValidateFormats(object):
+class TestValidateFormats:
def test_it_defaults_to_empty(self):
build = get_build_config({}, get_env_config())
@@ -537,7 +537,7 @@ def test_valid_build_config():
assert build.output_base
-class TestValidateBase(object):
+class TestValidateBase:
def test_it_validates_to_abspath(self, tmpdir):
apply_fs(tmpdir, {'configs': minimal_config, 'docs': {}})
@@ -586,7 +586,7 @@ def test_it_fails_if_base_does_not_exist(self, tmpdir):
assert excinfo.value.code == INVALID_PATH
-class TestValidateBuild(object):
+class TestValidateBuild:
def test_it_fails_if_build_is_invalid_option(self, tmpdir):
apply_fs(tmpdir, minimal_config)
@@ -842,7 +842,7 @@ def test_as_dict(tmpdir):
assert build.as_dict() == expected_dict
-class TestBuildConfigV2(object):
+class TestBuildConfigV2:
def get_build_config(
self, config, env_config=None, source_file='readthedocs.yml'):
diff --git a/readthedocs/config/tests/test_parser.py b/readthedocs/config/tests/test_parser.py
index 5c37c3c5cb0..4cbe457bbe8 100644
--- a/readthedocs/config/tests/test_parser.py
+++ b/readthedocs/config/tests/test_parser.py
@@ -8,57 +8,57 @@
def test_parse_empty_config_file():
- buf = StringIO(u'')
+ buf = StringIO('')
with raises(ParseError):
parse(buf)
def test_parse_invalid_yaml():
- buf = StringIO(u'- - !asdf')
+ buf = StringIO('- - !asdf')
with raises(ParseError):
parse(buf)
def test_parse_bad_type():
- buf = StringIO(u'Hello')
+ buf = StringIO('Hello')
with raises(ParseError):
parse(buf)
def test_parse_single_config():
- buf = StringIO(u'base: path')
+ buf = StringIO('base: path')
config = parse(buf)
assert isinstance(config, dict)
assert config['base'] == 'path'
def test_parse_null_value():
- buf = StringIO(u'base: null')
+ buf = StringIO('base: null')
config = parse(buf)
assert config['base'] is None
def test_parse_empty_value():
- buf = StringIO(u'base:')
+ buf = StringIO('base:')
config = parse(buf)
assert config['base'] is None
def test_parse_empty_string_value():
- buf = StringIO(u'base: ""')
+ buf = StringIO('base: ""')
config = parse(buf)
assert config['base'] == ''
def test_parse_empty_list():
- buf = StringIO(u'base: []')
+ buf = StringIO('base: []')
config = parse(buf)
assert config['base'] == []
def test_do_not_parse_multiple_configs_in_one_file():
buf = StringIO(
- u'''
+ '''
base: path
---
base: other_path
diff --git a/readthedocs/config/tests/test_validation.py b/readthedocs/config/tests/test_validation.py
index 8c2519570d2..6437acfbff2 100644
--- a/readthedocs/config/tests/test_validation.py
+++ b/readthedocs/config/tests/test_validation.py
@@ -14,7 +14,7 @@
validate_path, validate_string)
-class TestValidateBool(object):
+class TestValidateBool:
def test_it_accepts_true(self):
assert validate_bool(True) is True
@@ -33,7 +33,7 @@ def test_it_fails_on_string(self):
assert excinfo.value.code == INVALID_BOOL
-class TestValidateChoice(object):
+class TestValidateChoice:
def test_it_accepts_valid_choice(self):
result = validate_choice('choice', ('choice', 'another_choice'))
@@ -49,7 +49,7 @@ def test_it_rejects_invalid_choice(self):
assert excinfo.value.code == INVALID_CHOICE
-class TestValidateList(object):
+class TestValidateList:
def test_it_accepts_list_types(self):
result = validate_list(['choice', 'another_choice'])
@@ -74,12 +74,12 @@ def test_it_rejects_string_types(self):
assert excinfo.value.code == INVALID_LIST
-class TestValidateDirectory(object):
+class TestValidateDirectory:
def test_it_uses_validate_path(self, tmpdir):
patcher = patch('readthedocs.config.validation.validate_path')
with patcher as validate_path:
- path = text_type(tmpdir.mkdir('a directory'))
+ path = str(tmpdir.mkdir('a directory'))
validate_path.return_value = path
validate_directory(path, str(tmpdir))
validate_path.assert_called_with(path, str(tmpdir))
@@ -91,7 +91,7 @@ def test_it_rejects_files(self, tmpdir):
assert excinfo.value.code == INVALID_DIRECTORY
-class TestValidateFile(object):
+class TestValidateFile:
def test_it_uses_validate_path(self, tmpdir):
patcher = patch('readthedocs.config.validation.validate_path')
@@ -110,7 +110,7 @@ def test_it_rejects_directories(self, tmpdir):
assert excinfo.value.code == INVALID_FILE
-class TestValidatePath(object):
+class TestValidatePath:
def test_it_accepts_relative_path(self, tmpdir):
tmpdir.mkdir('a directory')
@@ -140,15 +140,15 @@ def test_it_rejects_non_existent_path(self, tmpdir):
assert excinfo.value.code == INVALID_PATH
-class TestValidateString(object):
+class TestValidateString:
def test_it_accepts_unicode(self):
- result = validate_string(u'Unicöde')
- assert isinstance(result, text_type)
+ result = validate_string('Unicöde')
+ assert isinstance(result, str)
def test_it_accepts_nonunicode(self):
result = validate_string('Unicode')
- assert isinstance(result, text_type)
+ assert isinstance(result, str)
def test_it_rejects_float(self):
with raises(ValidationError) as excinfo:
diff --git a/readthedocs/config/validation.py b/readthedocs/config/validation.py
index ab9164f335e..b1b42fc6775 100644
--- a/readthedocs/config/validation.py
+++ b/readthedocs/config/validation.py
@@ -41,12 +41,12 @@ def __init__(self, value, code, format_kwargs=None):
if format_kwargs is not None:
defaults.update(format_kwargs)
message = self.messages[code].format(**defaults)
- super(ValidationError, self).__init__(message)
+ super().__init__(message)
def validate_list(value):
"""Check if ``value`` is an iterable."""
- if isinstance(value, (dict, string_types)):
+ if isinstance(value, (dict, (str,))):
raise ValidationError(value, INVALID_LIST)
if not hasattr(value, '__iter__'):
raise ValidationError(value, INVALID_LIST)
@@ -113,6 +113,6 @@ def validate_path(value, base_path):
def validate_string(value):
"""Check that ``value`` is a string type."""
- if not isinstance(value, string_types):
+ if not isinstance(value, str):
raise ValidationError(value, INVALID_STRING)
- return text_type(value)
+ return str(value)
diff --git a/readthedocs/core/adapters.py b/readthedocs/core/adapters.py
index 170f8954e26..5bfbf6d5c2e 100644
--- a/readthedocs/core/adapters.py
+++ b/readthedocs/core/adapters.py
@@ -26,7 +26,7 @@ def format_email_subject(self, subject):
def send_mail(self, template_prefix, email, context):
subject = render_to_string(
- '{0}_subject.txt'.format(template_prefix), context
+ '{}_subject.txt'.format(template_prefix), context
)
subject = " ".join(subject.splitlines()).strip()
subject = self.format_email_subject(subject)
@@ -47,7 +47,7 @@ def send_mail(self, template_prefix, email, context):
send_email(
recipient=email,
subject=subject,
- template='{0}_message.txt'.format(template_prefix),
- template_html='{0}_message.html'.format(template_prefix),
+ template='{}_message.txt'.format(template_prefix),
+ template_html='{}_message.html'.format(template_prefix),
context=context
)
diff --git a/readthedocs/core/forms.py b/readthedocs/core/forms.py
index bc062286547..fdc93ffe28b 100644
--- a/readthedocs/core/forms.py
+++ b/readthedocs/core/forms.py
@@ -21,13 +21,13 @@ class UserProfileForm(forms.ModelForm):
first_name = CharField(label=_('First name'), required=False, max_length=30)
last_name = CharField(label=_('Last name'), required=False, max_length=30)
- class Meta(object):
+ class Meta:
model = UserProfile
# Don't allow users edit someone else's user page
fields = ['first_name', 'last_name', 'homepage']
def __init__(self, *args, **kwargs):
- super(UserProfileForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
try:
self.fields['first_name'].initial = self.instance.user.first_name
self.fields['last_name'].initial = self.instance.user.last_name
@@ -37,7 +37,7 @@ def __init__(self, *args, **kwargs):
def save(self, commit=True):
first_name = self.cleaned_data.pop('first_name', None)
last_name = self.cleaned_data.pop('last_name', None)
- profile = super(UserProfileForm, self).save(commit=commit)
+ profile = super().save(commit=commit)
if commit:
user = profile.user
user.first_name = first_name
@@ -52,7 +52,7 @@ class UserDeleteForm(forms.ModelForm):
help_text=_('Please type your username to confirm.'),
)
- class Meta(object):
+ class Meta:
model = User
fields = ['username']
@@ -66,7 +66,7 @@ def clean_username(self):
class UserAdvertisingForm(forms.ModelForm):
- class Meta(object):
+ class Meta:
model = UserProfile
fields = ['allow_ads']
diff --git a/readthedocs/core/management/commands/import_github_language.py b/readthedocs/core/management/commands/import_github_language.py
index ef2945ea557..f53a8088763 100644
--- a/readthedocs/core/management/commands/import_github_language.py
+++ b/readthedocs/core/management/commands/import_github_language.py
@@ -53,7 +53,7 @@ def handle(self, *args, **options):
print('No GitHub repo for %s' % repo_url)
continue
- cache_key = '%s-%s' % (user, repo)
+ cache_key = '{}-{}'.format(user, repo)
top_lang = cache.get(cache_key, None)
if not top_lang:
url = 'https://api.github.com/repos/{user}/{repo}/languages'.format(
@@ -73,7 +73,7 @@ def handle(self, *args, **options):
print('Cached top_lang: %s' % top_lang)
if top_lang in PL_DICT:
slug = PL_DICT[top_lang]
- print('Setting %s to %s' % (repo_url, slug))
+ print('Setting {} to {}'.format(repo_url, slug))
Project.objects.filter(pk=project.pk).update(programming_language=slug)
else:
print('Language unknown: %s' % top_lang)
diff --git a/readthedocs/core/middleware.py b/readthedocs/core/middleware.py
index 262fcb064e0..4556d92ff90 100644
--- a/readthedocs/core/middleware.py
+++ b/readthedocs/core/middleware.py
@@ -19,7 +19,7 @@
log = logging.getLogger(__name__)
-LOG_TEMPLATE = u"(Middleware) {msg} [{host}{path}]"
+LOG_TEMPLATE = "(Middleware) {msg} [{host}{path}]"
SUBDOMAIN_URLCONF = getattr(
settings,
'SUBDOMAIN_URLCONF',
@@ -112,7 +112,7 @@ def process_request(self, request):
cache.set(host, slug, 60 * 60)
# Cache the slug -> host mapping permanently.
log.info(LOG_TEMPLATE.format(
- msg='CNAME cached: %s->%s' % (slug, host),
+ msg='CNAME cached: {}->{}'.format(slug, host),
**log_kwargs))
request.slug = slug
request.urlconf = SUBDOMAIN_URLCONF
@@ -240,11 +240,11 @@ def process_request(self, request):
# Hack request.session otherwise the Authentication middleware complains.
request.session = {}
return
- super(FooterNoSessionMiddleware, self).process_request(request)
+ super().process_request(request)
def process_response(self, request, response):
for url in self.IGNORE_URLS:
if (request.path_info.startswith(url) and
settings.SESSION_COOKIE_NAME not in request.COOKIES):
return response
- return super(FooterNoSessionMiddleware, self).process_response(request, response)
+ return super().process_response(request, response)
diff --git a/readthedocs/core/mixins.py b/readthedocs/core/mixins.py
index 7655db20bd4..45bf2be75ab 100644
--- a/readthedocs/core/mixins.py
+++ b/readthedocs/core/mixins.py
@@ -12,13 +12,13 @@ class ListViewWithForm(ListView):
"""List view that also exposes a create form"""
def get_context_data(self, **kwargs):
- context = super(ListViewWithForm, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context['form'] = self.get_form(data=None, files=None)
return context
-class LoginRequiredMixin(object):
+class LoginRequiredMixin:
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
- return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)
+ return super().dispatch(*args, **kwargs)
diff --git a/readthedocs/core/permissions.py b/readthedocs/core/permissions.py
index c8a7fe6821b..ad2427b9b25 100644
--- a/readthedocs/core/permissions.py
+++ b/readthedocs/core/permissions.py
@@ -5,7 +5,7 @@
from readthedocs.core.utils.extend import SettingsOverrideObject
-class AdminPermissionBase(object):
+class AdminPermissionBase:
@classmethod
def is_admin(cls, user, project):
diff --git a/readthedocs/core/resolver.py b/readthedocs/core/resolver.py
index 43fce68b3f6..a17e832160c 100644
--- a/readthedocs/core/resolver.py
+++ b/readthedocs/core/resolver.py
@@ -10,7 +10,7 @@
from readthedocs.core.utils.extend import SettingsOverrideObject
-class ResolverBase(object):
+class ResolverBase:
"""
Read the Docs URL Resolver.
@@ -59,17 +59,17 @@ def base_resolve_path(self, project_slug, filename, version_slug=None,
# the path should always have a subdomain or CNAME domain
# pylint: disable=unused-argument
if subdomain or cname or (self._use_subdomain()):
- url = u'/'
+ url = '/'
else:
- url = u'/docs/{project_slug}/'
+ url = '/docs/{project_slug}/'
if subproject_slug:
- url += u'projects/{subproject_slug}/'
+ url += 'projects/{subproject_slug}/'
if single_version:
- url += u'{filename}'
+ url += '{filename}'
else:
- url += u'{language}/{version_slug}/{filename}'
+ url += '{language}/{version_slug}/{filename}'
return url.format(
project_slug=project_slug, filename=filename,
@@ -212,7 +212,7 @@ def _get_project_subdomain(self, project):
if self._use_subdomain():
project = self._get_canonical_project(project)
subdomain_slug = project.slug.replace('_', '-')
- return "%s.%s" % (subdomain_slug, public_domain)
+ return "{}.{}".format(subdomain_slug, public_domain)
def _get_project_custom_domain(self, project):
return project.domains.filter(canonical=True).first()
diff --git a/readthedocs/core/settings.py b/readthedocs/core/settings.py
index d66c6d02d28..b758cb57fba 100644
--- a/readthedocs/core/settings.py
+++ b/readthedocs/core/settings.py
@@ -6,7 +6,7 @@
import sys
-class Settings(object):
+class Settings:
"""Class-based settings wrapper."""
diff --git a/readthedocs/core/static.py b/readthedocs/core/static.py
index 89cd883877e..b7ec2b7962f 100644
--- a/readthedocs/core/static.py
+++ b/readthedocs/core/static.py
@@ -15,4 +15,4 @@ class SelectiveFileSystemFinder(FileSystemFinder):
def list(self, ignore_patterns):
ignore_patterns.extend(['epub', 'pdf', 'htmlzip', 'json', 'man'])
- return super(SelectiveFileSystemFinder, self).list(ignore_patterns)
+ return super().list(ignore_patterns)
diff --git a/readthedocs/core/symlink.py b/readthedocs/core/symlink.py
index ec110cf6a15..dcaad60bc3e 100644
--- a/readthedocs/core/symlink.py
+++ b/readthedocs/core/symlink.py
@@ -77,7 +77,7 @@
log = logging.getLogger(__name__)
-class Symlink(object):
+class Symlink:
"""Base class for symlinking of projects."""
@@ -177,7 +177,7 @@ def symlink_cnames(self, domain=None):
def remove_symlink_cname(self, domain):
"""Remove CNAME symlink."""
- log_msg = "Removing symlink for CNAME {0}".format(domain.domain)
+ log_msg = "Removing symlink for CNAME {}".format(domain.domain)
log.info(constants.LOG_TEMPLATE.format(project=self.project.slug,
version='', msg=log_msg))
symlink = os.path.join(self.CNAME_ROOT, domain.domain)
@@ -205,7 +205,7 @@ def symlink_subprojects(self):
from_to[rel.alias] = rel.child.slug
subprojects.add(rel.alias)
for from_slug, to_slug in list(from_to.items()):
- log_msg = "Symlinking subproject: {0} -> {1}".format(from_slug, to_slug)
+ log_msg = "Symlinking subproject: {} -> {}".format(from_slug, to_slug)
log.info(constants.LOG_TEMPLATE.format(project=self.project.slug,
version='', msg=log_msg))
symlink = os.path.join(self.subproject_root, from_slug)
diff --git a/readthedocs/core/tests/test_signals.py b/readthedocs/core/tests/test_signals.py
index c38705a1f8c..6c906c9e87d 100644
--- a/readthedocs/core/tests/test_signals.py
+++ b/readthedocs/core/tests/test_signals.py
@@ -8,7 +8,7 @@
@pytest.mark.django_db
-class TestProjectOrganizationSignal(object):
+class TestProjectOrganizationSignal:
@pytest.mark.parametrize('model_class', [Project, RemoteOrganization])
def test_project_organization_get_deleted_upon_user_delete(self, model_class):
diff --git a/readthedocs/core/utils/__init__.py b/readthedocs/core/utils/__init__.py
index 1927c98b74f..0b8134751a0 100644
--- a/readthedocs/core/utils/__init__.py
+++ b/readthedocs/core/utils/__init__.py
@@ -221,7 +221,7 @@ def slugify(value, *args, **kwargs):
return value
-slugify = allow_lazy(slugify, six.text_type, SafeText)
+slugify = allow_lazy(slugify, str, SafeText)
def safe_makedirs(directory_name):
diff --git a/readthedocs/core/utils/tasks/public.py b/readthedocs/core/utils/tasks/public.py
index 9fb2948ef71..1e51fc65b29 100644
--- a/readthedocs/core/utils/tasks/public.py
+++ b/readthedocs/core/utils/tasks/public.py
@@ -119,7 +119,7 @@ class TaskNoPermission(Exception):
def __init__(self, task_id, *args, **kwargs):
message = 'No permission to access task with id {id}'.format(
id=task_id)
- super(TaskNoPermission, self).__init__(message, *args, **kwargs)
+ super().__init__(message, *args, **kwargs)
def get_public_task_data(request, task_id):
diff --git a/readthedocs/core/utils/tasks/retrieve.py b/readthedocs/core/utils/tasks/retrieve.py
index c96b7823706..0aa3e7570f0 100644
--- a/readthedocs/core/utils/tasks/retrieve.py
+++ b/readthedocs/core/utils/tasks/retrieve.py
@@ -16,7 +16,7 @@
class TaskNotFound(Exception):
def __init__(self, task_id, *args, **kwargs):
message = 'No public task found with id {id}'.format(id=task_id)
- super(TaskNotFound, self).__init__(message, *args, **kwargs)
+ super().__init__(message, *args, **kwargs)
def get_task_data(task_id):
diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py
index 6f25e9bd92b..85aec548c42 100644
--- a/readthedocs/core/views/__init__.py
+++ b/readthedocs/core/views/__init__.py
@@ -35,7 +35,7 @@ class HomepageView(TemplateView):
def get_context_data(self, **kwargs):
"""Add latest builds and featured projects."""
- context = super(HomepageView, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context['featured_list'] = Project.objects.filter(featured=True)
context['projects_count'] = Project.objects.count()
return context
@@ -45,7 +45,7 @@ class SupportView(TemplateView):
template_name = 'support.html'
def get_context_data(self, **kwargs):
- context = super(SupportView, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
support_email = getattr(settings, 'SUPPORT_EMAIL', None)
if not support_email:
support_email = 'support@{domain}'.format(
diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py
index 6b00b2d77ef..3156e3cd4fb 100644
--- a/readthedocs/core/views/hooks.py
+++ b/readthedocs/core/views/hooks.py
@@ -156,14 +156,14 @@ def _build_url(url, projects, branches):
for project_slug, built in list(all_built.items()):
if built:
- msg = '(URL Build) Build Started: %s [%s]' % (
+ msg = '(URL Build) Build Started: {} [{}]'.format(
url, ' '.join(built))
log_info(project_slug, msg=msg)
ret += msg
for project_slug, not_building in list(all_not_building.items()):
if not_building:
- msg = '(URL Build) Not Building: %s [%s]' % (
+ msg = '(URL Build) Not Building: {} [{}]'.format(
url, ' '.join(not_building))
log_info(project_slug, msg=msg)
ret += msg
@@ -300,7 +300,7 @@ def bitbucket_build(request):
repository = data['repository']
if not repository['absolute_url']:
return HttpResponse('Invalid request', status=400)
- search_url = 'bitbucket.org{0}'.format(
+ search_url = 'bitbucket.org{}'.format(
repository['absolute_url'].rstrip('/')
)
elif version == 2:
@@ -309,7 +309,7 @@ def bitbucket_build(request):
for change in changes]
if not data['repository']['full_name']:
return HttpResponse('Invalid request', status=400)
- search_url = 'bitbucket.org/{0}'.format(
+ search_url = 'bitbucket.org/{}'.format(
data['repository']['full_name']
)
except (TypeError, ValueError, KeyError):
diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py
index 36510440ae3..d663dc79180 100644
--- a/readthedocs/core/views/serve.py
+++ b/readthedocs/core/views/serve.py
@@ -116,7 +116,7 @@ def redirect_page_with_filename(request, project, subproject, filename): # pyli
def _serve_401(request, project):
res = render(request, '401.html')
res.status_code = 401
- log.debug('Unauthorized access to {0} documentation'.format(project.slug))
+ log.debug('Unauthorized access to {} documentation'.format(project.slug))
return res
diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py
index 6119673b8fa..eeb06379c13 100644
--- a/readthedocs/doc_builder/backends/mkdocs.py
+++ b/readthedocs/doc_builder/backends/mkdocs.py
@@ -44,7 +44,7 @@ class BaseMkdocs(BaseBuilder):
DEFAULT_THEME_NAME = 'mkdocs'
def __init__(self, *args, **kwargs):
- super(BaseMkdocs, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.old_artifact_path = os.path.join(
self.version.project.checkout_path(self.version.slug),
self.build_dir)
diff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py
index 1d280b8a318..4f2f7b33829 100644
--- a/readthedocs/doc_builder/backends/sphinx.py
+++ b/readthedocs/doc_builder/backends/sphinx.py
@@ -40,7 +40,7 @@ class BaseSphinx(BaseBuilder):
"""The parent for most sphinx builders."""
def __init__(self, *args, **kwargs):
- super(BaseSphinx, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.config_file = self.config.sphinx.configuration
try:
if not self.config_file:
@@ -172,13 +172,9 @@ def append_conf(self, **__):
outfile = codecs.open(self.config_file, encoding='utf-8', mode='a')
except (ProjectConfigurationError, IOError):
trace = sys.exc_info()[2]
- six.reraise(
- ProjectConfigurationError,
- ProjectConfigurationError(
+ raise ProjectConfigurationError(
ProjectConfigurationError.NOT_FOUND
- ),
- trace
- )
+ ).with_traceback(trace)
# Append config to project conf file
tmpl = template_loader.get_template('doc_builder/conf.py.tmpl')
@@ -234,11 +230,11 @@ class HtmlBuilder(BaseSphinx):
sphinx_build_dir = '_build/html'
def __init__(self, *args, **kwargs):
- super(HtmlBuilder, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.sphinx_builder = 'readthedocs'
def move(self, **__):
- super(HtmlBuilder, self).move()
+ super().move()
# Copy JSON artifacts to its own directory
# to keep compatibility with the older builder.
json_path = os.path.abspath(
@@ -265,7 +261,7 @@ class HtmlDirBuilder(HtmlBuilder):
type = 'sphinx_htmldir'
def __init__(self, *args, **kwargs):
- super(HtmlDirBuilder, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.sphinx_builder = 'readthedocsdirhtml'
@@ -273,7 +269,7 @@ class SingleHtmlBuilder(HtmlBuilder):
type = 'sphinx_singlehtml'
def __init__(self, *args, **kwargs):
- super(SingleHtmlBuilder, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.sphinx_builder = 'readthedocssinglehtml'
@@ -338,7 +334,7 @@ class LatexBuildCommand(BuildCommand):
"""Ignore LaTeX exit code if there was file output."""
def run(self):
- super(LatexBuildCommand, self).run()
+ super().run()
# Force LaTeX exit code to be a little more optimistic. If LaTeX
# reports an output file, let's just assume we're fine.
if PDF_RE.search(self.output):
@@ -350,7 +346,7 @@ class DockerLatexBuildCommand(DockerBuildCommand):
"""Ignore LaTeX exit code if there was file output."""
def run(self):
- super(DockerLatexBuildCommand, self).run()
+ super().run()
# Force LaTeX exit code to be a little more optimistic. If LaTeX
# reports an output file, let's just assume we're fine.
if PDF_RE.search(self.output):
@@ -395,7 +391,7 @@ def build(self):
['pdflatex', '-interaction=nonstopmode', tex_file]
for tex_file in tex_files] # yapf: disable
makeindex_cmds = [
- ['makeindex', '-s', 'python.ist', '{0}.idx'.format(
+ ['makeindex', '-s', 'python.ist', '{}.idx'.format(
os.path.splitext(os.path.relpath(tex_file, latex_cwd))[0])]
for tex_file in tex_files] # yapf: disable
diff --git a/readthedocs/doc_builder/base.py b/readthedocs/doc_builder/base.py
index 83aac0da617..c382fefc393 100644
--- a/readthedocs/doc_builder/base.py
+++ b/readthedocs/doc_builder/base.py
@@ -26,7 +26,7 @@ def decorator(*args, **kw):
return decorator
-class BaseBuilder(object):
+class BaseBuilder:
"""
The Base for all Builders. Defines the API for subclasses.
diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py
index b8980ec6971..10ba06f5c60 100644
--- a/readthedocs/doc_builder/environments.py
+++ b/readthedocs/doc_builder/environments.py
@@ -123,7 +123,7 @@ def __init__(self, command, cwd=None, shell=False, environment=None,
def __str__(self):
# TODO do we want to expose the full command here?
- output = u''
+ output = ''
if self.output is not None:
output = self.output.encode('utf-8')
return '\n'.join([self.get_command(), output])
@@ -178,7 +178,7 @@ def run(self):
if self.input_data is not None:
cmd_input = self.input_data
- if isinstance(cmd_input, six.string_types):
+ if isinstance(cmd_input, str):
cmd_input_bytes = cmd_input.encode('utf-8')
else:
cmd_input_bytes = cmd_input
@@ -336,7 +336,7 @@ def get_wrapped_command(self):
r"\[\\\]\^\`\{\|\}\~])")
prefix = ''
if self.bin_path:
- prefix += 'PATH={0}:$PATH '.format(self.bin_path)
+ prefix += 'PATH={}:$PATH '.format(self.bin_path)
return ("/bin/sh -c 'cd {cwd} && {prefix}{cmd}'"
.format(
cwd=self.cwd,
@@ -345,7 +345,7 @@ def get_wrapped_command(self):
for part in self.command]))))
-class BaseEnvironment(object):
+class BaseEnvironment:
"""
Base environment class.
@@ -418,10 +418,10 @@ def run_command_class(
self.commands.append(build_cmd)
if build_cmd.failed:
- msg = u'Command {cmd} failed'.format(cmd=build_cmd.get_command())
+ msg = 'Command {cmd} failed'.format(cmd=build_cmd.get_command())
if build_cmd.output:
- msg += u':\n{out}'.format(out=build_cmd.output)
+ msg += ':\n{out}'.format(out=build_cmd.output)
if warn_only:
log.warning(LOG_TEMPLATE.format(
@@ -486,7 +486,7 @@ class BuildEnvironment(BaseEnvironment):
def __init__(self, project=None, version=None, build=None, config=None,
record=True, environment=None, update_on_success=True):
- super(BuildEnvironment, self).__init__(project, environment)
+ super().__init__(project, environment)
self.version = version
self.build = build
self.config = config
@@ -562,13 +562,13 @@ def run(self, *cmd, **kwargs):
kwargs.update({
'build_env': self,
})
- return super(BuildEnvironment, self).run(*cmd, **kwargs)
+ return super().run(*cmd, **kwargs)
def run_command_class(self, *cmd, **kwargs): # pylint: disable=arguments-differ
kwargs.update({
'build_env': self,
})
- return super(BuildEnvironment, self).run_command_class(*cmd, **kwargs)
+ return super().run_command_class(*cmd, **kwargs)
@property
def successful(self):
@@ -662,7 +662,7 @@ def update_build(self, state=None):
# Attempt to stop unicode errors on build reporting
for key, val in list(self.build.items()):
- if isinstance(val, six.binary_type):
+ if isinstance(val, bytes):
self.build[key] = val.decode('utf-8', 'ignore')
# We are selective about when we update the build object here
@@ -716,7 +716,7 @@ class DockerBuildEnvironment(BuildEnvironment):
def __init__(self, *args, **kwargs):
self.docker_socket = kwargs.pop('docker_socket', DOCKER_SOCKET)
- super(DockerBuildEnvironment, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.client = None
self.container = None
self.container_name = slugify(
@@ -760,7 +760,7 @@ def __enter__(self):
project=self.project.slug,
version=self.version.slug,
msg=(
- 'Removing stale container {0}'
+ 'Removing stale container {}'
.format(self.container_id)
),
)
@@ -824,7 +824,7 @@ def __exit__(self, exc_type, exc_value, tb):
if not all([exc_type, exc_value, tb]):
exc_type, exc_value, tb = sys.exc_info()
- return super(DockerBuildEnvironment, self).__exit__(exc_type, exc_value, tb)
+ return super().__exit__(exc_type, exc_value, tb)
def get_client(self):
"""Create Docker client connection."""
diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py
index ce2ce3d844b..80582595b00 100644
--- a/readthedocs/doc_builder/exceptions.py
+++ b/readthedocs/doc_builder/exceptions.py
@@ -13,7 +13,7 @@ class BuildEnvironmentException(Exception):
def __init__(self, message=None, **kwargs):
self.status_code = kwargs.pop('status_code', None) or self.status_code or 1
message = message or self.get_default_message()
- super(BuildEnvironmentException, self).__init__(message, **kwargs)
+ super().__init__(message, **kwargs)
def get_default_message(self):
return self.message
diff --git a/readthedocs/doc_builder/python_environments.py b/readthedocs/doc_builder/python_environments.py
index fa9bd9f8bf3..7d85a275f83 100644
--- a/readthedocs/doc_builder/python_environments.py
+++ b/readthedocs/doc_builder/python_environments.py
@@ -29,7 +29,7 @@
log = logging.getLogger(__name__)
-class PythonEnvironment(object):
+class PythonEnvironment:
"""An isolated environment into which Python packages can be installed."""
@@ -73,7 +73,7 @@ def install_package(self):
getattr(settings, 'USE_PIP_INSTALL', False)):
extra_req_param = ''
if self.config.python.extra_requirements:
- extra_req_param = '[{0}]'.format(
+ extra_req_param = '[{}]'.format(
','.join(self.config.python.extra_requirements)
)
self.build_env.run(
@@ -83,7 +83,7 @@ def install_package(self):
'--ignore-installed',
'--cache-dir',
self.project.pip_cache_path,
- '.{0}'.format(extra_req_param),
+ '.{}'.format(extra_req_param),
cwd=self.checkout_path,
bin_path=self.venv_bin(),
)
@@ -195,7 +195,7 @@ def save_environment_json(self):
with open(self.environment_json_path(), 'w') as fpath:
# Compatibility for Py2 and Py3. ``io.TextIOWrapper`` expects
# unicode but ``json.dumps`` returns str in Py2.
- fpath.write(six.text_type(json.dumps(data)))
+ fpath.write(str(json.dumps(data)))
class Virtualenv(PythonEnvironment):
diff --git a/readthedocs/gold/forms.py b/readthedocs/gold/forms.py
index 949ab9c4c61..fead5e26b11 100644
--- a/readthedocs/gold/forms.py
+++ b/readthedocs/gold/forms.py
@@ -24,7 +24,7 @@ class GoldSubscriptionForm(StripeResourceMixin, StripeModelForm):
:py:class:`StripeResourceMixin` for common operations against the Stripe API.
"""
- class Meta(object):
+ class Meta:
model = GoldUser
fields = ['last_4_card_digits', 'level']
@@ -44,7 +44,7 @@ class Meta(object):
def clean(self):
self.instance.user = self.customer
- return super(GoldSubscriptionForm, self).clean()
+ return super().clean()
def validate_stripe(self):
subscription = self.get_subscription()
@@ -97,7 +97,7 @@ class GoldProjectForm(forms.Form):
def __init__(self, active_user, *args, **kwargs):
self.user = kwargs.pop('user', None)
self.projects = kwargs.pop('projects', None)
- super(GoldProjectForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.fields['project'].choices = self.generate_choices(active_user)
def generate_choices(self, active_user):
@@ -114,7 +114,7 @@ def clean_project(self):
return project_slug
def clean(self):
- cleaned_data = super(GoldProjectForm, self).clean()
+ cleaned_data = super().clean()
if self.projects.count() < self.user.num_supported_projects:
return cleaned_data
diff --git a/readthedocs/gold/models.py b/readthedocs/gold/models.py
index b87e0f72345..278dc60227e 100644
--- a/readthedocs/gold/models.py
+++ b/readthedocs/gold/models.py
@@ -62,7 +62,7 @@ class GoldUser(models.Model):
business_vat_id = models.CharField(max_length=128, null=True, blank=True)
def __str__(self):
- return 'Gold Level %s for %s' % (self.level, self.user)
+ return 'Gold Level {} for {}'.format(self.level, self.user)
@property
def num_supported_projects(self):
diff --git a/readthedocs/gold/views.py b/readthedocs/gold/views.py
index a0122d8286c..7e2677e2a25 100644
--- a/readthedocs/gold/views.py
+++ b/readthedocs/gold/views.py
@@ -43,16 +43,16 @@ def get_object(self):
def get_form(self, data=None, files=None, **kwargs):
"""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)
+ return super().get_form(data, files, **kwargs)
def get_success_url(self, **__):
return reverse_lazy('gold_detail')
def get_template_names(self):
- return ('gold/subscription{0}.html'.format(self.template_name_suffix))
+ return ('gold/subscription{}.html'.format(self.template_name_suffix))
def get_context_data(self, **kwargs):
- context = super(GoldSubscriptionMixin, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
domains = Domain.objects.filter(project__users=self.request.user)
context['domains'] = domains
return context
@@ -70,7 +70,7 @@ def get(self, request, *args, **kwargs):
If there is a gold subscription instance, then we show the normal detail
page, otherwise show the registration form
"""
- resp = super(DetailGoldSubscription, self).get(request, *args, **kwargs)
+ resp = super().get(request, *args, **kwargs)
if self.object is None:
return HttpResponseRedirect(reverse('gold_subscription'))
return resp
@@ -94,7 +94,7 @@ class DeleteGoldSubscription(GoldSubscriptionMixin, DeleteView):
def post(self, request, *args, **kwargs):
"""Add success message to delete post."""
- resp = super(DeleteGoldSubscription, self).post(request, *args, **kwargs)
+ resp = super().post(request, *args, **kwargs)
success_message = self.get_success_message({})
if success_message:
messages.success(self.request, success_message)
diff --git a/readthedocs/integrations/admin.py b/readthedocs/integrations/admin.py
index cbeabea02e6..5fabe23be91 100644
--- a/readthedocs/integrations/admin.py
+++ b/readthedocs/integrations/admin.py
@@ -18,7 +18,7 @@ def inner(_, obj):
if include_styles:
formatter = HtmlFormatter(style='colorful')
styles = ''
- return mark_safe('{1}
{2}'.format(
+ return mark_safe('{}
{}'.format(
'float: left;',
obj.formatted_json(field),
styles,
@@ -96,11 +96,11 @@ def exchanges(self, obj):
JSONField doesn't do well with fieldsets for whatever reason. This is
just to link to the exchanges.
"""
- url = urls.reverse('admin:{0}_{1}_changelist'.format(
+ url = urls.reverse('admin:{}_{}_changelist'.format(
HttpExchange._meta.app_label, # pylint: disable=protected-access
HttpExchange._meta.model_name, # pylint: disable=protected-access
))
- return mark_safe('{3} HTTP transactions'.format(
+ return mark_safe('{} HTTP transactions'.format(
url,
'integrations',
obj.pk,
diff --git a/readthedocs/integrations/models.py b/readthedocs/integrations/models.py
index 7514699cef6..032c16c28a4 100644
--- a/readthedocs/integrations/models.py
+++ b/readthedocs/integrations/models.py
@@ -73,11 +73,11 @@ def from_exchange(self, req, resp, related_object, payload=None):
# headers. HTTP headers are prefixed with `HTTP_`, which we remove,
# and because the keys are all uppercase, we'll normalize them to
# title case-y hyphen separated values.
- request_headers = dict(
- (key[5:].title().replace('_', '-'), str(val))
+ request_headers = {
+ key[5:].title().replace('_', '-'): str(val)
for (key, val) in list(req.META.items())
- if key.startswith('HTTP_'),
- ) # yapf: disable
+ if key.startswith('HTTP_')
+ } # yapf: disable
request_headers['Content-Type'] = req.content_type
# Remove unwanted headers
@@ -146,7 +146,7 @@ class HttpExchange(models.Model):
objects = HttpExchangeManager()
- class Meta(object):
+ class Meta:
ordering = ['-date']
def __str__(self):
@@ -191,11 +191,11 @@ class IntegrationQuerySet(models.QuerySet):
def _get_subclass(self, integration_type):
# Build a mapping of integration_type -> class dynamically
- class_map = dict(
- (cls.integration_type_id, cls)
+ class_map = {
+ cls.integration_type_id: cls
for cls in self.model.__subclasses__()
- if hasattr(cls, 'integration_type_id'),
- ) # yapf: disable
+ if hasattr(cls, 'integration_type_id')
+ } # yapf: disable
return class_map.get(integration_type)
def _get_subclass_replacement(self, original):
@@ -215,7 +215,7 @@ def _get_subclass_replacement(self, original):
return new
def get(self, *args, **kwargs):
- original = super(IntegrationQuerySet, self).get(*args, **kwargs)
+ original = super().get(*args, **kwargs)
return self._get_subclass_replacement(original)
def subclass(self, instance):
@@ -285,7 +285,7 @@ class GitHubWebhook(Integration):
integration_type_id = Integration.GITHUB_WEBHOOK
has_sync = True
- class Meta(object):
+ class Meta:
proxy = True
@property
@@ -301,7 +301,7 @@ class BitbucketWebhook(Integration):
integration_type_id = Integration.BITBUCKET_WEBHOOK
has_sync = True
- class Meta(object):
+ class Meta:
proxy = True
@property
@@ -317,7 +317,7 @@ class GitLabWebhook(Integration):
integration_type_id = Integration.GITLAB_WEBHOOK
has_sync = True
- class Meta(object):
+ class Meta:
proxy = True
@property
@@ -333,7 +333,7 @@ class GenericAPIWebhook(Integration):
integration_type_id = Integration.API_WEBHOOK
has_sync = False
- class Meta(object):
+ class Meta:
proxy = True
def save(self, *args, **kwargs): # pylint: disable=arguments-differ
@@ -346,7 +346,7 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ
if token is None:
token = default_token()
self.provider_data = {'token': token}
- super(GenericAPIWebhook, self).save(*args, **kwargs)
+ super().save(*args, **kwargs)
@property
def token(self):
diff --git a/readthedocs/notifications/backends.py b/readthedocs/notifications/backends.py
index 28529f1f1a6..e2552c1bdaa 100644
--- a/readthedocs/notifications/backends.py
+++ b/readthedocs/notifications/backends.py
@@ -39,7 +39,7 @@ def send_notification(request, notification):
backend.send(notification)
-class Backend(object):
+class Backend:
def __init__(self, request):
self.request = request
diff --git a/readthedocs/notifications/forms.py b/readthedocs/notifications/forms.py
index b65c1c15e76..58924308515 100644
--- a/readthedocs/notifications/forms.py
+++ b/readthedocs/notifications/forms.py
@@ -29,12 +29,12 @@ class SendNotificationForm(forms.Form):
def __init__(self, *args, **kwargs):
self.notification_classes = kwargs.pop('notification_classes', [])
- super(SendNotificationForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.fields['source'].choices = [(cls.name, cls.name) for cls
in self.notification_classes]
def clean_source(self):
"""Get the source class from the class name."""
source = self.cleaned_data['source']
- classes = dict((cls.name, cls) for cls in self.notification_classes)
+ classes = {cls.name: cls for cls in self.notification_classes}
return classes.get(source, None)
diff --git a/readthedocs/notifications/notification.py b/readthedocs/notifications/notification.py
index d2300e1f4f0..8329bb91b26 100644
--- a/readthedocs/notifications/notification.py
+++ b/readthedocs/notifications/notification.py
@@ -17,7 +17,7 @@
log = logging.getLogger(__name__)
-class Notification(object):
+class Notification:
"""
An unsent notification linked to an object.
@@ -134,10 +134,10 @@ def __init__(
self.success = success
self.reason = reason
self.extra_context = extra_context or {}
- super(SiteNotification, self).__init__(context_object, request, user)
+ super().__init__(context_object, request, user)
def get_context_data(self):
- context = super(SiteNotification, self).get_context_data()
+ context = super().get_context_data()
context.update(self.extra_context)
return context
diff --git a/readthedocs/notifications/storages.py b/readthedocs/notifications/storages.py
index 1755db952e0..9a98ef7632f 100644
--- a/readthedocs/notifications/storages.py
+++ b/readthedocs/notifications/storages.py
@@ -49,7 +49,7 @@ class FallbackUniqueStorage(FallbackStorage):
def _get(self, *args, **kwargs):
# The database backend for persistent messages doesn't support setting
# messages with ``mark_safe``, therefore, we need to do it broadly here.
- messages, all_ret = (super(FallbackUniqueStorage, self)
+ messages, all_ret = (super()
._get(self, *args, **kwargs))
safe_messages = []
@@ -75,7 +75,7 @@ def add(self, level, message, extra_tags='', *args, **kwargs): # noqa
read=False))
if persist_messages.exists():
return
- super(FallbackUniqueStorage, self).add(level, message, extra_tags,
+ super().add(level, message, extra_tags,
*args, **kwargs)
diff --git a/readthedocs/notifications/views.py b/readthedocs/notifications/views.py
index 0ba04fae0c9..2cbdb0959d4 100644
--- a/readthedocs/notifications/views.py
+++ b/readthedocs/notifications/views.py
@@ -35,7 +35,7 @@ def get_form_kwargs(self):
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.
"""
- kwargs = super(SendNotificationView, self).get_form_kwargs()
+ kwargs = super().get_form_kwargs()
kwargs['notification_classes'] = self.notification_classes
if 'send' not in self.request.POST:
kwargs.pop('data', None)
@@ -44,7 +44,7 @@ def get_form_kwargs(self):
def get_initial(self):
"""Add selected ids to initial form data."""
- initial = super(SendNotificationView, self).get_initial()
+ initial = super().get_initial()
initial['_selected_action'] = self.request.POST.getlist(
admin.ACTION_CHECKBOX_NAME)
return initial
@@ -63,7 +63,7 @@ def form_valid(self, form):
if count == 0:
self.message_user("No recipients to send to", level=messages.ERROR)
else:
- self.message_user("Queued {0} messages".format(count))
+ self.message_user("Queued {} messages".format(count))
return HttpResponseRedirect(self.request.get_full_path())
def get_object_recipients(self, obj):
@@ -89,7 +89,7 @@ def get_queryset(self):
def get_context_data(self, **kwargs):
"""Return queryset in context."""
- context = super(SendNotificationView, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
recipients = []
for obj in self.get_queryset().all():
recipients.extend(self.get_object_recipients(obj))
diff --git a/readthedocs/oauth/migrations/0003_move_github.py b/readthedocs/oauth/migrations/0003_move_github.py
index 40fd4c562e0..7ff1dbc8e10 100644
--- a/readthedocs/oauth/migrations/0003_move_github.py
+++ b/readthedocs/oauth/migrations/0003_move_github.py
@@ -143,9 +143,9 @@ def forwards_move_repos(apps, schema_editor):
new_repo.private = data.get('is_private', False)
new_repo.json = json.dumps(data)
- clone_urls = dict((location['name'], location['href'])
+ clone_urls = {location['name']: location['href']
for location
- in data.get('links', {}).get('clone', {}))
+ in data.get('links', {}).get('clone', {})}
if new_repo.private:
new_repo.clone_url = clone_urls.get('ssh', project.git_url)
else:
diff --git a/readthedocs/oauth/models.py b/readthedocs/oauth/models.py
index b93f71b9faa..54a02191fe7 100644
--- a/readthedocs/oauth/models.py
+++ b/readthedocs/oauth/models.py
@@ -141,7 +141,7 @@ class RemoteRepository(models.Model):
objects = RemoteRepositoryQuerySet.as_manager()
- class Meta(object):
+ class Meta:
ordering = ['organization__name', 'name']
verbose_name_plural = 'remote repositories'
diff --git a/readthedocs/oauth/notifications.py b/readthedocs/oauth/notifications.py
index fc9aefc0c3b..41b9c78ce6e 100644
--- a/readthedocs/oauth/notifications.py
+++ b/readthedocs/oauth/notifications.py
@@ -22,7 +22,7 @@ class AttachWebhookNotification(SiteNotification):
}
def get_context_data(self):
- context = super(AttachWebhookNotification, self).get_context_data()
+ context = super().get_context_data()
project = self.extra_context.get('project')
context.update({
'url_connect_account': reverse(
@@ -44,7 +44,7 @@ class InvalidProjectWebhookNotification(SiteNotification):
"See the project integrations for more information.") # noqa
def get_context_data(self):
- context = super(InvalidProjectWebhookNotification, self).get_context_data()
+ context = super().get_context_data()
context.update({
'url_integrations': reverse(
'projects_integrations',
diff --git a/readthedocs/oauth/services/base.py b/readthedocs/oauth/services/base.py
index 93064779ef9..87086a03149 100644
--- a/readthedocs/oauth/services/base.py
+++ b/readthedocs/oauth/services/base.py
@@ -19,7 +19,7 @@
log = logging.getLogger(__name__)
-class Service(object):
+class Service:
"""
Service mapping for local accounts.
diff --git a/readthedocs/oauth/services/bitbucket.py b/readthedocs/oauth/services/bitbucket.py
index 0ce2f55a21c..2586de5697b 100644
--- a/readthedocs/oauth/services/bitbucket.py
+++ b/readthedocs/oauth/services/bitbucket.py
@@ -119,8 +119,8 @@ def create_repository(self, fields, privacy=None, organization=None):
repo.private = fields['is_private']
# Default to HTTPS, use SSH for private repositories
- clone_urls = dict((u['name'], u['href'])
- for u in fields['links']['clone'])
+ clone_urls = {u['name']: u['href']
+ for u in fields['links']['clone']}
repo.clone_url = self.https_url_pattern.sub(
'https://bitbucket.org/',
clone_urls.get('https')
diff --git a/readthedocs/payments/forms.py b/readthedocs/payments/forms.py
index eae6e88dcb6..89b2c1dc782 100644
--- a/readthedocs/payments/forms.py
+++ b/readthedocs/payments/forms.py
@@ -15,7 +15,7 @@
log = logging.getLogger(__name__)
-class StripeResourceMixin(object):
+class StripeResourceMixin:
"""Stripe actions for resources, available as a Form mixin class."""
@@ -117,7 +117,7 @@ class StripeModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.customer = kwargs.pop('customer', None)
- super(StripeModelForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def validate_stripe(self):
"""
@@ -147,7 +147,7 @@ def clean(self):
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()
+ cleaned_data = super().clean()
# Form isn't valid, no need to try to associate a card now
if not self.is_valid():
diff --git a/readthedocs/payments/mixins.py b/readthedocs/payments/mixins.py
index 0219da08098..d90fc169925 100644
--- a/readthedocs/payments/mixins.py
+++ b/readthedocs/payments/mixins.py
@@ -5,12 +5,12 @@
from django.conf import settings
-class StripeMixin(object):
+class StripeMixin:
"""Adds Stripe publishable key to the context data."""
def get_context_data(self, **kwargs):
- context = super(StripeMixin, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context['stripe_publishable'] = settings.STRIPE_PUBLISHABLE
return context
diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py
index d55954fdbcf..48109f7eafb 100644
--- a/readthedocs/projects/admin.py
+++ b/readthedocs/projects/admin.py
@@ -156,12 +156,12 @@ def ban_owner(self, request, queryset):
total += count
else:
messages.add_message(request, messages.ERROR,
- 'Project has multiple owners: {0}'.format(project))
+ 'Project has multiple owners: {}'.format(project))
if total == 0:
messages.add_message(request, messages.ERROR, 'No users banned')
else:
messages.add_message(request, messages.INFO,
- 'Banned {0} user(s)'.format(total))
+ 'Banned {} user(s)'.format(total))
ban_owner.short_description = 'Ban project owner'
@@ -178,7 +178,7 @@ def delete_selected_and_artifacts(self, request, queryset):
return delete_selected(self, request, queryset)
def get_actions(self, request):
- actions = super(ProjectAdmin, self).get_actions(request)
+ actions = super().get_actions(request)
actions['delete_selected'] = (
self.__class__.delete_selected_and_artifacts,
'delete_selected',
diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py
index 5f7f407f1ab..c447ab40c0b 100644
--- a/readthedocs/projects/forms.py
+++ b/readthedocs/projects/forms.py
@@ -52,17 +52,17 @@ class ProjectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
- super(ProjectForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def save(self, commit=True):
- project = super(ProjectForm, self).save(commit)
+ project = super().save(commit)
if commit:
if self.user and not project.users.filter(pk=self.user.pk).exists():
project.users.add(self.user)
return project
-class ProjectTriggerBuildMixin(object):
+class ProjectTriggerBuildMixin:
"""
Mixin to trigger build on form save.
@@ -73,7 +73,7 @@ class ProjectTriggerBuildMixin(object):
def save(self, commit=True):
"""Trigger build on commit save."""
- project = super(ProjectTriggerBuildMixin, self).save(commit)
+ project = super().save(commit)
if commit:
trigger_build(project=project)
return project
@@ -90,7 +90,7 @@ class ProjectBasicsForm(ProjectForm):
"""Form for basic project fields."""
- class Meta(object):
+ class Meta:
model = Project
fields = ('name', 'repo', 'repo_type')
@@ -101,7 +101,7 @@ class Meta(object):
def __init__(self, *args, **kwargs):
show_advanced = kwargs.pop('show_advanced', False)
- super(ProjectBasicsForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
if show_advanced:
self.fields['advanced'] = forms.BooleanField(
required=False,
@@ -112,7 +112,7 @@ def __init__(self, *args, **kwargs):
def save(self, commit=True):
"""Add remote repository relationship to the project instance."""
- instance = super(ProjectBasicsForm, self).save(commit)
+ instance = super().save(commit)
remote_repo = self.cleaned_data.get('remote_repository', None)
if remote_repo:
if commit:
@@ -165,7 +165,7 @@ class ProjectExtraForm(ProjectForm):
"""Additional project information form."""
- class Meta(object):
+ class Meta:
model = Project
fields = (
'description',
@@ -203,7 +203,7 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm):
'environment.'),
)
- class Meta(object):
+ class Meta:
model = Project
fields = (
# Standard build edits
@@ -227,7 +227,7 @@ class Meta(object):
)
def __init__(self, *args, **kwargs):
- super(ProjectAdvancedForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
default_choice = (None, '-' * 9)
all_versions = self.instance.versions.values_list(
@@ -255,7 +255,7 @@ def clean_conf_py_file(self):
class UpdateProjectForm(ProjectTriggerBuildMixin, ProjectBasicsForm,
ProjectExtraForm):
- class Meta(object):
+ class Meta:
model = Project
fields = (
# Basics
@@ -308,14 +308,14 @@ class ProjectRelationshipBaseForm(forms.ModelForm):
parent = forms.CharField(widget=forms.HiddenInput(), required=False)
- class Meta(object):
+ class Meta:
model = ProjectRelationship
fields = '__all__'
def __init__(self, *args, **kwargs):
self.project = kwargs.pop('project')
self.user = kwargs.pop('user')
- super(ProjectRelationshipBaseForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
# Don't display the update form with an editable child, as it will be
# filtered out from the queryset anyways.
if hasattr(self, 'instance') and self.instance.pk is not None:
@@ -362,11 +362,11 @@ class DualCheckboxWidget(forms.CheckboxInput):
"""Checkbox with link to the version's built documentation."""
def __init__(self, version, attrs=None, check_test=bool):
- super(DualCheckboxWidget, self).__init__(attrs, check_test)
+ super().__init__(attrs, check_test)
self.version = version
def render(self, name, value, attrs=None, renderer=None):
- checkbox = super(DualCheckboxWidget, self).render(name, value, attrs, renderer)
+ checkbox = super().render(name, value, attrs, renderer)
icon = self.render_icon()
return mark_safe('{}{}'.format(checkbox, icon))
@@ -459,7 +459,7 @@ class BaseUploadHTMLForm(forms.Form):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
- super(BaseUploadHTMLForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def clean(self):
version_slug = self.cleaned_data['version']
@@ -499,7 +499,7 @@ class UserForm(forms.Form):
def __init__(self, *args, **kwargs):
self.project = kwargs.pop('project', None)
- super(UserForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def clean_user(self):
name = self.cleaned_data['user']
@@ -525,7 +525,7 @@ class EmailHookForm(forms.Form):
def __init__(self, *args, **kwargs):
self.project = kwargs.pop('project', None)
- super(EmailHookForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def clean_email(self):
self.email = EmailHook.objects.get_or_create(
@@ -543,7 +543,7 @@ class WebHookForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.project = kwargs.pop('project', None)
- super(WebHookForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def save(self, commit=True):
self.webhook = WebHook.objects.get_or_create(
@@ -565,7 +565,7 @@ class TranslationBaseForm(forms.Form):
def __init__(self, *args, **kwargs):
self.parent = kwargs.pop('parent', None)
self.user = kwargs.pop('user')
- super(TranslationBaseForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.fields['project'].choices = self.get_choices()
def get_choices(self):
@@ -646,13 +646,13 @@ class RedirectForm(forms.ModelForm):
"""Form for project redirects."""
- class Meta(object):
+ class Meta:
model = Redirect
fields = ['redirect_type', 'from_url', 'to_url']
def __init__(self, *args, **kwargs):
self.project = kwargs.pop('project', None)
- super(RedirectForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def save(self, **_): # pylint: disable=arguments-differ
# TODO this should respect the unused argument `commit`. It's not clear
@@ -673,13 +673,13 @@ class DomainBaseForm(forms.ModelForm):
project = forms.CharField(widget=forms.HiddenInput(), required=False)
- class Meta(object):
+ class Meta:
model = Domain
exclude = ['machine', 'cname', 'count'] # pylint: disable=modelform-uses-exclude
def __init__(self, *args, **kwargs):
self.project = kwargs.pop('project', None)
- super(DomainBaseForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
def clean_project(self):
return self.project
@@ -717,13 +717,13 @@ class IntegrationForm(forms.ModelForm):
project = forms.CharField(widget=forms.HiddenInput(), required=False)
- class Meta(object):
+ class Meta:
model = Integration
exclude = ['provider_data', 'exchanges'] # pylint: disable=modelform-uses-exclude
def __init__(self, *args, **kwargs):
self.project = kwargs.pop('project', None)
- super(IntegrationForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
# Alter the integration type choices to only provider webhooks
self.fields['integration_type'].choices = Integration.WEBHOOK_INTEGRATIONS # yapf: disable # noqa
@@ -732,20 +732,20 @@ def clean_project(self):
def save(self, commit=True):
self.instance = Integration.objects.subclass(self.instance)
- return super(IntegrationForm, self).save(commit)
+ return super().save(commit)
class ProjectAdvertisingForm(forms.ModelForm):
"""Project promotion opt-out form."""
- class Meta(object):
+ class Meta:
model = Project
fields = ['allow_promos']
def __init__(self, *args, **kwargs):
self.project = kwargs.pop('project', None)
- super(ProjectAdvertisingForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
class FeatureForm(forms.ModelForm):
@@ -760,10 +760,10 @@ class FeatureForm(forms.ModelForm):
feature_id = forms.ChoiceField()
- class Meta(object):
+ class Meta:
model = Feature
fields = ['projects', 'feature_id', 'default_true']
def __init__(self, *args, **kwargs):
- super(FeatureForm, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.fields['feature_id'].choices = Feature.FEATURES
diff --git a/readthedocs/projects/migrations/0007_migrate_canonical_data.py b/readthedocs/projects/migrations/0007_migrate_canonical_data.py
index 743d6a145cb..8c842a6a0b9 100644
--- a/readthedocs/projects/migrations/0007_migrate_canonical_data.py
+++ b/readthedocs/projects/migrations/0007_migrate_canonical_data.py
@@ -16,10 +16,10 @@ def migrate_canonical(apps, schema_editor):
url=project.canonical_url,
canonical=True,
)
- print(u"Added {url} to {project}".format(url=project.canonical_url, project=project.name))
+ print("Added {url} to {project}".format(url=project.canonical_url, project=project.name))
except Exception as e:
print(e)
- print(u"Failed adding {url} to {project}".format(
+ print("Failed adding {url} to {project}".format(
url=project.canonical_url, project=project.name
))
diff --git a/readthedocs/projects/migrations/0010_migrate_domain_data.py b/readthedocs/projects/migrations/0010_migrate_domain_data.py
index ef60b2a00d1..636f200bfe7 100644
--- a/readthedocs/projects/migrations/0010_migrate_domain_data.py
+++ b/readthedocs/projects/migrations/0010_migrate_domain_data.py
@@ -24,10 +24,10 @@ def migrate_url(apps, schema_editor):
try:
domain.domain = domain_string
domain.save()
- print(u"Added {domain} from {url}".format(url=domain.url, domain=domain_string))
+ print("Added {domain} from {url}".format(url=domain.url, domain=domain_string))
except Exception as e:
print(e)
- print(u"Failed {domain} from {url}".format(url=domain.url, domain=domain_string))
+ print("Failed {domain} from {url}".format(url=domain.url, domain=domain_string))
dms = Domain.objects.filter(domain=domain_string).order_by('-count')
if dms.count() > 1:
diff --git a/readthedocs/projects/migrations/0016_build-queue-name.py b/readthedocs/projects/migrations/0016_build-queue-name.py
index 46833ada78c..d4e2201b230 100644
--- a/readthedocs/projects/migrations/0016_build-queue-name.py
+++ b/readthedocs/projects/migrations/0016_build-queue-name.py
@@ -11,7 +11,7 @@ def update_build_queue(apps, schema):
for project in Project.objects.all():
if project.build_queue is not None:
if not project.build_queue.startswith('build-'):
- project.build_queue = 'build-{0}'.format(project.build_queue)
+ project.build_queue = 'build-{}'.format(project.build_queue)
project.save()
diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py
index d02ecfc76c0..c87295b864d 100644
--- a/readthedocs/projects/models.py
+++ b/readthedocs/projects/models.py
@@ -56,12 +56,12 @@ class ProjectRelationship(models.Model):
objects = ChildRelatedProjectQuerySet.as_manager()
def __str__(self):
- return '%s -> %s' % (self.parent, self.child)
+ return '{} -> {}'.format(self.parent, self.child)
def save(self, *args, **kwargs): # pylint: disable=arguments-differ
if not self.alias:
self.alias = self.child.slug
- super(ProjectRelationship, self).save(*args, **kwargs)
+ super().save(*args, **kwargs)
# HACK
def get_absolute_url(self):
@@ -251,7 +251,7 @@ class Project(models.Model):
objects = ProjectQuerySet.as_manager()
all_objects = models.Manager()
- class Meta(object):
+ class Meta:
ordering = ('slug',)
permissions = (
# Translators: Permission around whether a user can view the
@@ -275,7 +275,7 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ
# documentation type to Sphinx for rST and Mkdocs for markdown.
# It now just forces Sphinx, due to markdown support.
self.documentation_type = 'sphinx'
- super(Project, self).save(*args, **kwargs)
+ super().save(*args, **kwargs)
for owner in self.users.all():
assign('view_project', owner, self)
try:
@@ -379,7 +379,7 @@ def get_production_media_path(self, type_, version_slug, include_file=True):
settings.PRODUCTION_MEDIA_ARTIFACTS, type_, self.slug, version_slug)
if include_file:
path = os.path.join(
- path, '%s.%s' % (self.slug, type_.replace('htmlzip', 'zip')))
+ path, '{}.{}'.format(self.slug, type_.replace('htmlzip', 'zip')))
return path
def get_production_media_url(self, type_, version_slug, full_path=True):
@@ -393,7 +393,7 @@ def get_production_media_url(self, type_, version_slug, full_path=True):
except NoReverseMatch:
return ''
if full_path:
- path = '//%s%s' % (settings.PRODUCTION_DOMAIN, path)
+ path = '//{}{}'.format(settings.PRODUCTION_DOMAIN, path)
return path
def subdomain(self):
@@ -856,7 +856,7 @@ def __init__(self, *args, **kwargs):
del kwargs[key]
except KeyError:
pass
- super(APIProject, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
# Overwrite the database property with the value from the API
self.ad_free = ad_free
@@ -903,7 +903,7 @@ def get_absolute_url(self):
return resolve(project=self.project, version_slug=self.version.slug, filename=self.path)
def __str__(self):
- return '%s: %s' % (self.name, self.project)
+ return '{}: {}'.format(self.name, self.project)
class Notification(models.Model):
@@ -911,7 +911,7 @@ class Notification(models.Model):
related_name='%(class)s_notifications')
objects = RelatedProjectQuerySet.as_manager()
- class Meta(object):
+ class Meta:
abstract = True
@@ -962,7 +962,7 @@ class Domain(models.Model):
objects = RelatedProjectQuerySet.as_manager()
- class Meta(object):
+ class Meta:
ordering = ('-canonical', '-machine', 'domain')
def __str__(self):
@@ -975,7 +975,7 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ
self.domain = parsed.netloc
else:
self.domain = parsed.path
- super(Domain, self).save(*args, **kwargs)
+ super().save(*args, **kwargs)
broadcast(type='app', task=tasks.symlink_domain,
args=[self.project.pk, self.pk],)
@@ -983,7 +983,7 @@ def delete(self, *args, **kwargs): # pylint: disable=arguments-differ
from readthedocs.projects import tasks
broadcast(type='app', task=tasks.symlink_domain,
args=[self.project.pk, self.pk, True],)
- super(Domain, self).delete(*args, **kwargs)
+ super().delete(*args, **kwargs)
@python_2_unicode_compatible
@@ -1053,7 +1053,7 @@ def add_features(sender, **kwargs):
objects = FeatureQuerySet.as_manager()
def __str__(self):
- return '{0} feature'.format(
+ return '{} feature'.format(
self.get_feature_display(),
)
diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py
index bcc81913753..10b6660deca 100644
--- a/readthedocs/projects/tasks.py
+++ b/readthedocs/projects/tasks.py
@@ -85,7 +85,7 @@
log = logging.getLogger(__name__)
-class SyncRepositoryMixin(object):
+class SyncRepositoryMixin:
"""Mixin that handles the VCS sync/update."""
@@ -1217,11 +1217,11 @@ def email_notification(version, build, email):
'pk': build.pk,
'error': build.error,
},
- 'build_url': 'https://{0}{1}'.format(
+ 'build_url': 'https://{}{}'.format(
getattr(settings, 'PRODUCTION_DOMAIN', 'readthedocs.org'),
build.get_absolute_url(),
),
- 'unsub_url': 'https://{0}{1}'.format(
+ 'unsub_url': 'https://{}{}'.format(
getattr(settings, 'PRODUCTION_DOMAIN', 'readthedocs.org'),
reverse('projects_notifications', args=[version.project.slug]),
),
@@ -1320,7 +1320,7 @@ def update_static_metadata(project_pk, path=None):
LOG_TEMPLATE.format(
project=project.slug,
version='',
- msg='Cannot write to metadata.json: {0}'.format(e),
+ msg='Cannot write to metadata.json: {}'.format(e),
)
)
@@ -1396,7 +1396,7 @@ def finish_inactive_builds():
build.error = _(
'This build was terminated due to inactivity. If you '
'continue to encounter this error, file a support '
- 'request with and reference this build id ({0}).'.format(build.pk),
+ 'request with and reference this build id ({}).'.format(build.pk),
)
build.save()
builds_finished += 1
diff --git a/readthedocs/projects/validators.py b/readthedocs/projects/validators.py
index 01d350a43aa..f2b7b468746 100644
--- a/readthedocs/projects/validators.py
+++ b/readthedocs/projects/validators.py
@@ -28,13 +28,13 @@ class DomainNameValidator(RegexValidator):
def __init__(self, accept_idna=True, **kwargs):
message = kwargs.get('message')
self.accept_idna = accept_idna
- super(DomainNameValidator, self).__init__(**kwargs)
+ super().__init__(**kwargs)
if not self.accept_idna and message is None:
self.message = _('Enter a valid domain name value')
def __call__(self, value):
try:
- super(DomainNameValidator, self).__call__(value)
+ super().__call__(value)
except ValidationError as exc:
if not self.accept_idna:
raise
@@ -44,14 +44,14 @@ def __call__(self, value):
idnavalue = value.encode('idna')
except UnicodeError:
raise exc
- super(DomainNameValidator, self).__call__(idnavalue)
+ super().__call__(idnavalue)
validate_domain_name = DomainNameValidator()
@deconstructible
-class RepositoryURLValidator(object):
+class RepositoryURLValidator:
disallow_relative_url = True
diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py
index 2a7cffa5bdd..d3ef3d3a108 100644
--- a/readthedocs/projects/version_handling.py
+++ b/readthedocs/projects/version_handling.py
@@ -25,7 +25,7 @@ def parse_version_failsafe(version_string):
:rtype: packaging.version.Version
"""
- if not isinstance(version_string, six.text_type):
+ if not isinstance(version_string, str):
uni_version = version_string.decode('utf-8')
else:
uni_version = version_string
diff --git a/readthedocs/projects/views/base.py b/readthedocs/projects/views/base.py
index 2bdb17fdfb2..b6e8e387dd1 100644
--- a/readthedocs/projects/views/base.py
+++ b/readthedocs/projects/views/base.py
@@ -21,13 +21,13 @@
USER_MATURITY_DAYS = getattr(settings, 'USER_MATURITY_DAYS', 7)
-class ProjectOnboardMixin(object):
+class ProjectOnboardMixin:
"""Add project onboard context data to project object views."""
def get_context_data(self, **kwargs):
"""Add onboard context data."""
- context = super(ProjectOnboardMixin, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
# If more than 1 project, don't show onboarding at all. This could
# change in the future, to onboard each user maybe?
if Project.objects.for_admin_user(self.request.user).count() > 1:
@@ -51,7 +51,7 @@ def get_context_data(self, **kwargs):
# Mixins
-class ProjectAdminMixin(object):
+class ProjectAdminMixin:
"""
Mixin class that provides project sublevel objects.
@@ -78,7 +78,7 @@ def get_project(self):
def get_context_data(self, **kwargs):
"""Add project to context data."""
- context = super(ProjectAdminMixin, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context['project'] = self.get_project()
return context
@@ -88,7 +88,7 @@ def get_form(self, data=None, files=None, **kwargs):
return self.form_class(data, files, **kwargs)
-class ProjectSpamMixin(object):
+class ProjectSpamMixin:
"""Protects POST views from spammers."""
@@ -100,7 +100,7 @@ def post(self, request, *args, **kwargs):
)
return HttpResponseRedirect(self.get_failure_url())
try:
- return super(ProjectSpamMixin, self).post(request, *args, **kwargs)
+ return super().post(request, *args, **kwargs)
except ProjectSpamError:
date_maturity = timezone.now() - timedelta(days=USER_MATURITY_DAYS)
if request.user.date_joined > date_maturity:
diff --git a/readthedocs/projects/views/mixins.py b/readthedocs/projects/views/mixins.py
index 50e03beb475..1692b0e461e 100644
--- a/readthedocs/projects/views/mixins.py
+++ b/readthedocs/projects/views/mixins.py
@@ -7,7 +7,7 @@
from readthedocs.projects.models import Project
-class ProjectRelationMixin(object):
+class ProjectRelationMixin:
"""
Mixin class for constructing model views for project dashboard.
@@ -41,6 +41,6 @@ def get_queryset(self):
)
def get_context_data(self, **kwargs):
- context = super(ProjectRelationMixin, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context[self.project_context_object_name] = self.get_project()
return context
diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py
index 330c22b5b7d..22b355f94a2 100644
--- a/readthedocs/projects/views/private.py
+++ b/readthedocs/projects/views/private.py
@@ -84,7 +84,7 @@ def get_queryset(self):
return Project.objects.dashboard(self.request.user)
def get_context_data(self, **kwargs):
- context = super(ProjectDashboard, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
return context
@@ -250,7 +250,7 @@ def get_form_kwargs(self, step=None):
def get_template_names(self):
"""Return template names based on step name."""
- return 'projects/import_{0}.html'.format(self.steps.current)
+ return 'projects/import_{}.html'.format(self.steps.current)
def done(self, form_list, **kwargs):
"""
@@ -351,7 +351,7 @@ def get(self, request, *args, **kwargs):
def get_form_data(self):
"""Get form data to post to import form."""
return {
- 'name': '{0}-demo'.format(self.request.user.username),
+ 'name': '{}-demo'.format(self.request.user.username),
'repo_type': 'git',
'repo': 'https://github.com/readthedocs/template.git',
}
@@ -405,7 +405,7 @@ def get(self, request, *args, **kwargs):
)
)), # yapf: disable
)
- return super(ImportView, self).get(request, *args, **kwargs)
+ return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
initial_data = {}
@@ -419,7 +419,7 @@ def post(self, request, *args, **kwargs):
return self.wizard_class.as_view(initial_dict=initial_data)(request)
def get_context_data(self, **kwargs):
- context = super(ImportView, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context['view_csrf_token'] = get_token(self.request)
context['has_connected_accounts'] = SocialAccount.objects.filter(
user=self.request.user,
@@ -440,10 +440,7 @@ def get_queryset(self):
def get_form(self, data=None, files=None, **kwargs):
kwargs['user'] = self.request.user
- return super(
- ProjectRelationshipMixin,
- self,
- ).get_form(data, files, **kwargs)
+ return super().get_form(data, files, **kwargs)
def form_valid(self, form):
broadcast(
@@ -451,7 +448,7 @@ def form_valid(self, form):
task=tasks.symlink_subproject,
args=[self.get_project().pk],
)
- return super(ProjectRelationshipMixin, self).form_valid(form)
+ return super().form_valid(form)
def get_success_url(self):
return reverse('projects_subprojects', args=[self.get_project().slug])
@@ -460,7 +457,7 @@ def get_success_url(self):
class ProjectRelationshipList(ProjectRelationshipMixin, ListView):
def get_context_data(self, **kwargs):
- ctx = super(ProjectRelationshipList, self).get_context_data(**kwargs)
+ ctx = super().get_context_data(**kwargs)
ctx['superproject'] = self.project.superprojects.first()
return ctx
@@ -775,7 +772,7 @@ def get_success_url(self):
def get_template_names(self):
if self.template_name:
return self.template_name
- return 'projects/integration{0}.html'.format(self.template_name_suffix)
+ return 'projects/integration{}.html'.format(self.template_name_suffix)
class IntegrationList(IntegrationMixin, ListView):
@@ -810,7 +807,7 @@ def get_template_names(self):
integration_type = self.get_integration().integration_type
suffix = self.SUFFIX_MAP.get(integration_type, integration_type)
return (
- 'projects/integration_{0}{1}.html'
+ 'projects/integration_{}{}.html'
.format(suffix, self.template_name_suffix)
)
diff --git a/readthedocs/projects/views/public.py b/readthedocs/projects/views/public.py
index 90f5ef978b1..028571a293d 100644
--- a/readthedocs/projects/views/public.py
+++ b/readthedocs/projects/views/public.py
@@ -62,7 +62,7 @@ def get_queryset(self):
return queryset
def get_context_data(self, **kwargs):
- context = super(ProjectIndex, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
context['person'] = self.user
context['tag'] = self.tag
return context
@@ -82,7 +82,7 @@ def get_queryset(self):
return Project.objects.protected(self.request.user)
def get_context_data(self, **kwargs):
- context = super(ProjectDetailView, self).get_context_data(**kwargs)
+ context = super().get_context_data(**kwargs)
project = self.get_object()
context['versions'] = Version.objects.public(
@@ -94,7 +94,7 @@ def get_context_data(self, **kwargs):
version_slug = project.get_default_version()
- context['badge_url'] = '%s://%s%s?version=%s' % (
+ context['badge_url'] = '{}://{}{}?version={}'.format(
protocol,
settings.PRODUCTION_DOMAIN,
reverse('project_badge', args=[project.slug]),
@@ -192,7 +192,7 @@ def project_download_media(request, project_slug, type_, version_slug):
if privacy_level == 'public' or settings.DEBUG:
path = os.path.join(
settings.MEDIA_URL, type_, project_slug, version_slug,
- '%s.%s' % (project_slug, type_.replace('htmlzip', 'zip')))
+ '{}.{}'.format(project_slug, type_.replace('htmlzip', 'zip')))
return HttpResponseRedirect(path)
# Get relative media path
@@ -207,7 +207,7 @@ def project_download_media(request, project_slug, type_, version_slug):
response['Content-Encoding'] = encoding
response['X-Accel-Redirect'] = path
# Include version in filename; this fixes a long-standing bug
- filename = '%s-%s.%s' % (
+ filename = '{}-{}.{}'.format(
project_slug, version_slug, path.split('.')[-1])
response['Content-Disposition'] = 'filename=%s' % filename
return response
diff --git a/readthedocs/redirects/models.py b/readthedocs/redirects/models.py
index cbd080ca28c..f8eddb16cc4 100644
--- a/readthedocs/redirects/models.py
+++ b/readthedocs/redirects/models.py
@@ -76,7 +76,7 @@ class Redirect(models.Model):
objects = RedirectManager()
- class Meta(object):
+ class Meta:
verbose_name = _('redirect')
verbose_name_plural = _('redirects')
ordering = ('-update_dt',)
diff --git a/readthedocs/restapi/permissions.py b/readthedocs/restapi/permissions.py
index 615872d307e..a9d7aa8d92f 100644
--- a/readthedocs/restapi/permissions.py
+++ b/readthedocs/restapi/permissions.py
@@ -51,11 +51,11 @@ class APIPermission(permissions.IsAuthenticatedOrReadOnly):
"""
def has_permission(self, request, view):
- has_perm = super(APIPermission, self).has_permission(request, view)
+ has_perm = super().has_permission(request, view)
return has_perm or (request.user and request.user.is_staff)
def has_object_permission(self, request, view, obj):
- has_perm = super(APIPermission, self).has_object_permission(
+ has_perm = super().has_object_permission(
request, view, obj)
return has_perm or (request.user and request.user.is_staff)
diff --git a/readthedocs/restapi/serializers.py b/readthedocs/restapi/serializers.py
index 1d473a9eada..ba6a88ddc90 100644
--- a/readthedocs/restapi/serializers.py
+++ b/readthedocs/restapi/serializers.py
@@ -15,7 +15,7 @@
class ProjectSerializer(serializers.ModelSerializer):
canonical_url = serializers.ReadOnlyField(source='get_docs_url')
- class Meta(object):
+ class Meta:
model = Project
fields = (
'id',
@@ -70,7 +70,7 @@ class VersionSerializer(serializers.ModelSerializer):
project = ProjectSerializer()
downloads = serializers.DictField(source='get_downloads', read_only=True)
- class Meta(object):
+ class Meta:
model = Version
fields = (
'id',
@@ -93,7 +93,7 @@ class BuildCommandSerializer(serializers.ModelSerializer):
run_time = serializers.ReadOnlyField()
- class Meta(object):
+ class Meta:
model = BuildCommandResult
exclude = ('')
@@ -111,7 +111,7 @@ class BuildSerializer(serializers.ModelSerializer):
# https://github.com/dmkoch/django-jsonfield/issues/188#issuecomment-300439829
config = serializers.JSONField(required=False)
- class Meta(object):
+ class Meta:
model = Build
# `_config` should be excluded to avoid conflicts with `config`
exclude = ('builder', '_config')
@@ -136,7 +136,7 @@ class SearchIndexSerializer(serializers.Serializer):
class DomainSerializer(serializers.ModelSerializer):
project = ProjectSerializer()
- class Meta(object):
+ class Meta:
model = Domain
fields = (
'id',
@@ -150,7 +150,7 @@ class Meta(object):
class RemoteOrganizationSerializer(serializers.ModelSerializer):
- class Meta(object):
+ class Meta:
model = RemoteOrganization
exclude = ('json', 'email', 'users')
@@ -162,7 +162,7 @@ class RemoteRepositorySerializer(serializers.ModelSerializer):
organization = RemoteOrganizationSerializer()
matches = serializers.SerializerMethodField()
- class Meta(object):
+ class Meta:
model = RemoteRepository
exclude = ('json', 'users')
@@ -184,7 +184,7 @@ class SocialAccountSerializer(serializers.ModelSerializer):
avatar_url = serializers.URLField(source='get_avatar_url')
provider = ProviderSerializer(source='get_provider')
- class Meta(object):
+ class Meta:
model = SocialAccount
exclude = ('extra_data',)
diff --git a/readthedocs/restapi/views/footer_views.py b/readthedocs/restapi/views/footer_views.py
index f09ceaff527..ad5ec7028b6 100644
--- a/readthedocs/restapi/views/footer_views.py
+++ b/readthedocs/restapi/views/footer_views.py
@@ -37,8 +37,8 @@ def get_version_compare_data(project, base_version=None):
highest_version_obj, highest_version_comparable = highest_version(
versions_qs)
ret_val = {
- 'project': six.text_type(highest_version_obj),
- 'version': six.text_type(highest_version_comparable),
+ 'project': str(highest_version_obj),
+ 'version': str(highest_version_comparable),
'is_highest': True,
}
if highest_version_obj:
diff --git a/readthedocs/restapi/views/integrations.py b/readthedocs/restapi/views/integrations.py
index 9a27e2ef9af..4950518408b 100644
--- a/readthedocs/restapi/views/integrations.py
+++ b/readthedocs/restapi/views/integrations.py
@@ -43,7 +43,7 @@
BITBUCKET_PUSH = 'repo:push'
-class WebhookMixin(object):
+class WebhookMixin:
"""Base class for Webhook mixins."""
@@ -75,7 +75,7 @@ def get_project(self, **kwargs):
def finalize_response(self, req, *args, **kwargs):
"""If the project was set on POST, store an HTTP exchange."""
- resp = super(WebhookMixin, self).finalize_response(req, *args, **kwargs)
+ resp = super().finalize_response(req, *args, **kwargs)
if hasattr(self, 'project') and self.project:
HttpExchange.objects.from_exchange(
req,
@@ -180,7 +180,7 @@ def get_data(self):
return json.loads(self.request.data['payload'])
except (ValueError, KeyError):
pass
- return super(GitHubWebhookView, self).get_data()
+ return super().get_data()
def handle_webhook(self):
# Get event and trigger other webhook events
@@ -350,7 +350,7 @@ class IsAuthenticatedOrHasToken(permissions.IsAuthenticated):
"""
def has_permission(self, request, view):
- has_perm = (super(IsAuthenticatedOrHasToken, self)
+ has_perm = (super()
.has_permission(request, view))
return has_perm or 'token' in request.data
@@ -404,7 +404,7 @@ def handle_webhook(self):
'branches',
[self.project.get_default_branch()]
)
- if isinstance(branches, six.string_types):
+ if isinstance(branches, str):
branches = [branches]
return self.get_response_push(self.project, branches)
except TypeError:
diff --git a/readthedocs/rtd_tests/base.py b/readthedocs/rtd_tests/base.py
index f11ec5a49b4..c6813f22f8f 100644
--- a/readthedocs/rtd_tests/base.py
+++ b/readthedocs/rtd_tests/base.py
@@ -42,7 +42,7 @@ class MockBuildTestCase(TestCase):
pass
-class RequestFactoryTestMixin(object):
+class RequestFactoryTestMixin:
"""
Adds helper methods for testing with :py:class:`RequestFactory`
@@ -107,14 +107,14 @@ def post_step(self, step, **kwargs):
if not self.url:
raise Exception('Missing wizard URL')
try:
- data = dict(
- ('{0}-{1}'.format(step, k), v)
+ data = {
+ '{}-{}'.format(step, k): v
for (k, v) in list(self.step_data[step].items())
- )
+ }
except KeyError:
pass
# Update with prefixed step data
- data['{0}-current_step'.format(self.wizard_class_slug)] = step
+ data['{}-current_step'.format(self.wizard_class_slug)] = step
view = self.wizard_class.as_view()
req = self.request(self.url, method='post', data=data, **kwargs)
resp = view(req)
@@ -146,7 +146,7 @@ def assertWizardResponse(self, response, step=None): # noqa
response.render()
self.assertContains(
response,
- u'name="{0}-current_step"'.format(self.wizard_class_slug)
+ 'name="{}-current_step"'.format(self.wizard_class_slug)
)
# We use camelCase on purpose here to conform with unittest's naming
@@ -170,4 +170,4 @@ def assertWizardFailure(self, response, field, match=None): # noqa
self.assertIn(field, response.context_data['wizard']['form'].errors)
if match is not None:
error = response.context_data['wizard']['form'].errors[field]
- self.assertRegex(six.text_type(error), match) # noqa
+ self.assertRegex(str(error), match) # noqa
diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py
index 4007dcfab19..11f872849dd 100644
--- a/readthedocs/rtd_tests/files/conf.py
+++ b/readthedocs/rtd_tests/files/conf.py
@@ -13,7 +13,7 @@
'.md': CommonMarkParser,
}
master_doc = 'index'
-project = u'Pip'
+project = 'Pip'
copyright = str(datetime.now().year)
version = '0.8.1'
release = '0.8.1'
@@ -23,6 +23,6 @@
html_theme = 'sphinx_rtd_theme'
file_insertion_enabled = False
latex_documents = [
- ('index', 'pip.tex', u'Pip Documentation',
- u'', 'manual'),
+ ('index', 'pip.tex', 'Pip Documentation',
+ '', 'manual'),
]
diff --git a/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py b/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py
index c6c9fcb64db..68b327ca8af 100644
--- a/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py
+++ b/readthedocs/rtd_tests/fixtures/sample_repo/source/conf.py
@@ -41,8 +41,8 @@
master_doc = 'index'
# General information about the project.
-project = u'sample'
-copyright = u'2011, Dan'
+project = 'sample'
+copyright = '2011, Dan'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -180,8 +180,8 @@
# Grouping the document tree into LaTeX files. List of tuples (source start
# file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
- ('index', 'sample.tex', u'sample Documentation',
- u'Dan', 'manual'),
+ ('index', 'sample.tex', 'sample Documentation',
+ 'Dan', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -213,6 +213,6 @@
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
- ('index', 'sample', u'sample Documentation',
- [u'Dan'], 1)
+ ('index', 'sample', 'sample Documentation',
+ ['Dan'], 1)
]
diff --git a/readthedocs/rtd_tests/mocks/environment.py b/readthedocs/rtd_tests/mocks/environment.py
index 4b963b769ba..3b2a28f49bd 100644
--- a/readthedocs/rtd_tests/mocks/environment.py
+++ b/readthedocs/rtd_tests/mocks/environment.py
@@ -4,7 +4,7 @@
import mock
-class EnvironmentMockGroup(object):
+class EnvironmentMockGroup:
"""Mock out necessary environment pieces"""
diff --git a/readthedocs/rtd_tests/mocks/mock_api.py b/readthedocs/rtd_tests/mocks/mock_api.py
index 84c40d7c4d1..68d1e544357 100644
--- a/readthedocs/rtd_tests/mocks/mock_api.py
+++ b/readthedocs/rtd_tests/mocks/mock_api.py
@@ -8,7 +8,7 @@
# Mock tastypi API.
-class ProjectData(object):
+class ProjectData:
def get(self):
return dict()
@@ -18,7 +18,7 @@ def put(self, x=None):
def mock_version(repo):
"""Construct and return a class implementing the Version interface."""
- class MockVersion(object):
+ class MockVersion:
def __init__(self, x=None):
pass
@@ -71,7 +71,7 @@ def get(self, **kwargs):
return MockVersion
-class MockApi(object):
+class MockApi:
def __init__(self, repo):
self.version = mock_version(repo)
diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py
index 707a4da79c3..af63a2378e7 100644
--- a/readthedocs/rtd_tests/tests/test_api.py
+++ b/readthedocs/rtd_tests/tests/test_api.py
@@ -359,7 +359,7 @@ def test_update_build_without_permission(self):
client.force_authenticate(user=api_user)
build = get(Build, project_id=1, version_id=1, state='cloning')
resp = client.put(
- '/api/v2/build/{0}/'.format(build.pk),
+ '/api/v2/build/{}/'.format(build.pk),
{
'project': 1,
'version': 1,
@@ -381,11 +381,11 @@ def test_make_build_protected_fields(self):
api_user = get(User, staff=False, password='test')
client.force_authenticate(user=api_user)
- resp = client.get('/api/v2/build/{0}/'.format(build.pk), format='json')
+ resp = client.get('/api/v2/build/{}/'.format(build.pk), format='json')
self.assertEqual(resp.status_code, 200)
client.force_authenticate(user=User.objects.get(username='super'))
- resp = client.get('/api/v2/build/{0}/'.format(build.pk), format='json')
+ resp = client.get('/api/v2/build/{}/'.format(build.pk), format='json')
self.assertEqual(resp.status_code, 200)
self.assertIn('builder', resp.data)
@@ -443,7 +443,7 @@ def test_get_raw_log_success(self):
api_user = get(User, user='test', password='test')
client.force_authenticate(user=api_user)
- resp = client.get('/api/v2/build/{0}.txt'.format(build.pk))
+ resp = client.get('/api/v2/build/{}.txt'.format(build.pk))
self.assertEqual(resp.status_code, 200)
self.assertIn('Read the Docs build information', resp.content.decode())
@@ -487,7 +487,7 @@ def test_get_raw_log_building(self):
api_user = get(User, user='test', password='test')
client.force_authenticate(user=api_user)
- resp = client.get('/api/v2/build/{0}.txt'.format(build.pk))
+ resp = client.get('/api/v2/build/{}.txt'.format(build.pk))
self.assertEqual(resp.status_code, 200)
self.assertIn('Read the Docs build information', resp.content.decode())
@@ -530,7 +530,7 @@ def test_get_raw_log_failure(self):
api_user = get(User, user='test', password='test')
client.force_authenticate(user=api_user)
- resp = client.get('/api/v2/build/{0}.txt'.format(build.pk))
+ resp = client.get('/api/v2/build/{}.txt'.format(build.pk))
self.assertEqual(resp.status_code, 200)
self.assertIn('Read the Docs build information', resp.content.decode())
@@ -556,7 +556,7 @@ def test_get_invalid_raw_log(self):
api_user = get(User, user='test', password='test')
client.force_authenticate(user=api_user)
- resp = client.get('/api/v2/build/{0}.txt'.format(404))
+ resp = client.get('/api/v2/build/{}.txt'.format(404))
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
def test_build_filter_by_commit(self):
@@ -661,8 +661,7 @@ def test_project_features(self):
resp = client.get('/api/v2/project/%s/' % (project.pk))
self.assertEqual(resp.status_code, 200)
self.assertIn('features', resp.data)
- six.assertCountEqual(
- self,
+ self.assertCountEqual(
resp.data['features'],
[feature1.feature_id, feature2.feature_id],
)
@@ -868,7 +867,7 @@ def test_webhook_skipped_project(self, trigger_build):
self.project.save()
response = client.post(
- '/api/v2/webhook/github/{0}/'.format(
+ '/api/v2/webhook/github/{}/'.format(
self.project.slug,
),
self.github_payload,
@@ -883,7 +882,7 @@ def test_github_webhook_for_branches(self, trigger_build):
client = APIClient()
client.post(
- '/api/v2/webhook/github/{0}/'.format(self.project.slug),
+ '/api/v2/webhook/github/{}/'.format(self.project.slug),
{'ref': 'master'},
format='json',
)
@@ -891,7 +890,7 @@ def test_github_webhook_for_branches(self, trigger_build):
[mock.call(force=True, version=self.version, project=self.project)])
client.post(
- '/api/v2/webhook/github/{0}/'.format(self.project.slug),
+ '/api/v2/webhook/github/{}/'.format(self.project.slug),
{'ref': 'non-existent'},
format='json',
)
@@ -899,7 +898,7 @@ def test_github_webhook_for_branches(self, trigger_build):
[mock.call(force=True, version=mock.ANY, project=self.project)])
client.post(
- '/api/v2/webhook/github/{0}/'.format(self.project.slug),
+ '/api/v2/webhook/github/{}/'.format(self.project.slug),
{'ref': 'refs/heads/master'},
format='json',
)
@@ -911,7 +910,7 @@ def test_github_webhook_for_tags(self, trigger_build):
client = APIClient()
client.post(
- '/api/v2/webhook/github/{0}/'.format(self.project.slug),
+ '/api/v2/webhook/github/{}/'.format(self.project.slug),
{'ref': 'v1.0'},
format='json',
)
@@ -919,7 +918,7 @@ def test_github_webhook_for_tags(self, trigger_build):
[mock.call(force=True, version=self.version_tag, project=self.project)])
client.post(
- '/api/v2/webhook/github/{0}/'.format(self.project.slug),
+ '/api/v2/webhook/github/{}/'.format(self.project.slug),
{'ref': 'refs/heads/non-existent'},
format='json',
)
@@ -927,7 +926,7 @@ def test_github_webhook_for_tags(self, trigger_build):
[mock.call(force=True, version=mock.ANY, project=self.project)])
client.post(
- '/api/v2/webhook/github/{0}/'.format(self.project.slug),
+ '/api/v2/webhook/github/{}/'.format(self.project.slug),
{'ref': 'refs/tags/v1.0'},
format='json',
)
@@ -986,7 +985,7 @@ def test_github_invalid_webhook(self, trigger_build):
"""GitHub webhook unhandled event."""
client = APIClient()
resp = client.post(
- '/api/v2/webhook/github/{0}/'.format(self.project.slug),
+ '/api/v2/webhook/github/{}/'.format(self.project.slug),
{'foo': 'bar'},
format='json',
HTTP_X_GITHUB_EVENT='pull_request',
@@ -1146,7 +1145,7 @@ def test_gitlab_invalid_webhook(self, trigger_build):
"""GitLab webhook unhandled event."""
client = APIClient()
resp = client.post(
- '/api/v2/webhook/gitlab/{0}/'.format(self.project.slug),
+ '/api/v2/webhook/gitlab/{}/'.format(self.project.slug),
{'object_kind': 'pull_request'},
format='json',
)
@@ -1236,7 +1235,7 @@ def test_bitbucket_invalid_webhook(self, trigger_build):
"""Bitbucket webhook unhandled event."""
client = APIClient()
resp = client.post(
- '/api/v2/webhook/bitbucket/{0}/'.format(self.project.slug),
+ '/api/v2/webhook/bitbucket/{}/'.format(self.project.slug),
{'foo': 'bar'}, format='json', HTTP_X_EVENT_KEY='pull_request')
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.data['detail'], 'Unhandled webhook event')
@@ -1244,7 +1243,7 @@ def test_bitbucket_invalid_webhook(self, trigger_build):
def test_generic_api_fails_without_auth(self, trigger_build):
client = APIClient()
resp = client.post(
- '/api/v2/webhook/generic/{0}/'.format(self.project.slug),
+ '/api/v2/webhook/generic/{}/'.format(self.project.slug),
{},
format='json',
)
@@ -1262,7 +1261,7 @@ def test_generic_api_respects_token_auth(self, trigger_build):
)
self.assertIsNotNone(integration.token)
resp = client.post(
- '/api/v2/webhook/{0}/{1}/'.format(
+ '/api/v2/webhook/{}/{}/'.format(
self.project.slug,
integration.pk,
),
@@ -1273,7 +1272,7 @@ def test_generic_api_respects_token_auth(self, trigger_build):
self.assertTrue(resp.data['build_triggered'])
# Test nonexistent branch
resp = client.post(
- '/api/v2/webhook/{0}/{1}/'.format(
+ '/api/v2/webhook/{}/{}/'.format(
self.project.slug,
integration.pk,
),
@@ -1289,7 +1288,7 @@ def test_generic_api_respects_basic_auth(self, trigger_build):
self.project.users.add(user)
client.force_authenticate(user=user)
resp = client.post(
- '/api/v2/webhook/generic/{0}/'.format(self.project.slug),
+ '/api/v2/webhook/generic/{}/'.format(self.project.slug),
{},
format='json',
)
@@ -1304,7 +1303,7 @@ def test_generic_api_falls_back_to_token_auth(self, trigger_build):
project=self.project, integration_type=Integration.API_WEBHOOK)
self.assertIsNotNone(integration.token)
resp = client.post(
- '/api/v2/webhook/{0}/{1}/'.format(
+ '/api/v2/webhook/{}/{}/'.format(
self.project.slug,
integration.pk,
),
diff --git a/readthedocs/rtd_tests/tests/test_backend.py b/readthedocs/rtd_tests/tests/test_backend.py
index ca0de802c9e..34510778fea 100644
--- a/readthedocs/rtd_tests/tests/test_backend.py
+++ b/readthedocs/rtd_tests/tests/test_backend.py
@@ -34,7 +34,7 @@
class TestGitBackend(RTDTestCase):
def setUp(self):
git_repo = make_test_git()
- super(TestGitBackend, self).setUp()
+ super().setUp()
self.eric = User(username='eric')
self.eric.set_password('test')
self.eric.save()
@@ -128,7 +128,7 @@ def test_git_tags(self):
# so we need to hack the repo path
repo.working_dir = repo_path
self.assertEqual(
- set(['v01', 'v02', 'release-ünîø∂é']),
+ {'v01', 'v02', 'release-ünîø∂é'},
{vcs.verbose_name for vcs in repo.tags},
)
@@ -207,28 +207,28 @@ def test_fetch_clean_tags_and_branches(self, checkout_path):
# We still have all branches and tags in the local repo
self.assertEqual(
- set(['v01', 'v02']),
- set(vcs.verbose_name for vcs in repo.tags)
+ {'v01', 'v02'},
+ {vcs.verbose_name for vcs in repo.tags}
)
self.assertEqual(
- set([
+ {
'invalidsubmodule', 'master', 'submodule', 'newbranch',
- ]),
- set(vcs.verbose_name for vcs in repo.branches)
+ },
+ {vcs.verbose_name for vcs in repo.branches}
)
repo.update()
# We don't have the eliminated branches and tags in the local repo
self.assertEqual(
- set(['v01']),
- set(vcs.verbose_name for vcs in repo.tags)
+ {'v01'},
+ {vcs.verbose_name for vcs in repo.tags}
)
self.assertEqual(
- set([
+ {
'invalidsubmodule', 'master', 'submodule'
- ]),
- set(vcs.verbose_name for vcs in repo.branches)
+ },
+ {vcs.verbose_name for vcs in repo.branches}
)
@@ -236,7 +236,7 @@ class TestHgBackend(RTDTestCase):
def setUp(self):
hg_repo = make_test_hg()
- super(TestHgBackend, self).setUp()
+ super().setUp()
self.eric = User(username='eric')
self.eric.set_password('test')
self.eric.save()
diff --git a/readthedocs/rtd_tests/tests/test_build_config.py b/readthedocs/rtd_tests/tests/test_build_config.py
index b0c2186d419..9085d6f7926 100644
--- a/readthedocs/rtd_tests/tests/test_build_config.py
+++ b/readthedocs/rtd_tests/tests/test_build_config.py
@@ -28,7 +28,7 @@ class PathValidator(Validator):
configuration_file = '.'
def _is_valid(self, value):
- if isinstance(value, six.string_types):
+ if isinstance(value, str):
file_ = path.join(
path.dirname(self.configuration_file),
value
diff --git a/readthedocs/rtd_tests/tests/test_celery.py b/readthedocs/rtd_tests/tests/test_celery.py
index 11a9e31e46f..e718e220b36 100644
--- a/readthedocs/rtd_tests/tests/test_celery.py
+++ b/readthedocs/rtd_tests/tests/test_celery.py
@@ -30,7 +30,7 @@ class TestCeleryBuilding(RTDTestCase):
def setUp(self):
repo = make_test_git()
self.repo = repo
- super(TestCeleryBuilding, self).setUp()
+ super().setUp()
self.eric = User(username='eric')
self.eric.set_password('test')
self.eric.save()
@@ -44,7 +44,7 @@ def setUp(self):
def tearDown(self):
shutil.rmtree(self.repo)
- super(TestCeleryBuilding, self).tearDown()
+ super().tearDown()
def test_remove_dir(self):
directory = mkdtemp()
diff --git a/readthedocs/rtd_tests/tests/test_config_integration.py b/readthedocs/rtd_tests/tests/test_config_integration.py
index 2b2c6ceb489..8474c4bea11 100644
--- a/readthedocs/rtd_tests/tests/test_config_integration.py
+++ b/readthedocs/rtd_tests/tests/test_config_integration.py
@@ -288,7 +288,7 @@ def test_requirements_file_from_yml(self, checkout_path):
@pytest.mark.django_db
@mock.patch('readthedocs.projects.models.Project.checkout_path')
-class TestLoadConfigV2(object):
+class TestLoadConfigV2:
@pytest.fixture(autouse=True)
def create_project(self):
diff --git a/readthedocs/rtd_tests/tests/test_doc_building.py b/readthedocs/rtd_tests/tests/test_doc_building.py
index 6a954de74d6..1b8f11c3880 100644
--- a/readthedocs/rtd_tests/tests/test_doc_building.py
+++ b/readthedocs/rtd_tests/tests/test_doc_building.py
@@ -45,7 +45,7 @@
DUMMY_BUILD_ID = 123
-SAMPLE_UNICODE = u'HérÉ îß sömê ünïçó∂é'
+SAMPLE_UNICODE = 'HérÉ îß sömê ünïçó∂é'
SAMPLE_UTF8_BYTES = SAMPLE_UNICODE.encode('utf-8')
@@ -83,7 +83,7 @@ def test_normal_execution(self):
self.assertTrue(build_env.done)
self.assertTrue(build_env.successful)
self.assertEqual(len(build_env.commands), 1)
- self.assertEqual(build_env.commands[0].output, u'This is okay')
+ self.assertEqual(build_env.commands[0].output, 'This is okay')
# api() is not called anymore, we use api_v2 instead
self.assertFalse(self.mocks.api()(DUMMY_BUILD_ID).put.called)
@@ -103,7 +103,7 @@ def test_normal_execution(self):
'version': self.version.pk,
'success': True,
'project': self.project.pk,
- 'setup_error': u'',
+ 'setup_error': '',
'length': mock.ANY,
'error': '',
'setup': '',
@@ -169,7 +169,7 @@ def test_record_command_as_success(self):
self.assertTrue(build_env.done)
self.assertTrue(build_env.successful)
self.assertEqual(len(build_env.commands), 1)
- self.assertEqual(build_env.commands[0].output, u'This is okay')
+ self.assertEqual(build_env.commands[0].output, 'This is okay')
# api() is not called anymore, we use api_v2 instead
self.assertFalse(self.mocks.api()(DUMMY_BUILD_ID).put.called)
@@ -189,7 +189,7 @@ def test_record_command_as_success(self):
'version': self.version.pk,
'success': True,
'project': self.project.pk,
- 'setup_error': u'',
+ 'setup_error': '',
'length': mock.ANY,
'error': '',
'setup': '',
@@ -254,7 +254,7 @@ def test_failing_execution(self):
self.assertTrue(build_env.done)
self.assertTrue(build_env.failed)
self.assertEqual(len(build_env.commands), 1)
- self.assertEqual(build_env.commands[0].output, u'This is not okay')
+ self.assertEqual(build_env.commands[0].output, 'This is not okay')
# api() is not called anymore, we use api_v2 instead
self.assertFalse(self.mocks.api()(DUMMY_BUILD_ID).put.called)
@@ -274,7 +274,7 @@ def test_failing_execution(self):
'version': self.version.pk,
'success': False,
'project': self.project.pk,
- 'setup_error': u'',
+ 'setup_error': '',
'length': mock.ANY,
'error': '',
'setup': '',
@@ -537,13 +537,13 @@ def _inner():
'version': self.version.pk,
'success': False,
'project': self.project.pk,
- 'setup_error': u'',
+ 'setup_error': '',
'exit_code': 1,
'length': mock.ANY,
'error': 'Build environment creation failed',
- 'setup': u'',
- 'output': u'',
- 'state': u'finished',
+ 'setup': '',
+ 'output': '',
+ 'state': 'finished',
'builder': mock.ANY,
})
@@ -588,13 +588,13 @@ def test_api_failure_on_docker_memory_limit(self):
'version': self.version.pk,
'success': False,
'project': self.project.pk,
- 'setup_error': u'',
+ 'setup_error': '',
'exit_code': -1,
'length': mock.ANY,
'error': '',
- 'setup': u'',
- 'output': u'',
- 'state': u'finished',
+ 'setup': '',
+ 'output': '',
+ 'state': 'finished',
'builder': mock.ANY,
})
@@ -694,7 +694,7 @@ def test_command_execution(self):
container='build-123-project-6-pip',
cmd="/bin/sh -c 'cd /tmp && echo\\ test'", stderr=True, stdout=True)
self.assertEqual(build_env.commands[0].exit_code, 1)
- self.assertEqual(build_env.commands[0].output, u'This is the return')
+ self.assertEqual(build_env.commands[0].output, 'This is the return')
self.assertEqual(build_env.commands[0].error, None)
self.assertTrue(build_env.failed)
@@ -789,7 +789,7 @@ def test_record_command_as_success(self):
container='build-123-project-6-pip',
cmd="/bin/sh -c 'cd /tmp && echo\\ test'", stderr=True, stdout=True)
self.assertEqual(build_env.commands[0].exit_code, 0)
- self.assertEqual(build_env.commands[0].output, u'This is the return')
+ self.assertEqual(build_env.commands[0].output, 'This is the return')
self.assertEqual(build_env.commands[0].error, None)
self.assertFalse(build_env.failed)
@@ -867,12 +867,12 @@ def test_command_execution_cleanup_exception(self):
'error': '',
'success': True,
'project': self.project.pk,
- 'setup_error': u'',
+ 'setup_error': '',
'exit_code': 0,
'length': 0,
- 'setup': u'',
- 'output': u'',
- 'state': u'finished',
+ 'setup': '',
+ 'output': '',
+ 'state': 'finished',
'builder': mock.ANY,
})
@@ -970,13 +970,13 @@ def test_container_timeout(self):
'version': self.version.pk,
'success': False,
'project': self.project.pk,
- 'setup_error': u'',
+ 'setup_error': '',
'exit_code': 1,
'length': 0,
'error': 'Build exited due to time out',
- 'setup': u'',
- 'output': u'',
- 'state': u'finished',
+ 'setup': '',
+ 'output': '',
+ 'state': 'finished',
'builder': mock.ANY,
})
@@ -1068,7 +1068,7 @@ def test_unicode_output(self, mock_subprocess):
cmd.run()
self.assertEqual(
cmd.output,
- u'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9')
+ 'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9')
class TestDockerBuildCommand(TestCase):
@@ -1117,7 +1117,7 @@ def test_unicode_output(self):
cmd.run()
self.assertEqual(
cmd.output,
- u'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9')
+ 'H\xe9r\xc9 \xee\xdf s\xf6m\xea \xfcn\xef\xe7\xf3\u2202\xe9')
self.assertEqual(self.mocks.docker_client.exec_start.call_count, 1)
self.assertEqual(self.mocks.docker_client.exec_create.call_count, 1)
self.assertEqual(self.mocks.docker_client.exec_inspect.call_count, 1)
@@ -1402,7 +1402,7 @@ def test_install_user_requirements_conda(self, checkout_path):
self.build_env_mock.run.assert_not_called()
-class AutoWipeEnvironmentBase(object):
+class AutoWipeEnvironmentBase:
fixtures = ['test_data']
build_env_class = None
diff --git a/readthedocs/rtd_tests/tests/test_extend.py b/readthedocs/rtd_tests/tests/test_extend.py
index 205fa64e7bc..d3c66552b6a 100644
--- a/readthedocs/rtd_tests/tests/test_extend.py
+++ b/readthedocs/rtd_tests/tests/test_extend.py
@@ -7,7 +7,7 @@
# Top level to ensure module name is correct
-class FooBase(object):
+class FooBase:
def bar(self):
return 1
diff --git a/readthedocs/rtd_tests/tests/test_integrations.py b/readthedocs/rtd_tests/tests/test_integrations.py
index 51006b7e757..17f9b291253 100644
--- a/readthedocs/rtd_tests/tests/test_integrations.py
+++ b/readthedocs/rtd_tests/tests/test_integrations.py
@@ -30,7 +30,7 @@ def test_exchange_json_request_body(self):
integration_type=Integration.GITHUB_WEBHOOK,
provider_data='')
resp = client.post(
- '/api/v2/webhook/github/{0}/'.format(project.slug),
+ '/api/v2/webhook/github/{}/'.format(project.slug),
{'ref': 'exchange_json'},
format='json'
)
@@ -41,8 +41,8 @@ def test_exchange_json_request_body(self):
)
self.assertEqual(
exchange.request_headers,
- {u'Content-Type': u'application/json; charset=None',
- u'Cookie': u''}
+ {'Content-Type': 'application/json; charset=None',
+ 'Cookie': ''}
)
self.assertEqual(
exchange.response_body,
@@ -51,8 +51,8 @@ def test_exchange_json_request_body(self):
)
self.assertEqual(
exchange.response_headers,
- {u'Allow': u'POST, OPTIONS',
- u'Content-Type': u'text/html; charset=utf-8'}
+ {'Allow': 'POST, OPTIONS',
+ 'Content-Type': 'text/html; charset=utf-8'}
)
def test_exchange_form_request_body(self):
@@ -63,7 +63,7 @@ def test_exchange_form_request_body(self):
integration_type=Integration.GITHUB_WEBHOOK,
provider_data='')
resp = client.post(
- '/api/v2/webhook/github/{0}/'.format(project.slug),
+ '/api/v2/webhook/github/{}/'.format(project.slug),
'payload=%7B%22ref%22%3A+%22exchange_form%22%7D',
content_type='application/x-www-form-urlencoded',
)
@@ -74,8 +74,8 @@ def test_exchange_form_request_body(self):
)
self.assertEqual(
exchange.request_headers,
- {u'Content-Type': u'application/x-www-form-urlencoded',
- u'Cookie': u''}
+ {'Content-Type': 'application/x-www-form-urlencoded',
+ 'Cookie': ''}
)
self.assertEqual(
exchange.response_body,
@@ -84,8 +84,8 @@ def test_exchange_form_request_body(self):
)
self.assertEqual(
exchange.response_headers,
- {u'Allow': u'POST, OPTIONS',
- u'Content-Type': u'text/html; charset=utf-8'}
+ {'Allow': 'POST, OPTIONS',
+ 'Content-Type': 'text/html; charset=utf-8'}
)
def test_extraneous_exchanges_deleted_in_correct_order(self):
@@ -103,13 +103,13 @@ def test_extraneous_exchanges_deleted_in_correct_order(self):
for _ in range(10):
resp = client.post(
- '/api/v2/webhook/github/{0}/'.format(project.slug),
+ '/api/v2/webhook/github/{}/'.format(project.slug),
{'ref': 'deleted'},
format='json'
)
for _ in range(10):
resp = client.post(
- '/api/v2/webhook/github/{0}/'.format(project.slug),
+ '/api/v2/webhook/github/{}/'.format(project.slug),
{'ref': 'preserved'},
format='json'
)
@@ -134,7 +134,7 @@ def test_request_headers_are_removed(self):
integration_type=Integration.GITHUB_WEBHOOK,
provider_data='')
resp = client.post(
- '/api/v2/webhook/github/{0}/'.format(project.slug),
+ '/api/v2/webhook/github/{}/'.format(project.slug),
{'ref': 'exchange_json'},
format='json',
HTTP_X_FORWARDED_FOR='1.2.3.4',
@@ -144,9 +144,9 @@ def test_request_headers_are_removed(self):
exchange = HttpExchange.objects.get(integrations=integration)
self.assertEqual(
exchange.request_headers,
- {u'Content-Type': u'application/json; charset=None',
- u'Cookie': u'',
- u'X-Foo': u'bar'}
+ {'Content-Type': 'application/json; charset=None',
+ 'Cookie': '',
+ 'X-Foo': 'bar'}
)
diff --git a/readthedocs/rtd_tests/tests/test_notifications.py b/readthedocs/rtd_tests/tests/test_notifications.py
index 2213b0d8c7c..adae07e49fe 100644
--- a/readthedocs/rtd_tests/tests/test_notifications.py
+++ b/readthedocs/rtd_tests/tests/test_notifications.py
@@ -43,7 +43,7 @@ class TestNotification(Notification):
self.assertEqual(notify.get_template_names('site'),
['builds/notifications/foo_site.html'])
self.assertEqual(notify.get_subject(),
- 'This is {0}'.format(build.id))
+ 'This is {}'.format(build.id))
self.assertEqual(notify.get_context_data(),
{'foo': build,
'production_uri': 'https://readthedocs.org',
@@ -82,7 +82,7 @@ class TestNotification(Notification):
request=mock.ANY,
template='core/email/common.txt',
context={'content': 'Test'},
- subject=u'This is {}'.format(build.id),
+ subject='This is {}'.format(build.id),
template_html='core/email/common.html',
recipient=user.email,
)
diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py
index 277c566e22e..f793f8c51ab 100644
--- a/readthedocs/rtd_tests/tests/test_privacy_urls.py
+++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py
@@ -19,7 +19,7 @@
from readthedocs.rtd_tests.utils import create_user
-class URLAccessMixin(object):
+class URLAccessMixin:
default_kwargs = {}
response_data = {}
@@ -93,10 +93,10 @@ def setUp(self):
for not_obj in self.context_data:
if isinstance(obj, list) or isinstance(obj, set) or isinstance(obj, tuple):
self.assertNotIn(not_obj, obj)
- print("%s not in %s" % (not_obj, obj))
+ print("{} not in {}".format(not_obj, obj))
else:
self.assertNotEqual(not_obj, obj)
- print("%s is not %s" % (not_obj, obj))
+ print("{} is not {}".format(not_obj, obj))
def _test_url(self, urlpatterns):
deconstructed_urls = extract_views_from_urlpatterns(urlpatterns)
@@ -134,7 +134,7 @@ def setUp(self):
class ProjectMixin(URLAccessMixin):
def setUp(self):
- super(ProjectMixin, self).setUp()
+ super().setUp()
self.build = get(Build, project=self.pip)
self.tag = get(Tag, slug='coolness')
self.subproject = get(Project, slug='sub', language='ja',
@@ -307,7 +307,7 @@ def is_admin(self):
class APIMixin(URLAccessMixin):
def setUp(self):
- super(APIMixin, self).setUp()
+ super().setUp()
self.build = get(Build, project=self.pip)
self.build_command_result = get(BuildCommandResult, project=self.pip)
self.domain = get(Domain, url='http://docs.foobar.com', project=self.pip)
diff --git a/readthedocs/rtd_tests/tests/test_project.py b/readthedocs/rtd_tests/tests/test_project.py
index 679b761a25a..bbbd43a11a3 100644
--- a/readthedocs/rtd_tests/tests/test_project.py
+++ b/readthedocs/rtd_tests/tests/test_project.py
@@ -22,7 +22,7 @@
from readthedocs.rtd_tests.mocks.paths import fake_paths_by_regex
-class ProjectMixin(object):
+class ProjectMixin:
fixtures = ['eric', 'test_data']
diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py
index 6d358123b65..e7fefb5bbbf 100644
--- a/readthedocs/rtd_tests/tests/test_project_forms.py
+++ b/readthedocs/rtd_tests/tests/test_project_forms.py
@@ -242,10 +242,10 @@ def test_list_only_active_versions_on_default_version(self):
# This version is created automatically by the project on save
self.assertTrue(self.project.versions.filter(slug=LATEST).exists())
self.assertEqual(
- set(
+ {
slug
for slug, _ in form.fields['default_version'].widget.choices
- ),
+ },
{'latest', 'public-1', 'public-2', 'private', 'protected'},
)
@@ -254,10 +254,10 @@ def test_list_all_versions_on_default_branch(self):
# This version is created automatically by the project on save
self.assertTrue(self.project.versions.filter(slug=LATEST).exists())
self.assertEqual(
- set(
+ {
identifier
for identifier, _ in form.fields['default_branch'].widget.choices
- ),
+ },
{
None, 'master', 'public-1', 'public-2',
'public-3', 'public/4', 'protected', 'private'
diff --git a/readthedocs/rtd_tests/tests/test_project_symlinks.py b/readthedocs/rtd_tests/tests/test_project_symlinks.py
index 307e4e4516f..e35ca38743d 100644
--- a/readthedocs/rtd_tests/tests/test_project_symlinks.py
+++ b/readthedocs/rtd_tests/tests/test_project_symlinks.py
@@ -97,7 +97,7 @@ def setUp(self):
new_callable=mock.PropertyMock
),
}
- self.patches = dict((key, mock.start()) for (key, mock) in list(self.mocks.items()))
+ self.patches = {key: mock.start() for (key, mock) in list(self.mocks.items())}
self.patches['PublicSymlinkBase.CNAME_ROOT'].return_value = os.path.join(
settings.SITE_ROOT, 'public_cname_root'
)
@@ -127,10 +127,10 @@ def assertFilesystem(self, filesystem):
self.assertEqual(filesystem, get_filesystem(settings.SITE_ROOT))
-class BaseSymlinkCnames(object):
+class BaseSymlinkCnames:
def setUp(self):
- super(BaseSymlinkCnames, self).setUp()
+ super().setUp()
self.project = get(Project, slug='kong', privacy_level=self.privacy,
main_language_project=None)
self.project.versions.update(privacy_level=self.privacy)
@@ -303,10 +303,10 @@ class TestPrivateSymlinkCnames(BaseSymlinkCnames, TempSiteRootTestCase):
symlink_class = PrivateSymlink
-class BaseSubprojects(object):
+class BaseSubprojects:
def setUp(self):
- super(BaseSubprojects, self).setUp()
+ super().setUp()
self.project = get(Project, slug='kong', privacy_level=self.privacy,
main_language_project=None)
self.project.versions.update(privacy_level=self.privacy)
@@ -514,10 +514,10 @@ class TestPrivateSubprojects(BaseSubprojects, TempSiteRootTestCase):
symlink_class = PrivateSymlink
-class BaseSymlinkTranslations(object):
+class BaseSymlinkTranslations:
def setUp(self):
- super(BaseSymlinkTranslations, self).setUp()
+ super().setUp()
self.project = get(Project, slug='kong', privacy_level=self.privacy,
main_language_project=None)
self.project.versions.update(privacy_level=self.privacy)
@@ -766,10 +766,10 @@ class TestPrivateSymlinkTranslations(BaseSymlinkTranslations, TempSiteRootTestCa
symlink_class = PrivateSymlink
-class BaseSymlinkSingleVersion(object):
+class BaseSymlinkSingleVersion:
def setUp(self):
- super(BaseSymlinkSingleVersion, self).setUp()
+ super().setUp()
self.project = get(Project, slug='kong', privacy_level=self.privacy,
main_language_project=None)
self.project.versions.update(privacy_level=self.privacy)
@@ -841,10 +841,10 @@ class TestPublicSymlinkSingleVersion(BaseSymlinkSingleVersion, TempSiteRootTestC
symlink_class = PrivateSymlink
-class BaseSymlinkVersions(object):
+class BaseSymlinkVersions:
def setUp(self):
- super(BaseSymlinkVersions, self).setUp()
+ super().setUp()
self.project = get(Project, slug='kong', privacy_level=self.privacy,
main_language_project=None)
self.project.versions.update(privacy_level=self.privacy)
@@ -970,11 +970,11 @@ class TestPrivateSymlinkVersions(BaseSymlinkVersions, TempSiteRootTestCase):
class TestPublicSymlinkUnicode(TempSiteRootTestCase):
def setUp(self):
- super(TestPublicSymlinkUnicode, self).setUp()
- self.project = get(Project, slug='kong', name=u'foo-∫',
+ super().setUp()
+ self.project = get(Project, slug='kong', name='foo-∫',
main_language_project=None)
self.project.save()
- self.stable = get(Version, slug='foo-a', verbose_name=u'foo-∂',
+ self.stable = get(Version, slug='foo-a', verbose_name='foo-∂',
active=True, project=self.project)
self.symlink = PublicSymlink(self.project)
@@ -1035,7 +1035,7 @@ def test_symlink_broadcast_calls_on_project_save(self):
class TestPublicPrivateSymlink(TempSiteRootTestCase):
def setUp(self):
- super(TestPublicPrivateSymlink, self).setUp()
+ super().setUp()
from django.contrib.auth.models import User
self.user = get(User)
diff --git a/readthedocs/rtd_tests/tests/test_project_views.py b/readthedocs/rtd_tests/tests/test_project_views.py
index c6e4a93f884..124fd018c00 100644
--- a/readthedocs/rtd_tests/tests/test_project_views.py
+++ b/readthedocs/rtd_tests/tests/test_project_views.py
@@ -32,7 +32,7 @@ class TestProfileMiddleware(RequestFactoryTestMixin, TestCase):
url = '/dashboard/import/manual/'
def setUp(self):
- super(TestProfileMiddleware, self).setUp()
+ super().setUp()
data = {
'basics': {
'name': 'foobar',
@@ -47,9 +47,9 @@ def setUp(self):
}
self.data = {}
for key in data:
- self.data.update({('{0}-{1}'.format(key, k), v)
+ self.data.update({('{}-{}'.format(key, k), v)
for (k, v) in list(data[key].items())})
- self.data['{0}-current_step'.format(self.wizard_class_slug)] = 'extra'
+ self.data['{}-current_step'.format(self.wizard_class_slug)] = 'extra'
def test_profile_middleware_no_profile(self):
"""User without profile and isn't banned"""
@@ -101,7 +101,7 @@ def tearDown(self):
def request(self, *args, **kwargs):
kwargs['user'] = self.user
- return super(TestBasicsForm, self).request(*args, **kwargs)
+ return super().request(*args, **kwargs)
def test_form_pass(self):
"""Only submit the basics"""
@@ -146,7 +146,7 @@ def test_form_missing(self):
class TestAdvancedForm(TestBasicsForm):
def setUp(self):
- super(TestAdvancedForm, self).setUp()
+ super().setUp()
self.step_data['basics']['advanced'] = True
self.step_data['extra'] = {
'description': 'Describe foobar',
@@ -169,10 +169,9 @@ def test_form_pass(self):
data = self.step_data['basics']
del data['advanced']
del self.step_data['extra']['tags']
- six.assertCountEqual(
- self,
+ self.assertCountEqual(
[tag.name for tag in proj.tags.all()],
- [u'bar', u'baz', u'foo'])
+ ['bar', 'baz', 'foo'])
data.update(self.step_data['extra'])
for (key, val) in list(data.items()):
self.assertEqual(getattr(proj, key), val)
diff --git a/readthedocs/rtd_tests/tests/test_resolver.py b/readthedocs/rtd_tests/tests/test_resolver.py
index 1ef55d564ca..841da05c1b6 100644
--- a/readthedocs/rtd_tests/tests/test_resolver.py
+++ b/readthedocs/rtd_tests/tests/test_resolver.py
@@ -627,7 +627,7 @@ def test_resolver_domain_https(self):
self.assertEqual(url, 'http://pip.readthedocs.io/en/latest/')
-class ResolverAltSetUp(object):
+class ResolverAltSetUp:
def setUp(self):
with mock.patch('readthedocs.projects.models.broadcast'):
diff --git a/readthedocs/rtd_tests/tests/test_urls.py b/readthedocs/rtd_tests/tests/test_urls.py
index d70e0be8b71..cb4204e2870 100644
--- a/readthedocs/rtd_tests/tests/test_urls.py
+++ b/readthedocs/rtd_tests/tests/test_urls.py
@@ -48,7 +48,7 @@ class TestVersionURLs(TestCase):
def test_version_url_with_caps(self):
url = reverse(
'project_download_media',
- kwargs={'type_': 'pdf', 'version_slug': u'1.4.X', 'project_slug': u'django'}
+ kwargs={'type_': 'pdf', 'version_slug': '1.4.X', 'project_slug': 'django'}
)
self.assertTrue(url)
diff --git a/readthedocs/rtd_tests/tests/test_version_commit_name.py b/readthedocs/rtd_tests/tests/test_version_commit_name.py
index e9ffd833ee7..943843c22f8 100644
--- a/readthedocs/rtd_tests/tests/test_version_commit_name.py
+++ b/readthedocs/rtd_tests/tests/test_version_commit_name.py
@@ -20,44 +20,44 @@ def test_branch_name_unicode_non_ascii(self):
self.assertEqual(version.identifier_friendly, unicode_name)
def test_branch_name_made_friendly_when_sha(self):
- commit_hash = u'3d92b728b7d7b842259ac2020c2fa389f13aff0d'
+ commit_hash = '3d92b728b7d7b842259ac2020c2fa389f13aff0d'
version = new(Version, identifier=commit_hash,
slug=STABLE, verbose_name=STABLE, type=TAG)
# we shorten commit hashes to keep things readable
self.assertEqual(version.identifier_friendly, '3d92b728')
def test_branch_name(self):
- version = new(Version, identifier=u'release-2.5.x',
- slug=u'release-2.5.x', verbose_name=u'release-2.5.x',
+ version = new(Version, identifier='release-2.5.x',
+ slug='release-2.5.x', verbose_name='release-2.5.x',
type=BRANCH)
self.assertEqual(version.commit_name, 'release-2.5.x')
def test_tag_name(self):
- version = new(Version, identifier=u'10f1b29a2bd2', slug=u'release-2.5.0',
- verbose_name=u'release-2.5.0', type=TAG)
- self.assertEqual(version.commit_name, u'release-2.5.0')
+ version = new(Version, identifier='10f1b29a2bd2', slug='release-2.5.0',
+ verbose_name='release-2.5.0', type=TAG)
+ self.assertEqual(version.commit_name, 'release-2.5.0')
def test_branch_with_name_stable(self):
- version = new(Version, identifier=u'origin/stable', slug=STABLE,
- verbose_name=u'stable', type=BRANCH)
- self.assertEqual(version.commit_name, u'stable')
+ version = new(Version, identifier='origin/stable', slug=STABLE,
+ verbose_name='stable', type=BRANCH)
+ self.assertEqual(version.commit_name, 'stable')
def test_stable_version_tag(self):
version = new(Version,
- identifier=u'3d92b728b7d7b842259ac2020c2fa389f13aff0d',
+ identifier='3d92b728b7d7b842259ac2020c2fa389f13aff0d',
slug=STABLE, verbose_name=STABLE, type=TAG)
self.assertEqual(version.commit_name,
- u'3d92b728b7d7b842259ac2020c2fa389f13aff0d')
+ '3d92b728b7d7b842259ac2020c2fa389f13aff0d')
def test_hg_latest_branch(self):
hg_project = get(Project, repo_type=REPO_TYPE_HG)
- version = new(Version, identifier=u'default', slug=LATEST,
+ version = new(Version, identifier='default', slug=LATEST,
verbose_name=LATEST, type=BRANCH, project=hg_project)
- self.assertEqual(version.commit_name, u'default')
+ self.assertEqual(version.commit_name, 'default')
def test_git_latest_branch(self):
git_project = get(Project, repo_type=REPO_TYPE_GIT)
version = new(Version, project=git_project,
- identifier=u'origin/master', slug=LATEST,
+ identifier='origin/master', slug=LATEST,
verbose_name=LATEST, type=BRANCH)
- self.assertEqual(version.commit_name, u'master')
+ self.assertEqual(version.commit_name, 'master')
diff --git a/readthedocs/search/indexes.py b/readthedocs/search/indexes.py
index 48e4baecc5e..189f441d84e 100644
--- a/readthedocs/search/indexes.py
+++ b/readthedocs/search/indexes.py
@@ -25,7 +25,7 @@
from django.conf import settings
-class Index(object):
+class Index:
"""Base class to define some common methods across indexes."""
@@ -92,7 +92,7 @@ def get_analysis(self):
}
def timestamped_index(self):
- return '{0}-{1}'.format(
+ return '{}-{}'.format(
self._index, timezone.now().strftime('%Y%m%d%H%M%S'))
def create_index(self, index=None):
diff --git a/readthedocs/search/parse_json.py b/readthedocs/search/parse_json.py
index 9b19a7e7cb3..baa4abe5eca 100644
--- a/readthedocs/search/parse_json.py
+++ b/readthedocs/search/parse_json.py
@@ -57,7 +57,7 @@ def generate_sections_from_pyquery(body):
h1_section = body('.section > h1')
if h1_section:
div = h1_section.parent()
- h1_title = h1_section.text().replace(u'¶', '').strip()
+ h1_title = h1_section.text().replace('¶', '').strip()
h1_id = div.attr('id')
h1_content = ""
next_p = body('h1').next()
@@ -79,7 +79,7 @@ def generate_sections_from_pyquery(body):
for num in range(len(section_list)):
div = section_list.eq(num).parent()
header = section_list.eq(num)
- title = header.text().replace(u'¶', '').strip()
+ title = header.text().replace('¶', '').strip()
section_id = div.attr('id')
content = div.html()
yield {
@@ -108,7 +108,7 @@ def process_file(filename):
return None
if 'body' in data and data['body']:
body = PyQuery(data['body'])
- body_content = body.text().replace(u'¶', '')
+ body_content = body.text().replace('¶', '')
sections.extend(generate_sections_from_pyquery(body))
else:
log.info('Unable to index content for: %s', filename)
diff --git a/readthedocs/search/tests/test_views.py b/readthedocs/search/tests/test_views.py
index 07444a731fb..d5ac18bafc9 100644
--- a/readthedocs/search/tests/test_views.py
+++ b/readthedocs/search/tests/test_views.py
@@ -14,7 +14,7 @@
@pytest.mark.django_db
@pytest.mark.search
-class TestElasticSearch(object):
+class TestElasticSearch:
url = reverse_lazy('search')
diff --git a/readthedocs/search/utils.py b/readthedocs/search/utils.py
index a742a341912..ec429e4ace9 100644
--- a/readthedocs/search/utils.py
+++ b/readthedocs/search/utils.py
@@ -207,7 +207,7 @@ def parse_sphinx_sections(content):
h1_section = body('.section > h1')
if h1_section:
div = h1_section.parent()
- h1_title = h1_section.text().replace(u'¶', '').strip()
+ h1_title = h1_section.text().replace('¶', '').strip()
h1_id = div.attr('id')
h1_content = ""
next_p = next(body('h1')) # pylint: disable=stop-iteration-return
@@ -229,7 +229,7 @@ def parse_sphinx_sections(content):
for num in range(len(section_list)):
div = section_list.eq(num).parent()
header = section_list.eq(num)
- title = header.text().replace(u'¶', '').strip()
+ title = header.text().replace('¶', '').strip()
section_id = div.attr('id')
content = div.html()
yield {
diff --git a/readthedocs/search/views.py b/readthedocs/search/views.py
index bac1969e80e..e1725f87fdc 100644
--- a/readthedocs/search/views.py
+++ b/readthedocs/search/views.py
@@ -14,7 +14,7 @@
from readthedocs.search import lib as search_lib
log = logging.getLogger(__name__)
-LOG_TEMPLATE = u'(Elastic Search) [{user}:{type}] [{project}:{version}:{language}] {msg}'
+LOG_TEMPLATE = '(Elastic Search) [{user}:{type}] [{project}:{version}:{language}] {msg}'
UserInput = collections.namedtuple(
'UserInput',
diff --git a/readthedocs/settings/base.py b/readthedocs/settings/base.py
index a6a3e867978..205dbdd9989 100644
--- a/readthedocs/settings/base.py
+++ b/readthedocs/settings/base.py
@@ -43,7 +43,7 @@ class CommunityBaseSettings(Settings):
PUBLIC_DOMAIN = None
PUBLIC_DOMAIN_USES_HTTPS = False
USE_SUBDOMAIN = False
- PUBLIC_API_URL = 'https://{0}'.format(PRODUCTION_DOMAIN)
+ PUBLIC_API_URL = 'https://{}'.format(PRODUCTION_DOMAIN)
# Email
DEFAULT_FROM_EMAIL = 'no-reply@readthedocs.org'
diff --git a/readthedocs/settings/dev.py b/readthedocs/settings/dev.py
index 7fa4dafe959..8fd9860a0bd 100644
--- a/readthedocs/settings/dev.py
+++ b/readthedocs/settings/dev.py
@@ -50,7 +50,7 @@ def DATABASES(self): # noqa
@property
def LOGGING(self): # noqa - avoid pep8 N802
- logging = super(CommunityDevSettings, self).LOGGING
+ logging = super().LOGGING
logging['formatters']['default']['format'] = '[%(asctime)s] ' + self.LOG_FORMAT
# Allow Sphinx and other tools to create loggers
logging['disable_existing_loggers'] = False
diff --git a/readthedocs/vcs_support/backends/bzr.py b/readthedocs/vcs_support/backends/bzr.py
index 5ea817c1156..9a309a8077a 100644
--- a/readthedocs/vcs_support/backends/bzr.py
+++ b/readthedocs/vcs_support/backends/bzr.py
@@ -26,7 +26,7 @@ class Backend(BaseVCS):
fallback_branch = ''
def update(self):
- super(Backend, self).update()
+ super().update()
retcode = self.run('bzr', 'status', record=False)[0]
if retcode == 0:
return self.up()
@@ -90,7 +90,7 @@ def commit(self):
return stdout.strip()
def checkout(self, identifier=None):
- super(Backend, self).checkout()
+ super().checkout()
if not identifier:
return self.up()
return self.run('bzr', 'switch', identifier)
diff --git a/readthedocs/vcs_support/backends/git.py b/readthedocs/vcs_support/backends/git.py
index f9608799570..80fafb89e9d 100644
--- a/readthedocs/vcs_support/backends/git.py
+++ b/readthedocs/vcs_support/backends/git.py
@@ -36,7 +36,7 @@ class Backend(BaseVCS):
repo_depth = 50
def __init__(self, *args, **kwargs):
- super(Backend, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.token = kwargs.get('token', None)
self.repo_url = self._get_clone_url()
@@ -46,7 +46,7 @@ def _get_clone_url(self):
hacked_url = re.sub('.git$', '', hacked_url)
clone_url = 'https://%s' % hacked_url
if self.token:
- clone_url = 'https://%s@%s' % (self.token, hacked_url)
+ clone_url = 'https://{}@{}'.format(self.token, hacked_url)
return clone_url
# Don't edit URL because all hosts aren't the same
# else:
@@ -58,7 +58,7 @@ def set_remote_url(self, url):
def update(self):
"""Clone or update the repository."""
- super(Backend, self).update()
+ super().update()
if self.repo_exists():
self.set_remote_url(self.repo_url)
return self.fetch()
@@ -228,7 +228,7 @@ def commit(self):
def checkout(self, identifier=None):
"""Checkout to identifier or latest."""
- super(Backend, self).checkout()
+ super().checkout()
# Find proper identifier
if not identifier:
identifier = self.default_branch or self.fallback_branch
@@ -291,7 +291,7 @@ def ref_exists(self, ref):
@property
def env(self):
- env = super(Backend, self).env
+ env = super().env
env['GIT_DIR'] = os.path.join(self.working_dir, '.git')
# Don't prompt for username, this requires Git 2.3+
env['GIT_TERMINAL_PROMPT'] = '0'
diff --git a/readthedocs/vcs_support/backends/hg.py b/readthedocs/vcs_support/backends/hg.py
index 4a66624f1ae..e65d4c650f0 100644
--- a/readthedocs/vcs_support/backends/hg.py
+++ b/readthedocs/vcs_support/backends/hg.py
@@ -20,7 +20,7 @@ class Backend(BaseVCS):
fallback_branch = 'default'
def update(self):
- super(Backend, self).update()
+ super().update()
retcode = self.run('hg', 'status', record=False)[0]
if retcode == 0:
return self.pull()
@@ -107,7 +107,7 @@ def commit(self):
return stdout.strip()
def checkout(self, identifier=None):
- super(Backend, self).checkout()
+ super().checkout()
if not identifier:
identifier = 'tip'
return self.run('hg', 'update', '--clean', identifier)
diff --git a/readthedocs/vcs_support/backends/svn.py b/readthedocs/vcs_support/backends/svn.py
index 61e29324281..2363116c6fd 100644
--- a/readthedocs/vcs_support/backends/svn.py
+++ b/readthedocs/vcs_support/backends/svn.py
@@ -25,7 +25,7 @@ class Backend(BaseVCS):
fallback_branch = '/trunk/'
def __init__(self, *args, **kwargs):
- super(Backend, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
if self.repo_url[-1] != '/':
self.base_url = self.repo_url
self.repo_url += '/'
@@ -36,7 +36,7 @@ def __init__(self, *args, **kwargs):
self.base_url = self.repo_url
def update(self):
- super(Backend, self).update()
+ super().update()
# For some reason `svn status` gives me retcode 0 in non-svn
# directories that's why I use `svn info` here.
retcode, _, _ = self.run('svn', 'info', record=False)
@@ -102,7 +102,7 @@ def commit(self):
return stdout.strip()
def checkout(self, identifier=None):
- super(Backend, self).checkout()
+ super().checkout()
return self.co(identifier)
def get_url(self, base_url, identifier):
diff --git a/readthedocs/vcs_support/base.py b/readthedocs/vcs_support/base.py
index 0e632e3b858..f430fd0d0a5 100644
--- a/readthedocs/vcs_support/base.py
+++ b/readthedocs/vcs_support/base.py
@@ -12,7 +12,7 @@
log = logging.getLogger(__name__)
-class VCSVersion(object):
+class VCSVersion:
"""
Represents a Version (tag or branch) in a VCS.
@@ -29,11 +29,11 @@ def __init__(self, repository, identifier, verbose_name):
self.verbose_name = verbose_name
def __repr__(self):
- return '
Date: Thu, 3 Jan 2019 17:04:44 +0100
Subject: [PATCH 04/42] Use unicode string for Sphinx config file
This file is generated by our code and can be used by Python2 when
building the documentation, so we need to keep the `u` before the
definition of the string.
---
readthedocs/rtd_tests/files/conf.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py
index 11f872849dd..b448eab9ab7 100644
--- a/readthedocs/rtd_tests/files/conf.py
+++ b/readthedocs/rtd_tests/files/conf.py
@@ -13,7 +13,7 @@
'.md': CommonMarkParser,
}
master_doc = 'index'
-project = 'Pip'
+project = u'Pip'
copyright = str(datetime.now().year)
version = '0.8.1'
release = '0.8.1'
From 1b589392703721642ecb4ebcb0b23663a2c1ef4e Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Thu, 3 Jan 2019 17:26:23 +0100
Subject: [PATCH 05/42] Remove unused imports to avoid lint issues
---
readthedocs/config/config.py | 2 --
readthedocs/config/validation.py | 2 --
2 files changed, 4 deletions(-)
diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py
index ac6b3db99a3..900fe637e2e 100644
--- a/readthedocs/config/config.py
+++ b/readthedocs/config/config.py
@@ -9,8 +9,6 @@
import re
from contextlib import contextmanager
-import six
-
from readthedocs.projects.constants import DOCUMENTATION_CHOICES
from .find import find_one
diff --git a/readthedocs/config/validation.py b/readthedocs/config/validation.py
index b1b42fc6775..baf7808d61e 100644
--- a/readthedocs/config/validation.py
+++ b/readthedocs/config/validation.py
@@ -3,8 +3,6 @@
import os
-from six import string_types, text_type
-
INVALID_BOOL = 'invalid-bool'
INVALID_CHOICE = 'invalid-choice'
INVALID_LIST = 'invalid-list'
From 271884a6649b3d0f8e1ee31b6ef22684f8dc1a0f Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Thu, 3 Jan 2019 17:30:05 +0100
Subject: [PATCH 06/42] Run tests with python3.6 on Travis
---
.travis.yml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index bce5b2f3742..e5083ec7d50 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,8 @@ env:
- ES_VERSION=1.3.9 ES_DOWNLOAD_URL=https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-${ES_VERSION}.tar.gz
matrix:
include:
+ - python: 3.6
+ env: TOXENV=py36
- python: 3.6
env: TOXENV=docs
- python: 3.6
@@ -44,6 +46,6 @@ notifications:
branches:
only:
- - master
+ - master
- rel # Community release branch
- relcorp # Corporate release branch
From 849abb3cc28dc369fe5db4a5eb6365e15cb97cdd Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Thu, 3 Jan 2019 17:30:20 +0100
Subject: [PATCH 07/42] Do not include search tests when running migrations env
---
scripts/travis/run_tests.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/travis/run_tests.sh b/scripts/travis/run_tests.sh
index 99c56387033..c5da9d551f7 100755
--- a/scripts/travis/run_tests.sh
+++ b/scripts/travis/run_tests.sh
@@ -1,4 +1,4 @@
-if ! [[ "$TOXENV" =~ ^(docs|lint|eslint) ]];
+if ! [[ "$TOXENV" =~ ^(docs|lint|eslint|migrations) ]];
then
args="'--including-search'"
fi
From c78187b62fb00113405ec4f433caf7122dbad49c Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 20:53:00 +0100
Subject: [PATCH 08/42] isort all the files
$ pre-commit run python-import-sorter --files `find readthedocs -name '*.py'`
It was ran with this configuration: https://github.com/rtfd/common/pull/28
---
readthedocs/analytics/apps.py | 1 -
readthedocs/analytics/tasks.py | 2 --
readthedocs/analytics/tests.py | 4 ---
readthedocs/analytics/utils.py | 6 ++---
readthedocs/analytics/vendor/ipaddress.py | 4 +--
readthedocs/api/base.py | 5 +---
readthedocs/api/client.py | 6 ++---
readthedocs/api/utils.py | 4 +--
readthedocs/builds/admin.py | 4 +--
readthedocs/builds/constants.py | 4 +--
readthedocs/builds/forms.py | 8 ------
readthedocs/builds/managers.py | 19 ++++++++-----
readthedocs/builds/models.py | 11 ++------
readthedocs/builds/querysets.py | 2 --
readthedocs/builds/signals.py | 1 -
readthedocs/builds/syncers.py | 5 +---
readthedocs/builds/urls.py | 4 +--
readthedocs/builds/utils.py | 8 +++---
readthedocs/builds/version_slug.py | 3 ---
readthedocs/builds/views.py | 8 ------
readthedocs/config/config.py | 3 +--
readthedocs/config/find.py | 2 --
readthedocs/config/models.py | 2 --
readthedocs/config/parser.py | 3 +--
readthedocs/config/tests/test_config.py | 2 --
readthedocs/config/tests/test_find.py | 6 ++---
readthedocs/config/tests/test_parser.py | 3 ---
readthedocs/config/tests/test_utils.py | 3 ---
readthedocs/config/tests/test_validation.py | 24 +++++++++++------
readthedocs/config/tests/utils.py | 2 --
readthedocs/config/validation.py | 3 +--
readthedocs/constants.py | 1 -
readthedocs/core/adapters.py | 2 +-
readthedocs/core/admin.py | 5 ++--
readthedocs/core/apps.py | 1 -
readthedocs/core/backends.py | 3 +--
readthedocs/core/context_processors.py | 1 -
readthedocs/core/fields.py | 1 -
readthedocs/core/forms.py | 5 +---
.../core/management/commands/archive.py | 7 +++--
.../core/management/commands/clean_builds.py | 4 +--
.../core/management/commands/import_github.py | 3 +--
.../commands/import_github_language.py | 9 +++----
.../commands/provision_elasticsearch.py | 9 +++++--
readthedocs/core/management/commands/pull.py | 1 -
.../commands/reindex_elasticsearch.py | 5 ++--
.../core/management/commands/set_metadata.py | 4 +--
.../core/management/commands/symlink.py | 3 +--
.../core/management/commands/update_api.py | 1 -
.../core/management/commands/update_repos.py | 4 +--
.../management/commands/update_versions.py | 1 -
readthedocs/core/middleware.py | 6 ++---
readthedocs/core/mixins.py | 4 +--
readthedocs/core/models.py | 8 +++---
readthedocs/core/permissions.py | 2 --
readthedocs/core/resolver.py | 4 +--
readthedocs/core/settings.py | 2 --
readthedocs/core/signals.py | 10 +++----
readthedocs/core/static.py | 2 --
readthedocs/core/symlink.py | 9 +------
readthedocs/core/tasks.py | 3 +--
readthedocs/core/templatetags/core_tags.py | 3 ---
readthedocs/core/templatetags/privacy_tags.py | 2 --
readthedocs/core/tests/test_signals.py | 4 +--
readthedocs/core/urls/single_version.py | 5 ++--
readthedocs/core/urls/subdomain.py | 15 +++++------
readthedocs/core/utils/extend.py | 2 --
readthedocs/core/utils/tasks/public.py | 8 +-----
readthedocs/core/utils/tasks/retrieve.py | 8 +-----
readthedocs/core/views/hooks.py | 8 +-----
readthedocs/core/views/serve.py | 9 +++----
readthedocs/doc_builder/backends/mkdocs.py | 4 +--
readthedocs/doc_builder/backends/sphinx.py | 9 +++----
readthedocs/doc_builder/base.py | 5 +---
readthedocs/doc_builder/config.py | 3 ---
readthedocs/doc_builder/constants.py | 4 +--
readthedocs/doc_builder/environments.py | 9 -------
readthedocs/doc_builder/exceptions.py | 2 --
readthedocs/doc_builder/loader.py | 2 +-
.../doc_builder/python_environments.py | 10 +------
readthedocs/doc_builder/signals.py | 3 +--
readthedocs/gold/admin.py | 2 +-
readthedocs/gold/apps.py | 1 -
readthedocs/gold/forms.py | 4 ---
readthedocs/gold/models.py | 8 +-----
readthedocs/gold/signals.py | 1 -
readthedocs/gold/tests/test_forms.py | 8 +++---
readthedocs/gold/tests/test_signals.py | 6 ++---
readthedocs/gold/urls.py | 1 -
readthedocs/gold/views.py | 9 +------
readthedocs/integrations/admin.py | 5 ++--
readthedocs/integrations/models.py | 8 ------
readthedocs/notifications/apps.py | 1 -
readthedocs/notifications/backends.py | 4 ---
readthedocs/notifications/constants.py | 1 -
readthedocs/notifications/forms.py | 1 -
readthedocs/notifications/notification.py | 9 +++----
readthedocs/notifications/storages.py | 9 ++++---
readthedocs/notifications/urls.py | 4 +--
readthedocs/notifications/views.py | 3 +--
readthedocs/oauth/admin.py | 3 +--
readthedocs/oauth/models.py | 6 +----
readthedocs/oauth/notifications.py | 2 --
readthedocs/oauth/querysets.py | 2 --
readthedocs/oauth/services/base.py | 5 +---
readthedocs/oauth/services/bitbucket.py | 9 +++----
readthedocs/oauth/services/github.py | 9 +++----
readthedocs/oauth/services/gitlab.py | 3 +--
readthedocs/oauth/tasks.py | 8 +-----
readthedocs/oauth/utils.py | 10 ++++---
readthedocs/payments/forms.py | 8 +++---
readthedocs/payments/mixins.py | 2 --
readthedocs/payments/utils.py | 2 +-
readthedocs/profiles/urls/private.py | 7 -----
readthedocs/profiles/urls/public.py | 1 -
readthedocs/profiles/views.py | 9 +------
readthedocs/projects/admin.py | 7 -----
readthedocs/projects/backends/views.py | 1 -
readthedocs/projects/constants.py | 4 +--
readthedocs/projects/exceptions.py | 2 --
readthedocs/projects/feeds.py | 1 -
readthedocs/projects/forms.py | 8 ------
.../commands/import_project_from_live.py | 8 +++---
readthedocs/projects/models.py | 19 +++++++------
readthedocs/projects/notifications.py | 1 -
readthedocs/projects/querysets.py | 2 --
readthedocs/projects/signals.py | 1 -
readthedocs/projects/tasks.py | 13 +++------
.../projects/templatetags/projects_tags.py | 1 -
readthedocs/projects/urls/private.py | 24 ++++++++++++-----
readthedocs/projects/urls/public.py | 6 ++---
readthedocs/projects/utils.py | 9 +------
readthedocs/projects/validators.py | 4 +--
readthedocs/projects/version_handling.py | 9 +++----
readthedocs/projects/views/base.py | 7 ++---
readthedocs/projects/views/mixins.py | 2 --
readthedocs/projects/views/private.py | 8 +-----
readthedocs/projects/views/public.py | 6 ++---
readthedocs/redirects/admin.py | 3 +--
readthedocs/redirects/managers.py | 1 -
readthedocs/redirects/models.py | 8 +++---
readthedocs/redirects/utils.py | 4 +--
readthedocs/restapi/client.py | 8 +-----
readthedocs/restapi/permissions.py | 2 --
readthedocs/restapi/serializers.py | 6 +----
readthedocs/restapi/signals.py | 3 +--
readthedocs/restapi/urls.py | 8 +-----
readthedocs/restapi/utils.py | 8 +-----
readthedocs/restapi/views/core_views.py | 7 ++---
readthedocs/restapi/views/footer_views.py | 8 +++---
readthedocs/restapi/views/integrations.py | 9 +------
readthedocs/restapi/views/model_views.py | 27 ++++++++++++-------
readthedocs/restapi/views/search_views.py | 3 +--
readthedocs/restapi/views/task_views.py | 8 +-----
readthedocs/rtd_tests/base.py | 9 +++----
readthedocs/rtd_tests/files/conf.py | 2 --
readthedocs/rtd_tests/mocks/environment.py | 3 ---
readthedocs/rtd_tests/mocks/mock_api.py | 5 ++--
readthedocs/rtd_tests/mocks/paths.py | 3 +--
.../tests/projects/test_admin_actions.py | 5 ++--
readthedocs/rtd_tests/tests/test_api.py | 10 +------
.../rtd_tests/tests/test_api_permissions.py | 5 ++--
.../tests/test_api_version_compare.py | 2 --
readthedocs/rtd_tests/tests/test_backend.py | 9 -------
.../rtd_tests/tests/test_backend_svn.py | 13 +++------
.../rtd_tests/tests/test_build_config.py | 6 ++---
.../rtd_tests/tests/test_build_forms.py | 3 ---
.../tests/test_build_notifications.py | 8 ++----
readthedocs/rtd_tests/tests/test_builds.py | 11 ++------
readthedocs/rtd_tests/tests/test_celery.py | 19 +++++++------
.../tests/test_config_integration.py | 4 ---
readthedocs/rtd_tests/tests/test_core_tags.py | 5 +---
.../rtd_tests/tests/test_core_utils.py | 9 +++----
.../rtd_tests/tests/test_doc_builder.py | 4 ---
.../rtd_tests/tests/test_doc_building.py | 9 -------
.../rtd_tests/tests/test_doc_serving.py | 13 ++++-----
readthedocs/rtd_tests/tests/test_domains.py | 5 +---
readthedocs/rtd_tests/tests/test_extend.py | 9 +++----
readthedocs/rtd_tests/tests/test_footer.py | 8 ------
readthedocs/rtd_tests/tests/test_gold.py | 10 +++----
.../rtd_tests/tests/test_imported_file.py | 4 +--
.../rtd_tests/tests/test_integrations.py | 13 ++++-----
.../rtd_tests/tests/test_middleware.py | 14 +++-------
.../rtd_tests/tests/test_notifications.py | 14 +++++-----
readthedocs/rtd_tests/tests/test_oauth.py | 9 +++----
.../rtd_tests/tests/test_post_commit_hooks.py | 5 +---
readthedocs/rtd_tests/tests/test_privacy.py | 13 +++++----
.../rtd_tests/tests/test_privacy_urls.py | 10 +++----
.../rtd_tests/tests/test_profile_views.py | 5 +---
readthedocs/rtd_tests/tests/test_project.py | 12 ++++-----
.../rtd_tests/tests/test_project_forms.py | 8 ------
.../rtd_tests/tests/test_project_querysets.py | 11 ++++----
.../rtd_tests/tests/test_project_symlinks.py | 15 ++++++-----
.../rtd_tests/tests/test_project_views.py | 26 +++++++++---------
readthedocs/rtd_tests/tests/test_redirects.py | 10 +++----
.../rtd_tests/tests/test_repo_parsing.py | 4 ---
readthedocs/rtd_tests/tests/test_resolver.py | 9 +++----
.../rtd_tests/tests/test_restapi_client.py | 4 ---
.../tests/test_search_json_parsing.py | 1 -
.../rtd_tests/tests/test_single_version.py | 5 +---
.../rtd_tests/tests/test_subprojects.py | 5 +---
.../rtd_tests/tests/test_sync_versions.py | 10 +------
readthedocs/rtd_tests/tests/test_urls.py | 5 +---
.../tests/test_version_commit_name.py | 13 +++------
.../rtd_tests/tests/test_version_config.py | 3 ---
.../rtd_tests/tests/test_version_slug.py | 6 ++---
readthedocs/rtd_tests/tests/test_views.py | 9 +------
readthedocs/rtd_tests/utils.py | 7 -----
readthedocs/search/indexes.py | 7 +----
readthedocs/search/lib.py | 14 +++++-----
readthedocs/search/parse_json.py | 6 ++---
readthedocs/search/signals.py | 2 +-
readthedocs/search/tests/conftest.py | 11 +++++---
readthedocs/search/tests/test_views.py | 1 -
readthedocs/search/tests/utils.py | 1 -
readthedocs/search/utils.py | 11 +++-----
readthedocs/search/views.py | 4 +--
readthedocs/urls.py | 20 +++++++-------
readthedocs/vcs_support/backends/bzr.py | 10 -------
readthedocs/vcs_support/backends/git.py | 9 +------
readthedocs/vcs_support/backends/hg.py | 7 -----
readthedocs/vcs_support/backends/svn.py | 10 -------
readthedocs/vcs_support/base.py | 4 ---
readthedocs/vcs_support/tests.py | 4 ---
readthedocs/vcs_support/utils.py | 3 ---
readthedocs/worker.py | 2 --
226 files changed, 415 insertions(+), 974 deletions(-)
diff --git a/readthedocs/analytics/apps.py b/readthedocs/analytics/apps.py
index afdea7f5dec..219a787dce7 100644
--- a/readthedocs/analytics/apps.py
+++ b/readthedocs/analytics/apps.py
@@ -1,6 +1,5 @@
"""Django app config for the analytics app."""
-from __future__ import absolute_import
from django.apps import AppConfig
diff --git a/readthedocs/analytics/tasks.py b/readthedocs/analytics/tasks.py
index 6c1ec2cfce1..b79102fc6fb 100644
--- a/readthedocs/analytics/tasks.py
+++ b/readthedocs/analytics/tasks.py
@@ -1,7 +1,5 @@
"""Tasks for Read the Docs' analytics"""
-from __future__ import absolute_import
-
from django.conf import settings
import readthedocs
diff --git a/readthedocs/analytics/tests.py b/readthedocs/analytics/tests.py
index 37b26957033..93435551684 100644
--- a/readthedocs/analytics/tests.py
+++ b/readthedocs/analytics/tests.py
@@ -1,10 +1,7 @@
-from __future__ import absolute_import, unicode_literals
-
from django.test import TestCase
from .utils import anonymize_ip_address, anonymize_user_agent
-
class UtilsTests(TestCase):
def test_anonymize_ip(self):
self.assertEqual(anonymize_ip_address('127.0.0.1'), '127.0.0.0')
@@ -29,4 +26,3 @@ def test_anonymize_ua(self):
anonymize_user_agent('Some rare user agent'),
'Rare user agent',
)
-
diff --git a/readthedocs/analytics/utils.py b/readthedocs/analytics/utils.py
index 44eef551125..52c44d5cd35 100644
--- a/readthedocs/analytics/utils.py
+++ b/readthedocs/analytics/utils.py
@@ -1,15 +1,15 @@
"""Utilities related to analytics"""
-from __future__ import absolute_import, unicode_literals
import hashlib
import logging
+import requests
from django.conf import settings
-from django.utils.encoding import force_text, force_bytes
from django.utils.crypto import get_random_string
-import requests
+from django.utils.encoding import force_bytes, force_text
from user_agents import parse
+
try:
# Python 3.3+ only
import ipaddress
diff --git a/readthedocs/analytics/vendor/ipaddress.py b/readthedocs/analytics/vendor/ipaddress.py
index b81d477bf96..8014d2ef777 100644
--- a/readthedocs/analytics/vendor/ipaddress.py
+++ b/readthedocs/analytics/vendor/ipaddress.py
@@ -9,12 +9,10 @@
"""
-from __future__ import unicode_literals
-
-
import itertools
import struct
+
__version__ = '1.0.22'
# Compatibility functions
diff --git a/readthedocs/api/base.py b/readthedocs/api/base.py
index 8e90c279b62..75e984d422e 100644
--- a/readthedocs/api/base.py
+++ b/readthedocs/api/base.py
@@ -1,10 +1,6 @@
# -*- coding: utf-8 -*-
"""API resources."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
-from builtins import object
import redis
from django.conf.urls import url
@@ -25,6 +21,7 @@
from .utils import PostAuthentication
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/api/client.py b/readthedocs/api/client.py
index dd742198b15..a3020163449 100644
--- a/readthedocs/api/client.py
+++ b/readthedocs/api/client.py
@@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
"""Slumber API client."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
-from django.conf import settings
import requests
+from django.conf import settings
from requests_toolbelt.adapters import host_header_ssl
from slumber import API
+
log = logging.getLogger(__name__)
PRODUCTION_DOMAIN = getattr(settings, 'PRODUCTION_DOMAIN', 'readthedocs.org')
diff --git a/readthedocs/api/utils.py b/readthedocs/api/utils.py
index c15cb02ec02..e2b5e9c331a 100644
--- a/readthedocs/api/utils.py
+++ b/readthedocs/api/utils.py
@@ -1,13 +1,11 @@
"""Utility classes for api module"""
-from __future__ import absolute_import
import logging
from django.utils.translation import ugettext
-
from tastypie.authentication import BasicAuthentication
from tastypie.authorization import Authorization
-from tastypie.resources import ModelResource
from tastypie.exceptions import NotFound
+from tastypie.resources import ModelResource
log = logging.getLogger(__name__)
diff --git a/readthedocs/builds/admin.py b/readthedocs/builds/admin.py
index 66c046f9c3e..59fe235aef3 100644
--- a/readthedocs/builds/admin.py
+++ b/readthedocs/builds/admin.py
@@ -1,10 +1,10 @@
"""Django admin interface for `~builds.models.Build` and related models."""
-from __future__ import absolute_import
from django.contrib import admin
-from readthedocs.builds.models import Build, Version, BuildCommandResult
from guardian.admin import GuardedModelAdmin
+from readthedocs.builds.models import Build, BuildCommandResult, Version
+
class BuildCommandResultInline(admin.TabularInline):
model = BuildCommandResult
diff --git a/readthedocs/builds/constants.py b/readthedocs/builds/constants.py
index 99d816814ba..660fb0b0f4b 100644
--- a/readthedocs/builds/constants.py
+++ b/readthedocs/builds/constants.py
@@ -1,8 +1,8 @@
"""Constants for the builds app."""
-from __future__ import absolute_import
-from django.utils.translation import ugettext_lazy as _
from django.conf import settings
+from django.utils.translation import ugettext_lazy as _
+
BUILD_STATE_TRIGGERED = 'triggered'
BUILD_STATE_CLONING = 'cloning'
diff --git a/readthedocs/builds/forms.py b/readthedocs/builds/forms.py
index 6e5e94b50ee..ae655bfc90e 100644
--- a/readthedocs/builds/forms.py
+++ b/readthedocs/builds/forms.py
@@ -1,13 +1,5 @@
"""Django forms for the builds app."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
-from builtins import object
from django import forms
from django.utils.translation import ugettext_lazy as _
diff --git a/readthedocs/builds/managers.py b/readthedocs/builds/managers.py
index bd9316c3ef3..bd886605886 100644
--- a/readthedocs/builds/managers.py
+++ b/readthedocs/builds/managers.py
@@ -1,14 +1,21 @@
"""Build and Version class model Managers"""
-from __future__ import absolute_import
-
from django.db import models
-from .constants import (BRANCH, TAG, LATEST, LATEST_VERBOSE_NAME, STABLE,
- STABLE_VERBOSE_NAME)
+from readthedocs.core.utils.extend import (
+ SettingsOverrideObject,
+ get_override_class,
+)
+
+from .constants import (
+ BRANCH,
+ LATEST,
+ LATEST_VERBOSE_NAME,
+ STABLE,
+ STABLE_VERBOSE_NAME,
+ TAG,
+)
from .querysets import VersionQuerySet
-from readthedocs.core.utils.extend import (SettingsOverrideObject,
- get_override_class)
__all__ = ['VersionManager']
diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py
index b27b7e145af..c11fa13d0a0 100644
--- a/readthedocs/builds/models.py
+++ b/readthedocs/builds/models.py
@@ -1,21 +1,14 @@
# -*- coding: utf-8 -*-
"""Models for the builds app."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import logging
import os.path
import re
from shutil import rmtree
-from builtins import object
from django.conf import settings
from django.db import models
+from django.urls import reverse
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext
@@ -23,7 +16,6 @@
from guardian.shortcuts import assign
from jsonfield import JSONField
from taggit.managers import TaggableManager
-from django.urls import reverse
from readthedocs.core.utils import broadcast
from readthedocs.projects.constants import (
@@ -55,6 +47,7 @@
)
from .version_slug import VersionSlugField
+
DEFAULT_VERSION_PRIVACY_LEVEL = getattr(
settings, 'DEFAULT_VERSION_PRIVACY_LEVEL', 'public')
diff --git a/readthedocs/builds/querysets.py b/readthedocs/builds/querysets.py
index 34408b9d982..a2a34938836 100644
--- a/readthedocs/builds/querysets.py
+++ b/readthedocs/builds/querysets.py
@@ -1,7 +1,5 @@
"""Build and Version QuerySet classes"""
-from __future__ import absolute_import
-
from django.db import models
from guardian.shortcuts import get_objects_for_user
diff --git a/readthedocs/builds/signals.py b/readthedocs/builds/signals.py
index c80f7d3e42d..c9bb49cc7cc 100644
--- a/readthedocs/builds/signals.py
+++ b/readthedocs/builds/signals.py
@@ -1,6 +1,5 @@
"""Build signals"""
-from __future__ import absolute_import
import django.dispatch
diff --git a/readthedocs/builds/syncers.py b/readthedocs/builds/syncers.py
index cf5a9a90e07..5f02f966bf7 100644
--- a/readthedocs/builds/syncers.py
+++ b/readthedocs/builds/syncers.py
@@ -5,18 +5,15 @@
local machine.
"""
-from __future__ import absolute_import
-
import getpass
import logging
import os
import shutil
-from builtins import object
from django.conf import settings
-from readthedocs.core.utils.extend import SettingsOverrideObject
from readthedocs.core.utils import safe_makedirs
+from readthedocs.core.utils.extend import SettingsOverrideObject
log = logging.getLogger(__name__)
diff --git a/readthedocs/builds/urls.py b/readthedocs/builds/urls.py
index a148362a51b..07f787d0b4b 100644
--- a/readthedocs/builds/urls.py
+++ b/readthedocs/builds/urls.py
@@ -1,12 +1,10 @@
# -*- coding: utf-8 -*-
"""URL configuration for builds app."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
from django.conf.urls import url
from .views import builds_redirect_detail, builds_redirect_list
+
urlpatterns = [
url(
r'^(?P[-\w]+)/(?P\d+)/$',
diff --git a/readthedocs/builds/utils.py b/readthedocs/builds/utils.py
index 7fcc245dbb8..d2d49270cb2 100644
--- a/readthedocs/builds/utils.py
+++ b/readthedocs/builds/utils.py
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
"""Utilities for the builds app."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
from readthedocs.projects.constants import (
- BITBUCKET_REGEXS, GITHUB_REGEXS, GITLAB_REGEXS)
+ BITBUCKET_REGEXS,
+ GITHUB_REGEXS,
+ GITLAB_REGEXS,
+)
def get_github_username_repo(url):
diff --git a/readthedocs/builds/version_slug.py b/readthedocs/builds/version_slug.py
index 7eb71f82798..18ad3d19f99 100644
--- a/readthedocs/builds/version_slug.py
+++ b/readthedocs/builds/version_slug.py
@@ -17,8 +17,6 @@
another number would be confusing.
"""
-from __future__ import absolute_import
-
import math
import re
import string
@@ -26,7 +24,6 @@
from django.db import models
from django.utils.encoding import force_text
-from builtins import range
def get_fields_with_model(cls):
diff --git a/readthedocs/builds/views.py b/readthedocs/builds/views.py
index 680bd271c29..37a46ae3ece 100644
--- a/readthedocs/builds/views.py
+++ b/readthedocs/builds/views.py
@@ -2,16 +2,8 @@
"""Views for builds app."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import logging
-from builtins import object
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import (
diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py
index 900fe637e2e..57e01439e0a 100644
--- a/readthedocs/config/config.py
+++ b/readthedocs/config/config.py
@@ -3,8 +3,6 @@
# pylint: disable=too-many-lines
"""Build configuration for rtd."""
-from __future__ import division, print_function, unicode_literals
-
import os
import re
from contextlib import contextmanager
@@ -26,6 +24,7 @@
validate_string,
)
+
__all__ = (
'ALL',
'load',
diff --git a/readthedocs/config/find.py b/readthedocs/config/find.py
index cb3e5e0c56d..ed49c765c1d 100644
--- a/readthedocs/config/find.py
+++ b/readthedocs/config/find.py
@@ -1,7 +1,5 @@
"""Helper functions to search files."""
-from __future__ import division, print_function, unicode_literals
-
import os
import re
diff --git a/readthedocs/config/models.py b/readthedocs/config/models.py
index bf12ddfa6d4..ce0604f18b7 100644
--- a/readthedocs/config/models.py
+++ b/readthedocs/config/models.py
@@ -1,7 +1,5 @@
"""Models for the response of the configuration object."""
-from __future__ import division, print_function, unicode_literals
-
from collections import namedtuple
diff --git a/readthedocs/config/parser.py b/readthedocs/config/parser.py
index 655b1601bf7..85e31782b00 100644
--- a/readthedocs/config/parser.py
+++ b/readthedocs/config/parser.py
@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
"""YAML parser for the RTD configuration file."""
-from __future__ import division, print_function, unicode_literals
-
import yaml
+
__all__ = ('parse', 'ParseError')
diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py
index 29b776d0fb5..e3e68110e99 100644
--- a/readthedocs/config/tests/test_config.py
+++ b/readthedocs/config/tests/test_config.py
@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import division, print_function, unicode_literals
-
import os
import re
import textwrap
diff --git a/readthedocs/config/tests/test_find.py b/readthedocs/config/tests/test_find.py
index 7ba651059e3..dd4715f20d9 100644
--- a/readthedocs/config/tests/test_find.py
+++ b/readthedocs/config/tests/test_find.py
@@ -1,13 +1,11 @@
-from __future__ import division, print_function, unicode_literals
-
import os
+
import pytest
-import six
+
from readthedocs.config.find import find_one
from .utils import apply_fs
-
def test_find_no_files(tmpdir):
with tmpdir.as_cwd():
path = find_one(os.getcwd(), r'readthedocs.yml')
diff --git a/readthedocs/config/tests/test_parser.py b/readthedocs/config/tests/test_parser.py
index 4cbe457bbe8..37476af9162 100644
--- a/readthedocs/config/tests/test_parser.py
+++ b/readthedocs/config/tests/test_parser.py
@@ -1,12 +1,9 @@
-from __future__ import division, print_function, unicode_literals
-
from io import StringIO
from pytest import raises
from readthedocs.config.parser import ParseError, parse
-
def test_parse_empty_config_file():
buf = StringIO('')
with raises(ParseError):
diff --git a/readthedocs/config/tests/test_utils.py b/readthedocs/config/tests/test_utils.py
index 3d89cb0d0c1..2332b30716b 100644
--- a/readthedocs/config/tests/test_utils.py
+++ b/readthedocs/config/tests/test_utils.py
@@ -1,8 +1,5 @@
-from __future__ import division, print_function, unicode_literals
-
from .utils import apply_fs
-
def test_apply_fs_with_empty_contents(tmpdir):
# Doesn't do anything if second parameter is empty.
apply_fs(tmpdir, {})
diff --git a/readthedocs/config/tests/test_validation.py b/readthedocs/config/tests/test_validation.py
index 6437acfbff2..fd7d9e781fb 100644
--- a/readthedocs/config/tests/test_validation.py
+++ b/readthedocs/config/tests/test_validation.py
@@ -1,18 +1,26 @@
# -*- coding: utf-8 -*-
-from __future__ import division, print_function, unicode_literals
-
import os
from mock import patch
from pytest import raises
-from six import text_type
from readthedocs.config.validation import (
- INVALID_BOOL, INVALID_CHOICE, INVALID_DIRECTORY, INVALID_FILE, INVALID_LIST,
- INVALID_PATH, INVALID_STRING, ValidationError, validate_bool,
- validate_choice, validate_directory, validate_file, validate_list,
- validate_path, validate_string)
-
+ INVALID_BOOL,
+ INVALID_CHOICE,
+ INVALID_DIRECTORY,
+ INVALID_FILE,
+ INVALID_LIST,
+ INVALID_PATH,
+ INVALID_STRING,
+ ValidationError,
+ validate_bool,
+ validate_choice,
+ validate_directory,
+ validate_file,
+ validate_list,
+ validate_path,
+ validate_string,
+)
class TestValidateBool:
def test_it_accepts_true(self):
diff --git a/readthedocs/config/tests/utils.py b/readthedocs/config/tests/utils.py
index b1b312420bb..1e390fa8a75 100644
--- a/readthedocs/config/tests/utils.py
+++ b/readthedocs/config/tests/utils.py
@@ -1,5 +1,3 @@
-from __future__ import division, print_function, unicode_literals
-
def apply_fs(tmpdir, contents):
"""
diff --git a/readthedocs/config/validation.py b/readthedocs/config/validation.py
index baf7808d61e..a69b8ed7769 100644
--- a/readthedocs/config/validation.py
+++ b/readthedocs/config/validation.py
@@ -1,8 +1,7 @@
"""Validations for the RTD configuration file."""
-from __future__ import division, print_function, unicode_literals
-
import os
+
INVALID_BOOL = 'invalid-bool'
INVALID_CHOICE = 'invalid-choice'
INVALID_LIST = 'invalid-list'
diff --git a/readthedocs/constants.py b/readthedocs/constants.py
index b9796e8f998..35cca627432 100644
--- a/readthedocs/constants.py
+++ b/readthedocs/constants.py
@@ -1,6 +1,5 @@
"""Common constants"""
-from __future__ import absolute_import
from readthedocs.builds.version_slug import VERSION_SLUG_REGEX
from readthedocs.projects.constants import LANGUAGES_REGEX, PROJECT_SLUG_REGEX
diff --git a/readthedocs/core/adapters.py b/readthedocs/core/adapters.py
index 5bfbf6d5c2e..803952a792d 100644
--- a/readthedocs/core/adapters.py
+++ b/readthedocs/core/adapters.py
@@ -1,6 +1,5 @@
"""Allauth overrides"""
-from __future__ import absolute_import
import json
import logging
@@ -9,6 +8,7 @@
from readthedocs.core.utils import send_email
+
try:
from django.utils.encoding import force_text
except ImportError:
diff --git a/readthedocs/core/admin.py b/readthedocs/core/admin.py
index b30f5460484..5e8042f1202 100644
--- a/readthedocs/core/admin.py
+++ b/readthedocs/core/admin.py
@@ -1,13 +1,12 @@
"""Django admin interface for core models."""
-from __future__ import absolute_import
from datetime import timedelta
from django.contrib import admin
-from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
-from django.utils.translation import ugettext_lazy as _
+from django.contrib.auth.models import User
from django.utils import timezone
+from django.utils.translation import ugettext_lazy as _
from readthedocs.core.models import UserProfile
from readthedocs.projects.models import Project
diff --git a/readthedocs/core/apps.py b/readthedocs/core/apps.py
index 4a9875ffb13..d422684ba03 100644
--- a/readthedocs/core/apps.py
+++ b/readthedocs/core/apps.py
@@ -1,6 +1,5 @@
"""App configurations for core app."""
-from __future__ import absolute_import
from django.apps import AppConfig
diff --git a/readthedocs/core/backends.py b/readthedocs/core/backends.py
index 6a8b8ec9007..64258fe91eb 100644
--- a/readthedocs/core/backends.py
+++ b/readthedocs/core/backends.py
@@ -1,10 +1,9 @@
"""Email backends for core app."""
-from __future__ import absolute_import
import smtplib
-from django.core.mail.utils import DNS_NAME
from django.core.mail.backends.smtp import EmailBackend
+from django.core.mail.utils import DNS_NAME
class SSLEmailBackend(EmailBackend):
diff --git a/readthedocs/core/context_processors.py b/readthedocs/core/context_processors.py
index 6ac71df2ac7..dc136c51171 100644
--- a/readthedocs/core/context_processors.py
+++ b/readthedocs/core/context_processors.py
@@ -1,6 +1,5 @@
"""Template context processors for core app."""
-from __future__ import absolute_import
from django.conf import settings
diff --git a/readthedocs/core/fields.py b/readthedocs/core/fields.py
index 87f06090870..822ad5ca151 100644
--- a/readthedocs/core/fields.py
+++ b/readthedocs/core/fields.py
@@ -1,6 +1,5 @@
"""Shared model fields and defaults"""
-from __future__ import absolute_import
import binascii
import os
diff --git a/readthedocs/core/forms.py b/readthedocs/core/forms.py
index fdc93ffe28b..66c09e84b93 100644
--- a/readthedocs/core/forms.py
+++ b/readthedocs/core/forms.py
@@ -1,11 +1,7 @@
# -*- coding: utf-8 -*-
"""Forms for core app."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
-from builtins import object
from django import forms
from django.contrib.auth.models import User
@@ -14,6 +10,7 @@
from .models import UserProfile
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/core/management/commands/archive.py b/readthedocs/core/management/commands/archive.py
index 33a35bc56c3..3402d1823b4 100644
--- a/readthedocs/core/management/commands/archive.py
+++ b/readthedocs/core/management/commands/archive.py
@@ -1,15 +1,14 @@
"""Rebuild documentation for all projects"""
-from __future__ import absolute_import
-from __future__ import print_function
-from glob import glob
-import os
import logging
+import os
+from glob import glob
from django.conf import settings
from django.core.management.base import BaseCommand
from django.template import loader as template_loader
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/core/management/commands/clean_builds.py b/readthedocs/core/management/commands/clean_builds.py
index e47a651cf8a..1a48748a963 100644
--- a/readthedocs/core/management/commands/clean_builds.py
+++ b/readthedocs/core/management/commands/clean_builds.py
@@ -1,8 +1,7 @@
"""Clean up stable build paths per project version"""
-from __future__ import absolute_import
-from datetime import timedelta
import logging
+from datetime import timedelta
from optparse import make_option
from django.core.management.base import BaseCommand
@@ -11,6 +10,7 @@
from readthedocs.builds.models import Build, Version
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/core/management/commands/import_github.py b/readthedocs/core/management/commands/import_github.py
index 3bb34b1b4ad..2e2e49176b7 100644
--- a/readthedocs/core/management/commands/import_github.py
+++ b/readthedocs/core/management/commands/import_github.py
@@ -1,8 +1,7 @@
"""Resync GitHub project for user"""
-from __future__ import absolute_import
-from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
+from django.core.management.base import BaseCommand
from readthedocs.oauth.services import GitHubService
diff --git a/readthedocs/core/management/commands/import_github_language.py b/readthedocs/core/management/commands/import_github_language.py
index f53a8088763..1e42685a57e 100644
--- a/readthedocs/core/management/commands/import_github_language.py
+++ b/readthedocs/core/management/commands/import_github_language.py
@@ -8,16 +8,15 @@
which should contain a proper GitHub Oauth Token for rate limiting.
"""
-from __future__ import absolute_import
-from __future__ import print_function
import os
-import requests
-from django.core.management.base import BaseCommand
+import requests
from django.core.cache import cache
+from django.core.management.base import BaseCommand
-from readthedocs.projects.models import Project
from readthedocs.projects.constants import GITHUB_REGEXS, PROGRAMMING_LANGUAGES
+from readthedocs.projects.models import Project
+
PL_DICT = {}
diff --git a/readthedocs/core/management/commands/provision_elasticsearch.py b/readthedocs/core/management/commands/provision_elasticsearch.py
index 9f29fa37a9e..690a45803d9 100644
--- a/readthedocs/core/management/commands/provision_elasticsearch.py
+++ b/readthedocs/core/management/commands/provision_elasticsearch.py
@@ -1,11 +1,16 @@
"""Provision Elastic Search"""
-from __future__ import absolute_import
import logging
from django.core.management.base import BaseCommand
-from readthedocs.search.indexes import Index, PageIndex, ProjectIndex, SectionIndex
+from readthedocs.search.indexes import (
+ Index,
+ PageIndex,
+ ProjectIndex,
+ SectionIndex,
+)
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/core/management/commands/pull.py b/readthedocs/core/management/commands/pull.py
index 3540a35c077..f0ebe4206bd 100644
--- a/readthedocs/core/management/commands/pull.py
+++ b/readthedocs/core/management/commands/pull.py
@@ -1,6 +1,5 @@
"""Trigger build for project slug"""
-from __future__ import absolute_import
import logging
from django.core.management.base import BaseCommand
diff --git a/readthedocs/core/management/commands/reindex_elasticsearch.py b/readthedocs/core/management/commands/reindex_elasticsearch.py
index 7a5f25a065a..9d2ab83495a 100644
--- a/readthedocs/core/management/commands/reindex_elasticsearch.py
+++ b/readthedocs/core/management/commands/reindex_elasticsearch.py
@@ -1,17 +1,16 @@
"""Reindex Elastic Search indexes"""
-from __future__ import absolute_import
import logging
from optparse import make_option
-from django.core.management.base import BaseCommand
-from django.core.management.base import CommandError
from django.conf import settings
+from django.core.management.base import BaseCommand, CommandError
from readthedocs.builds.constants import LATEST
from readthedocs.builds.models import Version
from readthedocs.projects.tasks import update_search
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/core/management/commands/set_metadata.py b/readthedocs/core/management/commands/set_metadata.py
index dbefcddbdd0..e8d17a9dbf1 100644
--- a/readthedocs/core/management/commands/set_metadata.py
+++ b/readthedocs/core/management/commands/set_metadata.py
@@ -1,13 +1,13 @@
"""Generate metadata for all projects"""
-from __future__ import absolute_import
import logging
from django.core.management.base import BaseCommand
+from readthedocs.core.utils import broadcast
from readthedocs.projects import tasks
from readthedocs.projects.models import Project
-from readthedocs.core.utils import broadcast
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/core/management/commands/symlink.py b/readthedocs/core/management/commands/symlink.py
index e5c039c622b..3fda03a41db 100644
--- a/readthedocs/core/management/commands/symlink.py
+++ b/readthedocs/core/management/commands/symlink.py
@@ -1,14 +1,13 @@
"""Update symlinks for projects"""
-from __future__ import absolute_import
import logging
from django.core.management.base import BaseCommand
from readthedocs.projects import tasks
-
from readthedocs.projects.models import Project
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/core/management/commands/update_api.py b/readthedocs/core/management/commands/update_api.py
index e95b23b1899..3a9b44b1c9e 100644
--- a/readthedocs/core/management/commands/update_api.py
+++ b/readthedocs/core/management/commands/update_api.py
@@ -6,7 +6,6 @@
./manage.py update_api
"""
-from __future__ import absolute_import
import logging
from django.core.management.base import BaseCommand
diff --git a/readthedocs/core/management/commands/update_repos.py b/readthedocs/core/management/commands/update_repos.py
index b5233d2d6df..17d0951909e 100644
--- a/readthedocs/core/management/commands/update_repos.py
+++ b/readthedocs/core/management/commands/update_repos.py
@@ -6,9 +6,6 @@
Invoked via ``./manage.py update_repos``.
"""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
from django.core.management.base import BaseCommand
@@ -18,6 +15,7 @@
from readthedocs.projects import tasks
from readthedocs.projects.models import Project
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/core/management/commands/update_versions.py b/readthedocs/core/management/commands/update_versions.py
index 8961d6706cf..9cc0c297130 100644
--- a/readthedocs/core/management/commands/update_versions.py
+++ b/readthedocs/core/management/commands/update_versions.py
@@ -1,6 +1,5 @@
"""Rebuild documentation for all projects"""
-from __future__ import absolute_import
from django.core.management.base import BaseCommand
from readthedocs.builds.models import Version
diff --git a/readthedocs/core/middleware.py b/readthedocs/core/middleware.py
index 4556d92ff90..967a4e84aa9 100644
--- a/readthedocs/core/middleware.py
+++ b/readthedocs/core/middleware.py
@@ -1,22 +1,20 @@
"""Middleware for core app."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
from django.conf import settings
from django.contrib.sessions.middleware import SessionMiddleware
from django.core.cache import cache
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
-from django.urls.base import get_urlconf, set_urlconf
from django.http import Http404, HttpResponseBadRequest
+from django.urls.base import get_urlconf, set_urlconf
from django.utils.deprecation import MiddlewareMixin
from django.utils.translation import ugettext_lazy as _
from readthedocs.core.utils import cname_to_slug
from readthedocs.projects.models import Domain, Project
+
log = logging.getLogger(__name__)
LOG_TEMPLATE = "(Middleware) {msg} [{host}{path}]"
diff --git a/readthedocs/core/mixins.py b/readthedocs/core/mixins.py
index 45bf2be75ab..2460daacd13 100644
--- a/readthedocs/core/mixins.py
+++ b/readthedocs/core/mixins.py
@@ -1,10 +1,8 @@
"""Common mixin classes for views"""
-from __future__ import absolute_import
-from builtins import object
-from vanilla import ListView
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
+from vanilla import ListView
class ListViewWithForm(ListView):
diff --git a/readthedocs/core/models.py b/readthedocs/core/models.py
index 8b72a79199f..6f4f019ae44 100644
--- a/readthedocs/core/models.py
+++ b/readthedocs/core/models.py
@@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
"""Models for the core app."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
from annoying.fields import AutoOneToOneField
from django.db import models
+from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
-from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
-from django.urls import reverse
+from django.utils.translation import ugettext_lazy as _
+
STANDARD_EMAIL = 'anonymous@readthedocs.org'
diff --git a/readthedocs/core/permissions.py b/readthedocs/core/permissions.py
index ad2427b9b25..6af5f2a58b8 100644
--- a/readthedocs/core/permissions.py
+++ b/readthedocs/core/permissions.py
@@ -1,7 +1,5 @@
"""Objects for User permission checks"""
-from __future__ import absolute_import
-
from readthedocs.core.utils.extend import SettingsOverrideObject
diff --git a/readthedocs/core/resolver.py b/readthedocs/core/resolver.py
index a17e832160c..5e0b27492aa 100644
--- a/readthedocs/core/resolver.py
+++ b/readthedocs/core/resolver.py
@@ -1,13 +1,11 @@
"""URL resolver for documentation."""
-from __future__ import absolute_import
-from builtins import object
import re
from django.conf import settings
-from readthedocs.projects.constants import PRIVATE, PUBLIC
from readthedocs.core.utils.extend import SettingsOverrideObject
+from readthedocs.projects.constants import PRIVATE, PUBLIC
class ResolverBase:
diff --git a/readthedocs/core/settings.py b/readthedocs/core/settings.py
index b758cb57fba..5967893cc0e 100644
--- a/readthedocs/core/settings.py
+++ b/readthedocs/core/settings.py
@@ -1,7 +1,5 @@
"""Class based settings for complex settings inheritance."""
-from __future__ import absolute_import
-from builtins import object
import inspect
import sys
diff --git a/readthedocs/core/signals.py b/readthedocs/core/signals.py
index d8b90b57d82..875cde381d9 100644
--- a/readthedocs/core/signals.py
+++ b/readthedocs/core/signals.py
@@ -2,21 +2,19 @@
"""Signal handling for core app."""
-from __future__ import absolute_import
-
import logging
from corsheaders import signals
from django.conf import settings
+from django.db.models import Count, Q
from django.db.models.signals import pre_delete
-from django.dispatch import Signal
-from django.db.models import Q, Count
-from django.dispatch import receiver
+from django.dispatch import Signal, receiver
from future.backports.urllib.parse import urlparse
from rest_framework.permissions import SAFE_METHODS
from readthedocs.oauth.models import RemoteOrganization
-from readthedocs.projects.models import Project, Domain
+from readthedocs.projects.models import Domain, Project
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/core/static.py b/readthedocs/core/static.py
index b7ec2b7962f..71d433b259c 100644
--- a/readthedocs/core/static.py
+++ b/readthedocs/core/static.py
@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import division, print_function, unicode_literals
-
from django.contrib.staticfiles.finders import FileSystemFinder
diff --git a/readthedocs/core/symlink.py b/readthedocs/core/symlink.py
index dcaad60bc3e..bde0d2d454f 100644
--- a/readthedocs/core/symlink.py
+++ b/readthedocs/core/symlink.py
@@ -52,19 +52,11 @@
fabric -> rtd-builds/fabric/en/latest/ # single version
"""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import logging
import os
import shutil
from collections import OrderedDict
-from builtins import object
from django.conf import settings
from readthedocs.builds.models import Version
@@ -74,6 +66,7 @@
from readthedocs.projects import constants
from readthedocs.projects.models import Domain
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/core/tasks.py b/readthedocs/core/tasks.py
index 8ed81f1bb1e..446a19b7ea5 100644
--- a/readthedocs/core/tasks.py
+++ b/readthedocs/core/tasks.py
@@ -1,12 +1,11 @@
"""Basic tasks."""
-from __future__ import absolute_import
import logging
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
-from django.template.loader import get_template
from django.template import TemplateDoesNotExist
+from django.template.loader import get_template
from django.utils import timezone
from messages_extends.models import Message as PersistentMessage
diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py
index e91c1fb667e..d05831aade9 100644
--- a/readthedocs/core/templatetags/core_tags.py
+++ b/readthedocs/core/templatetags/core_tags.py
@@ -1,10 +1,7 @@
"""Template tags for core app."""
-from __future__ import absolute_import
-
import hashlib
-from builtins import str # pylint: disable=redefined-builtin
from django import template
from django.conf import settings
from django.utils.encoding import force_bytes, force_text
diff --git a/readthedocs/core/templatetags/privacy_tags.py b/readthedocs/core/templatetags/privacy_tags.py
index d18778778f6..814018dc9df 100644
--- a/readthedocs/core/templatetags/privacy_tags.py
+++ b/readthedocs/core/templatetags/privacy_tags.py
@@ -1,7 +1,5 @@
"""Template tags to query projects by privacy."""
-from __future__ import absolute_import
-
from django import template
from readthedocs.core.permissions import AdminPermission
diff --git a/readthedocs/core/tests/test_signals.py b/readthedocs/core/tests/test_signals.py
index 6c906c9e87d..f96bb30d566 100644
--- a/readthedocs/core/tests/test_signals.py
+++ b/readthedocs/core/tests/test_signals.py
@@ -1,12 +1,10 @@
-import pytest
import django_dynamic_fixture
-
+import pytest
from django.contrib.auth.models import User
from readthedocs.oauth.models import RemoteOrganization
from readthedocs.projects.models import Project
-
@pytest.mark.django_db
class TestProjectOrganizationSignal:
diff --git a/readthedocs/core/urls/single_version.py b/readthedocs/core/urls/single_version.py
index afc84bda83f..b232ef9ae96 100644
--- a/readthedocs/core/urls/single_version.py
+++ b/readthedocs/core/urls/single_version.py
@@ -1,16 +1,15 @@
"""URL configuration for a single version."""
-from __future__ import absolute_import
-
from functools import reduce
from operator import add
-from django.conf.urls import url
from django.conf import settings
+from django.conf.urls import url
from django.conf.urls.static import static
from readthedocs.constants import pattern_opts
from readthedocs.core.views import serve
+
handler500 = 'readthedocs.core.views.server_error_500'
handler404 = 'readthedocs.core.views.server_error_404'
diff --git a/readthedocs/core/urls/subdomain.py b/readthedocs/core/urls/subdomain.py
index 826c6443660..7b5b752fc88 100644
--- a/readthedocs/core/urls/subdomain.py
+++ b/readthedocs/core/urls/subdomain.py
@@ -1,22 +1,19 @@
"""URL configurations for subdomains."""
-from __future__ import absolute_import
-
from functools import reduce
from operator import add
-from django.conf.urls import url
from django.conf import settings
+from django.conf.urls import url
from django.conf.urls.static import static
+from readthedocs.constants import pattern_opts
+from readthedocs.core.views import server_error_404, server_error_500
from readthedocs.core.views.serve import (
redirect_page_with_filename,
- redirect_project_slug, serve_docs
+ redirect_project_slug,
+ serve_docs,
)
-from readthedocs.core.views import (
- server_error_500,
- server_error_404,
-)
-from readthedocs.constants import pattern_opts
+
handler500 = server_error_500
handler404 = server_error_404
diff --git a/readthedocs/core/utils/extend.py b/readthedocs/core/utils/extend.py
index 567c0c23a4d..c01c76eee86 100644
--- a/readthedocs/core/utils/extend.py
+++ b/readthedocs/core/utils/extend.py
@@ -1,11 +1,9 @@
"""Patterns for extending Read the Docs."""
-from __future__ import absolute_import
import inspect
from django.conf import settings
from django.utils.module_loading import import_string
-import six
def get_override_class(proxy_class, default_class=None):
diff --git a/readthedocs/core/utils/tasks/public.py b/readthedocs/core/utils/tasks/public.py
index 1e51fc65b29..2c1ccaa9e0d 100644
--- a/readthedocs/core/utils/tasks/public.py
+++ b/readthedocs/core/utils/tasks/public.py
@@ -1,17 +1,11 @@
"""Celery tasks with publicly viewable status"""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
from celery import Task, states
from django.conf import settings
from .retrieve import TaskNotFound, get_task_data
+
__all__ = (
'PublicTask', 'TaskNoPermission', 'get_public_task_data'
)
diff --git a/readthedocs/core/utils/tasks/retrieve.py b/readthedocs/core/utils/tasks/retrieve.py
index 0aa3e7570f0..4c592116995 100644
--- a/readthedocs/core/utils/tasks/retrieve.py
+++ b/readthedocs/core/utils/tasks/retrieve.py
@@ -1,15 +1,9 @@
"""Utilities for retrieving task data."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
from celery import states
from celery.result import AsyncResult
+
__all__ = ('TaskNotFound', 'get_task_data')
diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py
index 3156e3cd4fb..a92640e127b 100644
--- a/readthedocs/core/views/hooks.py
+++ b/readthedocs/core/views/hooks.py
@@ -1,12 +1,5 @@
"""Views pertaining to builds."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import json
import logging
import re
@@ -21,6 +14,7 @@
from readthedocs.projects.models import Feature, Project
from readthedocs.projects.tasks import sync_repository_task
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py
index d663dc79180..7f8bb3651aa 100644
--- a/readthedocs/core/views/serve.py
+++ b/readthedocs/core/views/serve.py
@@ -25,18 +25,14 @@
SERVE_DOCS (['private']) - The list of ['private', 'public'] docs to serve.
"""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
import mimetypes
import os
from functools import wraps
from django.conf import settings
-from django.http import HttpResponse, HttpResponseRedirect, Http404
-from django.shortcuts import get_object_or_404
-from django.shortcuts import render
+from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.shortcuts import get_object_or_404, render
from django.views.static import serve
from readthedocs.builds.models import Version
@@ -46,6 +42,7 @@
from readthedocs.projects import constants
from readthedocs.projects.models import Project, ProjectRelationship
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py
index eeb06379c13..65b35f5f28f 100644
--- a/readthedocs/doc_builder/backends/mkdocs.py
+++ b/readthedocs/doc_builder/backends/mkdocs.py
@@ -3,9 +3,6 @@
.. _MkDocs: http://www.mkdocs.org/
"""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import json
import logging
import os
@@ -18,6 +15,7 @@
from readthedocs.doc_builder.exceptions import MkDocsYAMLParseError
from readthedocs.projects.models import Feature
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py
index 4f2f7b33829..8402cfd7e1c 100644
--- a/readthedocs/doc_builder/backends/sphinx.py
+++ b/readthedocs/doc_builder/backends/sphinx.py
@@ -4,27 +4,23 @@
.. _Sphinx: http://www.sphinx-doc.org/
"""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import codecs
-import shutil
import logging
import os
+import shutil
import sys
import zipfile
from glob import glob
-import six
from django.conf import settings
from django.template import loader as template_loader
from django.template.loader import render_to_string
from readthedocs.builds import utils as version_utils
from readthedocs.projects.exceptions import ProjectConfigurationError
+from readthedocs.projects.models import Feature
from readthedocs.projects.utils import safe_write
from readthedocs.restapi.client import api
-from readthedocs.projects.models import Feature
from ..base import BaseBuilder, restoring_chdir
from ..constants import PDF_RE
@@ -32,6 +28,7 @@
from ..exceptions import BuildEnvironmentError
from ..signals import finalize_sphinx_context_data
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/doc_builder/base.py b/readthedocs/doc_builder/base.py
index c382fefc393..8810ab5f782 100644
--- a/readthedocs/doc_builder/base.py
+++ b/readthedocs/doc_builder/base.py
@@ -1,15 +1,12 @@
# -*- coding: utf-8 -*-
"""Base classes for Builders."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
import os
import shutil
-from builtins import object
from functools import wraps
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/doc_builder/config.py b/readthedocs/doc_builder/config.py
index 00a0095bcc6..9eabeaa0ce0 100644
--- a/readthedocs/doc_builder/config.py
+++ b/readthedocs/doc_builder/config.py
@@ -1,9 +1,6 @@
# -*- coding: utf-8 -*-
"""An API to load config from a readthedocs.yml file."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
from os import path
from readthedocs.config import BuildConfigV1, ConfigError, InvalidConfig
diff --git a/readthedocs/doc_builder/constants.py b/readthedocs/doc_builder/constants.py
index 1cd1a5b1348..f2f15010bba 100644
--- a/readthedocs/doc_builder/constants.py
+++ b/readthedocs/doc_builder/constants.py
@@ -1,15 +1,13 @@
# -*- coding: utf-8 -*-
"""Doc build constants."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
import os
import re
from django.conf import settings
+
log = logging.getLogger(__name__)
MKDOCS_TEMPLATE_DIR = os.path.join(
diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py
index 10ba06f5c60..cbdff522d92 100644
--- a/readthedocs/doc_builder/environments.py
+++ b/readthedocs/doc_builder/environments.py
@@ -2,13 +2,6 @@
"""Documentation Builder Environments."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import logging
import os
import re
@@ -18,8 +11,6 @@
import traceback
from datetime import datetime
-import six
-from builtins import object, str
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from docker import APIClient
diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py
index 80582595b00..5898dfce015 100644
--- a/readthedocs/doc_builder/exceptions.py
+++ b/readthedocs/doc_builder/exceptions.py
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
"""Exceptions raised when building documentation."""
-from __future__ import division, print_function, unicode_literals
-
from django.utils.translation import ugettext_noop
diff --git a/readthedocs/doc_builder/loader.py b/readthedocs/doc_builder/loader.py
index 0edcaace778..8d733b3d8a7 100644
--- a/readthedocs/doc_builder/loader.py
+++ b/readthedocs/doc_builder/loader.py
@@ -1,9 +1,9 @@
"""Lookup tables for builders and backends."""
-from __future__ import absolute_import
from importlib import import_module
from django.conf import settings
+
# Managers
mkdocs = import_module(
getattr(settings, 'MKDOCS_BACKEND',
diff --git a/readthedocs/doc_builder/python_environments.py b/readthedocs/doc_builder/python_environments.py
index 7d85a275f83..92ec9a2f5f0 100644
--- a/readthedocs/doc_builder/python_environments.py
+++ b/readthedocs/doc_builder/python_environments.py
@@ -1,13 +1,6 @@
# -*- coding: utf-8 -*-
"""An abstraction over virtualenv and Conda environments."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import copy
import itertools
import json
@@ -15,8 +8,6 @@
import os
import shutil
-import six
-from builtins import object, open
from django.conf import settings
from readthedocs.doc_builder.config import load_yaml_config
@@ -26,6 +17,7 @@
from readthedocs.projects.constants import LOG_TEMPLATE
from readthedocs.projects.models import Feature
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/doc_builder/signals.py b/readthedocs/doc_builder/signals.py
index 419531a5630..08991fabb03 100644
--- a/readthedocs/doc_builder/signals.py
+++ b/readthedocs/doc_builder/signals.py
@@ -1,9 +1,8 @@
"""Signals for adding custom context data"""
-from __future__ import absolute_import
-
import django.dispatch
+
finalize_sphinx_context_data = django.dispatch.Signal(
providing_args=['buildenv', 'context', 'response_data']
)
diff --git a/readthedocs/gold/admin.py b/readthedocs/gold/admin.py
index ecd512b75a8..a1dc3e80e45 100644
--- a/readthedocs/gold/admin.py
+++ b/readthedocs/gold/admin.py
@@ -1,7 +1,7 @@
"""Django admin configuration for the Gold Membership app."""
-from __future__ import absolute_import
from django.contrib import admin
+
from .models import GoldUser
diff --git a/readthedocs/gold/apps.py b/readthedocs/gold/apps.py
index eda30768d46..380a21f0947 100644
--- a/readthedocs/gold/apps.py
+++ b/readthedocs/gold/apps.py
@@ -1,6 +1,5 @@
"""Django app configuration for the Gold Membership app."""
-from __future__ import absolute_import
from django.apps import AppConfig
diff --git a/readthedocs/gold/forms.py b/readthedocs/gold/forms.py
index fead5e26b11..31c4a18aa1d 100644
--- a/readthedocs/gold/forms.py
+++ b/readthedocs/gold/forms.py
@@ -1,10 +1,6 @@
"""Gold subscription forms."""
-from __future__ import absolute_import
-
-from builtins import object
from django import forms
-
from django.utils.translation import ugettext_lazy as _
from readthedocs.payments.forms import StripeModelForm, StripeResourceMixin
diff --git a/readthedocs/gold/models.py b/readthedocs/gold/models.py
index 278dc60227e..c73ec79e39d 100644
--- a/readthedocs/gold/models.py
+++ b/readthedocs/gold/models.py
@@ -1,13 +1,6 @@
# -*- coding: utf-8 -*-
"""Django models for recurring donations aka Gold Membership."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import math
from django.db import models
@@ -16,6 +9,7 @@
from readthedocs.projects.models import Project
+
#: The membership options that are currently available
LEVEL_CHOICES = (
('v1-org-5', '$5/mo'),
diff --git a/readthedocs/gold/signals.py b/readthedocs/gold/signals.py
index a3cef14ca9e..4c089ebb0d5 100644
--- a/readthedocs/gold/signals.py
+++ b/readthedocs/gold/signals.py
@@ -1,6 +1,5 @@
"""Gold model signals"""
-from __future__ import absolute_import
from django.db.models.signals import pre_delete
from django.dispatch import receiver
diff --git a/readthedocs/gold/tests/test_forms.py b/readthedocs/gold/tests/test_forms.py
index acf1c82fe1e..5ffaf918384 100644
--- a/readthedocs/gold/tests/test_forms.py
+++ b/readthedocs/gold/tests/test_forms.py
@@ -1,14 +1,12 @@
-from __future__ import absolute_import
-import mock
import django_dynamic_fixture as fixture
-from django.test import TestCase
+import mock
from django.contrib.auth.models import User
+from django.test import TestCase
from readthedocs.projects.models import Project
-from ..models import GoldUser
from ..forms import GoldSubscriptionForm
-
+from ..models import GoldUser
class GoldSubscriptionFormTests(TestCase):
diff --git a/readthedocs/gold/tests/test_signals.py b/readthedocs/gold/tests/test_signals.py
index 62618581c06..947f042e035 100644
--- a/readthedocs/gold/tests/test_signals.py
+++ b/readthedocs/gold/tests/test_signals.py
@@ -1,16 +1,14 @@
-from __future__ import absolute_import
-import mock
import django_dynamic_fixture as fixture
-from django.test import TestCase
+import mock
from django.contrib.auth.models import User
from django.db.models.signals import pre_delete
+from django.test import TestCase
from readthedocs.projects.models import Project
from ..models import GoldUser
from ..signals import delete_customer
-
class GoldSignalTests(TestCase):
def setUp(self):
diff --git a/readthedocs/gold/urls.py b/readthedocs/gold/urls.py
index dd8b6e354a4..2a1e1acafe3 100644
--- a/readthedocs/gold/urls.py
+++ b/readthedocs/gold/urls.py
@@ -1,6 +1,5 @@
"""Gold subscription URLs"""
-from __future__ import absolute_import
from django.conf.urls import url
from readthedocs.gold import views
diff --git a/readthedocs/gold/views.py b/readthedocs/gold/views.py
index 7e2677e2a25..2867d49dbf9 100644
--- a/readthedocs/gold/views.py
+++ b/readthedocs/gold/views.py
@@ -1,20 +1,13 @@
# -*- coding: utf-8 -*-
"""Gold subscription views."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals
-)
-
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.messages.views import SuccessMessageMixin
-from django.urls import reverse, reverse_lazy
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
+from django.urls import reverse, reverse_lazy
from django.utils.translation import ugettext_lazy as _
from vanilla import DeleteView, DetailView, UpdateView
diff --git a/readthedocs/integrations/admin.py b/readthedocs/integrations/admin.py
index 5fabe23be91..24f0ff95192 100644
--- a/readthedocs/integrations/admin.py
+++ b/readthedocs/integrations/admin.py
@@ -1,12 +1,11 @@
"""Integration admin models."""
-from __future__ import absolute_import
-from django.contrib import admin
from django import urls
+from django.contrib import admin
from django.utils.safestring import mark_safe
from pygments.formatters import HtmlFormatter
-from .models import Integration, HttpExchange
+from .models import HttpExchange, Integration
def pretty_json_field(field, description, include_styles=False):
diff --git a/readthedocs/integrations/models.py b/readthedocs/integrations/models.py
index 032c16c28a4..8dbbbf80e93 100644
--- a/readthedocs/integrations/models.py
+++ b/readthedocs/integrations/models.py
@@ -2,18 +2,10 @@
"""Integration models for external services."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import json
import re
import uuid
-from builtins import object, str
from django.contrib.contenttypes.fields import (
GenericForeignKey,
GenericRelation,
diff --git a/readthedocs/notifications/apps.py b/readthedocs/notifications/apps.py
index 38ed93cda31..3be3e557bf9 100644
--- a/readthedocs/notifications/apps.py
+++ b/readthedocs/notifications/apps.py
@@ -1,5 +1,4 @@
"""Django app configuration for the notifications app."""
-from __future__ import absolute_import
from django.apps import AppConfig
diff --git a/readthedocs/notifications/backends.py b/readthedocs/notifications/backends.py
index e2552c1bdaa..e2a3ed8dfbf 100644
--- a/readthedocs/notifications/backends.py
+++ b/readthedocs/notifications/backends.py
@@ -7,10 +7,6 @@
displayed on the site.
"""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
-from builtins import object
from django.conf import settings
from django.http import HttpRequest
from django.utils.module_loading import import_string
diff --git a/readthedocs/notifications/constants.py b/readthedocs/notifications/constants.py
index d15efa98448..4ee285d17a4 100644
--- a/readthedocs/notifications/constants.py
+++ b/readthedocs/notifications/constants.py
@@ -1,6 +1,5 @@
"""Notification constants"""
-from __future__ import absolute_import
from messages_extends import constants as message_constants
diff --git a/readthedocs/notifications/forms.py b/readthedocs/notifications/forms.py
index 58924308515..f0026d5b378 100644
--- a/readthedocs/notifications/forms.py
+++ b/readthedocs/notifications/forms.py
@@ -1,5 +1,4 @@
"""HTML forms for sending notifications."""
-from __future__ import absolute_import
from django import forms
from django.utils.translation import ugettext_lazy as _
diff --git a/readthedocs/notifications/notification.py b/readthedocs/notifications/notification.py
index 8329bb91b26..e363bc16a79 100644
--- a/readthedocs/notifications/notification.py
+++ b/readthedocs/notifications/notification.py
@@ -1,17 +1,16 @@
# -*- coding: utf-8 -*-
"""Support for templating of notifications."""
-from __future__ import absolute_import
-from builtins import object
import logging
+
from django.conf import settings
-from django.template import Template, Context
-from django.template.loader import render_to_string
from django.db import models
from django.http import HttpRequest
+from django.template import Context, Template
+from django.template.loader import render_to_string
-from .backends import send_notification
from . import constants
+from .backends import send_notification
log = logging.getLogger(__name__)
diff --git a/readthedocs/notifications/storages.py b/readthedocs/notifications/storages.py
index 9a98ef7632f..ccd9f8e9a13 100644
--- a/readthedocs/notifications/storages.py
+++ b/readthedocs/notifications/storages.py
@@ -1,20 +1,21 @@
# -*- coding: utf-8 -*-
"""Customised storage for notifications."""
-from __future__ import absolute_import
from django.contrib.messages.storage.base import Message
from django.db.models import Q
from django.utils.safestring import mark_safe
-from messages_extends.storages import FallbackStorage, PersistentStorage
-from messages_extends.models import Message as PersistentMessage
from messages_extends.constants import PERSISTENT_MESSAGE_LEVELS
+from messages_extends.models import Message as PersistentMessage
+from messages_extends.storages import FallbackStorage, PersistentStorage
+
+from .constants import NON_PERSISTENT_MESSAGE_LEVELS
+
try:
from django.utils import timezone
except ImportError:
from datetime import datetime as timezone
-from .constants import NON_PERSISTENT_MESSAGE_LEVELS
class FallbackUniqueStorage(FallbackStorage):
diff --git a/readthedocs/notifications/urls.py b/readthedocs/notifications/urls.py
index 349c1f293aa..d44522bc9f1 100644
--- a/readthedocs/notifications/urls.py
+++ b/readthedocs/notifications/urls.py
@@ -1,9 +1,7 @@
"""Renames for messages_extends URLs"""
-from __future__ import absolute_import
from django.conf.urls import url
-
-from messages_extends.views import message_mark_read, message_mark_all_read
+from messages_extends.views import message_mark_all_read, message_mark_read
urlpatterns = [
diff --git a/readthedocs/notifications/views.py b/readthedocs/notifications/views.py
index 2cbdb0959d4..3853e8cdbdd 100644
--- a/readthedocs/notifications/views.py
+++ b/readthedocs/notifications/views.py
@@ -1,8 +1,7 @@
"""Django views for the notifications app."""
-from __future__ import absolute_import
-from django.views.generic import FormView
from django.contrib import admin, messages
from django.http import HttpResponseRedirect
+from django.views.generic import FormView
from .forms import SendNotificationForm
diff --git a/readthedocs/oauth/admin.py b/readthedocs/oauth/admin.py
index b961e6472c5..777e48272bc 100644
--- a/readthedocs/oauth/admin.py
+++ b/readthedocs/oauth/admin.py
@@ -1,9 +1,8 @@
"""Admin configuration for the OAuth app."""
-from __future__ import absolute_import
from django.contrib import admin
-from .models import RemoteRepository, RemoteOrganization
+from .models import RemoteOrganization, RemoteRepository
class RemoteRepositoryAdmin(admin.ModelAdmin):
diff --git a/readthedocs/oauth/models.py b/readthedocs/oauth/models.py
index 54a02191fe7..7d6aeec15de 100644
--- a/readthedocs/oauth/models.py
+++ b/readthedocs/oauth/models.py
@@ -1,19 +1,15 @@
# -*- coding: utf-8 -*-
"""OAuth service models."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import json
-from builtins import object
from allauth.socialaccount.models import SocialAccount
from django.conf import settings
from django.contrib.auth.models import User
-from django.urls import reverse
from django.core.validators import URLValidator
from django.db import models
from django.db.models import Q
+from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
diff --git a/readthedocs/oauth/notifications.py b/readthedocs/oauth/notifications.py
index 41b9c78ce6e..915a2003c7e 100644
--- a/readthedocs/oauth/notifications.py
+++ b/readthedocs/oauth/notifications.py
@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import division, print_function, unicode_literals
-
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from messages_extends.constants import ERROR_PERSISTENT
diff --git a/readthedocs/oauth/querysets.py b/readthedocs/oauth/querysets.py
index d01703eb1f0..c23f6cbeea1 100644
--- a/readthedocs/oauth/querysets.py
+++ b/readthedocs/oauth/querysets.py
@@ -1,7 +1,5 @@
"""Managers for OAuth models"""
-from __future__ import absolute_import
-
from django.db import models
from readthedocs.core.utils.extend import SettingsOverrideObject
diff --git a/readthedocs/oauth/services/base.py b/readthedocs/oauth/services/base.py
index 87086a03149..77f2a7bf1a6 100644
--- a/readthedocs/oauth/services/base.py
+++ b/readthedocs/oauth/services/base.py
@@ -1,21 +1,18 @@
# -*- coding: utf-8 -*-
"""OAuth utility functions."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
from datetime import datetime
from allauth.socialaccount.models import SocialAccount
from allauth.socialaccount.providers import registry
-from builtins import object
from django.conf import settings
from django.utils import timezone
from oauthlib.oauth2.rfc6749.errors import InvalidClientIdError
from requests.exceptions import RequestException
from requests_oauthlib import OAuth2Session
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/oauth/services/bitbucket.py b/readthedocs/oauth/services/bitbucket.py
index 2586de5697b..70fb90b0b8a 100644
--- a/readthedocs/oauth/services/bitbucket.py
+++ b/readthedocs/oauth/services/bitbucket.py
@@ -1,16 +1,15 @@
"""OAuth utility functions."""
-from __future__ import absolute_import
-from builtins import str
-import logging
import json
+import logging
import re
+from allauth.socialaccount.providers.bitbucket_oauth2.views import (
+ BitbucketOAuth2Adapter,
+)
from django.conf import settings
from django.urls import reverse
from requests.exceptions import RequestException
-from allauth.socialaccount.providers.bitbucket_oauth2.views import (
- BitbucketOAuth2Adapter)
from readthedocs.builds import utils as build_utils
from readthedocs.integrations.models import Integration
diff --git a/readthedocs/oauth/services/github.py b/readthedocs/oauth/services/github.py
index f9a03c4ff76..2e844aaaf31 100644
--- a/readthedocs/oauth/services/github.py
+++ b/readthedocs/oauth/services/github.py
@@ -1,16 +1,14 @@
"""OAuth utility functions."""
-from __future__ import absolute_import
-from builtins import str
-import logging
import json
+import logging
import re
+from allauth.socialaccount.models import SocialToken
+from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
from django.conf import settings
from django.urls import reverse
from requests.exceptions import RequestException
-from allauth.socialaccount.models import SocialToken
-from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
from readthedocs.builds import utils as build_utils
from readthedocs.integrations.models import Integration
@@ -19,6 +17,7 @@
from ..models import RemoteOrganization, RemoteRepository
from .base import Service
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/oauth/services/gitlab.py b/readthedocs/oauth/services/gitlab.py
index b9562617adf..ada8a6f1d5f 100644
--- a/readthedocs/oauth/services/gitlab.py
+++ b/readthedocs/oauth/services/gitlab.py
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
"""OAuth utility functions."""
-from __future__ import division, print_function, unicode_literals
-
import json
import logging
import re
@@ -19,6 +17,7 @@
from ..models import RemoteOrganization, RemoteRepository
from .base import Service
+
try:
from urlparse import urljoin, urlparse
except ImportError:
diff --git a/readthedocs/oauth/tasks.py b/readthedocs/oauth/tasks.py
index 45b49ceac09..eee0efe4845 100644
--- a/readthedocs/oauth/tasks.py
+++ b/readthedocs/oauth/tasks.py
@@ -1,13 +1,6 @@
# -*- coding: utf-8 -*-
"""Tasks for OAuth services."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import logging
from allauth.socialaccount.providers import registry as allauth_registry
@@ -23,6 +16,7 @@
from .services import registry
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/oauth/utils.py b/readthedocs/oauth/utils.py
index 64e7dc7ed07..d797baa057f 100644
--- a/readthedocs/oauth/utils.py
+++ b/readthedocs/oauth/utils.py
@@ -1,9 +1,6 @@
# -*- coding: utf-8 -*-
"""Support code for OAuth, including webhook support."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
from django.contrib import messages
@@ -11,9 +8,14 @@
from readthedocs.integrations.models import Integration
from readthedocs.oauth.services import (
- BitbucketService, GitHubService, GitLabService, registry)
+ BitbucketService,
+ GitHubService,
+ GitLabService,
+ registry,
+)
from readthedocs.projects.models import Project
+
log = logging.getLogger(__name__)
SERVICE_MAP = {
diff --git a/readthedocs/payments/forms.py b/readthedocs/payments/forms.py
index 89b2c1dc782..c4501d68224 100644
--- a/readthedocs/payments/forms.py
+++ b/readthedocs/payments/forms.py
@@ -1,17 +1,15 @@
"""Payment forms."""
-from __future__ import absolute_import
-from builtins import str
-from builtins import object
import logging
-from stripe import Customer, Charge
-from stripe.error import InvalidRequestError
from django import forms
from django.utils.translation import ugettext_lazy as _
+from stripe import Charge, Customer
+from stripe.error import InvalidRequestError
from .utils import stripe
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/payments/mixins.py b/readthedocs/payments/mixins.py
index d90fc169925..3741147866c 100644
--- a/readthedocs/payments/mixins.py
+++ b/readthedocs/payments/mixins.py
@@ -1,7 +1,5 @@
"""Payment view mixin classes."""
-from __future__ import absolute_import
-from builtins import object
from django.conf import settings
diff --git a/readthedocs/payments/utils.py b/readthedocs/payments/utils.py
index a65b5b0a8f6..36dc1f76941 100644
--- a/readthedocs/payments/utils.py
+++ b/readthedocs/payments/utils.py
@@ -5,10 +5,10 @@
:py:class:`readthedocs.payments.forms.StripeResourceMixin`.
"""
-from __future__ import absolute_import
import stripe
from django.conf import settings
+
stripe.api_key = getattr(settings, 'STRIPE_SECRET', None)
diff --git a/readthedocs/profiles/urls/private.py b/readthedocs/profiles/urls/private.py
index 85acbf2d9fa..21a6e604fc3 100644
--- a/readthedocs/profiles/urls/private.py
+++ b/readthedocs/profiles/urls/private.py
@@ -1,12 +1,5 @@
"""URL patterns for views to modify user profiles."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
from django.conf.urls import url
from readthedocs.core.forms import UserProfileForm
diff --git a/readthedocs/profiles/urls/public.py b/readthedocs/profiles/urls/public.py
index 2a9c458e6fd..0cade801350 100644
--- a/readthedocs/profiles/urls/public.py
+++ b/readthedocs/profiles/urls/public.py
@@ -1,6 +1,5 @@
"""URL patterns to view user profiles."""
-from __future__ import absolute_import
from django.conf.urls import url
from readthedocs.profiles import views
diff --git a/readthedocs/profiles/views.py b/readthedocs/profiles/views.py
index d288f4767bf..0396795e9ca 100644
--- a/readthedocs/profiles/views.py
+++ b/readthedocs/profiles/views.py
@@ -1,20 +1,13 @@
# -*- coding: utf-8 -*-
"""Views for creating, editing and viewing site-specific user profiles."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
from django.contrib import messages
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
-from django.urls import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render
+from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from readthedocs.core.forms import UserAdvertisingForm, UserDeleteForm
diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py
index 48109f7eafb..5beebc58d50 100644
--- a/readthedocs/projects/admin.py
+++ b/readthedocs/projects/admin.py
@@ -1,13 +1,6 @@
# -*- coding: utf-8 -*-
"""Django administration interface for `projects.models`"""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
from django.contrib import admin, messages
from django.contrib.admin.actions import delete_selected
from django.utils.translation import ugettext_lazy as _
diff --git a/readthedocs/projects/backends/views.py b/readthedocs/projects/backends/views.py
index 2b3d0fa41c5..428ca3f82ca 100644
--- a/readthedocs/projects/backends/views.py
+++ b/readthedocs/projects/backends/views.py
@@ -5,7 +5,6 @@
settings override of the view class.
"""
-from __future__ import absolute_import
from readthedocs.core.utils.extend import SettingsOverrideObject
from readthedocs.projects.views import private
diff --git a/readthedocs/projects/constants.py b/readthedocs/projects/constants.py
index 7e6d130e93e..f357942d611 100644
--- a/readthedocs/projects/constants.py
+++ b/readthedocs/projects/constants.py
@@ -6,13 +6,11 @@
theme names and repository types.
"""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import re
from django.utils.translation import ugettext_lazy as _
+
DOCUMENTATION_CHOICES = (
('auto', _('Automatically Choose')),
('sphinx', _('Sphinx Html')),
diff --git a/readthedocs/projects/exceptions.py b/readthedocs/projects/exceptions.py
index 41daacdeaaf..07ccd562459 100644
--- a/readthedocs/projects/exceptions.py
+++ b/readthedocs/projects/exceptions.py
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
"""Project exceptions."""
-from __future__ import division, print_function, unicode_literals
-
from django.conf import settings
from django.utils.translation import ugettext_noop as _
diff --git a/readthedocs/projects/feeds.py b/readthedocs/projects/feeds.py
index b3739f4b005..de3cc008b60 100644
--- a/readthedocs/projects/feeds.py
+++ b/readthedocs/projects/feeds.py
@@ -1,6 +1,5 @@
"""Project RSS feeds"""
-from __future__ import absolute_import
from django.contrib.syndication.views import Feed
from readthedocs.projects.models import Project
diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py
index c447ab40c0b..a929213885e 100644
--- a/readthedocs/projects/forms.py
+++ b/readthedocs/projects/forms.py
@@ -1,16 +1,8 @@
# -*- coding: utf-8 -*-
"""Project forms."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
from random import choice
-from builtins import object
from django import forms
from django.conf import settings
from django.contrib.auth.models import User
diff --git a/readthedocs/projects/management/commands/import_project_from_live.py b/readthedocs/projects/management/commands/import_project_from_live.py
index 95c85778349..042caf709fc 100644
--- a/readthedocs/projects/management/commands/import_project_from_live.py
+++ b/readthedocs/projects/management/commands/import_project_from_live.py
@@ -1,12 +1,12 @@
"""Import project command"""
-from __future__ import absolute_import
-from django.core.management import call_command
-from django.core.management.base import BaseCommand
import json
-import slumber
+import slumber
from django.contrib.auth.models import User
+from django.core.management import call_command
+from django.core.management.base import BaseCommand
+
from ...models import Project
diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py
index c87295b864d..24bda8f18e9 100644
--- a/readthedocs/projects/models.py
+++ b/readthedocs/projects/models.py
@@ -1,18 +1,14 @@
# -*- coding: utf-8 -*-
"""Project models."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import fnmatch
import logging
import os
-from builtins import object # pylint: disable=redefined-builtin
from django.conf import settings
from django.contrib.auth.models import User
-from django.urls import NoReverseMatch, reverse
from django.db import models
+from django.urls import NoReverseMatch, reverse
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django_extensions.db.models import TimeStampedModel
@@ -26,15 +22,22 @@
from readthedocs.projects import constants
from readthedocs.projects.exceptions import ProjectConfigurationError
from readthedocs.projects.querysets import (
- ChildRelatedProjectQuerySet, FeatureQuerySet, ProjectQuerySet,
- RelatedProjectQuerySet)
+ ChildRelatedProjectQuerySet,
+ FeatureQuerySet,
+ ProjectQuerySet,
+ RelatedProjectQuerySet,
+)
from readthedocs.projects.templatetags.projects_tags import sort_version_aware
-from readthedocs.projects.validators import validate_domain_name, validate_repository_url
+from readthedocs.projects.validators import (
+ validate_domain_name,
+ validate_repository_url,
+)
from readthedocs.projects.version_handling import determine_stable_version
from readthedocs.restapi.client import api
from readthedocs.vcs_support.backends import backend_cls
from readthedocs.vcs_support.utils import Lock, NonBlockingLock
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py
index eafbecf66c3..fc46cb0fdca 100644
--- a/readthedocs/projects/notifications.py
+++ b/readthedocs/projects/notifications.py
@@ -1,6 +1,5 @@
"""Project notifications"""
-from __future__ import absolute_import
from readthedocs.notifications import Notification
from readthedocs.notifications.constants import REQUIREMENT
diff --git a/readthedocs/projects/querysets.py b/readthedocs/projects/querysets.py
index 3884eb26db6..4bde40fa39f 100644
--- a/readthedocs/projects/querysets.py
+++ b/readthedocs/projects/querysets.py
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
"""Project model QuerySet classes."""
-from __future__ import absolute_import
-
from django.db import models
from django.db.models import Q
from guardian.shortcuts import get_objects_for_user
diff --git a/readthedocs/projects/signals.py b/readthedocs/projects/signals.py
index 6ef49f9e67c..61251c3298f 100644
--- a/readthedocs/projects/signals.py
+++ b/readthedocs/projects/signals.py
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Project signals"""
-from __future__ import absolute_import
import django.dispatch
diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py
index 10b6660deca..06ec7981ea2 100644
--- a/readthedocs/projects/tasks.py
+++ b/readthedocs/projects/tasks.py
@@ -6,13 +6,6 @@
rebuilding documentation.
"""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import datetime
import hashlib
import json
@@ -23,11 +16,10 @@
from collections import Counter, defaultdict
import requests
-from builtins import str
from celery.exceptions import SoftTimeLimitExceeded
from django.conf import settings
-from django.urls import reverse
from django.db.models import Q
+from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from slumber.exceptions import HttpClientError
@@ -56,11 +48,11 @@
)
from readthedocs.doc_builder.exceptions import (
BuildEnvironmentError,
+ BuildEnvironmentWarning,
BuildTimeoutError,
ProjectBuildsSkippedError,
VersionLockedError,
YAMLParseError,
- BuildEnvironmentWarning,
)
from readthedocs.doc_builder.loader import get_builder_class
from readthedocs.doc_builder.python_environments import Conda, Virtualenv
@@ -82,6 +74,7 @@
files_changed,
)
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/projects/templatetags/projects_tags.py b/readthedocs/projects/templatetags/projects_tags.py
index 699b1a24900..34c97508523 100644
--- a/readthedocs/projects/templatetags/projects_tags.py
+++ b/readthedocs/projects/templatetags/projects_tags.py
@@ -1,6 +1,5 @@
"""Project template tags and filters"""
-from __future__ import absolute_import
from django import template
from readthedocs.projects.version_handling import comparable_version
diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py
index 1cdc294416d..a5ca6388f47 100644
--- a/readthedocs/projects/urls/private.py
+++ b/readthedocs/projects/urls/private.py
@@ -1,17 +1,27 @@
"""Project URLs for authenticated users"""
-from __future__ import absolute_import
from django.conf.urls import url
from readthedocs.constants import pattern_opts
+from readthedocs.projects.backends.views import ImportDemoView, ImportWizardView
from readthedocs.projects.views import private
from readthedocs.projects.views.private import (
- ProjectDashboard, ImportView,
- ProjectUpdate, ProjectAdvancedUpdate,
- DomainList, DomainCreate, DomainDelete, DomainUpdate,
- IntegrationList, IntegrationCreate, IntegrationDetail, IntegrationDelete,
- IntegrationExchangeDetail, IntegrationWebhookSync, ProjectAdvertisingUpdate)
-from readthedocs.projects.backends.views import ImportWizardView, ImportDemoView
+ DomainCreate,
+ DomainDelete,
+ DomainList,
+ DomainUpdate,
+ ImportView,
+ IntegrationCreate,
+ IntegrationDelete,
+ IntegrationDetail,
+ IntegrationExchangeDetail,
+ IntegrationList,
+ IntegrationWebhookSync,
+ ProjectAdvancedUpdate,
+ ProjectAdvertisingUpdate,
+ ProjectDashboard,
+ ProjectUpdate,
+)
urlpatterns = [
diff --git a/readthedocs/projects/urls/public.py b/readthedocs/projects/urls/public.py
index b46b8105aa4..be0d0fc179e 100644
--- a/readthedocs/projects/urls/public.py
+++ b/readthedocs/projects/urls/public.py
@@ -1,13 +1,11 @@
"""Project URLS for public users"""
-from __future__ import absolute_import
from django.conf.urls import url
-from readthedocs.projects.views import public
-from readthedocs.projects.views.public import ProjectIndex, ProjectDetailView
-
from readthedocs.builds import views as build_views
from readthedocs.constants import pattern_opts
+from readthedocs.projects.views import public
+from readthedocs.projects.views.public import ProjectDetailView, ProjectIndex
urlpatterns = [
diff --git a/readthedocs/projects/utils.py b/readthedocs/projects/utils.py
index 840dd17482a..cdacd26c276 100644
--- a/readthedocs/projects/utils.py
+++ b/readthedocs/projects/utils.py
@@ -1,19 +1,12 @@
# -*- coding: utf-8 -*-
"""Utility functions used by projects."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import logging
import os
-from builtins import open
from django.conf import settings
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/projects/validators.py b/readthedocs/projects/validators.py
index f2b7b468746..a362692c43d 100644
--- a/readthedocs/projects/validators.py
+++ b/readthedocs/projects/validators.py
@@ -1,14 +1,12 @@
"""Validators for projects app."""
-# From https://github.com/django/django/pull/3477/files
-from __future__ import absolute_import
import re
from django.conf import settings
from django.core.exceptions import ValidationError
+from django.core.validators import RegexValidator
from django.utils.deconstruct import deconstructible
from django.utils.translation import ugettext_lazy as _
-from django.core.validators import RegexValidator
from future.backports.urllib.parse import urlparse
diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py
index d3ef3d3a108..d1ef1751a18 100644
--- a/readthedocs/projects/version_handling.py
+++ b/readthedocs/projects/version_handling.py
@@ -1,15 +1,14 @@
# -*- coding: utf-8 -*-
"""Project version handling."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import unicodedata
-import six
from packaging.version import InvalidVersion, Version
from readthedocs.builds.constants import (
- LATEST_VERBOSE_NAME, STABLE_VERBOSE_NAME, TAG)
+ LATEST_VERBOSE_NAME,
+ STABLE_VERBOSE_NAME,
+ TAG,
+)
def parse_version_failsafe(version_string):
diff --git a/readthedocs/projects/views/base.py b/readthedocs/projects/views/base.py
index b6e8e387dd1..fa85dd440f3 100644
--- a/readthedocs/projects/views/base.py
+++ b/readthedocs/projects/views/base.py
@@ -1,21 +1,18 @@
# -*- coding: utf-8 -*-
"""Mix-in classes for project views."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
-from builtins import object
from datetime import timedelta
from django.conf import settings
-from django.urls import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
+from django.urls import reverse
from django.utils import timezone
from ..exceptions import ProjectSpamError
from ..models import Project
+
log = logging.getLogger(__name__)
USER_MATURITY_DAYS = getattr(settings, 'USER_MATURITY_DAYS', 7)
diff --git a/readthedocs/projects/views/mixins.py b/readthedocs/projects/views/mixins.py
index 1692b0e461e..ed04d0377fb 100644
--- a/readthedocs/projects/views/mixins.py
+++ b/readthedocs/projects/views/mixins.py
@@ -1,7 +1,5 @@
"""Mixin classes for project views."""
-from __future__ import absolute_import
-from builtins import object
from django.shortcuts import get_object_or_404
from readthedocs.projects.models import Project
diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py
index 22b355f94a2..7a3e8b7523a 100644
--- a/readthedocs/projects/views/private.py
+++ b/readthedocs/projects/views/private.py
@@ -2,13 +2,6 @@
"""Project views for authenticated users."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import logging
from allauth.socialaccount.models import SocialAccount
@@ -66,6 +59,7 @@
from readthedocs.projects.signals import project_import
from readthedocs.projects.views.base import ProjectAdminMixin, ProjectSpamMixin
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/projects/views/public.py b/readthedocs/projects/views/public.py
index 028571a293d..87a76947d80 100644
--- a/readthedocs/projects/views/public.py
+++ b/readthedocs/projects/views/public.py
@@ -1,9 +1,6 @@
# -*- coding: utf-8 -*-
"""Public project views."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import json
import logging
import mimetypes
@@ -16,9 +13,9 @@
from django.contrib import messages
from django.contrib.auth.models import User
from django.core.cache import cache
-from django.urls import reverse
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
+from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.views.generic import DetailView, ListView
from taggit.models import Tag
@@ -32,6 +29,7 @@
from .base import ProjectOnboardMixin
+
log = logging.getLogger(__name__)
search_log = logging.getLogger(__name__ + '.search')
mimetypes.add_type('application/epub+zip', '.epub')
diff --git a/readthedocs/redirects/admin.py b/readthedocs/redirects/admin.py
index 6bd2d73470d..2091ab87acc 100644
--- a/readthedocs/redirects/admin.py
+++ b/readthedocs/redirects/admin.py
@@ -1,8 +1,7 @@
"""Django admin configuration for the redirects app."""
-from __future__ import absolute_import
-
from django.contrib import admin
+
from .models import Redirect
diff --git a/readthedocs/redirects/managers.py b/readthedocs/redirects/managers.py
index 9c0f1bf47fa..af18ebae64f 100644
--- a/readthedocs/redirects/managers.py
+++ b/readthedocs/redirects/managers.py
@@ -1,6 +1,5 @@
"""Manager and queryset for the redirects app."""
-from __future__ import absolute_import
from django.db.models import Manager
from django.db.models.query import QuerySet
diff --git a/readthedocs/redirects/models.py b/readthedocs/redirects/models.py
index f8eddb16cc4..a4e9b72f731 100644
--- a/readthedocs/redirects/models.py
+++ b/readthedocs/redirects/models.py
@@ -1,16 +1,16 @@
"""Django models for the redirects app."""
-from __future__ import absolute_import
-from builtins import object
+import logging
+import re
+
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
-import logging
-import re
from readthedocs.core.resolver import resolve_path
from readthedocs.projects.models import Project
+
from .managers import RedirectManager
diff --git a/readthedocs/redirects/utils.py b/readthedocs/redirects/utils.py
index 1edc628626a..0817d99b1bd 100644
--- a/readthedocs/redirects/utils.py
+++ b/readthedocs/redirects/utils.py
@@ -7,11 +7,11 @@
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
import logging
import re
+from django.http import HttpResponseRedirect
+
from readthedocs.constants import LANGUAGES_REGEX
from readthedocs.projects.models import Project
diff --git a/readthedocs/restapi/client.py b/readthedocs/restapi/client.py
index 83f5b861d83..a2f9943c6da 100644
--- a/readthedocs/restapi/client.py
+++ b/readthedocs/restapi/client.py
@@ -2,13 +2,6 @@
"""Simple client to access our API with Slumber credentials."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import logging
import requests
@@ -18,6 +11,7 @@
from rest_framework.renderers import JSONRenderer
from slumber import API, serialize
+
log = logging.getLogger(__name__)
PRODUCTION_DOMAIN = getattr(settings, 'PRODUCTION_DOMAIN', 'readthedocs.org')
diff --git a/readthedocs/restapi/permissions.py b/readthedocs/restapi/permissions.py
index a9d7aa8d92f..4645cca618d 100644
--- a/readthedocs/restapi/permissions.py
+++ b/readthedocs/restapi/permissions.py
@@ -1,7 +1,5 @@
"""Defines access permissions for the API."""
-from __future__ import absolute_import
-
from rest_framework import permissions
from readthedocs.core.permissions import AdminPermission
diff --git a/readthedocs/restapi/serializers.py b/readthedocs/restapi/serializers.py
index ba6a88ddc90..4529bcdafbd 100644
--- a/readthedocs/restapi/serializers.py
+++ b/readthedocs/restapi/serializers.py
@@ -1,15 +1,11 @@
"""Defines serializers for each of our models."""
-from __future__ import absolute_import
-
-from builtins import object
-
from allauth.socialaccount.models import SocialAccount
from rest_framework import serializers
from readthedocs.builds.models import Build, BuildCommandResult, Version
from readthedocs.oauth.models import RemoteOrganization, RemoteRepository
-from readthedocs.projects.models import Project, Domain
+from readthedocs.projects.models import Domain, Project
class ProjectSerializer(serializers.ModelSerializer):
diff --git a/readthedocs/restapi/signals.py b/readthedocs/restapi/signals.py
index 6b6d0b3955f..6968cb5342e 100644
--- a/readthedocs/restapi/signals.py
+++ b/readthedocs/restapi/signals.py
@@ -1,9 +1,8 @@
"""We define custom Django signals to trigger when a footer is rendered."""
-from __future__ import absolute_import
-
import django.dispatch
+
footer_response = django.dispatch.Signal(
providing_args=["request", "context", "response_data"]
)
diff --git a/readthedocs/restapi/urls.py b/readthedocs/restapi/urls.py
index c8cdf6cd21e..50767355fee 100644
--- a/readthedocs/restapi/urls.py
+++ b/readthedocs/restapi/urls.py
@@ -2,13 +2,6 @@
"""Define routes between URL paths and views/endpoints."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
from django.conf import settings
from django.conf.urls import include, url
from rest_framework import routers
@@ -35,6 +28,7 @@
VersionViewSet,
)
+
router = routers.DefaultRouter()
router.register(r'build', BuildViewSet, basename='build')
router.register(r'command', BuildCommandViewSet, basename='buildcommandresult')
diff --git a/readthedocs/restapi/utils.py b/readthedocs/restapi/utils.py
index 8637cd1779b..19a325be54c 100644
--- a/readthedocs/restapi/utils.py
+++ b/readthedocs/restapi/utils.py
@@ -1,13 +1,6 @@
# -*- coding: utf-8 -*-
"""Utility functions that are used by both views and celery tasks."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import hashlib
import logging
@@ -25,6 +18,7 @@
from readthedocs.builds.models import Version
from readthedocs.search.indexes import PageIndex, ProjectIndex, SectionIndex
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/restapi/views/core_views.py b/readthedocs/restapi/views/core_views.py
index 08fc9e7d764..e6f2879b2eb 100644
--- a/readthedocs/restapi/views/core_views.py
+++ b/readthedocs/restapi/views/core_views.py
@@ -1,17 +1,14 @@
"""Utility endpoints relating to canonical urls, embedded content, etc."""
-from __future__ import absolute_import
-
+from django.shortcuts import get_object_or_404
from rest_framework import decorators, permissions, status
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
-from django.shortcuts import get_object_or_404
-
from readthedocs.builds.constants import LATEST
from readthedocs.builds.models import Version
-from readthedocs.projects.models import Project
from readthedocs.core.templatetags.core_tags import make_document_url
+from readthedocs.projects.models import Project
@decorators.api_view(['GET'])
diff --git a/readthedocs/restapi/views/footer_views.py b/readthedocs/restapi/views/footer_views.py
index ad5ec7028b6..2f0bcae0e61 100644
--- a/readthedocs/restapi/views/footer_views.py
+++ b/readthedocs/restapi/views/footer_views.py
@@ -1,10 +1,6 @@
# -*- coding: utf-8 -*-
"""Endpoint to generate footer HTML."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
-import six
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.template import loader as template_loader
@@ -17,7 +13,9 @@
from readthedocs.builds.models import Version
from readthedocs.projects.models import Project
from readthedocs.projects.version_handling import (
- highest_version, parse_version_failsafe)
+ highest_version,
+ parse_version_failsafe,
+)
from readthedocs.restapi.signals import footer_response
diff --git a/readthedocs/restapi/views/integrations.py b/readthedocs/restapi/views/integrations.py
index 4950518408b..b8320c3525c 100644
--- a/readthedocs/restapi/views/integrations.py
+++ b/readthedocs/restapi/views/integrations.py
@@ -1,18 +1,10 @@
# -*- coding: utf-8 -*-
"""Endpoints integrating with Github, Bitbucket, and other webhooks."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import json
import logging
import re
-import six
from django.shortcuts import get_object_or_404
from rest_framework import permissions, status
from rest_framework.exceptions import NotFound, ParseError
@@ -30,6 +22,7 @@
from readthedocs.integrations.utils import normalize_request_payload
from readthedocs.projects.models import Project
+
log = logging.getLogger(__name__)
GITHUB_EVENT_HEADER = 'HTTP_X_GITHUB_EVENT'
diff --git a/readthedocs/restapi/views/model_views.py b/readthedocs/restapi/views/model_views.py
index 3e925031472..4e021d5e281 100644
--- a/readthedocs/restapi/views/model_views.py
+++ b/readthedocs/restapi/views/model_views.py
@@ -1,13 +1,9 @@
# -*- coding: utf-8 -*-
"""Endpoints for listing Projects, Versions, Builds, etc."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
from allauth.socialaccount.models import SocialAccount
-from builtins import str
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
from rest_framework import decorators, permissions, status, viewsets
@@ -25,12 +21,25 @@
from .. import utils as api_utils
from ..permissions import (
- APIPermission, APIRestrictedPermission, IsOwner, RelatedProjectIsOwner)
+ APIPermission,
+ APIRestrictedPermission,
+ IsOwner,
+ RelatedProjectIsOwner,
+)
from ..serializers import (
- BuildAdminSerializer, BuildCommandSerializer, BuildSerializer,
- DomainSerializer, ProjectAdminSerializer, ProjectSerializer,
- RemoteOrganizationSerializer, RemoteRepositorySerializer,
- SocialAccountSerializer, VersionAdminSerializer, VersionSerializer)
+ BuildAdminSerializer,
+ BuildCommandSerializer,
+ BuildSerializer,
+ DomainSerializer,
+ ProjectAdminSerializer,
+ ProjectSerializer,
+ RemoteOrganizationSerializer,
+ RemoteRepositorySerializer,
+ SocialAccountSerializer,
+ VersionAdminSerializer,
+ VersionSerializer,
+)
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/restapi/views/search_views.py b/readthedocs/restapi/views/search_views.py
index abe36174097..771a3069b59 100644
--- a/readthedocs/restapi/views/search_views.py
+++ b/readthedocs/restapi/views/search_views.py
@@ -1,6 +1,5 @@
"""Endpoints related to searching through projects, sections, etc."""
-from __future__ import absolute_import
import logging
from rest_framework import decorators, permissions, status
@@ -10,8 +9,8 @@
from readthedocs.builds.constants import LATEST
from readthedocs.builds.models import Version
from readthedocs.projects.models import Project, ProjectRelationship
-from readthedocs.search.lib import search_file, search_project, search_section
from readthedocs.restapi import utils
+from readthedocs.search.lib import search_file, search_project, search_section
log = logging.getLogger(__name__)
diff --git a/readthedocs/restapi/views/task_views.py b/readthedocs/restapi/views/task_views.py
index 475fb17bda6..0213aeb754a 100644
--- a/readthedocs/restapi/views/task_views.py
+++ b/readthedocs/restapi/views/task_views.py
@@ -1,12 +1,5 @@
"""Endpoints relating to task/job status, etc."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import logging
from django.urls import reverse
@@ -18,6 +11,7 @@
from readthedocs.core.utils.tasks import TaskNoPermission, get_public_task_data
from readthedocs.oauth import tasks
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/rtd_tests/base.py b/readthedocs/rtd_tests/base.py
index c6813f22f8f..b5ab431150a 100644
--- a/readthedocs/rtd_tests/base.py
+++ b/readthedocs/rtd_tests/base.py
@@ -1,19 +1,16 @@
"""Base classes and mixins for unit tests."""
-from __future__ import absolute_import
-from builtins import object
+import logging
import os
import shutil
-import logging
import tempfile
from collections import OrderedDict
-from mock import patch
from django.conf import settings
-from django.test import TestCase, RequestFactory
from django.contrib.auth.models import AnonymousUser
from django.contrib.messages.storage.fallback import FallbackStorage
from django.contrib.sessions.middleware import SessionMiddleware
-import six
+from django.test import RequestFactory, TestCase
+from mock import patch
log = logging.getLogger(__name__)
diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py
index b448eab9ab7..7bdfdbe06dc 100644
--- a/readthedocs/rtd_tests/files/conf.py
+++ b/readthedocs/rtd_tests/files/conf.py
@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
-from __future__ import division, print_function, unicode_literals
-
from datetime import datetime
from recommonmark.parser import CommonMarkParser
diff --git a/readthedocs/rtd_tests/mocks/environment.py b/readthedocs/rtd_tests/mocks/environment.py
index 3b2a28f49bd..a5aa1b8fe21 100644
--- a/readthedocs/rtd_tests/mocks/environment.py
+++ b/readthedocs/rtd_tests/mocks/environment.py
@@ -1,9 +1,6 @@
# pylint: disable=missing-docstring
-from __future__ import absolute_import
-from builtins import object
import mock
-
class EnvironmentMockGroup:
"""Mock out necessary environment pieces"""
diff --git a/readthedocs/rtd_tests/mocks/mock_api.py b/readthedocs/rtd_tests/mocks/mock_api.py
index 68d1e544357..3dd54d6635a 100644
--- a/readthedocs/rtd_tests/mocks/mock_api.py
+++ b/readthedocs/rtd_tests/mocks/mock_api.py
@@ -1,8 +1,7 @@
"""Mock versions of many API-related classes."""
-from __future__ import absolute_import
-from builtins import object
-from contextlib import contextmanager
import json
+from contextlib import contextmanager
+
import mock
# Mock tastypi API.
diff --git a/readthedocs/rtd_tests/mocks/paths.py b/readthedocs/rtd_tests/mocks/paths.py
index 34fa7e5953f..ee8b29b7a26 100644
--- a/readthedocs/rtd_tests/mocks/paths.py
+++ b/readthedocs/rtd_tests/mocks/paths.py
@@ -1,9 +1,8 @@
"""Context managers to patch os.path.exists calls."""
-from __future__ import absolute_import
import os
import re
-import mock
+import mock
def fake_paths(check):
"""
diff --git a/readthedocs/rtd_tests/tests/projects/test_admin_actions.py b/readthedocs/rtd_tests/tests/projects/test_admin_actions.py
index 54111314cee..acdc99df38f 100644
--- a/readthedocs/rtd_tests/tests/projects/test_admin_actions.py
+++ b/readthedocs/rtd_tests/tests/projects/test_admin_actions.py
@@ -1,14 +1,13 @@
-import mock
import django_dynamic_fixture as fixture
+import mock
+from django import urls
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
from django.contrib.auth.models import User
-from django import urls
from django.test import TestCase
from readthedocs.core.models import UserProfile
from readthedocs.projects.models import Project
-
class ProjectAdminActionsTest(TestCase):
@classmethod
diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py
index af63a2378e7..44f2a037418 100644
--- a/readthedocs/rtd_tests/tests/test_api.py
+++ b/readthedocs/rtd_tests/tests/test_api.py
@@ -1,22 +1,14 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import base64
import datetime
import json
import mock
from allauth.socialaccount.models import SocialAccount
-from builtins import str
from django.contrib.auth.models import User
-from django.urls import reverse
from django.http import QueryDict
from django.test import TestCase
+from django.urls import reverse
from django.utils import six
from django_dynamic_fixture import get
from rest_framework import status
diff --git a/readthedocs/rtd_tests/tests/test_api_permissions.py b/readthedocs/rtd_tests/tests/test_api_permissions.py
index d3666927add..40d348c6ccf 100644
--- a/readthedocs/rtd_tests/tests/test_api_permissions.py
+++ b/readthedocs/rtd_tests/tests/test_api_permissions.py
@@ -1,10 +1,9 @@
-from __future__ import absolute_import
from functools import partial
-from mock import Mock
from unittest import TestCase
-from readthedocs.restapi.permissions import APIRestrictedPermission
+from mock import Mock
+from readthedocs.restapi.permissions import APIRestrictedPermission
class APIRestrictedPermissionTests(TestCase):
def get_request(self, method, is_admin):
diff --git a/readthedocs/rtd_tests/tests/test_api_version_compare.py b/readthedocs/rtd_tests/tests/test_api_version_compare.py
index 0daec18a086..df4df452cff 100644
--- a/readthedocs/rtd_tests/tests/test_api_version_compare.py
+++ b/readthedocs/rtd_tests/tests/test_api_version_compare.py
@@ -1,11 +1,9 @@
-from __future__ import absolute_import
from django.test import TestCase
from readthedocs.builds.constants import LATEST
from readthedocs.projects.models import Project
from readthedocs.restapi.views.footer_views import get_version_compare_data
-
class VersionCompareTests(TestCase):
fixtures = ['eric.json', 'test_data.json']
diff --git a/readthedocs/rtd_tests/tests/test_backend.py b/readthedocs/rtd_tests/tests/test_backend.py
index 34510778fea..76eb07d371c 100644
--- a/readthedocs/rtd_tests/tests/test_backend.py
+++ b/readthedocs/rtd_tests/tests/test_backend.py
@@ -1,19 +1,11 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import os
from os.path import exists
from tempfile import mkdtemp
import django_dynamic_fixture as fixture
import pytest
-import six
from django.contrib.auth.models import User
from mock import Mock, patch
@@ -30,7 +22,6 @@
make_test_hg,
)
-
class TestGitBackend(RTDTestCase):
def setUp(self):
git_repo = make_test_git()
diff --git a/readthedocs/rtd_tests/tests/test_backend_svn.py b/readthedocs/rtd_tests/tests/test_backend_svn.py
index 8de9ea776a0..e3cf9e2e217 100644
--- a/readthedocs/rtd_tests/tests/test_backend_svn.py
+++ b/readthedocs/rtd_tests/tests/test_backend_svn.py
@@ -1,19 +1,12 @@
# -*- coding: utf-8 -*-
"""Tests For SVN"""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
-from mock import patch
from django_dynamic_fixture import get
+from mock import patch
-from readthedocs.rtd_tests.base import RTDTestCase
-from readthedocs.projects.models import Project
from readthedocs.builds.models import Version
+from readthedocs.projects.models import Project
+from readthedocs.rtd_tests.base import RTDTestCase
from readthedocs.vcs_support.backends.svn import Backend as SvnBackend
class TestSvnBackend(RTDTestCase):
diff --git a/readthedocs/rtd_tests/tests/test_build_config.py b/readthedocs/rtd_tests/tests/test_build_config.py
index 9085d6f7926..90a28ce25f6 100644
--- a/readthedocs/rtd_tests/tests/test_build_config.py
+++ b/readthedocs/rtd_tests/tests/test_build_config.py
@@ -1,13 +1,11 @@
-from __future__ import division, print_function, unicode_literals
-
from os import path
import pytest
-import six
import yamale
-from readthedocs.config.tests import utils
from yamale.validators import DefaultValidators, Validator
+from readthedocs.config.tests import utils
+
V2_SCHEMA = path.join(
path.dirname(__file__),
'../fixtures/spec/v2/schema.yml'
diff --git a/readthedocs/rtd_tests/tests/test_build_forms.py b/readthedocs/rtd_tests/tests/test_build_forms.py
index b0ba6890d09..d69ca289481 100644
--- a/readthedocs/rtd_tests/tests/test_build_forms.py
+++ b/readthedocs/rtd_tests/tests/test_build_forms.py
@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
-from __future__ import division, print_function, unicode_literals
-
from django.test import TestCase
from django_dynamic_fixture import get
@@ -10,7 +8,6 @@
from readthedocs.projects.constants import PRIVATE
from readthedocs.projects.models import Project
-
class TestVersionForm(TestCase):
def setUp(self):
diff --git a/readthedocs/rtd_tests/tests/test_build_notifications.py b/readthedocs/rtd_tests/tests/test_build_notifications.py
index 9460589463a..b276d1d935e 100644
--- a/readthedocs/rtd_tests/tests/test_build_notifications.py
+++ b/readthedocs/rtd_tests/tests/test_build_notifications.py
@@ -1,19 +1,15 @@
# -*- coding: utf-8 -*-
"""Notifications sent after build is completed."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import django_dynamic_fixture as fixture
from django.core import mail
from django.test import TestCase
from mock import patch
from readthedocs.builds.models import Build, Version
-from readthedocs.projects.models import Project, EmailHook, WebHook
-from readthedocs.projects.tasks import send_notifications
from readthedocs.projects.forms import WebHookForm
-
+from readthedocs.projects.models import EmailHook, Project, WebHook
+from readthedocs.projects.tasks import send_notifications
class BuildNotificationsTests(TestCase):
def setUp(self):
diff --git a/readthedocs/rtd_tests/tests/test_builds.py b/readthedocs/rtd_tests/tests/test_builds.py
index 53e222203fe..6f8808bc1ad 100644
--- a/readthedocs/rtd_tests/tests/test_builds.py
+++ b/readthedocs/rtd_tests/tests/test_builds.py
@@ -1,12 +1,6 @@
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
+import os
import mock
-import os
from django.test import TestCase
from django_dynamic_fixture import fixture, get
@@ -14,13 +8,12 @@
from readthedocs.doc_builder.config import load_yaml_config
from readthedocs.doc_builder.environments import LocalBuildEnvironment
from readthedocs.doc_builder.python_environments import Virtualenv
-from readthedocs.projects.models import Project, EnvironmentVariable
+from readthedocs.projects.models import EnvironmentVariable, Project
from readthedocs.projects.tasks import UpdateDocsTaskStep
from readthedocs.rtd_tests.tests.test_config_integration import create_load
from ..mocks.environment import EnvironmentMockGroup
-
class BuildEnvironmentTests(TestCase):
def setUp(self):
diff --git a/readthedocs/rtd_tests/tests/test_celery.py b/readthedocs/rtd_tests/tests/test_celery.py
index e718e220b36..9191250e8be 100644
--- a/readthedocs/rtd_tests/tests/test_celery.py
+++ b/readthedocs/rtd_tests/tests/test_celery.py
@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import division, print_function, unicode_literals
-
import os
import shutil
from os.path import exists
@@ -8,20 +6,21 @@
from django.contrib.auth.models import User
from django_dynamic_fixture import get
-from mock import patch, MagicMock
+from mock import MagicMock, patch
from readthedocs.builds.constants import LATEST
-from readthedocs.projects.exceptions import RepositoryError
from readthedocs.builds.models import Build
-from readthedocs.projects.models import Project
from readthedocs.projects import tasks
-
-from readthedocs.rtd_tests.utils import (
- create_git_branch, create_git_tag, delete_git_branch)
-from readthedocs.rtd_tests.utils import make_test_git
+from readthedocs.projects.exceptions import RepositoryError
+from readthedocs.projects.models import Project
from readthedocs.rtd_tests.base import RTDTestCase
from readthedocs.rtd_tests.mocks.mock_api import mock_api
-
+from readthedocs.rtd_tests.utils import (
+ create_git_branch,
+ create_git_tag,
+ delete_git_branch,
+ make_test_git,
+)
class TestCeleryBuilding(RTDTestCase):
diff --git a/readthedocs/rtd_tests/tests/test_config_integration.py b/readthedocs/rtd_tests/tests/test_config_integration.py
index 8474c4bea11..e0b93243c6a 100644
--- a/readthedocs/rtd_tests/tests/test_config_integration.py
+++ b/readthedocs/rtd_tests/tests/test_config_integration.py
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import tempfile
from os import path
@@ -22,7 +19,6 @@
from readthedocs.projects.models import Feature, Project
from readthedocs.rtd_tests.utils import create_git_submodule, make_git_repo
-
def create_load(config=None):
"""
Mock out the function of the build load function.
diff --git a/readthedocs/rtd_tests/tests/test_core_tags.py b/readthedocs/rtd_tests/tests/test_core_tags.py
index 851fd40d1b6..20dd966ad4a 100644
--- a/readthedocs/rtd_tests/tests/test_core_tags.py
+++ b/readthedocs/rtd_tests/tests/test_core_tags.py
@@ -1,16 +1,13 @@
# -*- coding: utf-8 -*-
-from __future__ import absolute_import
import mock
import pytest
-
from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
-from readthedocs.projects.models import Project
from readthedocs.builds.constants import LATEST
from readthedocs.core.templatetags import core_tags
-
+from readthedocs.projects.models import Project
@override_settings(USE_SUBDOMAIN=False, PRODUCTION_DOMAIN='readthedocs.org')
class CoreTagsTests(TestCase):
diff --git a/readthedocs/rtd_tests/tests/test_core_utils.py b/readthedocs/rtd_tests/tests/test_core_utils.py
index 85e7d735507..e480f6398ad 100644
--- a/readthedocs/rtd_tests/tests/test_core_utils.py
+++ b/readthedocs/rtd_tests/tests/test_core_utils.py
@@ -1,16 +1,13 @@
# -*- coding: utf-8 -*-
"""Test core util functions"""
-from __future__ import absolute_import
import mock
-
-from django_dynamic_fixture import get
from django.test import TestCase
+from django_dynamic_fixture import get
-from readthedocs.projects.models import Project
from readthedocs.builds.models import Version
-from readthedocs.core.utils import trigger_build, slugify
-
+from readthedocs.core.utils import slugify, trigger_build
+from readthedocs.projects.models import Project
class CoreUtilTests(TestCase):
diff --git a/readthedocs/rtd_tests/tests/test_doc_builder.py b/readthedocs/rtd_tests/tests/test_doc_builder.py
index a384f5d00b7..383237d26cb 100644
--- a/readthedocs/rtd_tests/tests/test_doc_builder.py
+++ b/readthedocs/rtd_tests/tests/test_doc_builder.py
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import os
import tempfile
from collections import namedtuple
@@ -22,7 +19,6 @@
from readthedocs.projects.exceptions import ProjectConfigurationError
from readthedocs.projects.models import Feature, Project
-
class SphinxBuilderTest(TestCase):
fixtures = ['test_data']
diff --git a/readthedocs/rtd_tests/tests/test_doc_building.py b/readthedocs/rtd_tests/tests/test_doc_building.py
index 1b8f11c3880..1a1dbb579a1 100644
--- a/readthedocs/rtd_tests/tests/test_doc_building.py
+++ b/readthedocs/rtd_tests/tests/test_doc_building.py
@@ -5,13 +5,6 @@
* raw subprocess calls like .communicate expects bytes
* the Command wrappers encapsulate the bytes and expose unicode
"""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import json
import os
import re
@@ -20,7 +13,6 @@
import mock
import pytest
-from builtins import str
from django.test import TestCase
from django_dynamic_fixture import get
from docker.errors import APIError as DockerAPIError
@@ -43,7 +35,6 @@
from readthedocs.rtd_tests.mocks.paths import fake_paths_lookup
from readthedocs.rtd_tests.tests.test_config_integration import create_load
-
DUMMY_BUILD_ID = 123
SAMPLE_UNICODE = 'HérÉ îß sömê ünïçó∂é'
SAMPLE_UTF8_BYTES = SAMPLE_UNICODE.encode('utf-8')
diff --git a/readthedocs/rtd_tests/tests/test_doc_serving.py b/readthedocs/rtd_tests/tests/test_doc_serving.py
index 1236472010a..8d8e458197d 100644
--- a/readthedocs/rtd_tests/tests/test_doc_serving.py
+++ b/readthedocs/rtd_tests/tests/test_doc_serving.py
@@ -1,18 +1,15 @@
-from __future__ import absolute_import
-import mock
import django_dynamic_fixture as fixture
-
+import mock
+from django.conf import settings
from django.contrib.auth.models import User
+from django.http import Http404
from django.test import TestCase
from django.test.utils import override_settings
-from django.http import Http404
-from django.conf import settings
-from readthedocs.rtd_tests.base import RequestFactoryTestMixin
+from readthedocs.core.views.serve import _serve_symlink_docs
from readthedocs.projects import constants
from readthedocs.projects.models import Project
-from readthedocs.core.views.serve import _serve_symlink_docs
-
+from readthedocs.rtd_tests.base import RequestFactoryTestMixin
@override_settings(
USE_SUBDOMAIN=False, PUBLIC_DOMAIN='public.readthedocs.org', DEBUG=False
diff --git a/readthedocs/rtd_tests/tests/test_domains.py b/readthedocs/rtd_tests/tests/test_domains.py
index 5e487ce2c94..963b07a91f1 100644
--- a/readthedocs/rtd_tests/tests/test_domains.py
+++ b/readthedocs/rtd_tests/tests/test_domains.py
@@ -1,19 +1,16 @@
# -*- coding: utf-8 -*-
-from __future__ import absolute_import
import json
from django.core.cache import cache
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
-
from django_dynamic_fixture import get
from readthedocs.core.middleware import SubdomainMiddleware
-from readthedocs.projects.models import Project, Domain
from readthedocs.projects.forms import DomainForm
-
+from readthedocs.projects.models import Domain, Project
class MiddlewareTests(TestCase):
diff --git a/readthedocs/rtd_tests/tests/test_extend.py b/readthedocs/rtd_tests/tests/test_extend.py
index d3c66552b6a..21017e3088d 100644
--- a/readthedocs/rtd_tests/tests/test_extend.py
+++ b/readthedocs/rtd_tests/tests/test_extend.py
@@ -1,10 +1,9 @@
-from __future__ import absolute_import
-from builtins import object
from django.test import TestCase, override_settings
-from readthedocs.core.utils.extend import (SettingsOverrideObject,
- get_override_class)
-
+from readthedocs.core.utils.extend import (
+ SettingsOverrideObject,
+ get_override_class,
+)
# Top level to ensure module name is correct
class FooBase:
diff --git a/readthedocs/rtd_tests/tests/test_footer.py b/readthedocs/rtd_tests/tests/test_footer.py
index ec5ca4d754a..692e85cd121 100644
--- a/readthedocs/rtd_tests/tests/test_footer.py
+++ b/readthedocs/rtd_tests/tests/test_footer.py
@@ -1,11 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import mock
from django.test import TestCase
from rest_framework.test import APIRequestFactory, APITestCase
@@ -20,7 +13,6 @@
)
from readthedocs.rtd_tests.mocks.paths import fake_paths_by_regex
-
class Testmaker(APITestCase):
fixtures = ['test_data']
url = '/api/v2/footer_html/?project=pip&version=latest&page=index&docroot=/'
diff --git a/readthedocs/rtd_tests/tests/test_gold.py b/readthedocs/rtd_tests/tests/test_gold.py
index 63f50f8d696..9475f4fd110 100644
--- a/readthedocs/rtd_tests/tests/test_gold.py
+++ b/readthedocs/rtd_tests/tests/test_gold.py
@@ -1,15 +1,11 @@
-from __future__ import absolute_import
-
-from django.urls import reverse
from django.test import TestCase
+from django.urls import reverse
+from django_dynamic_fixture import fixture, get
-from django_dynamic_fixture import get, fixture
-
-from readthedocs.gold.models import GoldUser, LEVEL_CHOICES
+from readthedocs.gold.models import LEVEL_CHOICES, GoldUser
from readthedocs.projects.models import Project
from readthedocs.rtd_tests.utils import create_user
-
class GoldViewTests(TestCase):
def setUp(self):
diff --git a/readthedocs/rtd_tests/tests/test_imported_file.py b/readthedocs/rtd_tests/tests/test_imported_file.py
index 81f5e2a684b..d005fc9a182 100644
--- a/readthedocs/rtd_tests/tests/test_imported_file.py
+++ b/readthedocs/rtd_tests/tests/test_imported_file.py
@@ -1,9 +1,9 @@
-from __future__ import absolute_import
import os
+
from django.test import TestCase
+from readthedocs.projects.models import ImportedFile, Project
from readthedocs.projects.tasks import _manage_imported_files
-from readthedocs.projects.models import Project, ImportedFile
base_dir = os.path.dirname(os.path.dirname(__file__))
diff --git a/readthedocs/rtd_tests/tests/test_integrations.py b/readthedocs/rtd_tests/tests/test_integrations.py
index 17f9b291253..360fdb0f1ed 100644
--- a/readthedocs/rtd_tests/tests/test_integrations.py
+++ b/readthedocs/rtd_tests/tests/test_integrations.py
@@ -1,19 +1,16 @@
-from __future__ import absolute_import
-
-from builtins import range
import django_dynamic_fixture as fixture
-from django.test import TestCase, RequestFactory
from django.contrib.contenttypes.models import ContentType
-from rest_framework.test import APIClient
-from rest_framework.test import APIRequestFactory
+from django.test import RequestFactory, TestCase
from rest_framework.response import Response
+from rest_framework.test import APIClient, APIRequestFactory
from readthedocs.integrations.models import (
- HttpExchange, Integration, GitHubWebhook
+ GitHubWebhook,
+ HttpExchange,
+ Integration,
)
from readthedocs.projects.models import Project
-
class HttpExchangeTests(TestCase):
"""Test HttpExchange model by using existing views
diff --git a/readthedocs/rtd_tests/tests/test_middleware.py b/readthedocs/rtd_tests/tests/test_middleware.py
index 0bfe9b56304..f7e5d607624 100644
--- a/readthedocs/rtd_tests/tests/test_middleware.py
+++ b/readthedocs/rtd_tests/tests/test_middleware.py
@@ -1,26 +1,20 @@
# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-
-from django.http import Http404
+from corsheaders.middleware import CorsMiddleware
from django.conf import settings
from django.core.cache import cache
-from django.urls.base import get_urlconf, set_urlconf
+from django.http import Http404
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
-
+from django.urls.base import get_urlconf, set_urlconf
from django_dynamic_fixture import get
-
-from corsheaders.middleware import CorsMiddleware
from mock import patch
from readthedocs.core.middleware import SubdomainMiddleware
-from readthedocs.projects.models import Project, ProjectRelationship, Domain
-
+from readthedocs.projects.models import Domain, Project, ProjectRelationship
from readthedocs.rtd_tests.utils import create_user
-
@override_settings(USE_SUBDOMAIN=True)
class MiddlewareTests(TestCase):
diff --git a/readthedocs/rtd_tests/tests/test_notifications.py b/readthedocs/rtd_tests/tests/test_notifications.py
index adae07e49fe..ddc8c9ada56 100644
--- a/readthedocs/rtd_tests/tests/test_notifications.py
+++ b/readthedocs/rtd_tests/tests/test_notifications.py
@@ -1,19 +1,21 @@
# -*- coding: utf-8 -*-
"""Notification tests"""
-from __future__ import absolute_import
-import mock
import django_dynamic_fixture as fixture
+import mock
+from django.contrib.auth.models import AnonymousUser, User
from django.test import TestCase
from django.test.utils import override_settings
-from django.contrib.auth.models import User, AnonymousUser
from messages_extends.models import Message as PersistentMessage
+from readthedocs.builds.models import Build
from readthedocs.notifications import Notification, SiteNotification
from readthedocs.notifications.backends import EmailBackend, SiteBackend
-from readthedocs.notifications.constants import ERROR, INFO_NON_PERSISTENT, WARNING_NON_PERSISTENT
-from readthedocs.builds.models import Build
-
+from readthedocs.notifications.constants import (
+ ERROR,
+ INFO_NON_PERSISTENT,
+ WARNING_NON_PERSISTENT,
+)
@override_settings(
NOTIFICATION_BACKENDS=[
diff --git a/readthedocs/rtd_tests/tests/test_oauth.py b/readthedocs/rtd_tests/tests/test_oauth.py
index fedbaf0d47d..45c2983bb15 100644
--- a/readthedocs/rtd_tests/tests/test_oauth.py
+++ b/readthedocs/rtd_tests/tests/test_oauth.py
@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import mock
from django.conf import settings
from django.contrib.auth.models import User
@@ -10,11 +7,13 @@
from readthedocs.oauth.models import RemoteOrganization, RemoteRepository
from readthedocs.oauth.services import (
- BitbucketService, GitHubService, GitLabService)
+ BitbucketService,
+ GitHubService,
+ GitLabService,
+)
from readthedocs.projects import constants
from readthedocs.projects.models import Project
-
class GitHubOAuthTests(TestCase):
fixtures = ['eric', 'test_data']
diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
index 86297dbe4b4..97eceae19a8 100644
--- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
+++ b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
import json
import logging
@@ -9,8 +7,7 @@
from future.backports.urllib.parse import urlencode
from readthedocs.builds.models import Version
-from readthedocs.projects.models import Project, Feature
-
+from readthedocs.projects.models import Feature, Project
log = logging.getLogger(__name__)
diff --git a/readthedocs/rtd_tests/tests/test_privacy.py b/readthedocs/rtd_tests/tests/test_privacy.py
index 8a7359c9055..7844ece2e5e 100644
--- a/readthedocs/rtd_tests/tests/test_privacy.py
+++ b/readthedocs/rtd_tests/tests/test_privacy.py
@@ -1,17 +1,16 @@
-from __future__ import absolute_import
-import logging
import json
-import mock
+import logging
+import mock
+from django.contrib.auth.models import User
from django.test import TestCase
from django.test.utils import override_settings
-from django.contrib.auth.models import User
from readthedocs.builds.constants import LATEST
-from readthedocs.builds.models import Version, Build
-from readthedocs.projects.models import Project
-from readthedocs.projects.forms import UpdateProjectForm
+from readthedocs.builds.models import Build, Version
from readthedocs.projects import tasks
+from readthedocs.projects.forms import UpdateProjectForm
+from readthedocs.projects.models import Project
log = logging.getLogger(__name__)
diff --git a/readthedocs/rtd_tests/tests/test_privacy_urls.py b/readthedocs/rtd_tests/tests/test_privacy_urls.py
index f793f8c51ab..ba71d66bf70 100644
--- a/readthedocs/rtd_tests/tests/test_privacy_urls.py
+++ b/readthedocs/rtd_tests/tests/test_privacy_urls.py
@@ -1,24 +1,20 @@
-from __future__ import absolute_import
-from __future__ import print_function
import re
+import mock
from allauth.socialaccount.models import SocialAccount
-from builtins import object
from django.contrib.admindocs.views import extract_views_from_urlpatterns
from django.test import TestCase
from django.urls import reverse
from django_dynamic_fixture import get
-import mock
from taggit.models import Tag
from readthedocs.builds.models import Build, BuildCommandResult
from readthedocs.core.utils.tasks import TaskNoPermission
from readthedocs.integrations.models import HttpExchange, Integration
-from readthedocs.projects.models import Project, Domain
-from readthedocs.oauth.models import RemoteRepository, RemoteOrganization
+from readthedocs.oauth.models import RemoteOrganization, RemoteRepository
+from readthedocs.projects.models import Domain, Project
from readthedocs.rtd_tests.utils import create_user
-
class URLAccessMixin:
default_kwargs = {}
diff --git a/readthedocs/rtd_tests/tests/test_profile_views.py b/readthedocs/rtd_tests/tests/test_profile_views.py
index 9e3a75a449e..239f093beae 100644
--- a/readthedocs/rtd_tests/tests/test_profile_views.py
+++ b/readthedocs/rtd_tests/tests/test_profile_views.py
@@ -1,11 +1,8 @@
-from __future__ import division, print_function, unicode_literals
-
from django.contrib.auth.models import User
-from django.urls import reverse
from django.test import TestCase
+from django.urls import reverse
from django_dynamic_fixture import get
-
class ProfileViewsTest(TestCase):
def setUp(self):
diff --git a/readthedocs/rtd_tests/tests/test_project.py b/readthedocs/rtd_tests/tests/test_project.py
index bbbd43a11a3..b6bee71d091 100644
--- a/readthedocs/rtd_tests/tests/test_project.py
+++ b/readthedocs/rtd_tests/tests/test_project.py
@@ -1,27 +1,27 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import datetime
import json
from django.contrib.auth.models import User
from django.forms.models import model_to_dict
from django.test import TestCase
-from django_dynamic_fixture import get
from django.utils import timezone
+from django_dynamic_fixture import get
from mock import patch
from rest_framework.reverse import reverse
from readthedocs.builds.constants import (
- BUILD_STATE_CLONING, BUILD_STATE_FINISHED, BUILD_STATE_TRIGGERED, LATEST)
+ BUILD_STATE_CLONING,
+ BUILD_STATE_FINISHED,
+ BUILD_STATE_TRIGGERED,
+ LATEST,
+)
from readthedocs.builds.models import Build
from readthedocs.projects.exceptions import ProjectConfigurationError
from readthedocs.projects.models import Project
from readthedocs.projects.tasks import finish_inactive_builds
from readthedocs.rtd_tests.mocks.paths import fake_paths_by_regex
-
class ProjectMixin:
fixtures = ['eric', 'test_data']
diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py
index e7fefb5bbbf..bc423b8d4d6 100644
--- a/readthedocs/rtd_tests/tests/test_project_forms.py
+++ b/readthedocs/rtd_tests/tests/test_project_forms.py
@@ -1,12 +1,5 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import mock
from django.contrib.auth.models import User
from django.test import TestCase
@@ -33,7 +26,6 @@
)
from readthedocs.projects.models import Project
-
class TestProjectForms(TestCase):
@mock.patch.object(ClassifierValidator, '__call__')
diff --git a/readthedocs/rtd_tests/tests/test_project_querysets.py b/readthedocs/rtd_tests/tests/test_project_querysets.py
index 682e54cd9ea..a9193e47ead 100644
--- a/readthedocs/rtd_tests/tests/test_project_querysets.py
+++ b/readthedocs/rtd_tests/tests/test_project_querysets.py
@@ -1,14 +1,13 @@
-from __future__ import absolute_import
-
from datetime import timedelta
import django_dynamic_fixture as fixture
from django.test import TestCase
-from readthedocs.projects.models import Project, ProjectRelationship, Feature
-from readthedocs.projects.querysets import (ParentRelatedProjectQuerySet,
- ChildRelatedProjectQuerySet)
-
+from readthedocs.projects.models import Feature, Project, ProjectRelationship
+from readthedocs.projects.querysets import (
+ ChildRelatedProjectQuerySet,
+ ParentRelatedProjectQuerySet,
+)
class ProjectQuerySetTests(TestCase):
diff --git a/readthedocs/rtd_tests/tests/test_project_symlinks.py b/readthedocs/rtd_tests/tests/test_project_symlinks.py
index e35ca38743d..bd9cb76ef73 100644
--- a/readthedocs/rtd_tests/tests/test_project_symlinks.py
+++ b/readthedocs/rtd_tests/tests/test_project_symlinks.py
@@ -1,22 +1,23 @@
# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-from builtins import object
import os
import shutil
import tempfile
import mock
from django.conf import settings
-from django.urls import reverse
from django.test import TestCase, override_settings
+from django.urls import reverse
from django_dynamic_fixture import get
from readthedocs.builds.models import Version
-from readthedocs.projects.models import Project, Domain
-from readthedocs.projects.tasks import broadcast_remove_orphan_symlinks, remove_orphan_symlinks, symlink_project
-from readthedocs.core.symlink import PublicSymlink, PrivateSymlink
-
+from readthedocs.core.symlink import PrivateSymlink, PublicSymlink
+from readthedocs.projects.models import Domain, Project
+from readthedocs.projects.tasks import (
+ broadcast_remove_orphan_symlinks,
+ remove_orphan_symlinks,
+ symlink_project,
+)
def get_filesystem(path, top_level_path=None):
"""Recurse into path, return dictionary mapping of path and files
diff --git a/readthedocs/rtd_tests/tests/test_project_views.py b/readthedocs/rtd_tests/tests/test_project_views.py
index 124fd018c00..ecf3c8bd6ed 100644
--- a/readthedocs/rtd_tests/tests/test_project_views.py
+++ b/readthedocs/rtd_tests/tests/test_project_views.py
@@ -1,29 +1,27 @@
-from __future__ import absolute_import
from datetime import timedelta
-
-from mock import patch
-from django.test import TestCase
from django.contrib.auth.models import User
from django.contrib.messages import constants as message_const
-from django.urls import reverse
from django.http.response import HttpResponseRedirect
-from django.views.generic.base import ContextMixin
+from django.test import TestCase
+from django.urls import reverse
from django.utils import timezone
+from django.views.generic.base import ContextMixin
from django_dynamic_fixture import get, new
-
-import six
+from mock import patch
from readthedocs.builds.models import Build, Version
-from readthedocs.rtd_tests.base import (WizardTestCase, MockBuildTestCase,
- RequestFactoryTestMixin)
from readthedocs.oauth.models import RemoteRepository
+from readthedocs.projects import tasks
from readthedocs.projects.exceptions import ProjectSpamError
-from readthedocs.projects.models import Project, Domain
-from readthedocs.projects.views.private import ImportWizardView
+from readthedocs.projects.models import Domain, Project
from readthedocs.projects.views.mixins import ProjectRelationMixin
-from readthedocs.projects import tasks
-
+from readthedocs.projects.views.private import ImportWizardView
+from readthedocs.rtd_tests.base import (
+ MockBuildTestCase,
+ RequestFactoryTestMixin,
+ WizardTestCase,
+)
@patch('readthedocs.projects.views.private.trigger_build', lambda x: None)
class TestProfileMiddleware(RequestFactoryTestMixin, TestCase):
diff --git a/readthedocs/rtd_tests/tests/test_redirects.py b/readthedocs/rtd_tests/tests/test_redirects.py
index 335c8feba86..d53d55125b8 100644
--- a/readthedocs/rtd_tests/tests/test_redirects.py
+++ b/readthedocs/rtd_tests/tests/test_redirects.py
@@ -1,10 +1,9 @@
-from __future__ import absolute_import
+import logging
+
from django.http import Http404
from django.test import TestCase
from django.test.utils import override_settings
-
-from django_dynamic_fixture import get
-from django_dynamic_fixture import fixture
+from django_dynamic_fixture import fixture, get
from mock import patch
from readthedocs.builds.constants import LATEST
@@ -12,9 +11,6 @@
from readthedocs.projects.models import Project
from readthedocs.redirects.models import Redirect
-import logging
-
-
@override_settings(PUBLIC_DOMAIN='readthedocs.org', USE_SUBDOMAIN=False, APPEND_SLASH=False)
class RedirectTests(TestCase):
fixtures = ["eric", "test_data"]
diff --git a/readthedocs/rtd_tests/tests/test_repo_parsing.py b/readthedocs/rtd_tests/tests/test_repo_parsing.py
index f946db61e53..d77c51ecb2d 100644
--- a/readthedocs/rtd_tests/tests/test_repo_parsing.py
+++ b/readthedocs/rtd_tests/tests/test_repo_parsing.py
@@ -1,12 +1,8 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
from django.test import TestCase
from readthedocs.projects.models import Project
-
class TestRepoParsing(TestCase):
fixtures = ['eric', 'test_data']
diff --git a/readthedocs/rtd_tests/tests/test_resolver.py b/readthedocs/rtd_tests/tests/test_resolver.py
index 841da05c1b6..929ccc8dbee 100644
--- a/readthedocs/rtd_tests/tests/test_resolver.py
+++ b/readthedocs/rtd_tests/tests/test_resolver.py
@@ -1,19 +1,18 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import django_dynamic_fixture as fixture
import mock
from django.test import TestCase, override_settings
from readthedocs.core.resolver import (
- Resolver, resolve, resolve_domain, resolve_path
+ Resolver,
+ resolve,
+ resolve_domain,
+ resolve_path,
)
from readthedocs.projects.constants import PRIVATE
from readthedocs.projects.models import Domain, Project, ProjectRelationship
from readthedocs.rtd_tests.utils import create_user
-
@override_settings(PUBLIC_DOMAIN='readthedocs.org')
class ResolverBase(TestCase):
diff --git a/readthedocs/rtd_tests/tests/test_restapi_client.py b/readthedocs/rtd_tests/tests/test_restapi_client.py
index 88906fdc10f..1aeca51ee54 100644
--- a/readthedocs/rtd_tests/tests/test_restapi_client.py
+++ b/readthedocs/rtd_tests/tests/test_restapi_client.py
@@ -1,12 +1,8 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import, unicode_literals)
-
from django.test import TestCase
from readthedocs.restapi.client import DrfJsonSerializer
-
class TestDrfJsonSerializer(TestCase):
data = {
'proper': 'json'
diff --git a/readthedocs/rtd_tests/tests/test_search_json_parsing.py b/readthedocs/rtd_tests/tests/test_search_json_parsing.py
index fb91d31b276..b51ea834f1e 100644
--- a/readthedocs/rtd_tests/tests/test_search_json_parsing.py
+++ b/readthedocs/rtd_tests/tests/test_search_json_parsing.py
@@ -1,4 +1,3 @@
-from __future__ import absolute_import
import os
from django.test import TestCase
diff --git a/readthedocs/rtd_tests/tests/test_single_version.py b/readthedocs/rtd_tests/tests/test_single_version.py
index faee5f585f7..de1bc9300cd 100644
--- a/readthedocs/rtd_tests/tests/test_single_version.py
+++ b/readthedocs/rtd_tests/tests/test_single_version.py
@@ -1,12 +1,9 @@
-from __future__ import absolute_import
+import django_dynamic_fixture as fixture
from django.test import TestCase
from django.test.utils import override_settings
-import django_dynamic_fixture as fixture
-
from readthedocs.projects.models import Project
-
@override_settings(
USE_SUBDOMAIN=True, PUBLIC_DOMAIN='public.readthedocs.org', SERVE_PUBLIC_DOCS=True
)
diff --git a/readthedocs/rtd_tests/tests/test_subprojects.py b/readthedocs/rtd_tests/tests/test_subprojects.py
index 081bd89a0bb..542b2d8b02d 100644
--- a/readthedocs/rtd_tests/tests/test_subprojects.py
+++ b/readthedocs/rtd_tests/tests/test_subprojects.py
@@ -1,7 +1,5 @@
-from __future__ import absolute_import
-
-import mock
import django_dynamic_fixture as fixture
+import mock
from django.contrib.auth.models import User
from django.test import TestCase
from django.test.utils import override_settings
@@ -10,7 +8,6 @@
from readthedocs.projects.models import Project, ProjectRelationship
from readthedocs.rtd_tests.utils import create_user
-
class SubprojectFormTests(TestCase):
def test_empty_child(self):
diff --git a/readthedocs/rtd_tests/tests/test_sync_versions.py b/readthedocs/rtd_tests/tests/test_sync_versions.py
index 533ad424729..731cc0675bc 100644
--- a/readthedocs/rtd_tests/tests/test_sync_versions.py
+++ b/readthedocs/rtd_tests/tests/test_sync_versions.py
@@ -1,23 +1,15 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals
-)
-
import json
+import pytest
from django.test import TestCase
from django.urls import reverse
-import pytest
from readthedocs.builds.constants import BRANCH, STABLE, TAG
from readthedocs.builds.models import Version
from readthedocs.projects.models import Project
-
class TestSyncVersions(TestCase):
fixtures = ['eric', 'test_data']
diff --git a/readthedocs/rtd_tests/tests/test_urls.py b/readthedocs/rtd_tests/tests/test_urls.py
index cb4204e2870..55a4a840065 100644
--- a/readthedocs/rtd_tests/tests/test_urls.py
+++ b/readthedocs/rtd_tests/tests/test_urls.py
@@ -1,8 +1,5 @@
-from __future__ import absolute_import
-
-from django.urls import reverse, NoReverseMatch
from django.test import TestCase
-
+from django.urls import NoReverseMatch, reverse
class WipeUrlTests(TestCase):
diff --git a/readthedocs/rtd_tests/tests/test_version_commit_name.py b/readthedocs/rtd_tests/tests/test_version_commit_name.py
index 943843c22f8..23875955f00 100644
--- a/readthedocs/rtd_tests/tests/test_version_commit_name.py
+++ b/readthedocs/rtd_tests/tests/test_version_commit_name.py
@@ -1,18 +1,11 @@
-from __future__ import absolute_import
from django.test import TestCase
-from django_dynamic_fixture import get
-from django_dynamic_fixture import new
+from django_dynamic_fixture import get, new
-from readthedocs.builds.constants import BRANCH
-from readthedocs.builds.constants import LATEST
-from readthedocs.builds.constants import STABLE
-from readthedocs.builds.constants import TAG
+from readthedocs.builds.constants import BRANCH, LATEST, STABLE, TAG
from readthedocs.builds.models import Version
-from readthedocs.projects.constants import REPO_TYPE_GIT
-from readthedocs.projects.constants import REPO_TYPE_HG
+from readthedocs.projects.constants import REPO_TYPE_GIT, REPO_TYPE_HG
from readthedocs.projects.models import Project
-
class VersionCommitNameTests(TestCase):
def test_branch_name_unicode_non_ascii(self):
unicode_name = b'abc_\xd1\x84_\xe2\x99\x98'.decode('utf-8')
diff --git a/readthedocs/rtd_tests/tests/test_version_config.py b/readthedocs/rtd_tests/tests/test_version_config.py
index 82286ade4bf..4fd8d085454 100644
--- a/readthedocs/rtd_tests/tests/test_version_config.py
+++ b/readthedocs/rtd_tests/tests/test_version_config.py
@@ -1,12 +1,9 @@
-from __future__ import division, print_function, unicode_literals
-
from django.test import TestCase
from django_dynamic_fixture import get
from readthedocs.builds.models import Build, Version
from readthedocs.projects.models import Project
-
class VersionConfigTests(TestCase):
def setUp(self):
diff --git a/readthedocs/rtd_tests/tests/test_version_slug.py b/readthedocs/rtd_tests/tests/test_version_slug.py
index 31cf2ca72fc..44d8500f251 100644
--- a/readthedocs/rtd_tests/tests/test_version_slug.py
+++ b/readthedocs/rtd_tests/tests/test_version_slug.py
@@ -1,13 +1,11 @@
-from __future__ import absolute_import
import re
+
from django.test import TestCase
from readthedocs.builds.models import Version
-from readthedocs.builds.version_slug import VersionSlugField
-from readthedocs.builds.version_slug import VERSION_SLUG_REGEX
+from readthedocs.builds.version_slug import VERSION_SLUG_REGEX, VersionSlugField
from readthedocs.projects.models import Project
-
class VersionSlugPatternTests(TestCase):
pattern = re.compile('^{pattern}$'.format(pattern=VERSION_SLUG_REGEX))
diff --git a/readthedocs/rtd_tests/tests/test_views.py b/readthedocs/rtd_tests/tests/test_views.py
index e68989e1483..dd64a438a8e 100644
--- a/readthedocs/rtd_tests/tests/test_views.py
+++ b/readthedocs/rtd_tests/tests/test_views.py
@@ -1,15 +1,8 @@
# -*- coding: utf-8 -*-
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import mock
from django.contrib.auth.models import User
-from django.urls import reverse
from django.test import TestCase
+from django.urls import reverse
from django.utils.six.moves.urllib.parse import urlsplit
from django_dynamic_fixture import get, new
diff --git a/readthedocs/rtd_tests/utils.py b/readthedocs/rtd_tests/utils.py
index 1908ef0ccd5..a5768a19d7d 100644
--- a/readthedocs/rtd_tests/utils.py
+++ b/readthedocs/rtd_tests/utils.py
@@ -1,13 +1,6 @@
# -*- coding: utf-8 -*-
"""Utility functions for use in tests."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import logging
import subprocess
import textwrap
diff --git a/readthedocs/search/indexes.py b/readthedocs/search/indexes.py
index 189f441d84e..65fc1464330 100644
--- a/readthedocs/search/indexes.py
+++ b/readthedocs/search/indexes.py
@@ -14,16 +14,11 @@
TODO: Handle page removal case in Page.
"""
-from __future__ import absolute_import
-from builtins import object
-
+from django.conf import settings
from django.utils import timezone
-
from elasticsearch import Elasticsearch, exceptions
from elasticsearch.helpers import bulk_index
-from django.conf import settings
-
class Index:
diff --git a/readthedocs/search/lib.py b/readthedocs/search/lib.py
index 8500a829b03..b5ebf8754ca 100644
--- a/readthedocs/search/lib.py
+++ b/readthedocs/search/lib.py
@@ -1,17 +1,17 @@
"""Utilities related to searching Elastic."""
-from __future__ import absolute_import
-from __future__ import print_function
from pprint import pprint
from django.conf import settings
-from .indexes import PageIndex, ProjectIndex, SectionIndex
-
from readthedocs.builds.constants import LATEST
from readthedocs.projects.models import Project
-from readthedocs.search.signals import (before_project_search,
- before_file_search,
- before_section_search)
+from readthedocs.search.signals import (
+ before_file_search,
+ before_project_search,
+ before_section_search,
+)
+
+from .indexes import PageIndex, ProjectIndex, SectionIndex
def search_project(request, query, language=None):
diff --git a/readthedocs/search/parse_json.py b/readthedocs/search/parse_json.py
index baa4abe5eca..4ad53beeff6 100644
--- a/readthedocs/search/parse_json.py
+++ b/readthedocs/search/parse_json.py
@@ -1,17 +1,15 @@
# -*- coding: utf-8 -*-
"""Functions related to converting content into dict/JSON structures."""
-from __future__ import absolute_import
-
-import logging
import codecs
import fnmatch
import json
+import logging
import os
-from builtins import next, range # pylint: disable=redefined-builtin
from pyquery import PyQuery
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/search/signals.py b/readthedocs/search/signals.py
index 6abdf64cce9..7f599c35678 100644
--- a/readthedocs/search/signals.py
+++ b/readthedocs/search/signals.py
@@ -1,7 +1,7 @@
"""We define custom Django signals to trigger before executing searches."""
-from __future__ import absolute_import
import django.dispatch
+
before_project_search = django.dispatch.Signal(providing_args=["body"])
before_file_search = django.dispatch.Signal(providing_args=["body"])
before_section_search = django.dispatch.Signal(providing_args=["body"])
diff --git a/readthedocs/search/tests/conftest.py b/readthedocs/search/tests/conftest.py
index 59961f3a7e2..ab46332f7a2 100644
--- a/readthedocs/search/tests/conftest.py
+++ b/readthedocs/search/tests/conftest.py
@@ -6,9 +6,14 @@
from django_dynamic_fixture import G
from readthedocs.projects.models import Project
-from readthedocs.search.indexes import Index, ProjectIndex, PageIndex, SectionIndex
-from .dummy_data import DUMMY_PAGE_JSON, ALL_PROJECTS
-
+from readthedocs.search.indexes import (
+ Index,
+ PageIndex,
+ ProjectIndex,
+ SectionIndex,
+)
+
+from .dummy_data import ALL_PROJECTS, DUMMY_PAGE_JSON
@pytest.fixture(autouse=True)
def mock_elastic_index(mocker):
diff --git a/readthedocs/search/tests/test_views.py b/readthedocs/search/tests/test_views.py
index d5ac18bafc9..37072e94948 100644
--- a/readthedocs/search/tests/test_views.py
+++ b/readthedocs/search/tests/test_views.py
@@ -11,7 +11,6 @@
from readthedocs.projects.models import Project
from readthedocs.search.tests.utils import get_search_query_from_project_file
-
@pytest.mark.django_db
@pytest.mark.search
class TestElasticSearch:
diff --git a/readthedocs/search/tests/utils.py b/readthedocs/search/tests/utils.py
index a48ea83dd74..90ce5f70566 100644
--- a/readthedocs/search/tests/utils.py
+++ b/readthedocs/search/tests/utils.py
@@ -1,6 +1,5 @@
from readthedocs.search.tests.dummy_data import DUMMY_PAGE_JSON
-
def get_search_query_from_project_file(project_slug, page_num=0, data_type='title'):
"""Return search query from the project's page file.
Query is generated from the value of `data_type`
diff --git a/readthedocs/search/utils.py b/readthedocs/search/utils.py
index ec429e4ace9..8e795a569bd 100644
--- a/readthedocs/search/utils.py
+++ b/readthedocs/search/utils.py
@@ -1,16 +1,13 @@
# -*- coding: utf-8 -*-
"""Utilities related to reading and generating indexable search content."""
-from __future__ import absolute_import
-
-import os
-import fnmatch
-import re
import codecs
-import logging
+import fnmatch
import json
+import logging
+import os
+import re
-from builtins import next, range
from pyquery import PyQuery
diff --git a/readthedocs/search/views.py b/readthedocs/search/views.py
index e1725f87fdc..200dbadd7f8 100644
--- a/readthedocs/search/views.py
+++ b/readthedocs/search/views.py
@@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
"""Search views."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import collections
import logging
from pprint import pprint
@@ -13,6 +10,7 @@
from readthedocs.builds.constants import LATEST
from readthedocs.search import lib as search_lib
+
log = logging.getLogger(__name__)
LOG_TEMPLATE = '(Elastic Search) [{user}:{type}] [{project}:{version}:{language}] {msg}'
diff --git a/readthedocs/urls.py b/readthedocs/urls.py
index e441bc94459..ba6e67ef338 100644
--- a/readthedocs/urls.py
+++ b/readthedocs/urls.py
@@ -1,26 +1,28 @@
# pylint: disable=missing-docstring
-from __future__ import absolute_import
-
import os
from functools import reduce
from operator import add
-from django.conf.urls import url, include
-from django.contrib import admin
from django.conf import settings
+from django.conf.urls import include, url
from django.conf.urls.static import static
-from django.views.generic.base import TemplateView, RedirectView
+from django.contrib import admin
+from django.views.generic.base import RedirectView, TemplateView
from tastypie.api import Api
-from readthedocs.api.base import (ProjectResource, UserResource,
- VersionResource, FileResource)
-from readthedocs.core.urls import docs_urls, core_urls, deprecated_urls
+from readthedocs.api.base import (
+ FileResource,
+ ProjectResource,
+ UserResource,
+ VersionResource,
+)
+from readthedocs.core.urls import core_urls, deprecated_urls, docs_urls
from readthedocs.core.views import (
HomepageView,
SupportView,
+ do_not_track,
server_error_404,
server_error_500,
- do_not_track,
)
from readthedocs.search import views as search_views
diff --git a/readthedocs/vcs_support/backends/bzr.py b/readthedocs/vcs_support/backends/bzr.py
index 9a309a8077a..9c97870408c 100644
--- a/readthedocs/vcs_support/backends/bzr.py
+++ b/readthedocs/vcs_support/backends/bzr.py
@@ -1,19 +1,9 @@
# -*- coding: utf-8 -*-
"""Bazaar-related utilities."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import csv
import re
-from builtins import str # pylint: disable=redefined-builtin
-from six import StringIO
-
from readthedocs.projects.exceptions import RepositoryError
from readthedocs.vcs_support.base import BaseVCS, VCSVersion
diff --git a/readthedocs/vcs_support/backends/git.py b/readthedocs/vcs_support/backends/git.py
index 80fafb89e9d..5d1b4eaa8dd 100644
--- a/readthedocs/vcs_support/backends/git.py
+++ b/readthedocs/vcs_support/backends/git.py
@@ -1,19 +1,11 @@
# -*- coding: utf-8 -*-
"""Git-related utilities."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import logging
import os
import re
import git
-from builtins import str
from django.core.exceptions import ValidationError
from git.exc import BadName, InvalidGitRepositoryError
@@ -22,6 +14,7 @@
from readthedocs.projects.validators import validate_submodule_url
from readthedocs.vcs_support.base import BaseVCS, VCSVersion
+
log = logging.getLogger(__name__)
diff --git a/readthedocs/vcs_support/backends/hg.py b/readthedocs/vcs_support/backends/hg.py
index e65d4c650f0..7bb252322d8 100644
--- a/readthedocs/vcs_support/backends/hg.py
+++ b/readthedocs/vcs_support/backends/hg.py
@@ -1,12 +1,5 @@
# -*- coding: utf-8 -*-
"""Mercurial-related utilities."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
from readthedocs.projects.exceptions import RepositoryError
from readthedocs.vcs_support.base import BaseVCS, VCSVersion
diff --git a/readthedocs/vcs_support/backends/svn.py b/readthedocs/vcs_support/backends/svn.py
index 2363116c6fd..b4ad6130302 100644
--- a/readthedocs/vcs_support/backends/svn.py
+++ b/readthedocs/vcs_support/backends/svn.py
@@ -1,18 +1,8 @@
# -*- coding: utf-8 -*-
"""Subversion-related utilities."""
-from __future__ import (
- absolute_import,
- division,
- print_function,
- unicode_literals,
-)
-
import csv
-from builtins import str
-from six import StringIO # noqa
-
from readthedocs.projects.exceptions import RepositoryError
from readthedocs.vcs_support.base import BaseVCS, VCSVersion
diff --git a/readthedocs/vcs_support/base.py b/readthedocs/vcs_support/base.py
index f430fd0d0a5..9530cebde8c 100644
--- a/readthedocs/vcs_support/base.py
+++ b/readthedocs/vcs_support/base.py
@@ -1,13 +1,9 @@
# -*- coding: utf-8 -*-
"""Base classes for VCS backends."""
-from __future__ import (
- absolute_import, division, print_function, unicode_literals)
-
import logging
import os
import shutil
-from builtins import object
log = logging.getLogger(__name__)
diff --git a/readthedocs/vcs_support/tests.py b/readthedocs/vcs_support/tests.py
index 649f633c5f7..df9702ce7c8 100644
--- a/readthedocs/vcs_support/tests.py
+++ b/readthedocs/vcs_support/tests.py
@@ -1,10 +1,8 @@
-from __future__ import absolute_import
import os
import shutil
import unittest
import mock
-
from django.conf import settings
from readthedocs.vcs_support import utils
@@ -64,5 +62,3 @@ def test_forceacquire(self):
pass
except utils.LockTimeout:
raise AssertionError('Should have thrown LockTimeout')
-
-
diff --git a/readthedocs/vcs_support/utils.py b/readthedocs/vcs_support/utils.py
index eb5d22bb52f..56714763808 100644
--- a/readthedocs/vcs_support/utils.py
+++ b/readthedocs/vcs_support/utils.py
@@ -1,12 +1,9 @@
"""Locking utilities."""
-from __future__ import absolute_import
-
import errno
import logging
import os
import stat
import time
-from builtins import object
log = logging.getLogger(__name__)
diff --git a/readthedocs/worker.py b/readthedocs/worker.py
index 21023e9cdff..47dfdb48282 100644
--- a/readthedocs/worker.py
+++ b/readthedocs/worker.py
@@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
"""Celery worker application instantiation"""
-from __future__ import absolute_import, unicode_literals
-
import os
from celery import Celery
From c56b2893c916134b42d73e3eea94a15c6889e345 Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:03:29 +0100
Subject: [PATCH 09/42] Remove six and future from requirements
---
requirements/pip.txt | 2 --
1 file changed, 2 deletions(-)
diff --git a/requirements/pip.txt b/requirements/pip.txt
index 3490128e231..6d4ac7d2d4f 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -16,8 +16,6 @@ mkdocs==1.0.4
Markdown==3.0.1
django==1.11.16
-six==1.11.0
-future==0.17.1
django-tastypie==0.14.2
django-guardian==1.4.9
django-extensions==2.1.4
From 979f216507903b4ff4d30cf81e35fe445ed6aa68 Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:07:00 +0100
Subject: [PATCH 10/42] Manually remove usage of `future` module
---
readthedocs/__init__.py | 2 +-
readthedocs/core/signals.py | 2 +-
readthedocs/core/templatetags/core_tags.py | 2 +-
readthedocs/projects/forms.py | 2 +-
readthedocs/projects/migrations/0010_migrate_domain_data.py | 3 +--
readthedocs/projects/models.py | 2 +-
readthedocs/projects/validators.py | 2 +-
readthedocs/rtd_tests/tests/test_post_commit_hooks.py | 2 +-
8 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/readthedocs/__init__.py b/readthedocs/__init__.py
index bf6f944d7e6..1c8afb1a02b 100644
--- a/readthedocs/__init__.py
+++ b/readthedocs/__init__.py
@@ -3,7 +3,7 @@
import os.path
-from future.moves.configparser import RawConfigParser
+from configparser import RawConfigParser
def get_version(setupcfg_path):
diff --git a/readthedocs/core/signals.py b/readthedocs/core/signals.py
index 875cde381d9..f8fa5f5adec 100644
--- a/readthedocs/core/signals.py
+++ b/readthedocs/core/signals.py
@@ -3,13 +3,13 @@
"""Signal handling for core app."""
import logging
+from urllib.parse import urlparse
from corsheaders import signals
from django.conf import settings
from django.db.models import Count, Q
from django.db.models.signals import pre_delete
from django.dispatch import Signal, receiver
-from future.backports.urllib.parse import urlparse
from rest_framework.permissions import SAFE_METHODS
from readthedocs.oauth.models import RemoteOrganization
diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py
index d05831aade9..e1b9e6f7903 100644
--- a/readthedocs/core/templatetags/core_tags.py
+++ b/readthedocs/core/templatetags/core_tags.py
@@ -1,12 +1,12 @@
"""Template tags for core app."""
import hashlib
+from urllib.parse import urlparse
from django import template
from django.conf import settings
from django.utils.encoding import force_bytes, force_text
from django.utils.safestring import mark_safe
-from future.backports.urllib.parse import urlencode
from readthedocs import __version__
from readthedocs.core.resolver import resolve
diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py
index a929213885e..05f3afb7eac 100644
--- a/readthedocs/projects/forms.py
+++ b/readthedocs/projects/forms.py
@@ -2,6 +2,7 @@
"""Project forms."""
from random import choice
+from urllib.parse import urlparse
from django import forms
from django.conf import settings
@@ -9,7 +10,6 @@
from django.template.loader import render_to_string
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
-from future.backports.urllib.parse import urlparse
from guardian.shortcuts import assign
from textclassifier.validators import ClassifierValidator
diff --git a/readthedocs/projects/migrations/0010_migrate_domain_data.py b/readthedocs/projects/migrations/0010_migrate_domain_data.py
index 636f200bfe7..2d18756d6d0 100644
--- a/readthedocs/projects/migrations/0010_migrate_domain_data.py
+++ b/readthedocs/projects/migrations/0010_migrate_domain_data.py
@@ -2,8 +2,7 @@
from __future__ import (absolute_import, print_function, unicode_literals)
from django.db import models, migrations
-from future.backports.urllib.parse import urlparse
-
+from urllib.parse import urlparse
import readthedocs.projects.validators
diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py
index 24bda8f18e9..a54f15d87b0 100644
--- a/readthedocs/projects/models.py
+++ b/readthedocs/projects/models.py
@@ -4,6 +4,7 @@
import fnmatch
import logging
import os
+from urllib.parse import urlparse
from django.conf import settings
from django.contrib.auth.models import User
@@ -12,7 +13,6 @@
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django_extensions.db.models import TimeStampedModel
-from future.backports.urllib.parse import urlparse # noqa
from guardian.shortcuts import assign
from taggit.managers import TaggableManager
diff --git a/readthedocs/projects/validators.py b/readthedocs/projects/validators.py
index a362692c43d..d0f78d0ba02 100644
--- a/readthedocs/projects/validators.py
+++ b/readthedocs/projects/validators.py
@@ -1,13 +1,13 @@
"""Validators for projects app."""
import re
+from urllib.parse import urlparse
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.utils.deconstruct import deconstructible
from django.utils.translation import ugettext_lazy as _
-from future.backports.urllib.parse import urlparse
domain_regex = (
diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
index 97eceae19a8..6b4a6db070b 100644
--- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
+++ b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
@@ -4,7 +4,7 @@
import mock
from django.test import TestCase
from django_dynamic_fixture import get
-from future.backports.urllib.parse import urlencode
+from urllib.parse import urlparse
from readthedocs.builds.models import Version
from readthedocs.projects.models import Feature, Project
From f124be55434d87832ba5d50a59f6d158d3e547d2 Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:10:21 +0100
Subject: [PATCH 11/42] Remove skipif
---
readthedocs/config/tests/test_find.py | 17 -----------------
readthedocs/rtd_tests/tests/test_backend.py | 2 --
2 files changed, 19 deletions(-)
diff --git a/readthedocs/config/tests/test_find.py b/readthedocs/config/tests/test_find.py
index dd4715f20d9..048a6c9b90c 100644
--- a/readthedocs/config/tests/test_find.py
+++ b/readthedocs/config/tests/test_find.py
@@ -1,7 +1,3 @@
-import os
-
-import pytest
-
from readthedocs.config.find import find_one
from .utils import apply_fs
@@ -17,16 +13,3 @@ def test_find_at_root(tmpdir):
base = str(tmpdir)
path = find_one(base, r'readthedocs\.yml')
assert path == os.path.abspath(os.path.join(base, 'readthedocs.yml'))
-
-
-@pytest.mark.skipif(not six.PY2, reason='Only for python2')
-def test_find_unicode_path(tmpdir):
- base_path = os.path.abspath(
- os.path.join(os.path.dirname(__file__), 'fixtures/bad_encode_project')
- )
- path = find_one(base_path, r'readthedocs\.yml')
- assert path == ''
- unicode_base_path = base_path.decode('utf-8')
- assert isinstance(unicode_base_path, unicode)
- path = find_one(unicode_base_path, r'readthedocs\.yml')
- assert path == ''
diff --git a/readthedocs/rtd_tests/tests/test_backend.py b/readthedocs/rtd_tests/tests/test_backend.py
index 76eb07d371c..c47e8d4676a 100644
--- a/readthedocs/rtd_tests/tests/test_backend.py
+++ b/readthedocs/rtd_tests/tests/test_backend.py
@@ -5,7 +5,6 @@
from tempfile import mkdtemp
import django_dynamic_fixture as fixture
-import pytest
from django.contrib.auth.models import User
from mock import Mock, patch
@@ -72,7 +71,6 @@ def test_git_branches(self, checkout_path):
{branch.verbose_name for branch in repo.branches},
)
- @pytest.mark.skipif(six.PY2, reason='Only for python3')
@patch('readthedocs.projects.models.Project.checkout_path')
def test_git_branches_unicode(self, checkout_path):
repo_path = self.project.repo
From 05f5309d996c4f47baf69c1598e5f20aa585a029 Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:11:05 +0100
Subject: [PATCH 12/42] Remove usage of `from django.utils import six`
---
readthedocs/core/utils/__init__.py | 1 -
readthedocs/rtd_tests/tests/test_api.py | 1 -
2 files changed, 2 deletions(-)
diff --git a/readthedocs/core/utils/__init__.py b/readthedocs/core/utils/__init__.py
index 0b8134751a0..8632b1557d0 100644
--- a/readthedocs/core/utils/__init__.py
+++ b/readthedocs/core/utils/__init__.py
@@ -11,7 +11,6 @@
import re
from django.conf import settings
-from django.utils import six
from django.utils.functional import allow_lazy
from django.utils.safestring import SafeText, mark_safe
from django.utils.text import slugify as slugify_base
diff --git a/readthedocs/rtd_tests/tests/test_api.py b/readthedocs/rtd_tests/tests/test_api.py
index 44f2a037418..01693da1838 100644
--- a/readthedocs/rtd_tests/tests/test_api.py
+++ b/readthedocs/rtd_tests/tests/test_api.py
@@ -9,7 +9,6 @@
from django.http import QueryDict
from django.test import TestCase
from django.urls import reverse
-from django.utils import six
from django_dynamic_fixture import get
from rest_framework import status
from rest_framework.test import APIClient
From 685e547aa34f9809b68cbc720f9c68ba767cac5d Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:12:29 +0100
Subject: [PATCH 13/42] Use ipaddress from standard library
---
readthedocs/analytics/utils.py | 7 +-
readthedocs/analytics/vendor/__init__.py | 0
readthedocs/analytics/vendor/ipaddress.py | 2418 ---------------------
3 files changed, 1 insertion(+), 2424 deletions(-)
delete mode 100644 readthedocs/analytics/vendor/__init__.py
delete mode 100644 readthedocs/analytics/vendor/ipaddress.py
diff --git a/readthedocs/analytics/utils.py b/readthedocs/analytics/utils.py
index 52c44d5cd35..5c74cfd1eae 100644
--- a/readthedocs/analytics/utils.py
+++ b/readthedocs/analytics/utils.py
@@ -1,6 +1,7 @@
"""Utilities related to analytics"""
import hashlib
+import ipaddress
import logging
import requests
@@ -10,12 +11,6 @@
from user_agents import parse
-try:
- # Python 3.3+ only
- import ipaddress
-except ImportError:
- from .vendor import ipaddress
-
log = logging.getLogger(__name__) # noqa
diff --git a/readthedocs/analytics/vendor/__init__.py b/readthedocs/analytics/vendor/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/readthedocs/analytics/vendor/ipaddress.py b/readthedocs/analytics/vendor/ipaddress.py
deleted file mode 100644
index 8014d2ef777..00000000000
--- a/readthedocs/analytics/vendor/ipaddress.py
+++ /dev/null
@@ -1,2418 +0,0 @@
-# flake8: noqa
-# Copyright 2007 Google Inc.
-# Licensed to PSF under a Contributor Agreement.
-
-"""A fast, lightweight IPv4/IPv6 manipulation library in Python.
-
-This library is used to create/poke/manipulate IPv4 and IPv6 addresses
-and networks.
-
-"""
-
-import itertools
-import struct
-
-
-__version__ = '1.0.22'
-
-# Compatibility functions
-_compat_int_types = (int,)
-try:
- _compat_int_types = (int, long)
-except NameError:
- pass
-try:
- _compat_str = unicode
-except NameError:
- _compat_str = str
- assert bytes != str
-if b'\0'[0] == 0: # Python 3 semantics
- def _compat_bytes_to_byte_vals(byt):
- return byt
-else:
- def _compat_bytes_to_byte_vals(byt):
- return [struct.unpack(b'!B', b)[0] for b in byt]
-try:
- _compat_int_from_byte_vals = int.from_bytes
-except AttributeError:
- def _compat_int_from_byte_vals(bytvals, endianess):
- assert endianess == 'big'
- res = 0
- for bv in bytvals:
- assert isinstance(bv, _compat_int_types)
- res = (res << 8) + bv
- return res
-
-
-def _compat_to_bytes(intval, length, endianess):
- assert isinstance(intval, _compat_int_types)
- assert endianess == 'big'
- if length == 4:
- if intval < 0 or intval >= 2 ** 32:
- raise struct.error("integer out of range for 'I' format code")
- return struct.pack(b'!I', intval)
- elif length == 16:
- if intval < 0 or intval >= 2 ** 128:
- raise struct.error("integer out of range for 'QQ' format code")
- return struct.pack(b'!QQ', intval >> 64, intval & 0xffffffffffffffff)
- else:
- raise NotImplementedError()
-
-
-if hasattr(int, 'bit_length'):
- # Not int.bit_length , since that won't work in 2.7 where long exists
- def _compat_bit_length(i):
- return i.bit_length()
-else:
- def _compat_bit_length(i):
- for res in itertools.count():
- if i >> res == 0:
- return res
-
-
-def _compat_range(start, end, step=1):
- assert step > 0
- i = start
- while i < end:
- yield i
- i += step
-
-
-class _TotalOrderingMixin(object):
- __slots__ = ()
-
- # Helper that derives the other comparison operations from
- # __lt__ and __eq__
- # We avoid functools.total_ordering because it doesn't handle
- # NotImplemented correctly yet (http://bugs.python.org/issue10042)
- def __eq__(self, other):
- raise NotImplementedError
-
- def __ne__(self, other):
- equal = self.__eq__(other)
- if equal is NotImplemented:
- return NotImplemented
- return not equal
-
- def __lt__(self, other):
- raise NotImplementedError
-
- def __le__(self, other):
- less = self.__lt__(other)
- if less is NotImplemented or not less:
- return self.__eq__(other)
- return less
-
- def __gt__(self, other):
- less = self.__lt__(other)
- if less is NotImplemented:
- return NotImplemented
- equal = self.__eq__(other)
- if equal is NotImplemented:
- return NotImplemented
- return not (less or equal)
-
- def __ge__(self, other):
- less = self.__lt__(other)
- if less is NotImplemented:
- return NotImplemented
- return not less
-
-
-IPV4LENGTH = 32
-IPV6LENGTH = 128
-
-
-class AddressValueError(ValueError):
- """A Value Error related to the address."""
-
-
-class NetmaskValueError(ValueError):
- """A Value Error related to the netmask."""
-
-
-def ip_address(address):
- """Take an IP string/int and return an object of the correct type.
-
- Args:
- address: A string or integer, the IP address. Either IPv4 or
- IPv6 addresses may be supplied; integers less than 2**32 will
- be considered to be IPv4 by default.
-
- Returns:
- An IPv4Address or IPv6Address object.
-
- Raises:
- ValueError: if the *address* passed isn't either a v4 or a v6
- address
-
- """
- try:
- return IPv4Address(address)
- except (AddressValueError, NetmaskValueError):
- pass
-
- try:
- return IPv6Address(address)
- except (AddressValueError, NetmaskValueError):
- pass
-
- if isinstance(address, bytes):
- raise AddressValueError(
- '%r does not appear to be an IPv4 or IPv6 address. '
- 'Did you pass in a bytes (str in Python 2) instead of'
- ' a unicode object?' % address)
-
- raise ValueError('%r does not appear to be an IPv4 or IPv6 address' %
- address)
-
-
-def ip_network(address, strict=True):
- """Take an IP string/int and return an object of the correct type.
-
- Args:
- address: A string or integer, the IP network. Either IPv4 or
- IPv6 networks may be supplied; integers less than 2**32 will
- be considered to be IPv4 by default.
-
- Returns:
- An IPv4Network or IPv6Network object.
-
- Raises:
- ValueError: if the string passed isn't either a v4 or a v6
- address. Or if the network has host bits set.
-
- """
- try:
- return IPv4Network(address, strict)
- except (AddressValueError, NetmaskValueError):
- pass
-
- try:
- return IPv6Network(address, strict)
- except (AddressValueError, NetmaskValueError):
- pass
-
- if isinstance(address, bytes):
- raise AddressValueError(
- '%r does not appear to be an IPv4 or IPv6 network. '
- 'Did you pass in a bytes (str in Python 2) instead of'
- ' a unicode object?' % address)
-
- raise ValueError('%r does not appear to be an IPv4 or IPv6 network' %
- address)
-
-
-def ip_interface(address):
- """Take an IP string/int and return an object of the correct type.
-
- Args:
- address: A string or integer, the IP address. Either IPv4 or
- IPv6 addresses may be supplied; integers less than 2**32 will
- be considered to be IPv4 by default.
-
- Returns:
- An IPv4Interface or IPv6Interface object.
-
- Raises:
- ValueError: if the string passed isn't either a v4 or a v6
- address.
-
- Notes:
- The IPv?Interface classes describe an Address on a particular
- Network, so they're basically a combination of both the Address
- and Network classes.
-
- """
- try:
- return IPv4Interface(address)
- except (AddressValueError, NetmaskValueError):
- pass
-
- try:
- return IPv6Interface(address)
- except (AddressValueError, NetmaskValueError):
- pass
-
- raise ValueError('%r does not appear to be an IPv4 or IPv6 interface' %
- address)
-
-
-def v4_int_to_packed(address):
- """Represent an address as 4 packed bytes in network (big-endian) order.
-
- Args:
- address: An integer representation of an IPv4 IP address.
-
- Returns:
- The integer address packed as 4 bytes in network (big-endian) order.
-
- Raises:
- ValueError: If the integer is negative or too large to be an
- IPv4 IP address.
-
- """
- try:
- return _compat_to_bytes(address, 4, 'big')
- except (struct.error, OverflowError):
- raise ValueError("Address negative or too large for IPv4")
-
-
-def v6_int_to_packed(address):
- """Represent an address as 16 packed bytes in network (big-endian) order.
-
- Args:
- address: An integer representation of an IPv6 IP address.
-
- Returns:
- The integer address packed as 16 bytes in network (big-endian) order.
-
- """
- try:
- return _compat_to_bytes(address, 16, 'big')
- except (struct.error, OverflowError):
- raise ValueError("Address negative or too large for IPv6")
-
-
-def _split_optional_netmask(address):
- """Helper to split the netmask and raise AddressValueError if needed"""
- addr = _compat_str(address).split('/')
- if len(addr) > 2:
- raise AddressValueError("Only one '/' permitted in %r" % address)
- return addr
-
-
-def _find_address_range(addresses):
- """Find a sequence of sorted deduplicated IPv#Address.
-
- Args:
- addresses: a list of IPv#Address objects.
-
- Yields:
- A tuple containing the first and last IP addresses in the sequence.
-
- """
- it = iter(addresses)
- first = last = next(it)
- for ip in it:
- if ip._ip != last._ip + 1:
- yield first, last
- first = ip
- last = ip
- yield first, last
-
-
-def _count_righthand_zero_bits(number, bits):
- """Count the number of zero bits on the right hand side.
-
- Args:
- number: an integer.
- bits: maximum number of bits to count.
-
- Returns:
- The number of zero bits on the right hand side of the number.
-
- """
- if number == 0:
- return bits
- return min(bits, _compat_bit_length(~number & (number - 1)))
-
-
-def summarize_address_range(first, last):
- """Summarize a network range given the first and last IP addresses.
-
- Example:
- >>> list(summarize_address_range(IPv4Address('192.0.2.0'),
- ... IPv4Address('192.0.2.130')))
- ... #doctest: +NORMALIZE_WHITESPACE
- [IPv4Network('192.0.2.0/25'), IPv4Network('192.0.2.128/31'),
- IPv4Network('192.0.2.130/32')]
-
- Args:
- first: the first IPv4Address or IPv6Address in the range.
- last: the last IPv4Address or IPv6Address in the range.
-
- Returns:
- An iterator of the summarized IPv(4|6) network objects.
-
- Raise:
- TypeError:
- If the first and last objects are not IP addresses.
- If the first and last objects are not the same version.
- ValueError:
- If the last object is not greater than the first.
- If the version of the first address is not 4 or 6.
-
- """
- if (not (isinstance(first, _BaseAddress) and
- isinstance(last, _BaseAddress))):
- raise TypeError('first and last must be IP addresses, not networks')
- if first.version != last.version:
- raise TypeError("%s and %s are not of the same version" % (
- first, last))
- if first > last:
- raise ValueError('last IP address must be greater than first')
-
- if first.version == 4:
- ip = IPv4Network
- elif first.version == 6:
- ip = IPv6Network
- else:
- raise ValueError('unknown IP version')
-
- ip_bits = first._max_prefixlen
- first_int = first._ip
- last_int = last._ip
- while first_int <= last_int:
- nbits = min(_count_righthand_zero_bits(first_int, ip_bits),
- _compat_bit_length(last_int - first_int + 1) - 1)
- net = ip((first_int, ip_bits - nbits))
- yield net
- first_int += 1 << nbits
- if first_int - 1 == ip._ALL_ONES:
- break
-
-
-def _collapse_addresses_internal(addresses):
- """Loops through the addresses, collapsing concurrent netblocks.
-
- Example:
-
- ip1 = IPv4Network('192.0.2.0/26')
- ip2 = IPv4Network('192.0.2.64/26')
- ip3 = IPv4Network('192.0.2.128/26')
- ip4 = IPv4Network('192.0.2.192/26')
-
- _collapse_addresses_internal([ip1, ip2, ip3, ip4]) ->
- [IPv4Network('192.0.2.0/24')]
-
- This shouldn't be called directly; it is called via
- collapse_addresses([]).
-
- Args:
- addresses: A list of IPv4Network's or IPv6Network's
-
- Returns:
- A list of IPv4Network's or IPv6Network's depending on what we were
- passed.
-
- """
- # First merge
- to_merge = list(addresses)
- subnets = {}
- while to_merge:
- net = to_merge.pop()
- supernet = net.supernet()
- existing = subnets.get(supernet)
- if existing is None:
- subnets[supernet] = net
- elif existing != net:
- # Merge consecutive subnets
- del subnets[supernet]
- to_merge.append(supernet)
- # Then iterate over resulting networks, skipping subsumed subnets
- last = None
- for net in sorted(subnets.values()):
- if last is not None:
- # Since they are sorted,
- # last.network_address <= net.network_address is a given.
- if last.broadcast_address >= net.broadcast_address:
- continue
- yield net
- last = net
-
-
-def collapse_addresses(addresses):
- """Collapse a list of IP objects.
-
- Example:
- collapse_addresses([IPv4Network('192.0.2.0/25'),
- IPv4Network('192.0.2.128/25')]) ->
- [IPv4Network('192.0.2.0/24')]
-
- Args:
- addresses: An iterator of IPv4Network or IPv6Network objects.
-
- Returns:
- An iterator of the collapsed IPv(4|6)Network objects.
-
- Raises:
- TypeError: If passed a list of mixed version objects.
-
- """
- addrs = []
- ips = []
- nets = []
-
- # split IP addresses and networks
- for ip in addresses:
- if isinstance(ip, _BaseAddress):
- if ips and ips[-1]._version != ip._version:
- raise TypeError("%s and %s are not of the same version" % (
- ip, ips[-1]))
- ips.append(ip)
- elif ip._prefixlen == ip._max_prefixlen:
- if ips and ips[-1]._version != ip._version:
- raise TypeError("%s and %s are not of the same version" % (
- ip, ips[-1]))
- try:
- ips.append(ip.ip)
- except AttributeError:
- ips.append(ip.network_address)
- else:
- if nets and nets[-1]._version != ip._version:
- raise TypeError("%s and %s are not of the same version" % (
- ip, nets[-1]))
- nets.append(ip)
-
- # sort and dedup
- ips = sorted(set(ips))
-
- # find consecutive address ranges in the sorted sequence and summarize them
- if ips:
- for first, last in _find_address_range(ips):
- addrs.extend(summarize_address_range(first, last))
-
- return _collapse_addresses_internal(addrs + nets)
-
-
-def get_mixed_type_key(obj):
- """Return a key suitable for sorting between networks and addresses.
-
- Address and Network objects are not sortable by default; they're
- fundamentally different so the expression
-
- IPv4Address('192.0.2.0') <= IPv4Network('192.0.2.0/24')
-
- doesn't make any sense. There are some times however, where you may wish
- to have ipaddress sort these for you anyway. If you need to do this, you
- can use this function as the key= argument to sorted().
-
- Args:
- obj: either a Network or Address object.
- Returns:
- appropriate key.
-
- """
- if isinstance(obj, _BaseNetwork):
- return obj._get_networks_key()
- elif isinstance(obj, _BaseAddress):
- return obj._get_address_key()
- return NotImplemented
-
-
-class _IPAddressBase(_TotalOrderingMixin):
-
- """The mother class."""
-
- __slots__ = ()
-
- @property
- def exploded(self):
- """Return the longhand version of the IP address as a string."""
- return self._explode_shorthand_ip_string()
-
- @property
- def compressed(self):
- """Return the shorthand version of the IP address as a string."""
- return _compat_str(self)
-
- @property
- def reverse_pointer(self):
- """The name of the reverse DNS pointer for the IP address, e.g.:
- >>> ipaddress.ip_address("127.0.0.1").reverse_pointer
- '1.0.0.127.in-addr.arpa'
- >>> ipaddress.ip_address("2001:db8::1").reverse_pointer
- '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa'
-
- """
- return self._reverse_pointer()
-
- @property
- def version(self):
- msg = '%200s has no version specified' % (type(self),)
- raise NotImplementedError(msg)
-
- def _check_int_address(self, address):
- if address < 0:
- msg = "%d (< 0) is not permitted as an IPv%d address"
- raise AddressValueError(msg % (address, self._version))
- if address > self._ALL_ONES:
- msg = "%d (>= 2**%d) is not permitted as an IPv%d address"
- raise AddressValueError(msg % (address, self._max_prefixlen,
- self._version))
-
- def _check_packed_address(self, address, expected_len):
- address_len = len(address)
- if address_len != expected_len:
- msg = (
- '%r (len %d != %d) is not permitted as an IPv%d address. '
- 'Did you pass in a bytes (str in Python 2) instead of'
- ' a unicode object?')
- raise AddressValueError(msg % (address, address_len,
- expected_len, self._version))
-
- @classmethod
- def _ip_int_from_prefix(cls, prefixlen):
- """Turn the prefix length into a bitwise netmask
-
- Args:
- prefixlen: An integer, the prefix length.
-
- Returns:
- An integer.
-
- """
- return cls._ALL_ONES ^ (cls._ALL_ONES >> prefixlen)
-
- @classmethod
- def _prefix_from_ip_int(cls, ip_int):
- """Return prefix length from the bitwise netmask.
-
- Args:
- ip_int: An integer, the netmask in expanded bitwise format
-
- Returns:
- An integer, the prefix length.
-
- Raises:
- ValueError: If the input intermingles zeroes & ones
- """
- trailing_zeroes = _count_righthand_zero_bits(ip_int,
- cls._max_prefixlen)
- prefixlen = cls._max_prefixlen - trailing_zeroes
- leading_ones = ip_int >> trailing_zeroes
- all_ones = (1 << prefixlen) - 1
- if leading_ones != all_ones:
- byteslen = cls._max_prefixlen // 8
- details = _compat_to_bytes(ip_int, byteslen, 'big')
- msg = 'Netmask pattern %r mixes zeroes & ones'
- raise ValueError(msg % details)
- return prefixlen
-
- @classmethod
- def _report_invalid_netmask(cls, netmask_str):
- msg = '%r is not a valid netmask' % netmask_str
- raise NetmaskValueError(msg)
-
- @classmethod
- def _prefix_from_prefix_string(cls, prefixlen_str):
- """Return prefix length from a numeric string
-
- Args:
- prefixlen_str: The string to be converted
-
- Returns:
- An integer, the prefix length.
-
- Raises:
- NetmaskValueError: If the input is not a valid netmask
- """
- # int allows a leading +/- as well as surrounding whitespace,
- # so we ensure that isn't the case
- if not _BaseV4._DECIMAL_DIGITS.issuperset(prefixlen_str):
- cls._report_invalid_netmask(prefixlen_str)
- try:
- prefixlen = int(prefixlen_str)
- except ValueError:
- cls._report_invalid_netmask(prefixlen_str)
- if not (0 <= prefixlen <= cls._max_prefixlen):
- cls._report_invalid_netmask(prefixlen_str)
- return prefixlen
-
- @classmethod
- def _prefix_from_ip_string(cls, ip_str):
- """Turn a netmask/hostmask string into a prefix length
-
- Args:
- ip_str: The netmask/hostmask to be converted
-
- Returns:
- An integer, the prefix length.
-
- Raises:
- NetmaskValueError: If the input is not a valid netmask/hostmask
- """
- # Parse the netmask/hostmask like an IP address.
- try:
- ip_int = cls._ip_int_from_string(ip_str)
- except AddressValueError:
- cls._report_invalid_netmask(ip_str)
-
- # Try matching a netmask (this would be /1*0*/ as a bitwise regexp).
- # Note that the two ambiguous cases (all-ones and all-zeroes) are
- # treated as netmasks.
- try:
- return cls._prefix_from_ip_int(ip_int)
- except ValueError:
- pass
-
- # Invert the bits, and try matching a /0+1+/ hostmask instead.
- ip_int ^= cls._ALL_ONES
- try:
- return cls._prefix_from_ip_int(ip_int)
- except ValueError:
- cls._report_invalid_netmask(ip_str)
-
- def __reduce__(self):
- return self.__class__, (_compat_str(self),)
-
-
-class _BaseAddress(_IPAddressBase):
-
- """A generic IP object.
-
- This IP class contains the version independent methods which are
- used by single IP addresses.
- """
-
- __slots__ = ()
-
- def __int__(self):
- return self._ip
-
- def __eq__(self, other):
- try:
- return (self._ip == other._ip and
- self._version == other._version)
- except AttributeError:
- return NotImplemented
-
- def __lt__(self, other):
- if not isinstance(other, _IPAddressBase):
- return NotImplemented
- if not isinstance(other, _BaseAddress):
- raise TypeError('%s and %s are not of the same type' % (
- self, other))
- if self._version != other._version:
- raise TypeError('%s and %s are not of the same version' % (
- self, other))
- if self._ip != other._ip:
- return self._ip < other._ip
- return False
-
- # Shorthand for Integer addition and subtraction. This is not
- # meant to ever support addition/subtraction of addresses.
- def __add__(self, other):
- if not isinstance(other, _compat_int_types):
- return NotImplemented
- return self.__class__(int(self) + other)
-
- def __sub__(self, other):
- if not isinstance(other, _compat_int_types):
- return NotImplemented
- return self.__class__(int(self) - other)
-
- def __repr__(self):
- return '%s(%r)' % (self.__class__.__name__, _compat_str(self))
-
- def __str__(self):
- return _compat_str(self._string_from_ip_int(self._ip))
-
- def __hash__(self):
- return hash(hex(int(self._ip)))
-
- def _get_address_key(self):
- return (self._version, self)
-
- def __reduce__(self):
- return self.__class__, (self._ip,)
-
-
-class _BaseNetwork(_IPAddressBase):
-
- """A generic IP network object.
-
- This IP class contains the version independent methods which are
- used by networks.
-
- """
- def __init__(self, address):
- self._cache = {}
-
- def __repr__(self):
- return '%s(%r)' % (self.__class__.__name__, _compat_str(self))
-
- def __str__(self):
- return '%s/%d' % (self.network_address, self.prefixlen)
-
- def hosts(self):
- """Generate Iterator over usable hosts in a network.
-
- This is like __iter__ except it doesn't return the network
- or broadcast addresses.
-
- """
- network = int(self.network_address)
- broadcast = int(self.broadcast_address)
- for x in _compat_range(network + 1, broadcast):
- yield self._address_class(x)
-
- def __iter__(self):
- network = int(self.network_address)
- broadcast = int(self.broadcast_address)
- for x in _compat_range(network, broadcast + 1):
- yield self._address_class(x)
-
- def __getitem__(self, n):
- network = int(self.network_address)
- broadcast = int(self.broadcast_address)
- if n >= 0:
- if network + n > broadcast:
- raise IndexError('address out of range')
- return self._address_class(network + n)
- else:
- n += 1
- if broadcast + n < network:
- raise IndexError('address out of range')
- return self._address_class(broadcast + n)
-
- def __lt__(self, other):
- if not isinstance(other, _IPAddressBase):
- return NotImplemented
- if not isinstance(other, _BaseNetwork):
- raise TypeError('%s and %s are not of the same type' % (
- self, other))
- if self._version != other._version:
- raise TypeError('%s and %s are not of the same version' % (
- self, other))
- if self.network_address != other.network_address:
- return self.network_address < other.network_address
- if self.netmask != other.netmask:
- return self.netmask < other.netmask
- return False
-
- def __eq__(self, other):
- try:
- return (self._version == other._version and
- self.network_address == other.network_address and
- int(self.netmask) == int(other.netmask))
- except AttributeError:
- return NotImplemented
-
- def __hash__(self):
- return hash(int(self.network_address) ^ int(self.netmask))
-
- def __contains__(self, other):
- # always false if one is v4 and the other is v6.
- if self._version != other._version:
- return False
- # dealing with another network.
- if isinstance(other, _BaseNetwork):
- return False
- # dealing with another address
- else:
- # address
- return (int(self.network_address) <= int(other._ip) <=
- int(self.broadcast_address))
-
- def overlaps(self, other):
- """Tell if self is partly contained in other."""
- return self.network_address in other or (
- self.broadcast_address in other or (
- other.network_address in self or (
- other.broadcast_address in self)))
-
- @property
- def broadcast_address(self):
- x = self._cache.get('broadcast_address')
- if x is None:
- x = self._address_class(int(self.network_address) |
- int(self.hostmask))
- self._cache['broadcast_address'] = x
- return x
-
- @property
- def hostmask(self):
- x = self._cache.get('hostmask')
- if x is None:
- x = self._address_class(int(self.netmask) ^ self._ALL_ONES)
- self._cache['hostmask'] = x
- return x
-
- @property
- def with_prefixlen(self):
- return '%s/%d' % (self.network_address, self._prefixlen)
-
- @property
- def with_netmask(self):
- return '%s/%s' % (self.network_address, self.netmask)
-
- @property
- def with_hostmask(self):
- return '%s/%s' % (self.network_address, self.hostmask)
-
- @property
- def num_addresses(self):
- """Number of hosts in the current subnet."""
- return int(self.broadcast_address) - int(self.network_address) + 1
-
- @property
- def _address_class(self):
- # Returning bare address objects (rather than interfaces) allows for
- # more consistent behaviour across the network address, broadcast
- # address and individual host addresses.
- msg = '%200s has no associated address class' % (type(self),)
- raise NotImplementedError(msg)
-
- @property
- def prefixlen(self):
- return self._prefixlen
-
- def address_exclude(self, other):
- """Remove an address from a larger block.
-
- For example:
-
- addr1 = ip_network('192.0.2.0/28')
- addr2 = ip_network('192.0.2.1/32')
- list(addr1.address_exclude(addr2)) =
- [IPv4Network('192.0.2.0/32'), IPv4Network('192.0.2.2/31'),
- IPv4Network('192.0.2.4/30'), IPv4Network('192.0.2.8/29')]
-
- or IPv6:
-
- addr1 = ip_network('2001:db8::1/32')
- addr2 = ip_network('2001:db8::1/128')
- list(addr1.address_exclude(addr2)) =
- [ip_network('2001:db8::1/128'),
- ip_network('2001:db8::2/127'),
- ip_network('2001:db8::4/126'),
- ip_network('2001:db8::8/125'),
- ...
- ip_network('2001:db8:8000::/33')]
-
- Args:
- other: An IPv4Network or IPv6Network object of the same type.
-
- Returns:
- An iterator of the IPv(4|6)Network objects which is self
- minus other.
-
- Raises:
- TypeError: If self and other are of differing address
- versions, or if other is not a network object.
- ValueError: If other is not completely contained by self.
-
- """
- if not self._version == other._version:
- raise TypeError("%s and %s are not of the same version" % (
- self, other))
-
- if not isinstance(other, _BaseNetwork):
- raise TypeError("%s is not a network object" % other)
-
- if not other.subnet_of(self):
- raise ValueError('%s not contained in %s' % (other, self))
- if other == self:
- return
-
- # Make sure we're comparing the network of other.
- other = other.__class__('%s/%s' % (other.network_address,
- other.prefixlen))
-
- s1, s2 = self.subnets()
- while s1 != other and s2 != other:
- if other.subnet_of(s1):
- yield s2
- s1, s2 = s1.subnets()
- elif other.subnet_of(s2):
- yield s1
- s1, s2 = s2.subnets()
- else:
- # If we got here, there's a bug somewhere.
- raise AssertionError('Error performing exclusion: '
- 's1: %s s2: %s other: %s' %
- (s1, s2, other))
- if s1 == other:
- yield s2
- elif s2 == other:
- yield s1
- else:
- # If we got here, there's a bug somewhere.
- raise AssertionError('Error performing exclusion: '
- 's1: %s s2: %s other: %s' %
- (s1, s2, other))
-
- def compare_networks(self, other):
- """Compare two IP objects.
-
- This is only concerned about the comparison of the integer
- representation of the network addresses. This means that the
- host bits aren't considered at all in this method. If you want
- to compare host bits, you can easily enough do a
- 'HostA._ip < HostB._ip'
-
- Args:
- other: An IP object.
-
- Returns:
- If the IP versions of self and other are the same, returns:
-
- -1 if self < other:
- eg: IPv4Network('192.0.2.0/25') < IPv4Network('192.0.2.128/25')
- IPv6Network('2001:db8::1000/124') <
- IPv6Network('2001:db8::2000/124')
- 0 if self == other
- eg: IPv4Network('192.0.2.0/24') == IPv4Network('192.0.2.0/24')
- IPv6Network('2001:db8::1000/124') ==
- IPv6Network('2001:db8::1000/124')
- 1 if self > other
- eg: IPv4Network('192.0.2.128/25') > IPv4Network('192.0.2.0/25')
- IPv6Network('2001:db8::2000/124') >
- IPv6Network('2001:db8::1000/124')
-
- Raises:
- TypeError if the IP versions are different.
-
- """
- # does this need to raise a ValueError?
- if self._version != other._version:
- raise TypeError('%s and %s are not of the same type' % (
- self, other))
- # self._version == other._version below here:
- if self.network_address < other.network_address:
- return -1
- if self.network_address > other.network_address:
- return 1
- # self.network_address == other.network_address below here:
- if self.netmask < other.netmask:
- return -1
- if self.netmask > other.netmask:
- return 1
- return 0
-
- def _get_networks_key(self):
- """Network-only key function.
-
- Returns an object that identifies this address' network and
- netmask. This function is a suitable "key" argument for sorted()
- and list.sort().
-
- """
- return (self._version, self.network_address, self.netmask)
-
- def subnets(self, prefixlen_diff=1, new_prefix=None):
- """The subnets which join to make the current subnet.
-
- In the case that self contains only one IP
- (self._prefixlen == 32 for IPv4 or self._prefixlen == 128
- for IPv6), yield an iterator with just ourself.
-
- Args:
- prefixlen_diff: An integer, the amount the prefix length
- should be increased by. This should not be set if
- new_prefix is also set.
- new_prefix: The desired new prefix length. This must be a
- larger number (smaller prefix) than the existing prefix.
- This should not be set if prefixlen_diff is also set.
-
- Returns:
- An iterator of IPv(4|6) objects.
-
- Raises:
- ValueError: The prefixlen_diff is too small or too large.
- OR
- prefixlen_diff and new_prefix are both set or new_prefix
- is a smaller number than the current prefix (smaller
- number means a larger network)
-
- """
- if self._prefixlen == self._max_prefixlen:
- yield self
- return
-
- if new_prefix is not None:
- if new_prefix < self._prefixlen:
- raise ValueError('new prefix must be longer')
- if prefixlen_diff != 1:
- raise ValueError('cannot set prefixlen_diff and new_prefix')
- prefixlen_diff = new_prefix - self._prefixlen
-
- if prefixlen_diff < 0:
- raise ValueError('prefix length diff must be > 0')
- new_prefixlen = self._prefixlen + prefixlen_diff
-
- if new_prefixlen > self._max_prefixlen:
- raise ValueError(
- 'prefix length diff %d is invalid for netblock %s' % (
- new_prefixlen, self))
-
- start = int(self.network_address)
- end = int(self.broadcast_address) + 1
- step = (int(self.hostmask) + 1) >> prefixlen_diff
- for new_addr in _compat_range(start, end, step):
- current = self.__class__((new_addr, new_prefixlen))
- yield current
-
- def supernet(self, prefixlen_diff=1, new_prefix=None):
- """The supernet containing the current network.
-
- Args:
- prefixlen_diff: An integer, the amount the prefix length of
- the network should be decreased by. For example, given a
- /24 network and a prefixlen_diff of 3, a supernet with a
- /21 netmask is returned.
-
- Returns:
- An IPv4 network object.
-
- Raises:
- ValueError: If self.prefixlen - prefixlen_diff < 0. I.e., you have
- a negative prefix length.
- OR
- If prefixlen_diff and new_prefix are both set or new_prefix is a
- larger number than the current prefix (larger number means a
- smaller network)
-
- """
- if self._prefixlen == 0:
- return self
-
- if new_prefix is not None:
- if new_prefix > self._prefixlen:
- raise ValueError('new prefix must be shorter')
- if prefixlen_diff != 1:
- raise ValueError('cannot set prefixlen_diff and new_prefix')
- prefixlen_diff = self._prefixlen - new_prefix
-
- new_prefixlen = self.prefixlen - prefixlen_diff
- if new_prefixlen < 0:
- raise ValueError(
- 'current prefixlen is %d, cannot have a prefixlen_diff of %d' %
- (self.prefixlen, prefixlen_diff))
- return self.__class__((
- int(self.network_address) & (int(self.netmask) << prefixlen_diff),
- new_prefixlen))
-
- @property
- def is_multicast(self):
- """Test if the address is reserved for multicast use.
-
- Returns:
- A boolean, True if the address is a multicast address.
- See RFC 2373 2.7 for details.
-
- """
- return (self.network_address.is_multicast and
- self.broadcast_address.is_multicast)
-
- @staticmethod
- def _is_subnet_of(a, b):
- try:
- # Always false if one is v4 and the other is v6.
- if a._version != b._version:
- raise TypeError("%s and %s are not of the same version" (a, b))
- return (b.network_address <= a.network_address and
- b.broadcast_address >= a.broadcast_address)
- except AttributeError:
- raise TypeError("Unable to test subnet containment "
- "between %s and %s" % (a, b))
-
- def subnet_of(self, other):
- """Return True if this network is a subnet of other."""
- return self._is_subnet_of(self, other)
-
- def supernet_of(self, other):
- """Return True if this network is a supernet of other."""
- return self._is_subnet_of(other, self)
-
- @property
- def is_reserved(self):
- """Test if the address is otherwise IETF reserved.
-
- Returns:
- A boolean, True if the address is within one of the
- reserved IPv6 Network ranges.
-
- """
- return (self.network_address.is_reserved and
- self.broadcast_address.is_reserved)
-
- @property
- def is_link_local(self):
- """Test if the address is reserved for link-local.
-
- Returns:
- A boolean, True if the address is reserved per RFC 4291.
-
- """
- return (self.network_address.is_link_local and
- self.broadcast_address.is_link_local)
-
- @property
- def is_private(self):
- """Test if this address is allocated for private networks.
-
- Returns:
- A boolean, True if the address is reserved per
- iana-ipv4-special-registry or iana-ipv6-special-registry.
-
- """
- return (self.network_address.is_private and
- self.broadcast_address.is_private)
-
- @property
- def is_global(self):
- """Test if this address is allocated for public networks.
-
- Returns:
- A boolean, True if the address is not reserved per
- iana-ipv4-special-registry or iana-ipv6-special-registry.
-
- """
- return not self.is_private
-
- @property
- def is_unspecified(self):
- """Test if the address is unspecified.
-
- Returns:
- A boolean, True if this is the unspecified address as defined in
- RFC 2373 2.5.2.
-
- """
- return (self.network_address.is_unspecified and
- self.broadcast_address.is_unspecified)
-
- @property
- def is_loopback(self):
- """Test if the address is a loopback address.
-
- Returns:
- A boolean, True if the address is a loopback address as defined in
- RFC 2373 2.5.3.
-
- """
- return (self.network_address.is_loopback and
- self.broadcast_address.is_loopback)
-
-
-class _BaseV4(object):
-
- """Base IPv4 object.
-
- The following methods are used by IPv4 objects in both single IP
- addresses and networks.
-
- """
-
- __slots__ = ()
- _version = 4
- # Equivalent to 255.255.255.255 or 32 bits of 1's.
- _ALL_ONES = (2 ** IPV4LENGTH) - 1
- _DECIMAL_DIGITS = frozenset('0123456789')
-
- # the valid octets for host and netmasks. only useful for IPv4.
- _valid_mask_octets = frozenset([255, 254, 252, 248, 240, 224, 192, 128, 0])
-
- _max_prefixlen = IPV4LENGTH
- # There are only a handful of valid v4 netmasks, so we cache them all
- # when constructed (see _make_netmask()).
- _netmask_cache = {}
-
- def _explode_shorthand_ip_string(self):
- return _compat_str(self)
-
- @classmethod
- def _make_netmask(cls, arg):
- """Make a (netmask, prefix_len) tuple from the given argument.
-
- Argument can be:
- - an integer (the prefix length)
- - a string representing the prefix length (e.g. "24")
- - a string representing the prefix netmask (e.g. "255.255.255.0")
- """
- if arg not in cls._netmask_cache:
- if isinstance(arg, _compat_int_types):
- prefixlen = arg
- else:
- try:
- # Check for a netmask in prefix length form
- prefixlen = cls._prefix_from_prefix_string(arg)
- except NetmaskValueError:
- # Check for a netmask or hostmask in dotted-quad form.
- # This may raise NetmaskValueError.
- prefixlen = cls._prefix_from_ip_string(arg)
- netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen))
- cls._netmask_cache[arg] = netmask, prefixlen
- return cls._netmask_cache[arg]
-
- @classmethod
- def _ip_int_from_string(cls, ip_str):
- """Turn the given IP string into an integer for comparison.
-
- Args:
- ip_str: A string, the IP ip_str.
-
- Returns:
- The IP ip_str as an integer.
-
- Raises:
- AddressValueError: if ip_str isn't a valid IPv4 Address.
-
- """
- if not ip_str:
- raise AddressValueError('Address cannot be empty')
-
- octets = ip_str.split('.')
- if len(octets) != 4:
- raise AddressValueError("Expected 4 octets in %r" % ip_str)
-
- try:
- return _compat_int_from_byte_vals(
- map(cls._parse_octet, octets), 'big')
- except ValueError as exc:
- raise AddressValueError("%s in %r" % (exc, ip_str))
-
- @classmethod
- def _parse_octet(cls, octet_str):
- """Convert a decimal octet into an integer.
-
- Args:
- octet_str: A string, the number to parse.
-
- Returns:
- The octet as an integer.
-
- Raises:
- ValueError: if the octet isn't strictly a decimal from [0..255].
-
- """
- if not octet_str:
- raise ValueError("Empty octet not permitted")
- # Whitelist the characters, since int() allows a lot of bizarre stuff.
- if not cls._DECIMAL_DIGITS.issuperset(octet_str):
- msg = "Only decimal digits permitted in %r"
- raise ValueError(msg % octet_str)
- # We do the length check second, since the invalid character error
- # is likely to be more informative for the user
- if len(octet_str) > 3:
- msg = "At most 3 characters permitted in %r"
- raise ValueError(msg % octet_str)
- # Convert to integer (we know digits are legal)
- octet_int = int(octet_str, 10)
- # Any octets that look like they *might* be written in octal,
- # and which don't look exactly the same in both octal and
- # decimal are rejected as ambiguous
- if octet_int > 7 and octet_str[0] == '0':
- msg = "Ambiguous (octal/decimal) value in %r not permitted"
- raise ValueError(msg % octet_str)
- if octet_int > 255:
- raise ValueError("Octet %d (> 255) not permitted" % octet_int)
- return octet_int
-
- @classmethod
- def _string_from_ip_int(cls, ip_int):
- """Turns a 32-bit integer into dotted decimal notation.
-
- Args:
- ip_int: An integer, the IP address.
-
- Returns:
- The IP address as a string in dotted decimal notation.
-
- """
- return '.'.join(_compat_str(struct.unpack(b'!B', b)[0]
- if isinstance(b, bytes)
- else b)
- for b in _compat_to_bytes(ip_int, 4, 'big'))
-
- def _is_hostmask(self, ip_str):
- """Test if the IP string is a hostmask (rather than a netmask).
-
- Args:
- ip_str: A string, the potential hostmask.
-
- Returns:
- A boolean, True if the IP string is a hostmask.
-
- """
- bits = ip_str.split('.')
- try:
- parts = [x for x in map(int, bits) if x in self._valid_mask_octets]
- except ValueError:
- return False
- if len(parts) != len(bits):
- return False
- if parts[0] < parts[-1]:
- return True
- return False
-
- def _reverse_pointer(self):
- """Return the reverse DNS pointer name for the IPv4 address.
-
- This implements the method described in RFC1035 3.5.
-
- """
- reverse_octets = _compat_str(self).split('.')[::-1]
- return '.'.join(reverse_octets) + '.in-addr.arpa'
-
- @property
- def max_prefixlen(self):
- return self._max_prefixlen
-
- @property
- def version(self):
- return self._version
-
-
-class IPv4Address(_BaseV4, _BaseAddress):
-
- """Represent and manipulate single IPv4 Addresses."""
-
- __slots__ = ('_ip', '__weakref__')
-
- def __init__(self, address):
-
- """
- Args:
- address: A string or integer representing the IP
-
- Additionally, an integer can be passed, so
- IPv4Address('192.0.2.1') == IPv4Address(3221225985).
- or, more generally
- IPv4Address(int(IPv4Address('192.0.2.1'))) ==
- IPv4Address('192.0.2.1')
-
- Raises:
- AddressValueError: If ipaddress isn't a valid IPv4 address.
-
- """
- # Efficient constructor from integer.
- if isinstance(address, _compat_int_types):
- self._check_int_address(address)
- self._ip = address
- return
-
- # Constructing from a packed address
- if isinstance(address, bytes):
- self._check_packed_address(address, 4)
- bvs = _compat_bytes_to_byte_vals(address)
- self._ip = _compat_int_from_byte_vals(bvs, 'big')
- return
-
- # Assume input argument to be string or any object representation
- # which converts into a formatted IP string.
- addr_str = _compat_str(address)
- if '/' in addr_str:
- raise AddressValueError("Unexpected '/' in %r" % address)
- self._ip = self._ip_int_from_string(addr_str)
-
- @property
- def packed(self):
- """The binary representation of this address."""
- return v4_int_to_packed(self._ip)
-
- @property
- def is_reserved(self):
- """Test if the address is otherwise IETF reserved.
-
- Returns:
- A boolean, True if the address is within the
- reserved IPv4 Network range.
-
- """
- return self in self._constants._reserved_network
-
- @property
- def is_private(self):
- """Test if this address is allocated for private networks.
-
- Returns:
- A boolean, True if the address is reserved per
- iana-ipv4-special-registry.
-
- """
- return any(self in net for net in self._constants._private_networks)
-
- @property
- def is_global(self):
- return (
- self not in self._constants._public_network and
- not self.is_private)
-
- @property
- def is_multicast(self):
- """Test if the address is reserved for multicast use.
-
- Returns:
- A boolean, True if the address is multicast.
- See RFC 3171 for details.
-
- """
- return self in self._constants._multicast_network
-
- @property
- def is_unspecified(self):
- """Test if the address is unspecified.
-
- Returns:
- A boolean, True if this is the unspecified address as defined in
- RFC 5735 3.
-
- """
- return self == self._constants._unspecified_address
-
- @property
- def is_loopback(self):
- """Test if the address is a loopback address.
-
- Returns:
- A boolean, True if the address is a loopback per RFC 3330.
-
- """
- return self in self._constants._loopback_network
-
- @property
- def is_link_local(self):
- """Test if the address is reserved for link-local.
-
- Returns:
- A boolean, True if the address is link-local per RFC 3927.
-
- """
- return self in self._constants._linklocal_network
-
-
-class IPv4Interface(IPv4Address):
-
- def __init__(self, address):
- if isinstance(address, (bytes, _compat_int_types)):
- IPv4Address.__init__(self, address)
- self.network = IPv4Network(self._ip)
- self._prefixlen = self._max_prefixlen
- return
-
- if isinstance(address, tuple):
- IPv4Address.__init__(self, address[0])
- if len(address) > 1:
- self._prefixlen = int(address[1])
- else:
- self._prefixlen = self._max_prefixlen
-
- self.network = IPv4Network(address, strict=False)
- self.netmask = self.network.netmask
- self.hostmask = self.network.hostmask
- return
-
- addr = _split_optional_netmask(address)
- IPv4Address.__init__(self, addr[0])
-
- self.network = IPv4Network(address, strict=False)
- self._prefixlen = self.network._prefixlen
-
- self.netmask = self.network.netmask
- self.hostmask = self.network.hostmask
-
- def __str__(self):
- return '%s/%d' % (self._string_from_ip_int(self._ip),
- self.network.prefixlen)
-
- def __eq__(self, other):
- address_equal = IPv4Address.__eq__(self, other)
- if not address_equal or address_equal is NotImplemented:
- return address_equal
- try:
- return self.network == other.network
- except AttributeError:
- # An interface with an associated network is NOT the
- # same as an unassociated address. That's why the hash
- # takes the extra info into account.
- return False
-
- def __lt__(self, other):
- address_less = IPv4Address.__lt__(self, other)
- if address_less is NotImplemented:
- return NotImplemented
- try:
- return (self.network < other.network or
- self.network == other.network and address_less)
- except AttributeError:
- # We *do* allow addresses and interfaces to be sorted. The
- # unassociated address is considered less than all interfaces.
- return False
-
- def __hash__(self):
- return self._ip ^ self._prefixlen ^ int(self.network.network_address)
-
- __reduce__ = _IPAddressBase.__reduce__
-
- @property
- def ip(self):
- return IPv4Address(self._ip)
-
- @property
- def with_prefixlen(self):
- return '%s/%s' % (self._string_from_ip_int(self._ip),
- self._prefixlen)
-
- @property
- def with_netmask(self):
- return '%s/%s' % (self._string_from_ip_int(self._ip),
- self.netmask)
-
- @property
- def with_hostmask(self):
- return '%s/%s' % (self._string_from_ip_int(self._ip),
- self.hostmask)
-
-
-class IPv4Network(_BaseV4, _BaseNetwork):
-
- """This class represents and manipulates 32-bit IPv4 network + addresses..
-
- Attributes: [examples for IPv4Network('192.0.2.0/27')]
- .network_address: IPv4Address('192.0.2.0')
- .hostmask: IPv4Address('0.0.0.31')
- .broadcast_address: IPv4Address('192.0.2.32')
- .netmask: IPv4Address('255.255.255.224')
- .prefixlen: 27
-
- """
- # Class to use when creating address objects
- _address_class = IPv4Address
-
- def __init__(self, address, strict=True):
-
- """Instantiate a new IPv4 network object.
-
- Args:
- address: A string or integer representing the IP [& network].
- '192.0.2.0/24'
- '192.0.2.0/255.255.255.0'
- '192.0.0.2/0.0.0.255'
- are all functionally the same in IPv4. Similarly,
- '192.0.2.1'
- '192.0.2.1/255.255.255.255'
- '192.0.2.1/32'
- are also functionally equivalent. That is to say, failing to
- provide a subnetmask will create an object with a mask of /32.
-
- If the mask (portion after the / in the argument) is given in
- dotted quad form, it is treated as a netmask if it starts with a
- non-zero field (e.g. /255.0.0.0 == /8) and as a hostmask if it
- starts with a zero field (e.g. 0.255.255.255 == /8), with the
- single exception of an all-zero mask which is treated as a
- netmask == /0. If no mask is given, a default of /32 is used.
-
- Additionally, an integer can be passed, so
- IPv4Network('192.0.2.1') == IPv4Network(3221225985)
- or, more generally
- IPv4Interface(int(IPv4Interface('192.0.2.1'))) ==
- IPv4Interface('192.0.2.1')
-
- Raises:
- AddressValueError: If ipaddress isn't a valid IPv4 address.
- NetmaskValueError: If the netmask isn't valid for
- an IPv4 address.
- ValueError: If strict is True and a network address is not
- supplied.
-
- """
- _BaseNetwork.__init__(self, address)
-
- # Constructing from a packed address or integer
- if isinstance(address, (_compat_int_types, bytes)):
- self.network_address = IPv4Address(address)
- self.netmask, self._prefixlen = self._make_netmask(
- self._max_prefixlen)
- # fixme: address/network test here.
- return
-
- if isinstance(address, tuple):
- if len(address) > 1:
- arg = address[1]
- else:
- # We weren't given an address[1]
- arg = self._max_prefixlen
- self.network_address = IPv4Address(address[0])
- self.netmask, self._prefixlen = self._make_netmask(arg)
- packed = int(self.network_address)
- if packed & int(self.netmask) != packed:
- if strict:
- raise ValueError('%s has host bits set' % self)
- else:
- self.network_address = IPv4Address(packed &
- int(self.netmask))
- return
-
- # Assume input argument to be string or any object representation
- # which converts into a formatted IP prefix string.
- addr = _split_optional_netmask(address)
- self.network_address = IPv4Address(self._ip_int_from_string(addr[0]))
-
- if len(addr) == 2:
- arg = addr[1]
- else:
- arg = self._max_prefixlen
- self.netmask, self._prefixlen = self._make_netmask(arg)
-
- if strict:
- if (IPv4Address(int(self.network_address) & int(self.netmask)) !=
- self.network_address):
- raise ValueError('%s has host bits set' % self)
- self.network_address = IPv4Address(int(self.network_address) &
- int(self.netmask))
-
- if self._prefixlen == (self._max_prefixlen - 1):
- self.hosts = self.__iter__
-
- @property
- def is_global(self):
- """Test if this address is allocated for public networks.
-
- Returns:
- A boolean, True if the address is not reserved per
- iana-ipv4-special-registry.
-
- """
- return (not (self.network_address in IPv4Network('100.64.0.0/10') and
- self.broadcast_address in IPv4Network('100.64.0.0/10')) and
- not self.is_private)
-
-
-class _IPv4Constants(object):
-
- _linklocal_network = IPv4Network('169.254.0.0/16')
-
- _loopback_network = IPv4Network('127.0.0.0/8')
-
- _multicast_network = IPv4Network('224.0.0.0/4')
-
- _public_network = IPv4Network('100.64.0.0/10')
-
- _private_networks = [
- IPv4Network('0.0.0.0/8'),
- IPv4Network('10.0.0.0/8'),
- IPv4Network('127.0.0.0/8'),
- IPv4Network('169.254.0.0/16'),
- IPv4Network('172.16.0.0/12'),
- IPv4Network('192.0.0.0/29'),
- IPv4Network('192.0.0.170/31'),
- IPv4Network('192.0.2.0/24'),
- IPv4Network('192.168.0.0/16'),
- IPv4Network('198.18.0.0/15'),
- IPv4Network('198.51.100.0/24'),
- IPv4Network('203.0.113.0/24'),
- IPv4Network('240.0.0.0/4'),
- IPv4Network('255.255.255.255/32'),
- ]
-
- _reserved_network = IPv4Network('240.0.0.0/4')
-
- _unspecified_address = IPv4Address('0.0.0.0')
-
-
-IPv4Address._constants = _IPv4Constants
-
-
-class _BaseV6(object):
-
- """Base IPv6 object.
-
- The following methods are used by IPv6 objects in both single IP
- addresses and networks.
-
- """
-
- __slots__ = ()
- _version = 6
- _ALL_ONES = (2 ** IPV6LENGTH) - 1
- _HEXTET_COUNT = 8
- _HEX_DIGITS = frozenset('0123456789ABCDEFabcdef')
- _max_prefixlen = IPV6LENGTH
-
- # There are only a bunch of valid v6 netmasks, so we cache them all
- # when constructed (see _make_netmask()).
- _netmask_cache = {}
-
- @classmethod
- def _make_netmask(cls, arg):
- """Make a (netmask, prefix_len) tuple from the given argument.
-
- Argument can be:
- - an integer (the prefix length)
- - a string representing the prefix length (e.g. "24")
- - a string representing the prefix netmask (e.g. "255.255.255.0")
- """
- if arg not in cls._netmask_cache:
- if isinstance(arg, _compat_int_types):
- prefixlen = arg
- else:
- prefixlen = cls._prefix_from_prefix_string(arg)
- netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen))
- cls._netmask_cache[arg] = netmask, prefixlen
- return cls._netmask_cache[arg]
-
- @classmethod
- def _ip_int_from_string(cls, ip_str):
- """Turn an IPv6 ip_str into an integer.
-
- Args:
- ip_str: A string, the IPv6 ip_str.
-
- Returns:
- An int, the IPv6 address
-
- Raises:
- AddressValueError: if ip_str isn't a valid IPv6 Address.
-
- """
- if not ip_str:
- raise AddressValueError('Address cannot be empty')
-
- parts = ip_str.split(':')
-
- # An IPv6 address needs at least 2 colons (3 parts).
- _min_parts = 3
- if len(parts) < _min_parts:
- msg = "At least %d parts expected in %r" % (_min_parts, ip_str)
- raise AddressValueError(msg)
-
- # If the address has an IPv4-style suffix, convert it to hexadecimal.
- if '.' in parts[-1]:
- try:
- ipv4_int = IPv4Address(parts.pop())._ip
- except AddressValueError as exc:
- raise AddressValueError("%s in %r" % (exc, ip_str))
- parts.append('%x' % ((ipv4_int >> 16) & 0xFFFF))
- parts.append('%x' % (ipv4_int & 0xFFFF))
-
- # An IPv6 address can't have more than 8 colons (9 parts).
- # The extra colon comes from using the "::" notation for a single
- # leading or trailing zero part.
- _max_parts = cls._HEXTET_COUNT + 1
- if len(parts) > _max_parts:
- msg = "At most %d colons permitted in %r" % (
- _max_parts - 1, ip_str)
- raise AddressValueError(msg)
-
- # Disregarding the endpoints, find '::' with nothing in between.
- # This indicates that a run of zeroes has been skipped.
- skip_index = None
- for i in _compat_range(1, len(parts) - 1):
- if not parts[i]:
- if skip_index is not None:
- # Can't have more than one '::'
- msg = "At most one '::' permitted in %r" % ip_str
- raise AddressValueError(msg)
- skip_index = i
-
- # parts_hi is the number of parts to copy from above/before the '::'
- # parts_lo is the number of parts to copy from below/after the '::'
- if skip_index is not None:
- # If we found a '::', then check if it also covers the endpoints.
- parts_hi = skip_index
- parts_lo = len(parts) - skip_index - 1
- if not parts[0]:
- parts_hi -= 1
- if parts_hi:
- msg = "Leading ':' only permitted as part of '::' in %r"
- raise AddressValueError(msg % ip_str) # ^: requires ^::
- if not parts[-1]:
- parts_lo -= 1
- if parts_lo:
- msg = "Trailing ':' only permitted as part of '::' in %r"
- raise AddressValueError(msg % ip_str) # :$ requires ::$
- parts_skipped = cls._HEXTET_COUNT - (parts_hi + parts_lo)
- if parts_skipped < 1:
- msg = "Expected at most %d other parts with '::' in %r"
- raise AddressValueError(msg % (cls._HEXTET_COUNT - 1, ip_str))
- else:
- # Otherwise, allocate the entire address to parts_hi. The
- # endpoints could still be empty, but _parse_hextet() will check
- # for that.
- if len(parts) != cls._HEXTET_COUNT:
- msg = "Exactly %d parts expected without '::' in %r"
- raise AddressValueError(msg % (cls._HEXTET_COUNT, ip_str))
- if not parts[0]:
- msg = "Leading ':' only permitted as part of '::' in %r"
- raise AddressValueError(msg % ip_str) # ^: requires ^::
- if not parts[-1]:
- msg = "Trailing ':' only permitted as part of '::' in %r"
- raise AddressValueError(msg % ip_str) # :$ requires ::$
- parts_hi = len(parts)
- parts_lo = 0
- parts_skipped = 0
-
- try:
- # Now, parse the hextets into a 128-bit integer.
- ip_int = 0
- for i in range(parts_hi):
- ip_int <<= 16
- ip_int |= cls._parse_hextet(parts[i])
- ip_int <<= 16 * parts_skipped
- for i in range(-parts_lo, 0):
- ip_int <<= 16
- ip_int |= cls._parse_hextet(parts[i])
- return ip_int
- except ValueError as exc:
- raise AddressValueError("%s in %r" % (exc, ip_str))
-
- @classmethod
- def _parse_hextet(cls, hextet_str):
- """Convert an IPv6 hextet string into an integer.
-
- Args:
- hextet_str: A string, the number to parse.
-
- Returns:
- The hextet as an integer.
-
- Raises:
- ValueError: if the input isn't strictly a hex number from
- [0..FFFF].
-
- """
- # Whitelist the characters, since int() allows a lot of bizarre stuff.
- if not cls._HEX_DIGITS.issuperset(hextet_str):
- raise ValueError("Only hex digits permitted in %r" % hextet_str)
- # We do the length check second, since the invalid character error
- # is likely to be more informative for the user
- if len(hextet_str) > 4:
- msg = "At most 4 characters permitted in %r"
- raise ValueError(msg % hextet_str)
- # Length check means we can skip checking the integer value
- return int(hextet_str, 16)
-
- @classmethod
- def _compress_hextets(cls, hextets):
- """Compresses a list of hextets.
-
- Compresses a list of strings, replacing the longest continuous
- sequence of "0" in the list with "" and adding empty strings at
- the beginning or at the end of the string such that subsequently
- calling ":".join(hextets) will produce the compressed version of
- the IPv6 address.
-
- Args:
- hextets: A list of strings, the hextets to compress.
-
- Returns:
- A list of strings.
-
- """
- best_doublecolon_start = -1
- best_doublecolon_len = 0
- doublecolon_start = -1
- doublecolon_len = 0
- for index, hextet in enumerate(hextets):
- if hextet == '0':
- doublecolon_len += 1
- if doublecolon_start == -1:
- # Start of a sequence of zeros.
- doublecolon_start = index
- if doublecolon_len > best_doublecolon_len:
- # This is the longest sequence of zeros so far.
- best_doublecolon_len = doublecolon_len
- best_doublecolon_start = doublecolon_start
- else:
- doublecolon_len = 0
- doublecolon_start = -1
-
- if best_doublecolon_len > 1:
- best_doublecolon_end = (best_doublecolon_start +
- best_doublecolon_len)
- # For zeros at the end of the address.
- if best_doublecolon_end == len(hextets):
- hextets += ['']
- hextets[best_doublecolon_start:best_doublecolon_end] = ['']
- # For zeros at the beginning of the address.
- if best_doublecolon_start == 0:
- hextets = [''] + hextets
-
- return hextets
-
- @classmethod
- def _string_from_ip_int(cls, ip_int=None):
- """Turns a 128-bit integer into hexadecimal notation.
-
- Args:
- ip_int: An integer, the IP address.
-
- Returns:
- A string, the hexadecimal representation of the address.
-
- Raises:
- ValueError: The address is bigger than 128 bits of all ones.
-
- """
- if ip_int is None:
- ip_int = int(cls._ip)
-
- if ip_int > cls._ALL_ONES:
- raise ValueError('IPv6 address is too large')
-
- hex_str = '%032x' % ip_int
- hextets = ['%x' % int(hex_str[x:x + 4], 16) for x in range(0, 32, 4)]
-
- hextets = cls._compress_hextets(hextets)
- return ':'.join(hextets)
-
- def _explode_shorthand_ip_string(self):
- """Expand a shortened IPv6 address.
-
- Args:
- ip_str: A string, the IPv6 address.
-
- Returns:
- A string, the expanded IPv6 address.
-
- """
- if isinstance(self, IPv6Network):
- ip_str = _compat_str(self.network_address)
- elif isinstance(self, IPv6Interface):
- ip_str = _compat_str(self.ip)
- else:
- ip_str = _compat_str(self)
-
- ip_int = self._ip_int_from_string(ip_str)
- hex_str = '%032x' % ip_int
- parts = [hex_str[x:x + 4] for x in range(0, 32, 4)]
- if isinstance(self, (_BaseNetwork, IPv6Interface)):
- return '%s/%d' % (':'.join(parts), self._prefixlen)
- return ':'.join(parts)
-
- def _reverse_pointer(self):
- """Return the reverse DNS pointer name for the IPv6 address.
-
- This implements the method described in RFC3596 2.5.
-
- """
- reverse_chars = self.exploded[::-1].replace(':', '')
- return '.'.join(reverse_chars) + '.ip6.arpa'
-
- @property
- def max_prefixlen(self):
- return self._max_prefixlen
-
- @property
- def version(self):
- return self._version
-
-
-class IPv6Address(_BaseV6, _BaseAddress):
-
- """Represent and manipulate single IPv6 Addresses."""
-
- __slots__ = ('_ip', '__weakref__')
-
- def __init__(self, address):
- """Instantiate a new IPv6 address object.
-
- Args:
- address: A string or integer representing the IP
-
- Additionally, an integer can be passed, so
- IPv6Address('2001:db8::') ==
- IPv6Address(42540766411282592856903984951653826560)
- or, more generally
- IPv6Address(int(IPv6Address('2001:db8::'))) ==
- IPv6Address('2001:db8::')
-
- Raises:
- AddressValueError: If address isn't a valid IPv6 address.
-
- """
- # Efficient constructor from integer.
- if isinstance(address, _compat_int_types):
- self._check_int_address(address)
- self._ip = address
- return
-
- # Constructing from a packed address
- if isinstance(address, bytes):
- self._check_packed_address(address, 16)
- bvs = _compat_bytes_to_byte_vals(address)
- self._ip = _compat_int_from_byte_vals(bvs, 'big')
- return
-
- # Assume input argument to be string or any object representation
- # which converts into a formatted IP string.
- addr_str = _compat_str(address)
- if '/' in addr_str:
- raise AddressValueError("Unexpected '/' in %r" % address)
- self._ip = self._ip_int_from_string(addr_str)
-
- @property
- def packed(self):
- """The binary representation of this address."""
- return v6_int_to_packed(self._ip)
-
- @property
- def is_multicast(self):
- """Test if the address is reserved for multicast use.
-
- Returns:
- A boolean, True if the address is a multicast address.
- See RFC 2373 2.7 for details.
-
- """
- return self in self._constants._multicast_network
-
- @property
- def is_reserved(self):
- """Test if the address is otherwise IETF reserved.
-
- Returns:
- A boolean, True if the address is within one of the
- reserved IPv6 Network ranges.
-
- """
- return any(self in x for x in self._constants._reserved_networks)
-
- @property
- def is_link_local(self):
- """Test if the address is reserved for link-local.
-
- Returns:
- A boolean, True if the address is reserved per RFC 4291.
-
- """
- return self in self._constants._linklocal_network
-
- @property
- def is_site_local(self):
- """Test if the address is reserved for site-local.
-
- Note that the site-local address space has been deprecated by RFC 3879.
- Use is_private to test if this address is in the space of unique local
- addresses as defined by RFC 4193.
-
- Returns:
- A boolean, True if the address is reserved per RFC 3513 2.5.6.
-
- """
- return self in self._constants._sitelocal_network
-
- @property
- def is_private(self):
- """Test if this address is allocated for private networks.
-
- Returns:
- A boolean, True if the address is reserved per
- iana-ipv6-special-registry.
-
- """
- return any(self in net for net in self._constants._private_networks)
-
- @property
- def is_global(self):
- """Test if this address is allocated for public networks.
-
- Returns:
- A boolean, true if the address is not reserved per
- iana-ipv6-special-registry.
-
- """
- return not self.is_private
-
- @property
- def is_unspecified(self):
- """Test if the address is unspecified.
-
- Returns:
- A boolean, True if this is the unspecified address as defined in
- RFC 2373 2.5.2.
-
- """
- return self._ip == 0
-
- @property
- def is_loopback(self):
- """Test if the address is a loopback address.
-
- Returns:
- A boolean, True if the address is a loopback address as defined in
- RFC 2373 2.5.3.
-
- """
- return self._ip == 1
-
- @property
- def ipv4_mapped(self):
- """Return the IPv4 mapped address.
-
- Returns:
- If the IPv6 address is a v4 mapped address, return the
- IPv4 mapped address. Return None otherwise.
-
- """
- if (self._ip >> 32) != 0xFFFF:
- return None
- return IPv4Address(self._ip & 0xFFFFFFFF)
-
- @property
- def teredo(self):
- """Tuple of embedded teredo IPs.
-
- Returns:
- Tuple of the (server, client) IPs or None if the address
- doesn't appear to be a teredo address (doesn't start with
- 2001::/32)
-
- """
- if (self._ip >> 96) != 0x20010000:
- return None
- return (IPv4Address((self._ip >> 64) & 0xFFFFFFFF),
- IPv4Address(~self._ip & 0xFFFFFFFF))
-
- @property
- def sixtofour(self):
- """Return the IPv4 6to4 embedded address.
-
- Returns:
- The IPv4 6to4-embedded address if present or None if the
- address doesn't appear to contain a 6to4 embedded address.
-
- """
- if (self._ip >> 112) != 0x2002:
- return None
- return IPv4Address((self._ip >> 80) & 0xFFFFFFFF)
-
-
-class IPv6Interface(IPv6Address):
-
- def __init__(self, address):
- if isinstance(address, (bytes, _compat_int_types)):
- IPv6Address.__init__(self, address)
- self.network = IPv6Network(self._ip)
- self._prefixlen = self._max_prefixlen
- return
- if isinstance(address, tuple):
- IPv6Address.__init__(self, address[0])
- if len(address) > 1:
- self._prefixlen = int(address[1])
- else:
- self._prefixlen = self._max_prefixlen
- self.network = IPv6Network(address, strict=False)
- self.netmask = self.network.netmask
- self.hostmask = self.network.hostmask
- return
-
- addr = _split_optional_netmask(address)
- IPv6Address.__init__(self, addr[0])
- self.network = IPv6Network(address, strict=False)
- self.netmask = self.network.netmask
- self._prefixlen = self.network._prefixlen
- self.hostmask = self.network.hostmask
-
- def __str__(self):
- return '%s/%d' % (self._string_from_ip_int(self._ip),
- self.network.prefixlen)
-
- def __eq__(self, other):
- address_equal = IPv6Address.__eq__(self, other)
- if not address_equal or address_equal is NotImplemented:
- return address_equal
- try:
- return self.network == other.network
- except AttributeError:
- # An interface with an associated network is NOT the
- # same as an unassociated address. That's why the hash
- # takes the extra info into account.
- return False
-
- def __lt__(self, other):
- address_less = IPv6Address.__lt__(self, other)
- if address_less is NotImplemented:
- return NotImplemented
- try:
- return (self.network < other.network or
- self.network == other.network and address_less)
- except AttributeError:
- # We *do* allow addresses and interfaces to be sorted. The
- # unassociated address is considered less than all interfaces.
- return False
-
- def __hash__(self):
- return self._ip ^ self._prefixlen ^ int(self.network.network_address)
-
- __reduce__ = _IPAddressBase.__reduce__
-
- @property
- def ip(self):
- return IPv6Address(self._ip)
-
- @property
- def with_prefixlen(self):
- return '%s/%s' % (self._string_from_ip_int(self._ip),
- self._prefixlen)
-
- @property
- def with_netmask(self):
- return '%s/%s' % (self._string_from_ip_int(self._ip),
- self.netmask)
-
- @property
- def with_hostmask(self):
- return '%s/%s' % (self._string_from_ip_int(self._ip),
- self.hostmask)
-
- @property
- def is_unspecified(self):
- return self._ip == 0 and self.network.is_unspecified
-
- @property
- def is_loopback(self):
- return self._ip == 1 and self.network.is_loopback
-
-
-class IPv6Network(_BaseV6, _BaseNetwork):
-
- """This class represents and manipulates 128-bit IPv6 networks.
-
- Attributes: [examples for IPv6('2001:db8::1000/124')]
- .network_address: IPv6Address('2001:db8::1000')
- .hostmask: IPv6Address('::f')
- .broadcast_address: IPv6Address('2001:db8::100f')
- .netmask: IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0')
- .prefixlen: 124
-
- """
-
- # Class to use when creating address objects
- _address_class = IPv6Address
-
- def __init__(self, address, strict=True):
- """Instantiate a new IPv6 Network object.
-
- Args:
- address: A string or integer representing the IPv6 network or the
- IP and prefix/netmask.
- '2001:db8::/128'
- '2001:db8:0000:0000:0000:0000:0000:0000/128'
- '2001:db8::'
- are all functionally the same in IPv6. That is to say,
- failing to provide a subnetmask will create an object with
- a mask of /128.
-
- Additionally, an integer can be passed, so
- IPv6Network('2001:db8::') ==
- IPv6Network(42540766411282592856903984951653826560)
- or, more generally
- IPv6Network(int(IPv6Network('2001:db8::'))) ==
- IPv6Network('2001:db8::')
-
- strict: A boolean. If true, ensure that we have been passed
- A true network address, eg, 2001:db8::1000/124 and not an
- IP address on a network, eg, 2001:db8::1/124.
-
- Raises:
- AddressValueError: If address isn't a valid IPv6 address.
- NetmaskValueError: If the netmask isn't valid for
- an IPv6 address.
- ValueError: If strict was True and a network address was not
- supplied.
-
- """
- _BaseNetwork.__init__(self, address)
-
- # Efficient constructor from integer or packed address
- if isinstance(address, (bytes, _compat_int_types)):
- self.network_address = IPv6Address(address)
- self.netmask, self._prefixlen = self._make_netmask(
- self._max_prefixlen)
- return
-
- if isinstance(address, tuple):
- if len(address) > 1:
- arg = address[1]
- else:
- arg = self._max_prefixlen
- self.netmask, self._prefixlen = self._make_netmask(arg)
- self.network_address = IPv6Address(address[0])
- packed = int(self.network_address)
- if packed & int(self.netmask) != packed:
- if strict:
- raise ValueError('%s has host bits set' % self)
- else:
- self.network_address = IPv6Address(packed &
- int(self.netmask))
- return
-
- # Assume input argument to be string or any object representation
- # which converts into a formatted IP prefix string.
- addr = _split_optional_netmask(address)
-
- self.network_address = IPv6Address(self._ip_int_from_string(addr[0]))
-
- if len(addr) == 2:
- arg = addr[1]
- else:
- arg = self._max_prefixlen
- self.netmask, self._prefixlen = self._make_netmask(arg)
-
- if strict:
- if (IPv6Address(int(self.network_address) & int(self.netmask)) !=
- self.network_address):
- raise ValueError('%s has host bits set' % self)
- self.network_address = IPv6Address(int(self.network_address) &
- int(self.netmask))
-
- if self._prefixlen == (self._max_prefixlen - 1):
- self.hosts = self.__iter__
-
- def hosts(self):
- """Generate Iterator over usable hosts in a network.
-
- This is like __iter__ except it doesn't return the
- Subnet-Router anycast address.
-
- """
- network = int(self.network_address)
- broadcast = int(self.broadcast_address)
- for x in _compat_range(network + 1, broadcast + 1):
- yield self._address_class(x)
-
- @property
- def is_site_local(self):
- """Test if the address is reserved for site-local.
-
- Note that the site-local address space has been deprecated by RFC 3879.
- Use is_private to test if this address is in the space of unique local
- addresses as defined by RFC 4193.
-
- Returns:
- A boolean, True if the address is reserved per RFC 3513 2.5.6.
-
- """
- return (self.network_address.is_site_local and
- self.broadcast_address.is_site_local)
-
-
-class _IPv6Constants(object):
-
- _linklocal_network = IPv6Network('fe80::/10')
-
- _multicast_network = IPv6Network('ff00::/8')
-
- _private_networks = [
- IPv6Network('::1/128'),
- IPv6Network('::/128'),
- IPv6Network('::ffff:0:0/96'),
- IPv6Network('100::/64'),
- IPv6Network('2001::/23'),
- IPv6Network('2001:2::/48'),
- IPv6Network('2001:db8::/32'),
- IPv6Network('2001:10::/28'),
- IPv6Network('fc00::/7'),
- IPv6Network('fe80::/10'),
- ]
-
- _reserved_networks = [
- IPv6Network('::/8'), IPv6Network('100::/8'),
- IPv6Network('200::/7'), IPv6Network('400::/6'),
- IPv6Network('800::/5'), IPv6Network('1000::/4'),
- IPv6Network('4000::/3'), IPv6Network('6000::/3'),
- IPv6Network('8000::/3'), IPv6Network('A000::/3'),
- IPv6Network('C000::/3'), IPv6Network('E000::/4'),
- IPv6Network('F000::/5'), IPv6Network('F800::/6'),
- IPv6Network('FE00::/9'),
- ]
-
- _sitelocal_network = IPv6Network('fec0::/10')
-
-
-IPv6Address._constants = _IPv6Constants
From 796d09b4103c6ecbf8f4b10f97fbdd84e8c46552 Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:17:39 +0100
Subject: [PATCH 14/42] Remove six leafovers
---
readthedocs/core/utils/extend.py | 2 +-
readthedocs/rtd_tests/tests/test_views.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/readthedocs/core/utils/extend.py b/readthedocs/core/utils/extend.py
index c01c76eee86..ca8e3f40b2e 100644
--- a/readthedocs/core/utils/extend.py
+++ b/readthedocs/core/utils/extend.py
@@ -39,7 +39,7 @@ def __getattr__(cls, attr): # noqa: pep8 false positive
return getattr(proxy_class, attr)
-class SettingsOverrideObject(six.with_metaclass(SettingsOverrideMeta, object)):
+class SettingsOverrideObject(metaclass=SettingsOverrideMeta):
"""
Base class for creating class that can be overridden.
diff --git a/readthedocs/rtd_tests/tests/test_views.py b/readthedocs/rtd_tests/tests/test_views.py
index dd64a438a8e..56b669979f4 100644
--- a/readthedocs/rtd_tests/tests/test_views.py
+++ b/readthedocs/rtd_tests/tests/test_views.py
@@ -3,8 +3,8 @@
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
-from django.utils.six.moves.urllib.parse import urlsplit
from django_dynamic_fixture import get, new
+from urllib.parse import urlsplit
from readthedocs.builds.constants import LATEST
from readthedocs.builds.models import Build
From c2a662cad6dfd3e57078cbdf59e025eb01250a82 Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:20:11 +0100
Subject: [PATCH 15/42] Bug fix on types checking
---
readthedocs/config/validation.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/readthedocs/config/validation.py b/readthedocs/config/validation.py
index a69b8ed7769..c366b05073a 100644
--- a/readthedocs/config/validation.py
+++ b/readthedocs/config/validation.py
@@ -43,7 +43,7 @@ def __init__(self, value, code, format_kwargs=None):
def validate_list(value):
"""Check if ``value`` is an iterable."""
- if isinstance(value, (dict, (str,))):
+ if isinstance(value, (dict, str)):
raise ValidationError(value, INVALID_LIST)
if not hasattr(value, '__iter__'):
raise ValidationError(value, INVALID_LIST)
From 686c8201d7bac90afadecb4860e17971be9ba3fa Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:28:59 +0100
Subject: [PATCH 16/42] Import missing modules removed by mistake
---
readthedocs/config/tests/test_find.py | 2 ++
readthedocs/rtd_tests/tests/test_post_commit_hooks.py | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/readthedocs/config/tests/test_find.py b/readthedocs/config/tests/test_find.py
index 048a6c9b90c..4ebcb783cde 100644
--- a/readthedocs/config/tests/test_find.py
+++ b/readthedocs/config/tests/test_find.py
@@ -1,3 +1,5 @@
+import os
+
from readthedocs.config.find import find_one
from .utils import apply_fs
diff --git a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
index 6b4a6db070b..f9b310a6207 100644
--- a/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
+++ b/readthedocs/rtd_tests/tests/test_post_commit_hooks.py
@@ -4,7 +4,7 @@
import mock
from django.test import TestCase
from django_dynamic_fixture import get
-from urllib.parse import urlparse
+from urllib.parse import urlparse, urlencode
from readthedocs.builds.models import Version
from readthedocs.projects.models import Feature, Project
From 25eb8c480cdd24e340ba538acf252f1292cd6cd4 Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:29:11 +0100
Subject: [PATCH 17/42] Keep __future__ in conf.py because we build with Py2
also
---
readthedocs/rtd_tests/files/conf.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py
index 7bdfdbe06dc..331f306020d 100644
--- a/readthedocs/rtd_tests/files/conf.py
+++ b/readthedocs/rtd_tests/files/conf.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
+from __future__ import division, print_function, unicode_literals
from datetime import datetime
from recommonmark.parser import CommonMarkParser
From 06b07cc6ffd3aa64ec84442e0821f4a9425b5ac4 Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:31:00 +0100
Subject: [PATCH 18/42] Make our generated conf.py compatible with Py2 and Py3
---
readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl | 7 ++++---
readthedocs/rtd_tests/files/conf.py | 3 ++-
readthedocs/templates/sphinx/conf.py.conf | 6 +++---
3 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl
index e36cdd16af5..0ec2b9c331b 100644
--- a/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl
+++ b/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl
@@ -13,6 +13,7 @@
# https://github.com/rtfd/readthedocs.org/blob/master/readthedocs/doc_builder/templates/doc_builder/conf.py.tmpl
#
+from __future__ import absolute_import, division, print_function, unicode_literals
import importlib
import sys
@@ -81,9 +82,9 @@ context = {
("{{ slug }}", "{{ url }}"),{% endfor %}
],
'slug': '{{ project.slug }}',
- 'name': u'{{ project.name }}',
- 'rtd_language': u'{{ project.language }}',
- 'programming_language': u'{{ project.programming_language }}',
+ 'name': '{{ project.name }}',
+ 'rtd_language': '{{ project.language }}',
+ 'programming_language': '{{ project.programming_language }}',
'canonical_url': '{{ project.get_canonical_url }}',
'analytics_code': '{{ project.analytics_code }}',
'single_version': {{ project.single_version }},
diff --git a/readthedocs/rtd_tests/files/conf.py b/readthedocs/rtd_tests/files/conf.py
index 331f306020d..11f872849dd 100644
--- a/readthedocs/rtd_tests/files/conf.py
+++ b/readthedocs/rtd_tests/files/conf.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import division, print_function, unicode_literals
+
from datetime import datetime
from recommonmark.parser import CommonMarkParser
@@ -12,7 +13,7 @@
'.md': CommonMarkParser,
}
master_doc = 'index'
-project = u'Pip'
+project = 'Pip'
copyright = str(datetime.now().year)
version = '0.8.1'
release = '0.8.1'
diff --git a/readthedocs/templates/sphinx/conf.py.conf b/readthedocs/templates/sphinx/conf.py.conf
index 43e7b58b290..520670cca16 100644
--- a/readthedocs/templates/sphinx/conf.py.conf
+++ b/readthedocs/templates/sphinx/conf.py.conf
@@ -13,7 +13,7 @@ source_parsers = {
'.md': CommonMarkParser,
}
master_doc = 'index'
-project = u'{{ project.name }}'
+project = '{{ project.name }}'
copyright = str(datetime.now().year)
version = '{{ version.verbose_name }}'
release = '{{ version.verbose_name }}'
@@ -23,6 +23,6 @@ htmlhelp_basename = '{{ project.slug }}'
html_theme = 'sphinx_rtd_theme'
file_insertion_enabled = False
latex_documents = [
- ('index', '{{ project.slug }}.tex', u'{{ project.name }} Documentation',
- u'{{ project.copyright }}', 'manual'),
+ ('index', '{{ project.slug }}.tex', '{{ project.name }} Documentation',
+ '{{ project.copyright }}', 'manual'),
]
From e066c0d983775bb9be87fa0c10c68e27c2265ef1 Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:43:13 +0100
Subject: [PATCH 19/42] Import missing modules
---
readthedocs/core/templatetags/core_tags.py | 2 +-
readthedocs/vcs_support/backends/bzr.py | 1 +
readthedocs/vcs_support/backends/svn.py | 1 +
3 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py
index e1b9e6f7903..304ca3aff3a 100644
--- a/readthedocs/core/templatetags/core_tags.py
+++ b/readthedocs/core/templatetags/core_tags.py
@@ -1,7 +1,7 @@
"""Template tags for core app."""
import hashlib
-from urllib.parse import urlparse
+from urllib.parse import urlparse, urlencode
from django import template
from django.conf import settings
diff --git a/readthedocs/vcs_support/backends/bzr.py b/readthedocs/vcs_support/backends/bzr.py
index 9c97870408c..ebfb5574f08 100644
--- a/readthedocs/vcs_support/backends/bzr.py
+++ b/readthedocs/vcs_support/backends/bzr.py
@@ -3,6 +3,7 @@
import csv
import re
+from io import StringIO
from readthedocs.projects.exceptions import RepositoryError
from readthedocs.vcs_support.base import BaseVCS, VCSVersion
diff --git a/readthedocs/vcs_support/backends/svn.py b/readthedocs/vcs_support/backends/svn.py
index b4ad6130302..d591dc880cc 100644
--- a/readthedocs/vcs_support/backends/svn.py
+++ b/readthedocs/vcs_support/backends/svn.py
@@ -2,6 +2,7 @@
"""Subversion-related utilities."""
import csv
+from io import StringIO
from readthedocs.projects.exceptions import RepositoryError
from readthedocs.vcs_support.base import BaseVCS, VCSVersion
From d606c25d10b7b8979e4b0bc3da6d5963ed93968e Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:45:29 +0100
Subject: [PATCH 20/42] Upgrade common submodule
---
common | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/common b/common
index a6cb6bbafb3..890625b9bfc 160000
--- a/common
+++ b/common
@@ -1 +1 @@
-Subproject commit a6cb6bbafb3cf93bfad7bab98c6636021719cc48
+Subproject commit 890625b9bfca1981a3f68a9fb7d168bba714421d
From 3219f7acbdde68e889c828920d0b181bae93277e Mon Sep 17 00:00:00 2001
From: Manuel Kaufmann
Date: Tue, 8 Jan 2019 21:51:36 +0100
Subject: [PATCH 21/42] Run pre-commit on all the files
$ pre-commit run --files `find readthedocs -name '*'`
---
readthedocs/__init__.py | 1 +
readthedocs/analytics/__init__.py | 5 +-
readthedocs/analytics/apps.py | 3 +-
readthedocs/analytics/tasks.py | 36 +-
readthedocs/analytics/utils.py | 21 +-
readthedocs/api/base.py | 60 +-
readthedocs/api/utils.py | 19 +-
readthedocs/builds/admin.py | 14 +-
readthedocs/builds/constants.py | 1 +
readthedocs/builds/forms.py | 5 +-
readthedocs/builds/managers.py | 5 +-
readthedocs/builds/models.py | 145 +-
readthedocs/builds/querysets.py | 14 +-
readthedocs/builds/signals.py | 3 +-
readthedocs/builds/syncers.py | 53 +-
readthedocs/builds/urls.py | 1 +
readthedocs/builds/utils.py | 1 +
readthedocs/builds/version_slug.py | 14 +-
readthedocs/builds/views.py | 19 +-
readthedocs/config/__init__.py | 1 +
readthedocs/config/config.py | 82 +-
readthedocs/config/find.py | 1 +
readthedocs/config/models.py | 1 +
readthedocs/config/parser.py | 3 +-
readthedocs/config/validation.py | 13 +-
readthedocs/constants.py | 3 +-
readthedocs/core/__init__.py | 1 +
readthedocs/core/adapters.py | 18 +-
readthedocs/core/admin.py | 11 +-
readthedocs/core/apps.py | 1 +
readthedocs/core/backends.py | 8 +-
readthedocs/core/context_processors.py | 9 +-
readthedocs/core/fields.py | 5 +-
readthedocs/core/fixtures/flag_types.json | 28 +-
readthedocs/core/forms.py | 2 +
.../core/management/commands/archive.py | 10 +-
.../core/management/commands/clean_builds.py | 20 +-
.../core/management/commands/import_github.py | 8 +-
.../commands/import_github_language.py | 17 +-
.../commands/provision_elasticsearch.py | 11 +-
readthedocs/core/management/commands/pull.py | 3 +-
.../commands/reindex_elasticsearch.py | 22 +-
.../core/management/commands/set_metadata.py | 9 +-
.../core/management/commands/symlink.py | 6 +-
.../core/management/commands/update_api.py | 3 +-
.../management/commands/update_versions.py | 5 +-
readthedocs/core/middleware.py | 83 +-
readthedocs/core/mixins.py | 5 +-
readthedocs/core/models.py | 13 +-
readthedocs/core/permissions.py | 3 +-
readthedocs/core/resolver.py | 67 +-
readthedocs/core/signals.py | 10 +-
.../static/core/font/fontawesome-webfont.svg | 1418 ++++++++---------
readthedocs/core/symlink.py | 110 +-
readthedocs/core/tasks.py | 24 +-
readthedocs/core/templatetags/core_tags.py | 31 +-
readthedocs/core/templatetags/privacy_tags.py | 5 +-
readthedocs/core/urls/__init__.py | 81 +-
readthedocs/core/urls/single_version.py | 35 +-
readthedocs/core/urls/subdomain.py | 35 +-
readthedocs/core/utils/__init__.py | 1 -
readthedocs/core/utils/extend.py | 6 +-
readthedocs/core/utils/tasks/__init__.py | 3 +-
.../core/utils/tasks/permission_checks.py | 3 +-
readthedocs/core/utils/tasks/public.py | 18 +-
readthedocs/core/utils/tasks/retrieve.py | 4 +-
readthedocs/core/views/__init__.py | 22 +-
readthedocs/core/views/hooks.py | 78 +-
readthedocs/core/views/serve.py | 32 +-
readthedocs/doc_builder/backends/mkdocs.py | 46 +-
readthedocs/doc_builder/backends/sphinx.py | 86 +-
readthedocs/doc_builder/base.py | 15 +-
readthedocs/doc_builder/config.py | 5 +-
readthedocs/doc_builder/constants.py | 5 +-
readthedocs/doc_builder/environments.py | 136 +-
readthedocs/doc_builder/exceptions.py | 5 +-
readthedocs/doc_builder/loader.py | 17 +-
.../doc_builder/python_environments.py | 61 +-
readthedocs/doc_builder/signals.py | 5 +-
.../templates/doc_builder/data.js.tmpl | 2 +-
readthedocs/gold/__init__.py | 1 +
readthedocs/gold/admin.py | 1 +
readthedocs/gold/apps.py | 1 +
readthedocs/gold/forms.py | 20 +-
readthedocs/gold/signals.py | 5 +-
readthedocs/gold/templates/gold/projects.html | 1 -
.../templates/gold/subscription_form.html | 2 +-
readthedocs/gold/urls.py | 25 +-
readthedocs/gold/views.py | 21 +-
readthedocs/integrations/admin.py | 37 +-
readthedocs/integrations/models.py | 3 +-
readthedocs/integrations/utils.py | 1 +
readthedocs/notifications/__init__.py | 4 +-
readthedocs/notifications/apps.py | 1 +
readthedocs/notifications/backends.py | 1 +
readthedocs/notifications/constants.py | 4 +-
readthedocs/notifications/forms.py | 5 +-
readthedocs/notifications/notification.py | 20 +-
readthedocs/notifications/storages.py | 36 +-
readthedocs/notifications/urls.py | 17 +-
readthedocs/notifications/views.py | 37 +-
readthedocs/oauth/__init__.py | 1 +
readthedocs/oauth/admin.py | 1 +
readthedocs/oauth/apps.py | 3 +-
readthedocs/oauth/models.py | 45 +-
readthedocs/oauth/notifications.py | 11 +-
readthedocs/oauth/querysets.py | 5 +-
readthedocs/oauth/services/__init__.py | 7 +-
readthedocs/oauth/services/base.py | 4 +-
readthedocs/oauth/services/bitbucket.py | 76 +-
readthedocs/oauth/services/github.py | 64 +-
readthedocs/oauth/services/gitlab.py | 18 +-
readthedocs/oauth/tasks.py | 1 +
readthedocs/oauth/utils.py | 5 +-
readthedocs/payments/forms.py | 89 +-
readthedocs/payments/mixins.py | 1 +
readthedocs/payments/utils.py | 1 +
readthedocs/profiles/urls/private.py | 6 +-
readthedocs/profiles/urls/public.py | 7 +-
readthedocs/profiles/views.py | 24 +-
readthedocs/projects/__init__.py | 1 +
readthedocs/projects/admin.py | 49 +-
readthedocs/projects/apps.py | 3 +-
readthedocs/projects/backends/views.py | 3 +-
readthedocs/projects/constants.py | 10 +-
readthedocs/projects/exceptions.py | 17 +-
readthedocs/projects/feeds.py | 19 +-
readthedocs/projects/fixtures/test_auth.json | 2 +-
readthedocs/projects/forms.py | 141 +-
.../commands/import_project_from_live.py | 16 +-
readthedocs/projects/models.py | 578 +++++--
readthedocs/projects/notifications.py | 3 +-
readthedocs/projects/querysets.py | 14 +-
readthedocs/projects/signals.py | 15 +-
readthedocs/projects/tasks.py | 232 +--
.../projects/templatetags/projects_tags.py | 10 +-
readthedocs/projects/urls/private.py | 271 +++-
readthedocs/projects/urls/public.py | 94 +-
readthedocs/projects/utils.py | 1 +
readthedocs/projects/validators.py | 3 +-
readthedocs/projects/version_handling.py | 4 +-
readthedocs/projects/views/base.py | 4 +-
readthedocs/projects/views/mixins.py | 3 +-
readthedocs/projects/views/public.py | 99 +-
readthedocs/redirects/admin.py | 1 +
readthedocs/redirects/managers.py | 7 +-
readthedocs/redirects/models.py | 103 +-
readthedocs/redirects/utils.py | 12 +-
readthedocs/restapi/client.py | 2 +-
readthedocs/restapi/permissions.py | 6 +-
readthedocs/restapi/serializers.py | 25 +-
readthedocs/restapi/signals.py | 3 +-
readthedocs/restapi/urls.py | 20 +-
readthedocs/restapi/utils.py | 53 +-
readthedocs/restapi/views/core_views.py | 15 +-
readthedocs/restapi/views/footer_views.py | 18 +-
readthedocs/restapi/views/integrations.py | 40 +-
readthedocs/restapi/views/model_views.py | 61 +-
readthedocs/restapi/views/search_views.py | 59 +-
readthedocs/restapi/views/task_views.py | 22 +-
readthedocs/search/indexes.py | 91 +-
readthedocs/search/lib.py | 260 +--
readthedocs/search/parse_json.py | 25 +-
readthedocs/search/signals.py | 7 +-
readthedocs/search/utils.py | 45 +-
readthedocs/search/views.py | 17 +-
.../email/email_confirmation_message.html | 1 -
readthedocs/templates/core/badge_markup.html | 2 +-
.../templates/core/build_list_detailed.html | 1 -
readthedocs/templates/core/project_list.html | 1 -
.../templates/core/project_list_detailed.html | 2 +-
readthedocs/templates/core/widesearchbar.html | 2 +-
readthedocs/templates/dnt-policy.txt | 50 +-
.../templates/flagging/flag_confirm.html | 2 +-
.../templates/flagging/flag_success.html | 2 +-
.../projects/domain_confirm_delete.html | 2 -
.../templates/projects/domain_form.html | 1 -
.../templates/projects/domain_list.html | 3 +-
.../templates/projects/project_analytics.html | 4 +-
.../projects/project_version_list.html | 2 +-
.../templates/search/elastic_search.html | 8 +-
readthedocs/templates/style_catalog.html | 12 +-
readthedocs/urls.py | 40 +-
readthedocs/vcs_support/backends/__init__.py | 1 +
readthedocs/vcs_support/backends/bzr.py | 1 +
readthedocs/vcs_support/backends/git.py | 13 +-
readthedocs/vcs_support/backends/hg.py | 7 +-
readthedocs/vcs_support/backends/svn.py | 30 +-
readthedocs/vcs_support/base.py | 5 +-
readthedocs/vcs_support/utils.py | 54 +-
readthedocs/worker.py | 3 +-
readthedocs/wsgi.py | 5 +-
192 files changed, 4005 insertions(+), 2536 deletions(-)
diff --git a/readthedocs/__init__.py b/readthedocs/__init__.py
index 1c8afb1a02b..8f8c9ee7c80 100644
--- a/readthedocs/__init__.py
+++ b/readthedocs/__init__.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Read the Docs."""
import os.path
diff --git a/readthedocs/analytics/__init__.py b/readthedocs/analytics/__init__.py
index f2531d76edb..b8f7ef2906d 100644
--- a/readthedocs/analytics/__init__.py
+++ b/readthedocs/analytics/__init__.py
@@ -1,3 +1,4 @@
-"""App init"""
+# -*- coding: utf-8 -*-
+"""App init."""
-default_app_config = 'readthedocs.analytics.apps.AnalyticsAppConfig' # noqa
+default_app_config = 'readthedocs.analytics.apps.AnalyticsAppConfig' # noqa
diff --git a/readthedocs/analytics/apps.py b/readthedocs/analytics/apps.py
index 219a787dce7..3bfa8a3559d 100644
--- a/readthedocs/analytics/apps.py
+++ b/readthedocs/analytics/apps.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Django app config for the analytics app."""
from django.apps import AppConfig
@@ -5,7 +6,7 @@
class AnalyticsAppConfig(AppConfig):
- """Analytics app init code"""
+ """Analytics app init code."""
name = 'readthedocs.analytics'
verbose_name = 'Analytics'
diff --git a/readthedocs/analytics/tasks.py b/readthedocs/analytics/tasks.py
index b79102fc6fb..02b0a7524ef 100644
--- a/readthedocs/analytics/tasks.py
+++ b/readthedocs/analytics/tasks.py
@@ -1,4 +1,5 @@
-"""Tasks for Read the Docs' analytics"""
+# -*- coding: utf-8 -*-
+"""Tasks for Read the Docs' analytics."""
from django.conf import settings
@@ -9,24 +10,24 @@
DEFAULT_PARAMETERS = {
- 'v': '1', # analytics version (always 1)
- 'aip': '1', # anonymize IP
+ 'v': '1', # analytics version (always 1)
+ 'aip': '1', # anonymize IP
'tid': settings.GLOBAL_ANALYTICS_CODE,
# User data
- 'uip': None, # User IP address
- 'ua': None, # User agent
+ 'uip': None, # User IP address
+ 'ua': None, # User agent
# Application info
'an': 'Read the Docs',
- 'av': readthedocs.__version__, # App version
+ 'av': readthedocs.__version__, # App version
}
@app.task(queue='web')
def analytics_pageview(url, title=None, **kwargs):
"""
- Send a pageview to Google Analytics
+ Send a pageview to Google Analytics.
:see: https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters
:param url: the URL of the pageview
@@ -35,8 +36,8 @@ def analytics_pageview(url, title=None, **kwargs):
"""
data = {
't': 'pageview',
- 'dl': url, # URL of the pageview (required)
- 'dt': title, # Title of the page
+ 'dl': url, # URL of the pageview (required)
+ 'dt': title, # Title of the page
}
data.update(DEFAULT_PARAMETERS)
data.update(kwargs)
@@ -44,9 +45,12 @@ def analytics_pageview(url, title=None, **kwargs):
@app.task(queue='web')
-def analytics_event(event_category, event_action, event_label=None, event_value=None, **kwargs):
+def analytics_event(
+ event_category, event_action, event_label=None, event_value=None,
+ **kwargs
+):
"""
- Send an analytics event to Google Analytics
+ Send an analytics event to Google Analytics.
:see: https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide#event
:param event_category: the category of the event
@@ -56,11 +60,11 @@ def analytics_event(event_category, event_action, event_label=None, event_value=
:param kwargs: extra event parameters to send to GA
"""
data = {
- 't': 'event', # GA event - don't change
- 'ec': event_category, # Event category (required)
- 'ea': event_action, # Event action (required)
- 'el': event_label, # Event label
- 'ev': event_value, # Event value (numeric)
+ 't': 'event', # GA event - don't change
+ 'ec': event_category, # Event category (required)
+ 'ea': event_action, # Event action (required)
+ 'el': event_label, # Event label
+ 'ev': event_value, # Event value (numeric)
}
data.update(DEFAULT_PARAMETERS)
data.update(kwargs)
diff --git a/readthedocs/analytics/utils.py b/readthedocs/analytics/utils.py
index 5c74cfd1eae..8c06e6001fd 100644
--- a/readthedocs/analytics/utils.py
+++ b/readthedocs/analytics/utils.py
@@ -1,4 +1,5 @@
-"""Utilities related to analytics"""
+# -*- coding: utf-8 -*-
+"""Utilities related to analytics."""
import hashlib
import ipaddress
@@ -11,11 +12,11 @@
from user_agents import parse
-log = logging.getLogger(__name__) # noqa
+log = logging.getLogger(__name__) # noqa
def get_client_ip(request):
- """Gets the real IP based on a request object"""
+ """Gets the real IP based on a request object."""
ip_address = request.META.get('REMOTE_ADDR')
# Get the original IP address (eg. "X-Forwarded-For: client, proxy1, proxy2")
@@ -27,7 +28,7 @@ def get_client_ip(request):
def anonymize_ip_address(ip_address):
- """Anonymizes an IP address by zeroing the last 2 bytes"""
+ """Anonymizes an IP address by zeroing the last 2 bytes."""
# Used to anonymize an IP by zero-ing out the last 2 bytes
ip_mask = int('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000', 16)
@@ -41,7 +42,7 @@ def anonymize_ip_address(ip_address):
def anonymize_user_agent(user_agent):
- """Anonymizes rare user agents"""
+ """Anonymizes rare user agents."""
# If the browser family is not recognized, this is a rare user agent
parsed_ua = parse(user_agent)
if parsed_ua.browser.family == 'Other' or parsed_ua.os.family == 'Other':
@@ -51,7 +52,7 @@ def anonymize_user_agent(user_agent):
def send_to_analytics(data):
- """Sends data to Google Analytics"""
+ """Sends data to Google Analytics."""
if data.get('uip') and data.get('ua'):
data['cid'] = generate_client_id(data['uip'], data['ua'])
@@ -69,7 +70,7 @@ def send_to_analytics(data):
resp = requests.post(
'https://www.google-analytics.com/collect',
data=data,
- timeout=3, # seconds
+ timeout=3, # seconds
)
except requests.Timeout:
log.warning('Timeout sending to Google Analytics')
@@ -80,10 +81,10 @@ def send_to_analytics(data):
def generate_client_id(ip_address, user_agent):
"""
- Create an advertising ID
+ Create an advertising ID.
- This simplifies things but essentially if a user has the same IP and same UA,
- this will treat them as the same user for analytics purposes
+ This simplifies things but essentially if a user has the same IP and same
+ UA, this will treat them as the same user for analytics purposes
"""
salt = b'advertising-client-id'
diff --git a/readthedocs/api/base.py b/readthedocs/api/base.py
index 75e984d422e..a70fb024599 100644
--- a/readthedocs/api/base.py
+++ b/readthedocs/api/base.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""API resources."""
import logging
@@ -69,7 +70,9 @@ def post_list(self, request, **kwargs):
# Force this in an ugly way, at least should do "reverse"
deserialized['users'] = ['/api/v1/user/%s/' % request.user.id]
bundle = self.build_bundle(
- data=dict_strip_unicode_keys(deserialized), request=request)
+ data=dict_strip_unicode_keys(deserialized),
+ request=request,
+ )
self.is_valid(bundle)
updated_bundle = self.obj_create(bundle, request=request)
return HttpCreated(location=self.get_resource_uri(updated_bundle))
@@ -78,14 +81,20 @@ def prepend_urls(self):
return [
url(
r'^(?P%s)/schema/$' % self._meta.resource_name,
- self.wrap_view('get_schema'), name='api_get_schema'),
+ self.wrap_view('get_schema'),
+ name='api_get_schema',
+ ),
url(
r'^(?P%s)/search%s$' %
(self._meta.resource_name, trailing_slash()),
- self.wrap_view('get_search'), name='api_get_search'),
- url((r'^(?P%s)/(?P[a-z-_]+)/$') %
- self._meta.resource_name, self.wrap_view('dispatch_detail'),
- name='api_dispatch_detail'),
+ self.wrap_view('get_search'),
+ name='api_get_search',
+ ),
+ url(
+ (r'^(?P%s)/(?P[a-z-_]+)/$') % self._meta.resource_name,
+ self.wrap_view('dispatch_detail'),
+ name='api_dispatch_detail',
+ ),
]
@@ -122,17 +131,23 @@ def prepend_urls(self):
return [
url(
r'^(?P%s)/schema/$' % self._meta.resource_name,
- self.wrap_view('get_schema'), name='api_get_schema'),
+ self.wrap_view('get_schema'),
+ name='api_get_schema',
+ ),
url(
r'^(?P%s)/(?P[a-z-_]+[a-z0-9-_]+)/$' # noqa
% self._meta.resource_name,
self.wrap_view('dispatch_list'),
- name='api_version_list'),
- url((
- r'^(?P%s)/(?P[a-z-_]+[a-z0-9-_]+)/(?P'
- r'[a-z0-9-_.]+)/build/$') %
- self._meta.resource_name, self.wrap_view('build_version'),
- name='api_version_build_slug'),
+ name='api_version_list',
+ ),
+ url(
+ (
+ r'^(?P%s)/(?P[a-z-_]+[a-z0-9-_]+)/(?P'
+ r'[a-z0-9-_.]+)/build/$'
+ ) % self._meta.resource_name,
+ self.wrap_view('build_version'),
+ name='api_version_build_slug',
+ ),
]
@@ -154,11 +169,15 @@ def prepend_urls(self):
return [
url(
r'^(?P%s)/schema/$' % self._meta.resource_name,
- self.wrap_view('get_schema'), name='api_get_schema'),
+ self.wrap_view('get_schema'),
+ name='api_get_schema',
+ ),
url(
r'^(?P%s)/anchor%s$' %
(self._meta.resource_name, trailing_slash()),
- self.wrap_view('get_anchor'), name='api_get_anchor'),
+ self.wrap_view('get_anchor'),
+ name='api_get_anchor',
+ ),
]
def get_anchor(self, request, **__):
@@ -199,9 +218,12 @@ def prepend_urls(self):
return [
url(
r'^(?P%s)/schema/$' % self._meta.resource_name,
- self.wrap_view('get_schema'), name='api_get_schema'),
+ self.wrap_view('get_schema'),
+ name='api_get_schema',
+ ),
url(
- r'^(?P%s)/(?P[a-z-_]+)/$' %
- self._meta.resource_name, self.wrap_view('dispatch_detail'),
- name='api_dispatch_detail'),
+ r'^(?P%s)/(?P[a-z-_]+)/$' % self._meta.resource_name,
+ self.wrap_view('dispatch_detail'),
+ name='api_dispatch_detail',
+ ),
]
diff --git a/readthedocs/api/utils.py b/readthedocs/api/utils.py
index e2b5e9c331a..1f112a608e9 100644
--- a/readthedocs/api/utils.py
+++ b/readthedocs/api/utils.py
@@ -1,4 +1,5 @@
-"""Utility classes for api module"""
+# -*- coding: utf-8 -*-
+"""Utility classes for api module."""
import logging
from django.utils.translation import ugettext
@@ -16,14 +17,14 @@ class PostAuthentication(BasicAuthentication):
"""Require HTTP Basic authentication for any method other than GET."""
def is_authenticated(self, request, **kwargs):
- val = super().is_authenticated(request,
- **kwargs)
- if request.method == "GET":
+ val = super().is_authenticated(request, **kwargs)
+ if request.method == 'GET':
return True
return val
class EnhancedModelResource(ModelResource):
+
def obj_get_list(self, request=None, *_, **kwargs): # noqa
"""
A ORM-specific implementation of ``obj_get_list``.
@@ -42,12 +43,16 @@ def obj_get_list(self, request=None, *_, **kwargs): # noqa
try:
return self.get_object_list(request).filter(**applicable_filters)
except ValueError as e:
- raise NotFound(ugettext("Invalid resource lookup data provided "
- "(mismatched type).: %(error)s")
- % {'error': e})
+ raise NotFound(
+ ugettext(
+ 'Invalid resource lookup data provided '
+ '(mismatched type).: %(error)s',
+ ) % {'error': e}
+ )
class OwnerAuthorization(Authorization):
+
def apply_limits(self, request, object_list):
if request and hasattr(request, 'user') and request.method != 'GET':
if request.user.is_authenticated:
diff --git a/readthedocs/builds/admin.py b/readthedocs/builds/admin.py
index 59fe235aef3..36b64339d18 100644
--- a/readthedocs/builds/admin.py
+++ b/readthedocs/builds/admin.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Django admin interface for `~builds.models.Build` and related models."""
from django.contrib import admin
@@ -12,8 +13,13 @@ class BuildCommandResultInline(admin.TabularInline):
class BuildAdmin(admin.ModelAdmin):
- fields = ('project', 'version', 'type', 'state', 'error', 'success', 'length', 'cold_storage')
- list_display = ('id', 'project', 'version_name', 'success', 'type', 'state', 'date')
+ fields = (
+ 'project', 'version', 'type', 'state', 'error', 'success', 'length',
+ 'cold_storage'
+ )
+ list_display = (
+ 'id', 'project', 'version_name', 'success', 'type', 'state', 'date'
+ )
list_filter = ('type', 'state', 'success')
list_select_related = ('project', 'version')
raw_id_fields = ('project', 'version')
@@ -26,7 +32,9 @@ def version_name(self, obj):
class VersionAdmin(GuardedModelAdmin):
search_fields = ('slug', 'project__name')
- list_display = ('slug', 'type', 'project', 'privacy_level', 'active', 'built')
+ list_display = (
+ 'slug', 'type', 'project', 'privacy_level', 'active', 'built'
+ )
list_filter = ('type', 'privacy_level', 'active', 'built')
raw_id_fields = ('project',)
diff --git a/readthedocs/builds/constants.py b/readthedocs/builds/constants.py
index 660fb0b0f4b..1175874cf19 100644
--- a/readthedocs/builds/constants.py
+++ b/readthedocs/builds/constants.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Constants for the builds app."""
from django.conf import settings
diff --git a/readthedocs/builds/forms.py b/readthedocs/builds/forms.py
index ae655bfc90e..13f72a5b952 100644
--- a/readthedocs/builds/forms.py
+++ b/readthedocs/builds/forms.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Django forms for the builds app."""
from django import forms
@@ -18,10 +19,10 @@ def clean_active(self):
if self._is_default_version() and not active:
msg = _(
'{version} is the default version of the project, '
- 'it should be active.'
+ 'it should be active.',
)
raise forms.ValidationError(
- msg.format(version=self.instance.verbose_name)
+ msg.format(version=self.instance.verbose_name),
)
return active
diff --git a/readthedocs/builds/managers.py b/readthedocs/builds/managers.py
index bd886605886..d05308cc4b5 100644
--- a/readthedocs/builds/managers.py
+++ b/readthedocs/builds/managers.py
@@ -1,4 +1,5 @@
-"""Build and Version class model Managers"""
+# -*- coding: utf-8 -*-
+"""Build and Version class model Managers."""
from django.db import models
@@ -37,7 +38,7 @@ def from_queryset(cls, queryset_class, class_name=None):
# no direct members.
queryset_class = get_override_class(
VersionQuerySet,
- VersionQuerySet._default_class # pylint: disable=protected-access
+ VersionQuerySet._default_class, # pylint: disable=protected-access
)
return super().from_queryset(queryset_class, class_name)
diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py
index c11fa13d0a0..b6a5b088125 100644
--- a/readthedocs/builds/models.py
+++ b/readthedocs/builds/models.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Models for the builds app."""
import logging
@@ -49,7 +50,10 @@
DEFAULT_VERSION_PRIVACY_LEVEL = getattr(
- settings, 'DEFAULT_VERSION_PRIVACY_LEVEL', 'public')
+ settings,
+ 'DEFAULT_VERSION_PRIVACY_LEVEL',
+ 'public',
+)
log = logging.getLogger(__name__)
@@ -89,7 +93,10 @@ class Version(models.Model):
#: filesystem to determine how the paths for this version are called. It
#: must not be used for any other identifying purposes.
slug = VersionSlugField(
- _('Slug'), max_length=255, populate_from='verbose_name')
+ _('Slug'),
+ max_length=255,
+ populate_from='verbose_name',
+ )
supported = models.BooleanField(_('Supported'), default=True)
active = models.BooleanField(_('Active'), default=False)
@@ -113,7 +120,8 @@ class Meta:
permissions = (
# Translators: Permission around whether a user can view the
# version
- ('view_version', _('View Version')),)
+ ('view_version', _('View Version')),
+ )
def __str__(self):
return ugettext(
@@ -121,7 +129,8 @@ def __str__(self):
version=self.verbose_name,
project=self.project,
pk=self.pk,
- ))
+ ),
+ )
@property
def config(self):
@@ -132,9 +141,8 @@ def config(self):
:rtype: dict
"""
last_build = (
- self.builds.filter(state='finished', success=True)
- .order_by('-date')
- .first()
+ self.builds.filter(state='finished',
+ success=True).order_by('-date').first()
)
return last_build.config
@@ -177,7 +185,9 @@ def commit_name(self):
# If we came that far it's not a special version nor a branch or tag.
# Therefore just return the identifier to make a safe guess.
- log.debug('TODO: Raise an exception here. Testing what cases it happens')
+ log.debug(
+ 'TODO: Raise an exception here. Testing what cases it happens'
+ )
return self.identifier
def get_absolute_url(self):
@@ -191,7 +201,9 @@ def get_absolute_url(self):
)
private = self.privacy_level == PRIVATE
return self.project.get_docs_url(
- version_slug=self.slug, private=private)
+ version_slug=self.slug,
+ private=private,
+ )
def save(self, *args, **kwargs): # pylint: disable=arguments-differ
"""Add permissions to the Version for all owners on save."""
@@ -200,15 +212,24 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ
for owner in self.project.users.all():
assign('view_version', owner, self)
broadcast(
- type='app', task=tasks.symlink_project, args=[self.project.pk])
+ type='app',
+ task=tasks.symlink_project,
+ args=[self.project.pk],
+ )
return obj
def delete(self, *args, **kwargs): # pylint: disable=arguments-differ
from readthedocs.projects import tasks
log.info('Removing files for version %s', self.slug)
- broadcast(type='app', task=tasks.clear_artifacts, args=[self.get_artifact_paths()])
broadcast(
- type='app', task=tasks.symlink_project, args=[self.project.pk])
+ type='app', task=tasks.clear_artifacts,
+ args=[self.get_artifact_paths()]
+ )
+ broadcast(
+ type='app',
+ task=tasks.symlink_project,
+ args=[self.project.pk],
+ )
super().delete(*args, **kwargs)
@property
@@ -238,19 +259,27 @@ def get_downloads(self, pretty=False):
data['PDF'] = project.get_production_media_url('pdf', self.slug)
if project.has_htmlzip(self.slug):
data['HTML'] = project.get_production_media_url(
- 'htmlzip', self.slug)
+ 'htmlzip',
+ self.slug,
+ )
if project.has_epub(self.slug):
data['Epub'] = project.get_production_media_url(
- 'epub', self.slug)
+ 'epub',
+ self.slug,
+ )
else:
if project.has_pdf(self.slug):
data['pdf'] = project.get_production_media_url('pdf', self.slug)
if project.has_htmlzip(self.slug):
data['htmlzip'] = project.get_production_media_url(
- 'htmlzip', self.slug)
+ 'htmlzip',
+ self.slug,
+ )
if project.has_epub(self.slug):
data['epub'] = project.get_production_media_url(
- 'epub', self.slug)
+ 'epub',
+ self.slug,
+ )
return data
def get_conf_py_path(self):
@@ -278,7 +307,8 @@ def get_artifact_paths(self):
paths.append(
self.project.get_production_media_path(
type_=type_,
- version_slug=self.slug),
+ version_slug=self.slug,
+ ),
)
paths.append(self.project.rtd_build_path(version=self.slug))
@@ -300,7 +330,12 @@ def clean_build_path(self):
log.exception('Build path cleanup failed')
def get_github_url(
- self, docroot, filename, source_suffix='.rst', action='view'):
+ self,
+ docroot,
+ filename,
+ source_suffix='.rst',
+ action='view',
+ ):
"""
Return a GitHub URL for a given filename.
@@ -342,7 +377,12 @@ def get_github_url(
)
def get_gitlab_url(
- self, docroot, filename, source_suffix='.rst', action='view'):
+ self,
+ docroot,
+ filename,
+ source_suffix='.rst',
+ action='view',
+ ):
repo_url = self.project.repo
if 'gitlab' not in repo_url:
return ''
@@ -439,13 +479,28 @@ class Build(models.Model):
"""Build data."""
project = models.ForeignKey(
- Project, verbose_name=_('Project'), related_name='builds')
+ Project,
+ verbose_name=_('Project'),
+ related_name='builds',
+ )
version = models.ForeignKey(
- Version, verbose_name=_('Version'), null=True, related_name='builds')
+ Version,
+ verbose_name=_('Version'),
+ null=True,
+ related_name='builds',
+ )
type = models.CharField(
- _('Type'), max_length=55, choices=BUILD_TYPES, default='html')
+ _('Type'),
+ max_length=55,
+ choices=BUILD_TYPES,
+ default='html',
+ )
state = models.CharField(
- _('State'), max_length=55, choices=BUILD_STATE, default='finished')
+ _('State'),
+ max_length=55,
+ choices=BUILD_STATE,
+ default='finished',
+ )
date = models.DateTimeField(_('Date'), auto_now_add=True)
success = models.BooleanField(_('Success'), default=True)
@@ -455,16 +510,26 @@ class Build(models.Model):
error = models.TextField(_('Error'), default='', blank=True)
exit_code = models.IntegerField(_('Exit code'), null=True, blank=True)
commit = models.CharField(
- _('Commit'), max_length=255, null=True, blank=True)
+ _('Commit'),
+ max_length=255,
+ null=True,
+ blank=True,
+ )
_config = JSONField(_('Configuration used in the build'), default=dict)
length = models.IntegerField(_('Build Length'), null=True, blank=True)
builder = models.CharField(
- _('Builder'), max_length=255, null=True, blank=True)
+ _('Builder'),
+ max_length=255,
+ null=True,
+ blank=True,
+ )
cold_storage = models.NullBooleanField(
- _('Cold Storage'), help_text='Build steps stored outside the database.')
+ _('Cold Storage'),
+ help_text='Build steps stored outside the database.',
+ )
# Manager
@@ -491,14 +556,11 @@ def previous(self):
date = self.date or timezone.now()
if self.project is not None and self.version is not None:
return (
- Build.objects
- .filter(
+ Build.objects.filter(
project=self.project,
version=self.version,
date__lt=date,
- )
- .order_by('-date')
- .first()
+ ).order_by('-date').first()
)
return None
@@ -508,9 +570,9 @@ def config(self):
Get the config used for this build.
Since we are saving the config into the JSON field only when it differs
- from the previous one, this helper returns the correct JSON used in
- this Build object (it could be stored in this object or one of the
- previous ones).
+ from the previous one, this helper returns the correct JSON used in this
+ Build object (it could be stored in this object or one of the previous
+ ones).
"""
if self.CONFIG_KEY in self._config:
return Build.objects.get(pk=self._config[self.CONFIG_KEY])._config
@@ -538,8 +600,8 @@ def save(self, *args, **kwargs): # noqa
"""
if self.pk is None or self._config_changed:
previous = self.previous
- if (previous is not None and
- self._config and self._config == previous.config):
+ if (previous is not None and self._config and
+ self._config == previous.config):
previous_pk = previous._config.get(self.CONFIG_KEY, previous.pk)
self._config = {self.CONFIG_KEY: previous_pk}
super().save(*args, **kwargs)
@@ -553,7 +615,8 @@ def __str__(self):
self.project.users.all().values_list('username', flat=True),
),
pk=self.pk,
- ))
+ ),
+ )
def get_absolute_url(self):
return reverse('builds_detail', args=[self.project.slug, self.pk])
@@ -594,7 +657,10 @@ class BuildCommandResult(BuildCommandResultMixin, models.Model):
"""Build command for a ``Build``."""
build = models.ForeignKey(
- Build, verbose_name=_('Build'), related_name='commands')
+ Build,
+ verbose_name=_('Build'),
+ related_name='commands',
+ )
command = models.TextField(_('Command'))
description = models.TextField(_('Description'), blank=True)
@@ -613,7 +679,8 @@ class Meta:
def __str__(self):
return (
ugettext('Build command {pk} for build {build}')
- .format(pk=self.pk, build=self.build))
+ .format(pk=self.pk, build=self.build)
+ )
@property
def run_time(self):
diff --git a/readthedocs/builds/querysets.py b/readthedocs/builds/querysets.py
index a2a34938836..5c3778257e5 100644
--- a/readthedocs/builds/querysets.py
+++ b/readthedocs/builds/querysets.py
@@ -1,4 +1,5 @@
-"""Build and Version QuerySet classes"""
+# -*- coding: utf-8 -*-
+"""Build and Version QuerySet classes."""
from django.db import models
from guardian.shortcuts import get_objects_for_user
@@ -35,7 +36,9 @@ def public(self, user=None, project=None, only_active=True):
return queryset
def protected(self, user=None, project=None, only_active=True):
- queryset = self.filter(privacy_level__in=[constants.PUBLIC, constants.PROTECTED])
+ queryset = self.filter(
+ privacy_level__in=[constants.PUBLIC, constants.PROTECTED]
+ )
if user:
queryset = self._add_user_repos(queryset, user)
if project:
@@ -58,10 +61,10 @@ def api(self, user=None):
return self.public(user, only_active=False)
def for_project(self, project):
- """Return all versions for a project, including translations"""
+ """Return all versions for a project, including translations."""
return self.filter(
models.Q(project=project) |
- models.Q(project__main_language_project=project)
+ models.Q(project__main_language_project=project),
)
@@ -117,8 +120,7 @@ def _add_user_repos(self, queryset, user=None):
if user.is_authenticated:
user_queryset = get_objects_for_user(user, 'builds.view_version')
pks = user_queryset.values_list('pk', flat=True)
- queryset = self.filter(
- build__version__pk__in=pks) | queryset
+ queryset = self.filter(build__version__pk__in=pks,) | queryset
return queryset.distinct()
def public(self, user=None, project=None):
diff --git a/readthedocs/builds/signals.py b/readthedocs/builds/signals.py
index c9bb49cc7cc..fb16ca75dd6 100644
--- a/readthedocs/builds/signals.py
+++ b/readthedocs/builds/signals.py
@@ -1,4 +1,5 @@
-"""Build signals"""
+# -*- coding: utf-8 -*-
+"""Build signals."""
import django.dispatch
diff --git a/readthedocs/builds/syncers.py b/readthedocs/builds/syncers.py
index 5f02f966bf7..185e0d30934 100644
--- a/readthedocs/builds/syncers.py
+++ b/readthedocs/builds/syncers.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
Classes to copy files between build and web servers.
@@ -21,7 +22,7 @@
class BaseSyncer:
- """A base object for syncers and pullers"""
+ """A base object for syncers and pullers."""
@classmethod
def copy(cls, path, target, is_file=False, **kwargs):
@@ -33,7 +34,7 @@ class LocalSyncer(BaseSyncer):
@classmethod
def copy(cls, path, target, is_file=False, **kwargs):
"""A copy command that works with files or directories."""
- log.info("Local Copy %s to %s", path, target)
+ log.info('Local Copy %s to %s', path, target)
if is_file:
if path == target:
# Don't copy the same file over itself
@@ -59,28 +60,31 @@ def copy(cls, path, target, is_file=False, **kwargs):
sync_user = getattr(settings, 'SYNC_USER', getpass.getuser())
app_servers = getattr(settings, 'MULTIPLE_APP_SERVERS', [])
if app_servers:
- log.info("Remote Copy %s to %s on %s", path, target, app_servers)
+ log.info('Remote Copy %s to %s on %s', path, target, app_servers)
for server in app_servers:
- mkdir_cmd = ("ssh {}@{} mkdir -p {}".format(sync_user, server, target))
+ mkdir_cmd = (
+ 'ssh {}@{} mkdir -p {}'.format(sync_user, server, target)
+ )
ret = os.system(mkdir_cmd)
if ret != 0:
- log.debug("Copy error to app servers: cmd=%s", mkdir_cmd)
+ log.debug('Copy error to app servers: cmd=%s', mkdir_cmd)
if is_file:
- slash = ""
+ slash = ''
else:
- slash = "/"
+ slash = '/'
# Add a slash when copying directories
sync_cmd = (
- "rsync -e 'ssh -T' -av --delete {path}{slash} {user}@{server}:{target}"
- .format(
+ "rsync -e 'ssh -T' -av --delete {path}{slash} {user}@{server}:{target}".format(
path=path,
slash=slash,
user=sync_user,
server=server,
- target=target))
+ target=target,
+ )
+ )
ret = os.system(sync_cmd)
if ret != 0:
- log.debug("Copy error to app servers: cmd=%s", sync_cmd)
+ log.debug('Copy error to app servers: cmd=%s', sync_cmd)
class DoubleRemotePuller(BaseSyncer):
@@ -95,29 +99,32 @@ def copy(cls, path, target, host, is_file=False, **kwargs): # pylint: disable=a
sync_user = getattr(settings, 'SYNC_USER', getpass.getuser())
app_servers = getattr(settings, 'MULTIPLE_APP_SERVERS', [])
if not is_file:
- path += "/"
- log.info("Remote Copy %s to %s", path, target)
+ path += '/'
+ log.info('Remote Copy %s to %s', path, target)
for server in app_servers:
if not is_file:
- mkdir_cmd = "ssh {user}@{server} mkdir -p {target}".format(
- user=sync_user, server=server, target=target
+ mkdir_cmd = 'ssh {user}@{server} mkdir -p {target}'.format(
+ user=sync_user,
+ server=server,
+ target=target,
)
ret = os.system(mkdir_cmd)
if ret != 0:
- log.debug("MkDir error to app servers: cmd=%s", mkdir_cmd)
+ log.debug('MkDir error to app servers: cmd=%s', mkdir_cmd)
# Add a slash when copying directories
sync_cmd = (
"ssh {user}@{server} 'rsync -av "
- "--delete --exclude projects {user}@{host}:{path} {target}'"
- .format(
+ "--delete --exclude projects {user}@{host}:{path} {target}'".format(
host=host,
path=path,
user=sync_user,
server=server,
- target=target))
+ target=target,
+ )
+ )
ret = os.system(sync_cmd)
if ret != 0:
- log.debug("Copy error to app servers: cmd=%s", sync_cmd)
+ log.debug('Copy error to app servers: cmd=%s', sync_cmd)
class RemotePuller(BaseSyncer):
@@ -131,8 +138,8 @@ def copy(cls, path, target, host, is_file=False, **kwargs): # pylint: disable=a
"""
sync_user = getattr(settings, 'SYNC_USER', getpass.getuser())
if not is_file:
- path += "/"
- log.info("Remote Pull %s to %s", path, target)
+ path += '/'
+ log.info('Remote Pull %s to %s', path, target)
if not is_file and not os.path.exists(target):
safe_makedirs(target)
# Add a slash when copying directories
@@ -145,7 +152,7 @@ def copy(cls, path, target, host, is_file=False, **kwargs): # pylint: disable=a
ret = os.system(sync_cmd)
if ret != 0:
log.debug(
- "Copy error to app servers. Command: [%s] Return: [%s]",
+ 'Copy error to app servers. Command: [%s] Return: [%s]',
sync_cmd,
ret,
)
diff --git a/readthedocs/builds/urls.py b/readthedocs/builds/urls.py
index 07f787d0b4b..9c421662047 100644
--- a/readthedocs/builds/urls.py
+++ b/readthedocs/builds/urls.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""URL configuration for builds app."""
from django.conf.urls import url
diff --git a/readthedocs/builds/utils.py b/readthedocs/builds/utils.py
index d2d49270cb2..e0025dc77ae 100644
--- a/readthedocs/builds/utils.py
+++ b/readthedocs/builds/utils.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Utilities for the builds app."""
from readthedocs.projects.constants import (
diff --git a/readthedocs/builds/version_slug.py b/readthedocs/builds/version_slug.py
index 18ad3d19f99..d3e8bbe14b7 100644
--- a/readthedocs/builds/version_slug.py
+++ b/readthedocs/builds/version_slug.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
Contains logic for handling version slugs.
@@ -34,12 +35,10 @@ def get_fields_with_model(cls):
prescrived in the Django docs.
https://docs.djangoproject.com/en/1.11/ref/models/meta/#migrating-from-the-old-api
"""
- return [
- (f, f.model if f.model != cls else None)
- for f in cls._meta.get_fields()
- if not f.is_relation or f.one_to_one or
- (f.many_to_one and f.related_model)
- ]
+ return [(f, f.model if f.model != cls else None)
+ for f in cls._meta.get_fields()
+ if not f.is_relation or f.one_to_one or
+ (f.many_to_one and f.related_model)]
# Regex breakdown:
@@ -165,7 +164,8 @@ def create_slug(self, model_instance):
count += 1
assert self.test_pattern.match(slug), (
- 'Invalid generated slug: {slug}'.format(slug=slug))
+ 'Invalid generated slug: {slug}'.format(slug=slug)
+ )
return slug
def pre_save(self, model_instance, add):
diff --git a/readthedocs/builds/views.py b/readthedocs/builds/views.py
index 37a46ae3ece..69ed90dadd8 100644
--- a/readthedocs/builds/views.py
+++ b/readthedocs/builds/views.py
@@ -35,7 +35,8 @@ def get_queryset(self):
slug=self.project_slug,
)
queryset = Build.objects.public(
- user=self.request.user, project=self.project
+ user=self.request.user,
+ project=self.project,
)
return queryset
@@ -57,7 +58,9 @@ def post(self, request, project_slug):
slug=version_slug,
)
- update_docs_task, build = trigger_build(project=project, version=version)
+ update_docs_task, build = trigger_build(
+ project=project, version=version
+ )
if (update_docs_task, build) == (None, None):
# Build was skipped
messages.add_message(
@@ -79,13 +82,15 @@ class BuildList(BuildBase, BuildTriggerMixin, ListView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- active_builds = self.get_queryset().exclude(state='finished'
- ).values('id')
+ active_builds = self.get_queryset().exclude(
+ state='finished',
+ ).values('id')
context['project'] = self.project
context['active_builds'] = active_builds
context['versions'] = Version.objects.public(
- user=self.request.user, project=self.project
+ user=self.request.user,
+ project=self.project,
)
context['build_qs'] = self.get_queryset()
@@ -106,11 +111,11 @@ def get_context_data(self, **kwargs):
def builds_redirect_list(request, project_slug): # pylint: disable=unused-argument
return HttpResponsePermanentRedirect(
- reverse('builds_project_list', args=[project_slug])
+ reverse('builds_project_list', args=[project_slug]),
)
def builds_redirect_detail(request, project_slug, pk): # pylint: disable=unused-argument
return HttpResponsePermanentRedirect(
- reverse('builds_detail', args=[project_slug, pk])
+ reverse('builds_detail', args=[project_slug, pk]),
)
diff --git a/readthedocs/config/__init__.py b/readthedocs/config/__init__.py
index 314f6c394cc..fd6326a50e7 100644
--- a/readthedocs/config/__init__.py
+++ b/readthedocs/config/__init__.py
@@ -1,2 +1,3 @@
+# -*- coding: utf-8 -*-
from .config import * # noqa
from .parser import * # noqa
diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py
index 57e01439e0a..a397544e99b 100644
--- a/readthedocs/config/config.py
+++ b/readthedocs/config/config.py
@@ -96,7 +96,7 @@ def __init__(self, configuration):
)
super().__init__(
template.format(self.configuration),
- CONFIG_NOT_SUPPORTED
+ CONFIG_NOT_SUPPORTED,
)
@@ -137,9 +137,15 @@ class BuildConfigBase:
"""
PUBLIC_ATTRIBUTES = [
- 'version', 'formats', 'python',
- 'conda', 'build', 'doctype',
- 'sphinx', 'mkdocs', 'submodules',
+ 'version',
+ 'formats',
+ 'python',
+ 'conda',
+ 'build',
+ 'doctype',
+ 'sphinx',
+ 'mkdocs',
+ 'submodules',
]
version = None
@@ -235,9 +241,7 @@ def python_full_version(self):
# Get the highest version of the major series version if user only
# gave us a version of '2', or '3'
ver = max(
- v
- for v in self.get_valid_python_versions()
- if v < ver + 1
+ v for v in self.get_valid_python_versions() if v < ver + 1
)
return ver
@@ -324,11 +328,12 @@ def validate(self):
def validate_output_base(self):
"""Validates that ``output_base`` exists and set its absolute path."""
assert 'output_base' in self.env_config, (
- '"output_base" required in "env_config"')
+ '"output_base" required in "env_config"'
+ )
output_base = os.path.abspath(
os.path.join(
self.env_config.get('output_base', self.base_path),
- )
+ ),
)
return output_base
@@ -345,8 +350,10 @@ def validate_name(self):
'name',
self.NAME_INVALID_MESSAGE.format(
name=name,
- name_re=name_re),
- code=NAME_INVALID)
+ name_re=name_re,
+ ),
+ code=NAME_INVALID,
+ )
return name
@@ -395,18 +402,16 @@ def validate_build(self):
# Prepend proper image name to user's image name
build['image'] = '{}:{}'.format(
DOCKER_DEFAULT_IMAGE,
- build['image']
+ build['image'],
)
# Update docker default settings from image name
if build['image'] in DOCKER_IMAGE_SETTINGS:
- self.env_config.update(
- DOCKER_IMAGE_SETTINGS[build['image']]
- )
+ self.env_config.update(DOCKER_IMAGE_SETTINGS[build['image']],)
# Update docker settings from user config
if 'DOCKER_IMAGE_SETTINGS' in self.env_config and \
build['image'] in self.env_config['DOCKER_IMAGE_SETTINGS']:
self.env_config.update(
- self.env_config['DOCKER_IMAGE_SETTINGS'][build['image']]
+ self.env_config['DOCKER_IMAGE_SETTINGS'][build['image']],
)
# Allow to override specific project
@@ -434,20 +439,22 @@ def validate_python(self):
self.error(
'python',
self.PYTHON_INVALID_MESSAGE,
- code=PYTHON_INVALID)
+ code=PYTHON_INVALID,
+ )
# Validate use_system_site_packages.
if 'use_system_site_packages' in raw_python:
- with self.catch_validation_error(
- 'python.use_system_site_packages'):
+ with self.catch_validation_error('python.use_system_site_packages',):
python['use_system_site_packages'] = validate_bool(
- raw_python['use_system_site_packages'])
+ raw_python['use_system_site_packages'],
+ )
# Validate pip_install.
if 'pip_install' in raw_python:
with self.catch_validation_error('python.pip_install'):
python['install_with_pip'] = validate_bool(
- raw_python['pip_install'])
+ raw_python['pip_install'],
+ )
# Validate extra_requirements.
if 'extra_requirements' in raw_python:
@@ -456,22 +463,23 @@ def validate_python(self):
self.error(
'python.extra_requirements',
self.PYTHON_EXTRA_REQUIREMENTS_INVALID_MESSAGE,
- code=PYTHON_INVALID)
+ code=PYTHON_INVALID,
+ )
if not python['install_with_pip']:
python['extra_requirements'] = []
else:
for extra_name in raw_extra_requirements:
- with self.catch_validation_error(
- 'python.extra_requirements'):
+ with self.catch_validation_error('python.extra_requirements',):
python['extra_requirements'].append(
- validate_string(extra_name)
+ validate_string(extra_name),
)
# Validate setup_py_install.
if 'setup_py_install' in raw_python:
with self.catch_validation_error('python.setup_py_install'):
python['install_with_setup'] = validate_bool(
- raw_python['setup_py_install'])
+ raw_python['setup_py_install'],
+ )
if 'version' in raw_python:
with self.catch_validation_error('python.version'):
@@ -505,7 +513,8 @@ def validate_conda(self):
if 'file' in raw_conda:
with self.catch_validation_error('conda.file'):
conda_environment = validate_file(
- raw_conda['file'], self.base_path
+ raw_conda['file'],
+ self.base_path,
)
conda['environment'] = conda_environment
@@ -758,7 +767,8 @@ def validate_python(self):
with self.catch_validation_error('python.extra_requirements'):
extra_requirements = self.pop_config(
- 'python.extra_requirements', []
+ 'python.extra_requirements',
+ [],
)
extra_requirements = validate_list(extra_requirements)
if extra_requirements and not python['install_with_pip']:
@@ -876,7 +886,8 @@ def validate_sphinx(self):
if not configuration:
configuration = None
configuration = self.pop_config(
- 'sphinx.configuration', configuration
+ 'sphinx.configuration',
+ configuration,
)
if configuration is not None:
configuration = validate_file(configuration, self.base_path)
@@ -892,9 +903,8 @@ def validate_final_doc_type(self):
"""
Validates that the doctype is the same as the admin panel.
- This a temporal validation, as the configuration file
- should support per version doctype, but we need to
- adapt the rtd code for that.
+ This a temporal validation, as the configuration file should support per
+ version doctype, but we need to adapt the rtd code for that.
"""
dashboard_doctype = self.defaults.get('doctype', 'sphinx')
if self.doctype != dashboard_doctype:
@@ -904,7 +914,7 @@ def validate_final_doc_type(self):
if dashboard_doctype == 'mkdocs' or not self.sphinx:
error_msg += ' but there is no "{}" key specified.'.format(
- 'mkdocs' if dashboard_doctype == 'mkdocs' else 'sphinx'
+ 'mkdocs' if dashboard_doctype == 'mkdocs' else 'sphinx',
)
else:
error_msg += ' but your "sphinx.builder" key does not match.'
@@ -966,8 +976,8 @@ def validate_keys(self):
"""
Checks that we don't have extra keys (invalid ones).
- This should be called after all the validations are done
- and all keys are popped from `self.raw_config`.
+ This should be called after all the validations are done and all keys
+ are popped from `self.raw_config`.
"""
msg = (
'Invalid configuration option: {}. '
@@ -1059,7 +1069,7 @@ def load(path, env_config):
if not filename:
raise ConfigError(
'No configuration file found',
- code=CONFIG_REQUIRED
+ code=CONFIG_REQUIRED,
)
with open(filename, 'r') as configuration_file:
try:
diff --git a/readthedocs/config/find.py b/readthedocs/config/find.py
index ed49c765c1d..6fc3cb6a7a9 100644
--- a/readthedocs/config/find.py
+++ b/readthedocs/config/find.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Helper functions to search files."""
import os
diff --git a/readthedocs/config/models.py b/readthedocs/config/models.py
index ce0604f18b7..1153fe3f981 100644
--- a/readthedocs/config/models.py
+++ b/readthedocs/config/models.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Models for the response of the configuration object."""
from collections import namedtuple
diff --git a/readthedocs/config/parser.py b/readthedocs/config/parser.py
index 85e31782b00..376774d6144 100644
--- a/readthedocs/config/parser.py
+++ b/readthedocs/config/parser.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""YAML parser for the RTD configuration file."""
import yaml
@@ -11,8 +12,6 @@ class ParseError(Exception):
"""Parser related errors."""
- pass
-
def parse(stream):
"""
diff --git a/readthedocs/config/validation.py b/readthedocs/config/validation.py
index c366b05073a..b1840b880aa 100644
--- a/readthedocs/config/validation.py
+++ b/readthedocs/config/validation.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Validations for the RTD configuration file."""
import os
@@ -26,7 +27,7 @@ class ValidationError(Exception):
INVALID_PATH: 'path {value} does not exist',
INVALID_STRING: 'expected string',
INVALID_LIST: 'expected list',
- VALUE_NOT_FOUND: '{value} not found'
+ VALUE_NOT_FOUND: '{value} not found',
}
def __init__(self, value, code, format_kwargs=None):
@@ -60,9 +61,13 @@ def validate_choice(value, choices):
"""Check that ``value`` is in ``choices``."""
choices = validate_list(choices)
if value not in choices:
- raise ValidationError(value, INVALID_CHOICE, {
- 'choices': ', '.join(map(str, choices))
- })
+ raise ValidationError(
+ value,
+ INVALID_CHOICE,
+ {
+ 'choices': ', '.join(map(str, choices)),
+ },
+ )
return value
diff --git a/readthedocs/constants.py b/readthedocs/constants.py
index 35cca627432..485b937398d 100644
--- a/readthedocs/constants.py
+++ b/readthedocs/constants.py
@@ -1,4 +1,5 @@
-"""Common constants"""
+# -*- coding: utf-8 -*-
+"""Common constants."""
from readthedocs.builds.version_slug import VERSION_SLUG_REGEX
from readthedocs.projects.constants import LANGUAGES_REGEX, PROJECT_SLUG_REGEX
diff --git a/readthedocs/core/__init__.py b/readthedocs/core/__init__.py
index ed1c53debf0..c6205d82c60 100644
--- a/readthedocs/core/__init__.py
+++ b/readthedocs/core/__init__.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""App initialization."""
default_app_config = 'readthedocs.core.apps.CoreAppConfig'
diff --git a/readthedocs/core/adapters.py b/readthedocs/core/adapters.py
index 803952a792d..aa2fad046dc 100644
--- a/readthedocs/core/adapters.py
+++ b/readthedocs/core/adapters.py
@@ -1,4 +1,5 @@
-"""Allauth overrides"""
+# -*- coding: utf-8 -*-
+"""Allauth overrides."""
import json
import logging
@@ -19,16 +20,17 @@
class AccountAdapter(DefaultAccountAdapter):
- """Customize Allauth emails to match our current patterns"""
+ """Customize Allauth emails to match our current patterns."""
def format_email_subject(self, subject):
return force_text(subject)
def send_mail(self, template_prefix, email, context):
subject = render_to_string(
- '{}_subject.txt'.format(template_prefix), context
+ '{}_subject.txt'.format(template_prefix),
+ context,
)
- subject = " ".join(subject.splitlines()).strip()
+ subject = ' '.join(subject.splitlines()).strip()
subject = self.format_email_subject(subject)
# Allauth sends some additional data in the context, remove it if the
@@ -41,13 +43,15 @@ def send_mail(self, template_prefix, email, context):
removed_keys.append(key)
del context[key]
if removed_keys:
- log.debug('Removed context we were unable to serialize: %s',
- removed_keys)
+ log.debug(
+ 'Removed context we were unable to serialize: %s',
+ removed_keys,
+ )
send_email(
recipient=email,
subject=subject,
template='{}_message.txt'.format(template_prefix),
template_html='{}_message.html'.format(template_prefix),
- context=context
+ context=context,
)
diff --git a/readthedocs/core/admin.py b/readthedocs/core/admin.py
index 5e8042f1202..da3ebfa15b9 100644
--- a/readthedocs/core/admin.py
+++ b/readthedocs/core/admin.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Django admin interface for core models."""
from datetime import timedelta
@@ -58,8 +59,14 @@ class UserAdminExtra(UserAdmin):
"""Admin configuration for User."""
- list_display = ('username', 'email', 'first_name',
- 'last_name', 'is_staff', 'is_banned')
+ list_display = (
+ 'username',
+ 'email',
+ 'first_name',
+ 'last_name',
+ 'is_staff',
+ 'is_banned',
+ )
list_filter = (UserProjectFilter,) + UserAdmin.list_filter
actions = ['ban_user']
inlines = [UserProjectInline]
diff --git a/readthedocs/core/apps.py b/readthedocs/core/apps.py
index d422684ba03..0944efc1646 100644
--- a/readthedocs/core/apps.py
+++ b/readthedocs/core/apps.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""App configurations for core app."""
from django.apps import AppConfig
diff --git a/readthedocs/core/backends.py b/readthedocs/core/backends.py
index 64258fe91eb..381be54dc86 100644
--- a/readthedocs/core/backends.py
+++ b/readthedocs/core/backends.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Email backends for core app."""
import smtplib
@@ -12,8 +13,11 @@ def open(self):
if self.connection:
return False
try:
- self.connection = smtplib.SMTP_SSL(self.host, self.port,
- local_hostname=DNS_NAME.get_fqdn())
+ self.connection = smtplib.SMTP_SSL(
+ self.host,
+ self.port,
+ local_hostname=DNS_NAME.get_fqdn(),
+ )
if self.username and self.password:
self.connection.login(self.username, self.password)
return True
diff --git a/readthedocs/core/context_processors.py b/readthedocs/core/context_processors.py
index dc136c51171..815afbdaa6a 100644
--- a/readthedocs/core/context_processors.py
+++ b/readthedocs/core/context_processors.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Template context processors for core app."""
from django.conf import settings
@@ -10,10 +11,14 @@ def readthedocs_processor(request):
'PRODUCTION_DOMAIN': getattr(settings, 'PRODUCTION_DOMAIN', None),
'USE_SUBDOMAINS': getattr(settings, 'USE_SUBDOMAINS', None),
'GLOBAL_ANALYTICS_CODE': getattr(settings, 'GLOBAL_ANALYTICS_CODE'),
- 'DASHBOARD_ANALYTICS_CODE': getattr(settings, 'DASHBOARD_ANALYTICS_CODE'),
+ 'DASHBOARD_ANALYTICS_CODE': getattr(
+ settings, 'DASHBOARD_ANALYTICS_CODE'
+ ),
'SITE_ROOT': getattr(settings, 'SITE_ROOT', '') + '/',
'TEMPLATE_ROOT': getattr(settings, 'TEMPLATE_ROOT', '') + '/',
- 'DO_NOT_TRACK_ENABLED': getattr(settings, 'DO_NOT_TRACK_ENABLED', False),
+ 'DO_NOT_TRACK_ENABLED': getattr(
+ settings, 'DO_NOT_TRACK_ENABLED', False
+ ),
'USE_PROMOS': getattr(settings, 'USE_PROMOS', False),
}
return exports
diff --git a/readthedocs/core/fields.py b/readthedocs/core/fields.py
index 822ad5ca151..1b6d9f99420 100644
--- a/readthedocs/core/fields.py
+++ b/readthedocs/core/fields.py
@@ -1,9 +1,10 @@
-"""Shared model fields and defaults"""
+# -*- coding: utf-8 -*-
+"""Shared model fields and defaults."""
import binascii
import os
def default_token():
- """Generate default value for token field"""
+ """Generate default value for token field."""
return binascii.hexlify(os.urandom(20)).decode()
diff --git a/readthedocs/core/fixtures/flag_types.json b/readthedocs/core/fixtures/flag_types.json
index afe8ef1cd79..52f13740581 100644
--- a/readthedocs/core/fixtures/flag_types.json
+++ b/readthedocs/core/fixtures/flag_types.json
@@ -1,28 +1,28 @@
[
{
- "pk": 1,
- "model": "flagging.flagtype",
+ "pk": 1,
+ "model": "flagging.flagtype",
"fields": {
- "description": "This item is inappropriate to the purpose of the site",
- "slug": "inappropriate",
+ "description": "This item is inappropriate to the purpose of the site",
+ "slug": "inappropriate",
"title": "Inappropriate"
}
- },
+ },
{
- "pk": 2,
- "model": "flagging.flagtype",
+ "pk": 2,
+ "model": "flagging.flagtype",
"fields": {
- "description": "This item is spam",
- "slug": "spam",
+ "description": "This item is spam",
+ "slug": "spam",
"title": "Spam"
}
- },
+ },
{
- "pk": 3,
- "model": "flagging.flagtype",
+ "pk": 3,
+ "model": "flagging.flagtype",
"fields": {
- "description": "These docs are a duplicate of other, official docs, on the site",
- "slug": "duplicate",
+ "description": "These docs are a duplicate of other, official docs, on the site",
+ "slug": "duplicate",
"title": "Duplicate"
}
}
diff --git a/readthedocs/core/forms.py b/readthedocs/core/forms.py
index 66c09e84b93..34ebfbd0d2e 100644
--- a/readthedocs/core/forms.py
+++ b/readthedocs/core/forms.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Forms for core app."""
import logging
@@ -63,6 +64,7 @@ def clean_username(self):
class UserAdvertisingForm(forms.ModelForm):
+
class Meta:
model = UserProfile
fields = ['allow_ads']
diff --git a/readthedocs/core/management/commands/archive.py b/readthedocs/core/management/commands/archive.py
index 3402d1823b4..e90d8de00a0 100644
--- a/readthedocs/core/management/commands/archive.py
+++ b/readthedocs/core/management/commands/archive.py
@@ -1,4 +1,5 @@
-"""Rebuild documentation for all projects"""
+# -*- coding: utf-8 -*-
+"""Rebuild documentation for all projects."""
import logging
import os
@@ -20,10 +21,10 @@ def handle(self, *args, **options):
doc_index = {}
os.chdir(settings.DOCROOT)
- for directory in glob("*"):
+ for directory in glob('*'):
doc_index[directory] = []
path = os.path.join(directory, 'rtd-builds')
- for version in glob(os.path.join(path, "*")):
+ for version in glob(os.path.join(path, '*')):
v = version.replace(path + '/', '')
doc_index[directory].append(v)
@@ -31,5 +32,6 @@ def handle(self, *args, **options):
'doc_index': doc_index,
'MEDIA_URL': settings.MEDIA_URL,
}
- html = template_loader.get_template('archive/index.html').render(context)
+ html = template_loader.get_template('archive/index.html'
+ ).render(context)
print(html)
diff --git a/readthedocs/core/management/commands/clean_builds.py b/readthedocs/core/management/commands/clean_builds.py
index 1a48748a963..64d52ec84a0 100644
--- a/readthedocs/core/management/commands/clean_builds.py
+++ b/readthedocs/core/management/commands/clean_builds.py
@@ -1,8 +1,8 @@
-"""Clean up stable build paths per project version"""
+# -*- coding: utf-8 -*-
+"""Clean up stable build paths per project version."""
import logging
from datetime import timedelta
-from optparse import make_option
from django.core.management.base import BaseCommand
from django.db.models import Max
@@ -24,24 +24,24 @@ def add_arguments(self, parser):
dest='days',
type='int',
default=365,
- help='Find builds older than DAYS days, default: 365'
+ help='Find builds older than DAYS days, default: 365',
)
parser.add_argument(
'--dryrun',
action='store_true',
dest='dryrun',
- help='Perform dry run on build cleanup'
+ help='Perform dry run on build cleanup',
)
def handle(self, *args, **options):
- """Find stale builds and remove build paths"""
+ """Find stale builds and remove build paths."""
max_date = timezone.now() - timedelta(days=options['days'])
- queryset = (Build.objects
- .values('project', 'version')
- .annotate(max_date=Max('date'))
- .filter(max_date__lt=max_date)
- .order_by('-max_date'))
+ queryset = (
+ Build.objects.values('project', 'version').annotate(
+ max_date=Max('date')
+ ).filter(max_date__lt=max_date).order_by('-max_date')
+ )
for build in queryset:
try:
# Get version from build version id, perform sanity check on
diff --git a/readthedocs/core/management/commands/import_github.py b/readthedocs/core/management/commands/import_github.py
index 2e2e49176b7..85530968f4b 100644
--- a/readthedocs/core/management/commands/import_github.py
+++ b/readthedocs/core/management/commands/import_github.py
@@ -1,4 +1,5 @@
-"""Resync GitHub project for user"""
+# -*- coding: utf-8 -*-
+"""Resync GitHub project for user."""
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
@@ -13,7 +14,6 @@ class Command(BaseCommand):
def handle(self, *args, **options):
if args:
for slug in args:
- for service in GitHubService.for_user(
- User.objects.get(username=slug)
- ):
+ for service in GitHubService.for_user(User.objects.get(username=slug
+ ),):
service.sync()
diff --git a/readthedocs/core/management/commands/import_github_language.py b/readthedocs/core/management/commands/import_github_language.py
index 1e42685a57e..e3679a52614 100644
--- a/readthedocs/core/management/commands/import_github_language.py
+++ b/readthedocs/core/management/commands/import_github_language.py
@@ -1,5 +1,6 @@
+# -*- coding: utf-8 -*-
"""
-Import a project's programming language from GitHub
+Import a project's programming language from GitHub.
This builds a basic management command that will set
a projects language to the most used one in GitHub.
@@ -35,11 +36,8 @@ def handle(self, *args, **options):
print('Invalid GitHub token, exiting')
return
- for project in Project.objects.filter(
- programming_language__in=['none', '', 'words']
- ).filter(
- repo__contains='github'
- ):
+ for project in Project.objects.filter(programming_language__in=[
+ 'none', '', 'words'],).filter(repo__contains='github',):
user = repo = ''
repo_url = project.repo
for regex in GITHUB_REGEXS:
@@ -65,7 +63,9 @@ def handle(self, *args, **options):
languages = resp.json()
if not languages:
continue
- sorted_langs = sorted(list(languages.items()), key=lambda x: x[1], reverse=True)
+ sorted_langs = sorted(
+ list(languages.items()), key=lambda x: x[1], reverse=True
+ )
print('Sorted langs: %s ' % sorted_langs)
top_lang = sorted_langs[0][0]
else:
@@ -73,7 +73,8 @@ def handle(self, *args, **options):
if top_lang in PL_DICT:
slug = PL_DICT[top_lang]
print('Setting {} to {}'.format(repo_url, slug))
- Project.objects.filter(pk=project.pk).update(programming_language=slug)
+ Project.objects.filter(pk=project.pk
+ ).update(programming_language=slug)
else:
print('Language unknown: %s' % top_lang)
cache.set(cache_key, top_lang, 60 * 600)
diff --git a/readthedocs/core/management/commands/provision_elasticsearch.py b/readthedocs/core/management/commands/provision_elasticsearch.py
index 690a45803d9..adab81d4958 100644
--- a/readthedocs/core/management/commands/provision_elasticsearch.py
+++ b/readthedocs/core/management/commands/provision_elasticsearch.py
@@ -1,4 +1,5 @@
-"""Provision Elastic Search"""
+# -*- coding: utf-8 -*-
+"""Provision Elastic Search."""
import logging
@@ -20,19 +21,19 @@ class Command(BaseCommand):
help = __doc__
def handle(self, *args, **options):
- """Provision new ES instance"""
+ """Provision new ES instance."""
index = Index()
index_name = index.timestamped_index()
- log.info("Creating indexes..")
+ log.info('Creating indexes..')
index.create_index(index_name)
index.update_aliases(index_name)
- log.info("Updating mappings..")
+ log.info('Updating mappings..')
proj = ProjectIndex()
proj.put_mapping()
page = PageIndex()
page.put_mapping()
sec = SectionIndex()
sec.put_mapping()
- log.info("Done!")
+ log.info('Done!')
diff --git a/readthedocs/core/management/commands/pull.py b/readthedocs/core/management/commands/pull.py
index f0ebe4206bd..3c965f8b1af 100644
--- a/readthedocs/core/management/commands/pull.py
+++ b/readthedocs/core/management/commands/pull.py
@@ -1,4 +1,5 @@
-"""Trigger build for project slug"""
+# -*- coding: utf-8 -*-
+"""Trigger build for project slug."""
import logging
diff --git a/readthedocs/core/management/commands/reindex_elasticsearch.py b/readthedocs/core/management/commands/reindex_elasticsearch.py
index 9d2ab83495a..0d552e86e3e 100644
--- a/readthedocs/core/management/commands/reindex_elasticsearch.py
+++ b/readthedocs/core/management/commands/reindex_elasticsearch.py
@@ -1,7 +1,7 @@
-"""Reindex Elastic Search indexes"""
+# -*- coding: utf-8 -*-
+"""Reindex Elastic Search indexes."""
import logging
-from optparse import make_option
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
@@ -23,11 +23,11 @@ def add_arguments(self, parser):
'-p',
dest='project',
default='',
- help='Project to index'
+ help='Project to index',
)
def handle(self, *args, **options):
- """Build/index all versions or a single project's version"""
+ """Build/index all versions or a single project's version."""
project = options['project']
queryset = Version.objects.all()
@@ -36,13 +36,14 @@ def handle(self, *args, **options):
queryset = queryset.filter(project__slug=project)
if not queryset.exists():
raise CommandError(
- 'No project with slug: {slug}'.format(slug=project))
- log.info("Building all versions for %s", project)
+ 'No project with slug: {slug}'.format(slug=project),
+ )
+ log.info('Building all versions for %s', project)
elif getattr(settings, 'INDEX_ONLY_LATEST', True):
queryset = queryset.filter(slug=LATEST)
for version in queryset:
- log.info("Reindexing %s", version)
+ log.info('Reindexing %s', version)
try:
commit = version.project.vcs_repo(version.slug).commit
except: # noqa
@@ -51,7 +52,10 @@ def handle(self, *args, **options):
commit = None
try:
- update_search(version.pk, commit,
- delete_non_commit_files=False)
+ update_search(
+ version.pk,
+ commit,
+ delete_non_commit_files=False,
+ )
except Exception as e:
log.exception('Reindex failed for %s, %s', version, e)
diff --git a/readthedocs/core/management/commands/set_metadata.py b/readthedocs/core/management/commands/set_metadata.py
index e8d17a9dbf1..e5782e404ef 100644
--- a/readthedocs/core/management/commands/set_metadata.py
+++ b/readthedocs/core/management/commands/set_metadata.py
@@ -1,4 +1,5 @@
-"""Generate metadata for all projects"""
+# -*- coding: utf-8 -*-
+"""Generate metadata for all projects."""
import logging
@@ -19,8 +20,10 @@ class Command(BaseCommand):
def handle(self, *args, **options):
queryset = Project.objects.all()
for p in queryset:
- log.info("Generating metadata for %s", p)
+ log.info('Generating metadata for %s', p)
try:
- broadcast(type='app', task=tasks.update_static_metadata, args=[p.pk])
+ broadcast(
+ type='app', task=tasks.update_static_metadata, args=[p.pk]
+ )
except Exception:
log.exception('Build failed for %s', p)
diff --git a/readthedocs/core/management/commands/symlink.py b/readthedocs/core/management/commands/symlink.py
index 3fda03a41db..a7743a74057 100644
--- a/readthedocs/core/management/commands/symlink.py
+++ b/readthedocs/core/management/commands/symlink.py
@@ -1,4 +1,5 @@
-"""Update symlinks for projects"""
+# -*- coding: utf-8 -*-
+"""Update symlinks for projects."""
import logging
@@ -23,7 +24,8 @@ def handle(self, *args, **options):
if 'all' in projects:
pks = Project.objects.values_list('pk', flat=True)
else:
- pks = Project.objects.filter(slug__in=projects).values_list('pk', flat=True)
+ pks = Project.objects.filter(slug__in=projects
+ ).values_list('pk', flat=True)
for proj in pks:
try:
tasks.symlink_project(project_pk=proj)
diff --git a/readthedocs/core/management/commands/update_api.py b/readthedocs/core/management/commands/update_api.py
index 3a9b44b1c9e..5b87bd4a571 100644
--- a/readthedocs/core/management/commands/update_api.py
+++ b/readthedocs/core/management/commands/update_api.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
Build documentation using the API and not hitting a database.
@@ -31,6 +32,6 @@ def handle(self, *args, **options):
for slug in options['projects']:
project_data = api.project(slug).get()
p = APIProject(**project_data)
- log.info("Building %s", p)
+ log.info('Building %s', p)
# pylint: disable=no-value-for-parameter
tasks.update_docs_task(p.pk, docker=docker)
diff --git a/readthedocs/core/management/commands/update_versions.py b/readthedocs/core/management/commands/update_versions.py
index 9cc0c297130..bf066c7757b 100644
--- a/readthedocs/core/management/commands/update_versions.py
+++ b/readthedocs/core/management/commands/update_versions.py
@@ -1,4 +1,5 @@
-"""Rebuild documentation for all projects"""
+# -*- coding: utf-8 -*-
+"""Rebuild documentation for all projects."""
from django.core.management.base import BaseCommand
@@ -16,5 +17,5 @@ def handle(self, *args, **options):
update_docs_task(
version.project_id,
record=False,
- version_pk=version.pk
+ version_pk=version.pk,
)
diff --git a/readthedocs/core/middleware.py b/readthedocs/core/middleware.py
index 967a4e84aa9..1a874e12011 100644
--- a/readthedocs/core/middleware.py
+++ b/readthedocs/core/middleware.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Middleware for core app."""
import logging
@@ -7,7 +8,7 @@
from django.core.cache import cache
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.http import Http404, HttpResponseBadRequest
-from django.urls.base import get_urlconf, set_urlconf
+from django.urls.base import set_urlconf
from django.utils.deprecation import MiddlewareMixin
from django.utils.translation import ugettext_lazy as _
@@ -17,16 +18,16 @@
log = logging.getLogger(__name__)
-LOG_TEMPLATE = "(Middleware) {msg} [{host}{path}]"
+LOG_TEMPLATE = '(Middleware) {msg} [{host}{path}]'
SUBDOMAIN_URLCONF = getattr(
settings,
'SUBDOMAIN_URLCONF',
- 'readthedocs.core.urls.subdomain'
+ 'readthedocs.core.urls.subdomain',
)
SINGLE_VERSION_URLCONF = getattr(
settings,
'SINGLE_VERSION_URLCONF',
- 'readthedocs.core.urls.single_version'
+ 'readthedocs.core.urls.single_version',
)
@@ -52,7 +53,7 @@ def process_request(self, request):
production_domain = getattr(
settings,
'PRODUCTION_DOMAIN',
- 'readthedocs.org'
+ 'readthedocs.org',
)
if public_domain is None:
@@ -65,10 +66,8 @@ def process_request(self, request):
if len(domain_parts) == len(public_domain.split('.')) + 1:
subdomain = domain_parts[0]
is_www = subdomain.lower() == 'www'
- if not is_www and (
- # Support ports during local dev
- public_domain in host or public_domain in full_host
- ):
+ if not is_www and ( # Support ports during local dev
+ public_domain in host or public_domain in full_host):
if not Project.objects.filter(slug=subdomain).exists():
raise Http404(_('Project not found'))
request.subdomain = True
@@ -77,10 +76,8 @@ def process_request(self, request):
return None
# Serve CNAMEs
- if (public_domain not in host and
- production_domain not in host and
- 'localhost' not in host and
- 'testserver' not in host):
+ if (public_domain not in host and production_domain not in host and
+ 'localhost' not in host and 'testserver' not in host):
request.cname = True
domains = Domain.objects.filter(domain=host)
if domains.count():
@@ -89,18 +86,24 @@ def process_request(self, request):
request.slug = domain.project.slug
request.urlconf = SUBDOMAIN_URLCONF
request.domain_object = True
- log.debug(LOG_TEMPLATE.format(
- msg='Domain Object Detected: %s' % domain.domain,
- **log_kwargs))
+ log.debug(
+ LOG_TEMPLATE.format(
+ msg='Domain Object Detected: %s' % domain.domain,
+ **log_kwargs
+ )
+ )
break
if (not hasattr(request, 'domain_object') and
'HTTP_X_RTD_SLUG' in request.META):
request.slug = request.META['HTTP_X_RTD_SLUG'].lower()
request.urlconf = SUBDOMAIN_URLCONF
request.rtdheader = True
- log.debug(LOG_TEMPLATE.format(
- msg='X-RTD-Slug header detected: %s' % request.slug,
- **log_kwargs))
+ log.debug(
+ LOG_TEMPLATE.format(
+ msg='X-RTD-Slug header detected: %s' % request.slug,
+ **log_kwargs
+ )
+ )
# Try header first, then DNS
elif not hasattr(request, 'domain_object'):
try:
@@ -109,26 +112,39 @@ def process_request(self, request):
slug = cname_to_slug(host)
cache.set(host, slug, 60 * 60)
# Cache the slug -> host mapping permanently.
- log.info(LOG_TEMPLATE.format(
- msg='CNAME cached: {}->{}'.format(slug, host),
- **log_kwargs))
+ log.info(
+ LOG_TEMPLATE.format(
+ msg='CNAME cached: {}->{}'.format(slug, host),
+ **log_kwargs
+ )
+ )
request.slug = slug
request.urlconf = SUBDOMAIN_URLCONF
- log.warning(LOG_TEMPLATE.format(
- msg='CNAME detected: %s' % request.slug,
- **log_kwargs))
+ log.warning(
+ LOG_TEMPLATE.format(
+ msg='CNAME detected: %s' % request.slug,
+ **log_kwargs
+ )
+ )
except: # noqa
# Some crazy person is CNAMEing to us. 404.
- log.warning(LOG_TEMPLATE.format(msg='CNAME 404', **log_kwargs))
+ log.warning(
+ LOG_TEMPLATE.format(msg='CNAME 404', **log_kwargs)
+ )
raise Http404(_('Invalid hostname'))
# Google was finding crazy www.blah.readthedocs.org domains.
# Block these explicitly after trying CNAME logic.
if len(domain_parts) > 3 and not settings.DEBUG:
# Stop www.fooo.readthedocs.org
if domain_parts[0] == 'www':
- log.debug(LOG_TEMPLATE.format(msg='404ing long domain', **log_kwargs))
+ log.debug(
+ LOG_TEMPLATE.format(msg='404ing long domain', **log_kwargs)
+ )
return HttpResponseBadRequest(_('Invalid hostname'))
- log.debug(LOG_TEMPLATE.format(msg='Allowing long domain name', **log_kwargs))
+ log.debug(
+ LOG_TEMPLATE
+ .format(msg='Allowing long domain name', **log_kwargs)
+ )
# raise Http404(_('Invalid hostname'))
# Normal request.
return None
@@ -186,8 +202,9 @@ def process_request(self, request):
host = request.get_host()
path = request.get_full_path()
log_kwargs = dict(host=host, path=path)
- log.debug(LOG_TEMPLATE.format(
- msg='Handling single_version request', **log_kwargs)
+ log.debug(
+ LOG_TEMPLATE.
+ format(msg='Handling single_version request', **log_kwargs),
)
return None
@@ -217,7 +234,7 @@ def process_request(self, request):
else:
# HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs. The
# client's IP will be the first one.
- real_ip = real_ip.split(",")[0].strip()
+ real_ip = real_ip.split(',')[0].strip()
request.META['REMOTE_ADDR'] = real_ip
@@ -229,7 +246,9 @@ class FooterNoSessionMiddleware(SessionMiddleware):
This will reduce the size of our session table drastically.
"""
- IGNORE_URLS = ['/api/v2/footer_html', '/sustainability/view', '/sustainability/click']
+ IGNORE_URLS = [
+ '/api/v2/footer_html', '/sustainability/view', '/sustainability/click'
+ ]
def process_request(self, request):
for url in self.IGNORE_URLS:
diff --git a/readthedocs/core/mixins.py b/readthedocs/core/mixins.py
index 2460daacd13..a2cf841aeda 100644
--- a/readthedocs/core/mixins.py
+++ b/readthedocs/core/mixins.py
@@ -1,4 +1,5 @@
-"""Common mixin classes for views"""
+# -*- coding: utf-8 -*-
+"""Common mixin classes for views."""
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
@@ -7,7 +8,7 @@
class ListViewWithForm(ListView):
- """List view that also exposes a create form"""
+ """List view that also exposes a create form."""
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
diff --git a/readthedocs/core/models.py b/readthedocs/core/models.py
index 6f4f019ae44..63ac39d102d 100644
--- a/readthedocs/core/models.py
+++ b/readthedocs/core/models.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Models for the core app."""
import logging
@@ -21,7 +22,10 @@ class UserProfile(models.Model):
"""Additional information about a User."""
user = AutoOneToOneField(
- 'auth.User', verbose_name=_('User'), related_name='profile')
+ 'auth.User',
+ verbose_name=_('User'),
+ related_name='profile',
+ )
whitelisted = models.BooleanField(_('Whitelisted'), default=False)
banned = models.BooleanField(_('Banned'), default=False)
homepage = models.CharField(_('Homepage'), max_length=100, blank=True)
@@ -39,10 +43,13 @@ class UserProfile(models.Model):
def __str__(self):
return (
ugettext("%(username)s's profile") %
- {'username': self.user.username})
+ {'username': self.user.username}
+ )
def get_absolute_url(self):
- return reverse('profiles_profile_detail', kwargs={'username': self.user.username})
+ return reverse(
+ 'profiles_profile_detail', kwargs={'username': self.user.username}
+ )
def get_contribution_details(self):
"""
diff --git a/readthedocs/core/permissions.py b/readthedocs/core/permissions.py
index 6af5f2a58b8..28d01bd7a90 100644
--- a/readthedocs/core/permissions.py
+++ b/readthedocs/core/permissions.py
@@ -1,4 +1,5 @@
-"""Objects for User permission checks"""
+# -*- coding: utf-8 -*-
+"""Objects for User permission checks."""
from readthedocs.core.utils.extend import SettingsOverrideObject
diff --git a/readthedocs/core/resolver.py b/readthedocs/core/resolver.py
index 5e0b27492aa..10b5f42d562 100644
--- a/readthedocs/core/resolver.py
+++ b/readthedocs/core/resolver.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""URL resolver for documentation."""
import re
@@ -49,9 +50,18 @@ class ResolverBase:
/docs//projects//
"""
- 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):
+ 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."""
# Only support `/docs/project' URLs outside our normal environment. Normally
# the path should always have a subdomain or CNAME domain
@@ -70,14 +80,25 @@ def base_resolve_path(self, project_slug, filename, version_slug=None,
url += '{language}/{version_slug}/{filename}'
return url.format(
- project_slug=project_slug, filename=filename,
- version_slug=version_slug, language=language,
- single_version=single_version, subproject_slug=subproject_slug,
+ project_slug=project_slug,
+ filename=filename,
+ version_slug=version_slug,
+ language=language,
+ single_version=single_version,
+ subproject_slug=subproject_slug,
)
- def resolve_path(self, project, filename='', version_slug=None,
- language=None, single_version=None, subdomain=None,
- cname=None, private=None):
+ 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."""
cname = cname or project.domains.filter(canonical=True).first()
version_slug = version_slug or project.get_default_version()
@@ -136,8 +157,10 @@ def resolve_domain(self, project, private=None):
return getattr(settings, 'PRODUCTION_DOMAIN')
- def resolve(self, project, require_https=False, filename='', private=None,
- **kwargs):
+ def resolve(
+ self, project, require_https=False, filename='', private=None,
+ **kwargs
+ ):
if private is None:
version_slug = kwargs.get('version_slug')
if version_slug is None:
@@ -171,8 +194,8 @@ def resolve(self, project, require_https=False, filename='', private=None,
return '{protocol}://{domain}{path}'.format(
protocol=protocol,
domain=domain,
- path=self.resolve_path(project, filename=filename, private=private,
- **kwargs),
+ path=self.
+ resolve_path(project, filename=filename, private=private, **kwargs),
)
def _get_canonical_project(self, project, projects=None):
@@ -210,7 +233,7 @@ def _get_project_subdomain(self, project):
if self._use_subdomain():
project = self._get_canonical_project(project)
subdomain_slug = project.slug.replace('_', '-')
- return "{}.{}".format(subdomain_slug, public_domain)
+ return '{}.{}'.format(subdomain_slug, public_domain)
def _get_project_custom_domain(self, project):
return project.domains.filter(canonical=True).first()
@@ -221,7 +244,9 @@ def _get_private(self, project, version_slug):
version = project.versions.get(slug=version_slug)
private = version.privacy_level == PRIVATE
except Version.DoesNotExist:
- private = getattr(settings, 'DEFAULT_PRIVACY_LEVEL', PUBLIC) == PRIVATE
+ private = getattr(
+ settings, 'DEFAULT_PRIVACY_LEVEL', PUBLIC
+ ) == PRIVATE
return private
def _fix_filename(self, project, filename):
@@ -239,17 +264,17 @@ def _fix_filename(self, project, filename):
if filename:
if filename.endswith('/') or filename.endswith('.html'):
path = filename
- elif project.documentation_type == "sphinx_singlehtml":
- path = "index.html#document-" + filename
- elif project.documentation_type in ["sphinx_htmldir", "mkdocs"]:
- path = filename + "/"
+ elif project.documentation_type == 'sphinx_singlehtml':
+ path = 'index.html#document-' + filename
+ elif project.documentation_type in ['sphinx_htmldir', 'mkdocs']:
+ path = filename + '/'
elif '#' in filename:
# do nothing if the filename contains URL fragments
path = filename
else:
- path = filename + ".html"
+ path = filename + '.html'
else:
- path = ""
+ path = ''
return path
def _use_custom_domain(self, custom_domain):
diff --git a/readthedocs/core/signals.py b/readthedocs/core/signals.py
index f8fa5f5adec..a4f029c6b3e 100644
--- a/readthedocs/core/signals.py
+++ b/readthedocs/core/signals.py
@@ -90,15 +90,17 @@ def delete_projects_and_organizations(sender, instance, *args, **kwargs):
# https://github.com/rtfd/readthedocs.org/pull/4577
# https://docs.djangoproject.com/en/2.1/topics/db/aggregation/#order-of-annotate-and-filter-clauses # noqa
projects = (
- Project.objects.annotate(num_users=Count('users'))
- .filter(users=instance.id).exclude(num_users__gt=1)
+ Project.objects.annotate(num_users=Count('users')
+ ).filter(users=instance.id
+ ).exclude(num_users__gt=1)
)
# Here we count the users list from the organization that the user belong
# Then exclude the organizations where there are more than one user
oauth_organizations = (
- RemoteOrganization.objects.annotate(num_users=Count('users'))
- .filter(users=instance.id).exclude(num_users__gt=1)
+ RemoteOrganization.objects.annotate(num_users=Count('users')
+ ).filter(users=instance.id
+ ).exclude(num_users__gt=1)
)
projects.delete()
diff --git a/readthedocs/core/static/core/font/fontawesome-webfont.svg b/readthedocs/core/static/core/font/fontawesome-webfont.svg
index 855c845e538..52c0773359b 100644
--- a/readthedocs/core/static/core/font/fontawesome-webfont.svg
+++ b/readthedocs/core/static/core/font/fontawesome-webfont.svg
@@ -8,7 +8,7 @@ Copyright Dave Gandy 2016. All rights reserved.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/readthedocs/core/symlink.py b/readthedocs/core/symlink.py
index bde0d2d454f..b43da937ee4 100644
--- a/readthedocs/core/symlink.py
+++ b/readthedocs/core/symlink.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
A class that manages the symlinks for nginx to serve public files.
@@ -77,10 +78,12 @@ class Symlink:
def __init__(self, project):
self.project = project
self.project_root = os.path.join(
- self.WEB_ROOT, project.slug
+ self.WEB_ROOT,
+ project.slug,
)
self.subproject_root = os.path.join(
- self.project_root, 'projects'
+ self.project_root,
+ 'projects',
)
self.environment = LocalEnvironment(project)
self.sanity_check()
@@ -92,9 +95,13 @@ def sanity_check(self):
This will leave it in the proper state for the single_project setting.
"""
if os.path.islink(self.project_root) and not self.project.single_version:
- log.info(constants.LOG_TEMPLATE.format(
- project=self.project.slug, version='',
- msg="Removing single version symlink"))
+ log.info(
+ constants.LOG_TEMPLATE.format(
+ project=self.project.slug,
+ version='',
+ msg='Removing single version symlink',
+ )
+ )
safe_unlink(self.project_root)
safe_makedirs(self.project_root)
elif (self.project.single_version and
@@ -147,13 +154,15 @@ def symlink_cnames(self, domain=None):
domains = Domain.objects.filter(project=self.project)
for dom in domains:
log_msg = 'Symlinking CNAME: {} -> {}'.format(
- dom.domain, self.project.slug
+ dom.domain,
+ self.project.slug,
)
log.info(
constants.LOG_TEMPLATE.format(
project=self.project.slug,
- version='', msg=log_msg
- )
+ version='',
+ msg=log_msg,
+ ),
)
# CNAME to doc root
@@ -162,17 +171,26 @@ def symlink_cnames(self, domain=None):
# Project symlink
project_cname_symlink = os.path.join(
- self.PROJECT_CNAME_ROOT, dom.domain
+ self.PROJECT_CNAME_ROOT,
+ dom.domain,
)
self.environment.run(
- 'ln', '-nsf', self.project.doc_path, project_cname_symlink
+ 'ln',
+ '-nsf',
+ self.project.doc_path,
+ project_cname_symlink,
)
def remove_symlink_cname(self, domain):
"""Remove CNAME symlink."""
- log_msg = "Removing symlink for CNAME {}".format(domain.domain)
- log.info(constants.LOG_TEMPLATE.format(project=self.project.slug,
- version='', msg=log_msg))
+ log_msg = 'Removing symlink for CNAME {}'.format(domain.domain)
+ log.info(
+ constants.LOG_TEMPLATE.format(
+ project=self.project.slug,
+ version='',
+ msg=log_msg,
+ )
+ )
symlink = os.path.join(self.CNAME_ROOT, domain.domain)
safe_unlink(symlink)
@@ -180,8 +198,7 @@ def symlink_subprojects(self):
"""
Symlink project subprojects.
- Link from $WEB_ROOT/projects/ ->
- $WEB_ROOT/
+ Link from $WEB_ROOT/projects/ -> $WEB_ROOT/
"""
subprojects = set()
rels = self.get_subprojects()
@@ -198,12 +215,20 @@ def symlink_subprojects(self):
from_to[rel.alias] = rel.child.slug
subprojects.add(rel.alias)
for from_slug, to_slug in list(from_to.items()):
- log_msg = "Symlinking subproject: {} -> {}".format(from_slug, to_slug)
- log.info(constants.LOG_TEMPLATE.format(project=self.project.slug,
- version='', msg=log_msg))
+ log_msg = 'Symlinking subproject: {} -> {}'.format(
+ from_slug, to_slug
+ )
+ log.info(
+ constants.LOG_TEMPLATE.format(
+ project=self.project.slug,
+ version='',
+ msg=log_msg,
+ )
+ )
symlink = os.path.join(self.subproject_root, from_slug)
docs_dir = os.path.join(
- self.WEB_ROOT, to_slug
+ self.WEB_ROOT,
+ to_slug,
)
symlink_dir = os.sep.join(symlink.split(os.path.sep)[:-1])
if not os.path.lexists(symlink_dir):
@@ -215,7 +240,8 @@ def symlink_subprojects(self):
if result.exit_code > 0:
log.error(
'Could not symlink path: status=%d error=%s',
- result.exit_code, result.error
+ result.exit_code,
+ result.error,
)
# Remove old symlinks
@@ -229,7 +255,7 @@ def symlink_translations(self):
Symlink project translations.
Link from $WEB_ROOT/// ->
- $WEB_ROOT///
+ $WEB_ROOT///
"""
translations = {}
@@ -249,8 +275,9 @@ def symlink_translations(self):
log.info(
constants.LOG_TEMPLATE.format(
project=self.project.slug,
- version='', msg=log_msg
- )
+ version='',
+ msg=log_msg,
+ ),
)
symlink = os.path.join(self.project_root, language)
docs_dir = os.path.join(self.WEB_ROOT, slug, language)
@@ -271,7 +298,7 @@ def symlink_single_version(self):
Symlink project single version.
Link from $WEB_ROOT/ ->
- HOME/user_builds//rtd-builds/latest/
+ HOME/user_builds//rtd-builds/latest/
"""
version = self.get_default_version()
@@ -288,7 +315,7 @@ def symlink_single_version(self):
settings.DOCROOT,
self.project.slug,
'rtd-builds',
- version.slug
+ version.slug,
)
self.environment.run('ln', '-nsf', docs_dir, symlink)
@@ -297,11 +324,13 @@ def symlink_versions(self):
Symlink project's versions.
Link from $WEB_ROOT//// ->
- HOME/user_builds//rtd-builds/
+ HOME/user_builds//rtd-builds/
"""
versions = set()
version_dir = os.path.join(
- self.WEB_ROOT, self.project.slug, self.project.language
+ self.WEB_ROOT,
+ self.project.slug,
+ self.project.language,
)
# Include active public versions,
# as well as public versions that are built but not active, for archived versions
@@ -315,15 +344,15 @@ def symlink_versions(self):
constants.LOG_TEMPLATE.format(
project=self.project.slug,
version='',
- msg=log_msg
- )
+ msg=log_msg,
+ ),
)
symlink = os.path.join(version_dir, version.slug)
docs_dir = os.path.join(
settings.DOCROOT,
self.project.slug,
'rtd-builds',
- version.slug
+ version.slug,
)
self.environment.run('ln', '-nsf', docs_dir, symlink)
versions.add(version.slug)
@@ -346,11 +375,16 @@ def get_default_version(self):
class PublicSymlinkBase(Symlink):
CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'public_cname_root')
WEB_ROOT = os.path.join(settings.SITE_ROOT, 'public_web_root')
- PROJECT_CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'public_cname_project')
+ PROJECT_CNAME_ROOT = os.path.join(
+ settings.SITE_ROOT, 'public_cname_project'
+ )
def get_version_queryset(self):
- return (self.project.versions.protected(only_active=False).filter(built=True) |
- self.project.versions.protected(only_active=True))
+ return (
+ self.project.versions.protected(only_active=False
+ ).filter(built=True) |
+ self.project.versions.protected(only_active=True)
+ )
def get_subprojects(self):
return self.project.subprojects.protected()
@@ -362,11 +396,15 @@ def get_translations(self):
class PrivateSymlinkBase(Symlink):
CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'private_cname_root')
WEB_ROOT = os.path.join(settings.SITE_ROOT, 'private_web_root')
- PROJECT_CNAME_ROOT = os.path.join(settings.SITE_ROOT, 'private_cname_project')
+ PROJECT_CNAME_ROOT = os.path.join(
+ settings.SITE_ROOT, 'private_cname_project'
+ )
def get_version_queryset(self):
- return (self.project.versions.private(only_active=False).filter(built=True) |
- self.project.versions.private(only_active=True))
+ return (
+ self.project.versions.private(only_active=False).filter(built=True) |
+ self.project.versions.private(only_active=True)
+ )
def get_subprojects(self):
return self.project.subprojects.private()
diff --git a/readthedocs/core/tasks.py b/readthedocs/core/tasks.py
index 446a19b7ea5..d053614c5ee 100644
--- a/readthedocs/core/tasks.py
+++ b/readthedocs/core/tasks.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Basic tasks."""
import logging
@@ -18,8 +19,10 @@
@app.task(queue='web', time_limit=EMAIL_TIME_LIMIT)
-def send_email_task(recipient, subject, template, template_html,
- context=None, from_email=None, **kwargs):
+def send_email_task(
+ recipient, subject, template, template_html, context=None,
+ from_email=None, **kwargs
+):
"""
Send multipart email.
@@ -43,14 +46,15 @@ def send_email_task(recipient, subject, template, template_html,
"""
msg = EmailMultiAlternatives(
subject,
- get_template(template).render(context),
- from_email or settings.DEFAULT_FROM_EMAIL,
- [recipient],
- **kwargs
+ get_template(template).render(context), from_email or
+ settings.DEFAULT_FROM_EMAIL,
+ [recipient], **kwargs
)
try:
- msg.attach_alternative(get_template(template_html).render(context),
- 'text/html')
+ msg.attach_alternative(
+ get_template(template_html).render(context),
+ 'text/html',
+ )
except TemplateDoesNotExist:
pass
msg.send()
@@ -61,5 +65,7 @@ def send_email_task(recipient, subject, template, template_html,
def clear_persistent_messages():
# Delete all expired message_extend's messages
log.info("Deleting all expired message_extend's messages")
- expired_messages = PersistentMessage.objects.filter(expires__lt=timezone.now())
+ expired_messages = PersistentMessage.objects.filter(
+ expires__lt=timezone.now()
+ )
expired_messages.delete()
diff --git a/readthedocs/core/templatetags/core_tags.py b/readthedocs/core/templatetags/core_tags.py
index 304ca3aff3a..b4862e5257f 100644
--- a/readthedocs/core/templatetags/core_tags.py
+++ b/readthedocs/core/templatetags/core_tags.py
@@ -1,7 +1,8 @@
+# -*- coding: utf-8 -*-
"""Template tags for core app."""
import hashlib
-from urllib.parse import urlparse, urlencode
+from urllib.parse import urlencode
from django import template
from django.conf import settings
@@ -19,23 +20,25 @@
@register.filter
def gravatar(email, size=48):
"""
- Hacked from djangosnippets.org, but basically given an email address
+ Hacked from djangosnippets.org, but basically given an email address.
render an img tag with the hashed up bits needed for leetness
omgwtfstillreading
"""
- url = "http://www.gravatar.com/avatar.php?%s" % urlencode({
+ url = 'http://www.gravatar.com/avatar.php?%s' % urlencode({
'gravatar_id': hashlib.md5(email).hexdigest(),
- 'size': str(size)
+ 'size': str(size),
})
- return ('' % (url, size, size))
+ return (
+ '' % (url, size, size)
+ )
-@register.simple_tag(name="doc_url")
+@register.simple_tag(name='doc_url')
def make_document_url(project, version=None, page=''):
if not project:
- return ""
+ return ''
return resolve(project=project, version_slug=version, filename=page)
@@ -48,7 +51,7 @@ def restructuredtext(value, short=False):
if settings.DEBUG:
raise template.TemplateSyntaxError(
"Error in 'restructuredtext' filter: "
- "The Python docutils library isn't installed."
+ "The Python docutils library isn't installed.",
)
return force_text(value)
else:
@@ -56,20 +59,22 @@ def restructuredtext(value, short=False):
'raw_enabled': False,
'file_insertion_enabled': False,
}
- docutils_settings.update(getattr(settings, 'RESTRUCTUREDTEXT_FILTER_SETTINGS', {}))
+ docutils_settings.update(
+ getattr(settings, 'RESTRUCTUREDTEXT_FILTER_SETTINGS', {})
+ )
try:
parts = publish_parts(
source=force_bytes(value),
- writer_name="html4css1",
+ writer_name='html4css1',
settings_overrides=docutils_settings,
)
except ApplicationError:
return force_text(value)
- out = force_text(parts["fragment"])
+ out = force_text(parts['fragment'])
try:
if short:
- out = out.split("\n")[0]
+ out = out.split('\n')[0]
except IndexError:
pass
return mark_safe(out)
diff --git a/readthedocs/core/templatetags/privacy_tags.py b/readthedocs/core/templatetags/privacy_tags.py
index 814018dc9df..fbbe9804140 100644
--- a/readthedocs/core/templatetags/privacy_tags.py
+++ b/readthedocs/core/templatetags/privacy_tags.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Template tags to query projects by privacy."""
from django import template
@@ -16,6 +17,8 @@ def is_admin(user, project):
@register.simple_tag(takes_context=True)
def get_public_projects(context, user):
- projects = Project.objects.for_user_and_viewer(user=user, viewer=context['request'].user)
+ projects = Project.objects.for_user_and_viewer(
+ user=user, viewer=context['request'].user
+ )
context['public_projects'] = projects
return ''
diff --git a/readthedocs/core/urls/__init__.py b/readthedocs/core/urls/__init__.py
index 48b2e9a614a..4c0951e9cf1 100644
--- a/readthedocs/core/urls/__init__.py
+++ b/readthedocs/core/urls/__init__.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""URL configuration for core app."""
from __future__ import absolute_import
@@ -8,53 +9,77 @@
from readthedocs.core.views import hooks, serve
from readthedocs.projects.feeds import LatestProjectsFeed, NewProjectsFeed
-
docs_urls = [
- url((r'^docs/(?P{project_slug})/page/'
- r'(?P{filename_slug})$'.format(**pattern_opts)),
+ url(
+ (
+ r'^docs/(?P{project_slug})/page/'
+ r'(?P{filename_slug})$'.format(**pattern_opts)
+ ),
serve.redirect_page_with_filename,
- name='docs_detail'),
-
- url((r'^docs/(?P{project_slug})/'
- r'(?:|projects/(?P{project_slug})/)$'.format(**pattern_opts)),
+ name='docs_detail',
+ ),
+ url(
+ (
+ r'^docs/(?P{project_slug})/'
+ r'(?:|projects/(?P{project_slug})/)$'.format(
+ **pattern_opts
+ )
+ ),
serve.redirect_project_slug,
- name='docs_detail'),
-
- url((r'^docs/(?P{project_slug})/'
- r'(?:|projects/(?P{project_slug})/)'
- r'(?P{lang_slug})/'
- r'(?P{version_slug})/'
- r'(?P{filename_slug})'.format(**pattern_opts)),
+ name='docs_detail',
+ ),
+ url(
+ (
+ r'^docs/(?P{project_slug})/'
+ r'(?:|projects/(?P{project_slug})/)'
+ r'(?P{lang_slug})/'
+ r'(?P{version_slug})/'
+ r'(?P{filename_slug})'.format(**pattern_opts)
+ ),
serve.serve_docs,
- name='docs_detail'),
+ name='docs_detail',
+ ),
]
-
core_urls = [
# Hooks
url(r'^github', hooks.github_build, name='github_build'),
url(r'^gitlab', hooks.gitlab_build, name='gitlab_build'),
url(r'^bitbucket', hooks.bitbucket_build, name='bitbucket_build'),
- url((r'^build/'
- r'(?P{project_slug})'.format(**pattern_opts)),
+ url(
+ (
+ r'^build/'
+ r'(?P{project_slug})'.format(**pattern_opts)
+ ),
hooks.generic_build,
- name='generic_build'),
+ name='generic_build',
+ ),
# Random other stuff
- url(r'^random/(?P{project_slug})'.format(**pattern_opts),
+ url(
+ r'^random/(?P{project_slug})'.format(**pattern_opts),
views.random_page,
- name='random_page'),
+ name='random_page',
+ ),
url(r'^random/$', views.random_page, name='random_page'),
- url((r'^wipe/(?P{project_slug})/'
- r'(?P{version_slug})/$'.format(**pattern_opts)),
+ url(
+ (
+ r'^wipe/(?P{project_slug})/'
+ r'(?P{version_slug})/$'.format(**pattern_opts)
+ ),
views.wipe_version,
- name='wipe_version'),
+ name='wipe_version',
+ ),
]
deprecated_urls = [
- url(r'^feeds/new/$',
+ url(
+ r'^feeds/new/$',
NewProjectsFeed(),
- name="new_feed"),
- url(r'^feeds/latest/$',
+ name='new_feed',
+ ),
+ url(
+ r'^feeds/latest/$',
LatestProjectsFeed(),
- name="latest_feed"),
+ name='latest_feed',
+ ),
]
diff --git a/readthedocs/core/urls/single_version.py b/readthedocs/core/urls/single_version.py
index b232ef9ae96..f8d33a761c7 100644
--- a/readthedocs/core/urls/single_version.py
+++ b/readthedocs/core/urls/single_version.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""URL configuration for a single version."""
from functools import reduce
from operator import add
@@ -14,33 +15,43 @@
handler404 = 'readthedocs.core.views.server_error_404'
single_version_urls = [
- url(r'^(?:|projects/(?P{project_slug})/)'
+ url(
+ r'^(?:|projects/(?P{project_slug})/)'
r'page/(?P.*)$'.format(**pattern_opts),
serve.redirect_page_with_filename,
- name='docs_detail'),
-
- url((r'^(?:|projects/(?P{project_slug})/)'
- r'(?P{filename_slug})$'.format(**pattern_opts)),
+ name='docs_detail',
+ ),
+ url(
+ (
+ r'^(?:|projects/(?P{project_slug})/)'
+ r'(?P{filename_slug})$'.format(**pattern_opts)
+ ),
serve.serve_docs,
- name='docs_detail'),
+ name='docs_detail',
+ ),
]
groups = [single_version_urls]
# Needed to serve media locally
if getattr(settings, 'DEBUG', False):
- groups.insert(0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT))
+ groups.insert(
+ 0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+ )
# Allow `/docs/` URL's when not using subdomains or during local dev
if not getattr(settings, 'USE_SUBDOMAIN', False) or settings.DEBUG:
docs_url = [
- url((r'^docs/(?P[-\w]+)/'
- r'(?:|projects/(?P{project_slug})/)'
- r'(?P{filename_slug})$'.format(**pattern_opts)),
+ url(
+ (
+ r'^docs/(?P[-\w]+)/'
+ r'(?:|projects/(?P{project_slug})/)'
+ r'(?P{filename_slug})$'.format(**pattern_opts)
+ ),
serve.serve_docs,
- name='docs_detail')
+ name='docs_detail',
+ ),
]
groups.insert(1, docs_url)
-
urlpatterns = reduce(add, groups)
diff --git a/readthedocs/core/urls/subdomain.py b/readthedocs/core/urls/subdomain.py
index 7b5b752fc88..cddc49a98a5 100644
--- a/readthedocs/core/urls/subdomain.py
+++ b/readthedocs/core/urls/subdomain.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""URL configurations for subdomains."""
from functools import reduce
from operator import add
@@ -19,27 +20,37 @@
handler404 = server_error_404
subdomain_urls = [
- url(r'^(?:|projects/(?P{project_slug})/)'
+ url(
+ r'^(?:|projects/(?P{project_slug})/)'
r'page/(?P.*)$'.format(**pattern_opts),
redirect_page_with_filename,
- name='docs_detail'),
-
- url((r'^(?:|projects/(?P{project_slug})/)$').format(**pattern_opts),
+ name='docs_detail',
+ ),
+ url(
+ (r'^(?:|projects/(?P{project_slug})/)$').format(
+ **pattern_opts
+ ),
redirect_project_slug,
- name='redirect_project_slug'),
-
- url((r'^(?:|projects/(?P{project_slug})/)'
- r'(?P{lang_slug})/'
- r'(?P{version_slug})/'
- r'(?P{filename_slug})$'.format(**pattern_opts)),
+ name='redirect_project_slug',
+ ),
+ url(
+ (
+ r'^(?:|projects/(?P{project_slug})/)'
+ r'(?P{lang_slug})/'
+ r'(?P{version_slug})/'
+ r'(?P{filename_slug})$'.format(**pattern_opts)
+ ),
serve_docs,
- name='docs_detail'),
+ name='docs_detail',
+ ),
]
groups = [subdomain_urls]
# Needed to serve media locally
if getattr(settings, 'DEBUG', False):
- groups.insert(0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT))
+ groups.insert(
+ 0, static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+ )
urlpatterns = reduce(add, groups)
diff --git a/readthedocs/core/utils/__init__.py b/readthedocs/core/utils/__init__.py
index 8632b1557d0..2123ce5a326 100644
--- a/readthedocs/core/utils/__init__.py
+++ b/readthedocs/core/utils/__init__.py
@@ -19,7 +19,6 @@
from readthedocs.builds.constants import LATEST, BUILD_STATE_TRIGGERED
from readthedocs.doc_builder.constants import DOCKER_LIMITS
-
log = logging.getLogger(__name__)
SYNC_USER = getattr(settings, 'SYNC_USER', getpass.getuser())
diff --git a/readthedocs/core/utils/extend.py b/readthedocs/core/utils/extend.py
index ca8e3f40b2e..96319e691a8 100644
--- a/readthedocs/core/utils/extend.py
+++ b/readthedocs/core/utils/extend.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Patterns for extending Read the Docs."""
import inspect
@@ -19,7 +20,7 @@ def get_override_class(proxy_class, default_class=None):
default_class = getattr(proxy_class, '_default_class')
class_id = '.'.join([
inspect.getmodule(proxy_class).__name__,
- proxy_class.__name__
+ proxy_class.__name__,
])
class_path = getattr(settings, 'CLASS_OVERRIDES', {}).get(class_id)
# pylint: disable=protected-access
@@ -32,7 +33,8 @@ def get_override_class(proxy_class, default_class=None):
class SettingsOverrideMeta(type):
- """Meta class for passing along classmethod class to the underlying class.""" # noqa
+ """Meta class for passing along classmethod class to the underlying
+ class.""" # noqa
def __getattr__(cls, attr): # noqa: pep8 false positive
proxy_class = get_override_class(cls, getattr(cls, '_default_class'))
diff --git a/readthedocs/core/utils/tasks/__init__.py b/readthedocs/core/utils/tasks/__init__.py
index 344215036f9..492708ad57f 100644
--- a/readthedocs/core/utils/tasks/__init__.py
+++ b/readthedocs/core/utils/tasks/__init__.py
@@ -1,4 +1,5 @@
-"""Common task exports"""
+# -*- coding: utf-8 -*-
+"""Common task exports."""
from .permission_checks import user_id_matches # noqa for unused import
from .public import PublicTask # noqa
diff --git a/readthedocs/core/utils/tasks/permission_checks.py b/readthedocs/core/utils/tasks/permission_checks.py
index 1643e866015..4a32e6a1a34 100644
--- a/readthedocs/core/utils/tasks/permission_checks.py
+++ b/readthedocs/core/utils/tasks/permission_checks.py
@@ -1,4 +1,5 @@
-"""Permission checks for tasks"""
+# -*- coding: utf-8 -*-
+"""Permission checks for tasks."""
__all__ = ('user_id_matches',)
diff --git a/readthedocs/core/utils/tasks/public.py b/readthedocs/core/utils/tasks/public.py
index 2c1ccaa9e0d..2f0bb011430 100644
--- a/readthedocs/core/utils/tasks/public.py
+++ b/readthedocs/core/utils/tasks/public.py
@@ -1,4 +1,5 @@
-"""Celery tasks with publicly viewable status"""
+# -*- coding: utf-8 -*-
+"""Celery tasks with publicly viewable status."""
from celery import Task, states
from django.conf import settings
@@ -7,10 +8,11 @@
__all__ = (
- 'PublicTask', 'TaskNoPermission', 'get_public_task_data'
+ 'PublicTask',
+ 'TaskNoPermission',
+ 'get_public_task_data',
)
-
STATUS_UPDATES_ENABLED = not getattr(settings, 'CELERY_ALWAYS_EAGER', False)
@@ -45,7 +47,7 @@ def update_progress_data(self):
def set_permission_context(self, context):
"""
- Set data that can be used by ``check_permission`` to authorize a
+ Set data that can be used by ``check_permission`` to authorize a.
request for the this task. By default it will be the ``kwargs`` passed
into the task.
@@ -103,22 +105,26 @@ def permission_check(check):
def my_public_task(user_id):
pass
"""
+
def decorator(func):
func.check_permission = check
return func
+
return decorator
class TaskNoPermission(Exception):
+
def __init__(self, task_id, *args, **kwargs):
message = 'No permission to access task with id {id}'.format(
- id=task_id)
+ id=task_id,
+ )
super().__init__(message, *args, **kwargs)
def get_public_task_data(request, task_id):
"""
- Return task details as tuple
+ Return task details as tuple.
Will raise `TaskNoPermission` if `request` has no permission to access info
of the task with id `task_id`. This is also the case of no task with the
diff --git a/readthedocs/core/utils/tasks/retrieve.py b/readthedocs/core/utils/tasks/retrieve.py
index 4c592116995..a2b008d32dd 100644
--- a/readthedocs/core/utils/tasks/retrieve.py
+++ b/readthedocs/core/utils/tasks/retrieve.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Utilities for retrieving task data."""
from celery import states
@@ -8,6 +9,7 @@
class TaskNotFound(Exception):
+
def __init__(self, task_id, *args, **kwargs):
message = 'No public task found with id {id}'.format(id=task_id)
super().__init__(message, *args, **kwargs)
@@ -15,7 +17,7 @@ def __init__(self, task_id, *args, **kwargs):
def get_task_data(task_id):
"""
- Will raise `TaskNotFound` if the task is in state ``PENDING`` or the task
+ Will raise `TaskNotFound` if the task is in state ``PENDING`` or the task.
meta data has no ``'task_name'`` key set.
"""
diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py
index 85aec548c42..597c2ac6263 100644
--- a/readthedocs/core/views/__init__.py
+++ b/readthedocs/core/views/__init__.py
@@ -133,13 +133,15 @@ def do_not_track(request):
dnt_header = request.META.get('HTTP_DNT')
# https://w3c.github.io/dnt/drafts/tracking-dnt.html#status-representation
- return JsonResponse({ # pylint: disable=redundant-content-type-for-json-response
- 'policy': 'https://docs.readthedocs.io/en/latest/privacy-policy.html',
- 'same-party': [
- 'readthedocs.org',
- 'readthedocs.com',
- 'readthedocs.io', # .org Documentation Sites
- 'readthedocs-hosted.com', # .com Documentation Sites
- ],
- 'tracking': 'N' if dnt_header == '1' else 'T',
- }, content_type='application/tracking-status+json')
+ return JsonResponse(
+ { # pylint: disable=redundant-content-type-for-json-response
+ 'policy': 'https://docs.readthedocs.io/en/latest/privacy-policy.html',
+ 'same-party': [
+ 'readthedocs.org',
+ 'readthedocs.com',
+ 'readthedocs.io', # .org Documentation Sites
+ 'readthedocs-hosted.com', # .com Documentation Sites
+ ],
+ 'tracking': 'N' if dnt_header == '1' else 'T',
+ }, content_type='application/tracking-status+json',
+ )
diff --git a/readthedocs/core/views/hooks.py b/readthedocs/core/views/hooks.py
index a92640e127b..16213e24bb6 100644
--- a/readthedocs/core/views/hooks.py
+++ b/readthedocs/core/views/hooks.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Views pertaining to builds."""
import json
@@ -41,13 +42,14 @@ def _build_version(project, slug, already_built=()):
version = project.versions.filter(active=True, slug=slug).first()
if version and slug not in already_built:
log.info(
- "(Version build) Building %s:%s",
- project.slug, version.slug,
+ '(Version build) Building %s:%s',
+ project.slug,
+ version.slug,
)
trigger_build(project=project, version=version, force=True)
return slug
- log.info("(Version build) Not Building %s", slug)
+ log.info('(Version build) Not Building %s', slug)
return None
@@ -64,8 +66,11 @@ def build_branches(project, branch_list):
for branch in branch_list:
versions = project.versions_from_branch_name(branch)
for version in versions:
- log.info("(Branch Build) Processing %s:%s",
- project.slug, version.slug)
+ log.info(
+ '(Branch Build) Processing %s:%s',
+ project.slug,
+ version.slug,
+ )
ret = _build_version(project, version.slug, already_built=to_build)
if ret:
to_build.add(ret)
@@ -89,9 +94,7 @@ def sync_versions(project):
try:
version_identifier = project.get_default_branch()
version = (
- project.versions
- .filter(identifier=version_identifier)
- .first()
+ project.versions.filter(identifier=version_identifier).first()
)
if not version:
log.info('Unable to sync from %s version', version_identifier)
@@ -114,10 +117,13 @@ def get_project_from_url(url):
def log_info(project, msg):
- log.info(constants.LOG_TEMPLATE
- .format(project=project,
- version='',
- msg=msg))
+ log.info(
+ constants.LOG_TEMPLATE.format(
+ project=project,
+ version='',
+ msg=msg,
+ )
+ )
def _build_url(url, projects, branches):
@@ -127,7 +133,7 @@ def _build_url(url, projects, branches):
Check each of the ``branches`` to see if they are active and should be
built.
"""
- ret = ""
+ ret = ''
all_built = {}
all_not_building = {}
@@ -151,14 +157,18 @@ def _build_url(url, projects, branches):
for project_slug, built in list(all_built.items()):
if built:
msg = '(URL Build) Build Started: {} [{}]'.format(
- url, ' '.join(built))
+ url,
+ ' '.join(built),
+ )
log_info(project_slug, msg=msg)
ret += msg
for project_slug, not_building in list(all_not_building.items()):
if not_building:
msg = '(URL Build) Not Building: {} [{}]'.format(
- url, ' '.join(not_building))
+ url,
+ ' '.join(not_building),
+ )
log_info(project_slug, msg=msg)
ret += msg
@@ -192,7 +202,8 @@ def github_build(request): # noqa: D205
else:
data = json.loads(request.body)
http_url = data['repository']['url']
- http_search_url = http_url.replace('http://', '').replace('https://', '')
+ http_search_url = http_url.replace('http://',
+ '').replace('https://', '')
ssh_url = data['repository']['ssh_url']
ssh_search_url = ssh_url.replace('git@', '').replace('.git', '')
branches = [data['ref'].replace('refs/heads/', '')]
@@ -205,14 +216,14 @@ def github_build(request): # noqa: D205
log.info(
'GitHub webhook search: url=%s branches=%s',
http_search_url,
- branches
+ branches,
)
ssh_projects = get_project_from_url(ssh_search_url)
if ssh_projects:
log.info(
'GitHub webhook search: url=%s branches=%s',
ssh_search_url,
- branches
+ branches,
)
projects = repo_projects | ssh_projects
return _build_url(http_search_url, projects, branches)
@@ -287,24 +298,26 @@ def bitbucket_build(request):
else:
data = json.loads(request.body)
- version = 2 if request.META.get('HTTP_USER_AGENT') == 'Bitbucket-Webhooks/2.0' else 1
+ version = 2 if request.META.get(
+ 'HTTP_USER_AGENT'
+ ) == 'Bitbucket-Webhooks/2.0' else 1
if version == 1:
- branches = [commit.get('branch', '')
- for commit in data['commits']]
+ branches = [
+ commit.get('branch', '') for commit in data['commits']
+ ]
repository = data['repository']
if not repository['absolute_url']:
return HttpResponse('Invalid request', status=400)
search_url = 'bitbucket.org{}'.format(
- repository['absolute_url'].rstrip('/')
+ repository['absolute_url'].rstrip('/'),
)
elif version == 2:
changes = data['push']['changes']
- branches = [change['new']['name']
- for change in changes]
+ branches = [change['new']['name'] for change in changes]
if not data['repository']['full_name']:
return HttpResponse('Invalid request', status=400)
search_url = 'bitbucket.org/{}'.format(
- data['repository']['full_name']
+ data['repository']['full_name'],
)
except (TypeError, ValueError, KeyError):
log.exception('Invalid Bitbucket webhook payload')
@@ -352,10 +365,12 @@ def generic_build(request, project_id_or_slug=None):
project = Project.objects.get(slug=project_id_or_slug)
except (Project.DoesNotExist, ValueError):
log.exception(
- "(Incoming Generic Build) Repo not found: %s",
- project_id_or_slug)
+ '(Incoming Generic Build) Repo not found: %s',
+ project_id_or_slug,
+ )
return HttpResponseNotFound(
- 'Repo not found: %s' % project_id_or_slug)
+ 'Repo not found: %s' % project_id_or_slug,
+ )
# This endpoint doesn't require authorization, we shouldn't allow builds to
# be triggered from this any longer. Deprecation plan is to selectively
# allow access to this endpoint for now.
@@ -364,8 +379,11 @@ def generic_build(request, project_id_or_slug=None):
if request.method == 'POST':
slug = request.POST.get('version_slug', project.default_version)
log.info(
- "(Incoming Generic Build) %s [%s]", project.slug, slug)
+ '(Incoming Generic Build) %s [%s]',
+ project.slug,
+ slug,
+ )
_build_version(project, slug)
else:
- return HttpResponse("You must POST to this resource.")
+ return HttpResponse('You must POST to this resource.')
return redirect('builds_project_list', project.slug)
diff --git a/readthedocs/core/views/serve.py b/readthedocs/core/views/serve.py
index 7f8bb3651aa..9f87d82abf7 100644
--- a/readthedocs/core/views/serve.py
+++ b/readthedocs/core/views/serve.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""
Doc serving from Python.
@@ -54,8 +55,11 @@ def map_subproject_slug(view_func):
.. warning:: Does not take into account any kind of privacy settings.
"""
+
@wraps(view_func)
- def inner_view(request, subproject=None, subproject_slug=None, *args, **kwargs): # noqa
+ def inner_view(
+ request, subproject=None, subproject_slug=None, *args, **kwargs
+ ): # noqa
if subproject is None and subproject_slug:
# Try to fetch by subproject alias first, otherwise we might end up
# redirected to an unrelated project.
@@ -81,8 +85,11 @@ def map_project_slug(view_func):
.. warning:: Does not take into account any kind of privacy settings.
"""
+
@wraps(view_func)
- def inner_view(request, project=None, project_slug=None, *args, **kwargs): # noqa
+ def inner_view(
+ request, project=None, project_slug=None, *args, **kwargs
+ ): # noqa
if project is None:
if not project_slug:
project_slug = request.slug
@@ -107,7 +114,8 @@ def redirect_project_slug(request, project, subproject): # pylint: disable=unus
def redirect_page_with_filename(request, project, subproject, filename): # pylint: disable=unused-argument # noqa
"""Redirect /page/file.html to /en/latest/file.html."""
return HttpResponseRedirect(
- resolve(subproject or project, filename=filename))
+ resolve(subproject or project, filename=filename),
+ )
def _serve_401(request, project):
@@ -125,7 +133,8 @@ def _serve_file(request, filename, basepath):
# Serve from Nginx
content_type, encoding = mimetypes.guess_type(
- os.path.join(basepath, filename))
+ os.path.join(basepath, filename),
+ )
content_type = content_type or 'application/octet-stream'
response = HttpResponse(content_type=content_type)
if encoding:
@@ -144,9 +153,15 @@ def _serve_file(request, filename, basepath):
@map_project_slug
@map_subproject_slug
def serve_docs(
- request, project, subproject, lang_slug=None, version_slug=None,
- filename=''):
- """Exists to map existing proj, lang, version, filename views to the file format."""
+ request,
+ project,
+ subproject,
+ lang_slug=None,
+ version_slug=None,
+ filename='',
+):
+ """Exists to map existing proj, lang, version, filename views to the file
+ format."""
if not version_slug:
version_slug = project.get_default_version()
try:
@@ -211,4 +226,5 @@ def _serve_symlink_docs(request, project, privacy_level, filename=''):
files_tried.append(os.path.join(basepath, filename))
raise Http404(
- 'File not found. Tried these files: %s' % ','.join(files_tried))
+ 'File not found. Tried these files: %s' % ','.join(files_tried),
+ )
diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py
index 65b35f5f28f..805eb0ce5d3 100644
--- a/readthedocs/doc_builder/backends/mkdocs.py
+++ b/readthedocs/doc_builder/backends/mkdocs.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
MkDocs_ backend for building docs.
@@ -45,7 +46,8 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.old_artifact_path = os.path.join(
self.version.project.checkout_path(self.version.slug),
- self.build_dir)
+ self.build_dir,
+ )
self.root_path = self.version.project.checkout_path(self.version.slug)
self.yaml_file = self.get_yaml_config()
@@ -65,14 +67,13 @@ def __init__(self, *args, **kwargs):
else:
self.DEFAULT_THEME_NAME = 'mkdocs'
-
def get_yaml_config(self):
"""Find the ``mkdocs.yml`` file in the project root."""
mkdoc_path = self.config.mkdocs.configuration
if not mkdoc_path:
mkdoc_path = os.path.join(
self.project.checkout_path(self.version.slug),
- 'mkdocs.yml'
+ 'mkdocs.yml',
)
if not os.path.exists(mkdoc_path):
return None
@@ -85,9 +86,7 @@ def load_yaml_config(self):
Raise BuildEnvironmentError if failed due to syntax errors.
"""
try:
- return yaml.safe_load(
- open(self.yaml_file, 'r')
- )
+ return yaml.safe_load(open(self.yaml_file, 'r'),)
except IOError:
return {
'site_name': self.version.project.name,
@@ -96,10 +95,12 @@ def load_yaml_config(self):
note = ''
if hasattr(exc, 'problem_mark'):
mark = exc.problem_mark
- note = ' (line %d, column %d)' % (mark.line + 1, mark.column + 1)
+ note = ' (line %d, column %d)' % (
+ mark.line + 1, mark.column + 1
+ )
raise MkDocsYAMLParseError(
'Your mkdocs.yml could not be loaded, '
- 'possibly due to a syntax error{note}'.format(note=note)
+ 'possibly due to a syntax error{note}'.format(note=note),
)
def append_conf(self, **__):
@@ -131,13 +132,13 @@ def append_conf(self, **__):
# of the mkdocs configuration file.
docs_path = os.path.join(
os.path.dirname(self.yaml_file),
- docs_dir
+ docs_dir,
)
# RTD javascript writing
rtd_data = self.generate_rtd_data(
docs_dir=os.path.relpath(docs_path, self.root_path),
- mkdocs_config=user_config
+ mkdocs_config=user_config,
)
with open(os.path.join(docs_path, 'readthedocs-data.js'), 'w') as f:
f.write(rtd_data)
@@ -156,7 +157,7 @@ def append_conf(self, **__):
# Write the modified mkdocs configuration
yaml.safe_dump(
user_config,
- open(self.yaml_file, 'w')
+ open(self.yaml_file, 'w'),
)
# Write the mkdocs.yml to the build logs
@@ -183,13 +184,17 @@ def generate_rtd_data(self, docs_dir, mkdocs_config):
'programming_language': self.version.project.programming_language,
'page': None,
'theme': self.get_theme_name(mkdocs_config),
- 'builder': "mkdocs",
+ 'builder': 'mkdocs',
'docroot': docs_dir,
- 'source_suffix': ".md",
- 'api_host': getattr(settings, 'PUBLIC_API_URL', 'https://readthedocs.org'),
+ 'source_suffix': '.md',
+ 'api_host': getattr(
+ settings, 'PUBLIC_API_URL', 'https://readthedocs.org'
+ ),
'ad_free': not self.project.show_advertising,
'commit': self.version.project.vcs_repo(self.version.slug).commit,
- 'global_analytics_code': getattr(settings, 'GLOBAL_ANALYTICS_CODE', 'UA-17997319-1'),
+ 'global_analytics_code': getattr(
+ settings, 'GLOBAL_ANALYTICS_CODE', 'UA-17997319-1'
+ ),
'user_analytics_code': analytics_code,
}
data_json = json.dumps(readthedocs_data, indent=4)
@@ -210,21 +215,22 @@ def build(self):
self.python_env.venv_bin(filename='mkdocs'),
self.builder,
'--clean',
- '--site-dir', self.build_dir,
- '--config-file', self.yaml_file,
+ '--site-dir',
+ self.build_dir,
+ '--config-file',
+ self.yaml_file,
]
if self.config.mkdocs.fail_on_warning:
build_command.append('--strict')
cmd_ret = self.run(
- *build_command,
- cwd=checkout_path,
+ *build_command, cwd=checkout_path,
bin_path=self.python_env.venv_bin()
)
return cmd_ret.successful
def get_theme_name(self, mkdocs_config):
"""
- Get the theme configuration in the mkdocs_config
+ Get the theme configuration in the mkdocs_config.
In v0.17.0, the theme configuration switched
from two separate configs (both optional) to a nested directive.
diff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py
index 8402cfd7e1c..b656433e527 100644
--- a/readthedocs/doc_builder/backends/sphinx.py
+++ b/readthedocs/doc_builder/backends/sphinx.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""
Sphinx_ backend for building docs.
@@ -44,7 +45,7 @@ def __init__(self, *args, **kwargs):
self.config_file = self.project.conf_file(self.version.slug)
self.old_artifact_path = os.path.join(
os.path.dirname(self.config_file),
- self.sphinx_build_dir
+ self.sphinx_build_dir,
)
except ProjectConfigurationError:
docs_dir = self.docs_dir()
@@ -57,11 +58,13 @@ def _write_config(self, master_doc='index'):
"""Create ``conf.py`` if it doesn't exist."""
docs_dir = self.docs_dir()
conf_template = render_to_string(
- 'sphinx/conf.py.conf', {
+ 'sphinx/conf.py.conf',
+ {
'project': self.project,
'version': self.version,
'master_doc': master_doc,
- })
+ },
+ )
conf_file = os.path.join(docs_dir, 'conf.py')
safe_write(conf_file, conf_template)
@@ -73,25 +76,28 @@ def get_config_params(self):
os.path.dirname(
os.path.relpath(
self.config_file,
- self.project.checkout_path(self.version.slug)
- )
+ self.project.checkout_path(self.version.slug),
+ ),
),
'',
)
remote_version = self.version.commit_name
github_user, github_repo = version_utils.get_github_username_repo(
- url=self.project.repo)
+ url=self.project.repo,
+ )
github_version_is_editable = (self.version.type == 'branch')
display_github = github_user is not None
bitbucket_user, bitbucket_repo = version_utils.get_bitbucket_username_repo( # noqa
- url=self.project.repo)
+ url=self.project.repo,
+ )
bitbucket_version_is_editable = (self.version.type == 'branch')
display_bitbucket = bitbucket_user is not None
gitlab_user, gitlab_repo = version_utils.get_gitlab_username_repo(
- url=self.project.repo)
+ url=self.project.repo,
+ )
gitlab_version_is_editable = (self.version.type == 'branch')
display_gitlab = gitlab_user is not None
@@ -143,7 +149,7 @@ def get_config_params(self):
# Features
'dont_overwrite_sphinx_context': self.project.has_feature(
- Feature.DONT_OVERWRITE_SPHINX_CONTEXT
+ Feature.DONT_OVERWRITE_SPHINX_CONTEXT,
),
}
@@ -156,22 +162,22 @@ def get_config_params(self):
return data
def append_conf(self, **__):
- """Find or create a ``conf.py`` with a rendered ``doc_builder/conf.py.tmpl`` appended"""
+ """Find or create a ``conf.py`` with a rendered
+ ``doc_builder/conf.py.tmpl`` appended."""
if self.config_file is None:
master_doc = self.create_index(extension='rst')
self._write_config(master_doc=master_doc)
try:
self.config_file = (
- self.config_file or
- self.project.conf_file(self.version.slug)
+ self.config_file or self.project.conf_file(self.version.slug)
)
outfile = codecs.open(self.config_file, encoding='utf-8', mode='a')
except (ProjectConfigurationError, IOError):
trace = sys.exc_info()[2]
raise ProjectConfigurationError(
- ProjectConfigurationError.NOT_FOUND
- ).with_traceback(trace)
+ ProjectConfigurationError.NOT_FOUND,
+ ).with_traceback(trace)
# Append config to project conf file
tmpl = template_loader.get_template('doc_builder/conf.py.tmpl')
@@ -215,8 +221,7 @@ def build(self):
self.sphinx_build_dir,
])
cmd_ret = self.run(
- *build_command,
- cwd=os.path.dirname(self.config_file),
+ *build_command, cwd=os.path.dirname(self.config_file),
bin_path=self.python_env.venv_bin()
)
return cmd_ret.successful
@@ -235,10 +240,11 @@ def move(self, **__):
# Copy JSON artifacts to its own directory
# to keep compatibility with the older builder.
json_path = os.path.abspath(
- os.path.join(self.old_artifact_path, '..', 'json')
+ os.path.join(self.old_artifact_path, '..', 'json'),
)
json_path_target = self.project.artifact_path(
- version=self.version.slug, type_='sphinx_search'
+ version=self.version.slug,
+ type_='sphinx_search',
)
if os.path.exists(json_path):
if os.path.exists(json_path_target):
@@ -246,12 +252,10 @@ def move(self, **__):
log.info('Copying json on the local filesystem')
shutil.copytree(
json_path,
- json_path_target
+ json_path_target,
)
else:
- log.warning(
- 'Not moving json because the build dir is unknown.'
- )
+ log.warning('Not moving json because the build dir is unknown.',)
class HtmlDirBuilder(HtmlBuilder):
@@ -297,7 +301,8 @@ def move(self, **__):
filename=to_write,
arcname=os.path.join(
'{}-{}'.format(self.project.slug, self.version.slug),
- to_write),
+ to_write,
+ ),
)
archive.close()
@@ -386,11 +391,16 @@ def build(self):
# Run LaTeX -> PDF conversions
pdflatex_cmds = [
['pdflatex', '-interaction=nonstopmode', tex_file]
- for tex_file in tex_files] # yapf: disable
+ for tex_file in tex_files
+ ] # yapf: disable
makeindex_cmds = [
- ['makeindex', '-s', 'python.ist', '{}.idx'.format(
- os.path.splitext(os.path.relpath(tex_file, latex_cwd))[0])]
- for tex_file in tex_files] # yapf: disable
+ [
+ 'makeindex', '-s', 'python.ist', '{}.idx'.format(
+ os.path.splitext(os.path.relpath(tex_file, latex_cwd))[0],
+ ),
+ ]
+ for tex_file in tex_files
+ ] # yapf: disable
if self.build_env.command_class == DockerBuildCommand:
latex_class = DockerLatexBuildCommand
@@ -399,15 +409,27 @@ def build(self):
pdf_commands = []
for cmd in pdflatex_cmds:
cmd_ret = self.build_env.run_command_class(
- cls=latex_class, cmd=cmd, cwd=latex_cwd, warn_only=True)
+ cls=latex_class,
+ cmd=cmd,
+ cwd=latex_cwd,
+ warn_only=True,
+ )
pdf_commands.append(cmd_ret)
for cmd in makeindex_cmds:
cmd_ret = self.build_env.run_command_class(
- cls=latex_class, cmd=cmd, cwd=latex_cwd, warn_only=True)
+ cls=latex_class,
+ cmd=cmd,
+ cwd=latex_cwd,
+ warn_only=True,
+ )
pdf_commands.append(cmd_ret)
for cmd in pdflatex_cmds:
cmd_ret = self.build_env.run_command_class(
- cls=latex_class, cmd=cmd, cwd=latex_cwd, warn_only=True)
+ cls=latex_class,
+ cmd=cmd,
+ cwd=latex_cwd,
+ warn_only=True,
+ )
pdf_match = PDF_RE.search(cmd_ret.output)
if pdf_match:
self.pdf_file_name = pdf_match.group(1).strip()
@@ -441,7 +463,9 @@ def move(self, **__):
from_file = None
if from_file:
to_file = os.path.join(
- self.target, '{}.pdf'.format(self.project.slug))
+ self.target,
+ '{}.pdf'.format(self.project.slug),
+ )
self.run(
'mv',
'-f',
diff --git a/readthedocs/doc_builder/base.py b/readthedocs/doc_builder/base.py
index 8810ab5f782..6b143ca8db9 100644
--- a/readthedocs/doc_builder/base.py
+++ b/readthedocs/doc_builder/base.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Base classes for Builders."""
import logging
@@ -46,7 +47,9 @@ def __init__(self, build_env, python_env, force=False):
self.config = python_env.config if python_env else None
self._force = force
self.target = self.project.artifact_path(
- version=self.version.slug, type_=self.type)
+ version=self.version.slug,
+ type_=self.type,
+ )
def force(self, **__):
"""An optional step to force a build even when nothing has changed."""
@@ -67,7 +70,7 @@ def move(self, **__):
shutil.copytree(
self.old_artifact_path,
self.target,
- ignore=shutil.ignore_patterns(*self.ignore_patterns)
+ ignore=shutil.ignore_patterns(*self.ignore_patterns),
)
else:
log.warning('Not moving docs, because the build dir is unknown.')
@@ -96,10 +99,14 @@ def create_index(self, extension='md', **__):
docs_dir = self.docs_dir()
index_filename = os.path.join(
- docs_dir, 'index.{ext}'.format(ext=extension))
+ docs_dir,
+ 'index.{ext}'.format(ext=extension),
+ )
if not os.path.exists(index_filename):
readme_filename = os.path.join(
- docs_dir, 'README.{ext}'.format(ext=extension))
+ docs_dir,
+ 'README.{ext}'.format(ext=extension),
+ )
if os.path.exists(readme_filename):
return 'README'
diff --git a/readthedocs/doc_builder/config.py b/readthedocs/doc_builder/config.py
index 9eabeaa0ce0..092939d9629 100644
--- a/readthedocs/doc_builder/config.py
+++ b/readthedocs/doc_builder/config.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""An API to load config from a readthedocs.yml file."""
from os import path
@@ -30,7 +31,7 @@ def load_yaml_config(version):
try:
sphinx_configuration = path.join(
version.get_conf_py_path(),
- 'conf.py'
+ 'conf.py',
)
except ProjectConfigurationError:
sphinx_configuration = None
@@ -51,7 +52,7 @@ def load_yaml_config(version):
'sphinx_configuration': sphinx_configuration,
'build_image': project.container_image,
'doctype': project.documentation_type,
- }
+ },
}
img_settings = DOCKER_IMAGE_SETTINGS.get(img_name, None)
if img_settings:
diff --git a/readthedocs/doc_builder/constants.py b/readthedocs/doc_builder/constants.py
index f2f15010bba..1abacebba0a 100644
--- a/readthedocs/doc_builder/constants.py
+++ b/readthedocs/doc_builder/constants.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Doc build constants."""
import logging
@@ -31,7 +32,9 @@
old_config = getattr(settings, 'DOCKER_BUILD_IMAGES', None)
if old_config:
- log.warning('Old config detected, DOCKER_BUILD_IMAGES->DOCKER_IMAGE_SETTINGS')
+ log.warning(
+ 'Old config detected, DOCKER_BUILD_IMAGES->DOCKER_IMAGE_SETTINGS'
+ )
DOCKER_IMAGE_SETTINGS.update(old_config)
DOCKER_LIMITS = {'memory': '200m', 'time': 600}
diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py
index cbdff522d92..9276e187a7d 100644
--- a/readthedocs/doc_builder/environments.py
+++ b/readthedocs/doc_builder/environments.py
@@ -84,9 +84,19 @@ class BuildCommand(BuildCommandResultMixin):
:param description: a more grokable description of the command being run
"""
- def __init__(self, command, cwd=None, shell=False, environment=None,
- combine_output=True, input_data=None, build_env=None,
- bin_path=None, description=None, record_as_success=False):
+ def __init__(
+ self,
+ command,
+ cwd=None,
+ shell=False,
+ environment=None,
+ combine_output=True,
+ input_data=None,
+ build_env=None,
+ bin_path=None,
+ description=None,
+ record_as_success=False,
+ ):
self.command = command
self.shell = shell
if cwd is None:
@@ -301,11 +311,16 @@ def run(self):
# nicer. Sometimes the kernel kills the command and Docker doesn't
# not use the specific exit code, so we check if the word `Killed`
# is in the last 15 lines of the command's output
- killed_in_output = 'Killed' in '\n'.join(self.output.splitlines()[-15:])
- if self.exit_code == DOCKER_OOM_EXIT_CODE or (self.exit_code == 1 and killed_in_output):
- self.output += str(_(
- '\n\nCommand killed due to excessive memory consumption\n'
- ))
+ killed_in_output = 'Killed' in '\n'.join(
+ self.output.splitlines()[-15:]
+ )
+ if self.exit_code == DOCKER_OOM_EXIT_CODE or (self.exit_code == 1 and
+ killed_in_output):
+ self.output += str(
+ _(
+ '\n\nCommand killed due to excessive memory consumption\n',
+ )
+ )
except DockerAPIError:
self.exit_code = -1
if self.output is None or not self.output:
@@ -323,17 +338,25 @@ def get_wrapped_command(self):
install requests<0.8``. This escapes a good majority of those
characters.
"""
- bash_escape_re = re.compile(r"([\t\ \!\"\#\$\&\'\(\)\*\:\;\<\>\?\@"
- r"\[\\\]\^\`\{\|\}\~])")
+ bash_escape_re = re.compile(
+ r"([\t\ \!\"\#\$\&\'\(\)\*\:\;\<\>\?\@"
+ r'\[\\\]\^\`\{\|\}\~])',
+ )
prefix = ''
if self.bin_path:
prefix += 'PATH={}:$PATH '.format(self.bin_path)
- return ("/bin/sh -c 'cd {cwd} && {prefix}{cmd}'"
- .format(
- cwd=self.cwd,
- prefix=prefix,
- cmd=(' '.join([bash_escape_re.sub(r'\\\1', part)
- for part in self.command]))))
+ return (
+ "/bin/sh -c 'cd {cwd} && {prefix}{cmd}'".format(
+ cwd=self.cwd,
+ prefix=prefix,
+ cmd=(
+ ' '.join([
+ bash_escape_re.sub(r'\\\1', part)
+ for part in self.command
+ ])
+ ),
+ )
+ )
class BaseEnvironment:
@@ -359,7 +382,8 @@ def run(self, *cmd, **kwargs):
def run_command_class(
self, cls, cmd, record=None, warn_only=False,
- record_as_success=False, **kwargs):
+ record_as_success=False, **kwargs
+ ):
"""
Run command from this environment.
@@ -415,11 +439,13 @@ def run_command_class(
msg += ':\n{out}'.format(out=build_cmd.output)
if warn_only:
- log.warning(LOG_TEMPLATE.format(
- project=self.project.slug,
- version='latest',
- msg=msg,
- ))
+ log.warning(
+ LOG_TEMPLATE.format(
+ project=self.project.slug,
+ version='latest',
+ msg=msg,
+ )
+ )
else:
raise BuildEnvironmentWarning(msg)
return build_cmd
@@ -475,8 +501,16 @@ class BuildEnvironment(BaseEnvironment):
MkDocsYAMLParseError,
)
- def __init__(self, project=None, version=None, build=None, config=None,
- record=True, environment=None, update_on_success=True):
+ def __init__(
+ self,
+ project=None,
+ version=None,
+ build=None,
+ config=None,
+ record=True,
+ environment=None,
+ update_on_success=True,
+ ):
super().__init__(project, environment)
self.version = version
self.build = build
@@ -563,23 +597,30 @@ def run_command_class(self, *cmd, **kwargs): # pylint: disable=arguments-differ
@property
def successful(self):
- """Is build completed, without top level failures or failing commands.""" # noqa
- return (self.done and self.failure is None and
- all(cmd.successful for cmd in self.commands))
+ """Is build completed, without top level failures or failing
+ commands.""" # noqa
+ 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."""
- return (self.done and (
- self.failure is not None or
- any(cmd.failed for cmd in self.commands)
- ))
+ return (
+ self.done and (
+ self.failure is not None or
+ any(cmd.failed for cmd in self.commands)
+ )
+ )
@property
def done(self):
"""Is build in finished state."""
- return (self.build is not None and
- self.build['state'] == BUILD_STATE_FINISHED)
+ return (
+ self.build is not None and
+ self.build['state'] == BUILD_STATE_FINISHED
+ )
def update_build(self, state=None):
"""
@@ -627,10 +668,10 @@ def update_build(self, state=None):
# BuildEnvironmentError
if not isinstance(
self.failure,
- (
- BuildEnvironmentException,
- BuildEnvironmentWarning,
- ),
+ (
+ BuildEnvironmentException,
+ BuildEnvironmentWarning,
+ ),
):
log.error(
'Build failed with unhandled exception: %s',
@@ -668,7 +709,7 @@ def update_build(self, state=None):
if update_build:
try:
api_v2.build(self.build['id']).put(self.build)
- except HttpClientError as e:
+ except HttpClientError:
log.exception(
'Unable to update build: id=%d',
self.build['id'],
@@ -751,10 +792,11 @@ def __enter__(self):
project=self.project.slug,
version=self.version.slug,
msg=(
- 'Removing stale container {}'
- .format(self.container_id)
+ 'Removing stale container {}'.format(
+ self.container_id
+ )
),
- )
+ ),
)
client = self.get_client()
client.remove_container(self.container_id)
@@ -826,7 +868,7 @@ def get_client(self):
version=DOCKER_VERSION,
)
return self.client
- except DockerException as e:
+ except DockerException:
log.exception(
LOG_TEMPLATE.format(
project=self.project.slug,
@@ -924,10 +966,10 @@ def update_build_from_container_state(self):
)
elif state.get('Error'):
self.failure = BuildEnvironmentError((
- _('Build exited due to unknown error: {0}')
- .format(state.get('Error'))
- ),
- )
+ _('Build exited due to unknown error: {0}').format(
+ state.get('Error')
+ )
+ ),)
def create_container(self):
"""Create docker container."""
@@ -952,7 +994,7 @@ def create_container(self):
environment=self.environment,
)
client.start(container=self.container_id)
- except ConnectionError as e:
+ except ConnectionError:
log.exception(
LOG_TEMPLATE.format(
project=self.project.slug,
diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py
index 5898dfce015..ad6a458c874 100644
--- a/readthedocs/doc_builder/exceptions.py
+++ b/readthedocs/doc_builder/exceptions.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Exceptions raised when building documentation."""
from django.utils.translation import ugettext_noop
@@ -9,7 +10,9 @@ class BuildEnvironmentException(Exception):
status_code = None
def __init__(self, message=None, **kwargs):
- self.status_code = kwargs.pop('status_code', None) or self.status_code or 1
+ self.status_code = kwargs.pop(
+ 'status_code', None
+ ) or self.status_code or 1
message = message or self.get_default_message()
super().__init__(message, **kwargs)
diff --git a/readthedocs/doc_builder/loader.py b/readthedocs/doc_builder/loader.py
index 8d733b3d8a7..b878009d206 100644
--- a/readthedocs/doc_builder/loader.py
+++ b/readthedocs/doc_builder/loader.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Lookup tables for builders and backends."""
from importlib import import_module
@@ -6,11 +7,19 @@
# Managers
mkdocs = import_module(
- getattr(settings, 'MKDOCS_BACKEND',
- 'readthedocs.doc_builder.backends.mkdocs'))
+ getattr(
+ settings,
+ 'MKDOCS_BACKEND',
+ 'readthedocs.doc_builder.backends.mkdocs',
+ ),
+)
sphinx = import_module(
- getattr(settings, 'SPHINX_BACKEND',
- 'readthedocs.doc_builder.backends.sphinx'))
+ getattr(
+ settings,
+ 'SPHINX_BACKEND',
+ 'readthedocs.doc_builder.backends.sphinx',
+ ),
+)
BUILDER_BY_NAME = {
# Possible HTML Builders
diff --git a/readthedocs/doc_builder/python_environments.py b/readthedocs/doc_builder/python_environments.py
index 92ec9a2f5f0..76b85188077 100644
--- a/readthedocs/doc_builder/python_environments.py
+++ b/readthedocs/doc_builder/python_environments.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""An abstraction over virtualenv and Conda environments."""
import copy
@@ -40,24 +41,29 @@ def delete_existing_build_dir(self):
# Handle deleting old build dir
build_dir = os.path.join(
self.venv_path(),
- 'build')
+ 'build',
+ )
if os.path.exists(build_dir):
- log.info(LOG_TEMPLATE.format(
- project=self.project.slug,
- version=self.version.slug,
- msg='Removing existing build directory',
- ))
+ log.info(
+ LOG_TEMPLATE.format(
+ project=self.project.slug,
+ version=self.version.slug,
+ msg='Removing existing build directory',
+ )
+ )
shutil.rmtree(build_dir)
def delete_existing_venv_dir(self):
venv_dir = self.venv_path()
# Handle deleting old venv dir
if os.path.exists(venv_dir):
- log.info(LOG_TEMPLATE.format(
- project=self.project.slug,
- version=self.version.slug,
- msg='Removing existing venv directory',
- ))
+ log.info(
+ LOG_TEMPLATE.format(
+ project=self.project.slug,
+ version=self.version.slug,
+ msg='Removing existing venv directory',
+ )
+ )
shutil.rmtree(venv_dir)
def install_package(self):
@@ -66,7 +72,7 @@ def install_package(self):
extra_req_param = ''
if self.config.python.extra_requirements:
extra_req_param = '[{}]'.format(
- ','.join(self.config.python.extra_requirements)
+ ','.join(self.config.python.extra_requirements),
)
self.build_env.run(
'python',
@@ -134,7 +140,9 @@ def is_obsolete(self):
with open(self.environment_json_path(), 'r') as fpath:
environment_conf = json.load(fpath)
except (IOError, TypeError, KeyError, ValueError):
- log.warning('Unable to read/parse readthedocs-environment.json file')
+ log.warning(
+ 'Unable to read/parse readthedocs-environment.json file'
+ )
# We remove the JSON file here to avoid cycling over time with a
# corrupted file.
os.remove(self.environment_json_path())
@@ -168,7 +176,8 @@ def is_obsolete(self):
])
def save_environment_json(self):
- """Save on disk Python and build image versions used to create the venv."""
+ """Save on disk Python and build image versions used to create the
+ venv."""
data = {
'python': {
'version': self.config.python_full_version,
@@ -233,9 +242,7 @@ def install_core_requirements(self):
# so it is used when installing the other requirements.
cmd = pip_install_cmd + ['pip']
self.build_env.run(
- *cmd,
- bin_path=self.venv_bin(),
- cwd=self.checkout_path
+ *cmd, bin_path=self.venv_bin(), cwd=self.checkout_path
)
requirements = [
@@ -267,7 +274,7 @@ def install_core_requirements(self):
negative='sphinx<1.8',
),
'sphinx-rtd-theme<0.5',
- 'readthedocs-sphinx-ext<0.6'
+ 'readthedocs-sphinx-ext<0.6',
])
cmd = copy.copy(pip_install_cmd)
@@ -288,8 +295,10 @@ def install_user_requirements(self):
requirements_file_path = self.config.python.requirements
if not requirements_file_path and requirements_file_path != '':
builder_class = get_builder_class(self.config.doctype)
- docs_dir = (builder_class(build_env=self.build_env, python_env=self)
- .docs_dir())
+ docs_dir = (
+ builder_class(build_env=self.build_env,
+ python_env=self).docs_dir()
+ )
paths = [docs_dir, '']
req_files = ['pip_requirements.txt', 'requirements.txt']
for path, req_file in itertools.product(paths, req_files):
@@ -337,11 +346,13 @@ def setup_base(self):
if os.path.exists(version_path):
# Re-create conda directory each time to keep fresh state
- log.info(LOG_TEMPLATE.format(
- project=self.project.slug,
- version=self.version.slug,
- msg='Removing existing conda directory',
- ))
+ log.info(
+ LOG_TEMPLATE.format(
+ project=self.project.slug,
+ version=self.version.slug,
+ msg='Removing existing conda directory',
+ )
+ )
shutil.rmtree(version_path)
self.build_env.run(
'conda',
diff --git a/readthedocs/doc_builder/signals.py b/readthedocs/doc_builder/signals.py
index 08991fabb03..9911ba3ab5c 100644
--- a/readthedocs/doc_builder/signals.py
+++ b/readthedocs/doc_builder/signals.py
@@ -1,8 +1,9 @@
-"""Signals for adding custom context data"""
+# -*- coding: utf-8 -*-
+"""Signals for adding custom context data."""
import django.dispatch
finalize_sphinx_context_data = django.dispatch.Signal(
- providing_args=['buildenv', 'context', 'response_data']
+ providing_args=['buildenv', 'context', 'response_data'],
)
diff --git a/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl b/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl
index 4dda93914e8..29ab61b0e65 100644
--- a/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl
+++ b/readthedocs/doc_builder/templates/doc_builder/data.js.tmpl
@@ -6,7 +6,7 @@ var doc_slug = "{{ slug }}";
var page_name = "{{ pagename }}";
var html_theme = "{{ html_theme }}";
-// mkdocs_page_input_path is only defined on the RTD mkdocs theme but it isn't
+// mkdocs_page_input_path is only defined on the RTD mkdocs theme but it isn't
// available on all pages (e.g. missing in search result)
if (typeof mkdocs_page_input_path !== "undefined") {
READTHEDOCS_DATA["page"] = mkdocs_page_input_path.substr(
diff --git a/readthedocs/gold/__init__.py b/readthedocs/gold/__init__.py
index b26c8ed7c84..b7fcb9ea10e 100644
--- a/readthedocs/gold/__init__.py
+++ b/readthedocs/gold/__init__.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
A Django app for Gold Membership.
diff --git a/readthedocs/gold/admin.py b/readthedocs/gold/admin.py
index a1dc3e80e45..020d2be9493 100644
--- a/readthedocs/gold/admin.py
+++ b/readthedocs/gold/admin.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Django admin configuration for the Gold Membership app."""
from django.contrib import admin
diff --git a/readthedocs/gold/apps.py b/readthedocs/gold/apps.py
index 380a21f0947..5891264c8ef 100644
--- a/readthedocs/gold/apps.py
+++ b/readthedocs/gold/apps.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Django app configuration for the Gold Membership app."""
from django.apps import AppConfig
diff --git a/readthedocs/gold/forms.py b/readthedocs/gold/forms.py
index 31c4a18aa1d..4840745d8b8 100644
--- a/readthedocs/gold/forms.py
+++ b/readthedocs/gold/forms.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Gold subscription forms."""
from django import forms
@@ -28,9 +29,11 @@ class Meta:
required=True,
min_length=4,
max_length=4,
- widget=forms.HiddenInput(attrs={
- 'data-bind': 'valueInit: last_4_card_digits, value: last_4_card_digits',
- })
+ widget=forms.HiddenInput(
+ attrs={
+ 'data-bind': 'valueInit: last_4_card_digits, value: last_4_card_digits',
+ }
+ ),
)
level = forms.ChoiceField(
@@ -50,7 +53,8 @@ def validate_stripe(self):
def get_customer_kwargs(self):
data = {
- 'description': self.customer.get_full_name() or self.customer.username,
+ 'description': self.customer.get_full_name() or
+ self.customer.username,
'email': self.customer.email,
'id': self.instance.stripe_id or None,
}
@@ -78,7 +82,7 @@ def get_subscription(self):
# Add a new subscription
subscription = customer.subscriptions.create(
plan=self.cleaned_data['level'],
- source=self.cleaned_data['stripe_token']
+ source=self.cleaned_data['stripe_token'],
)
return subscription
@@ -87,7 +91,7 @@ def get_subscription(self):
class GoldProjectForm(forms.Form):
project = forms.ChoiceField(
required=True,
- help_text='Select a project.'
+ help_text='Select a project.',
)
def __init__(self, active_user, *args, **kwargs):
@@ -114,4 +118,6 @@ def clean(self):
if self.projects.count() < self.user.num_supported_projects:
return cleaned_data
- self.add_error(None, 'You already have the max number of supported projects.')
+ self.add_error(
+ None, 'You already have the max number of supported projects.'
+ )
diff --git a/readthedocs/gold/signals.py b/readthedocs/gold/signals.py
index 4c089ebb0d5..155230f3854 100644
--- a/readthedocs/gold/signals.py
+++ b/readthedocs/gold/signals.py
@@ -1,4 +1,5 @@
-"""Gold model signals"""
+# -*- coding: utf-8 -*-
+"""Gold model signals."""
from django.db.models.signals import pre_delete
from django.dispatch import receiver
@@ -10,6 +11,6 @@
@receiver(pre_delete, sender=GoldUser)
def delete_customer(sender, instance, **__):
- """On Gold subscription deletion, remove the customer from Stripe"""
+ """On Gold subscription deletion, remove the customer from Stripe."""
if sender == GoldUser and instance.stripe_id is not None:
utils.delete_customer(instance.stripe_id)
diff --git a/readthedocs/gold/templates/gold/projects.html b/readthedocs/gold/templates/gold/projects.html
index 682b7bf3c9f..e832b53ee67 100644
--- a/readthedocs/gold/templates/gold/projects.html
+++ b/readthedocs/gold/templates/gold/projects.html
@@ -48,4 +48,3 @@ {% trans "Add a project" %}
{% endblock %}
-
diff --git a/readthedocs/gold/templates/gold/subscription_form.html b/readthedocs/gold/templates/gold/subscription_form.html
index 9dc1ae592dc..241d7ddd22a 100644
--- a/readthedocs/gold/templates/gold/subscription_form.html
+++ b/readthedocs/gold/templates/gold/subscription_form.html
@@ -79,7 +79,7 @@ Domains
You are currently using {{ domains.count }} domains:
-
+
{% for domain in domains %}
- {{ domain.domain }} ({{ domain.project.name }})
diff --git a/readthedocs/gold/urls.py b/readthedocs/gold/urls.py
index 2a1e1acafe3..c57ccab39b8 100644
--- a/readthedocs/gold/urls.py
+++ b/readthedocs/gold/urls.py
@@ -1,4 +1,5 @@
-"""Gold subscription URLs"""
+# -*- coding: utf-8 -*-
+"""Gold subscription URLs."""
from django.conf.urls import url
@@ -8,12 +9,22 @@
urlpatterns = [
url(r'^$', views.DetailGoldSubscription.as_view(), name='gold_detail'),
- url(r'^subscription/$', views.UpdateGoldSubscription.as_view(),
- name='gold_subscription'),
- url(r'^cancel/$', views.DeleteGoldSubscription.as_view(), name='gold_cancel'),
+ url(
+ r'^subscription/$',
+ views.UpdateGoldSubscription.as_view(),
+ name='gold_subscription',
+ ),
+ url(
+ r'^cancel/$', views.DeleteGoldSubscription.as_view(), name='gold_cancel'
+ ),
url(r'^projects/$', views.projects, name='gold_projects'),
- url((r'^projects/remove/(?P{project_slug})/$'
- .format(project_slug=PROJECT_SLUG_REGEX)),
+ url(
+ (
+ r'^projects/remove/(?P{project_slug})/$'.format(
+ project_slug=PROJECT_SLUG_REGEX
+ )
+ ),
views.projects_remove,
- name='gold_projects_remove'),
+ name='gold_projects_remove',
+ ),
]
diff --git a/readthedocs/gold/views.py b/readthedocs/gold/views.py
index 2867d49dbf9..d5f0d6121c5 100644
--- a/readthedocs/gold/views.py
+++ b/readthedocs/gold/views.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Gold subscription views."""
from django.conf import settings
@@ -19,8 +20,11 @@
from .models import GoldUser
-class GoldSubscriptionMixin(SuccessMessageMixin, StripeMixin,
- LoginRequiredMixin):
+class GoldSubscriptionMixin(
+ SuccessMessageMixin,
+ StripeMixin,
+ LoginRequiredMixin,
+):
"""Gold subscription mixin for view classes."""
@@ -101,7 +105,11 @@ def projects(request):
if request.method == 'POST':
form = GoldProjectForm(
- active_user=request.user, data=request.POST, user=gold_user, projects=gold_projects)
+ active_user=request.user,
+ data=request.POST,
+ user=gold_user,
+ projects=gold_projects,
+ )
if form.is_valid():
to_add = Project.objects.get(slug=form.cleaned_data['project'])
gold_user.projects.add(to_add)
@@ -114,13 +122,16 @@ def projects(request):
form = GoldProjectForm(active_user=request.user)
return render(
- request, 'gold/projects.html', {
+ request,
+ 'gold/projects.html',
+ {
'form': form,
'gold_user': gold_user,
'publishable': settings.STRIPE_PUBLISHABLE,
'user': request.user,
'projects': gold_projects,
- })
+ },
+ )
@login_required
diff --git a/readthedocs/integrations/admin.py b/readthedocs/integrations/admin.py
index 24f0ff95192..06001c024f4 100644
--- a/readthedocs/integrations/admin.py
+++ b/readthedocs/integrations/admin.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Integration admin models."""
from django import urls
@@ -17,11 +18,13 @@ def inner(_, obj):
if include_styles:
formatter = HtmlFormatter(style='colorful')
styles = ''
- return mark_safe('{}
{}'.format(
- 'float: left;',
- obj.formatted_json(field),
- styles,
- ))
+ return mark_safe(
+ '{}
{}'.format(
+ 'float: left;',
+ obj.formatted_json(field),
+ styles,
+ )
+ )
inner.short_description = description
return inner
@@ -95,16 +98,20 @@ def exchanges(self, obj):
JSONField doesn't do well with fieldsets for whatever reason. This is
just to link to the exchanges.
"""
- url = urls.reverse('admin:{}_{}_changelist'.format(
- HttpExchange._meta.app_label, # pylint: disable=protected-access
- HttpExchange._meta.model_name, # pylint: disable=protected-access
- ))
- return mark_safe('{} HTTP transactions'.format(
- url,
- 'integrations',
- obj.pk,
- obj.exchanges.count(),
- ))
+ url = urls.reverse(
+ 'admin:{}_{}_changelist'.format(
+ HttpExchange._meta.app_label, # pylint: disable=protected-access
+ HttpExchange._meta.model_name, # pylint: disable=protected-access
+ )
+ )
+ return mark_safe(
+ '{} HTTP transactions'.format(
+ url,
+ 'integrations',
+ obj.pk,
+ obj.exchanges.count(),
+ )
+ )
exchanges.short_description = 'HTTP exchanges'
diff --git a/readthedocs/integrations/models.py b/readthedocs/integrations/models.py
index 8dbbbf80e93..c562c372983 100644
--- a/readthedocs/integrations/models.py
+++ b/readthedocs/integrations/models.py
@@ -269,7 +269,8 @@ class Integration(models.Model):
def __str__(self):
return (
_('{0} for {1}')
- .format(self.get_integration_type_display(), self.project.name))
+ .format(self.get_integration_type_display(), self.project.name)
+ )
class GitHubWebhook(Integration):
diff --git a/readthedocs/integrations/utils.py b/readthedocs/integrations/utils.py
index 978da9c8504..e4c78bf4750 100644
--- a/readthedocs/integrations/utils.py
+++ b/readthedocs/integrations/utils.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Integration utility functions."""
diff --git a/readthedocs/notifications/__init__.py b/readthedocs/notifications/__init__.py
index c1860cbc8d1..74496a1c90b 100644
--- a/readthedocs/notifications/__init__.py
+++ b/readthedocs/notifications/__init__.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
Extensions to Django messages to support notifications to users.
@@ -18,8 +19,7 @@
__all__ = (
'Notification',
'SiteNotification',
- 'send_notification'
+ 'send_notification',
)
-
default_app_config = 'readthedocs.notifications.apps.NotificationsAppConfig'
diff --git a/readthedocs/notifications/apps.py b/readthedocs/notifications/apps.py
index 3be3e557bf9..9876fdb521e 100644
--- a/readthedocs/notifications/apps.py
+++ b/readthedocs/notifications/apps.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Django app configuration for the notifications app."""
from django.apps import AppConfig
diff --git a/readthedocs/notifications/backends.py b/readthedocs/notifications/backends.py
index e2a3ed8dfbf..78b22cb76a3 100644
--- a/readthedocs/notifications/backends.py
+++ b/readthedocs/notifications/backends.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""
Pluggable backends for the delivery of notifications.
diff --git a/readthedocs/notifications/constants.py b/readthedocs/notifications/constants.py
index 4ee285d17a4..2716e6dfbea 100644
--- a/readthedocs/notifications/constants.py
+++ b/readthedocs/notifications/constants.py
@@ -1,4 +1,5 @@
-"""Notification constants"""
+# -*- coding: utf-8 -*-
+"""Notification constants."""
from messages_extends import constants as message_constants
@@ -18,7 +19,6 @@
ERROR: message_constants.ERROR_PERSISTENT,
}
-
# Message levels to save the message into the database and mark as read
# immediately after retrieved (one-time shown message)
DEBUG_NON_PERSISTENT = 100
diff --git a/readthedocs/notifications/forms.py b/readthedocs/notifications/forms.py
index f0026d5b378..4a6d480398d 100644
--- a/readthedocs/notifications/forms.py
+++ b/readthedocs/notifications/forms.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""HTML forms for sending notifications."""
from django import forms
from django.utils.translation import ugettext_lazy as _
@@ -29,8 +30,8 @@ class SendNotificationForm(forms.Form):
def __init__(self, *args, **kwargs):
self.notification_classes = kwargs.pop('notification_classes', [])
super().__init__(*args, **kwargs)
- self.fields['source'].choices = [(cls.name, cls.name) for cls
- in self.notification_classes]
+ self.fields['source'].choices = [(cls.name, cls.name)
+ for cls in self.notification_classes]
def clean_source(self):
"""Get the source class from the class name."""
diff --git a/readthedocs/notifications/notification.py b/readthedocs/notifications/notification.py
index e363bc16a79..18fa576bbed 100644
--- a/readthedocs/notifications/notification.py
+++ b/readthedocs/notifications/notification.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Support for templating of notifications."""
import logging
@@ -51,7 +52,8 @@ def get_context_data(self):
self.context_object_name: self.object,
'request': self.request,
'production_uri': '{scheme}://{host}'.format(
- scheme='https', host=settings.PRODUCTION_DOMAIN,
+ scheme='https',
+ host=settings.PRODUCTION_DOMAIN,
),
}
@@ -60,13 +62,13 @@ def get_template_names(self, backend_name, source_format=constants.HTML):
if self.object and isinstance(self.object, models.Model):
meta = self.object._meta # pylint: disable=protected-access
names.append(
- '{app}/notifications/{name}_{backend}.{source_format}'
- .format(
+ '{app}/notifications/{name}_{backend}.{source_format}'.format(
app=meta.app_label,
name=self.name or meta.model_name,
backend=backend_name,
source_format=source_format,
- ))
+ ),
+ )
return names
raise AttributeError()
@@ -120,8 +122,14 @@ class SiteNotification(Notification):
failure_level = constants.ERROR_NON_PERSISTENT
def __init__(
- self, user, success, reason=None, context_object=None,
- request=None, extra_context=None):
+ self,
+ user,
+ success,
+ reason=None,
+ context_object=None,
+ request=None,
+ extra_context=None,
+ ):
self.object = context_object
self.user = user or request.user
diff --git a/readthedocs/notifications/storages.py b/readthedocs/notifications/storages.py
index ccd9f8e9a13..31ee884d640 100644
--- a/readthedocs/notifications/storages.py
+++ b/readthedocs/notifications/storages.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Customised storage for notifications."""
from django.contrib.messages.storage.base import Message
@@ -17,7 +18,6 @@
from datetime import datetime as timezone
-
class FallbackUniqueStorage(FallbackStorage):
"""
@@ -50,8 +50,7 @@ class FallbackUniqueStorage(FallbackStorage):
def _get(self, *args, **kwargs):
# The database backend for persistent messages doesn't support setting
# messages with ``mark_safe``, therefore, we need to do it broadly here.
- messages, all_ret = (super()
- ._get(self, *args, **kwargs))
+ messages, all_ret = (super()._get(self, *args, **kwargs))
safe_messages = []
for message in messages:
@@ -60,9 +59,11 @@ def _get(self, *args, **kwargs):
# process ephemeral messages
if message.level in PERSISTENT_MESSAGE_LEVELS + NON_PERSISTENT_MESSAGE_LEVELS:
message_pk = message.pk
- message = Message(message.level,
- mark_safe(message.message),
- message.extra_tags)
+ message = Message(
+ message.level,
+ mark_safe(message.message),
+ message.extra_tags,
+ )
message.pk = message_pk
safe_messages.append(message)
return safe_messages, all_ret
@@ -70,14 +71,16 @@ def _get(self, *args, **kwargs):
def add(self, level, message, extra_tags='', *args, **kwargs): # noqa
user = kwargs.get('user') or self.request.user
if not user.is_anonymous:
- persist_messages = (PersistentMessage.objects
- .filter(message=message,
- user=user,
- read=False))
+ persist_messages = (
+ PersistentMessage.objects.filter(
+ message=message,
+ user=user,
+ read=False,
+ )
+ )
if persist_messages.exists():
return
- super().add(level, message, extra_tags,
- *args, **kwargs)
+ super().add(level, message, extra_tags, *args, **kwargs)
class NonPersistentStorage(PersistentStorage):
@@ -134,7 +137,7 @@ def process_message(self, message, *args, **kwargs):
if message.level not in NON_PERSISTENT_MESSAGE_LEVELS:
return message
- user = kwargs.get("user") or self.get_user()
+ user = kwargs.get('user') or self.get_user()
try:
anonymous = user.is_anonymous
@@ -142,14 +145,15 @@ def process_message(self, message, *args, **kwargs):
anonymous = user.is_anonymous
if anonymous:
raise NotImplementedError(
- 'Persistent message levels cannot be used for anonymous users.')
+ 'Persistent message levels cannot be used for anonymous users.',
+ )
message_persistent = PersistentMessage()
message_persistent.level = message.level
message_persistent.message = message.message
message_persistent.extra_tags = message.extra_tags
message_persistent.user = user
- if "expires" in kwargs:
- message_persistent.expires = kwargs["expires"]
+ if 'expires' in kwargs:
+ message_persistent.expires = kwargs['expires']
message_persistent.save()
return None
diff --git a/readthedocs/notifications/urls.py b/readthedocs/notifications/urls.py
index d44522bc9f1..1b03e5b3843 100644
--- a/readthedocs/notifications/urls.py
+++ b/readthedocs/notifications/urls.py
@@ -1,12 +1,19 @@
-"""Renames for messages_extends URLs"""
+# -*- coding: utf-8 -*-
+"""Renames for messages_extends URLs."""
from django.conf.urls import url
from messages_extends.views import message_mark_all_read, message_mark_read
urlpatterns = [
- url(r'^dismiss/(?P\d+)/$', message_mark_read,
- name='message_mark_read'),
- url(r'^dismiss/all/$', message_mark_all_read,
- name='message_mark_all_read'),
+ url(
+ r'^dismiss/(?P\d+)/$',
+ message_mark_read,
+ name='message_mark_read',
+ ),
+ url(
+ r'^dismiss/all/$',
+ message_mark_all_read,
+ name='message_mark_all_read',
+ ),
]
diff --git a/readthedocs/notifications/views.py b/readthedocs/notifications/views.py
index 3853e8cdbdd..7252e8c3e55 100644
--- a/readthedocs/notifications/views.py
+++ b/readthedocs/notifications/views.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Django views for the notifications app."""
from django.contrib import admin, messages
from django.http import HttpResponseRedirect
@@ -45,7 +46,8 @@ def get_initial(self):
"""Add selected ids to initial form data."""
initial = super().get_initial()
initial['_selected_action'] = self.request.POST.getlist(
- admin.ACTION_CHECKBOX_NAME)
+ admin.ACTION_CHECKBOX_NAME,
+ )
return initial
def form_valid(self, form):
@@ -54,15 +56,17 @@ def form_valid(self, form):
notification_cls = form.cleaned_data['source']
for obj in self.get_queryset().all():
for recipient in self.get_object_recipients(obj):
- notification = notification_cls(context_object=obj,
- request=self.request,
- user=recipient)
+ notification = notification_cls(
+ context_object=obj,
+ request=self.request,
+ user=recipient,
+ )
notification.send()
count += 1
if count == 0:
- self.message_user("No recipients to send to", level=messages.ERROR)
+ self.message_user('No recipients to send to', level=messages.ERROR)
else:
- self.message_user("Queued {} messages".format(count))
+ self.message_user('Queued {} messages'.format(count))
return HttpResponseRedirect(self.request.get_full_path())
def get_object_recipients(self, obj):
@@ -96,14 +100,25 @@ def get_context_data(self, **kwargs):
context['action_name'] = self.action_name
return context
- def message_user(self, message, level=messages.INFO, extra_tags='',
- fail_silently=False):
+ 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
"""
# TODO generalize this or check if implementation in ModelAdmin is
# usable here
- messages.add_message(self.request, level, message, extra_tags=extra_tags,
- fail_silently=fail_silently)
+ messages.add_message(
+ self.request,
+ level,
+ message,
+ extra_tags=extra_tags,
+ fail_silently=fail_silently,
+ )
diff --git a/readthedocs/oauth/__init__.py b/readthedocs/oauth/__init__.py
index 510c93a3526..32bb8a9a4a5 100644
--- a/readthedocs/oauth/__init__.py
+++ b/readthedocs/oauth/__init__.py
@@ -1 +1,2 @@
+# -*- coding: utf-8 -*-
default_app_config = 'readthedocs.oauth.apps.OAuthConfig'
diff --git a/readthedocs/oauth/admin.py b/readthedocs/oauth/admin.py
index 777e48272bc..dd221e9b2da 100644
--- a/readthedocs/oauth/admin.py
+++ b/readthedocs/oauth/admin.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Admin configuration for the OAuth app."""
from django.contrib import admin
diff --git a/readthedocs/oauth/apps.py b/readthedocs/oauth/apps.py
index b8998b8f458..c064f8fc8ee 100644
--- a/readthedocs/oauth/apps.py
+++ b/readthedocs/oauth/apps.py
@@ -1,4 +1,5 @@
-"""OAuth app config"""
+# -*- coding: utf-8 -*-
+"""OAuth app config."""
from django.apps import AppConfig
diff --git a/readthedocs/oauth/models.py b/readthedocs/oauth/models.py
index 7d6aeec15de..40d224df6b3 100644
--- a/readthedocs/oauth/models.py
+++ b/readthedocs/oauth/models.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
+
"""OAuth service models."""
import json
from allauth.socialaccount.models import SocialAccount
-from django.conf import settings
from django.contrib.auth.models import User
from django.core.validators import URLValidator
from django.db import models
@@ -33,10 +33,17 @@ class RemoteOrganization(models.Model):
modified_date = models.DateTimeField(_('Modified date'), auto_now=True)
users = models.ManyToManyField(
- User, verbose_name=_('Users'), related_name='oauth_organizations')
+ User,
+ verbose_name=_('Users'),
+ related_name='oauth_organizations',
+ )
account = models.ForeignKey(
- SocialAccount, verbose_name=_('Connected account'),
- related_name='remote_organizations', null=True, blank=True)
+ SocialAccount,
+ verbose_name=_('Connected account'),
+ related_name='remote_organizations',
+ null=True,
+ blank=True,
+ )
active = models.BooleanField(_('Active'), default=False)
slug = models.CharField(_('Slug'), max_length=255)
@@ -44,7 +51,11 @@ class RemoteOrganization(models.Model):
email = models.EmailField(_('Email'), max_length=255, null=True, blank=True)
avatar_url = models.URLField(_('Avatar image URL'), null=True, blank=True)
url = models.URLField(
- _('URL to organization page'), max_length=200, null=True, blank=True)
+ _('URL to organization page'),
+ max_length=200,
+ null=True,
+ blank=True,
+ )
json = models.TextField(_('Serialized API response'))
@@ -78,13 +89,24 @@ class RemoteRepository(models.Model):
# This should now be a OneToOne
users = models.ManyToManyField(
- User, verbose_name=_('Users'), related_name='oauth_repositories')
+ User,
+ verbose_name=_('Users'),
+ related_name='oauth_repositories',
+ )
account = models.ForeignKey(
- SocialAccount, verbose_name=_('Connected account'),
- related_name='remote_repositories', null=True, blank=True)
+ SocialAccount,
+ verbose_name=_('Connected account'),
+ related_name='remote_repositories',
+ null=True,
+ blank=True,
+ )
organization = models.ForeignKey(
- RemoteOrganization, verbose_name=_('Organization'),
- related_name='repositories', null=True, blank=True)
+ RemoteOrganization,
+ verbose_name=_('Organization'),
+ related_name='repositories',
+ null=True,
+ blank=True,
+ )
active = models.BooleanField(_('Active'), default=False)
project = models.OneToOneField(
@@ -119,7 +141,7 @@ class RemoteRepository(models.Model):
max_length=512,
blank=True,
validators=[
- URLValidator(schemes=['http', 'https', 'ssh', 'git', 'svn'])
+ URLValidator(schemes=['http', 'https', 'ssh', 'git', 'svn']),
],
)
html_url = models.URLField(_('HTML URL'), null=True, blank=True)
@@ -156,7 +178,6 @@ def get_serialized(self, key=None, default=None):
@property
def clone_fuzzy_url(self):
"""Try to match against several permutations of project URL."""
- pass
def matches(self, user):
"""Projects that exist with repository URL already."""
diff --git a/readthedocs/oauth/notifications.py b/readthedocs/oauth/notifications.py
index 915a2003c7e..afc9e43c8c4 100644
--- a/readthedocs/oauth/notifications.py
+++ b/readthedocs/oauth/notifications.py
@@ -15,8 +15,12 @@ class AttachWebhookNotification(SiteNotification):
context_object_name = 'provider'
success_message = _('Webhook successfully added.')
failure_message = {
- NO_PERMISSIONS: _('Could not add webhook for {{ project.name }}. Make sure you have the correct {{ provider.name }} permissions.'), # noqa
- NO_ACCOUNTS: _('Could not add webhook for {{ project.name }}. Please connect your {{ provider.name }} account.'), # noqa
+ NO_PERMISSIONS: _(
+ 'Could not add webhook for {{ project.name }}. Make sure you have the correct {{ provider.name }} permissions.'
+ ), # noqa
+ NO_ACCOUNTS: _(
+ 'Could not add webhook for {{ project.name }}. Please connect your {{ provider.name }} account.'
+ ), # noqa
}
def get_context_data(self):
@@ -39,7 +43,8 @@ class InvalidProjectWebhookNotification(SiteNotification):
failure_message = _(
"The project {{ project.name }} doesn't have a valid webhook set up, "
"commits won't trigger new builds for this project. "
- "See the project integrations for more information.") # noqa
+ "See the project integrations for more information.",
+ ) # noqa
def get_context_data(self):
context = super().get_context_data()
diff --git a/readthedocs/oauth/querysets.py b/readthedocs/oauth/querysets.py
index c23f6cbeea1..f410e96663a 100644
--- a/readthedocs/oauth/querysets.py
+++ b/readthedocs/oauth/querysets.py
@@ -1,4 +1,5 @@
-"""Managers for OAuth models"""
+# -*- coding: utf-8 -*-
+"""Managers for OAuth models."""
from django.db import models
@@ -10,7 +11,7 @@ class RelatedUserQuerySetBase(models.QuerySet):
"""For models with relations through :py:class:`User`"""
def api(self, user=None):
- """Return objects for user"""
+ """Return objects for user."""
if not user.is_authenticated:
return self.none()
return self.filter(users=user)
diff --git a/readthedocs/oauth/services/__init__.py b/readthedocs/oauth/services/__init__.py
index b1b5003b08a..a249e15d934 100644
--- a/readthedocs/oauth/services/__init__.py
+++ b/readthedocs/oauth/services/__init__.py
@@ -1,8 +1,13 @@
# -*- coding: utf-8 -*-
+
"""Conditional classes for OAuth services."""
from __future__ import (
- absolute_import, division, print_function, unicode_literals)
+ absolute_import,
+ division,
+ print_function,
+ unicode_literals,
+)
from readthedocs.core.utils.extend import SettingsOverrideObject
from readthedocs.oauth.services import bitbucket, github, gitlab
diff --git a/readthedocs/oauth/services/base.py b/readthedocs/oauth/services/base.py
index 77f2a7bf1a6..b1f0e7a12c5 100644
--- a/readthedocs/oauth/services/base.py
+++ b/readthedocs/oauth/services/base.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""OAuth utility functions."""
import logging
@@ -115,10 +116,11 @@ def token_updater(self, token):
u'expires_at': 1449218652.558185
}
"""
+
def _updater(data):
token.token = data['access_token']
token.expires_at = timezone.make_aware(
- datetime.fromtimestamp(data['expires_at'])
+ datetime.fromtimestamp(data['expires_at']),
)
token.save()
log.info('Updated token %s:', token)
diff --git a/readthedocs/oauth/services/bitbucket.py b/readthedocs/oauth/services/bitbucket.py
index 70fb90b0b8a..b980df55d4b 100644
--- a/readthedocs/oauth/services/bitbucket.py
+++ b/readthedocs/oauth/services/bitbucket.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""OAuth utility functions."""
import json
@@ -40,25 +41,30 @@ def sync_repositories(self):
# Get user repos
try:
repos = self.paginate(
- 'https://bitbucket.org/api/2.0/repositories/?role=member')
+ 'https://bitbucket.org/api/2.0/repositories/?role=member',
+ )
for repo in repos:
self.create_repository(repo)
- except (TypeError, ValueError) as e:
+ except (TypeError, ValueError):
log.exception('Error syncing Bitbucket repositories')
- raise Exception('Could not sync your Bitbucket repositories, '
- 'try reconnecting your account')
+ raise Exception(
+ 'Could not sync your Bitbucket repositories, '
+ 'try reconnecting your account',
+ )
# Because privileges aren't returned with repository data, run query
# again for repositories that user has admin role for, and update
# existing repositories.
try:
resp = self.paginate(
- 'https://bitbucket.org/api/2.0/repositories/?role=admin')
+ 'https://bitbucket.org/api/2.0/repositories/?role=admin',
+ )
repos = (
- RemoteRepository.objects
- .filter(users=self.user,
- full_name__in=[r['full_name'] for r in resp],
- account=self.account)
+ RemoteRepository.objects.filter(
+ users=self.user,
+ full_name__in=[r['full_name'] for r in resp],
+ account=self.account,
+ )
)
for repo in repos:
repo.admin = True
@@ -70,17 +76,19 @@ def sync_teams(self):
"""Sync Bitbucket teams and team repositories."""
try:
teams = self.paginate(
- 'https://api.bitbucket.org/2.0/teams/?role=member'
+ 'https://api.bitbucket.org/2.0/teams/?role=member',
)
for team in teams:
org = self.create_organization(team)
repos = self.paginate(team['links']['repositories']['href'])
for repo in repos:
self.create_repository(repo, organization=org)
- except ValueError as e:
+ except ValueError:
log.exception('Error syncing Bitbucket organizations')
- raise Exception('Could not sync your Bitbucket team repositories, '
- 'try reconnecting your account')
+ raise Exception(
+ 'Could not sync your Bitbucket team repositories, '
+ 'try reconnecting your account',
+ )
def create_repository(self, fields, privacy=None, organization=None):
"""
@@ -98,17 +106,17 @@ def create_repository(self, fields, privacy=None, organization=None):
:rtype: RemoteRepository
"""
privacy = privacy or settings.DEFAULT_PRIVACY_LEVEL
- if (
- (privacy == 'private') or
- (fields['is_private'] is False and privacy == 'public')
- ):
+ if ((privacy == 'private') or
+ (fields['is_private'] is False and privacy == 'public')):
repo, _ = RemoteRepository.objects.get_or_create(
full_name=fields['full_name'],
account=self.account,
)
if repo.organization and repo.organization != organization:
- log.debug('Not importing %s because mismatched orgs',
- fields['name'])
+ log.debug(
+ 'Not importing %s because mismatched orgs',
+ fields['name'],
+ )
return None
repo.organization = organization
@@ -118,11 +126,13 @@ def create_repository(self, fields, privacy=None, organization=None):
repo.private = fields['is_private']
# Default to HTTPS, use SSH for private repositories
- clone_urls = {u['name']: u['href']
- for u in fields['links']['clone']}
+ clone_urls = {
+ u['name']: u['href']
+ for u in fields['links']['clone']
+ }
repo.clone_url = self.https_url_pattern.sub(
'https://bitbucket.org/',
- clone_urls.get('https')
+ clone_urls.get('https'),
)
repo.ssh_url = clone_urls.get('ssh')
if repo.private:
@@ -178,14 +188,18 @@ def get_paginated_results(self, response):
def get_webhook_data(self, project, integration):
"""Get webhook JSON data to post to the API."""
return json.dumps({
- 'description': 'Read the Docs ({domain})'.format(domain=settings.PRODUCTION_DOMAIN),
+ 'description': 'Read the Docs ({domain})'.format(
+ domain=settings.PRODUCTION_DOMAIN
+ ),
'url': 'https://{domain}{path}'.format(
domain=settings.PRODUCTION_DOMAIN,
path=reverse(
'api_webhook',
- kwargs={'project_slug': project.slug,
- 'integration_pk': integration.pk}
- )
+ kwargs={
+ 'project_slug': project.slug,
+ 'integration_pk': integration.pk,
+ },
+ ),
),
'active': True,
'events': ['repo:push'],
@@ -210,10 +224,12 @@ def setup_webhook(self, project):
resp = None
try:
resp = session.post(
- ('https://api.bitbucket.org/2.0/repositories/{owner}/{repo}/hooks'
- .format(owner=owner, repo=repo)),
+ (
+ 'https://api.bitbucket.org/2.0/repositories/{owner}/{repo}/hooks'
+ .format(owner=owner, repo=repo)
+ ),
data=data,
- headers={'content-type': 'application/json'}
+ headers={'content-type': 'application/json'},
)
if resp.status_code == 201:
recv_data = resp.json()
@@ -264,7 +280,7 @@ def update_webhook(self, project, integration):
resp = session.put(
url,
data=data,
- headers={'content-type': 'application/json'}
+ headers={'content-type': 'application/json'},
)
if resp.status_code == 200:
recv_data = resp.json()
diff --git a/readthedocs/oauth/services/github.py b/readthedocs/oauth/services/github.py
index 2e844aaaf31..19bd3c3768c 100644
--- a/readthedocs/oauth/services/github.py
+++ b/readthedocs/oauth/services/github.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""OAuth utility functions."""
import json
@@ -40,10 +41,12 @@ def sync_repositories(self):
try:
for repo in repos:
self.create_repository(repo)
- except (TypeError, ValueError) as e:
+ except (TypeError, ValueError):
log.exception('Error syncing GitHub repositories')
- raise Exception('Could not sync your GitHub repositories, '
- 'try reconnecting your account')
+ raise Exception(
+ 'Could not sync your GitHub repositories, '
+ 'try reconnecting your account',
+ )
def sync_organizations(self):
"""Sync organizations from GitHub API."""
@@ -55,14 +58,16 @@ def sync_organizations(self):
# Add repos
# TODO ?per_page=100
org_repos = self.paginate(
- '{org_url}/repos'.format(org_url=org['url'])
+ '{org_url}/repos'.format(org_url=org['url']),
)
for repo in org_repos:
self.create_repository(repo, organization=org_obj)
- except (TypeError, ValueError) as e:
+ except (TypeError, ValueError):
log.exception('Error syncing GitHub organizations')
- raise Exception('Could not sync your GitHub organizations, '
- 'try reconnecting your account')
+ raise Exception(
+ 'Could not sync your GitHub organizations, '
+ 'try reconnecting your account',
+ )
def create_repository(self, fields, privacy=None, organization=None):
"""
@@ -75,10 +80,8 @@ def create_repository(self, fields, privacy=None, organization=None):
:rtype: RemoteRepository
"""
privacy = privacy or settings.DEFAULT_PRIVACY_LEVEL
- if (
- (privacy == 'private') or
- (fields['private'] is False and privacy == 'public')
- ):
+ if ((privacy == 'private') or
+ (fields['private'] is False and privacy == 'public')):
try:
repo = RemoteRepository.objects.get(
full_name=fields['full_name'],
@@ -92,8 +95,10 @@ def create_repository(self, fields, privacy=None, organization=None):
)
repo.users.add(self.user)
if repo.organization and repo.organization != organization:
- log.debug('Not importing %s because mismatched orgs',
- fields['name'])
+ log.debug(
+ 'Not importing %s because mismatched orgs',
+ fields['name'],
+ )
return None
repo.organization = organization
@@ -116,8 +121,10 @@ def create_repository(self, fields, privacy=None, organization=None):
repo.save()
return repo
else:
- log.debug('Not importing %s because mismatched type',
- fields['name'])
+ log.debug(
+ 'Not importing %s because mismatched type',
+ fields['name'],
+ )
def create_organization(self, fields):
"""
@@ -165,9 +172,11 @@ def get_webhook_data(self, project, integration):
domain=settings.PRODUCTION_DOMAIN,
path=reverse(
'api_webhook',
- kwargs={'project_slug': project.slug,
- 'integration_pk': integration.pk}
- )
+ kwargs={
+ 'project_slug': project.slug,
+ 'integration_pk': integration.pk,
+ },
+ ),
),
'content_type': 'json',
},
@@ -193,18 +202,22 @@ def setup_webhook(self, project):
resp = None
try:
resp = session.post(
- ('https://api.github.com/repos/{owner}/{repo}/hooks'
- .format(owner=owner, repo=repo)),
+ (
+ 'https://api.github.com/repos/{owner}/{repo}/hooks'
+ .format(owner=owner, repo=repo)
+ ),
data=data,
- headers={'content-type': 'application/json'}
+ headers={'content-type': 'application/json'},
)
# GitHub will return 200 if already synced
if resp.status_code in [200, 201]:
recv_data = resp.json()
integration.provider_data = recv_data
integration.save()
- log.info('GitHub webhook creation successful for project: %s',
- project)
+ log.info(
+ 'GitHub webhook creation successful for project: %s',
+ project,
+ )
return (True, resp)
# Catch exceptions with request or deserializing JSON
except (RequestException, ValueError):
@@ -248,7 +261,7 @@ def update_webhook(self, project, integration):
resp = session.patch(
url,
data=data,
- headers={'content-type': 'application/json'}
+ headers={'content-type': 'application/json'},
)
# GitHub will return 200 if already synced
if resp.status_code in [200, 201]:
@@ -301,7 +314,8 @@ def get_token_for_project(cls, project, force_local=False):
for user in project.users.all():
tokens = SocialToken.objects.filter(
account__user=user,
- app__provider=cls.adapter.provider_id)
+ app__provider=cls.adapter.provider_id,
+ )
if tokens.exists():
token = tokens[0].token
except Exception:
diff --git a/readthedocs/oauth/services/gitlab.py b/readthedocs/oauth/services/gitlab.py
index ada8a6f1d5f..5f94bd51690 100644
--- a/readthedocs/oauth/services/gitlab.py
+++ b/readthedocs/oauth/services/gitlab.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""OAuth utility functions."""
import json
@@ -40,7 +41,8 @@ class GitLabService(Service):
# Just use the network location to determine if it's a GitLab project
# because private repos have another base url, eg. git@gitlab.example.com
url_pattern = re.compile(
- re.escape(urlparse(adapter.provider_base_url).netloc))
+ re.escape(urlparse(adapter.provider_base_url).netloc),
+ )
def _get_repo_id(self, project):
# The ID or URL-encoded path of the project
@@ -93,7 +95,8 @@ def sync_repositories(self):
log.exception('Error syncing GitLab repositories')
raise Exception(
'Could not sync your GitLab repositories, try reconnecting '
- 'your account')
+ 'your account',
+ )
def sync_organizations(self):
orgs = self.paginate(
@@ -123,7 +126,8 @@ def sync_organizations(self):
log.exception('Error syncing GitLab organizations')
raise Exception(
'Could not sync your GitLab organization, try reconnecting '
- 'your account')
+ 'your account',
+ )
def is_owned_by(self, owner_id):
return self.account.extra_data['id'] == owner_id
@@ -348,7 +352,9 @@ def update_webhook(self, project, integration):
integration.provider_data = recv_data
integration.save()
log.info(
- 'GitLab webhook update successful for project: %s', project)
+ 'GitLab webhook update successful for project: %s',
+ project,
+ )
return (True, resp)
# GitLab returns 404 when the webhook doesn't exist. In this case,
@@ -359,7 +365,9 @@ def update_webhook(self, project, integration):
# Catch exceptions with request or deserializing JSON
except (RequestException, ValueError):
log.exception(
- 'GitLab webhook update failed for project: %s', project)
+ 'GitLab webhook update failed for project: %s',
+ project,
+ )
else:
log.error(
'GitLab webhook update failed for project: %s',
diff --git a/readthedocs/oauth/tasks.py b/readthedocs/oauth/tasks.py
index eee0efe4845..18c0ccb2eca 100644
--- a/readthedocs/oauth/tasks.py
+++ b/readthedocs/oauth/tasks.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Tasks for OAuth services."""
import logging
diff --git a/readthedocs/oauth/utils.py b/readthedocs/oauth/utils.py
index d797baa057f..b33fc9a6e65 100644
--- a/readthedocs/oauth/utils.py
+++ b/readthedocs/oauth/utils.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Support code for OAuth, including webhook support."""
import logging
@@ -54,7 +55,9 @@ def update_webhook(project, integration, request=None):
request,
_(
'Webhook activation failed. '
- 'Make sure you have the necessary permissions.'))
+ 'Make sure you have the necessary permissions.',
+ ),
+ )
project.has_valid_webhook = False
project.save()
return False
diff --git a/readthedocs/payments/forms.py b/readthedocs/payments/forms.py
index c4501d68224..e64813e6a40 100644
--- a/readthedocs/payments/forms.py
+++ b/readthedocs/payments/forms.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Payment forms."""
import logging
@@ -36,23 +37,29 @@ def get_customer_kwargs(self):
raise NotImplementedError
def get_customer(self):
- return self.ensure_stripe_resource(resource=Customer,
- attrs=self.get_customer_kwargs())
+ return self.ensure_stripe_resource(
+ resource=Customer,
+ attrs=self.get_customer_kwargs(),
+ )
def get_subscription_kwargs(self):
raise NotImplementedError
def get_subscription(self):
customer = self.get_customer()
- return self.ensure_stripe_resource(resource=customer.subscriptions,
- attrs=self.get_subscription_kwargs())
+ return self.ensure_stripe_resource(
+ resource=customer.subscriptions,
+ attrs=self.get_subscription_kwargs(),
+ )
def get_charge_kwargs(self):
raise NotImplementedError
def get_charge(self):
- return self.ensure_stripe_resource(resource=Charge,
- attrs=self.get_charge_kwargs())
+ return self.ensure_stripe_resource(
+ resource=Charge,
+ attrs=self.get_charge_kwargs(),
+ )
class StripeModelForm(forms.ModelForm):
@@ -77,41 +84,58 @@ class StripeModelForm(forms.ModelForm):
# Stripe token input from Stripe.js
stripe_token = forms.CharField(
required=False,
- widget=forms.HiddenInput(attrs={
- 'data-bind': 'valueInit: stripe_token',
- })
+ widget=forms.HiddenInput(
+ attrs={
+ 'data-bind': 'valueInit: stripe_token',
+ }
+ ),
)
# Fields used for fetching token with javascript, listed as form fields so
# that data can survive validation errors
cc_number = forms.CharField(
label=_('Card number'),
- widget=forms.TextInput(attrs={
- 'data-bind': ('valueInit: cc_number, '
- 'textInput: cc_number, '
- '''css: {'field-error': error_cc_number() != null}''')
- }),
+ widget=forms.TextInput(
+ attrs={
+ 'data-bind': (
+ 'valueInit: cc_number, '
+ 'textInput: cc_number, '
+ '''css: {'field-error': error_cc_number() != null}'''
+ ),
+ }
+ ),
max_length=25,
- required=False)
+ required=False,
+ )
cc_expiry = forms.CharField(
label=_('Card expiration'),
- widget=forms.TextInput(attrs={
- 'data-bind': ('valueInit: cc_expiry, '
- 'textInput: cc_expiry, '
- '''css: {'field-error': error_cc_expiry() != null}''')
- }),
+ widget=forms.TextInput(
+ attrs={
+ 'data-bind': (
+ 'valueInit: cc_expiry, '
+ 'textInput: cc_expiry, '
+ '''css: {'field-error': error_cc_expiry() != null}'''
+ ),
+ }
+ ),
max_length=10,
- required=False)
+ required=False,
+ )
cc_cvv = forms.CharField(
label=_('Card CVV'),
- widget=forms.TextInput(attrs={
- 'data-bind': ('valueInit: cc_cvv, '
- 'textInput: cc_cvv, '
- '''css: {'field-error': error_cc_cvv() != null}'''),
- 'autocomplete': 'off',
- }),
+ widget=forms.TextInput(
+ attrs={
+ 'data-bind': (
+ 'valueInit: cc_cvv, '
+ 'textInput: cc_cvv, '
+ '''css: {'field-error': error_cc_cvv() != null}'''
+ ),
+ 'autocomplete': 'off',
+ }
+ ),
max_length=8,
- required=False)
+ required=False,
+ )
def __init__(self, *args, **kwargs):
self.customer = kwargs.pop('customer', None)
@@ -171,7 +195,8 @@ def clean(self):
except stripe.error.StripeError as e:
log.exception('There was a problem communicating with Stripe')
raise forms.ValidationError(
- _('There was a problem communicating with Stripe'))
+ _('There was a problem communicating with Stripe'),
+ )
return cleaned_data
def clear_card_data(self):
@@ -184,12 +209,14 @@ def clear_card_data(self):
try:
self.data['stripe_token'] = None
except AttributeError:
- raise AttributeError('Form was passed immutable QueryDict POST data')
+ raise AttributeError(
+ 'Form was passed immutable QueryDict POST data'
+ )
def fields_with_cc_group(self):
group = {
'is_cc_group': True,
- 'fields': []
+ 'fields': [],
}
for field in self:
if field.name in ['cc_number', 'cc_expiry', 'cc_cvv']:
diff --git a/readthedocs/payments/mixins.py b/readthedocs/payments/mixins.py
index 3741147866c..2ba34ec47e5 100644
--- a/readthedocs/payments/mixins.py
+++ b/readthedocs/payments/mixins.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Payment view mixin classes."""
from django.conf import settings
diff --git a/readthedocs/payments/utils.py b/readthedocs/payments/utils.py
index 36dc1f76941..e85159ae944 100644
--- a/readthedocs/payments/utils.py
+++ b/readthedocs/payments/utils.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
Payment utility functions.
diff --git a/readthedocs/profiles/urls/private.py b/readthedocs/profiles/urls/private.py
index 21a6e604fc3..1fe6d288643 100644
--- a/readthedocs/profiles/urls/private.py
+++ b/readthedocs/profiles/urls/private.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""URL patterns for views to modify user profiles."""
from django.conf.urls import url
@@ -8,12 +9,13 @@
urlpatterns = [
url(
- r'^edit/', views.edit_profile,
+ r'^edit/',
+ views.edit_profile,
{
'form_class': UserProfileForm,
'template_name': 'profiles/private/edit_profile.html',
},
- name='profiles_profile_edit'
+ name='profiles_profile_edit',
),
url(r'^delete/', views.delete_account, name='delete_account'),
url(
diff --git a/readthedocs/profiles/urls/public.py b/readthedocs/profiles/urls/public.py
index 0cade801350..7b028431265 100644
--- a/readthedocs/profiles/urls/public.py
+++ b/readthedocs/profiles/urls/public.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""URL patterns to view user profiles."""
from django.conf.urls import url
@@ -6,8 +7,10 @@
urlpatterns = [
- url(r'^(?P[+\w@.-]+)/$',
+ url(
+ r'^(?P[+\w@.-]+)/$',
views.profile_detail,
{'template_name': 'profiles/public/profile_detail.html'},
- name='profiles_profile_detail'),
+ name='profiles_profile_detail',
+ ),
]
diff --git a/readthedocs/profiles/views.py b/readthedocs/profiles/views.py
index 0396795e9ca..e2e85b22de8 100644
--- a/readthedocs/profiles/views.py
+++ b/readthedocs/profiles/views.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Views for creating, editing and viewing site-specific user profiles."""
from django.contrib import messages
@@ -15,8 +16,12 @@
@login_required
def edit_profile(
- request, form_class, success_url=None,
- template_name='profiles/private/edit_profile.html', extra_context=None):
+ request,
+ form_class,
+ success_url=None,
+ template_name='profiles/private/edit_profile.html',
+ extra_context=None,
+):
"""
Edit the current user's profile.
@@ -63,10 +68,14 @@ def edit_profile(
if success_url is None:
success_url = reverse(
'profiles_profile_detail',
- kwargs={'username': request.user.username})
+ kwargs={'username': request.user.username},
+ )
if request.method == 'POST':
form = form_class(
- data=request.POST, files=request.FILES, instance=profile_obj)
+ data=request.POST,
+ files=request.FILES,
+ instance=profile_obj,
+ )
if form.is_valid():
form.save()
return HttpResponseRedirect(success_url)
@@ -107,9 +116,12 @@ def delete_account(request):
def profile_detail(
- request, username, public_profile_field=None,
+ request,
+ username,
+ public_profile_field=None,
template_name='profiles/public/profile_detail.html',
- extra_context=None):
+ extra_context=None,
+):
"""
Detail view of a user's profile.
diff --git a/readthedocs/projects/__init__.py b/readthedocs/projects/__init__.py
index ff5ded49b17..186dc5eb841 100644
--- a/readthedocs/projects/__init__.py
+++ b/readthedocs/projects/__init__.py
@@ -1 +1,2 @@
+# -*- coding: utf-8 -*-
default_app_config = 'readthedocs.projects.apps.ProjectsConfig'
diff --git a/readthedocs/projects/admin.py b/readthedocs/projects/admin.py
index 5beebc58d50..acbcef5317f 100644
--- a/readthedocs/projects/admin.py
+++ b/readthedocs/projects/admin.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Django administration interface for `projects.models`"""
from django.contrib import admin, messages
@@ -94,9 +95,7 @@ class ProjectOwnerBannedFilter(admin.SimpleListFilter):
OWNER_BANNED = 'true'
def lookups(self, request, model_admin):
- return (
- (self.OWNER_BANNED, _('Yes')),
- )
+ return ((self.OWNER_BANNED, _('Yes')),)
def queryset(self, request, queryset):
if self.value() == self.OWNER_BANNED:
@@ -110,13 +109,22 @@ class ProjectAdmin(GuardedModelAdmin):
prepopulated_fields = {'slug': ('name',)}
list_display = ('name', 'slug', 'repo', 'repo_type', 'featured')
- list_filter = ('repo_type', 'featured', 'privacy_level',
- 'documentation_type', 'programming_language',
- ProjectOwnerBannedFilter)
+ list_filter = (
+ 'repo_type',
+ 'featured',
+ 'privacy_level',
+ 'documentation_type',
+ 'programming_language',
+ ProjectOwnerBannedFilter,
+ )
list_editable = ('featured',)
search_fields = ('slug', 'repo')
- inlines = [ProjectRelationshipInline, RedirectInline,
- VersionInline, DomainInline]
+ inlines = [
+ ProjectRelationshipInline,
+ RedirectInline,
+ VersionInline,
+ DomainInline,
+ ]
readonly_fields = ('feature_flags',)
raw_id_fields = ('users', 'main_language_project')
actions = ['send_owner_email', 'ban_owner']
@@ -126,7 +134,7 @@ def feature_flags(self, obj):
def send_owner_email(self, request, queryset):
view = ProjectSendNotificationView.as_view(
- action_name='send_owner_email'
+ action_name='send_owner_email',
)
return view(request, queryset=queryset)
@@ -143,18 +151,25 @@ def ban_owner(self, request, queryset):
total = 0
for project in queryset:
if project.users.count() == 1:
- count = (UserProfile.objects
- .filter(user__projects=project)
- .update(banned=True))
+ count = (
+ UserProfile.objects.filter(user__projects=project
+ ).update(banned=True)
+ )
total += count
else:
- messages.add_message(request, messages.ERROR,
- 'Project has multiple owners: {}'.format(project))
+ messages.add_message(
+ request,
+ messages.ERROR,
+ 'Project has multiple owners: {}'.format(project),
+ )
if total == 0:
messages.add_message(request, messages.ERROR, 'No users banned')
else:
- messages.add_message(request, messages.INFO,
- 'Banned {} user(s)'.format(total))
+ messages.add_message(
+ request,
+ messages.INFO,
+ 'Banned {} user(s)'.format(total),
+ )
ban_owner.short_description = 'Ban project owner'
@@ -175,7 +190,7 @@ def get_actions(self, request):
actions['delete_selected'] = (
self.__class__.delete_selected_and_artifacts,
'delete_selected',
- delete_selected.short_description
+ delete_selected.short_description,
)
return actions
diff --git a/readthedocs/projects/apps.py b/readthedocs/projects/apps.py
index e29afbe49ce..818c5b5e3d0 100644
--- a/readthedocs/projects/apps.py
+++ b/readthedocs/projects/apps.py
@@ -1,4 +1,5 @@
-"""Project app config"""
+# -*- coding: utf-8 -*-
+"""Project app config."""
from django.apps import AppConfig
diff --git a/readthedocs/projects/backends/views.py b/readthedocs/projects/backends/views.py
index 428ca3f82ca..326a7c9637f 100644
--- a/readthedocs/projects/backends/views.py
+++ b/readthedocs/projects/backends/views.py
@@ -1,5 +1,6 @@
+# -*- coding: utf-8 -*-
"""
-Project views loaded by configuration settings
+Project views loaded by configuration settings.
Use these views instead of calling the views directly, in order to allow for
settings override of the view class.
diff --git a/readthedocs/projects/constants.py b/readthedocs/projects/constants.py
index f357942d611..b06d5efb9aa 100644
--- a/readthedocs/projects/constants.py
+++ b/readthedocs/projects/constants.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""
Project constants.
@@ -310,10 +311,13 @@
]
GITHUB_URL = (
'https://github.com/{user}/{repo}/'
- '{action}/{version}{docroot}{path}{source_suffix}')
+ '{action}/{version}{docroot}{path}{source_suffix}'
+)
BITBUCKET_URL = (
'https://bitbucket.org/{user}/{repo}/'
- 'src/{version}{docroot}{path}{source_suffix}')
+ 'src/{version}{docroot}{path}{source_suffix}'
+)
GITLAB_URL = (
'https://gitlab.com/{user}/{repo}/'
- '{action}/{version}{docroot}{path}{source_suffix}')
+ '{action}/{version}{docroot}{path}{source_suffix}'
+)
diff --git a/readthedocs/projects/exceptions.py b/readthedocs/projects/exceptions.py
index 07ccd562459..20dfa0c6b63 100644
--- a/readthedocs/projects/exceptions.py
+++ b/readthedocs/projects/exceptions.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Project exceptions."""
from django.conf import settings
@@ -13,13 +14,13 @@ class ProjectConfigurationError(BuildEnvironmentError):
NOT_FOUND = _(
'A configuration file was not found. '
- 'Make sure you have a conf.py file in your repository.'
+ 'Make sure you have a conf.py file in your repository.',
)
MULTIPLE_CONF_FILES = _(
'We found more than one conf.py and are not sure which one to use. '
'Please, specify the correct file under the Advanced settings tab '
- "in the project's Admin."
+ "in the project's Admin.",
)
@@ -29,20 +30,18 @@ class RepositoryError(BuildEnvironmentError):
PRIVATE_ALLOWED = _(
'There was a problem connecting to your repository, '
- 'ensure that your repository URL is correct.'
+ 'ensure that your repository URL is correct.',
)
PRIVATE_NOT_ALLOWED = _(
'There was a problem connecting to your repository, '
'ensure that your repository URL is correct and your repository is public. '
- 'Private repositories are not supported.'
+ 'Private repositories are not supported.',
)
- INVALID_SUBMODULES = _(
- 'One or more submodule URLs are not valid: {}.'
- )
+ INVALID_SUBMODULES = _('One or more submodule URLs are not valid: {}.',)
DUPLICATED_RESERVED_VERSIONS = _(
- 'You can not have two versions with the name latest or stable.'
+ 'You can not have two versions with the name latest or stable.',
)
def get_default_message(self):
@@ -59,5 +58,3 @@ class ProjectSpamError(Exception):
This error is not raised to users, we use this for banning users in the
background.
"""
-
- pass
diff --git a/readthedocs/projects/feeds.py b/readthedocs/projects/feeds.py
index de3cc008b60..4a578c6a922 100644
--- a/readthedocs/projects/feeds.py
+++ b/readthedocs/projects/feeds.py
@@ -1,4 +1,5 @@
-"""Project RSS feeds"""
+# -*- coding: utf-8 -*-
+"""Project RSS feeds."""
from django.contrib.syndication.views import Feed
@@ -7,11 +8,11 @@
class LatestProjectsFeed(Feed):
- """RSS feed for projects that were recently updated"""
+ """RSS feed for projects that were recently updated."""
- title = "Recently updated documentation"
- link = "http://readthedocs.org"
- description = "Recently updated documentation on Read the Docs"
+ title = 'Recently updated documentation'
+ link = 'http://readthedocs.org'
+ description = 'Recently updated documentation on Read the Docs'
def items(self):
return Project.objects.public().order_by('-modified_date')[:10]
@@ -25,11 +26,11 @@ def item_description(self, item):
class NewProjectsFeed(Feed):
- """RSS feed for newly created projects"""
+ """RSS feed for newly created projects."""
- title = "Newest documentation"
- link = "http://readthedocs.org"
- description = "Recently created documentation on Read the Docs"
+ title = 'Newest documentation'
+ link = 'http://readthedocs.org'
+ description = 'Recently created documentation on Read the Docs'
def items(self):
return Project.objects.public().order_by('-pk')[:10]
diff --git a/readthedocs/projects/fixtures/test_auth.json b/readthedocs/projects/fixtures/test_auth.json
index 83d7738406e..c0d160196f1 100644
--- a/readthedocs/projects/fixtures/test_auth.json
+++ b/readthedocs/projects/fixtures/test_auth.json
@@ -701,4 +701,4 @@
"date_joined": "2014-02-09T19:48:39.934+00:00"
}
}
-]
\ No newline at end of file
+]
diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py
index 05f3afb7eac..69da7eff642 100644
--- a/readthedocs/projects/forms.py
+++ b/readthedocs/projects/forms.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Project forms."""
from random import choice
@@ -19,7 +20,6 @@
from readthedocs.integrations.models import Integration
from readthedocs.oauth.models import RemoteRepository
from readthedocs.projects import constants
-from readthedocs.projects.constants import PUBLIC
from readthedocs.projects.exceptions import ProjectSpamError
from readthedocs.projects.models import (
Domain,
@@ -120,12 +120,11 @@ def clean_name(self):
potential_slug = slugify(name)
if Project.objects.filter(slug=potential_slug).exists():
raise forms.ValidationError(
- _('Invalid project name, a project already exists with that name')) # yapf: disable # noqa
+ _('Invalid project name, a project already exists with that name'),
+ ) # yapf: disable # noqa
if not potential_slug:
# Check the generated slug won't be empty
- raise forms.ValidationError(
- _('Invalid project name'),
- )
+ raise forms.ValidationError(_('Invalid project name'),)
return name
@@ -179,7 +178,9 @@ def clean_tags(self):
for tag in tags:
if len(tag) > 100:
raise forms.ValidationError(
- _('Length of each tag must be less than or equal to 100 characters.')
+ _(
+ 'Length of each tag must be less than or equal to 100 characters.'
+ ),
)
return tags
@@ -191,8 +192,10 @@ class ProjectAdvancedForm(ProjectTriggerBuildMixin, ProjectForm):
python_interpreter = forms.ChoiceField(
choices=constants.PYTHON_CHOICES,
initial='python',
- help_text=_('The Python interpreter used to create the virtual '
- 'environment.'),
+ help_text=_(
+ 'The Python interpreter used to create the virtual '
+ 'environment.',
+ ),
)
class Meta:
@@ -223,30 +226,39 @@ def __init__(self, *args, **kwargs):
default_choice = (None, '-' * 9)
all_versions = self.instance.versions.values_list(
- 'identifier', 'verbose_name'
+ 'identifier',
+ 'verbose_name',
)
self.fields['default_branch'].widget = forms.Select(
- choices=[default_choice] + list(all_versions)
+ choices=[default_choice] + list(all_versions),
)
active_versions = self.instance.all_active_versions().values_list(
- 'slug', 'verbose_name'
+ 'slug',
+ 'verbose_name',
)
self.fields['default_version'].widget = forms.Select(
- choices=active_versions
+ choices=active_versions,
)
def clean_conf_py_file(self):
filename = self.cleaned_data.get('conf_py_file', '').strip()
if filename and 'conf.py' not in filename:
raise forms.ValidationError(
- _('Your configuration file is invalid, make sure it contains '
- 'conf.py in it.')) # yapf: disable
+ _(
+ 'Your configuration file is invalid, make sure it contains '
+ 'conf.py in it.',
+ ),
+ ) # yapf: disable
return filename
-class UpdateProjectForm(ProjectTriggerBuildMixin, ProjectBasicsForm,
- ProjectExtraForm):
+class UpdateProjectForm(
+ ProjectTriggerBuildMixin,
+ ProjectBasicsForm,
+ ProjectExtraForm,
+):
+
class Meta:
model = Project
fields = (
@@ -269,27 +281,26 @@ def clean_language(self):
if project:
msg = _(
'There is already a "{lang}" translation '
- 'for the {proj} project.'
+ 'for the {proj} project.',
)
if project.translations.filter(language=language).exists():
raise forms.ValidationError(
- msg.format(lang=language, proj=project.slug)
+ msg.format(lang=language, proj=project.slug),
)
main_project = project.main_language_project
if main_project:
if main_project.language == language:
raise forms.ValidationError(
- msg.format(lang=language, proj=main_project.slug)
+ msg.format(lang=language, proj=main_project.slug),
)
siblings = (
- main_project.translations
- .filter(language=language)
- .exclude(pk=project.pk)
- .exists()
+ main_project.translations.filter(language=language
+ ).exclude(pk=project.pk
+ ).exists()
)
if siblings:
raise forms.ValidationError(
- msg.format(lang=language, proj=main_project.slug)
+ msg.format(lang=language, proj=main_project.slug),
)
return language
@@ -311,7 +322,9 @@ def __init__(self, *args, **kwargs):
# Don't display the update form with an editable child, as it will be
# filtered out from the queryset anyways.
if hasattr(self, 'instance') and self.instance.pk is not None:
- self.fields['child'].queryset = Project.objects.filter(pk=self.instance.child.pk)
+ self.fields['child'].queryset = Project.objects.filter(
+ pk=self.instance.child.pk
+ )
else:
self.fields['child'].queryset = self.get_subproject_queryset()
@@ -320,14 +333,16 @@ def clean_parent(self):
# This validation error is mostly for testing, users shouldn't see
# this in normal circumstances
raise forms.ValidationError(
- _('Subproject nesting is not supported'))
+ _('Subproject nesting is not supported'),
+ )
return self.project
def clean_child(self):
child = self.cleaned_data['child']
if child == self.project:
raise forms.ValidationError(
- _('A project can not be a subproject of itself'))
+ _('A project can not be a subproject of itself'),
+ )
return child
def get_subproject_queryset(self):
@@ -338,10 +353,10 @@ def get_subproject_queryset(self):
project, or are a superproject, as neither case is supported.
"""
queryset = (
- Project.objects.for_admin_user(self.user)
- .exclude(subprojects__isnull=False)
- .exclude(superprojects__isnull=False)
- .exclude(pk=self.project.pk))
+ Project.objects.for_admin_user(self.user).exclude(
+ subprojects__isnull=False
+ ).exclude(superprojects__isnull=False).exclude(pk=self.project.pk)
+ )
return queryset
@@ -446,8 +461,10 @@ def build_versions_form(project):
class BaseUploadHTMLForm(forms.Form):
content = forms.FileField(label=_('Zip file of HTML'))
- overwrite = forms.BooleanField(required=False,
- label=_('Overwrite existing HTML?'))
+ overwrite = forms.BooleanField(
+ required=False,
+ label=_('Overwrite existing HTML?'),
+ )
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
@@ -498,7 +515,8 @@ def clean_user(self):
user_qs = User.objects.filter(username=name)
if not user_qs.exists():
raise forms.ValidationError(
- _('User {name} does not exist').format(name=name))
+ _('User {name} does not exist').format(name=name),
+ )
self.user = user_qs[0]
return name
@@ -521,7 +539,9 @@ def __init__(self, *args, **kwargs):
def clean_email(self):
self.email = EmailHook.objects.get_or_create(
- email=self.cleaned_data['email'], project=self.project)[0]
+ email=self.cleaned_data['email'],
+ project=self.project,
+ )[0]
return self.email
def save(self):
@@ -539,7 +559,9 @@ def __init__(self, *args, **kwargs):
def save(self, commit=True):
self.webhook = WebHook.objects.get_or_create(
- url=self.cleaned_data['url'], project=self.project)[0]
+ url=self.cleaned_data['url'],
+ project=self.project,
+ )[0]
self.project.webhook_notifications.add(self.webhook)
return self.project
@@ -561,11 +583,13 @@ def __init__(self, *args, **kwargs):
self.fields['project'].choices = self.get_choices()
def get_choices(self):
- return [
- (project.slug, '{project} ({lang})'.format(
- project=project.slug, lang=project.get_language_display()))
- for project in self.get_translation_queryset().all()
- ]
+ return [(
+ project.slug,
+ '{project} ({lang})'.format(
+ project=project.slug,
+ lang=project.get_language_display(),
+ ),
+ ) for project in self.get_translation_queryset().all()]
def clean_project(self):
translation_project_slug = self.cleaned_data['project']
@@ -574,36 +598,31 @@ def clean_project(self):
if self.parent.main_language_project is not None:
msg = 'Project "{project}" is already a translation'
raise forms.ValidationError(
- (_(msg).format(project=self.parent.slug))
+ (_(msg).format(project=self.parent.slug)),
)
project_translation_qs = self.get_translation_queryset().filter(
- slug=translation_project_slug
+ slug=translation_project_slug,
)
if not project_translation_qs.exists():
msg = 'Project "{project}" does not exist.'
raise forms.ValidationError(
- (_(msg).format(project=translation_project_slug))
+ (_(msg).format(project=translation_project_slug)),
)
self.translation = project_translation_qs.first()
if self.translation.language == self.parent.language:
- msg = (
- 'Both projects can not have the same language ({lang}).'
- )
+ msg = ('Both projects can not have the same language ({lang}).')
raise forms.ValidationError(
- _(msg).format(lang=self.parent.get_language_display())
+ _(msg).format(lang=self.parent.get_language_display()),
)
exists_translation = (
- self.parent.translations
- .filter(language=self.translation.language)
- .exists()
+ self.parent.translations.filter(language=self.translation.language
+ ).exists()
)
if exists_translation:
- msg = (
- 'This project already has a translation for {lang}.'
- )
+ msg = ('This project already has a translation for {lang}.')
raise forms.ValidationError(
- _(msg).format(lang=self.translation.get_language_display())
+ _(msg).format(lang=self.translation.get_language_display()),
)
is_parent = self.translation.translations.exists()
if is_parent:
@@ -616,9 +635,9 @@ def clean_project(self):
def get_translation_queryset(self):
queryset = (
- Project.objects.for_admin_user(self.user)
- .filter(main_language_project=None)
- .exclude(pk=self.parent.pk)
+ Project.objects.for_admin_user(self.user).filter(
+ main_language_project=None
+ ).exclude(pk=self.parent.pk)
)
return queryset
@@ -688,10 +707,12 @@ def clean_canonical(self):
canonical = self.cleaned_data['canonical']
_id = self.initial.get('id')
if canonical and Domain.objects.filter(
- project=self.project, canonical=True
+ project=self.project,
+ canonical=True,
).exclude(pk=_id).exists():
raise forms.ValidationError(
- _('Only 1 Domain can be canonical at a time.'))
+ _('Only 1 Domain can be canonical at a time.'),
+ )
return canonical
diff --git a/readthedocs/projects/management/commands/import_project_from_live.py b/readthedocs/projects/management/commands/import_project_from_live.py
index 042caf709fc..4f823cffd3d 100644
--- a/readthedocs/projects/management/commands/import_project_from_live.py
+++ b/readthedocs/projects/management/commands/import_project_from_live.py
@@ -1,4 +1,5 @@
-"""Import project command"""
+# -*- coding: utf-8 -*-
+"""Import project command."""
import json
@@ -13,7 +14,7 @@
class Command(BaseCommand):
"""
- Import project from production API
+ Import project from production API.
This is a helper to debug issues with projects on the server more easily
locally. It allows you to import projects based on the data that the public
@@ -22,8 +23,8 @@ class Command(BaseCommand):
help = (
"Retrieves the data of a project from readthedocs.org's API and puts "
- "it into the local database. This is mostly useful for debugging "
- "issues with projects on the live site."
+ 'it into the local database. This is mostly useful for debugging '
+ 'issues with projects on the live site.'
)
def add_arguments(self, parser):
@@ -41,10 +42,11 @@ def handle(self, *args, **options):
project_data = project_data['objects'][0]
except (KeyError, IndexError):
self.stderr.write(
- 'Cannot find {slug} in API. Response was:\n{response}'
- .format(
+ 'Cannot find {slug} in API. Response was:\n{response}'.format(
slug=slug,
- response=json.dumps(project_data)))
+ response=json.dumps(project_data),
+ ),
+ )
try:
project = Project.objects.get(slug=slug)
diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py
index a54f15d87b0..cc34197bf1a 100644
--- a/readthedocs/projects/models.py
+++ b/readthedocs/projects/models.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Project models."""
import fnmatch
@@ -50,11 +51,19 @@ class ProjectRelationship(models.Model):
This is used for subprojects
"""
- parent = models.ForeignKey('Project', verbose_name=_('Parent'),
- related_name='subprojects')
- child = models.ForeignKey('Project', verbose_name=_('Child'),
- related_name='superprojects')
- alias = models.SlugField(_('Alias'), max_length=255, null=True, blank=True, db_index=False)
+ parent = models.ForeignKey(
+ 'Project',
+ verbose_name=_('Parent'),
+ related_name='subprojects',
+ )
+ child = models.ForeignKey(
+ 'Project',
+ verbose_name=_('Child'),
+ related_name='superprojects',
+ )
+ alias = models.SlugField(
+ _('Alias'), max_length=255, null=True, blank=True, db_index=False
+ )
objects = ChildRelatedProjectQuerySet.as_manager()
@@ -81,113 +90,204 @@ class Project(models.Model):
modified_date = models.DateTimeField(_('Modified date'), auto_now=True)
# Generally from conf.py
- users = models.ManyToManyField(User, verbose_name=_('User'),
- related_name='projects')
+ users = models.ManyToManyField(
+ User,
+ verbose_name=_('User'),
+ related_name='projects',
+ )
# A DNS label can contain up to 63 characters.
name = models.CharField(_('Name'), max_length=63)
slug = models.SlugField(_('Slug'), max_length=63, unique=True)
- description = models.TextField(_('Description'), blank=True,
- help_text=_('The reStructuredText '
- 'description of the project'))
- repo = models.CharField(_('Repository URL'), max_length=255,
- validators=[validate_repository_url],
- help_text=_('Hosted documentation repository URL'))
- repo_type = models.CharField(_('Repository type'), max_length=10,
- choices=constants.REPO_CHOICES, default='git')
- project_url = models.URLField(_('Project homepage'), blank=True,
- help_text=_('The project\'s homepage'))
- canonical_url = models.URLField(_('Canonical URL'), blank=True,
- help_text=_('URL that documentation is expected to serve from'))
+ description = models.TextField(
+ _('Description'),
+ blank=True,
+ help_text=_(
+ 'The reStructuredText '
+ 'description of the project',
+ ),
+ )
+ repo = models.CharField(
+ _('Repository URL'),
+ max_length=255,
+ validators=[validate_repository_url],
+ help_text=_('Hosted documentation repository URL'),
+ )
+ repo_type = models.CharField(
+ _('Repository type'),
+ max_length=10,
+ choices=constants.REPO_CHOICES,
+ default='git',
+ )
+ project_url = models.URLField(
+ _('Project homepage'),
+ blank=True,
+ help_text=_('The project\'s homepage'),
+ )
+ canonical_url = models.URLField(
+ _('Canonical URL'),
+ blank=True,
+ help_text=_('URL that documentation is expected to serve from'),
+ )
single_version = models.BooleanField(
- _('Single version'), default=False,
- help_text=_('A single version site has no translations and only your '
- '"latest" version, served at the root of the domain. Use '
- 'this with caution, only turn it on if you will never '
- 'have multiple versions of your docs.'))
+ _('Single version'),
+ default=False,
+ help_text=_(
+ 'A single version site has no translations and only your '
+ '"latest" version, served at the root of the domain. Use '
+ 'this with caution, only turn it on if you will never '
+ 'have multiple versions of your docs.',
+ ),
+ )
default_version = models.CharField(
- _('Default version'), max_length=255, default=LATEST,
- help_text=_('The version of your project that / redirects to'))
+ _('Default version'),
+ max_length=255,
+ default=LATEST,
+ help_text=_('The version of your project that / redirects to'),
+ )
# In default_branch, None means the backend should choose the
# appropriate branch. Eg 'master' for git
default_branch = models.CharField(
- _('Default branch'), max_length=255, default=None, null=True,
- blank=True, help_text=_('What branch "latest" points to. Leave empty '
- 'to use the default value for your VCS (eg. '
- 'trunk
or master
).'))
+ _('Default branch'),
+ max_length=255,
+ default=None,
+ null=True,
+ blank=True,
+ help_text=_(
+ 'What branch "latest" points to. Leave empty '
+ 'to use the default value for your VCS (eg. '
+ 'trunk
or master
).',
+ ),
+ )
requirements_file = models.CharField(
- _('Requirements file'), max_length=255, default=None, null=True,
- blank=True, help_text=_(
+ _('Requirements file'),
+ max_length=255,
+ default=None,
+ null=True,
+ blank=True,
+ help_text=_(
'A '
'pip requirements file needed to build your documentation. '
- 'Path from the root of your project.'))
+ 'Path from the root of your project.',
+ ),
+ )
documentation_type = models.CharField(
- _('Documentation type'), max_length=20,
- choices=constants.DOCUMENTATION_CHOICES, default='sphinx',
- help_text=_('Type of documentation you are building. More info.'))
+ _('Documentation type'),
+ max_length=20,
+ choices=constants.DOCUMENTATION_CHOICES,
+ default='sphinx',
+ help_text=_(
+ 'Type of documentation you are building. More info.',
+ ),
+ )
# Project features
cdn_enabled = models.BooleanField(_('CDN Enabled'), default=False)
analytics_code = models.CharField(
- _('Analytics code'), max_length=50, null=True, blank=True,
- help_text=_('Google Analytics Tracking ID '
- '(ex. UA-22345342-1
). '
- 'This may slow down your page loads.'))
+ _('Analytics code'),
+ max_length=50,
+ null=True,
+ blank=True,
+ help_text=_(
+ 'Google Analytics Tracking ID '
+ '(ex. UA-22345342-1
). '
+ 'This may slow down your page loads.',
+ ),
+ )
container_image = models.CharField(
- _('Alternative container image'), max_length=64, null=True, blank=True)
+ _('Alternative container image'),
+ max_length=64,
+ null=True,
+ blank=True,
+ )
container_mem_limit = models.CharField(
- _('Container memory limit'), max_length=10, null=True, blank=True,
- help_text=_('Memory limit in Docker format '
- '-- example: 512m
or 1g
'))
+ _('Container memory limit'),
+ max_length=10,
+ null=True,
+ blank=True,
+ help_text=_(
+ 'Memory limit in Docker format '
+ '-- example: 512m
or 1g
',
+ ),
+ )
container_time_limit = models.IntegerField(
_('Container time limit in seconds'),
null=True,
blank=True,
)
build_queue = models.CharField(
- _('Alternate build queue id'), max_length=32, null=True, blank=True)
+ _('Alternate build queue id'),
+ max_length=32,
+ null=True,
+ blank=True,
+ )
allow_promos = models.BooleanField(
- _('Allow paid advertising'), default=True, help_text=_(
- 'If unchecked, users will still see community ads.'))
+ _('Allow paid advertising'),
+ default=True,
+ help_text=_(
+ 'If unchecked, users will still see community ads.',
+ ),
+ )
ad_free = models.BooleanField(
_('Ad-free'),
default=False,
help_text='If checked, do not show advertising for this project',
)
show_version_warning = models.BooleanField(
- _('Show version warning'), default=False,
- help_text=_('Show warning banner in non-stable nor latest versions.')
+ _('Show version warning'),
+ default=False,
+ help_text=_('Show warning banner in non-stable nor latest versions.'),
)
# Sphinx specific build options.
enable_epub_build = models.BooleanField(
- _('Enable EPUB build'), default=True,
+ _('Enable EPUB build'),
+ default=True,
help_text=_(
- 'Create a EPUB version of your documentation with each build.'))
+ 'Create a EPUB version of your documentation with each build.',
+ ),
+ )
enable_pdf_build = models.BooleanField(
- _('Enable PDF build'), default=True,
+ _('Enable PDF build'),
+ default=True,
help_text=_(
- 'Create a PDF version of your documentation with each build.'))
+ 'Create a PDF version of your documentation with each build.',
+ ),
+ )
# Other model data.
- path = models.CharField(_('Path'), max_length=255, editable=False,
- help_text=_('The directory where '
- 'conf.py
lives'))
+ path = models.CharField(
+ _('Path'),
+ max_length=255,
+ editable=False,
+ help_text=_(
+ 'The directory where '
+ 'conf.py
lives',
+ ),
+ )
conf_py_file = models.CharField(
- _('Python configuration file'), max_length=255, default='', blank=True,
- help_text=_('Path from project root to conf.py
file '
- '(ex. docs/conf.py
). '
- 'Leave blank if you want us to find it for you.'))
+ _('Python configuration file'),
+ max_length=255,
+ default='',
+ blank=True,
+ help_text=_(
+ 'Path from project root to conf.py
file '
+ '(ex. docs/conf.py
). '
+ 'Leave blank if you want us to find it for you.',
+ ),
+ )
featured = models.BooleanField(_('Featured'), default=False)
skip = models.BooleanField(_('Skip'), default=False)
install_project = models.BooleanField(
_('Install Project'),
- help_text=_('Install your project inside a virtualenv using setup.py '
- 'install
'),
- default=False
+ help_text=_(
+ 'Install your project inside a virtualenv using setup.py '
+ 'install
',
+ ),
+ default=False,
)
# This model attribute holds the python interpreter used to create the
@@ -197,57 +297,93 @@ class Project(models.Model):
max_length=20,
choices=constants.PYTHON_CHOICES,
default='python',
- help_text=_('The Python interpreter used to create the virtual '
- 'environment.'))
+ help_text=_(
+ 'The Python interpreter used to create the virtual '
+ 'environment.',
+ ),
+ )
use_system_packages = models.BooleanField(
_('Use system packages'),
- help_text=_('Give the virtual environment access to the global '
- 'site-packages dir.'),
- default=False
+ help_text=_(
+ 'Give the virtual environment access to the global '
+ 'site-packages dir.',
+ ),
+ default=False,
)
privacy_level = models.CharField(
- _('Privacy Level'), max_length=20, choices=constants.PRIVACY_CHOICES,
+ _('Privacy Level'),
+ max_length=20,
+ choices=constants.PRIVACY_CHOICES,
default=getattr(settings, 'DEFAULT_PRIVACY_LEVEL', 'public'),
- help_text=_('Level of privacy that you want on the repository. '
- 'Protected means public but not in listings.'))
+ help_text=_(
+ 'Level of privacy that you want on the repository. '
+ 'Protected means public but not in listings.',
+ ),
+ )
version_privacy_level = models.CharField(
- _('Version Privacy Level'), max_length=20,
- choices=constants.PRIVACY_CHOICES, default=getattr(
- settings, 'DEFAULT_PRIVACY_LEVEL', 'public'),
- help_text=_('Default level of privacy you want on built '
- 'versions of documentation.'))
+ _('Version Privacy Level'),
+ max_length=20,
+ choices=constants.PRIVACY_CHOICES,
+ default=getattr(
+ settings,
+ 'DEFAULT_PRIVACY_LEVEL',
+ 'public',
+ ),
+ help_text=_(
+ 'Default level of privacy you want on built '
+ 'versions of documentation.',
+ ),
+ )
# Subprojects
related_projects = models.ManyToManyField(
- 'self', verbose_name=_('Related projects'), blank=True,
- symmetrical=False, through=ProjectRelationship)
+ 'self',
+ verbose_name=_('Related projects'),
+ blank=True,
+ symmetrical=False,
+ through=ProjectRelationship,
+ )
# Language bits
- language = models.CharField(_('Language'), max_length=20, default='en',
- help_text=_('The language the project '
- 'documentation is rendered in. '
- "Note: this affects your project's URL."),
- choices=constants.LANGUAGES)
+ language = models.CharField(
+ _('Language'),
+ max_length=20,
+ default='en',
+ help_text=_(
+ 'The language the project '
+ 'documentation is rendered in. '
+ "Note: this affects your project's URL.",
+ ),
+ choices=constants.LANGUAGES,
+ )
programming_language = models.CharField(
_('Programming Language'),
max_length=20,
default='words',
help_text=_(
- 'The primary programming language the project is written in.'),
- choices=constants.PROGRAMMING_LANGUAGES, blank=True)
+ 'The primary programming language the project is written in.',
+ ),
+ choices=constants.PROGRAMMING_LANGUAGES,
+ blank=True,
+ )
# A subproject pointed at its main language, so it can be tracked
- main_language_project = models.ForeignKey('self',
- related_name='translations',
- on_delete=models.SET_NULL,
- blank=True, null=True)
+ main_language_project = models.ForeignKey(
+ 'self',
+ related_name='translations',
+ on_delete=models.SET_NULL,
+ blank=True,
+ null=True,
+ )
has_valid_webhook = models.BooleanField(
- default=False, help_text=_('This project has been built with a webhook')
+ default=False,
+ help_text=_('This project has been built with a webhook'),
)
has_valid_clone = models.BooleanField(
- default=False, help_text=_('This project has been successfully cloned')
+ default=False,
+ help_text=_('This project has been successfully cloned'),
)
tags = TaggableManager(blank=True)
@@ -317,7 +453,10 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ
try:
if not first_save:
broadcast(
- type='app', task=tasks.update_static_metadata, args=[self.pk],)
+ type='app',
+ task=tasks.update_static_metadata,
+ args=[self.pk],
+ )
except Exception:
log.exception('failed to update static metadata')
try:
@@ -336,12 +475,18 @@ def get_docs_url(self, version_slug=None, lang_slug=None, private=None):
Always use http for now, to avoid content warnings.
"""
- return resolve(project=self, version_slug=version_slug, language=lang_slug, private=private)
+ return resolve(
+ project=self, version_slug=version_slug, language=lang_slug,
+ private=private
+ )
def get_builds_url(self):
- return reverse('builds_project_list', kwargs={
- 'project_slug': self.slug,
- })
+ return reverse(
+ 'builds_project_list',
+ kwargs={
+ 'project_slug': self.slug,
+ },
+ )
def get_canonical_url(self):
if getattr(settings, 'DONT_HIT_DB', True):
@@ -355,11 +500,8 @@ def get_subproject_urls(self):
This is used in search result linking
"""
if getattr(settings, 'DONT_HIT_DB', True):
- return [(proj['slug'], proj['canonical_url'])
- for proj in (
- api.project(self.pk)
- .subprojects()
- .get()['subprojects'])]
+ return [(proj['slug'], proj['canonical_url']) for proj in
+ (api.project(self.pk).subprojects().get()['subprojects'])]
return [(proj.child.slug, proj.child.get_docs_url())
for proj in self.subprojects.all()]
@@ -374,25 +516,39 @@ def get_production_media_path(self, type_, version_slug, include_file=True):
:returns: Full path to media file or path
"""
- if getattr(settings, 'DEFAULT_PRIVACY_LEVEL', 'public') == 'public' or settings.DEBUG:
+ if getattr(settings, 'DEFAULT_PRIVACY_LEVEL',
+ 'public') == 'public' or settings.DEBUG:
path = os.path.join(
- settings.MEDIA_ROOT, type_, self.slug, version_slug)
+ settings.MEDIA_ROOT,
+ type_,
+ self.slug,
+ version_slug,
+ )
else:
path = os.path.join(
- settings.PRODUCTION_MEDIA_ARTIFACTS, type_, self.slug, version_slug)
+ settings.PRODUCTION_MEDIA_ARTIFACTS,
+ type_,
+ self.slug,
+ version_slug,
+ )
if include_file:
path = os.path.join(
- path, '{}.{}'.format(self.slug, type_.replace('htmlzip', 'zip')))
+ path,
+ '{}.{}'.format(self.slug, type_.replace('htmlzip', 'zip')),
+ )
return path
def get_production_media_url(self, type_, version_slug, full_path=True):
"""Get the URL for downloading a specific media file."""
try:
- path = reverse('project_download_media', kwargs={
- 'project_slug': self.slug,
- 'type_': type_,
- 'version_slug': version_slug,
- })
+ path = reverse(
+ 'project_download_media',
+ kwargs={
+ 'project_slug': self.slug,
+ 'type_': type_,
+ 'version_slug': version_slug,
+ },
+ )
except NoReverseMatch:
return ''
if full_path:
@@ -406,11 +562,17 @@ def subdomain(self):
def get_downloads(self):
downloads = {}
downloads['htmlzip'] = self.get_production_media_url(
- 'htmlzip', self.get_default_version())
+ 'htmlzip',
+ self.get_default_version(),
+ )
downloads['epub'] = self.get_production_media_url(
- 'epub', self.get_default_version())
+ 'epub',
+ self.get_default_version(),
+ )
downloads['pdf'] = self.get_production_media_url(
- 'pdf', self.get_default_version())
+ 'pdf',
+ self.get_default_version(),
+ )
return downloads
@property
@@ -510,7 +672,9 @@ def conf_file(self, version=LATEST):
"""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,)
+ self.checkout_path(version),
+ self.conf_py_file,
+ )
if os.path.exists(conf_path):
log.info('Inserting conf.py file path from model')
@@ -533,12 +697,10 @@ def conf_file(self, version=LATEST):
# the `doc` word in the path, we raise an error informing this to the user
if len(files) > 1:
raise ProjectConfigurationError(
- ProjectConfigurationError.MULTIPLE_CONF_FILES
+ ProjectConfigurationError.MULTIPLE_CONF_FILES,
)
- raise ProjectConfigurationError(
- ProjectConfigurationError.NOT_FOUND
- )
+ raise ProjectConfigurationError(ProjectConfigurationError.NOT_FOUND,)
def conf_dir(self, version=LATEST):
conf_file = self.conf_file(version)
@@ -564,18 +726,30 @@ def has_aliases(self):
def has_pdf(self, version_slug=LATEST):
if not self.enable_pdf_build:
return False
- return os.path.exists(self.get_production_media_path(
- type_='pdf', version_slug=version_slug))
+ return os.path.exists(
+ self.get_production_media_path(
+ type_='pdf',
+ version_slug=version_slug,
+ )
+ )
def has_epub(self, version_slug=LATEST):
if not self.enable_epub_build:
return False
- return os.path.exists(self.get_production_media_path(
- type_='epub', version_slug=version_slug))
+ return os.path.exists(
+ self.get_production_media_path(
+ type_='epub',
+ version_slug=version_slug,
+ )
+ )
def has_htmlzip(self, version_slug=LATEST):
- return os.path.exists(self.get_production_media_path(
- type_='htmlzip', version_slug=version_slug))
+ return os.path.exists(
+ self.get_production_media_path(
+ type_='htmlzip',
+ version_slug=version_slug,
+ )
+ )
@property
def sponsored(self):
@@ -667,7 +841,8 @@ def get_latest_build(self, finished=True):
def api_versions(self):
from readthedocs.builds.models import APIVersion
ret = []
- for version_data in api.project(self.pk).active_versions.get()['versions']:
+ for version_data in api.project(self.pk
+ ).active_versions.get()['versions']:
version = APIVersion(**version_data)
ret.append(version)
return sort_version_aware(ret)
@@ -675,8 +850,10 @@ def api_versions(self):
def active_versions(self):
from readthedocs.builds.models import Version
versions = Version.objects.public(project=self, only_active=True)
- return (versions.filter(built=True, active=True) |
- versions.filter(active=True, uploaded=True))
+ return (
+ versions.filter(built=True, active=True) |
+ versions.filter(active=True, uploaded=True)
+ )
def ordered_active_versions(self, user=None):
from readthedocs.builds.models import Version
@@ -717,12 +894,15 @@ def update_stable_version(self):
current_stable = self.get_stable_version()
if current_stable:
identifier_updated = (
- new_stable.identifier != current_stable.identifier)
+ new_stable.identifier != current_stable.identifier
+ )
if identifier_updated and current_stable.active and current_stable.machine:
log.info(
'Update stable version: {project}:{version}'.format(
project=self.slug,
- version=new_stable.identifier))
+ version=new_stable.identifier,
+ ),
+ )
current_stable.identifier = new_stable.identifier
current_stable.save()
return new_stable
@@ -730,10 +910,13 @@ def update_stable_version(self):
log.info(
'Creating new stable version: {project}:{version}'.format(
project=self.slug,
- version=new_stable.identifier))
+ version=new_stable.identifier,
+ ),
+ )
current_stable = self.versions.create_stable(
type=new_stable.type,
- identifier=new_stable.identifier)
+ identifier=new_stable.identifier,
+ )
return new_stable
def versions_from_branch_name(self, branch):
@@ -756,7 +939,8 @@ def get_default_version(self):
return self.default_version
# check if the default_version exists
version_qs = self.versions.filter(
- slug=self.default_version, active=True
+ slug=self.default_version,
+ active=True,
)
if version_qs.exists():
return self.default_version
@@ -770,7 +954,9 @@ def get_default_branch(self):
def add_subproject(self, child, alias=None):
subproject, __ = ProjectRelationship.objects.get_or_create(
- parent=self, child=child, alias=alias,
+ parent=self,
+ child=child,
+ alias=alias,
)
return subproject
@@ -803,7 +989,7 @@ def get_feature_value(self, feature, positive, negative):
@property
def show_advertising(self):
"""
- Whether this project is ad-free
+ Whether this project is ad-free.
:returns: ``True`` if advertising should be shown and ``False`` otherwise
:rtype: bool
@@ -853,8 +1039,13 @@ def __init__(self, *args, **kwargs):
ad_free = (not kwargs.pop('show_advertising', True))
# These fields only exist on the API return, not on the model, so we'll
# remove them to avoid throwing exceptions due to unexpected fields
- for key in ['users', 'resource_uri', 'absolute_url', 'downloads',
- 'main_language_project', 'related_projects']:
+ for key in [
+ 'users',
+ 'resource_uri',
+ 'absolute_url',
+ 'downloads',
+ 'main_language_project',
+ 'related_projects',]:
try:
del kwargs[key]
except KeyError:
@@ -891,10 +1082,17 @@ class ImportedFile(models.Model):
things like CDN invalidation.
"""
- project = models.ForeignKey('Project', verbose_name=_('Project'),
- related_name='imported_files')
- version = models.ForeignKey('builds.Version', verbose_name=_('Version'),
- related_name='imported_files', null=True)
+ project = models.ForeignKey(
+ 'Project',
+ verbose_name=_('Project'),
+ related_name='imported_files',
+ )
+ version = models.ForeignKey(
+ 'builds.Version',
+ verbose_name=_('Version'),
+ related_name='imported_files',
+ null=True,
+ )
name = models.CharField(_('Name'), max_length=255)
slug = models.SlugField(_('Slug'))
path = models.CharField(_('Path'), max_length=255)
@@ -903,15 +1101,20 @@ class ImportedFile(models.Model):
modified_date = models.DateTimeField(_('Modified date'), auto_now=True)
def get_absolute_url(self):
- return resolve(project=self.project, version_slug=self.version.slug, filename=self.path)
+ return resolve(
+ project=self.project, version_slug=self.version.slug,
+ filename=self.path
+ )
def __str__(self):
return '{}: {}'.format(self.name, self.project)
class Notification(models.Model):
- project = models.ForeignKey(Project,
- related_name='%(class)s_notifications')
+ project = models.ForeignKey(
+ Project,
+ related_name='%(class)s_notifications',
+ )
objects = RelatedProjectQuerySet.as_manager()
class Meta:
@@ -928,8 +1131,11 @@ def __str__(self):
@python_2_unicode_compatible
class WebHook(Notification):
- url = models.URLField(max_length=600, blank=True,
- help_text=_('URL to send the webhook to'))
+ url = models.URLField(
+ max_length=600,
+ blank=True,
+ help_text=_('URL to send the webhook to'),
+ )
def __str__(self):
return self.url
@@ -941,27 +1147,38 @@ class Domain(models.Model):
"""A custom domain name for a project."""
project = models.ForeignKey(Project, related_name='domains')
- domain = models.CharField(_('Domain'), unique=True, max_length=255,
- validators=[validate_domain_name])
+ domain = models.CharField(
+ _('Domain'),
+ unique=True,
+ max_length=255,
+ validators=[validate_domain_name],
+ )
machine = models.BooleanField(
- default=False, help_text=_('This Domain was auto-created')
+ default=False,
+ help_text=_('This Domain was auto-created'),
)
cname = models.BooleanField(
- default=False, help_text=_('This Domain is a CNAME for the project')
+ default=False,
+ help_text=_('This Domain is a CNAME for the project'),
)
canonical = models.BooleanField(
default=False,
help_text=_(
'This Domain is the primary one where the documentation is '
- 'served from')
+ 'served from',
+ ),
)
https = models.BooleanField(
_('Use HTTPS'),
default=False,
- help_text=_('Always use HTTPS for this domain')
+ help_text=_('Always use HTTPS for this domain'),
+ )
+ count = models.IntegerField(
+ default=0,
+ help_text=_(
+ 'Number of times this domain has been hit',
+ ),
)
- count = models.IntegerField(default=0, help_text=_(
- 'Number of times this domain has been hit'),)
objects = RelatedProjectQuerySet.as_manager()
@@ -969,7 +1186,9 @@ class Meta:
ordering = ('-canonical', '-machine', 'domain')
def __str__(self):
- return '{domain} pointed at {project}'.format(domain=self.domain, project=self.project.name)
+ return '{domain} pointed at {project}'.format(
+ domain=self.domain, project=self.project.name
+ )
def save(self, *args, **kwargs): # pylint: disable=arguments-differ
from readthedocs.projects import tasks
@@ -979,13 +1198,19 @@ def save(self, *args, **kwargs): # pylint: disable=arguments-differ
else:
self.domain = parsed.path
super().save(*args, **kwargs)
- broadcast(type='app', task=tasks.symlink_domain,
- args=[self.project.pk, self.pk],)
+ broadcast(
+ type='app',
+ task=tasks.symlink_domain,
+ args=[self.project.pk, self.pk],
+ )
def delete(self, *args, **kwargs): # pylint: disable=arguments-differ
from readthedocs.projects import tasks
- broadcast(type='app', task=tasks.symlink_domain,
- args=[self.project.pk, self.pk, True],)
+ broadcast(
+ type='app',
+ task=tasks.symlink_domain,
+ args=[self.project.pk, self.pk, True],
+ )
super().delete(*args, **kwargs)
@@ -1024,13 +1249,28 @@ def add_features(sender, **kwargs):
(ALLOW_DEPRECATED_WEBHOOKS, _('Allow deprecated webhook views')),
(PIP_ALWAYS_UPGRADE, _('Always run pip install --upgrade')),
(SKIP_SUBMODULES, _('Skip git submodule checkout')),
- (DONT_OVERWRITE_SPHINX_CONTEXT, _(
- 'Do not overwrite context vars in conf.py with Read the Docs context')),
- (ALLOW_V2_CONFIG_FILE, _(
- 'Allow to use the v2 of the configuration file')),
- (MKDOCS_THEME_RTD, _('Use Read the Docs theme for MkDocs as default theme')),
- (DONT_SHALLOW_CLONE, _(
- 'Do not shallow clone when cloning git repos')),
+ (
+ DONT_OVERWRITE_SPHINX_CONTEXT,
+ _(
+ 'Do not overwrite context vars in conf.py with Read the Docs context',
+ ),
+ ),
+ (
+ ALLOW_V2_CONFIG_FILE,
+ _(
+ 'Allow to use the v2 of the configuration file',
+ ),
+ ),
+ (
+ MKDOCS_THEME_RTD,
+ _('Use Read the Docs theme for MkDocs as default theme')
+ ),
+ (
+ DONT_SHALLOW_CLONE,
+ _(
+ 'Do not shallow clone when cloning git repos',
+ ),
+ ),
)
projects = models.ManyToManyField(
@@ -1056,9 +1296,7 @@ def add_features(sender, **kwargs):
objects = FeatureQuerySet.as_manager()
def __str__(self):
- return '{} feature'.format(
- self.get_feature_display(),
- )
+ return '{} feature'.format(self.get_feature_display(),)
def get_feature_display(self):
"""
diff --git a/readthedocs/projects/notifications.py b/readthedocs/projects/notifications.py
index fc46cb0fdca..9d108ab6927 100644
--- a/readthedocs/projects/notifications.py
+++ b/readthedocs/projects/notifications.py
@@ -1,4 +1,5 @@
-"""Project notifications"""
+# -*- coding: utf-8 -*-
+"""Project notifications."""
from readthedocs.notifications import Notification
from readthedocs.notifications.constants import REQUIREMENT
diff --git a/readthedocs/projects/querysets.py b/readthedocs/projects/querysets.py
index 4bde40fa39f..396003dad13 100644
--- a/readthedocs/projects/querysets.py
+++ b/readthedocs/projects/querysets.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Project model QuerySet classes."""
from django.db import models
@@ -43,7 +44,9 @@ def public(self, user=None):
return queryset
def protected(self, user=None):
- queryset = self.filter(privacy_level__in=[constants.PUBLIC, constants.PROTECTED])
+ queryset = self.filter(
+ privacy_level__in=[constants.PUBLIC, constants.PROTECTED]
+ )
if user:
return self._add_user_repos(queryset, user)
return queryset
@@ -89,7 +92,8 @@ class ProjectQuerySet(SettingsOverrideObject):
class RelatedProjectQuerySetBase(models.QuerySet):
"""
- A manager for things that relate to Project and need to get their perms from the project.
+ A manager for things that relate to Project and need to get their perms from
+ the project.
This shouldn't be used as a subclass.
"""
@@ -120,7 +124,9 @@ def public(self, user=None, project=None):
def protected(self, user=None, project=None):
kwargs = {
- '%s__privacy_level__in' % self.project_field: [constants.PUBLIC, constants.PROTECTED]
+ '%s__privacy_level__in' % self.project_field: [
+ constants.PUBLIC, constants.PROTECTED
+ ],
}
queryset = self.filter(**kwargs)
if user:
@@ -175,5 +181,5 @@ class FeatureQuerySet(models.QuerySet):
def for_project(self, project):
return self.filter(
Q(projects=project) |
- Q(default_true=True, add_date__gt=project.pub_date)
+ Q(default_true=True, add_date__gt=project.pub_date),
).distinct()
diff --git a/readthedocs/projects/signals.py b/readthedocs/projects/signals.py
index 61251c3298f..983953a810e 100644
--- a/readthedocs/projects/signals.py
+++ b/readthedocs/projects/signals.py
@@ -1,15 +1,16 @@
# -*- coding: utf-8 -*-
-"""Project signals"""
+
+"""Project signals."""
import django.dispatch
-before_vcs = django.dispatch.Signal(providing_args=["version"])
-after_vcs = django.dispatch.Signal(providing_args=["version"])
+before_vcs = django.dispatch.Signal(providing_args=['version'])
+after_vcs = django.dispatch.Signal(providing_args=['version'])
-before_build = django.dispatch.Signal(providing_args=["version"])
-after_build = django.dispatch.Signal(providing_args=["version"])
+before_build = django.dispatch.Signal(providing_args=['version'])
+after_build = django.dispatch.Signal(providing_args=['version'])
-project_import = django.dispatch.Signal(providing_args=["project"])
+project_import = django.dispatch.Signal(providing_args=['project'])
-files_changed = django.dispatch.Signal(providing_args=["project", "files"])
+files_changed = django.dispatch.Signal(providing_args=['project', 'files'])
diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py
index 06ec7981ea2..23878e81afe 100644
--- a/readthedocs/projects/tasks.py
+++ b/readthedocs/projects/tasks.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""
Tasks related to projects.
@@ -98,9 +99,9 @@ def get_version(project=None, version_pk=None):
if version_pk:
version_data = api_v2.version(version_pk).get()
else:
- version_data = (api_v2
- .version(project.slug)
- .get(slug=LATEST)['objects'][0])
+ version_data = (
+ api_v2.version(project.slug).get(slug=LATEST)['objects'][0]
+ )
return APIVersion(**version_data)
def get_vcs_repo(self):
@@ -135,11 +136,13 @@ def sync_repo(self):
slug=self.version.slug,
identifier=self.version.identifier,
)
- log.info(LOG_TEMPLATE.format(
- project=self.project.slug,
- version=self.version.slug,
- msg=msg,
- ))
+ log.info(
+ LOG_TEMPLATE.format(
+ project=self.project.slug,
+ version=self.version.slug,
+ msg=msg,
+ )
+ )
version_repo = self.get_vcs_repo()
version_repo.update()
self.sync_versions(version_repo)
@@ -151,34 +154,28 @@ def sync_versions(self, version_repo):
"""
Update tags/branches hitting the API.
- It may trigger a new build to the stable version
- when hittig the ``sync_versions`` endpoint.
+ It may trigger a new build to the stable version when hittig the
+ ``sync_versions`` endpoint.
"""
version_post_data = {'repo': version_repo.repo_url}
if version_repo.supports_tags:
- version_post_data['tags'] = [
- {
- 'identifier': v.identifier,
- 'verbose_name': v.verbose_name,
- }
- for v in version_repo.tags
- ]
+ version_post_data['tags'] = [{
+ 'identifier': v.identifier,
+ 'verbose_name': v.verbose_name,
+ } for v in version_repo.tags]
if version_repo.supports_branches:
- version_post_data['branches'] = [
- {
- 'identifier': v.identifier,
- 'verbose_name': v.verbose_name,
- }
- for v in version_repo.branches
- ]
+ version_post_data['branches'] = [{
+ 'identifier': v.identifier,
+ 'verbose_name': v.verbose_name,
+ } for v in version_repo.branches]
self.validate_duplicate_reserved_versions(version_post_data)
try:
api_v2.project(self.project.pk).sync_versions.post(
- version_post_data
+ version_post_data,
)
except HttpClientError:
log.exception('Sync Versions Exception')
@@ -203,7 +200,7 @@ def validate_duplicate_reserved_versions(self, data):
for reserved_name in [STABLE_VERBOSE_NAME, LATEST_VERBOSE_NAME]:
if counter[reserved_name] > 1:
raise RepositoryError(
- RepositoryError.DUPLICATED_RESERVED_VERSIONS
+ RepositoryError.DUPLICATED_RESERVED_VERSIONS,
)
@@ -261,7 +258,6 @@ def run(self, version_pk): # pylint: disable=arguments-differ
'version': self.version.slug,
},
},
-
)
return False
@@ -275,8 +271,8 @@ def run(self, version_pk): # pylint: disable=arguments-differ
ProjectBuildsSkippedError,
YAMLParseError,
BuildTimeoutError,
- ProjectBuildsSkippedError
- )
+ ProjectBuildsSkippedError,
+ ),
)
def update_docs_task(self, project_id, *args, **kwargs):
step = UpdateDocsTaskStep(task=self)
@@ -298,12 +294,19 @@ class UpdateDocsTaskStep(SyncRepositoryMixin):
underlying task. Previously, we were using a custom ``celery.Task`` for
this, but this class is only instantiated once -- on startup. The effect
was that this instance shared state between workers.
-
"""
- def __init__(self, build_env=None, python_env=None, config=None,
- force=False, build=None, project=None,
- version=None, task=None):
+ def __init__(
+ self,
+ build_env=None,
+ python_env=None,
+ config=None,
+ force=False,
+ build=None,
+ project=None,
+ version=None,
+ task=None,
+ ):
self.build_env = build_env
self.python_env = python_env
self.build_force = force
@@ -322,8 +325,10 @@ def __init__(self, build_env=None, python_env=None, config=None,
self.setup_env = None
# pylint: disable=arguments-differ
- def run(self, pk, version_pk=None, build_pk=None, record=True,
- docker=None, force=False, **__):
+ def run(
+ self, pk, version_pk=None, build_pk=None, record=True, docker=None,
+ force=False, **__
+ ):
"""
Run a documentation sync n' build.
@@ -383,7 +388,7 @@ def run(self, pk, version_pk=None, build_pk=None, record=True,
self.setup_env.failure = BuildEnvironmentError(
BuildEnvironmentError.GENERIC_WITH_BUILD_ID.format(
build_id=build_pk,
- )
+ ),
)
self.setup_env.update_build(BUILD_STATE_FINISHED)
@@ -411,7 +416,7 @@ def run(self, pk, version_pk=None, build_pk=None, record=True,
self.build_env.failure = BuildEnvironmentError(
BuildEnvironmentError.GENERIC_WITH_BUILD_ID.format(
build_id=build_pk,
- )
+ ),
)
self.build_env.update_build(BUILD_STATE_FINISHED)
@@ -450,7 +455,7 @@ def run_setup(self, record=True):
raise YAMLParseError(
YAMLParseError.GENERIC_WITH_PARSE_EXCEPTION.format(
exception=str(e),
- )
+ ),
)
self.save_build_config()
@@ -458,13 +463,15 @@ def run_setup(self, record=True):
if self.setup_env.failure or self.config is None:
msg = 'Failing build because of setup failure: {}'.format(
- self.setup_env.failure
+ self.setup_env.failure,
+ )
+ log.info(
+ LOG_TEMPLATE.format(
+ project=self.project.slug,
+ version=self.version.slug,
+ msg=msg,
+ )
)
- log.info(LOG_TEMPLATE.format(
- project=self.project.slug,
- version=self.version.slug,
- msg=msg,
- ))
# Send notification to users only if the build didn't fail because
# of VersionLockedError: this exception occurs when a build is
@@ -519,11 +526,13 @@ def run_build(self, docker, record):
with self.build_env:
python_env_cls = Virtualenv
if self.config.conda is not None:
- log.info(LOG_TEMPLATE.format(
- project=self.project.slug,
- version=self.version.slug,
- msg='Using conda',
- ))
+ log.info(
+ LOG_TEMPLATE.format(
+ project=self.project.slug,
+ version=self.version.slug,
+ msg='Using conda',
+ )
+ )
python_env_cls = Conda
self.python_env = python_env_cls(
version=self.version,
@@ -578,12 +587,14 @@ def get_build(build_pk):
if build_pk:
build = api_v2.build(build_pk).get()
private_keys = [
- 'project', 'version', 'resource_uri', 'absolute_uri',
+ 'project',
+ 'version',
+ 'resource_uri',
+ 'absolute_uri',
]
return {
key: val
- for key, val in build.items()
- if key not in private_keys
+ for key, val in build.items() if key not in private_keys
}
def setup_vcs(self):
@@ -596,11 +607,13 @@ def setup_vcs(self):
"""
self.setup_env.update_build(state=BUILD_STATE_CLONING)
- log.info(LOG_TEMPLATE.format(
- project=self.project.slug,
- version=self.version.slug,
- msg='Updating docs from VCS',
- ))
+ log.info(
+ LOG_TEMPLATE.format(
+ project=self.project.slug,
+ version=self.version.slug,
+ msg='Updating docs from VCS',
+ )
+ )
try:
self.sync_repo()
except RepositoryError:
@@ -681,8 +694,14 @@ def save_build_config(self):
})
self.build['config'] = config
- def update_app_instances(self, html=False, localmedia=False, search=False,
- pdf=False, epub=False):
+ def update_app_instances(
+ self,
+ html=False,
+ localmedia=False,
+ search=False,
+ pdf=False,
+ epub=False,
+ ):
"""
Update application instances with build artifacts.
@@ -793,7 +812,7 @@ def build_docs_html(self):
type='app',
task=move_files,
args=[self.version.pk, socket.gethostname()],
- kwargs=dict(html=True)
+ kwargs=dict(html=True),
)
except socket.error:
log.exception('move_files task has failed on socket error.')
@@ -845,7 +864,7 @@ def build_docs_class(self, builder_class):
"""
builder = get_builder_class(builder_class)(
self.build_env,
- python_env=self.python_env
+ python_env=self.python_env,
)
success = builder.build()
builder.move()
@@ -862,8 +881,16 @@ def is_type_sphinx(self):
# Web tasks
@app.task(queue='web')
-def sync_files(project_pk, version_pk, hostname=None, html=False,
- localmedia=False, search=False, pdf=False, epub=False):
+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.
@@ -906,8 +933,15 @@ 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):
+def move_files(
+ version_pk,
+ hostname,
+ html=False,
+ localmedia=False,
+ search=False,
+ pdf=False,
+ epub=False,
+):
"""
Task to move built documentation to web servers.
@@ -930,7 +964,7 @@ def move_files(version_pk, hostname, html=False, localmedia=False,
project=version.project.slug,
version=version.slug,
msg='Moving files',
- )
+ ),
)
if html:
@@ -1007,13 +1041,16 @@ def update_search(version_pk, commit, delete_non_commit_files=True):
else:
log.debug(
'Unknown documentation type: %s',
- version.project.documentation_type
+ version.project.documentation_type,
)
return
log_msg = ' '.join([page['path'] for page in page_list])
- log.info("(Search Index) Sending Data: %s [%s]", version.project.slug,
- log_msg)
+ log.info(
+ '(Search Index) Sending Data: %s [%s]',
+ version.project.slug,
+ log_msg,
+ )
index_search_request(
version=version,
page_list=page_list,
@@ -1057,7 +1094,9 @@ def remove_orphan_symlinks():
"""
for symlink in [PublicSymlink, PrivateSymlink]:
for domain_path in [symlink.PROJECT_CNAME_ROOT, symlink.CNAME_ROOT]:
- valid_cnames = set(Domain.objects.all().values_list('domain', flat=True))
+ valid_cnames = set(
+ Domain.objects.all().values_list('domain', flat=True)
+ )
orphan_cnames = set(os.listdir(domain_path)) - valid_cnames
for cname in orphan_cnames:
orphan_domain_path = os.path.join(domain_path, cname)
@@ -1102,7 +1141,7 @@ def fileify(version_pk, commit):
'Imported File not being built because no commit '
'information'
),
- )
+ ),
)
return
@@ -1113,7 +1152,7 @@ def fileify(version_pk, commit):
project=version.project.slug,
version=version.slug,
msg='Creating ImportedFiles',
- )
+ ),
)
_manage_imported_files(version, path, commit)
else:
@@ -1122,7 +1161,7 @@ def fileify(version_pk, commit):
project=project.slug,
version=version.slug,
msg='No ImportedFile files',
- )
+ ),
)
@@ -1137,8 +1176,10 @@ def _manage_imported_files(version, path, commit):
changed_files = set()
for root, __, filenames in os.walk(path):
for filename in filenames:
- dirpath = os.path.join(root.replace(path, '').lstrip('/'),
- filename.lstrip('/'))
+ dirpath = os.path.join(
+ root.replace(path, '').lstrip('/'),
+ filename.lstrip('/'),
+ )
full_path = os.path.join(root, filename)
md5 = hashlib.md5(open(full_path, 'rb').read()).hexdigest()
try:
@@ -1158,16 +1199,22 @@ def _manage_imported_files(version, path, commit):
obj.commit = commit
obj.save()
# Delete ImportedFiles from previous versions
- ImportedFile.objects.filter(project=version.project,
- version=version
- ).exclude(commit=commit).delete()
+ ImportedFile.objects.filter(
+ project=version.project,
+ version=version,
+ ).exclude(commit=commit).delete()
changed_files = [
resolve_path(
- version.project, filename=file, version_slug=version.slug,
+ version.project,
+ filename=file,
+ version_slug=version.slug,
) for file in changed_files
]
- files_changed.send(sender=Project, project=version.project,
- files=changed_files)
+ files_changed.send(
+ sender=Project,
+ project=version.project,
+ files=changed_files,
+ )
@app.task(queue='web')
@@ -1177,7 +1224,8 @@ def send_notifications(version_pk, build_pk):
for hook in version.project.webhook_notifications.all():
webhook_notification(version, build, hook.url)
- for email in version.project.emailhook_notifications.all().values_list('email', flat=True):
+ for email in version.project.emailhook_notifications.all().values_list('email',
+ flat=True):
email_notification(version, build, email)
@@ -1194,7 +1242,7 @@ def email_notification(version, build, email):
project=version.project.slug,
version=version.slug,
msg='sending email to: %s' % email,
- )
+ ),
)
# We send only what we need from the Django model objects here to avoid
@@ -1221,9 +1269,12 @@ def email_notification(version, build, email):
}
if build.commit:
- title = _('Failed: {project[name]} ({commit})').format(commit=build.commit[:8], **context)
+ title = _('Failed: {project[name]} ({commit})'
+ ).format(commit=build.commit[:8], **context)
else:
- title = _('Failed: {project[name]} ({version[verbose_name]})').format(**context)
+ title = _('Failed: {project[name]} ({version[verbose_name]})').format(
+ **context
+ )
send_email(
email,
@@ -1258,7 +1309,7 @@ def webhook_notification(version, build, hook_url):
project=project.slug,
version='',
msg='sending notification to: %s' % hook_url,
- )
+ ),
)
try:
requests.post(hook_url, data=data)
@@ -1291,7 +1342,7 @@ def update_static_metadata(project_pk, path=None):
project=project.slug,
version='',
msg='Updating static metadata',
- )
+ ),
)
translations = [trans.language for trans in project.translations.all()]
languages = set(translations)
@@ -1314,7 +1365,7 @@ def update_static_metadata(project_pk, path=None):
project=project.slug,
version='',
msg='Cannot write to metadata.json: {}'.format(e),
- )
+ ),
)
@@ -1368,8 +1419,9 @@ def finish_inactive_builds():
"""
time_limit = int(DOCKER_LIMITS['time'] * 1.2)
delta = datetime.timedelta(seconds=time_limit)
- query = (~Q(state=BUILD_STATE_FINISHED) &
- Q(date__lte=timezone.now() - delta))
+ query = (
+ ~Q(state=BUILD_STATE_FINISHED) & Q(date__lte=timezone.now() - delta)
+ )
builds_finished = 0
builds = Build.objects.filter(query)[:50]
diff --git a/readthedocs/projects/templatetags/projects_tags.py b/readthedocs/projects/templatetags/projects_tags.py
index 34c97508523..a655487a05d 100644
--- a/readthedocs/projects/templatetags/projects_tags.py
+++ b/readthedocs/projects/templatetags/projects_tags.py
@@ -1,4 +1,5 @@
-"""Project template tags and filters"""
+# -*- coding: utf-8 -*-
+"""Project template tags and filters."""
from django import template
@@ -10,14 +11,15 @@
@register.filter
def sort_version_aware(versions):
- """Takes a list of versions objects and sort them using version schemes"""
+ """Takes a list of versions objects and sort them using version schemes."""
return sorted(
versions,
key=lambda version: comparable_version(version.verbose_name),
- reverse=True)
+ reverse=True,
+ )
@register.filter
def is_project_user(user, project):
- """Return if user is a member of project.users"""
+ """Return if user is a member of project.users."""
return user in project.users.all()
diff --git a/readthedocs/projects/urls/private.py b/readthedocs/projects/urls/private.py
index a5ca6388f47..82c29500cab 100644
--- a/readthedocs/projects/urls/private.py
+++ b/readthedocs/projects/urls/private.py
@@ -1,4 +1,5 @@
-"""Project URLs for authenticated users"""
+# -*- coding: utf-8 -*-
+"""Project URLs for authenticated users."""
from django.conf.urls import url
@@ -25,159 +26,257 @@
urlpatterns = [
- url(r'^$',
+ url(
+ r'^$',
ProjectDashboard.as_view(),
- name='projects_dashboard'),
+ name='projects_dashboard',
+ ),
- url(r'^import/$',
+ url(
+ r'^import/$',
ImportView.as_view(wizard_class=ImportWizardView),
{'wizard': ImportWizardView},
- name='projects_import'),
+ name='projects_import',
+ ),
- url(r'^import/manual/$',
+ url(
+ r'^import/manual/$',
ImportWizardView.as_view(),
- name='projects_import_manual'),
+ name='projects_import_manual',
+ ),
- url(r'^import/manual/demo/$',
+ url(
+ r'^import/manual/demo/$',
ImportDemoView.as_view(),
- name='projects_import_demo'),
+ name='projects_import_demo',
+ ),
- url(r'^(?P[-\w]+)/$',
+ url(
+ r'^(?P[-\w]+)/$',
private.project_manage,
- name='projects_manage'),
+ name='projects_manage',
+ ),
- url(r'^(?P[-\w]+)/edit/$',
+ url(
+ r'^(?P[-\w]+)/edit/$',
ProjectUpdate.as_view(),
- name='projects_edit'),
+ name='projects_edit',
+ ),
- url(r'^(?P[-\w]+)/advanced/$',
+ url(
+ r'^(?P[-\w]+)/advanced/$',
ProjectAdvancedUpdate.as_view(),
- name='projects_advanced'),
+ name='projects_advanced',
+ ),
- url(r'^(?P[-\w]+)/version/(?P[^/]+)/delete_html/$',
+ url(
+ r'^(?P[-\w]+)/version/(?P[^/]+)/delete_html/$',
private.project_version_delete_html,
- name='project_version_delete_html'),
+ name='project_version_delete_html',
+ ),
- url(r'^(?P[-\w]+)/version/(?P[^/]+)/$',
+ url(
+ r'^(?P[-\w]+)/version/(?P[^/]+)/$',
private.project_version_detail,
- name='project_version_detail'),
+ name='project_version_detail',
+ ),
- url(r'^(?P[-\w]+)/versions/$',
+ url(
+ r'^(?P[-\w]+)/versions/$',
private.project_versions,
- name='projects_versions'),
+ name='projects_versions',
+ ),
- url(r'^(?P[-\w]+)/delete/$',
+ url(
+ r'^(?P[-\w]+)/delete/$',
private.project_delete,
- name='projects_delete'),
+ name='projects_delete',
+ ),
- url(r'^(?P[-\w]+)/users/$',
+ url(
+ r'^(?P[-\w]+)/users/$',
private.project_users,
- name='projects_users'),
+ name='projects_users',
+ ),
- url(r'^(?P[-\w]+)/users/delete/$',
+ url(
+ r'^(?P[-\w]+)/users/delete/$',
private.project_users_delete,
- name='projects_users_delete'),
+ name='projects_users_delete',
+ ),
- url(r'^(?P[-\w]+)/notifications/$',
+ url(
+ r'^(?P[-\w]+)/notifications/$',
private.project_notifications,
- name='projects_notifications'),
+ name='projects_notifications',
+ ),
- url(r'^(?P[-\w]+)/notifications/delete/$',
+ url(
+ r'^(?P[-\w]+)/notifications/delete/$',
private.project_notifications_delete,
- name='projects_notification_delete'),
+ name='projects_notification_delete',
+ ),
- url(r'^(?P[-\w]+)/translations/$',
+ url(
+ r'^(?P[-\w]+)/translations/$',
private.project_translations,
- name='projects_translations'),
+ name='projects_translations',
+ ),
- url(r'^(?P[-\w]+)/translations/delete/(?P[-\w]+)/$', # noqa
+ url(
+ r'^(?P[-\w]+)/translations/delete/(?P[-\w]+)/$', # noqa
private.project_translations_delete,
- name='projects_translations_delete'),
+ name='projects_translations_delete',
+ ),
- url(r'^(?P[-\w]+)/redirects/$',
+ url(
+ r'^(?P[-\w]+)/redirects/$',
private.project_redirects,
- name='projects_redirects'),
+ name='projects_redirects',
+ ),
- url(r'^(?P[-\w]+)/redirects/delete/$',
+ url(
+ r'^(?P[-\w]+)/redirects/delete/$',
private.project_redirects_delete,
- name='projects_redirects_delete'),
+ name='projects_redirects_delete',
+ ),
- url(r'^(?P[-\w]+)/advertising/$',
+ url(
+ r'^(?P[-\w]+)/advertising/$',
ProjectAdvertisingUpdate.as_view(),
- name='projects_advertising'),
+ name='projects_advertising',
+ ),
]
domain_urls = [
- url(r'^(?P[-\w]+)/domains/$',
+ url(
+ r'^(?P[-\w]+)/domains/$',
DomainList.as_view(),
- name='projects_domains'),
- url(r'^(?P[-\w]+)/domains/create/$',
+ name='projects_domains',
+ ),
+ url(
+ r'^(?P[-\w]+)/domains/create/$',
DomainCreate.as_view(),
- name='projects_domains_create'),
- url(r'^(?P[-\w]+)/domains/(?P[-\w]+)/edit/$',
+ name='projects_domains_create',
+ ),
+ url(
+ r'^(?P[-\w]+)/domains/(?P[-\w]+)/edit/$',
DomainUpdate.as_view(),
- name='projects_domains_edit'),
- url(r'^(?P[-\w]+)/domains/(?P[-\w]+)/delete/$',
+ name='projects_domains_edit',
+ ),
+ url(
+ r'^(?P[-\w]+)/domains/(?P[-\w]+)/delete/$',
DomainDelete.as_view(),
- name='projects_domains_delete'),
+ name='projects_domains_delete',
+ ),
]
urlpatterns += domain_urls
integration_urls = [
- url(r'^(?P{project_slug})/integrations/$'.format(**pattern_opts),
+ url(
+ r'^(?P{project_slug})/integrations/$'.format(
+ **pattern_opts
+ ),
IntegrationList.as_view(),
- name='projects_integrations'),
- url(r'^(?P{project_slug})/integrations/sync/$'.format(**pattern_opts),
+ name='projects_integrations',
+ ),
+ url(
+ r'^(?P{project_slug})/integrations/sync/$'.format(
+ **pattern_opts
+ ),
IntegrationWebhookSync.as_view(),
- name='projects_integrations_webhooks_sync'),
- url((r'^(?P{project_slug})/integrations/create/$'
- .format(**pattern_opts)),
+ name='projects_integrations_webhooks_sync',
+ ),
+ url(
+ (
+ r'^(?P{project_slug})/integrations/create/$'.format(
+ **pattern_opts
+ )
+ ),
IntegrationCreate.as_view(),
- name='projects_integrations_create'),
- url((r'^(?P{project_slug})/'
- r'integrations/(?P{integer_pk})/$'
- .format(**pattern_opts)),
+ name='projects_integrations_create',
+ ),
+ url(
+ (
+ r'^(?P{project_slug})/'
+ r'integrations/(?P{integer_pk})/$'.format(
+ **pattern_opts
+ )
+ ),
IntegrationDetail.as_view(),
- name='projects_integrations_detail'),
- url((r'^(?P{project_slug})/'
- r'integrations/(?P{integer_pk})/'
- r'exchange/(?P[-\w]+)/$'
- .format(**pattern_opts)),
+ name='projects_integrations_detail',
+ ),
+ url(
+ (
+ r'^(?P{project_slug})/'
+ r'integrations/(?P{integer_pk})/'
+ r'exchange/(?P[-\w]+)/$'.format(**pattern_opts)
+ ),
IntegrationExchangeDetail.as_view(),
- name='projects_integrations_exchanges_detail'),
- url((r'^(?P{project_slug})/'
- r'integrations/(?P{integer_pk})/sync/$'
- .format(**pattern_opts)),
+ name='projects_integrations_exchanges_detail',
+ ),
+ url(
+ (
+ r'^(?P{project_slug})/'
+ r'integrations/(?P{integer_pk})/sync/$'.format(
+ **pattern_opts
+ )
+ ),
IntegrationWebhookSync.as_view(),
- name='projects_integrations_webhooks_sync'),
- url((r'^(?P{project_slug})/'
- r'integrations/(?P{integer_pk})/delete/$'
- .format(**pattern_opts)),
+ name='projects_integrations_webhooks_sync',
+ ),
+ url(
+ (
+ r'^(?P{project_slug})/'
+ r'integrations/(?P{integer_pk})/delete/$'.format(
+ **pattern_opts
+ )
+ ),
IntegrationDelete.as_view(),
- name='projects_integrations_delete'),
+ name='projects_integrations_delete',
+ ),
]
urlpatterns += integration_urls
subproject_urls = [
- url(r'^(?P{project_slug})/subprojects/$'.format(**pattern_opts),
+ url(
+ r'^(?P{project_slug})/subprojects/$'.format(
+ **pattern_opts
+ ),
private.ProjectRelationshipList.as_view(),
- name='projects_subprojects'),
- url((r'^(?P{project_slug})/subprojects/create/$'
- .format(**pattern_opts)),
+ name='projects_subprojects',
+ ),
+ url(
+ (
+ r'^(?P{project_slug})/subprojects/create/$'.format(
+ **pattern_opts
+ )
+ ),
private.ProjectRelationshipCreate.as_view(),
- name='projects_subprojects_create'),
- url((r'^(?P{project_slug})/'
- r'subprojects/(?P{project_slug})/edit/$'
- .format(**pattern_opts)),
+ name='projects_subprojects_create',
+ ),
+ url(
+ (
+ r'^(?P{project_slug})/'
+ r'subprojects/(?P{project_slug})/edit/$'.format(
+ **pattern_opts
+ )
+ ),
private.ProjectRelationshipUpdate.as_view(),
- name='projects_subprojects_update'),
- url((r'^(?P{project_slug})/'
- r'subprojects/(?P{project_slug})/delete/$'
- .format(**pattern_opts)),
+ name='projects_subprojects_update',
+ ),
+ url(
+ (
+ r'^(?P{project_slug})/'
+ r'subprojects/(?P{project_slug})/delete/$'.format(
+ **pattern_opts
+ )
+ ),
private.ProjectRelationshipDelete.as_view(),
- name='projects_subprojects_delete'),
+ name='projects_subprojects_delete',
+ ),
]
urlpatterns += subproject_urls
diff --git a/readthedocs/projects/urls/public.py b/readthedocs/projects/urls/public.py
index be0d0fc179e..7b17b476db9 100644
--- a/readthedocs/projects/urls/public.py
+++ b/readthedocs/projects/urls/public.py
@@ -1,4 +1,5 @@
-"""Project URLS for public users"""
+# -*- coding: utf-8 -*-
+"""Project URLS for public users."""
from django.conf.urls import url
@@ -9,51 +10,70 @@
urlpatterns = [
- url(r'^$',
+ url(
+ r'^$',
ProjectIndex.as_view(),
- name='projects_list'),
-
- url(r'^(?P{project_slug})/$'.format(**pattern_opts),
+ name='projects_list',
+ ),
+ url(
+ r'^(?P{project_slug})/$'.format(**pattern_opts),
ProjectDetailView.as_view(),
- name='projects_detail'),
-
- url(r'^(?P{project_slug})/downloads/$'.format(**pattern_opts),
+ name='projects_detail',
+ ),
+ url(
+ r'^(?P{project_slug})/downloads/$'.format(**pattern_opts),
public.project_downloads,
- name='project_downloads'),
-
- url((r'^(?P{project_slug})/downloads/(?P[-\w]+)/'
- r'(?P{version_slug})/$'.format(**pattern_opts)),
+ name='project_downloads',
+ ),
+ url(
+ (
+ r'^(?P{project_slug})/downloads/(?P[-\w]+)/'
+ r'(?P{version_slug})/$'.format(**pattern_opts)
+ ),
public.project_download_media,
- name='project_download_media'),
-
- url(r'^(?P{project_slug})/badge/$'.format(**pattern_opts),
+ name='project_download_media',
+ ),
+ url(
+ r'^(?P{project_slug})/badge/$'.format(**pattern_opts),
public.project_badge,
- name='project_badge'),
-
- url((r'^(?P{project_slug})/tools/embed/$'
- .format(**pattern_opts)),
+ name='project_badge',
+ ),
+ url(
+ (
+ r'^(?P{project_slug})/tools/embed/$'.format(
+ **pattern_opts
+ )
+ ),
public.project_embed,
- name='project_embed'),
-
- url(r'^(?P{project_slug})/search/$'.format(**pattern_opts),
+ name='project_embed',
+ ),
+ url(
+ r'^(?P{project_slug})/search/$'.format(**pattern_opts),
public.elastic_project_search,
- name='elastic_project_search'),
-
- url((r'^(?P{project_slug})/builds/(?P\d+)/$'
- .format(**pattern_opts)),
+ name='elastic_project_search',
+ ),
+ url(
+ (
+ r'^(?P{project_slug})/builds/(?P\d+)/$'.format(
+ **pattern_opts
+ )
+ ),
build_views.BuildDetail.as_view(),
- name='builds_detail'),
-
- url((r'^(?P{project_slug})/builds/$'
- .format(**pattern_opts)),
+ name='builds_detail',
+ ),
+ url(
+ (r'^(?P{project_slug})/builds/$'.format(**pattern_opts)),
build_views.BuildList.as_view(),
- name='builds_project_list'),
-
- url(r'^(?P{project_slug})/versions/$'.format(**pattern_opts),
+ name='builds_project_list',
+ ),
+ url(
+ r'^(?P{project_slug})/versions/$'.format(**pattern_opts),
public.project_versions,
- name='project_version_list'),
-
- url(r'^tags/(?P[-\w]+)/$',
+ name='project_version_list',
+ ),
+ url(
+ r'^tags/(?P[-\w]+)/$',
ProjectIndex.as_view(),
- name='projects_tag_detail'),
+ name='projects_tag_detail',
+ ),
]
diff --git a/readthedocs/projects/utils.py b/readthedocs/projects/utils.py
index cdacd26c276..f83a3906a44 100644
--- a/readthedocs/projects/utils.py
+++ b/readthedocs/projects/utils.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Utility functions used by projects."""
import logging
diff --git a/readthedocs/projects/validators.py b/readthedocs/projects/validators.py
index d0f78d0ba02..8fe3bf7f530 100644
--- a/readthedocs/projects/validators.py
+++ b/readthedocs/projects/validators.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Validators for projects app."""
import re
@@ -97,7 +98,7 @@ def __call__(self, value):
class SubmoduleURLValidator(RepositoryURLValidator):
"""
- A URL validator for repository submodules
+ A URL validator for repository submodules.
If a repository has a relative submodule, the URL path is effectively the
supermodule's remote ``origin`` URL with the relative path applied.
diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py
index d1ef1751a18..09f7592d751 100644
--- a/readthedocs/projects/version_handling.py
+++ b/readthedocs/projects/version_handling.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Project version handling."""
import unicodedata
@@ -88,7 +89,8 @@ def sort_versions(version_list):
versions,
key=lambda version_info: version_info[1],
reverse=True,
- ))
+ ),
+ )
def highest_version(version_list):
diff --git a/readthedocs/projects/views/base.py b/readthedocs/projects/views/base.py
index fa85dd440f3..a29d65f663d 100644
--- a/readthedocs/projects/views/base.py
+++ b/readthedocs/projects/views/base.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Mix-in classes for project views."""
import logging
from datetime import timedelta
@@ -71,7 +72,8 @@ def get_project(self):
return None
return get_object_or_404(
Project.objects.for_admin_user(user=self.request.user),
- slug=self.kwargs[self.project_url_field])
+ slug=self.kwargs[self.project_url_field],
+ )
def get_context_data(self, **kwargs):
"""Add project to context data."""
diff --git a/readthedocs/projects/views/mixins.py b/readthedocs/projects/views/mixins.py
index ed04d0377fb..5ce119e4ea4 100644
--- a/readthedocs/projects/views/mixins.py
+++ b/readthedocs/projects/views/mixins.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Mixin classes for project views."""
from django.shortcuts import get_object_or_404
@@ -30,7 +31,7 @@ def get_project(self):
return None
return get_object_or_404(
self.get_project_queryset(),
- slug=self.kwargs[self.project_lookup_url_kwarg]
+ slug=self.kwargs[self.project_lookup_url_kwarg],
)
def get_queryset(self):
diff --git a/readthedocs/projects/views/public.py b/readthedocs/projects/views/public.py
index 87a76947d80..3a15e756c46 100644
--- a/readthedocs/projects/views/public.py
+++ b/readthedocs/projects/views/public.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+
"""Public project views."""
import json
@@ -13,7 +14,7 @@
from django.contrib import messages
from django.contrib.auth.models import User
from django.core.cache import cache
-from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views.decorators.cache import never_cache
@@ -23,7 +24,7 @@
from readthedocs.builds.constants import LATEST
from readthedocs.builds.models import Version
from readthedocs.builds.views import BuildTriggerMixin
-from readthedocs.projects.models import ImportedFile, Project
+from readthedocs.projects.models import Project
from readthedocs.search.indexes import PageIndex
from readthedocs.search.views import LOG_TEMPLATE
@@ -52,7 +53,9 @@ def get_queryset(self):
if self.kwargs.get('username'):
self.user = get_object_or_404(
- User, username=self.kwargs.get('username'))
+ User,
+ username=self.kwargs.get('username'),
+ )
queryset = queryset.filter(user=self.user)
else:
self.user = None
@@ -84,7 +87,9 @@ def get_context_data(self, **kwargs):
project = self.get_object()
context['versions'] = Version.objects.public(
- user=self.request.user, project=project)
+ user=self.request.user,
+ project=project,
+ )
protocol = 'http'
if self.request.is_secure():
@@ -110,8 +115,9 @@ def get_context_data(self, **kwargs):
def project_badge(request, project_slug):
"""Return a sweet badge for the project."""
style = request.GET.get('style', 'flat')
- if style not in ("flat", "plastic", "flat-square", "for-the-badge", "social"):
- style = "flat"
+ if style not in ('flat', 'plastic', 'flat-square', 'for-the-badge',
+ 'social'):
+ style = 'flat'
# Get the local path to the badge files
badge_path = os.path.join(
@@ -127,10 +133,13 @@ def project_badge(request, project_slug):
file_path = badge_path % 'unknown'
version = Version.objects.public(request.user).filter(
- project__slug=project_slug, slug=version_slug).first()
+ project__slug=project_slug,
+ slug=version_slug,
+ ).first()
if version:
- last_build = version.builds.filter(type='html', state='finished').order_by('-date').first()
+ last_build = version.builds.filter(type='html',
+ state='finished').order_by('-date').first()
if last_build:
if last_build.success:
file_path = badge_path % 'passing'
@@ -144,14 +153,18 @@ def project_badge(request, project_slug):
content_type='image/svg+xml',
)
except (IOError, OSError):
- log.exception('Failed to read local filesystem while serving a docs badge')
+ log.exception(
+ 'Failed to read local filesystem while serving a docs badge'
+ )
return HttpResponse(status=503)
def project_downloads(request, project_slug):
"""A detail view for a project with various dataz."""
project = get_object_or_404(
- Project.objects.protected(request.user), slug=project_slug)
+ Project.objects.protected(request.user),
+ slug=project_slug,
+ )
versions = Version.objects.public(user=request.user, project=project)
version_data = OrderedDict()
for version in versions:
@@ -189,15 +202,21 @@ def project_download_media(request, project_slug, type_, version_slug):
privacy_level = getattr(settings, 'DEFAULT_PRIVACY_LEVEL', 'public')
if privacy_level == 'public' or settings.DEBUG:
path = os.path.join(
- settings.MEDIA_URL, type_, project_slug, version_slug,
- '{}.{}'.format(project_slug, type_.replace('htmlzip', 'zip')))
+ settings.MEDIA_URL,
+ type_,
+ project_slug,
+ version_slug,
+ '{}.{}'.format(project_slug, type_.replace('htmlzip', 'zip')),
+ )
return HttpResponseRedirect(path)
# Get relative media path
path = (
version.project.get_production_media_path(
- type_=type_, version_slug=version_slug)
- .replace(settings.PRODUCTION_ROOT, '/prod_artifacts'))
+ type_=type_,
+ version_slug=version_slug,
+ ).replace(settings.PRODUCTION_ROOT, '/prod_artifacts')
+ )
content_type, encoding = mimetypes.guess_type(path)
content_type = content_type or 'application/octet-stream'
response = HttpResponse(content_type=content_type)
@@ -206,7 +225,10 @@ def project_download_media(request, project_slug, type_, version_slug):
response['X-Accel-Redirect'] = path
# Include version in filename; this fixes a long-standing bug
filename = '{}-{}.{}'.format(
- project_slug, version_slug, path.split('.')[-1])
+ project_slug,
+ version_slug,
+ path.split('.')[-1],
+ )
response['Content-Disposition'] = 'filename=%s' % filename
return response
@@ -229,7 +251,8 @@ def elastic_project_search(request, project_slug):
version=version_slug or '',
language='',
msg=query or '',
- ))
+ ),
+ )
if query:
@@ -241,22 +264,22 @@ def elastic_project_search(request, project_slug):
{'match': {'title': {'query': query, 'boost': 10}}},
{'match': {'headers': {'query': query, 'boost': 5}}},
{'match': {'content': {'query': query}}},
- ]
- }
+ ],
+ },
},
'highlight': {
'fields': {
'title': {},
'headers': {},
'content': {},
- }
+ },
},
'fields': ['title', 'project', 'version', 'path'],
'filter': {
'and': [
{'term': {'project': project_slug}},
{'term': {'version': version_slug}},
- ]
+ ],
},
'size': 50, # TODO: Support pagination.
}
@@ -293,10 +316,15 @@ def project_versions(request, project_slug):
Shows the available versions and lets the user choose which ones to build.
"""
project = get_object_or_404(
- Project.objects.protected(request.user), slug=project_slug)
+ Project.objects.protected(request.user),
+ slug=project_slug,
+ )
versions = Version.objects.public(
- user=request.user, project=project, only_active=False)
+ user=request.user,
+ project=project,
+ only_active=False,
+ )
active_versions = versions.filter(active=True)
inactive_versions = versions.filter(active=False)
@@ -322,7 +350,9 @@ def project_versions(request, project_slug):
def project_analytics(request, project_slug):
"""Have a analytics API placeholder."""
project = get_object_or_404(
- Project.objects.protected(request.user), slug=project_slug)
+ Project.objects.protected(request.user),
+ slug=project_slug,
+ )
analytics_cache = cache.get('analytics:%s' % project_slug)
if analytics_cache:
analytics = json.loads(analytics_cache)
@@ -330,8 +360,10 @@ def project_analytics(request, project_slug):
try:
resp = requests.get(
'{host}/api/v1/index/1/heatmap/'.format(
- host=settings.GROK_API_HOST),
- params={'project': project.slug, 'days': 7, 'compare': True})
+ host=settings.GROK_API_HOST,
+ ),
+ params={'project': project.slug, 'days': 7, 'compare': True},
+ )
analytics = resp.json()
cache.set('analytics:%s' % project_slug, resp.content, 1800)
except requests.exceptions.RequestException:
@@ -342,12 +374,18 @@ def project_analytics(request, project_slug):
reversed(
sorted(
list(analytics['page'].items()),
- key=operator.itemgetter(1))))
+ key=operator.itemgetter(1),
+ ),
+ ),
+ )
version_list = list(
reversed(
sorted(
list(analytics['version'].items()),
- key=operator.itemgetter(1))))
+ key=operator.itemgetter(1),
+ ),
+ ),
+ )
else:
page_list = []
version_list = []
@@ -373,9 +411,12 @@ def project_analytics(request, project_slug):
def project_embed(request, project_slug):
"""Have a content API placeholder."""
project = get_object_or_404(
- Project.objects.protected(request.user), slug=project_slug)
+ Project.objects.protected(request.user),
+ slug=project_slug,
+ )
version = project.versions.get(slug=LATEST)
- files = version.imported_files.filter(name__endswith='.html').order_by('path')
+ files = version.imported_files.filter(name__endswith='.html'
+ ).order_by('path')
return render(
request,
diff --git a/readthedocs/redirects/admin.py b/readthedocs/redirects/admin.py
index 2091ab87acc..8b97822657f 100644
--- a/readthedocs/redirects/admin.py
+++ b/readthedocs/redirects/admin.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Django admin configuration for the redirects app."""
from django.contrib import admin
diff --git a/readthedocs/redirects/managers.py b/readthedocs/redirects/managers.py
index af18ebae64f..c0f48f83e4a 100644
--- a/readthedocs/redirects/managers.py
+++ b/readthedocs/redirects/managers.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Manager and queryset for the redirects app."""
from django.db.models import Manager
@@ -5,10 +6,14 @@
class RedirectQuerySet(QuerySet):
+
def get_redirect_path(self, path, language=None, version_slug=None):
for redirect in self.select_related('project'):
new_path = redirect.get_redirect_path(
- path=path, language=language, version_slug=version_slug)
+ path=path,
+ language=language,
+ version_slug=version_slug,
+ )
if new_path:
return new_path
diff --git a/readthedocs/redirects/models.py b/readthedocs/redirects/models.py
index a4e9b72f731..478b746304b 100644
--- a/readthedocs/redirects/models.py
+++ b/readthedocs/redirects/models.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Django models for the redirects app."""
import logging
@@ -16,7 +17,6 @@
log = logging.getLogger(__name__)
-
HTTP_STATUS_CHOICES = (
(301, _('301 - Permanent Redirect')),
(302, _('302 - Temporary Redirect')),
@@ -40,12 +40,14 @@
# make sense for "Prefix Redirects" since the from URL is considered after the
# ``/$lang/$version/`` part. Also, there is a feature for the "Exact
# Redirects" that should be mentioned here: the usage of ``$rest``
-from_url_helptext = _('Absolute path, excluding the domain. '
- 'Example: /docs/ or /install.html'
- )
-to_url_helptext = _('Absolute or relative URL. Example: '
- '/tutorial/install.html'
- )
+from_url_helptext = _(
+ 'Absolute path, excluding the domain. '
+ 'Example: /docs/ or /install.html',
+)
+to_url_helptext = _(
+ 'Absolute or relative URL. Example: '
+ '/tutorial/install.html',
+)
redirect_type_helptext = _('The type of redirect you wish to use.')
@@ -54,21 +56,40 @@ class Redirect(models.Model):
"""A HTTP redirect associated with a Project."""
- project = models.ForeignKey(Project, verbose_name=_('Project'),
- related_name='redirects')
-
- redirect_type = models.CharField(_('Redirect Type'), max_length=255, choices=TYPE_CHOICES,
- help_text=redirect_type_helptext)
-
- from_url = models.CharField(_('From URL'), max_length=255,
- db_index=True, help_text=from_url_helptext, blank=True)
-
- to_url = models.CharField(_('To URL'), max_length=255,
- db_index=True, help_text=to_url_helptext, blank=True)
-
- http_status = models.SmallIntegerField(_('HTTP Status'),
- choices=HTTP_STATUS_CHOICES,
- default=301)
+ project = models.ForeignKey(
+ Project,
+ verbose_name=_('Project'),
+ related_name='redirects',
+ )
+
+ redirect_type = models.CharField(
+ _('Redirect Type'),
+ max_length=255,
+ choices=TYPE_CHOICES,
+ help_text=redirect_type_helptext,
+ )
+
+ from_url = models.CharField(
+ _('From URL'),
+ max_length=255,
+ db_index=True,
+ help_text=from_url_helptext,
+ blank=True,
+ )
+
+ to_url = models.CharField(
+ _('To URL'),
+ max_length=255,
+ db_index=True,
+ help_text=to_url_helptext,
+ blank=True,
+ )
+
+ http_status = models.SmallIntegerField(
+ _('HTTP Status'),
+ choices=HTTP_STATUS_CHOICES,
+ default=301,
+ )
status = models.BooleanField(choices=STATUS_CHOICES, default=True)
create_dt = models.DateTimeField(auto_now_add=True)
@@ -86,10 +107,12 @@ def __str__(self):
if self.redirect_type in ['prefix', 'page', 'exact']:
return redirect_text.format(
type=self.get_redirect_type_display(),
- from_to_url=self.get_from_to_url_display()
+ from_to_url=self.get_from_to_url_display(),
)
- return ugettext('Redirect: {}'.format(
- self.get_redirect_type_display())
+ return ugettext(
+ 'Redirect: {}'.format(
+ self.get_redirect_type_display(),
+ ),
)
def get_from_to_url_display(self):
@@ -99,11 +122,11 @@ def get_from_to_url_display(self):
if self.redirect_type == 'prefix':
to_url = '/{lang}/{version}/'.format(
lang=self.project.language,
- version=self.project.default_version
+ version=self.project.default_version,
)
return '{from_url} -> {to_url}'.format(
from_url=from_url,
- to_url=to_url
+ to_url=to_url,
)
return ''
@@ -119,13 +142,19 @@ def get_full_path(self, filename, language=None, version_slug=None):
return filename
return resolve_path(
- project=self.project, language=language,
- version_slug=version_slug, filename=filename
+ project=self.project,
+ language=language,
+ version_slug=version_slug,
+ filename=filename,
)
def get_redirect_path(self, path, language=None, version_slug=None):
- method = getattr(self, 'redirect_{type}'.format(
- type=self.redirect_type))
+ method = getattr(
+ self,
+ 'redirect_{type}'.format(
+ type=self.redirect_type,
+ ),
+ )
return method(path, language=language, version_slug=version_slug)
def redirect_prefix(self, path, language=None, version_slug=None):
@@ -135,7 +164,8 @@ def redirect_prefix(self, path, language=None, version_slug=None):
to = self.get_full_path(
filename=cut_path,
language=language,
- version_slug=version_slug)
+ version_slug=version_slug,
+ )
return to
def redirect_page(self, path, language=None, version_slug=None):
@@ -144,7 +174,8 @@ def redirect_page(self, path, language=None, version_slug=None):
to = self.get_full_path(
filename=self.to_url.lstrip('/'),
language=language,
- version_slug=version_slug)
+ version_slug=version_slug,
+ )
return to
def redirect_exact(self, path, language=None, version_slug=None):
@@ -171,7 +202,8 @@ def redirect_sphinx_html(self, path, language=None, version_slug=None):
return self.get_full_path(
filename=to,
language=language,
- version_slug=version_slug)
+ version_slug=version_slug,
+ )
def redirect_sphinx_htmldir(self, path, language=None, version_slug=None):
if path.endswith('.html'):
@@ -181,4 +213,5 @@ def redirect_sphinx_htmldir(self, path, language=None, version_slug=None):
return self.get_full_path(
filename=to,
language=language,
- version_slug=version_slug)
+ version_slug=version_slug,
+ )
diff --git a/readthedocs/redirects/utils.py b/readthedocs/redirects/utils.py
index 0817d99b1bd..e6446e136e5 100644
--- a/readthedocs/redirects/utils.py
+++ b/readthedocs/redirects/utils.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""
Redirection view support.
@@ -37,7 +38,8 @@ def project_and_path_from_request(request, path):
# docs prefix.
match = re.match(
r'^/docs/(?P[^/]+)(?P/.*)$',
- path)
+ path,
+ )
if match:
project_slug = match.groupdict()['project_slug']
path = match.groupdict()['path']
@@ -56,7 +58,8 @@ def project_and_path_from_request(request, path):
def language_and_version_from_path(path):
match = re.match(
r'^/(?P%s)/(?P[^/]+)(?P/.*)$' % LANGUAGES_REGEX,
- path)
+ path,
+ )
if match:
language = match.groupdict()['language']
version_slug = match.groupdict()['version_slug']
@@ -76,7 +79,10 @@ def get_redirect_response(request, path):
language, version_slug, path = language_and_version_from_path(path)
new_path = project.redirects.get_redirect_path(
- path=path, language=language, version_slug=version_slug)
+ path=path,
+ language=language,
+ version_slug=version_slug,
+ )
if new_path is None:
return None
diff --git a/readthedocs/restapi/client.py b/readthedocs/restapi/client.py
index a2f9943c6da..53428b707fd 100644
--- a/readthedocs/restapi/client.py
+++ b/readthedocs/restapi/client.py
@@ -22,7 +22,7 @@
class DrfJsonSerializer(serialize.JsonSerializer):
- """Additional serialization help from the DRF renderer"""
+ """Additional serialization help from the DRF renderer."""
key = 'json-drf'
diff --git a/readthedocs/restapi/permissions.py b/readthedocs/restapi/permissions.py
index 4645cca618d..e08087824e7 100644
--- a/readthedocs/restapi/permissions.py
+++ b/readthedocs/restapi/permissions.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Defines access permissions for the API."""
from rest_framework import permissions
@@ -54,7 +55,10 @@ def has_permission(self, request, view):
def has_object_permission(self, request, view, obj):
has_perm = super().has_object_permission(
- request, view, obj)
+ request,
+ view,
+ obj,
+ )
return has_perm or (request.user and request.user.is_staff)
diff --git a/readthedocs/restapi/serializers.py b/readthedocs/restapi/serializers.py
index 4529bcdafbd..bded753b453 100644
--- a/readthedocs/restapi/serializers.py
+++ b/readthedocs/restapi/serializers.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""Defines serializers for each of our models."""
from allauth.socialaccount.models import SocialAccount
@@ -15,9 +16,15 @@ class Meta:
model = Project
fields = (
'id',
- 'name', 'slug', 'description', 'language',
- 'programming_language', 'repo', 'repo_type',
- 'default_version', 'default_branch',
+ 'name',
+ 'slug',
+ 'description',
+ 'language',
+ 'programming_language',
+ 'repo',
+ 'repo_type',
+ 'default_version',
+ 'default_branch',
'documentation_type',
'users',
'canonical_url',
@@ -70,9 +77,12 @@ class Meta:
model = Version
fields = (
'id',
- 'project', 'slug',
- 'identifier', 'verbose_name',
- 'active', 'built',
+ 'project',
+ 'slug',
+ 'identifier',
+ 'verbose_name',
+ 'active',
+ 'built',
'downloads',
'type',
)
@@ -186,7 +196,6 @@ class Meta:
def get_username(self, obj):
return (
- obj.extra_data.get('username') or
- obj.extra_data.get('login')
+ obj.extra_data.get('username') or obj.extra_data.get('login')
# FIXME: which one is GitLab?
)
diff --git a/readthedocs/restapi/signals.py b/readthedocs/restapi/signals.py
index 6968cb5342e..e43a701346e 100644
--- a/readthedocs/restapi/signals.py
+++ b/readthedocs/restapi/signals.py
@@ -1,8 +1,9 @@
+# -*- coding: utf-8 -*-
"""We define custom Django signals to trigger when a footer is rendered."""
import django.dispatch
footer_response = django.dispatch.Signal(
- providing_args=["request", "context", "response_data"]
+ providing_args=['request', 'context', 'response_data'],
)
diff --git a/readthedocs/restapi/urls.py b/readthedocs/restapi/urls.py
index 50767355fee..7a0d5c54e2d 100644
--- a/readthedocs/restapi/urls.py
+++ b/readthedocs/restapi/urls.py
@@ -95,26 +95,30 @@
integration_urls = [
url(
- r'webhook/github/(?P{project_slug})/$'
- .format(**pattern_opts),
+ r'webhook/github/(?P{project_slug})/$'.format(
+ **pattern_opts
+ ),
integrations.GitHubWebhookView.as_view(),
name='api_webhook_github',
),
url(
- r'webhook/gitlab/(?P{project_slug})/$'
- .format(**pattern_opts),
+ r'webhook/gitlab/(?P{project_slug})/$'.format(
+ **pattern_opts
+ ),
integrations.GitLabWebhookView.as_view(),
name='api_webhook_gitlab',
),
url(
- r'webhook/bitbucket/(?P{project_slug})/$'
- .format(**pattern_opts),
+ r'webhook/bitbucket/(?P{project_slug})/$'.format(
+ **pattern_opts
+ ),
integrations.BitbucketWebhookView.as_view(),
name='api_webhook_bitbucket',
),
url(
- r'webhook/generic/(?P