From c3a02681023ad73d41415f27b7b61d34eb862348 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 23 Sep 2024 11:30:48 +0200 Subject: [PATCH] Addons: prepare Proxito and dashboard to enable them by default (#11513) * Addons: prepare Proxito and dashboard to enable them by default Prepare Proxito's code and dashboard to enable addons by default starting on October 7th, as planned: https://about.readthedocs.com/blog/2024/07/addons-by-default/ Note this code is temporary and can be deployed _before_ reaching that particular date. The idea is to remove this code once this behavior becomes the default. Related https://github.com/readthedocs/readthedocs.org/issues/11474 * Update tests * Revert "Update tests" This reverts commit 235514af2c5ea19657bc5bd1ea8b1d0044182047. * Allow "for Business" to disable addons completely Keep the ability to let commercial users to disable Addons completely. This means that our Cloudflare working won't even inject them. * Create `AddonsConfig` on project import/add * Typo --- readthedocs/projects/forms.py | 15 ++++++- readthedocs/projects/views/private.py | 4 ++ readthedocs/proxito/middleware.py | 63 ++++++++++++++++++++------- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 0c77b850156..9e3a1fbe04d 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -1,16 +1,19 @@ """Project forms.""" +import datetime import json from random import choice from re import fullmatch from urllib.parse import urlparse +import pytz from allauth.socialaccount.models import SocialAccount from django import forms from django.conf import settings from django.contrib.auth.models import User from django.db.models import Q from django.urls import reverse +from django.utils import timezone from django.utils.translation import gettext_lazy as _ from readthedocs.builds.constants import INTERNAL @@ -677,14 +680,24 @@ class Meta: def __init__(self, *args, **kwargs): self.project = kwargs.pop("project", None) + + tzinfo = pytz.timezone("America/Los_Angeles") + addons_enabled_by_default = timezone.now() > datetime.datetime( + 2024, 10, 7, 0, 0, 0, tzinfo=tzinfo + ) + addons, created = AddonsConfig.objects.get_or_create(project=self.project) if created: - addons.enabled = False + addons.enabled = addons_enabled_by_default addons.save() kwargs["instance"] = addons super().__init__(*args, **kwargs) + # Keep the ability to disable addons completely on Read the Docs for Business + if not settings.RTD_ALLOW_ORGANIZATIONS and addons_enabled_by_default: + self.fields.pop("enabled") + def clean(self): if ( self.cleaned_data["flyout_sorting"] == ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py index ae42051d594..a56a70566ca 100644 --- a/readthedocs/projects/views/private.py +++ b/readthedocs/projects/views/private.py @@ -74,6 +74,7 @@ WebHookForm, ) from readthedocs.projects.models import ( + AddonsConfig, Domain, EmailHook, EnvironmentVariable, @@ -423,6 +424,9 @@ def done(self, form_list, **kwargs): # attributes directly from other forms project = basics_form.save() + # Create an AddonsConfig object for this project. + AddonsConfig.objects.get_or_create(project=project) + self.finish_import_project(self.request, project) return HttpResponseRedirect( diff --git a/readthedocs/proxito/middleware.py b/readthedocs/proxito/middleware.py index edcf80acbae..03928ffc20a 100644 --- a/readthedocs/proxito/middleware.py +++ b/readthedocs/proxito/middleware.py @@ -5,9 +5,11 @@ Additional processing is done to get the project from the URL in the ``views.py`` as well. """ +import datetime import re from urllib.parse import urlparse +import pytz import structlog from corsheaders.middleware import ( ACCESS_CONTROL_ALLOW_METHODS, @@ -17,6 +19,7 @@ from django.core.exceptions import SuspiciousOperation from django.shortcuts import redirect from django.urls import reverse +from django.utils import timezone from django.utils.deprecation import MiddlewareMixin from django.utils.encoding import iri_to_uri from django.utils.html import escape @@ -31,7 +34,7 @@ unresolver, ) from readthedocs.core.utils import get_cache_tag -from readthedocs.projects.models import Project +from readthedocs.projects.models import Feature, Project from readthedocs.proxito.cache import add_cache_tags, cache_response, private_response from readthedocs.proxito.redirects import redirect_to_https @@ -289,23 +292,51 @@ def add_hosting_integrations_headers(self, request, response): version_slug = getattr(request, "path_version_slug", "") if project_slug: - force_addons = Project.objects.filter( - slug=project_slug, - addons__enabled=True, - ).exists() - if force_addons: - response["X-RTD-Force-Addons"] = "true" - return - - if version_slug: - addons = Version.objects.filter( - project__slug=project_slug, - slug=version_slug, - addons=True, + tzinfo = pytz.timezone("America/Los_Angeles") + addons_enabled_by_default = timezone.now() > datetime.datetime( + 2024, + 10, + 7, + 0, + 0, + 0, + tzinfo=tzinfo, + ) + if addons_enabled_by_default: + addons = Project.objects.filter( + slug=project_slug, addons__enabled=True + ).exists() + + if addons: + response["X-RTD-Force-Addons"] = "true" + return + + else: + # TODO: remove "else" code once DISABLE_SPHINX_MANIPULATION and addons becomes the default + # https://about.readthedocs.com/blog/2024/07/addons-by-default/ + disable_sphinx_manipulation_enabled = Feature.objects.filter( + feature_id=Feature.DISABLE_SPHINX_MANIPULATION, + projects__slug=Project.objects.filter(slug=project_slug).first(), ).exists() - if addons: - response["X-RTD-Hosting-Integrations"] = "true" + force_addons = Project.objects.filter( + slug=project_slug, + addons__enabled=True, + ).exists() + + if force_addons or disable_sphinx_manipulation_enabled: + response["X-RTD-Force-Addons"] = "true" + return + + if version_slug: + addons = Version.objects.filter( + project__slug=project_slug, + slug=version_slug, + addons=True, + ).exists() + + if addons: + response["X-RTD-Hosting-Integrations"] = "true" def add_cors_headers(self, request, response): """