Skip to content

Proxito: remove old implementation #10660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Aug 28, 2023
35 changes: 0 additions & 35 deletions readthedocs/core/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ def base_resolve_path(
project_relationship=None,
subdomain=None,
cname=None,
urlconf=None,
custom_prefix=None,
):
"""
Expand Down Expand Up @@ -97,38 +96,6 @@ def base_resolve_path(
else:
path = unsafe_join_url_path(path, "{language}/{version}/{filename}")

# TODO: remove this when all projects have migrated to path prefixes.
# Allow users to override their own URLConf
# If a custom prefix is given, we don't use the custom URLConf,
# since they are not compatible with each other.
# We also don't check if the project has the new proxito implementation
# enabled, this is so we can start generating links with the new
# custom prefixes without starting to serve docs with it (this helps to ease
# the migration from urlconf to custom prefixes).
if urlconf and not custom_prefix:
path = urlconf
path = path.replace(
"$version",
"{version}",
)
path = path.replace(
'$language',
'{language}',
)
path = path.replace(
'$filename',
'{filename}',
)
path = path.replace(
"$subproject",
"{subproject}",
)
if "$" in path:
log.warning(
"Unconverted variable in a resolver URLConf.",
path=path,
)

subproject_alias = project_relationship.alias if project_relationship else ""
return path.format(
project=project_slug,
Expand All @@ -147,7 +114,6 @@ def resolve_path(
single_version=None,
subdomain=None,
cname=None,
urlconf=None,
):
"""Resolve a URL with a subset of fields defined."""
version_slug = version_slug or project.get_default_version()
Expand Down Expand Up @@ -181,7 +147,6 @@ def resolve_path(
project_relationship=project_relationship,
cname=cname,
subdomain=subdomain,
urlconf=urlconf or project.urlconf,
custom_prefix=custom_prefix,
)

Expand Down
2 changes: 2 additions & 0 deletions readthedocs/core/unresolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ def unresolve_path(self, unresolved_domain, path, append_indexhtml=True):
:param append_indexhtml: If `True` directories will be normalized
to end with ``/index.html``.
"""
# Make sure we always have a leading slash.
path = self._normalize_filename(path)
# We don't call unparse() on the path,
# since it could be parsed as a full URL if it starts with a protocol.
parsed_url = ParseResult(
Expand Down
20 changes: 20 additions & 0 deletions readthedocs/projects/migrations/0105_remove_project_urlconf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.2.4 on 2023-08-23 20:35

from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("projects", "0104_alter_httpheader_value"),
]

operations = [
migrations.RemoveField(
model_name="historicalproject",
name="urlconf",
),
migrations.RemoveField(
model_name="project",
name="urlconf",
),
Comment on lines +16 to +19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't deploy break if we remove this field? I assume the current production code is not using this field.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to run this migration after we have deployed the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK! Make sure to make this pretty clear on the release project. We usually run migrations before deploying new code.

]
135 changes: 2 additions & 133 deletions readthedocs/projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import hashlib
import hmac
import os
import re
from shlex import quote
from urllib.parse import urlparse

Expand All @@ -15,18 +14,16 @@
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Prefetch
from django.urls import include, re_path, reverse
from django.urls import reverse
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views import defaults
from django_extensions.db.fields import CreationDateTimeField, ModificationDateTimeField
from django_extensions.db.models import TimeStampedModel
from taggit.managers import TaggableManager

from readthedocs.builds.constants import EXTERNAL, INTERNAL, LATEST, STABLE
from readthedocs.constants import pattern_opts
from readthedocs.core.history import ExtraHistoricalRecords
from readthedocs.core.resolver import resolve, resolve_domain
from readthedocs.core.utils import extract_valid_attributes_for_model, slugify
Expand Down Expand Up @@ -230,18 +227,6 @@ class Project(models.Model):
'DirectoryHTMLBuilder">More info on sphinx builders</a>.',
),
)
# NOTE: This is deprecated, use the `custom_prefix*` attributes instead.
urlconf = models.CharField(
_('Documentation URL Configuration'),
max_length=255,
default=None,
blank=True,
null=True,
help_text=_(
'Supports the following keys: $language, $version, $subproject, $filename. '
'An example: `$language/$version/$filename`.'
),
)

custom_prefix = models.CharField(
_("Custom path prefix"),
Expand Down Expand Up @@ -718,7 +703,7 @@ def proxied_api_prefix(self):
"""
Get the path prefix for proxied API paths (``/_/``).

Returns `None` if the project doesn't have a custom urlconf.
Returns `None` if the project doesn't have a custom prefix.
"""
# When using a custom prefix, we can only handle serving
# docs pages under the prefix, not special paths like `/_/`.
Expand All @@ -730,10 +715,6 @@ def proxied_api_prefix(self):
Feature.USE_PROXIED_APIS_WITH_PREFIX
):
return self.custom_prefix
if self.urlconf:
# Return the value before the first defined variable,
# as that is a prefix and not part of our normal doc patterns.
return self.urlconf.split("$", 1)[0]
return None

@cached_property
Expand All @@ -752,111 +733,6 @@ def subproject_prefix(self):
return None
return parent_relationship.subproject_prefix

@property
def regex_urlconf(self):
"""
Convert User's URLConf into a proper django URLConf.

This replaces the user-facing syntax with the regex syntax.
"""
to_convert = re.escape(self.urlconf)

# We should standardize these names so we can loop over them easier
to_convert = to_convert.replace(
'\\$version',
'(?P<version_slug>{regex})'.format(regex=pattern_opts['version_slug'])
)
to_convert = to_convert.replace(
'\\$language',
'(?P<lang_slug>{regex})'.format(regex=pattern_opts['lang_slug'])
)
to_convert = to_convert.replace(
'\\$filename',
'(?P<filename>{regex})'.format(regex=pattern_opts['filename_slug'])
)
to_convert = to_convert.replace(
'\\$subproject',
'(?P<subproject_slug>{regex})'.format(regex=pattern_opts['project_slug'])
)

if '\\$' in to_convert:
log.warning(
'Unconverted variable in a project URLConf.',
project_slug=self.slug,
to_convert=to_convert,
)
return to_convert

@property
def proxito_urlconf(self):
"""
Returns a URLConf class that is dynamically inserted via proxito.

It is used for doc serving on projects that have their own ``urlconf``.
"""
from readthedocs.projects.views.public import ProjectDownloadMedia
from readthedocs.proxito.urls import core_urls
from readthedocs.proxito.views.serve import ServeDocs, ServeStaticFiles
from readthedocs.proxito.views.utils import proxito_404_page_handler

class ProxitoURLConf:

"""A URLConf dynamically inserted by Proxito."""

proxied_urls = [
re_path(
r'{proxied_api_url}api/v2/'.format(
proxied_api_url=re.escape(self.proxied_api_url),
),
include('readthedocs.api.v2.proxied_urls'),
name='user_proxied_api'
),
re_path(
r'{proxied_api_url}downloads/'
r'(?P<lang_slug>{lang_slug})/'
r'(?P<version_slug>{version_slug})/'
r'(?P<type_>[-\w]+)/$'.format(
proxied_api_url=re.escape(self.proxied_api_url),
**pattern_opts),
ProjectDownloadMedia.as_view(same_domain_url=True),
name='user_proxied_downloads'
),
re_path(
r"{proxied_api_url}static/"
r"(?P<filename>{filename_slug})$".format(
proxied_api_url=re.escape(self.proxied_api_url),
**pattern_opts,
),
ServeStaticFiles.as_view(),
name="proxito_static_files",
),
]
docs_urls = [
re_path(
'^{regex_urlconf}$'.format(regex_urlconf=self.regex_urlconf),
ServeDocs.as_view(),
name='user_proxied_serve_docs'
),
# paths for redirects at the root
re_path(
'^{proxied_api_url}$'.format(
proxied_api_url=re.escape(self.urlconf.split('$', 1)[0]),
),
ServeDocs.as_view(),
name='user_proxied_serve_docs_subpath_redirect'
),
re_path(
'^(?P<filename>{regex})$'.format(regex=pattern_opts['filename_slug']),
ServeDocs.as_view(),
name='user_proxied_serve_docs_root_redirect'
),
]
urlpatterns = proxied_urls + core_urls + docs_urls
handler404 = proxito_404_page_handler
handler500 = defaults.server_error

return ProxitoURLConf

@cached_property
def is_subproject(self):
"""Return whether or not this project is a subproject."""
Expand Down Expand Up @@ -1935,7 +1811,6 @@ def add_features(sender, **kwargs):
ALLOW_FORCED_REDIRECTS = "allow_forced_redirects"
DISABLE_PAGEVIEWS = "disable_pageviews"
RESOLVE_PROJECT_FROM_HEADER = "resolve_project_from_header"
USE_UNRESOLVER_WITH_PROXITO = "use_unresolver_with_proxito"
USE_PROXIED_APIS_WITH_PREFIX = "use_proxied_apis_with_prefix"
ALLOW_VERSION_WARNING_BANNER = "allow_version_warning_banner"

Expand Down Expand Up @@ -2006,12 +1881,6 @@ def add_features(sender, **kwargs):
RESOLVE_PROJECT_FROM_HEADER,
_("Proxito: Allow usage of the X-RTD-Slug header"),
),
(
USE_UNRESOLVER_WITH_PROXITO,
_(
"Proxito: Use new unresolver implementation for serving documentation files."
),
),
(
USE_PROXIED_APIS_WITH_PREFIX,
_(
Expand Down
22 changes: 0 additions & 22 deletions readthedocs/proxito/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
Additional processing is done to get the project from the URL in the ``views.py`` as well.
"""
import re
import sys
from urllib.parse import urlparse

import structlog
Expand All @@ -26,7 +25,6 @@
unresolver,
)
from readthedocs.core.utils import get_cache_tag
from readthedocs.projects.models import Feature
from readthedocs.proxito.cache import add_cache_tags, cache_response, private_response
from readthedocs.proxito.redirects import redirect_to_https

Expand Down Expand Up @@ -264,26 +262,6 @@ def process_request(self, request): # noqa
project_slug=project.slug,
)

# This is hacky because Django wants a module for the URLConf,
# instead of also accepting string
if project.urlconf and not project.has_feature(
Feature.USE_UNRESOLVER_WITH_PROXITO
):
# Stop Django from caching URLs
# https://github.com/django/django/blob/7cf7d74/django/urls/resolvers.py#L65-L69 # noqa
project_timestamp = project.modified_date.strftime("%Y%m%d.%H%M%S%f")
url_key = f"readthedocs.urls.fake.{project.slug}.{project_timestamp}"

log.info(
"Setting URLConf",
project_slug=project.slug,
url_key=url_key,
urlconf=project.urlconf,
)
if url_key not in sys.modules:
sys.modules[url_key] = project.proxito_urlconf
request.urlconf = url_key

return None

def add_hosting_integrations_headers(self, request, response):
Expand Down
10 changes: 0 additions & 10 deletions readthedocs/proxito/tests/test_custom_path_prefixes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from django.test.utils import override_settings
from django_dynamic_fixture import get

from readthedocs.projects.models import Feature
from readthedocs.proxito.tests.base import BaseDocServing


Expand All @@ -11,14 +9,6 @@
RTD_EXTERNAL_VERSION_DOMAIN="readthedocs.build",
)
class TestCustomPathPrefixes(BaseDocServing):
def setUp(self):
super().setUp()
get(
Feature,
feature_id=Feature.USE_UNRESOLVER_WITH_PROXITO,
default_true=True,
future_default_true=True,
)

def test_custom_prefix_multi_version_project(self):
self.project.custom_prefix = "/custom/prefix/"
Expand Down
Loading