Merge branch 'master' into config-file-v2-docs
commit
eb1cd4e25a
|
@ -1,28 +1,30 @@
|
|||
# ProBot Mergeable Bot
|
||||
# https://github.com/jusx/mergeable
|
||||
|
||||
version: 2
|
||||
mergeable:
|
||||
pull_requests:
|
||||
approvals:
|
||||
# Minimum of approvals needed.
|
||||
min: 1
|
||||
message: 'The PR must have a minimum of 1 approvals.'
|
||||
|
||||
description:
|
||||
no_empty:
|
||||
# Do not allow empty descriptions on PR.
|
||||
enabled: false
|
||||
message: 'Description can not be empty.'
|
||||
|
||||
must_exclude:
|
||||
# Do not allow 'DO NOT MERGE' phrase on PR's description.
|
||||
regex: 'DO NOT MERGE'
|
||||
message: 'Description says that the PR should not be merged yet.'
|
||||
|
||||
# Do not allow 'WIP' on PR's title.
|
||||
title: 'WIP'
|
||||
|
||||
label:
|
||||
# Do not allow PR with label 'PR: work in progress'
|
||||
must_exclude: 'PR: work in progress'
|
||||
message: 'This PR is work in progress.'
|
||||
- when: pull_request.*
|
||||
validate:
|
||||
- do: title
|
||||
# Do not merge when it is marked work in progress (WIP)
|
||||
must_exclude:
|
||||
regex: ^\[WIP\]
|
||||
message: This is work in progress. Do not merge yet.
|
||||
- do: description
|
||||
must_exclude:
|
||||
# Do not allow 'DO NOT MERGE' phrase on PR's description.
|
||||
regex: 'DO NOT MERGE'
|
||||
message: 'Description says that the PR should not be merged yet.'
|
||||
no_empty:
|
||||
# Do not allow empty descriptions on PR.
|
||||
enabled: true
|
||||
message: 'Description can not be empty.'
|
||||
- do: approvals
|
||||
min:
|
||||
count: 1
|
||||
message: 'The PR must have a minimum of 1 approvals.'
|
||||
- do: label
|
||||
# Do not allow PR with label 'PR: work in progress'
|
||||
must_exclude:
|
||||
regex: 'PR: work in progress'
|
||||
message: 'This PR is work in progress.'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[rstcheck]
|
||||
ignore_directives=automodule,http:get,tabs,tab,prompt
|
||||
ignore_roles=djangosetting,setting,buildpyversions
|
||||
ignore_roles=djangosetting,setting,featureflags,buildpyversions
|
||||
ignore_messages=(Duplicate implicit target name: ".*")|(Hyperlink target ".*" is not referenced)
|
||||
|
|
|
@ -71,8 +71,8 @@ since it will stay up to date with your Read the Docs project::
|
|||
|
||||
.. _Read the Docs README: https://github.com/rtfd/readthedocs.org/blob/master/README.rst
|
||||
.. _project page: https://readthedocs.org/projects/pip/
|
||||
.. |green| image:: https://media.readthedocs.org/static/projects/badges/passing-flat.svg
|
||||
.. |red| image:: https://media.readthedocs.org/static/projects/badges/failing-flat.svg
|
||||
.. |yellow| image:: https://media.readthedocs.org/static/projects/badges/unknown-flat.svg
|
||||
.. |green| image:: https://assets.readthedocs.org/static/projects/badges/passing-flat.svg
|
||||
.. |red| image:: https://assets.readthedocs.org/static/projects/badges/failing-flat.svg
|
||||
.. |yellow| image:: https://assets.readthedocs.org/static/projects/badges/unknown-flat.svg
|
||||
.. |nbsp| unicode:: 0xA0
|
||||
:trim:
|
||||
|
|
|
@ -61,7 +61,7 @@ After any build is successfully finished, `HTMLFile` objects are created for eac
|
|||
`django-elasticsearch-dsl`_ package listens to the `post_create`/`post_delete` signals
|
||||
to index/delete documents, but it has performance drawbacks as it send HTTP request whenever
|
||||
any `HTMLFile` objects is created or deleted. To optimize the performance, `bulk_post_create`
|
||||
and `bulk_post_delete` Signals_ are dispatched with list of `HTMLFIle` objects so its possible
|
||||
and `bulk_post_delete` signals_ are dispatched with list of `HTMLFIle` objects so its possible
|
||||
to bulk index documents in elasticsearch ( `bulk_post_create` signal is dispatched for created
|
||||
and `bulk_post_delete` is dispatched for deleted objects). Both of the signals are dispatched
|
||||
with the list of the instances of `HTMLFile` in `instance_list` parameter.
|
||||
|
@ -107,4 +107,4 @@ As per requirements of `django-elasticsearch-dsl`_, it is stored in the
|
|||
.. _Elasticsearch document: https://www.elastic.co/guide/en/elasticsearch/guide/current/document.html
|
||||
.. _django-elasticsearch-dsl: https://github.com/sabricot/django-elasticsearch-dsl
|
||||
.. _elasticsearch-dsl: http://elasticsearch-dsl.readthedocs.io/en/latest/
|
||||
.. _Signals: https://docs.djangoproject.com/en/2.1/topics/signals/
|
||||
.. _signals: https://docs.djangoproject.com/en/2.1/topics/signals/
|
||||
|
|
|
@ -15,6 +15,8 @@ buildpyversions
|
|||
from django.conf import settings
|
||||
from docutils import nodes, utils
|
||||
|
||||
from readthedocs.projects.models import Feature
|
||||
|
||||
|
||||
def django_setting_role(typ, rawtext, text, lineno, inliner, options=None,
|
||||
content=None):
|
||||
|
@ -39,6 +41,17 @@ def python_supported_versions_role(typ, rawtext, text, lineno, inliner,
|
|||
return (node_list, [])
|
||||
|
||||
|
||||
def feature_flags_role(typ, rawtext, text, lineno, inliner, options=None,
|
||||
content=None):
|
||||
"""Up to date feature flags from the application."""
|
||||
all_features = Feature.FEATURES
|
||||
requested_feature = utils.unescape(text)
|
||||
for feature in all_features:
|
||||
if requested_feature.lower() == feature[0].lower():
|
||||
desc = nodes.Text(feature[1], feature[1])
|
||||
return [desc], []
|
||||
|
||||
|
||||
def setup(_):
|
||||
from docutils.parsers.rst import roles
|
||||
roles.register_local_role(
|
||||
|
@ -47,7 +60,11 @@ def setup(_):
|
|||
)
|
||||
roles.register_local_role(
|
||||
'buildpyversions',
|
||||
python_supported_versions_role
|
||||
python_supported_versions_role,
|
||||
)
|
||||
roles.register_local_role(
|
||||
'featureflags',
|
||||
feature_flags_role,
|
||||
)
|
||||
|
||||
return {
|
||||
|
|
10
docs/faq.rst
10
docs/faq.rst
|
@ -64,6 +64,16 @@ environment, and will be set to ``True`` when building on RTD::
|
|||
Woo
|
||||
{% endif %}
|
||||
|
||||
My project requires different settings than those available under Admin
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Read the Docs offers some settings which can be used for a variety of purposes,
|
||||
such as to use the latest version of sphinx or pip. To enable these settings,
|
||||
please open a request issue on our `github`_ and we will change the settings for the project.
|
||||
Read more about these settings :doc:`here <guides/feature-flags>`.
|
||||
|
||||
.. _github: https://github.com/rtfd/readthedocs.org
|
||||
|
||||
I get import errors on libraries that depend on C modules
|
||||
---------------------------------------------------------
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
Feature Flags
|
||||
=============
|
||||
|
||||
Read the Docs offers some additional flag settings which can be only be configured by the site admin.
|
||||
These are optional settings and you might not need it for every project.
|
||||
By default, these flags are disabled for every project.
|
||||
A seperate request can be made by opening an issue on our `github`_ to enable
|
||||
or disable one or more of these featured flags for a particular project.
|
||||
|
||||
.. _github: https://github.com/rtfd/readthedocs.org
|
||||
|
||||
Available Flags
|
||||
---------------
|
||||
|
||||
``USE_SPHINX_LATEST``: :featureflags:`USE_SPHINX_LATEST`
|
||||
|
||||
``USE_SETUPTOOLS_LATEST``: :featureflags:`USE_SETUPTOOLS_LATEST`
|
||||
|
||||
``ALLOW_DEPRECATED_WEBHOOKS``: :featureflags:`ALLOW_DEPRECATED_WEBHOOKS`
|
||||
|
||||
``PIP_ALWAYS_UPGRADE``: :featureflags:`PIP_ALWAYS_UPGRADE`
|
||||
|
||||
``SKIP_SUBMODULES``: :featureflags:`SKIP_SUBMODULES`
|
||||
|
||||
``DONT_OVERWRITE_SPHINX_CONTEXT``: :featureflags:`DONT_OVERWRITE_SPHINX_CONTEXT`
|
||||
|
||||
``ALLOW_V2_CONFIG_FILE``: :featureflags:`ALLOW_V2_CONFIG_FILE`
|
||||
|
||||
``MKDOCS_THEME_RTD``: :featureflags:`MKDOCS_THEME_RTD`
|
||||
|
||||
``DONT_SHALLOW_CLONE``: :featureflags:`DONT_SHALLOW_CLONE`
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import copy
|
||||
import os
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -146,6 +147,8 @@ class BuildConfigBase:
|
|||
'mkdocs',
|
||||
'submodules',
|
||||
]
|
||||
|
||||
default_build_image = DOCKER_DEFAULT_VERSION
|
||||
version = None
|
||||
|
||||
def __init__(self, env_config, raw_config, source_file):
|
||||
|
@ -246,6 +249,40 @@ class BuildConfigBase:
|
|||
)
|
||||
return ver
|
||||
|
||||
@property
|
||||
def valid_build_images(self):
|
||||
"""
|
||||
Return all the valid Docker image choices for ``build.image`` option.
|
||||
|
||||
The user can use any of this values in the YAML file. These values are
|
||||
the keys of ``DOCKER_IMAGE_SETTINGS`` Django setting (without the
|
||||
``readthedocs/build`` part) plus ``stable`` and ``latest``.
|
||||
"""
|
||||
images = {'stable', 'latest'}
|
||||
for k in DOCKER_IMAGE_SETTINGS:
|
||||
_, version = k.split(':')
|
||||
if re.fullmatch(r'^[\d\.]+$', version):
|
||||
images.add(version)
|
||||
return images
|
||||
|
||||
def get_valid_python_versions_for_image(self, build_image):
|
||||
"""
|
||||
Return all the valid Python versions for a Docker image.
|
||||
|
||||
The Docker image (``build_image``) has to be its complete name, already
|
||||
validated: ``readthedocs/build:4.0``, not just ``4.0``.
|
||||
|
||||
Returns supported versions for the ``DOCKER_DEFAULT_VERSION`` if not
|
||||
``build_image`` found.
|
||||
"""
|
||||
|
||||
if build_image not in DOCKER_IMAGE_SETTINGS:
|
||||
build_image = '{}:{}'.format(
|
||||
DOCKER_DEFAULT_IMAGE,
|
||||
self.default_build_image,
|
||||
)
|
||||
return DOCKER_IMAGE_SETTINGS[build_image]['python']['supported_versions']
|
||||
|
||||
def as_dict(self):
|
||||
config = {}
|
||||
for name in self.PUBLIC_ATTRIBUTES:
|
||||
|
@ -268,18 +305,23 @@ class BuildConfigV1(BuildConfigBase):
|
|||
'"python.extra_requirements" section must be a list.'
|
||||
)
|
||||
|
||||
PYTHON_SUPPORTED_VERSIONS = [2, 2.7, 3, 3.5]
|
||||
DOCKER_SUPPORTED_VERSIONS = ['1.0', '2.0', 'latest']
|
||||
|
||||
version = '1'
|
||||
|
||||
def get_valid_python_versions(self):
|
||||
"""Get all valid python versions."""
|
||||
"""
|
||||
Return all valid Python versions.
|
||||
|
||||
.. note::
|
||||
|
||||
It does not take current build image used into account.
|
||||
"""
|
||||
try:
|
||||
return self.env_config['python']['supported_versions']
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
return self.PYTHON_SUPPORTED_VERSIONS
|
||||
versions = set()
|
||||
for _, options in DOCKER_IMAGE_SETTINGS.items():
|
||||
versions = versions.union(options['python']['supported_versions'])
|
||||
return versions
|
||||
|
||||
def get_valid_formats(self): # noqa
|
||||
"""Get all valid documentation formats."""
|
||||
|
@ -339,7 +381,7 @@ class BuildConfigV1(BuildConfigBase):
|
|||
with self.catch_validation_error('build'):
|
||||
build['image'] = validate_choice(
|
||||
str(_build['image']),
|
||||
self.DOCKER_SUPPORTED_VERSIONS,
|
||||
self.valid_build_images,
|
||||
)
|
||||
if ':' not in build['image']:
|
||||
# Prepend proper image name to user's image name
|
||||
|
@ -577,8 +619,6 @@ class BuildConfigV2(BuildConfigBase):
|
|||
|
||||
version = '2'
|
||||
valid_formats = ['htmlzip', 'pdf', 'epub']
|
||||
valid_build_images = ['1.0', '2.0', '3.0', 'stable', 'latest']
|
||||
default_build_image = 'latest'
|
||||
valid_install_method = [PIP, SETUPTOOLS]
|
||||
valid_sphinx_builders = {
|
||||
'html': 'sphinx',
|
||||
|
@ -793,13 +833,7 @@ class BuildConfigV2(BuildConfigBase):
|
|||
This should be called after ``validate_build()``.
|
||||
"""
|
||||
build_image = self.build.image
|
||||
if build_image not in DOCKER_IMAGE_SETTINGS:
|
||||
build_image = '{}:{}'.format(
|
||||
DOCKER_DEFAULT_IMAGE,
|
||||
self.default_build_image,
|
||||
)
|
||||
python = DOCKER_IMAGE_SETTINGS[build_image]['python']
|
||||
return python['supported_versions']
|
||||
return self.get_valid_python_versions_for_image(build_image)
|
||||
|
||||
def validate_doc_types(self):
|
||||
"""
|
||||
|
|
|
@ -363,8 +363,8 @@ class TestValidatePythonVersion:
|
|||
)
|
||||
build.validate()
|
||||
assert build.python.version == 3
|
||||
assert build.python_interpreter == 'python3.5'
|
||||
assert build.python_full_version == 3.5
|
||||
assert build.python_interpreter == 'python3.7'
|
||||
assert build.python_full_version == 3.7
|
||||
|
||||
def test_it_validates_env_supported_versions(self):
|
||||
build = get_build_config(
|
||||
|
@ -483,7 +483,7 @@ class TestValidateBuild:
|
|||
apply_fs(tmpdir, yaml_config_dir)
|
||||
build = BuildConfigV1(
|
||||
{},
|
||||
{'build': {'image': 3.0}},
|
||||
{'build': {'image': 3.2}},
|
||||
source_file=str(tmpdir.join('readthedocs.yml')),
|
||||
)
|
||||
with raises(InvalidConfig) as excinfo:
|
||||
|
@ -513,7 +513,7 @@ class TestValidateBuild:
|
|||
{},
|
||||
{
|
||||
'build': {'image': 'latest'},
|
||||
'python': {'version': '3.3'},
|
||||
'python': {'version': '3.6'},
|
||||
},
|
||||
source_file=str(tmpdir.join('readthedocs.yml')),
|
||||
)
|
||||
|
@ -538,7 +538,7 @@ class TestValidateBuild:
|
|||
source_file=str(tmpdir.join('readthedocs.yml')),
|
||||
)
|
||||
build.validate()
|
||||
assert build.build.image == 'readthedocs/build:2.0'
|
||||
assert build.build.image == 'readthedocs/build:latest'
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'image', ['latest', 'readthedocs/build:3.0', 'rtd/build:latest'],
|
||||
|
@ -712,7 +712,7 @@ def test_as_dict(tmpdir):
|
|||
'use_system_site_packages': False,
|
||||
},
|
||||
'build': {
|
||||
'image': 'readthedocs/build:2.0',
|
||||
'image': 'readthedocs/build:latest',
|
||||
},
|
||||
'conda': None,
|
||||
'sphinx': {
|
||||
|
|
|
@ -27,8 +27,15 @@ from sphinx import version_info
|
|||
if globals().get('source_suffix', False):
|
||||
if isinstance(source_suffix, string_types):
|
||||
SUFFIX = source_suffix
|
||||
else:
|
||||
elif isinstance(source_suffix, (list, tuple)):
|
||||
# Sphinx >= 1.3 supports list/tuple to define multiple suffixes
|
||||
SUFFIX = source_suffix[0]
|
||||
elif isinstance(source_suffix, dict):
|
||||
# Sphinx >= 1.8 supports a mapping dictionary for mulitple suffixes
|
||||
SUFFIX = source_suffix.keys()[0]
|
||||
else:
|
||||
# default to .rst
|
||||
SUFFIX = '.rst'
|
||||
else:
|
||||
SUFFIX = '.rst'
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""OAuth utility functions."""
|
||||
|
||||
import logging
|
||||
|
@ -17,6 +15,13 @@ from requests_oauthlib import OAuth2Session
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SyncServiceError(Exception):
|
||||
|
||||
"""Error raised when a service failed to sync."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class Service:
|
||||
|
||||
"""
|
||||
|
@ -147,7 +152,7 @@ class Service:
|
|||
# Bad credentials: the token we have in our database is not
|
||||
# valid. Probably the user has revoked the access to our App. He
|
||||
# needs to reconnect his account
|
||||
raise Exception(
|
||||
raise SyncServiceError(
|
||||
'Our access to your {provider} account was revoked. '
|
||||
'Please, reconnect it from your social account connections.'.format(
|
||||
provider=self.provider_name,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""OAuth utility functions."""
|
||||
|
||||
import json
|
||||
|
@ -17,7 +15,7 @@ from readthedocs.builds import utils as build_utils
|
|||
from readthedocs.integrations.models import Integration
|
||||
|
||||
from ..models import RemoteOrganization, RemoteRepository
|
||||
from .base import Service
|
||||
from .base import Service, SyncServiceError
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -47,10 +45,10 @@ class BitbucketService(Service):
|
|||
for repo in repos:
|
||||
self.create_repository(repo)
|
||||
except (TypeError, ValueError):
|
||||
log.exception('Error syncing Bitbucket repositories')
|
||||
raise Exception(
|
||||
log.warning('Error syncing Bitbucket repositories')
|
||||
raise SyncServiceError(
|
||||
'Could not sync your Bitbucket repositories, '
|
||||
'try reconnecting your account',
|
||||
'try reconnecting your account'
|
||||
)
|
||||
|
||||
# Because privileges aren't returned with repository data, run query
|
||||
|
@ -85,8 +83,8 @@ class BitbucketService(Service):
|
|||
for repo in repos:
|
||||
self.create_repository(repo, organization=org)
|
||||
except ValueError:
|
||||
log.exception('Error syncing Bitbucket organizations')
|
||||
raise Exception(
|
||||
log.warning('Error syncing Bitbucket organizations')
|
||||
raise SyncServiceError(
|
||||
'Could not sync your Bitbucket team repositories, '
|
||||
'try reconnecting your account',
|
||||
)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""OAuth utility functions."""
|
||||
|
||||
import json
|
||||
|
@ -17,7 +15,8 @@ from readthedocs.integrations.models import Integration
|
|||
from readthedocs.restapi.client import api
|
||||
|
||||
from ..models import RemoteOrganization, RemoteRepository
|
||||
from .base import Service
|
||||
from .base import Service, SyncServiceError
|
||||
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -43,10 +42,10 @@ class GitHubService(Service):
|
|||
for repo in repos:
|
||||
self.create_repository(repo)
|
||||
except (TypeError, ValueError):
|
||||
log.exception('Error syncing GitHub repositories')
|
||||
raise Exception(
|
||||
log.warning('Error syncing GitHub repositories')
|
||||
raise SyncServiceError(
|
||||
'Could not sync your GitHub repositories, '
|
||||
'try reconnecting your account',
|
||||
'try reconnecting your account'
|
||||
)
|
||||
|
||||
def sync_organizations(self):
|
||||
|
@ -64,10 +63,10 @@ class GitHubService(Service):
|
|||
for repo in org_repos:
|
||||
self.create_repository(repo, organization=org_obj)
|
||||
except (TypeError, ValueError):
|
||||
log.exception('Error syncing GitHub organizations')
|
||||
raise Exception(
|
||||
log.warning('Error syncing GitHub organizations')
|
||||
raise SyncServiceError(
|
||||
'Could not sync your GitHub organizations, '
|
||||
'try reconnecting your account',
|
||||
'try reconnecting your account'
|
||||
)
|
||||
|
||||
def create_repository(self, fields, privacy=None, organization=None):
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""OAuth utility functions."""
|
||||
|
||||
import json
|
||||
|
@ -16,7 +14,8 @@ from readthedocs.integrations.models import Integration
|
|||
from readthedocs.projects.models import Project
|
||||
|
||||
from ..models import RemoteOrganization, RemoteRepository
|
||||
from .base import Service
|
||||
from .base import Service, SyncServiceError
|
||||
|
||||
|
||||
|
||||
try:
|
||||
|
@ -92,10 +91,10 @@ class GitLabService(Service):
|
|||
for repo in repos:
|
||||
self.create_repository(repo)
|
||||
except (TypeError, ValueError):
|
||||
log.exception('Error syncing GitLab repositories')
|
||||
raise Exception(
|
||||
'Could not sync your GitLab repositories, try reconnecting '
|
||||
'your account',
|
||||
log.warning('Error syncing GitLab repositories')
|
||||
raise SyncServiceError(
|
||||
'Could not sync your GitLab repositories, '
|
||||
'try reconnecting your account'
|
||||
)
|
||||
|
||||
def sync_organizations(self):
|
||||
|
@ -123,10 +122,10 @@ class GitLabService(Service):
|
|||
for repo in org_repos:
|
||||
self.create_repository(repo, organization=org_obj)
|
||||
except (TypeError, ValueError):
|
||||
log.exception('Error syncing GitLab organizations')
|
||||
raise Exception(
|
||||
'Could not sync your GitLab organization, try reconnecting '
|
||||
'your account',
|
||||
log.warning('Error syncing GitLab organizations')
|
||||
raise SyncServiceError(
|
||||
'Could not sync your GitLab organization, '
|
||||
'try reconnecting your account'
|
||||
)
|
||||
|
||||
def is_owned_by(self, owner_id):
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Tasks for OAuth services."""
|
||||
|
||||
import logging
|
||||
|
||||
from allauth.socialaccount.providers import registry as allauth_registry
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from readthedocs.core.utils.tasks import PublicTask, user_id_matches
|
||||
from readthedocs.oauth.notifications import (
|
||||
AttachWebhookNotification,
|
||||
InvalidProjectWebhookNotification,
|
||||
)
|
||||
from readthedocs.oauth.services.base import SyncServiceError
|
||||
from readthedocs.projects.models import Project
|
||||
from readthedocs.worker import app
|
||||
|
||||
|
@ -25,9 +25,21 @@ log = logging.getLogger(__name__)
|
|||
@app.task(queue='web', base=PublicTask)
|
||||
def sync_remote_repositories(user_id):
|
||||
user = User.objects.get(pk=user_id)
|
||||
failed_services = set()
|
||||
for service_cls in registry:
|
||||
for service in service_cls.for_user(user):
|
||||
service.sync()
|
||||
try:
|
||||
service.sync()
|
||||
except SyncServiceError:
|
||||
failed_services.add(service.provider_name)
|
||||
if failed_services:
|
||||
msg = _(
|
||||
'Our access to your following accounts was revoked: {providers}. '
|
||||
'Please, reconnect them from your social account connections.'
|
||||
)
|
||||
raise Exception(
|
||||
msg.format(providers=', '.join(failed_services))
|
||||
)
|
||||
|
||||
|
||||
@app.task(queue='web')
|
||||
|
|
|
@ -105,7 +105,6 @@ class ProjectViewSet(UserSelectViewSet):
|
|||
admin_serializer_class = ProjectAdminSerializer
|
||||
model = Project
|
||||
pagination_class = api_utils.ProjectPagination
|
||||
filter_fields = ('slug',) # django-filter<2.0.0
|
||||
filterset_fields = ('slug',)
|
||||
|
||||
@decorators.action(detail=True)
|
||||
|
@ -250,10 +249,6 @@ class VersionViewSet(UserSelectViewSet):
|
|||
serializer_class = VersionSerializer
|
||||
admin_serializer_class = VersionAdminSerializer
|
||||
model = Version
|
||||
filter_fields = (
|
||||
'active',
|
||||
'project__slug',
|
||||
) # django-filter<2.0.0
|
||||
filterset_fields = (
|
||||
'active',
|
||||
'project__slug',
|
||||
|
@ -266,7 +261,6 @@ class BuildViewSetBase(UserSelectViewSet):
|
|||
serializer_class = BuildSerializer
|
||||
admin_serializer_class = BuildAdminSerializer
|
||||
model = Build
|
||||
filter_fields = ('project__slug', 'commit') # django-filter<2.0.0
|
||||
filterset_fields = ('project__slug', 'commit')
|
||||
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ class LoadConfigTests(TestCase):
|
|||
config = load_yaml_config(self.version)
|
||||
self.assertEqual(
|
||||
config.get_valid_python_versions(),
|
||||
[2, 2.7, 3, 3.3, 3.4, 3.5, 3.6],
|
||||
[2, 2.7, 3, 3.5, 3.6, 3.7],
|
||||
)
|
||||
|
||||
@mock.patch('readthedocs.doc_builder.config.load_config')
|
||||
|
@ -507,7 +507,7 @@ class TestLoadConfigV2:
|
|||
|
||||
config = self.get_update_docs_task().config
|
||||
assert config.python.version == 3
|
||||
assert config.python_full_version == 3.6
|
||||
assert config.python_full_version == 3.7
|
||||
|
||||
@patch('readthedocs.doc_builder.environments.BuildEnvironment.run')
|
||||
def test_python_install_requirements(self, run, checkout_path, tmpdir):
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
from allauth.socialaccount.models import SocialAccount
|
||||
from allauth.socialaccount.providers.bitbucket_oauth2.views import (
|
||||
BitbucketOAuth2Adapter,
|
||||
)
|
||||
from allauth.socialaccount.providers.github.views import GitHubOAuth2Adapter
|
||||
from allauth.socialaccount.providers.gitlab.views import GitLabOAuth2Adapter
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django_dynamic_fixture import get
|
||||
from mock import patch
|
||||
|
||||
from readthedocs.builds.models import Version
|
||||
from readthedocs.oauth.services.base import SyncServiceError
|
||||
from readthedocs.oauth.tasks import sync_remote_repositories
|
||||
from readthedocs.projects.models import Project
|
||||
|
||||
|
||||
class SyncRemoteRepositoriesTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = get(User)
|
||||
self.project = get(Project, users=[self.user])
|
||||
self.version = get(Version, project=self.project)
|
||||
self.socialaccount_gh = get(
|
||||
SocialAccount,
|
||||
user=self.user,
|
||||
provider=GitHubOAuth2Adapter.provider_id,
|
||||
)
|
||||
self.socialaccount_gl = get(
|
||||
SocialAccount,
|
||||
user=self.user,
|
||||
provider=GitLabOAuth2Adapter.provider_id,
|
||||
)
|
||||
self.socialaccount_bb = get(
|
||||
SocialAccount,
|
||||
user=self.user,
|
||||
provider=BitbucketOAuth2Adapter.provider_id,
|
||||
)
|
||||
|
||||
@patch('readthedocs.oauth.services.github.GitHubService.sync')
|
||||
@patch('readthedocs.oauth.services.gitlab.GitLabService.sync')
|
||||
@patch('readthedocs.oauth.services.bitbucket.BitbucketService.sync')
|
||||
def test_sync_repository(self, sync_bb, sync_gl, sync_gh):
|
||||
r = sync_remote_repositories(self.user.pk)
|
||||
self.assertNotIn('error', r)
|
||||
sync_bb.assert_called_once()
|
||||
sync_gl.assert_called_once()
|
||||
sync_gh.assert_called_once()
|
||||
|
||||
@patch('readthedocs.oauth.services.github.GitHubService.sync')
|
||||
@patch('readthedocs.oauth.services.gitlab.GitLabService.sync')
|
||||
@patch('readthedocs.oauth.services.bitbucket.BitbucketService.sync')
|
||||
def test_sync_repository_failsync(self, sync_bb, sync_gl, sync_gh):
|
||||
sync_gh.side_effect = SyncServiceError
|
||||
r = sync_remote_repositories(self.user.pk)
|
||||
self.assertIn('GitHub', r['error'])
|
||||
self.assertNotIn('GitLab', r['error'])
|
||||
self.assertNotIn('Bitbucket', r['error'])
|
||||
sync_bb.assert_called_once()
|
||||
sync_gl.assert_called_once()
|
||||
sync_gh.assert_called_once()
|
||||
|
||||
@patch('readthedocs.oauth.services.github.GitHubService.sync')
|
||||
@patch('readthedocs.oauth.services.gitlab.GitLabService.sync')
|
||||
@patch('readthedocs.oauth.services.bitbucket.BitbucketService.sync')
|
||||
def test_sync_repository_failsync_more_than_one(self, sync_bb, sync_gl, sync_gh):
|
||||
sync_gh.side_effect = SyncServiceError
|
||||
sync_bb.side_effect = SyncServiceError
|
||||
r = sync_remote_repositories(self.user.pk)
|
||||
self.assertIn('GitHub', r['error'])
|
||||
self.assertIn('Bitbucket', r['error'])
|
||||
self.assertNotIn('GitLab', r['error'])
|
||||
sync_bb.assert_called_once()
|
||||
sync_gl.assert_called_once()
|
||||
sync_gh.assert_called_once()
|
|
@ -274,7 +274,7 @@ class CommunityBaseSettings(Settings):
|
|||
# Docker
|
||||
DOCKER_ENABLE = False
|
||||
DOCKER_DEFAULT_IMAGE = 'readthedocs/build'
|
||||
DOCKER_DEFAULT_VERSION = '2.0'
|
||||
DOCKER_DEFAULT_VERSION = 'latest'
|
||||
DOCKER_IMAGE = '{}:{}'.format(DOCKER_DEFAULT_IMAGE, DOCKER_DEFAULT_VERSION)
|
||||
DOCKER_IMAGE_SETTINGS = {
|
||||
'readthedocs/build:1.0': {
|
||||
|
@ -286,14 +286,17 @@ class CommunityBaseSettings(Settings):
|
|||
'readthedocs/build:3.0': {
|
||||
'python': {'supported_versions': [2, 2.7, 3, 3.3, 3.4, 3.5, 3.6]},
|
||||
},
|
||||
'readthedocs/build:stable': {
|
||||
'python': {'supported_versions': [2, 2.7, 3, 3.3, 3.4, 3.5, 3.6]},
|
||||
},
|
||||
'readthedocs/build:latest': {
|
||||
'python': {'supported_versions': [2, 2.7, 3, 3.3, 3.4, 3.5, 3.6]},
|
||||
'readthedocs/build:4.0': {
|
||||
'python': {'supported_versions': [2, 2.7, 3, 3.5, 3.6, 3.7]},
|
||||
},
|
||||
}
|
||||
|
||||
# Alias tagged via ``docker tag`` on the build servers
|
||||
DOCKER_IMAGE_SETTINGS.update({
|
||||
'readthedocs/build:stable': DOCKER_IMAGE_SETTINGS.get('readthedocs/build:3.0'),
|
||||
'readthedocs/build:latest': DOCKER_IMAGE_SETTINGS.get('readthedocs/build:4.0'),
|
||||
})
|
||||
|
||||
# All auth
|
||||
ACCOUNT_ADAPTER = 'readthedocs.core.adapters.AccountAdapter'
|
||||
ACCOUNT_EMAIL_REQUIRED = True
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
-r pip.txt
|
||||
# http://initd.org/psycopg/docs/install.html#binary-install-from-pypi
|
||||
psycopg2==2.7.6.1 --no-binary psycopg2
|
||||
psycopg2==2.7.7 --no-binary psycopg2
|
||||
gunicorn==19.9.0
|
||||
django-redis-cache==1.8.1
|
||||
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
-r pip.txt
|
||||
# We need these special cases of Python < 3 because we run tests with
|
||||
# Python 2 still
|
||||
# prospector 1.1.6.2 is not compatible with 2.1.0
|
||||
astroid==2.0.4; python_version > '3'
|
||||
astroid<1.7; python_version < '3'
|
||||
astroid<=2.0.4
|
||||
# prospector 1.1.6.2 is not compatible with 2.2.2
|
||||
pylint==2.1.1; python_version > '3'
|
||||
pylint<2; python_version < '3'
|
||||
# prospector 1.1.6.2 is not compatible with 2.0.5
|
||||
pylint-django==2.0.2; python_version > '3'
|
||||
pylint-django<0.12; python_version < '3'
|
||||
pylint<=2.1.1
|
||||
pylint-django<2.0.5
|
||||
pylint-celery==0.3
|
||||
prospector==1.1.6.2
|
||||
# prospector 1.1.6.2 is not compatible with 2.0.0
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Base packages
|
||||
pip==18.1
|
||||
pip==19.0.1
|
||||
appdirs==1.4.3
|
||||
virtualenv==16.2.0
|
||||
virtualenv==16.3.0
|
||||
|
||||
django==1.11.18
|
||||
django-tastypie==0.14.2
|
||||
|
@ -11,13 +11,7 @@ django-extensions==2.1.4
|
|||
djangorestframework==3.9.1
|
||||
|
||||
# Filtering for the REST API
|
||||
# When Python2 gets deprecated we can upgrade django-filter
|
||||
# > from .filterset import FilterSet
|
||||
# E File "/home/travis/build/rtfd/readthedocs.org/.tox/py27/lib/python2.7/site-packages/django_filters/filterset.py", line 184
|
||||
# E def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
|
||||
# E ^
|
||||
# E SyntaxError: invalid syntax
|
||||
django-filter<2.0.0
|
||||
django-filter==2.1.0
|
||||
|
||||
django-vanilla-views==1.0.6
|
||||
jsonfield==2.0.2
|
||||
|
@ -57,8 +51,14 @@ GitPython==2.1.10
|
|||
|
||||
# Search
|
||||
elasticsearch==6.2.0
|
||||
|
||||
|
||||
# elasticsearch-dsl==6.3.1 produces this error
|
||||
# File "/home/travis/build/rtfd/readthedocs.org/.tox/py36/lib/python3.6/site-packages/django_elasticsearch_dsl/documents.py", line 8, in <module>
|
||||
# from elasticsearch_dsl.document import DocTypeMeta as DSLDocTypeMeta
|
||||
# ImportError: cannot import name 'DocTypeMeta'
|
||||
elasticsearch-dsl==6.1.0
|
||||
django-elasticsearch-dsl==0.5.0
|
||||
django-elasticsearch-dsl==0.5.1
|
||||
pyquery==1.4.0
|
||||
|
||||
# Utils
|
||||
|
@ -68,7 +68,7 @@ beautifulsoup4==4.7.1
|
|||
Unipath==1.1
|
||||
django-kombu==0.9.4
|
||||
mock==2.0.0
|
||||
stripe==2.18.0
|
||||
stripe==2.19.0
|
||||
|
||||
django-formtools==2.1
|
||||
|
||||
|
|
Loading…
Reference in New Issue