Skip to content

Commit 4f56386

Browse files
committed
Merge tag '7.2.1' into rel
2 parents a5cdb95 + 58c82ed commit 4f56386

34 files changed

+792
-95
lines changed

.readthedocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ search:
1919
ranking:
2020
# Deprecated content
2121
api/v1.html: -1
22-
config-file/v1.html: -1
22+
config-file/v1.html: -5
2323

2424
# Useful content, but not something we want most users finding
2525
changelog.html: -6

CHANGELOG.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
Version 7.2.1
2+
-------------
3+
4+
:Date: February 15, 2022
5+
6+
* `@humitos <https://github.com/humitos>`__: Sentry: ignore logging known exceptions (`#8919 <https://github.com/readthedocs/readthedocs.org/pull/8919>`__)
7+
* `@humitos <https://github.com/humitos>`__: Build: do not send notifications on known failed builds (`#8918 <https://github.com/readthedocs/readthedocs.org/pull/8918>`__)
8+
* `@humitos <https://github.com/humitos>`__: Celery: use `on_retry` to handle `BuildMaxConcurrencyError` (`#8917 <https://github.com/readthedocs/readthedocs.org/pull/8917>`__)
9+
* `@lkeegan <https://github.com/lkeegan>`__: typo fix (`#8911 <https://github.com/readthedocs/readthedocs.org/pull/8911>`__)
10+
* `@stsewd <https://github.com/stsewd>`__: Fix tests (`#8908 <https://github.com/readthedocs/readthedocs.org/pull/8908>`__)
11+
* `@agjohnson <https://github.com/agjohnson>`__: Throw an exception from Celery retry() (`#8905 <https://github.com/readthedocs/readthedocs.org/pull/8905>`__)
12+
* `@agjohnson <https://github.com/agjohnson>`__: Reduce verbose logging on generic command failure (`#8904 <https://github.com/readthedocs/readthedocs.org/pull/8904>`__)
13+
* `@humitos <https://github.com/humitos>`__: Build: define missing variable (`#8903 <https://github.com/readthedocs/readthedocs.org/pull/8903>`__)
14+
* `@humitos <https://github.com/humitos>`__: Search: call apply_async to fix the issue (`#8900 <https://github.com/readthedocs/readthedocs.org/pull/8900>`__)
15+
* `@humitos <https://github.com/humitos>`__: Build: allow to not record commands on sync_repository_task (`#8899 <https://github.com/readthedocs/readthedocs.org/pull/8899>`__)
16+
* `@humitos <https://github.com/humitos>`__: Release 7.2.0 (`#8898 <https://github.com/readthedocs/readthedocs.org/pull/8898>`__)
17+
* `@stsewd <https://github.com/stsewd>`__: Fix linter (`#8897 <https://github.com/readthedocs/readthedocs.org/pull/8897>`__)
18+
* `@stsewd <https://github.com/stsewd>`__: Support for CDN when privacy levels are enabled (`#8896 <https://github.com/readthedocs/readthedocs.org/pull/8896>`__)
19+
* `@ericholscher <https://github.com/ericholscher>`__: Don't be so excited always in our emails :) (`#8888 <https://github.com/readthedocs/readthedocs.org/pull/8888>`__)
20+
* `@humitos <https://github.com/humitos>`__: Docs: reduce visibility of Config File V1 (`#8887 <https://github.com/readthedocs/readthedocs.org/pull/8887>`__)
21+
* `@humitos <https://github.com/humitos>`__: Builder: unpin pip (`#8886 <https://github.com/readthedocs/readthedocs.org/pull/8886>`__)
22+
* `@stsewd <https://github.com/stsewd>`__: Dependencies: remove unused packages (`#8881 <https://github.com/readthedocs/readthedocs.org/pull/8881>`__)
23+
* `@stsewd <https://github.com/stsewd>`__: Subscriptions: move some querysets (`#8876 <https://github.com/readthedocs/readthedocs.org/pull/8876>`__)
24+
* `@humitos <https://github.com/humitos>`__: Django3: delete old JSONField and use the new ones (`#8869 <https://github.com/readthedocs/readthedocs.org/pull/8869>`__)
25+
* `@humitos <https://github.com/humitos>`__: Django3: add new `django.db.models.JSONField` (`#8868 <https://github.com/readthedocs/readthedocs.org/pull/8868>`__)
26+
127
Version 7.2.0
228
-------------
329

docs/user/config-file/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,3 @@ The main advantages of using a configuration file over the web interface are:
2525
:maxdepth: 3
2626

2727
Version 2 <v2>
28-
Version 1 <v1>

docs/user/config-file/v1.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
:orphan:
2+
13
Configuration File V1 (Deprecated)
24
==================================
35

@@ -6,7 +8,7 @@ Read the Docs has support for configuring builds with a YAML file.
68

79
.. warning::
810

9-
Version 1 shouldn't be used.
11+
Version 1 is deprecated and shouldn't be used.
1012
The version 2 of the configuration file is now available.
1113
See the :ref:`new features <config-file/v2:New settings>`
1214
and :ref:`how to migrate from v1 <config-file/v2:Migrating from v1>`.

docs/user/config-file/v2.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -747,8 +747,8 @@ the ``apt_packages`` key described above.
747747

748748
.. warning::
749749

750-
When using the new speficiation,
751-
the ``build.os`` and ``python.version`` options cannot be used.
750+
When using the new specification,
751+
the ``build.image`` and ``python.version`` options cannot be used.
752752
Doing so will error the build.
753753

754754
build (legacy)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.2.11 on 2022-01-31 11:47
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('builds', '0037_alter_build_cold_storage'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='build',
15+
name='_config_json',
16+
field=models.JSONField(default=dict, verbose_name='Configuration used in the build'),
17+
),
18+
]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from django.db import migrations, models
2+
from django.db.models import F
3+
from django.db.models.functions import Cast
4+
5+
6+
def forwards_func(apps, schema_editor):
7+
Build = apps.get_model('builds', 'Build')
8+
(
9+
Build.objects
10+
.annotate(_config_in_json=Cast('_config', output_field=models.JSONField()))
11+
# Copy `_config` JSONField (text) into `_config_json` (jsonb)
12+
.update(_config_json=F('_config_in_json'))
13+
)
14+
15+
16+
class Migration(migrations.Migration):
17+
18+
dependencies = [
19+
('builds', '0038_add_new_jsonfields'),
20+
]
21+
22+
operations = [
23+
migrations.RunPython(forwards_func)
24+
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 3.2.11 on 2022-01-31 12:12
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('builds', '0039_migrate_config_data'),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name='build',
15+
name='_config',
16+
),
17+
migrations.RenameField(
18+
model_name='build',
19+
old_name='_config_json',
20+
new_name='_config',
21+
),
22+
]

readthedocs/builds/models.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
"""Models for the builds app."""
22

33
import datetime
4-
import structlog
54
import os.path
65
import re
76
from functools import partial
8-
from shutil import rmtree
97

108
import regex
9+
import structlog
1110
from django.conf import settings
1211
from django.db import models
1312
from django.db.models import F
@@ -20,7 +19,6 @@
2019
ModificationDateTimeField,
2120
)
2221
from django_extensions.db.models import TimeStampedModel
23-
from jsonfield import JSONField
2422
from polymorphic.models import PolymorphicModel
2523

2624
import readthedocs.builds.automation_actions as actions
@@ -80,6 +78,7 @@
8078
GITLAB_URL,
8179
MEDIA_TYPES,
8280
PRIVACY_CHOICES,
81+
PRIVATE,
8382
SPHINX,
8483
SPHINX_HTMLDIR,
8584
SPHINX_SINGLEHTML,
@@ -196,6 +195,18 @@ def __str__(self):
196195
),
197196
)
198197

198+
@property
199+
def is_private(self):
200+
"""
201+
Check if the version is private (taking external versions into consideration).
202+
203+
The privacy level of an external version is given by
204+
the value of ``project.external_builds_privacy_level``.
205+
"""
206+
if self.is_external:
207+
return self.project.external_builds_privacy_level == PRIVATE
208+
return self.privacy_level == PRIVATE
209+
199210
@property
200211
def is_external(self):
201212
return self.type == EXTERNAL
@@ -650,7 +661,7 @@ class Build(models.Model):
650661
null=True,
651662
blank=True,
652663
)
653-
_config = JSONField(_('Configuration used in the build'), default=dict)
664+
_config = models.JSONField(_('Configuration used in the build'), default=dict)
654665

655666
length = models.IntegerField(_('Build Length'), null=True, blank=True)
656667

@@ -719,6 +730,10 @@ def config(self):
719730
Build object (it could be stored in this object or one of the previous
720731
ones).
721732
"""
733+
# TODO: now that we are using a proper JSONField here, we could
734+
# probably change this field to be a ForeignKey to avoid repeating the
735+
# config file over and over again and re-use them to save db data as
736+
# well
722737
if self.CONFIG_KEY in self._config:
723738
return (
724739
Build.objects
@@ -762,6 +777,11 @@ def save(self, *args, **kwargs): # noqa
762777
self.version_name = self.version.verbose_name
763778
self.version_slug = self.version.slug
764779
self.version_type = self.version.type
780+
781+
# TODO: delete copying config after deploy
782+
# Copy `_config` into the new `_config_json` JSONField
783+
self._config_json = self._config
784+
765785
super().save(*args, **kwargs)
766786
self._config_changed = False
767787

readthedocs/core/mixins.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
"""Common mixin classes for views."""
2+
from functools import lru_cache
23

4+
from django.conf import settings
35
from django.contrib.auth.mixins import LoginRequiredMixin
46
from vanilla import ListView
57

8+
from readthedocs.projects.models import Feature
9+
610

711
class ListViewWithForm(ListView):
812

@@ -24,3 +28,42 @@ class ProxiedAPIMixin:
2428
# DRF has BasicAuthentication and SessionAuthentication as default classes.
2529
# We don't support neither in the community site.
2630
authentication_classes = []
31+
32+
33+
class CachedView:
34+
35+
"""
36+
Allow to cache views at the CDN level when privacy levels are enabled.
37+
38+
The cache control header is only used when privacy levels
39+
are enabled (otherwise everything is public by default).
40+
41+
Views that can be cached should always return the same response for all
42+
users (anonymous and authenticated users), like when the version attached
43+
to the request is public.
44+
45+
To cache a view you can either set the `cache_request` attribute to `True`,
46+
or override the `can_be_cached` method.
47+
48+
We use ``CDN-Cache-Control``, to control caching at the CDN level only.
49+
This doesn't affect caching at the browser level (``Cache-Control``).
50+
51+
See https://developers.cloudflare.com/cache/about/cdn-cache-control.
52+
"""
53+
54+
cache_request = False
55+
56+
def dispatch(self, request, *args, **kwargs):
57+
response = super().dispatch(request, *args, **kwargs)
58+
if settings.ALLOW_PRIVATE_REPOS and self.can_be_cached(request):
59+
response.headers['CDN-Cache-Control'] = 'public'
60+
return response
61+
62+
def can_be_cached(self, request):
63+
return self.cache_request
64+
65+
@lru_cache(maxsize=1)
66+
def _is_cache_enabled(self, project):
67+
"""Helper function to check if CND is enabled for a project."""
68+
# TODO: check for the organization's plan.
69+
return settings.ALLOW_PRIVATE_REPOS and project.has_feature(Feature.CDN_ENABLED)

readthedocs/core/utils/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@ def prepare_build(
186186
project_slug=project.slug,
187187
version_slug=version.slug,
188188
)
189-
options['countdown'] = 5 * 60
190-
options['max_retries'] = 25
189+
options['countdown'] = settings.RTD_BUILDS_RETRY_DELAY
190+
options['max_retries'] = settings.RTD_BUILDS_MAX_RETRIES
191191
build.error = BuildMaxConcurrencyError.message.format(
192192
limit=max_concurrent_builds,
193193
)

readthedocs/doc_builder/environments.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -444,24 +444,21 @@ def run_command_class(
444444
self.commands.append(build_cmd)
445445

446446
if build_cmd.failed:
447-
msg = 'Command {cmd} failed'.format(cmd=build_cmd.get_command())
448-
449-
# TODO: improve this error report. This is showing _the full_
450-
# stdout to the user exposing it at the top of the Build Detail's
451-
# page in red. It would be good to reduce the noise here and just
452-
# point the user to take a look at its output from the command's
453-
# output itself.
454-
if build_cmd.output:
455-
msg += ':\n{out}'.format(out=build_cmd.output)
456-
457447
if warn_only:
448+
msg = 'Command {cmd} failed'.format(cmd=build_cmd.get_command())
449+
if build_cmd.output:
450+
msg += ':\n{out}'.format(out=build_cmd.output)
458451
log.warning(
459452
msg,
460453
project_slug=self.project.slug if self.project else '',
461454
version_slug=self.version.slug if self.version else '',
462455
)
463456
else:
464-
raise BuildUserError(msg)
457+
# TODO: for now, this still outputs a generic error message
458+
# that is the same across all commands. We could improve this
459+
# with more granular error messages that vary by the command
460+
# being run.
461+
raise BuildUserError()
465462
return build_cmd
466463

467464

readthedocs/doc_builder/python_environments.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -297,13 +297,7 @@ def install_core_requirements(self):
297297
Feature.DONT_INSTALL_LATEST_PIP,
298298
# 20.3 uses the new resolver by default.
299299
positive='pip<20.3',
300-
301-
# We are pinning pip to 21.3.1 because builds are failing when
302-
# using a newer version. This is a temporal workaround to avoid
303-
# builds failing at this step, but we should come back to this and
304-
# unpin pip for this case.
305-
# https://github.com/readthedocs/readthedocs.org/issues/8864#issuecomment-1025499598
306-
negative='pip<=21.3.1',
300+
negative='pip',
307301
)
308302
cmd = pip_install_cmd + [pip_version, 'setuptools<58.3.0']
309303
self.build_env.run(

readthedocs/integrations/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class IntegrationAdmin(admin.ModelAdmin):
9393
search_fields = ('project__slug', 'project__name')
9494
readonly_fields = ['exchanges']
9595

96+
# TODO: review this now that we are using official Django's JSONField
9697
def exchanges(self, obj):
9798
"""
9899
Manually make an inline-ish block.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Generated by Django 3.2.11 on 2022-01-31 11:47
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('integrations', '0007_update-provider-data'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='httpexchange',
15+
name='request_headers_json',
16+
field=models.JSONField(default=None, null=True, verbose_name='Request headers'),
17+
),
18+
migrations.AddField(
19+
model_name='httpexchange',
20+
name='response_headers_json',
21+
field=models.JSONField(default=None, null=True, verbose_name='Request headers'),
22+
),
23+
migrations.AddField(
24+
model_name='integration',
25+
name='provider_data_json',
26+
field=models.JSONField(default=dict, verbose_name='Provider data'),
27+
),
28+
]

0 commit comments

Comments
 (0)