From ea93e9e18886f6fc78116c64a1197be8ab61f6fd Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Fri, 26 Jan 2024 14:37:31 +0100 Subject: [PATCH 01/17] Addons: sorting algorithm for versions customizable on flyout Allow users to choose one of the pre-defined algorithms: - Lexicographically - SemVer (Read the Docs) - CalVer - Custom pattern The sorting algorithm is implemented in the backend. So, the list returned under `addons.flyout.versions` will be sorted acordingly the algorithm the user chose. There is no need to do anything extra in the front-end. The algorithm follows the next general rule: _"all the versions that don't match the pattern defined, are considered invalid and sorted lexicographically between them and added to the end of the list"_. That means that valid versions will appear always first in the list and sorted together with other _valid versions_. There is one _key feature_ here and is that the user can define any pattern supported by `bumpver`, which is the module we use behind the scenes to perform the sorting. See https://github.com/mbarkhau/bumpver#pattern-examples On the other hand, `SemVer (Read the Docs)` is implemented used the exact same code we were using for the old flyout implementation. This is mainly to keep backward compatibility, but its usage is not recommended since it's pretty hard to explain to users and hide non-clear/weird behavior. Closes https://github.com/readthedocs/addons/issues/222 --- readthedocs/projects/constants.py | 20 +++++ readthedocs/projects/forms.py | 15 ++++ .../migrations/0114_addons_flyout_sorting.py | 37 ++++++++ readthedocs/projects/models.py | 13 +++ readthedocs/projects/version_handling.py | 88 ++++++++++++++++++- readthedocs/proxito/views/hosting.py | 44 +++++++++- requirements/pip.in | 3 + 7 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 readthedocs/projects/migrations/0114_addons_flyout_sorting.py diff --git a/readthedocs/projects/constants.py b/readthedocs/projects/constants.py index f9256de3814..4509969ef21 100644 --- a/readthedocs/projects/constants.py +++ b/readthedocs/projects/constants.py @@ -431,3 +431,23 @@ _("Single version without translations (/)"), ), ) + + +ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY = "lexicographically" +# Compatibility to keep the behavior of the old flyout. +# This isn't a good algorithm, but it's a way to keep the old behavior in case we need it. +ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE = "semver-readthedocs-compatible" +# https://pypi.org/project/packaging/ +ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING = "python-packaging" +ADDONS_FLYOUT_SORTING_CALVER = "calver" +# Let the user to define a custom pattern and use BumpVer to parse and sort the versions. +# https://github.com/mbarkhau/bumpver#pattern-examples +ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN = "custom-pattern" + +ADDONS_FLYOUT_SORTING_CHOICES = ( + (ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY, "Lexicographically"), + (ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE, "SemVer (Read the Docs)"), + (ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING, "Python Packaging (PEP 440 and PEP 425)"), + (ADDONS_FLYOUT_SORTING_CALVER, "CalVer (YYYY.0M.0M)"), + (ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN, "Define your own pattern"), +) diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 3287b847def..727c2ffef2c 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -18,6 +18,7 @@ from readthedocs.integrations.models import Integration from readthedocs.invitations.models import Invitation from readthedocs.oauth.models import RemoteRepository +from readthedocs.projects.constants import ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN from readthedocs.projects.models import ( AddonsConfig, Domain, @@ -498,6 +499,8 @@ class Meta: "doc_diff_enabled", "external_version_warning_enabled", "flyout_enabled", + "flyout_sorting", + "flyout_sorting_custom_pattern", "hotkeys_enabled", "search_enabled", "stable_latest_version_warning_enabled", @@ -522,6 +525,18 @@ def __init__(self, *args, **kwargs): kwargs["instance"] = addons super().__init__(*args, **kwargs) + def clean(self): + if ( + self.cleaned_data["flyout_sorting"] == ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN + and not self.cleaned_data["flyout_sorting_custom_pattern"] + ): + raise forms.ValidationError( + _( + "The flyout sorting custom pattern is required when selecting a custom pattern." + ), + ) + return super().clean() + def clean_project(self): return self.project diff --git a/readthedocs/projects/migrations/0114_addons_flyout_sorting.py b/readthedocs/projects/migrations/0114_addons_flyout_sorting.py new file mode 100644 index 00000000000..e28db8001c4 --- /dev/null +++ b/readthedocs/projects/migrations/0114_addons_flyout_sorting.py @@ -0,0 +1,37 @@ +# Generated by Django 4.2.9 on 2024-01-26 12:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("projects", "0113_disable_analytics_addons"), + ] + + operations = [ + migrations.AddField( + model_name="addonsconfig", + name="flyout_sorting_custom_pattern", + field=models.CharField( + blank=True, + default=None, + help_text="Sorting pattern supported by BumpVer", + max_length=32, + null=True, + ), + ), + migrations.AlterField( + model_name="addonsconfig", + name="flyout_sorting", + field=models.CharField( + choices=[ + ("lexicographically", "Lexicographically"), + ("semver-readthedocs-compatible", "SemVer (Read the Docs)"), + ("python-packaging", "Python Packaging (PEP 440 and PEP 425)"), + ("calver", "CalVer (YYYY.0M.0M)"), + ("custom-pattern", "Define your own pattern"), + ], + default="lexicographically", + ), + ), + ] diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 82ded4eb40f..32c5ee304e3 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -61,6 +61,8 @@ from readthedocs.vcs_support.backends import backend_cls from .constants import ( + ADDONS_FLYOUT_SORTING_CHOICES, + ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY, DOWNLOADABLE_MEDIA_TYPES, MEDIA_TYPES, MULTIPLE_VERSIONS_WITH_TRANSLATIONS, @@ -179,6 +181,17 @@ class AddonsConfig(TimeStampedModel): # Flyout flyout_enabled = models.BooleanField(default=True) + flyout_sorting = models.CharField( + choices=ADDONS_FLYOUT_SORTING_CHOICES, + default=ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY, + ) + flyout_sorting_custom_pattern = models.CharField( + max_length=32, + default=None, + null=True, + blank=True, + help_text="Sorting pattern supported by BumpVer", + ) # Hotkeys hotkeys_enabled = models.BooleanField(default=True) diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index 6efbd38effe..a3cd8594789 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -1,7 +1,10 @@ """Project version handling.""" +import operator import unicodedata -from packaging.version import InvalidVersion, Version +from bumpver.v2version import parse_version_info +from bumpver.version import PatternError +from packaging.version import InvalidVersion, Version, parse from readthedocs.builds.constants import LATEST_VERBOSE_NAME, STABLE_VERBOSE_NAME, TAG from readthedocs.vcs_support.backends import backend_cls @@ -158,3 +161,86 @@ def determine_stable_version(version_list): version_obj, comparable = versions[0] return version_obj return None + + +def sort_versions_python_packaging(version_list): + """ + Sort Read the Docs versions list using ``packaging`` algorithm. + + All the invalid version (raise ``InvalidVersion``) are added at the end + sorted lexicographically. + + https://pypi.org/project/packaging/ + https://packaging.python.org/en/latest/specifications/version-specifiers/ + """ + lexicographically_sorted_version_list = sorted( + version_list, + key=operator.attrgetter("slug"), + ) + + valid_versions = [] + invalid_versions = [] + for i, version in enumerate(lexicographically_sorted_version_list): + try: + valid_versions.append((version, Version(version.slug))) + except InvalidVersion: + # When the version is invalid, we put it at the end while keeping + # the lexicographically sorting between the invalid ones. + invalid_versions.append((version, parse(str(100000 + i)))) + + return [ + item[0] + for item in sorted(valid_versions, key=operator.itemgetter(1)) + + invalid_versions + ] + + +def sort_versions_calver(version_list): + """ + Sort Read the Docs versions using CalVer pattern: ``YYYY.0M.0M``. + + All the invalid version are added at the end sorted lexicographically. + """ + raw_pattern = "YYYY.0M.0D" + return sort_versions_custom_pattern(version_list, raw_pattern) + + +def sort_versions_custom_pattern(version_list, raw_pattern): + """ + Sort Read the Docs versions using a custom pattern. + + All the invalid version (raise ``PatternError``) are added at the end + sorted lexicographically. + + It uses ``Bumpver`` behinds the scenes for the parsing and sorting. + https://github.com/mbarkhau/bumpver + """ + raw_pattern = "YYYY.0M.0D" + lexicographically_sorted_version_list = sorted( + version_list, + key=operator.attrgetter("slug"), + ) + + valid_versions = [] + invalid_versions = [] + for i, version in enumerate(lexicographically_sorted_version_list): + try: + valid_versions.append( + ( + version, + parse_version_info( + version.slug, + raw_pattern=raw_pattern, + ), + ) + ) + except PatternError: + # When the version is invalid, we put it at the end while keeping + # the lexicographically sorting between the invalid ones. + invalid_versions.append((version, parse(str(100000 + i)))) + + return [ + item[0] + for item in sorted(valid_versions, key=operator.itemgetter(1)) + + invalid_versions + ] diff --git a/readthedocs/proxito/views/hosting.py b/readthedocs/proxito/views/hosting.py index f3366f83f47..ba1c8eed812 100644 --- a/readthedocs/proxito/views/hosting.py +++ b/readthedocs/proxito/views/hosting.py @@ -23,7 +23,19 @@ from readthedocs.core.resolver import Resolver from readthedocs.core.unresolver import UnresolverError, unresolver from readthedocs.core.utils.extend import SettingsOverrideObject +from readthedocs.projects.constants import ( + ADDONS_FLYOUT_SORTING_CALVER, + ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN, + ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING, + ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE, +) from readthedocs.projects.models import AddonsConfig, Project +from readthedocs.projects.version_handling import ( + comparable_version, + sort_versions_calver, + sort_versions_custom_pattern, + sort_versions_python_packaging, +) log = structlog.get_logger(__name__) # noqa @@ -265,6 +277,32 @@ def _v0(self, project, version, build, filename, url, user): .only("slug", "type") .order_by("slug") ) + if ( + project.addons.flyout_sorting + == ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE + ): + versions_active_built_not_hidden = sorted( + versions_active_built_not_hidden, + key=lambda version: comparable_version( + version.verbose_name, + repo_type=project.repo_type, + ), + ) + elif ( + project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING + ): + versions_active_built_not_hidden = sort_versions_python_packaging( + versions_active_built_not_hidden + ) + elif project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_CALVER: + versions_active_built_not_hidden = sort_versions_calver( + versions_active_built_not_hidden + ) + elif project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN: + versions_active_built_not_hidden = sort_versions_custom_pattern( + versions_active_built_not_hidden, + project.addons.flyout_sorting_custom_pattern, + ) if version: version_downloads = version.get_downloads(pretty=True).items() @@ -332,9 +370,9 @@ def _v0(self, project, version, build, filename, url, user): # NOTE: I think we are moving away from these selectors # since we are doing floating noticications now. # "query_selector": "[role=main]", - "versions": list( - versions_active_built_not_hidden.values_list("slug", flat=True) - ), + "versions": [ + version_.slug for version_ in versions_active_built_not_hidden + ], }, "flyout": { "enabled": project.addons.flyout_enabled, diff --git a/requirements/pip.in b/requirements/pip.in index 26026dc12d0..363d46560bc 100644 --- a/requirements/pip.in +++ b/requirements/pip.in @@ -176,3 +176,6 @@ dparse gunicorn django-cacheops + +# Used by Addons for sorting patterns +bumpver From 5fb9b47c8edc2ee21834df7a4b33b5dec0d5fcd8 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 29 Jan 2024 13:01:07 +0100 Subject: [PATCH 02/17] Use alphabetically instead of lexicographically --- readthedocs/projects/constants.py | 4 ++-- .../migrations/0114_addons_flyout_sorting.py | 4 ++-- readthedocs/projects/version_handling.py | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/readthedocs/projects/constants.py b/readthedocs/projects/constants.py index 4509969ef21..d7577a904f5 100644 --- a/readthedocs/projects/constants.py +++ b/readthedocs/projects/constants.py @@ -433,7 +433,7 @@ ) -ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY = "lexicographically" +ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY = "alphabetically" # Compatibility to keep the behavior of the old flyout. # This isn't a good algorithm, but it's a way to keep the old behavior in case we need it. ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE = "semver-readthedocs-compatible" @@ -445,7 +445,7 @@ ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN = "custom-pattern" ADDONS_FLYOUT_SORTING_CHOICES = ( - (ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY, "Lexicographically"), + (ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY, "Alphabetically"), (ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE, "SemVer (Read the Docs)"), (ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING, "Python Packaging (PEP 440 and PEP 425)"), (ADDONS_FLYOUT_SORTING_CALVER, "CalVer (YYYY.0M.0M)"), diff --git a/readthedocs/projects/migrations/0114_addons_flyout_sorting.py b/readthedocs/projects/migrations/0114_addons_flyout_sorting.py index e28db8001c4..d65f2c59059 100644 --- a/readthedocs/projects/migrations/0114_addons_flyout_sorting.py +++ b/readthedocs/projects/migrations/0114_addons_flyout_sorting.py @@ -25,13 +25,13 @@ class Migration(migrations.Migration): name="flyout_sorting", field=models.CharField( choices=[ - ("lexicographically", "Lexicographically"), + ("alphabetically", "Alphabetically"), ("semver-readthedocs-compatible", "SemVer (Read the Docs)"), ("python-packaging", "Python Packaging (PEP 440 and PEP 425)"), ("calver", "CalVer (YYYY.0M.0M)"), ("custom-pattern", "Define your own pattern"), ], - default="lexicographically", + default="alphabetically", ), ), ] diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index a3cd8594789..d42bc9040a9 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -168,24 +168,24 @@ def sort_versions_python_packaging(version_list): Sort Read the Docs versions list using ``packaging`` algorithm. All the invalid version (raise ``InvalidVersion``) are added at the end - sorted lexicographically. + sorted alphabetically. https://pypi.org/project/packaging/ https://packaging.python.org/en/latest/specifications/version-specifiers/ """ - lexicographically_sorted_version_list = sorted( + alphabetically_sorted_version_list = sorted( version_list, key=operator.attrgetter("slug"), ) valid_versions = [] invalid_versions = [] - for i, version in enumerate(lexicographically_sorted_version_list): + for i, version in enumerate(alphabetically_sorted_version_list): try: valid_versions.append((version, Version(version.slug))) except InvalidVersion: # When the version is invalid, we put it at the end while keeping - # the lexicographically sorting between the invalid ones. + # the alphabetically sorting between the invalid ones. invalid_versions.append((version, parse(str(100000 + i)))) return [ @@ -199,7 +199,7 @@ def sort_versions_calver(version_list): """ Sort Read the Docs versions using CalVer pattern: ``YYYY.0M.0M``. - All the invalid version are added at the end sorted lexicographically. + All the invalid version are added at the end sorted alphabetically. """ raw_pattern = "YYYY.0M.0D" return sort_versions_custom_pattern(version_list, raw_pattern) @@ -210,20 +210,20 @@ def sort_versions_custom_pattern(version_list, raw_pattern): Sort Read the Docs versions using a custom pattern. All the invalid version (raise ``PatternError``) are added at the end - sorted lexicographically. + sorted alphabetically. It uses ``Bumpver`` behinds the scenes for the parsing and sorting. https://github.com/mbarkhau/bumpver """ raw_pattern = "YYYY.0M.0D" - lexicographically_sorted_version_list = sorted( + alphabetically_sorted_version_list = sorted( version_list, key=operator.attrgetter("slug"), ) valid_versions = [] invalid_versions = [] - for i, version in enumerate(lexicographically_sorted_version_list): + for i, version in enumerate(alphabetically_sorted_version_list): try: valid_versions.append( ( @@ -236,7 +236,7 @@ def sort_versions_custom_pattern(version_list, raw_pattern): ) except PatternError: # When the version is invalid, we put it at the end while keeping - # the lexicographically sorting between the invalid ones. + # the alphabetically sorting between the invalid ones. invalid_versions.append((version, parse(str(100000 + i)))) return [ From f32910349efd468b377c52b99e7e607a51fdffb9 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 29 Jan 2024 13:05:22 +0100 Subject: [PATCH 03/17] Link BumpVer pattern examples --- readthedocs/projects/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 32c5ee304e3..3d066e79065 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -190,7 +190,8 @@ class AddonsConfig(TimeStampedModel): default=None, null=True, blank=True, - help_text="Sorting pattern supported by BumpVer", + help_text="Sorting pattern supported by BumpVer " + '( See examples', ) # Hotkeys From fd016d6de0f48254d090f04b9ac0084bf469f24e Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 29 Jan 2024 13:09:30 +0100 Subject: [PATCH 04/17] Migration help_text --- readthedocs/projects/migrations/0114_addons_flyout_sorting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readthedocs/projects/migrations/0114_addons_flyout_sorting.py b/readthedocs/projects/migrations/0114_addons_flyout_sorting.py index d65f2c59059..8213f73b6d0 100644 --- a/readthedocs/projects/migrations/0114_addons_flyout_sorting.py +++ b/readthedocs/projects/migrations/0114_addons_flyout_sorting.py @@ -15,7 +15,8 @@ class Migration(migrations.Migration): field=models.CharField( blank=True, default=None, - help_text="Sorting pattern supported by BumpVer", + help_text="Sorting pattern supported by BumpVer " + '( See examples', max_length=32, null=True, ), From 5d40efb959ca4f09438f673680544a93cb63997a Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 29 Jan 2024 13:12:39 +0100 Subject: [PATCH 05/17] Add bumpver as requirements --- requirements/deploy.txt | 26 +++++++++++++++++++++----- requirements/docker.txt | 27 +++++++++++++++++++++------ requirements/docs.txt | 2 +- requirements/pip.txt | 17 +++++++++++++---- requirements/testing.txt | 30 ++++++++++++++++++++++-------- 5 files changed, 78 insertions(+), 24 deletions(-) diff --git a/requirements/deploy.txt b/requirements/deploy.txt index ee3e12a5eea..52d93cd1424 100644 --- a/requirements/deploy.txt +++ b/requirements/deploy.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/deploy.txt requirements/deploy.in +# pip-compile --output-file=requirements/deploy.txt --resolver=backtracking requirements/deploy.in # amqp==5.2.0 # via @@ -32,6 +32,8 @@ botocore==1.34.23 # -r requirements/pip.txt # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.txt celery==5.2.7 # via # -r requirements/pip.txt @@ -53,6 +55,7 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/pip.txt + # bumpver # celery # click-didyoumean # click-plugins @@ -69,6 +72,10 @@ click-repl==0.3.0 # via # -r requirements/pip.txt # celery +colorama==0.4.6 + # via + # -r requirements/pip.txt + # bumpver cron-descriptor==1.4.0 # via # -r requirements/pip.txt @@ -153,9 +160,7 @@ django-polymorphic==3.1.0 django-simple-history==3.0.0 # via -r requirements/pip.txt django-storages[boto3]==1.14.2 - # via - # -r requirements/pip.txt - # django-storages + # via -r requirements/pip.txt django-structlog==2.2.0 # via -r requirements/pip.txt django-taggit==5.0.1 @@ -232,6 +237,14 @@ kombu==5.3.5 # via # -r requirements/pip.txt # celery +lexid==2021.1006 + # via + # -r requirements/pip.txt + # bumpver +looseversion==1.3.0 + # via + # -r requirements/pip.txt + # bumpver lxml==5.1.0 # via # -r requirements/pip.txt @@ -286,7 +299,6 @@ pyjwt[crypto]==2.8.0 # via # -r requirements/pip.txt # django-allauth - # pyjwt pyquery==2.0.0 # via -r requirements/pip.txt python-crontab==3.0.0 @@ -369,6 +381,10 @@ structlog==23.2.0 # structlog-sentry structlog-sentry==2.0.3 # via -r requirements/deploy.in +toml==0.10.2 + # via + # -r requirements/pip.txt + # bumpver tomli==2.0.1 # via # -r requirements/pip.txt diff --git a/requirements/docker.txt b/requirements/docker.txt index 108613e6d26..4f3bda56707 100644 --- a/requirements/docker.txt +++ b/requirements/docker.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/docker.txt requirements/docker.in +# pip-compile --output-file=requirements/docker.txt --resolver=backtracking requirements/docker.in # amqp==5.2.0 # via @@ -34,6 +34,8 @@ botocore==1.34.23 # -r requirements/pip.txt # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.txt cachetools==5.3.2 # via tox celery==5.2.7 @@ -58,6 +60,7 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/pip.txt + # bumpver # celery # click-didyoumean # click-plugins @@ -75,7 +78,10 @@ click-repl==0.3.0 # -r requirements/pip.txt # celery colorama==0.4.6 - # via tox + # via + # -r requirements/pip.txt + # bumpver + # tox cron-descriptor==1.4.0 # via # -r requirements/pip.txt @@ -164,9 +170,7 @@ django-polymorphic==3.1.0 django-simple-history==3.0.0 # via -r requirements/pip.txt django-storages[boto3]==1.14.2 - # via - # -r requirements/pip.txt - # django-storages + # via -r requirements/pip.txt django-structlog==2.2.0 # via -r requirements/pip.txt django-taggit==5.0.1 @@ -248,6 +252,14 @@ kombu==5.3.5 # via # -r requirements/pip.txt # celery +lexid==2021.1006 + # via + # -r requirements/pip.txt + # bumpver +looseversion==1.3.0 + # via + # -r requirements/pip.txt + # bumpver lxml==5.1.0 # via # -r requirements/pip.txt @@ -315,7 +327,6 @@ pyjwt[crypto]==2.8.0 # via # -r requirements/pip.txt # django-allauth - # pyjwt pyproject-api==1.6.1 # via tox pyquery==2.0.0 @@ -399,6 +410,10 @@ structlog==23.2.0 # via # -r requirements/pip.txt # django-structlog +toml==0.10.2 + # via + # -r requirements/pip.txt + # bumpver tomli==2.0.1 # via # -r requirements/pip.txt diff --git a/requirements/docs.txt b/requirements/docs.txt index 54b681efd49..9bf1f73aa7c 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/docs.txt requirements/docs.in +# pip-compile --output-file=requirements/docs.txt --resolver=backtracking requirements/docs.in # alabaster==0.7.16 # via sphinx diff --git a/requirements/pip.txt b/requirements/pip.txt index bc92f0e02e6..c3c5ac54fe6 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/pip.txt requirements/pip.in +# pip-compile --output-file=requirements/pip.txt --resolver=backtracking requirements/pip.in # amqp==5.2.0 # via kombu @@ -20,6 +20,8 @@ botocore==1.34.23 # via # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.in celery==5.2.7 # via # -r requirements/pip.in @@ -34,6 +36,7 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via + # bumpver # celery # click-didyoumean # click-plugins @@ -44,6 +47,8 @@ click-plugins==1.1.1 # via celery click-repl==0.3.0 # via celery +colorama==0.4.6 + # via bumpver cron-descriptor==1.4.0 # via django-celery-beat cryptography==41.0.7 @@ -172,6 +177,10 @@ jsonfield==3.1.0 # dj-stripe kombu==5.3.5 # via celery +lexid==2021.1006 + # via bumpver +looseversion==1.3.0 + # via bumpver lxml==5.1.0 # via pyquery markdown==3.5.2 @@ -198,9 +207,7 @@ pycparser==2.21 pygments==2.17.2 # via -r requirements/pip.in pyjwt[crypto]==2.8.0 - # via - # django-allauth - # pyjwt + # via django-allauth pyquery==2.0.0 # via -r requirements/pip.in python-crontab==3.0.0 @@ -267,6 +274,8 @@ structlog==23.2.0 # via # -r requirements/pip.in # django-structlog +toml==0.10.2 + # via bumpver tomli==2.0.1 # via dparse typing-extensions==4.9.0 diff --git a/requirements/testing.txt b/requirements/testing.txt index fc84031cb0b..6def8e56678 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/testing.txt requirements/testing.in +# pip-compile --output-file=requirements/testing.txt --resolver=backtracking requirements/testing.in # alabaster==0.7.16 # via sphinx @@ -34,6 +34,8 @@ botocore==1.34.23 # -r requirements/pip.txt # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.txt celery==5.2.7 # via # -r requirements/pip.txt @@ -54,6 +56,7 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/pip.txt + # bumpver # celery # click-didyoumean # click-plugins @@ -70,10 +73,12 @@ click-repl==0.3.0 # via # -r requirements/pip.txt # celery -coverage[toml]==7.4.0 +colorama==0.4.6 # via - # coverage - # pytest-cov + # -r requirements/pip.txt + # bumpver +coverage[toml]==7.4.0 + # via pytest-cov cron-descriptor==1.4.0 # via # -r requirements/pip.txt @@ -158,9 +163,7 @@ django-polymorphic==3.1.0 django-simple-history==3.0.0 # via -r requirements/pip.txt django-storages[boto3]==1.14.2 - # via - # -r requirements/pip.txt - # django-storages + # via -r requirements/pip.txt django-structlog==2.2.0 # via -r requirements/pip.txt django-taggit==5.0.1 @@ -239,6 +242,14 @@ kombu==5.3.5 # via # -r requirements/pip.txt # celery +lexid==2021.1006 + # via + # -r requirements/pip.txt + # bumpver +looseversion==1.3.0 + # via + # -r requirements/pip.txt + # bumpver lxml==5.1.0 # via # -r requirements/pip.txt @@ -288,7 +299,6 @@ pyjwt[crypto]==2.8.0 # via # -r requirements/pip.txt # django-allauth - # pyjwt pyquery==2.0.0 # via -r requirements/pip.txt pytest==7.4.4 @@ -401,6 +411,10 @@ structlog==23.2.0 # via # -r requirements/pip.txt # django-structlog +toml==0.10.2 + # via + # -r requirements/pip.txt + # bumpver tomli==2.0.1 # via # -r requirements/pip.txt From 2a2b6f519d463b11d3a397277e7d30fd1604d7f3 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 6 Feb 2024 13:11:31 +0100 Subject: [PATCH 06/17] Addons: allow to put `stable` and `latest` first when sorting --- readthedocs/projects/forms.py | 1 + ...115_addons_flyout_sorting_stable_latest.py | 20 ++++++ readthedocs/projects/models.py | 4 ++ readthedocs/projects/version_handling.py | 61 +++++++++++++------ readthedocs/proxito/views/hosting.py | 16 +++-- 5 files changed, 79 insertions(+), 23 deletions(-) create mode 100644 readthedocs/projects/migrations/0115_addons_flyout_sorting_stable_latest.py diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 727c2ffef2c..f880ec23e56 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -500,6 +500,7 @@ class Meta: "external_version_warning_enabled", "flyout_enabled", "flyout_sorting", + "flyout_sorting_stable_latest_at_beginning", "flyout_sorting_custom_pattern", "hotkeys_enabled", "search_enabled", diff --git a/readthedocs/projects/migrations/0115_addons_flyout_sorting_stable_latest.py b/readthedocs/projects/migrations/0115_addons_flyout_sorting_stable_latest.py new file mode 100644 index 00000000000..08bfed1c48b --- /dev/null +++ b/readthedocs/projects/migrations/0115_addons_flyout_sorting_stable_latest.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.9 on 2024-02-05 12:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("projects", "0114_addons_flyout_sorting"), + ] + + operations = [ + migrations.AddField( + model_name="addonsconfig", + name="flyout_sorting_stable_latest_at_beginning", + field=models.BooleanField( + default=True, + help_text="Show stable and latest at the beginning", + ), + ), + ] diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 3d066e79065..485215fb87a 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -193,6 +193,10 @@ class AddonsConfig(TimeStampedModel): help_text="Sorting pattern supported by BumpVer " '( See examples', ) + flyout_sorting_stable_latest_at_beginning = models.BooleanField( + default=True, + help_text="Show stable and latest at the beginning", + ) # Hotkeys hotkeys_enabled = models.BooleanField(default=True) diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index d42bc9040a9..d5656edf48e 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -4,9 +4,15 @@ from bumpver.v2version import parse_version_info from bumpver.version import PatternError -from packaging.version import InvalidVersion, Version, parse - -from readthedocs.builds.constants import LATEST_VERBOSE_NAME, STABLE_VERBOSE_NAME, TAG +from packaging.version import InvalidVersion, Version + +from readthedocs.builds.constants import ( + LATEST, + LATEST_VERBOSE_NAME, + STABLE, + STABLE_VERBOSE_NAME, + TAG, +) from readthedocs.vcs_support.backends import backend_cls @@ -163,7 +169,7 @@ def determine_stable_version(version_list): return None -def sort_versions_python_packaging(version_list): +def sort_versions_python_packaging(version_list, stable_latest_at_beginning): """ Sort Read the Docs versions list using ``packaging`` algorithm. @@ -178,34 +184,46 @@ def sort_versions_python_packaging(version_list): key=operator.attrgetter("slug"), ) + initial_versions = [] + valid_versions = [] invalid_versions = [] for i, version in enumerate(alphabetically_sorted_version_list): + if stable_latest_at_beginning: + if version.slug in (STABLE, LATEST): + # It relies on the version list sorted alphabetically first ("l" comes first than "s") + initial_versions.insert(0, (version, version.slug)) + continue + try: valid_versions.append((version, Version(version.slug))) except InvalidVersion: # When the version is invalid, we put it at the end while keeping # the alphabetically sorting between the invalid ones. - invalid_versions.append((version, parse(str(100000 + i)))) + invalid_versions.append((version, None)) - return [ - item[0] - for item in sorted(valid_versions, key=operator.itemgetter(1)) + all_versions = ( + initial_versions + + sorted(valid_versions, key=operator.itemgetter(1)) + invalid_versions - ] + ) + + return [item[0] for item in all_versions if item[0] is not None] -def sort_versions_calver(version_list): +def sort_versions_calver(version_list, stable_latest_at_beginning): """ Sort Read the Docs versions using CalVer pattern: ``YYYY.0M.0M``. All the invalid version are added at the end sorted alphabetically. """ raw_pattern = "YYYY.0M.0D" - return sort_versions_custom_pattern(version_list, raw_pattern) + return sort_versions_custom_pattern( + version_list, raw_pattern, stable_latest_at_beginning + ) -def sort_versions_custom_pattern(version_list, raw_pattern): +def sort_versions_custom_pattern(version_list, raw_pattern, stable_latest_at_beginning): """ Sort Read the Docs versions using a custom pattern. @@ -221,9 +239,16 @@ def sort_versions_custom_pattern(version_list, raw_pattern): key=operator.attrgetter("slug"), ) + initial_versions = [] valid_versions = [] invalid_versions = [] for i, version in enumerate(alphabetically_sorted_version_list): + if stable_latest_at_beginning: + if version.slug in (STABLE, LATEST): + # It relies on the version list sorted alphabetically first ("l" comes first than "s") + initial_versions.insert(0, (version, version.slug)) + continue + try: valid_versions.append( ( @@ -237,10 +262,12 @@ def sort_versions_custom_pattern(version_list, raw_pattern): except PatternError: # When the version is invalid, we put it at the end while keeping # the alphabetically sorting between the invalid ones. - invalid_versions.append((version, parse(str(100000 + i)))) + invalid_versions.append((version, None)) - return [ - item[0] - for item in sorted(valid_versions, key=operator.itemgetter(1)) + all_versions = ( + initial_versions + + sorted(valid_versions, key=operator.itemgetter(1)) + invalid_versions - ] + ) + + return [item[0] for item in all_versions if item[0] is not None] diff --git a/readthedocs/proxito/views/hosting.py b/readthedocs/proxito/views/hosting.py index ba1c8eed812..fa02b5ef7cd 100644 --- a/readthedocs/proxito/views/hosting.py +++ b/readthedocs/proxito/views/hosting.py @@ -265,6 +265,10 @@ def _v0(self, project, version, build, filename, url, user): version_downloads = [] versions_active_built_not_hidden = Version.objects.none() + # Automatically create an AddonsConfig with the default values for + # projects that don't have one already + AddonsConfig.objects.get_or_create(project=project) + if project.supports_multiple_versions: versions_active_built_not_hidden = ( Version.internal.public( @@ -277,6 +281,7 @@ def _v0(self, project, version, build, filename, url, user): .only("slug", "type") .order_by("slug") ) + if ( project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE @@ -292,16 +297,19 @@ def _v0(self, project, version, build, filename, url, user): project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING ): versions_active_built_not_hidden = sort_versions_python_packaging( - versions_active_built_not_hidden + versions_active_built_not_hidden, + project.addons.flyout_sorting_stable_latest_at_beginning, ) elif project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_CALVER: versions_active_built_not_hidden = sort_versions_calver( - versions_active_built_not_hidden + versions_active_built_not_hidden, + project.addons.flyout_sorting_stable_latest_at_beginning, ) elif project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN: versions_active_built_not_hidden = sort_versions_custom_pattern( versions_active_built_not_hidden, project.addons.flyout_sorting_custom_pattern, + project.addons.flyout_sorting_stable_latest_at_beginning, ) if version: @@ -318,10 +326,6 @@ def _v0(self, project, version, build, filename, url, user): # en (original), es, ru project_translations = itertools.chain([main_project], project_translations) - # Automatically create an AddonsConfig with the default values for - # projects that don't have one already - AddonsConfig.objects.get_or_create(project=project) - data = { "api_version": "0", "comment": ( From 30455a21d584f4981d4a4114b436424d367bcd95 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Mar 2024 13:41:56 +0100 Subject: [PATCH 07/17] Mark the string as translatable --- readthedocs/projects/constants.py | 521 +++++++++++++++--------------- readthedocs/projects/models.py | 4 +- 2 files changed, 260 insertions(+), 265 deletions(-) diff --git a/readthedocs/projects/constants.py b/readthedocs/projects/constants.py index d7577a904f5..87cf6d59ef8 100644 --- a/readthedocs/projects/constants.py +++ b/readthedocs/projects/constants.py @@ -10,18 +10,18 @@ from django.utils.translation import gettext_lazy as _ -SPHINX = 'sphinx' -MKDOCS = 'mkdocs' -SPHINX_HTMLDIR = 'sphinx_htmldir' -SPHINX_SINGLEHTML = 'sphinx_singlehtml' +SPHINX = "sphinx" +MKDOCS = "mkdocs" +SPHINX_HTMLDIR = "sphinx_htmldir" +SPHINX_SINGLEHTML = "sphinx_singlehtml" # This type is defined by the users in their mkdocs.yml file. MKDOCS_HTML = "mkdocs_html" GENERIC = "generic" DOCUMENTATION_CHOICES = ( - (SPHINX, _('Sphinx Html')), - (MKDOCS, _('Mkdocs')), - (SPHINX_HTMLDIR, _('Sphinx HtmlDir')), - (SPHINX_SINGLEHTML, _('Sphinx Single Page HTML')), + (SPHINX, _("Sphinx Html")), + (MKDOCS, _("Mkdocs")), + (SPHINX_HTMLDIR, _("Sphinx HtmlDir")), + (SPHINX_SINGLEHTML, _("Sphinx Single Page HTML")), ) DOCTYPE_CHOICES = DOCUMENTATION_CHOICES + ( (MKDOCS_HTML, _("Mkdocs Html Pages")), @@ -29,11 +29,11 @@ ) -MEDIA_TYPE_HTML = 'html' -MEDIA_TYPE_PDF = 'pdf' -MEDIA_TYPE_EPUB = 'epub' -MEDIA_TYPE_HTMLZIP = 'htmlzip' -MEDIA_TYPE_JSON = 'json' +MEDIA_TYPE_HTML = "html" +MEDIA_TYPE_PDF = "pdf" +MEDIA_TYPE_EPUB = "epub" +MEDIA_TYPE_HTMLZIP = "htmlzip" +MEDIA_TYPE_JSON = "json" DOWNLOADABLE_MEDIA_TYPES = ( MEDIA_TYPE_PDF, MEDIA_TYPE_EPUB, @@ -51,24 +51,24 @@ BUILD_COMMANDS_OUTPUT_PATH_HTML = os.path.join(BUILD_COMMANDS_OUTPUT_PATH, "html") SAMPLE_FILES = ( - ('Installation', 'projects/samples/installation.rst.html'), - ('Getting started', 'projects/samples/getting_started.rst.html'), + ("Installation", "projects/samples/installation.rst.html"), + ("Getting started", "projects/samples/getting_started.rst.html"), ) SCRAPE_CONF_SETTINGS = [ - 'copyright', - 'project', - 'version', - 'release', - 'source_suffix', - 'html_theme', - 'extensions', + "copyright", + "project", + "version", + "release", + "source_suffix", + "html_theme", + "extensions", ] HEADING_MARKUP = ( - (1, '='), - (2, '-'), - (3, '^'), + (1, "="), + (2, "-"), + (3, "^"), (4, '"'), ) @@ -76,208 +76,208 @@ DELETED_STATUS = 99 STATUS_CHOICES = ( - (LIVE_STATUS, _('Live')), - (DELETED_STATUS, _('Deleted')), + (LIVE_STATUS, _("Live")), + (DELETED_STATUS, _("Deleted")), ) -REPO_TYPE_GIT = 'git' -REPO_TYPE_SVN = 'svn' -REPO_TYPE_HG = 'hg' -REPO_TYPE_BZR = 'bzr' +REPO_TYPE_GIT = "git" +REPO_TYPE_SVN = "svn" +REPO_TYPE_HG = "hg" +REPO_TYPE_BZR = "bzr" REPO_CHOICES = ( - (REPO_TYPE_GIT, _('Git')), - (REPO_TYPE_SVN, _('Subversion')), - (REPO_TYPE_HG, _('Mercurial')), - (REPO_TYPE_BZR, _('Bazaar')), + (REPO_TYPE_GIT, _("Git")), + (REPO_TYPE_SVN, _("Subversion")), + (REPO_TYPE_HG, _("Mercurial")), + (REPO_TYPE_BZR, _("Bazaar")), ) -PUBLIC = 'public' -PRIVATE = 'private' +PUBLIC = "public" +PRIVATE = "private" PRIVACY_CHOICES = ( - (PUBLIC, _('Public')), - (PRIVATE, _('Private')), + (PUBLIC, _("Public")), + (PRIVATE, _("Private")), ) IMPORTANT_VERSION_FILTERS = { - 'slug': 'important', + "slug": "important", } # in the future this constant can be replaced with a implementation that # detect all available Python interpreters in the fly (Maybe using # update-alternatives linux tool family?). PYTHON_CHOICES = ( - ('python', _('CPython 2.x')), - ('python3', _('CPython 3.x')), + ("python", _("CPython 2.x")), + ("python3", _("CPython 3.x")), ) # Via http://sphinx-doc.org/latest/config.html#confval-language # Languages supported for the lang_slug in the URL # Translations for builtin Sphinx messages only available for a subset of these LANGUAGES = ( - ('aa', 'Afar'), - ('ab', 'Abkhaz'), - ('acr', 'Achi'), - ('af', 'Afrikaans'), - ('agu', 'Awakateko'), - ('am', 'Amharic'), - ('ar', 'Arabic'), - ('as', 'Assamese'), - ('ay', 'Aymara'), - ('az', 'Azerbaijani'), - ('ba', 'Bashkir'), - ('be', 'Belarusian'), - ('bg', 'Bulgarian'), - ('bh', 'Bihari'), - ('bi', 'Bislama'), - ('bn', 'Bengali'), - ('bo', 'Tibetan'), - ('br', 'Breton'), - ('ca', 'Catalan'), - ('caa', 'Ch\'orti\''), - ('cac', 'Chuj'), - ('cab', 'Garífuna'), - ('cak', 'Kaqchikel'), - ('co', 'Corsican'), - ('cs', 'Czech'), - ('cy', 'Welsh'), - ('da', 'Danish'), - ('de', 'German'), - ('dz', 'Dzongkha'), - ('el', 'Greek'), - ('en', 'English'), - ('eo', 'Esperanto'), - ('es', 'Spanish'), - ('et', 'Estonian'), - ('eu', 'Basque'), - ('fa', 'Iranian'), - ('fi', 'Finnish'), - ('fj', 'Fijian'), - ('fo', 'Faroese'), - ('fr', 'French'), - ('fy', 'Western Frisian'), - ('ga', 'Irish'), - ('gd', 'Scottish Gaelic'), - ('gl', 'Galician'), - ('gn', 'Guarani'), - ('gu', 'Gujarati'), - ('ha', 'Hausa'), - ('hi', 'Hindi'), - ('he', 'Hebrew'), - ('hr', 'Croatian'), - ('hu', 'Hungarian'), - ('hy', 'Armenian'), - ('ia', 'Interlingua'), - ('id', 'Indonesian'), - ('ie', 'Interlingue'), - ('ik', 'Inupiaq'), - ('is', 'Icelandic'), - ('it', 'Italian'), - ('itz', 'Itza\''), - ('iu', 'Inuktitut'), - ('ixl', 'Ixil'), - ('ja', 'Japanese'), - ('jac', 'Popti\''), - ('jv', 'Javanese'), - ('ka', 'Georgian'), - ('kjb', 'Q\'anjob\'al'), - ('kek', 'Q\'eqchi\''), - ('kk', 'Kazakh'), - ('kl', 'Kalaallisut'), - ('km', 'Khmer'), - ('kn', 'Kannada'), - ('knj', 'Akateko'), - ('ko', 'Korean'), - ('ks', 'Kashmiri'), - ('ku', 'Kurdish'), - ('ky', 'Kyrgyz'), - ('la', 'Latin'), - ('ln', 'Lingala'), - ('lo', 'Lao'), - ('lt', 'Lithuanian'), - ('lv', 'Latvian'), - ('mam', 'Mam'), - ('mg', 'Malagasy'), - ('mi', 'Maori'), - ('mk', 'Macedonian'), - ('ml', 'Malayalam'), - ('mn', 'Mongolian'), - ('mop', 'Mopan'), - ('mr', 'Marathi'), - ('ms', 'Malay'), - ('mt', 'Maltese'), - ('my', 'Burmese'), - ('na', 'Nauru'), - ('ne', 'Nepali'), - ('nl', 'Dutch'), - ('no', 'Norwegian'), - ('oc', 'Occitan'), - ('om', 'Oromo'), - ('or', 'Oriya'), - ('pa', 'Panjabi'), - ('pl', 'Polish'), - ('pnb', 'Western Punjabi'), - ('poc', 'Poqomam'), - ('poh', 'Poqomchi'), - ('ps', 'Pashto'), - ('pt', 'Portuguese'), - ('qu', 'Quechua'), - ('quc', 'K\'iche\''), - ('qum', 'Sipakapense'), - ('quv', 'Sakapulteko'), - ('rm', 'Romansh'), - ('rn', 'Kirundi'), - ('ro', 'Romanian'), - ('ru', 'Russian'), - ('rw', 'Kinyarwanda'), - ('sa', 'Sanskrit'), - ('sd', 'Sindhi'), - ('sg', 'Sango'), - ('si', 'Sinhala'), - ('sk', 'Slovak'), - ('skr', 'Saraiki'), - ('sl', 'Slovenian'), - ('sm', 'Samoan'), - ('sn', 'Shona'), - ('so', 'Somali'), - ('sq', 'Albanian'), - ('sr', 'Serbian'), - ('ss', 'Swati'), - ('st', 'Southern Sotho'), - ('su', 'Sudanese'), - ('sv', 'Swedish'), - ('sw', 'Swahili'), - ('ta', 'Tamil'), - ('te', 'Telugu'), - ('tg', 'Tajik'), - ('th', 'Thai'), - ('ti', 'Tigrinya'), - ('tk', 'Turkmen'), - ('tl', 'Tagalog'), - ('tn', 'Tswana'), - ('to', 'Tonga'), - ('tr', 'Turkish'), - ('ts', 'Tsonga'), - ('tt', 'Tatar'), - ('ttc', 'Tektiteko'), - ('tzj', 'Tz\'utujil'), - ('tw', 'Twi'), - ('ug', 'Uyghur'), - ('uk', 'Ukrainian'), - ('ur', 'Urdu'), - ('usp', 'Uspanteko'), - ('uz', 'Uzbek'), - ('vi', 'Vietnamese'), - ('vo', 'Volapuk'), - ('wo', 'Wolof'), - ('xh', 'Xhosa'), - ('xin', 'Xinka'), - ('yi', 'Yiddish'), - ('yo', 'Yoruba'), - ('za', 'Zhuang'), - ('zh', 'Chinese'), - ('zu', 'Zulu'), + ("aa", "Afar"), + ("ab", "Abkhaz"), + ("acr", "Achi"), + ("af", "Afrikaans"), + ("agu", "Awakateko"), + ("am", "Amharic"), + ("ar", "Arabic"), + ("as", "Assamese"), + ("ay", "Aymara"), + ("az", "Azerbaijani"), + ("ba", "Bashkir"), + ("be", "Belarusian"), + ("bg", "Bulgarian"), + ("bh", "Bihari"), + ("bi", "Bislama"), + ("bn", "Bengali"), + ("bo", "Tibetan"), + ("br", "Breton"), + ("ca", "Catalan"), + ("caa", "Ch'orti'"), + ("cac", "Chuj"), + ("cab", "Garífuna"), + ("cak", "Kaqchikel"), + ("co", "Corsican"), + ("cs", "Czech"), + ("cy", "Welsh"), + ("da", "Danish"), + ("de", "German"), + ("dz", "Dzongkha"), + ("el", "Greek"), + ("en", "English"), + ("eo", "Esperanto"), + ("es", "Spanish"), + ("et", "Estonian"), + ("eu", "Basque"), + ("fa", "Iranian"), + ("fi", "Finnish"), + ("fj", "Fijian"), + ("fo", "Faroese"), + ("fr", "French"), + ("fy", "Western Frisian"), + ("ga", "Irish"), + ("gd", "Scottish Gaelic"), + ("gl", "Galician"), + ("gn", "Guarani"), + ("gu", "Gujarati"), + ("ha", "Hausa"), + ("hi", "Hindi"), + ("he", "Hebrew"), + ("hr", "Croatian"), + ("hu", "Hungarian"), + ("hy", "Armenian"), + ("ia", "Interlingua"), + ("id", "Indonesian"), + ("ie", "Interlingue"), + ("ik", "Inupiaq"), + ("is", "Icelandic"), + ("it", "Italian"), + ("itz", "Itza'"), + ("iu", "Inuktitut"), + ("ixl", "Ixil"), + ("ja", "Japanese"), + ("jac", "Popti'"), + ("jv", "Javanese"), + ("ka", "Georgian"), + ("kjb", "Q'anjob'al"), + ("kek", "Q'eqchi'"), + ("kk", "Kazakh"), + ("kl", "Kalaallisut"), + ("km", "Khmer"), + ("kn", "Kannada"), + ("knj", "Akateko"), + ("ko", "Korean"), + ("ks", "Kashmiri"), + ("ku", "Kurdish"), + ("ky", "Kyrgyz"), + ("la", "Latin"), + ("ln", "Lingala"), + ("lo", "Lao"), + ("lt", "Lithuanian"), + ("lv", "Latvian"), + ("mam", "Mam"), + ("mg", "Malagasy"), + ("mi", "Maori"), + ("mk", "Macedonian"), + ("ml", "Malayalam"), + ("mn", "Mongolian"), + ("mop", "Mopan"), + ("mr", "Marathi"), + ("ms", "Malay"), + ("mt", "Maltese"), + ("my", "Burmese"), + ("na", "Nauru"), + ("ne", "Nepali"), + ("nl", "Dutch"), + ("no", "Norwegian"), + ("oc", "Occitan"), + ("om", "Oromo"), + ("or", "Oriya"), + ("pa", "Panjabi"), + ("pl", "Polish"), + ("pnb", "Western Punjabi"), + ("poc", "Poqomam"), + ("poh", "Poqomchi"), + ("ps", "Pashto"), + ("pt", "Portuguese"), + ("qu", "Quechua"), + ("quc", "K'iche'"), + ("qum", "Sipakapense"), + ("quv", "Sakapulteko"), + ("rm", "Romansh"), + ("rn", "Kirundi"), + ("ro", "Romanian"), + ("ru", "Russian"), + ("rw", "Kinyarwanda"), + ("sa", "Sanskrit"), + ("sd", "Sindhi"), + ("sg", "Sango"), + ("si", "Sinhala"), + ("sk", "Slovak"), + ("skr", "Saraiki"), + ("sl", "Slovenian"), + ("sm", "Samoan"), + ("sn", "Shona"), + ("so", "Somali"), + ("sq", "Albanian"), + ("sr", "Serbian"), + ("ss", "Swati"), + ("st", "Southern Sotho"), + ("su", "Sudanese"), + ("sv", "Swedish"), + ("sw", "Swahili"), + ("ta", "Tamil"), + ("te", "Telugu"), + ("tg", "Tajik"), + ("th", "Thai"), + ("ti", "Tigrinya"), + ("tk", "Turkmen"), + ("tl", "Tagalog"), + ("tn", "Tswana"), + ("to", "Tonga"), + ("tr", "Turkish"), + ("ts", "Tsonga"), + ("tt", "Tatar"), + ("ttc", "Tektiteko"), + ("tzj", "Tz'utujil"), + ("tw", "Twi"), + ("ug", "Uyghur"), + ("uk", "Ukrainian"), + ("ur", "Urdu"), + ("usp", "Uspanteko"), + ("uz", "Uzbek"), + ("vi", "Vietnamese"), + ("vo", "Volapuk"), + ("wo", "Wolof"), + ("xh", "Xhosa"), + ("xin", "Xinka"), + ("yi", "Yiddish"), + ("yo", "Yoruba"), + ("za", "Zhuang"), + ("zh", "Chinese"), + ("zu", "Zulu"), # Try these to test our non-2 letter language support ("nb-no", "Norwegian Bokmal"), ("pt-br", "Brazilian Portuguese"), @@ -312,76 +312,68 @@ ) PROGRAMMING_LANGUAGES = ( - ('words', 'Only Words'), - ('py', 'Python'), - ('js', 'JavaScript'), - ('php', 'PHP'), - ('ruby', 'Ruby'), - ('perl', 'Perl'), - ('java', 'Java'), - ('go', 'Go'), - ('julia', 'Julia'), - ('c', 'C'), - ('csharp', 'C#'), - ('cpp', 'C++'), - ('objc', 'Objective-C'), - ('css', 'CSS'), - ('ts', 'TypeScript'), - ('swift', 'Swift'), - ('vb', 'Visual Basic'), - ('r', 'R'), - ('scala', 'Scala'), - ('groovy', 'Groovy'), - ('coffee', 'CoffeeScript'), - ('lua', 'Lua'), - ('haskell', 'Haskell'), - ('other', 'Other'), + ("words", "Only Words"), + ("py", "Python"), + ("js", "JavaScript"), + ("php", "PHP"), + ("ruby", "Ruby"), + ("perl", "Perl"), + ("java", "Java"), + ("go", "Go"), + ("julia", "Julia"), + ("c", "C"), + ("csharp", "C#"), + ("cpp", "C++"), + ("objc", "Objective-C"), + ("css", "CSS"), + ("ts", "TypeScript"), + ("swift", "Swift"), + ("vb", "Visual Basic"), + ("r", "R"), + ("scala", "Scala"), + ("groovy", "Groovy"), + ("coffee", "CoffeeScript"), + ("lua", "Lua"), + ("haskell", "Haskell"), + ("other", "Other"), ) -PROJECT_PK_REGEX = r'(?:[-\w]+)' -PROJECT_SLUG_REGEX = r'(?:[-\w]+)' +PROJECT_PK_REGEX = r"(?:[-\w]+)" +PROJECT_SLUG_REGEX = r"(?:[-\w]+)" GITHUB_REGEXS = [ - re.compile(r'github.com/(.+)/(.+)(?:\.git){1}$'), + re.compile(r"github.com/(.+)/(.+)(?:\.git){1}$"), # This must come before the one without a / to make sure we don't capture the / - re.compile(r'github.com/(.+)/(.+)/'), - re.compile(r'github.com/(.+)/(.+)'), - re.compile(r'github.com:(.+)/(.+)\.git$'), + re.compile(r"github.com/(.+)/(.+)/"), + re.compile(r"github.com/(.+)/(.+)"), + re.compile(r"github.com:(.+)/(.+)\.git$"), ] BITBUCKET_REGEXS = [ - re.compile(r'bitbucket.org/(.+)/(.+)\.git$'), - re.compile(r'@bitbucket.org/(.+)/(.+)\.git$'), + re.compile(r"bitbucket.org/(.+)/(.+)\.git$"), + re.compile(r"@bitbucket.org/(.+)/(.+)\.git$"), # This must come before the one without a / to make sure we don't capture the / - re.compile(r'bitbucket.org/(.+)/(.+)/'), - re.compile(r'bitbucket.org/(.+)/(.+)'), - re.compile(r'bitbucket.org:(.+)/(.+)\.git$'), + re.compile(r"bitbucket.org/(.+)/(.+)/"), + re.compile(r"bitbucket.org/(.+)/(.+)"), + re.compile(r"bitbucket.org:(.+)/(.+)\.git$"), ] GITLAB_REGEXS = [ - re.compile(r'gitlab.com/(.+)/(.+)(?:\.git){1}$'), + re.compile(r"gitlab.com/(.+)/(.+)(?:\.git){1}$"), # This must come before the one without a / to make sure we don't capture the / - re.compile(r'gitlab.com/(.+)/(.+)/'), - re.compile(r'gitlab.com/(.+)/(.+)'), - re.compile(r'gitlab.com:(.+)/(.+)\.git$'), + re.compile(r"gitlab.com/(.+)/(.+)/"), + re.compile(r"gitlab.com/(.+)/(.+)"), + re.compile(r"gitlab.com:(.+)/(.+)\.git$"), ] GITHUB_URL = ( - 'https://github.com/{user}/{repo}/' - '{action}/{version}{docroot}{path}{source_suffix}' -) -GITHUB_COMMIT_URL = ( - 'https://github.com/{user}/{repo}/' - 'commit/{commit}' -) -GITHUB_PULL_REQUEST_URL = ( - 'https://github.com/{user}/{repo}/' - 'pull/{number}' + "https://github.com/{user}/{repo}/" + "{action}/{version}{docroot}{path}{source_suffix}" ) +GITHUB_COMMIT_URL = "https://github.com/{user}/{repo}/commit/{commit}" +GITHUB_PULL_REQUEST_URL = "https://github.com/{user}/{repo}/pull/{number}" GITHUB_PULL_REQUEST_COMMIT_URL = ( - 'https://github.com/{user}/{repo}/' - 'pull/{number}/commits/{commit}' + "https://github.com/{user}/{repo}/pull/{number}/commits/{commit}" ) BITBUCKET_URL = ( - 'https://bitbucket.org/{user}/{repo}/' - 'src/{version}{docroot}{path}{source_suffix}' + "https://bitbucket.org/{user}/{repo}/src/{version}{docroot}{path}{source_suffix}" ) BITBUCKET_COMMIT_URL = "https://bitbucket.org/{user}/{repo}/commits/{commit}" GITLAB_URL = ( @@ -433,7 +425,7 @@ ) -ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY = "alphabetically" +ADDONS_FLYOUT_SORTING_ALPHABETICALLY = "alphabetically" # Compatibility to keep the behavior of the old flyout. # This isn't a good algorithm, but it's a way to keep the old behavior in case we need it. ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE = "semver-readthedocs-compatible" @@ -445,9 +437,12 @@ ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN = "custom-pattern" ADDONS_FLYOUT_SORTING_CHOICES = ( - (ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY, "Alphabetically"), - (ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE, "SemVer (Read the Docs)"), - (ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING, "Python Packaging (PEP 440 and PEP 425)"), - (ADDONS_FLYOUT_SORTING_CALVER, "CalVer (YYYY.0M.0M)"), - (ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN, "Define your own pattern"), + (ADDONS_FLYOUT_SORTING_ALPHABETICALLY, _("Alphabetically")), + (ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE, _("SemVer (Read the Docs)")), + ( + ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING, + _("Python Packaging (PEP 440 and PEP 425)"), + ), + (ADDONS_FLYOUT_SORTING_CALVER, _("CalVer (YYYY.0M.0M)")), + (ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN, _("Define your own pattern")), ) diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index a3be593d4e9..665d9543fa8 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -61,8 +61,8 @@ from readthedocs.vcs_support.backends import backend_cls from .constants import ( + ADDONS_FLYOUT_SORTING_ALPHABETICALLY, ADDONS_FLYOUT_SORTING_CHOICES, - ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY, DOWNLOADABLE_MEDIA_TYPES, MEDIA_TYPES, MULTIPLE_VERSIONS_WITH_TRANSLATIONS, @@ -186,7 +186,7 @@ class AddonsConfig(TimeStampedModel): flyout_enabled = models.BooleanField(default=True) flyout_sorting = models.CharField( choices=ADDONS_FLYOUT_SORTING_CHOICES, - default=ADDONS_FLYOUT_SORTING_LEXICOGRAPHYCALLY, + default=ADDONS_FLYOUT_SORTING_ALPHABETICALLY, ) flyout_sorting_custom_pattern = models.CharField( max_length=32, From cea20527ac19e917e4c20fbc6567c79121c56e8d Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Mar 2024 13:42:35 +0100 Subject: [PATCH 08/17] Install `bumpver` in the requirements --- requirements/deploy.txt | 21 ++++++++++++++++++++- requirements/docker.txt | 22 ++++++++++++++++++++-- requirements/pip.txt | 13 ++++++++++++- requirements/testing.txt | 21 ++++++++++++++++++++- 4 files changed, 72 insertions(+), 5 deletions(-) diff --git a/requirements/deploy.txt b/requirements/deploy.txt index a5aaf705587..0bb7779e9c5 100644 --- a/requirements/deploy.txt +++ b/requirements/deploy.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/deploy.txt requirements/deploy.in +# pip-compile --output-file=requirements/deploy.txt --resolver=backtracking requirements/deploy.in # amqp==5.2.0 # via @@ -32,6 +32,8 @@ botocore==1.34.54 # -r requirements/pip.txt # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.txt celery==5.2.7 # via # -r requirements/pip.txt @@ -53,6 +55,7 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/pip.txt + # bumpver # celery # click-didyoumean # click-plugins @@ -69,6 +72,10 @@ click-repl==0.3.0 # via # -r requirements/pip.txt # celery +colorama==0.4.6 + # via + # -r requirements/pip.txt + # bumpver cron-descriptor==1.4.3 # via # -r requirements/pip.txt @@ -233,6 +240,14 @@ kombu==5.3.5 # via # -r requirements/pip.txt # celery +lexid==2021.1006 + # via + # -r requirements/pip.txt + # bumpver +looseversion==1.3.0 + # via + # -r requirements/pip.txt + # bumpver lxml==5.1.0 # via # -r requirements/pip.txt @@ -369,6 +384,10 @@ structlog==23.2.0 # structlog-sentry structlog-sentry==2.0.3 # via -r requirements/deploy.in +toml==0.10.2 + # via + # -r requirements/pip.txt + # bumpver tomli==2.0.1 # via # -r requirements/pip.txt diff --git a/requirements/docker.txt b/requirements/docker.txt index b9a74425aa3..3d6f2c03d3c 100644 --- a/requirements/docker.txt +++ b/requirements/docker.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/docker.txt requirements/docker.in +# pip-compile --output-file=requirements/docker.txt --resolver=backtracking requirements/docker.in # amqp==5.2.0 # via @@ -34,6 +34,8 @@ botocore==1.34.54 # -r requirements/pip.txt # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.txt cachetools==5.3.3 # via tox celery==5.2.7 @@ -58,6 +60,7 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/pip.txt + # bumpver # celery # click-didyoumean # click-plugins @@ -75,7 +78,10 @@ click-repl==0.3.0 # -r requirements/pip.txt # celery colorama==0.4.6 - # via tox + # via + # -r requirements/pip.txt + # bumpver + # tox cron-descriptor==1.4.3 # via # -r requirements/pip.txt @@ -249,6 +255,14 @@ kombu==5.3.5 # via # -r requirements/pip.txt # celery +lexid==2021.1006 + # via + # -r requirements/pip.txt + # bumpver +looseversion==1.3.0 + # via + # -r requirements/pip.txt + # bumpver lxml==5.1.0 # via # -r requirements/pip.txt @@ -399,6 +413,10 @@ structlog==23.2.0 # via # -r requirements/pip.txt # django-structlog +toml==0.10.2 + # via + # -r requirements/pip.txt + # bumpver tomli==2.0.1 # via # -r requirements/pip.txt diff --git a/requirements/pip.txt b/requirements/pip.txt index 4482e0e898b..fb648b8f6c0 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/pip.txt requirements/pip.in +# pip-compile --output-file=requirements/pip.txt --resolver=backtracking requirements/pip.in # amqp==5.2.0 # via kombu @@ -20,6 +20,8 @@ botocore==1.34.54 # via # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.in celery==5.2.7 # via # -r requirements/pip.in @@ -34,6 +36,7 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via + # bumpver # celery # click-didyoumean # click-plugins @@ -44,6 +47,8 @@ click-plugins==1.1.1 # via celery click-repl==0.3.0 # via celery +colorama==0.4.6 + # via bumpver cron-descriptor==1.4.3 # via django-celery-beat cryptography==42.0.5 @@ -175,6 +180,10 @@ jsonfield==3.1.0 # dj-stripe kombu==5.3.5 # via celery +lexid==2021.1006 + # via bumpver +looseversion==1.3.0 + # via bumpver lxml==5.1.0 # via pyquery markdown==3.5.2 @@ -268,6 +277,8 @@ structlog==23.2.0 # via # -r requirements/pip.in # django-structlog +toml==0.10.2 + # via bumpver tomli==2.0.1 # via dparse typing-extensions==4.10.0 diff --git a/requirements/testing.txt b/requirements/testing.txt index e8bcf5ade56..3dd31d1b261 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/testing.txt requirements/testing.in +# pip-compile --output-file=requirements/testing.txt --resolver=backtracking requirements/testing.in # alabaster==0.7.16 # via sphinx @@ -34,6 +34,8 @@ botocore==1.34.54 # -r requirements/pip.txt # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.txt celery==5.2.7 # via # -r requirements/pip.txt @@ -54,6 +56,7 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/pip.txt + # bumpver # celery # click-didyoumean # click-plugins @@ -70,6 +73,10 @@ click-repl==0.3.0 # via # -r requirements/pip.txt # celery +colorama==0.4.6 + # via + # -r requirements/pip.txt + # bumpver coverage[toml]==7.4.3 # via pytest-cov cron-descriptor==1.4.3 @@ -238,6 +245,14 @@ kombu==5.3.5 # via # -r requirements/pip.txt # celery +lexid==2021.1006 + # via + # -r requirements/pip.txt + # bumpver +looseversion==1.3.0 + # via + # -r requirements/pip.txt + # bumpver lxml==5.1.0 # via # -r requirements/pip.txt @@ -399,6 +414,10 @@ structlog==23.2.0 # via # -r requirements/pip.txt # django-structlog +toml==0.10.2 + # via + # -r requirements/pip.txt + # bumpver tomli==2.0.1 # via # -r requirements/pip.txt From 4ca07f9416c6cac23bcd4750caa4308abef38867 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Mar 2024 13:56:35 +0100 Subject: [PATCH 09/17] Migrations update --- ...addons_flyout_sorting.py => 0118_addons_flyout_sorting.py} | 4 +++- ...sorting_stable_latest.py => 0119_addons_flyout_sorting.py} | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) rename readthedocs/projects/migrations/{0114_addons_flyout_sorting.py => 0118_addons_flyout_sorting.py} (91%) rename readthedocs/projects/migrations/{0115_addons_flyout_sorting_stable_latest.py => 0119_addons_flyout_sorting.py} (81%) diff --git a/readthedocs/projects/migrations/0114_addons_flyout_sorting.py b/readthedocs/projects/migrations/0118_addons_flyout_sorting.py similarity index 91% rename from readthedocs/projects/migrations/0114_addons_flyout_sorting.py rename to readthedocs/projects/migrations/0118_addons_flyout_sorting.py index 8213f73b6d0..ca086d3e95e 100644 --- a/readthedocs/projects/migrations/0114_addons_flyout_sorting.py +++ b/readthedocs/projects/migrations/0118_addons_flyout_sorting.py @@ -1,11 +1,13 @@ # Generated by Django 4.2.9 on 2024-01-26 12:08 from django.db import migrations, models +from django_safemigrate import Safe class Migration(migrations.Migration): + safe = Safe.before_deploy dependencies = [ - ("projects", "0113_disable_analytics_addons"), + ("projects", "0117_remove_old_fields"), ] operations = [ diff --git a/readthedocs/projects/migrations/0115_addons_flyout_sorting_stable_latest.py b/readthedocs/projects/migrations/0119_addons_flyout_sorting.py similarity index 81% rename from readthedocs/projects/migrations/0115_addons_flyout_sorting_stable_latest.py rename to readthedocs/projects/migrations/0119_addons_flyout_sorting.py index 08bfed1c48b..b57c896c113 100644 --- a/readthedocs/projects/migrations/0115_addons_flyout_sorting_stable_latest.py +++ b/readthedocs/projects/migrations/0119_addons_flyout_sorting.py @@ -1,11 +1,13 @@ # Generated by Django 4.2.9 on 2024-02-05 12:25 from django.db import migrations, models +from django_safemigrate import Safe class Migration(migrations.Migration): + safe = Safe.before_deploy dependencies = [ - ("projects", "0114_addons_flyout_sorting"), + ("projects", "0118_addons_flyout_sorting"), ] operations = [ From 670153839e2531fadfd375659c7119bf86b912f5 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Mar 2024 14:14:23 +0100 Subject: [PATCH 10/17] Rename field to match sorting (latest, stable) --- readthedocs/projects/forms.py | 2 +- .../migrations/0119_addons_flyout_sorting.py | 2 +- readthedocs/projects/models.py | 2 +- readthedocs/projects/version_handling.py | 12 ++++++------ readthedocs/proxito/views/hosting.py | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 033ccaf1da9..edf9e01a8ea 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -577,7 +577,7 @@ class Meta: "external_version_warning_enabled", "flyout_enabled", "flyout_sorting", - "flyout_sorting_stable_latest_at_beginning", + "flyout_sorting_latest_stable_at_beginning", "flyout_sorting_custom_pattern", "hotkeys_enabled", "search_enabled", diff --git a/readthedocs/projects/migrations/0119_addons_flyout_sorting.py b/readthedocs/projects/migrations/0119_addons_flyout_sorting.py index b57c896c113..103c6ee68bf 100644 --- a/readthedocs/projects/migrations/0119_addons_flyout_sorting.py +++ b/readthedocs/projects/migrations/0119_addons_flyout_sorting.py @@ -13,7 +13,7 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name="addonsconfig", - name="flyout_sorting_stable_latest_at_beginning", + name="flyout_sorting_latest_stable_at_beginning", field=models.BooleanField( default=True, help_text="Show stable and latest at the beginning", diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 665d9543fa8..170735da627 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -196,7 +196,7 @@ class AddonsConfig(TimeStampedModel): help_text="Sorting pattern supported by BumpVer " '( See examples', ) - flyout_sorting_stable_latest_at_beginning = models.BooleanField( + flyout_sorting_latest_stable_at_beginning = models.BooleanField( default=True, help_text="Show stable and latest at the beginning", ) diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index d5656edf48e..28783318d0c 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -169,7 +169,7 @@ def determine_stable_version(version_list): return None -def sort_versions_python_packaging(version_list, stable_latest_at_beginning): +def sort_versions_python_packaging(version_list, latest_stable_at_beginning): """ Sort Read the Docs versions list using ``packaging`` algorithm. @@ -189,7 +189,7 @@ def sort_versions_python_packaging(version_list, stable_latest_at_beginning): valid_versions = [] invalid_versions = [] for i, version in enumerate(alphabetically_sorted_version_list): - if stable_latest_at_beginning: + if latest_stable_at_beginning: if version.slug in (STABLE, LATEST): # It relies on the version list sorted alphabetically first ("l" comes first than "s") initial_versions.insert(0, (version, version.slug)) @@ -211,7 +211,7 @@ def sort_versions_python_packaging(version_list, stable_latest_at_beginning): return [item[0] for item in all_versions if item[0] is not None] -def sort_versions_calver(version_list, stable_latest_at_beginning): +def sort_versions_calver(version_list, latest_stable_at_beginning): """ Sort Read the Docs versions using CalVer pattern: ``YYYY.0M.0M``. @@ -219,11 +219,11 @@ def sort_versions_calver(version_list, stable_latest_at_beginning): """ raw_pattern = "YYYY.0M.0D" return sort_versions_custom_pattern( - version_list, raw_pattern, stable_latest_at_beginning + version_list, raw_pattern, latest_stable_at_beginning ) -def sort_versions_custom_pattern(version_list, raw_pattern, stable_latest_at_beginning): +def sort_versions_custom_pattern(version_list, raw_pattern, latest_stable_at_beginning): """ Sort Read the Docs versions using a custom pattern. @@ -243,7 +243,7 @@ def sort_versions_custom_pattern(version_list, raw_pattern, stable_latest_at_beg valid_versions = [] invalid_versions = [] for i, version in enumerate(alphabetically_sorted_version_list): - if stable_latest_at_beginning: + if latest_stable_at_beginning: if version.slug in (STABLE, LATEST): # It relies on the version list sorted alphabetically first ("l" comes first than "s") initial_versions.insert(0, (version, version.slug)) diff --git a/readthedocs/proxito/views/hosting.py b/readthedocs/proxito/views/hosting.py index 5a2be757580..1bdad2fc9a8 100644 --- a/readthedocs/proxito/views/hosting.py +++ b/readthedocs/proxito/views/hosting.py @@ -301,18 +301,18 @@ def _v0(self, project, version, build, filename, url, user): ): versions_active_built_not_hidden = sort_versions_python_packaging( versions_active_built_not_hidden, - project.addons.flyout_sorting_stable_latest_at_beginning, + project.addons.flyout_sorting_latest_stable_at_beginning, ) elif project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_CALVER: versions_active_built_not_hidden = sort_versions_calver( versions_active_built_not_hidden, - project.addons.flyout_sorting_stable_latest_at_beginning, + project.addons.flyout_sorting_latest_stable_at_beginning, ) elif project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN: versions_active_built_not_hidden = sort_versions_custom_pattern( versions_active_built_not_hidden, project.addons.flyout_sorting_custom_pattern, - project.addons.flyout_sorting_stable_latest_at_beginning, + project.addons.flyout_sorting_latest_stable_at_beginning, ) if version: From 51e1bd7357c7216c347902e1f5b557767ba0891f Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Mar 2024 16:30:10 +0100 Subject: [PATCH 11/17] Fix migrations --- .../migrations/0118_addons_flyout_sorting.py | 51 +++++++++++++++++-- .../migrations/0119_addons_flyout_sorting.py | 22 -------- readthedocs/projects/models.py | 3 +- 3 files changed, 49 insertions(+), 27 deletions(-) delete mode 100644 readthedocs/projects/migrations/0119_addons_flyout_sorting.py diff --git a/readthedocs/projects/migrations/0118_addons_flyout_sorting.py b/readthedocs/projects/migrations/0118_addons_flyout_sorting.py index ca086d3e95e..0066e13825b 100644 --- a/readthedocs/projects/migrations/0118_addons_flyout_sorting.py +++ b/readthedocs/projects/migrations/0118_addons_flyout_sorting.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.9 on 2024-01-26 12:08 +# Generated by Django 4.2.10 on 2024-03-04 13:32 from django.db import migrations, models from django_safemigrate import Safe @@ -6,25 +6,48 @@ class Migration(migrations.Migration): safe = Safe.before_deploy + dependencies = [ ("projects", "0117_remove_old_fields"), ] operations = [ + migrations.AddField( + model_name="addonsconfig", + name="flyout_sorting", + field=models.CharField( + choices=[ + ("alphabetically", "Alphabetically"), + ("semver-readthedocs-compatible", "SemVer (Read the Docs)"), + ("python-packaging", "Python Packaging (PEP 440 and PEP 425)"), + ("calver", "CalVer (YYYY.0M.0M)"), + ("custom-pattern", "Define your own pattern"), + ], + default="alphabetically", + max_length=64, + ), + ), migrations.AddField( model_name="addonsconfig", name="flyout_sorting_custom_pattern", field=models.CharField( blank=True, default=None, - help_text="Sorting pattern supported by BumpVer " - '( See examples', + help_text='Sorting pattern supported by BumpVer ( See examples', max_length=32, null=True, ), ), - migrations.AlterField( + migrations.AddField( model_name="addonsconfig", + name="flyout_sorting_latest_stable_at_beginning", + field=models.BooleanField( + default=True, + help_text="Show latest and stable at the beginning", + ), + ), + migrations.AddField( + model_name="historicaladdonsconfig", name="flyout_sorting", field=models.CharField( choices=[ @@ -35,6 +58,26 @@ class Migration(migrations.Migration): ("custom-pattern", "Define your own pattern"), ], default="alphabetically", + max_length=64, + ), + ), + migrations.AddField( + model_name="historicaladdonsconfig", + name="flyout_sorting_custom_pattern", + field=models.CharField( + blank=True, + default=None, + help_text='Sorting pattern supported by BumpVer ( See examples', + max_length=32, + null=True, + ), + ), + migrations.AddField( + model_name="historicaladdonsconfig", + name="flyout_sorting_latest_stable_at_beginning", + field=models.BooleanField( + default=True, + help_text="Show latest and stable at the beginning", ), ), ] diff --git a/readthedocs/projects/migrations/0119_addons_flyout_sorting.py b/readthedocs/projects/migrations/0119_addons_flyout_sorting.py deleted file mode 100644 index 103c6ee68bf..00000000000 --- a/readthedocs/projects/migrations/0119_addons_flyout_sorting.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.9 on 2024-02-05 12:25 - -from django.db import migrations, models -from django_safemigrate import Safe - - -class Migration(migrations.Migration): - safe = Safe.before_deploy - dependencies = [ - ("projects", "0118_addons_flyout_sorting"), - ] - - operations = [ - migrations.AddField( - model_name="addonsconfig", - name="flyout_sorting_latest_stable_at_beginning", - field=models.BooleanField( - default=True, - help_text="Show stable and latest at the beginning", - ), - ), - ] diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 170735da627..8cf69b6357f 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -187,6 +187,7 @@ class AddonsConfig(TimeStampedModel): flyout_sorting = models.CharField( choices=ADDONS_FLYOUT_SORTING_CHOICES, default=ADDONS_FLYOUT_SORTING_ALPHABETICALLY, + max_length=64, ) flyout_sorting_custom_pattern = models.CharField( max_length=32, @@ -198,7 +199,7 @@ class AddonsConfig(TimeStampedModel): ) flyout_sorting_latest_stable_at_beginning = models.BooleanField( default=True, - help_text="Show stable and latest at the beginning", + help_text="Show latest and stable at the beginning", ) # Hotkeys From cdbe804520f9fcb813999ecec326406c8b55642e Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Mar 2024 16:30:45 +0100 Subject: [PATCH 12/17] Fix algorithms --- readthedocs/projects/version_handling.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index 28783318d0c..b5edc91e902 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -192,7 +192,7 @@ def sort_versions_python_packaging(version_list, latest_stable_at_beginning): if latest_stable_at_beginning: if version.slug in (STABLE, LATEST): # It relies on the version list sorted alphabetically first ("l" comes first than "s") - initial_versions.insert(0, (version, version.slug)) + initial_versions.append((version, version.slug)) continue try: @@ -233,7 +233,6 @@ def sort_versions_custom_pattern(version_list, raw_pattern, latest_stable_at_beg It uses ``Bumpver`` behinds the scenes for the parsing and sorting. https://github.com/mbarkhau/bumpver """ - raw_pattern = "YYYY.0M.0D" alphabetically_sorted_version_list = sorted( version_list, key=operator.attrgetter("slug"), @@ -246,7 +245,7 @@ def sort_versions_custom_pattern(version_list, raw_pattern, latest_stable_at_beg if latest_stable_at_beginning: if version.slug in (STABLE, LATEST): # It relies on the version list sorted alphabetically first ("l" comes first than "s") - initial_versions.insert(0, (version, version.slug)) + initial_versions.append((version, version.slug)) continue try: From 4d3dcb26ace0c3f54eb4f5e36c970d1a4b25ac98 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Mar 2024 16:31:32 +0100 Subject: [PATCH 13/17] Add tests for sorting algorithms --- .../projects/tests/test_version_handling.py | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 readthedocs/projects/tests/test_version_handling.py diff --git a/readthedocs/projects/tests/test_version_handling.py b/readthedocs/projects/tests/test_version_handling.py new file mode 100644 index 00000000000..97ca98a9bd5 --- /dev/null +++ b/readthedocs/projects/tests/test_version_handling.py @@ -0,0 +1,207 @@ +import django_dynamic_fixture as fixture +import pytest + +from readthedocs.builds.models import Build, Version +from readthedocs.projects.models import AddonsConfig, Project +from readthedocs.projects.version_handling import ( + sort_versions_calver, + sort_versions_custom_pattern, + sort_versions_python_packaging, +) + + +@pytest.mark.django_db(databases="__all__") +class TestVersionHandling: + @pytest.fixture(autouse=True) + def setup(self, requests_mock): + # Save the reference to query it from inside the test + self.requests_mock = requests_mock + + self.project = fixture.get(Project, slug="project") + self.addons = fixture.get(AddonsConfig, project=self.project) + self.version = self.project.versions.get(slug="latest") + self.build = fixture.get( + Build, + version=self.version, + commit="a1b2c3", + ) + + def test_sort_versions_python_packaging(self): + slugs = [ + "v1.0", + "1.1", + "invalid", + "2.5.3", + "1.1.0", + "another-invalid", + ] + + expected = [ + # `latest` and `stable` are at the beginning + "latest", + "v1.0", + "1.1", + "1.1.0", + "2.5.3", + # Invalid versions are at the end sorted alphabetically. + "another-invalid", + "invalid", + ] + + for slug in slugs: + fixture.get( + Version, + slug=slug, + project=self.project, + ) + + sorted_versions = sort_versions_python_packaging( + self.project.versions.all(), + latest_stable_at_beginning=True, + ) + assert expected == [version.slug for version in sorted_versions] + + def test_sort_versions_python_packaging_latest_stable_not_at_beginning(self): + slugs = [ + "v1.0", + "1.1", + "invalid", + "2.5.3", + "1.1.0", + "another-invalid", + ] + + expected = [ + "v1.0", + "1.1", + "1.1.0", + "2.5.3", + # Invalid versions are at the end sorted alphabetically. + "another-invalid", + "invalid", + "latest", + ] + + for slug in slugs: + fixture.get( + Version, + slug=slug, + project=self.project, + ) + + sorted_versions = sort_versions_python_packaging( + self.project.versions.all(), + latest_stable_at_beginning=False, + ) + assert expected == [version.slug for version in sorted_versions] + + def test_sort_versions_calver(self): + slugs = [ + "2022.01.22", + "2023.04.22", + "2021.01.22", + "2022.05.02", + # invalid ones + "2001.16.32", + "2001.02.2", + "2001-02-27", + "1.1", + "invalid", + "2.5.3", + "1.1.0", + "another-invalid", + ] + + expected = [ + # `latest` and `stable` are at the beginning + "latest", + "stable", + "2021.01.22", + "2022.01.22", + "2022.05.02", + "2023.04.22", + # invalid ones (alphabetically) + "1.1", + "1.1.0", + "2.5.3", + "2001-02-27", + "2001.02.2", + "2001.16.32", + "another-invalid", + "invalid", + ] + + for slug in slugs: + fixture.get( + Version, + slug=slug, + project=self.project, + ) + + fixture.get( + Version, + slug="stable", + machine=True, + project=self.project, + ) + + sorted_versions = sort_versions_calver( + self.project.versions.all(), + latest_stable_at_beginning=True, + ) + + assert expected == [version.slug for version in sorted_versions] + + def test_sort_versions_custom_pattern(self): + slugs = [ + "v1.0", + "v1.1", + "v2.3", + # invalid ones + "v1.1.0", + "v2.3rc1", + "invalid", + "2.5.3", + "2022.01.22", + "1.1", + "another-invalid", + ] + + expected = [ + # `latest` and `stable` are at the beginning + "latest", + "stable", + "v1.0", + "v1.1", + "v2.3", + # invalid ones (alphabetically) + "1.1", + "2.5.3", + "2022.01.22", + "another-invalid", + "invalid", + "v1.1.0", + "v2.3rc1", + ] + + for slug in slugs: + fixture.get( + Version, + slug=slug, + project=self.project, + ) + + fixture.get( + Version, + slug="stable", + machine=True, + project=self.project, + ) + + sorted_versions = sort_versions_custom_pattern( + self.project.versions.all(), + raw_pattern="vMAJOR.MINOR", + latest_stable_at_beginning=True, + ) + + assert expected == [version.slug for version in sorted_versions] From e2ebaa1b4d132263f96f9beea9bbaa9557b1ad30 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Mar 2024 16:35:55 +0100 Subject: [PATCH 14/17] Minor fixes --- readthedocs/projects/migrations/0118_addons_flyout_sorting.py | 4 ++-- readthedocs/projects/models.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/readthedocs/projects/migrations/0118_addons_flyout_sorting.py b/readthedocs/projects/migrations/0118_addons_flyout_sorting.py index 0066e13825b..408bc82074a 100644 --- a/readthedocs/projects/migrations/0118_addons_flyout_sorting.py +++ b/readthedocs/projects/migrations/0118_addons_flyout_sorting.py @@ -33,7 +33,7 @@ class Migration(migrations.Migration): field=models.CharField( blank=True, default=None, - help_text='Sorting pattern supported by BumpVer ( See examples', + help_text='Sorting pattern supported by BumpVer (See examples', max_length=32, null=True, ), @@ -67,7 +67,7 @@ class Migration(migrations.Migration): field=models.CharField( blank=True, default=None, - help_text='Sorting pattern supported by BumpVer ( See examples', + help_text='Sorting pattern supported by BumpVer (See examples', max_length=32, null=True, ), diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 8cf69b6357f..f304309cc63 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -195,7 +195,7 @@ class AddonsConfig(TimeStampedModel): null=True, blank=True, help_text="Sorting pattern supported by BumpVer " - '( See examples', + '(See examples)', ) flyout_sorting_latest_stable_at_beginning = models.BooleanField( default=True, From eeb07271f1f39ff9e993e11f8065742861a1a086 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 4 Mar 2024 16:50:12 +0100 Subject: [PATCH 15/17] Add tests for `AddonsConfigForm` --- .../rtd_tests/tests/test_project_forms.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py index 09361decd6e..0655bf96893 100644 --- a/readthedocs/rtd_tests/tests/test_project_forms.py +++ b/readthedocs/rtd_tests/tests/test_project_forms.py @@ -12,6 +12,8 @@ from readthedocs.core.forms import RichValidationError from readthedocs.organizations.models import Organization, Team from readthedocs.projects.constants import ( + ADDONS_FLYOUT_SORTING_CALVER, + ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN, MULTIPLE_VERSIONS_WITH_TRANSLATIONS, MULTIPLE_VERSIONS_WITHOUT_TRANSLATIONS, PRIVATE, @@ -22,6 +24,7 @@ SPHINX, ) from readthedocs.projects.forms import ( + AddonsConfigForm, EmailHookForm, EnvironmentVariableForm, ProjectAutomaticForm, @@ -1039,3 +1042,68 @@ def test_create(self): EnvironmentVariable.objects.latest().value, r"'string escaped here: #$\1[]{}\|'", ) + + +class TestAddonsConfigForm(TestCase): + def setUp(self): + self.project = get(Project) + + def test_addonsconfig_form(self): + data = { + "enabled": True, + "analytics_enabled": False, + "doc_diff_enabled": False, + "external_version_warning_enabled": True, + "flyout_enabled": True, + "flyout_sorting": ADDONS_FLYOUT_SORTING_CALVER, + "flyout_sorting_latest_stable_at_beginning": True, + "flyout_sorting_custom_pattern": None, + "hotkeys_enabled": False, + "search_enabled": False, + "stable_latest_version_warning_enabled": True, + } + form = AddonsConfigForm(data=data, project=self.project) + self.assertTrue(form.is_valid()) + form.save() + + self.assertEqual(self.project.addons.enabled, True) + self.assertEqual(self.project.addons.analytics_enabled, False) + self.assertEqual(self.project.addons.doc_diff_enabled, False) + self.assertEqual(self.project.addons.external_version_warning_enabled, True) + self.assertEqual(self.project.addons.flyout_enabled, True) + self.assertEqual( + self.project.addons.flyout_sorting, + ADDONS_FLYOUT_SORTING_CALVER, + ) + self.assertEqual( + self.project.addons.flyout_sorting_latest_stable_at_beginning, + True, + ) + self.assertEqual(self.project.addons.flyout_sorting_custom_pattern, None) + self.assertEqual(self.project.addons.hotkeys_enabled, False) + self.assertEqual(self.project.addons.search_enabled, False) + self.assertEqual( + self.project.addons.stable_latest_version_warning_enabled, + True, + ) + + def test_addonsconfig_form_invalid_sorting_custom_pattern(self): + data = { + "enabled": True, + "analytics_enabled": False, + "doc_diff_enabled": False, + "external_version_warning_enabled": True, + "flyout_enabled": True, + "flyout_sorting": ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN, + "flyout_sorting_latest_stable_at_beginning": True, + "flyout_sorting_custom_pattern": None, + "hotkeys_enabled": False, + "search_enabled": False, + "stable_latest_version_warning_enabled": True, + } + form = AddonsConfigForm(data=data, project=self.project) + self.assertFalse(form.is_valid()) + self.assertEqual( + "The flyout sorting custom pattern is required when selecting a custom pattern.", + form.errors["__all__"][0], + ) From 6cdbe42d8b09ebe35ee48ad7430f9fcd142d1353 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 5 Mar 2024 11:01:38 +0100 Subject: [PATCH 16/17] Test: reduce the number of queries in 1 --- readthedocs/proxito/tests/test_hosting.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readthedocs/proxito/tests/test_hosting.py b/readthedocs/proxito/tests/test_hosting.py index 59824761486..1ebc43b2371 100644 --- a/readthedocs/proxito/tests/test_hosting.py +++ b/readthedocs/proxito/tests/test_hosting.py @@ -701,7 +701,7 @@ def test_number_of_queries_project_version_slug(self): active=True, ) - with self.assertNumQueries(21): + with self.assertNumQueries(20): r = self.client.get( reverse("proxito_readthedocs_docs_addons"), { @@ -730,7 +730,7 @@ def test_number_of_queries_url(self): active=True, ) - with self.assertNumQueries(21): + with self.assertNumQueries(20): r = self.client.get( reverse("proxito_readthedocs_docs_addons"), { @@ -766,7 +766,7 @@ def test_number_of_queries_url_subproject(self): active=True, ) - with self.assertNumQueries(25): + with self.assertNumQueries(24): r = self.client.get( reverse("proxito_readthedocs_docs_addons"), { @@ -792,7 +792,7 @@ def test_number_of_queries_url_translations(self): language=language, ) - with self.assertNumQueries(25): + with self.assertNumQueries(24): r = self.client.get( reverse("proxito_readthedocs_docs_addons"), { From 6288537b2e2caa0303dd0ebb0c221782d92b0c69 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 7 Mar 2024 10:15:43 +0100 Subject: [PATCH 17/17] Use descending sorting for _valid versions_ It makes more sense to show `latest stable ` than `` first. https://github.com/readthedocs/readthedocs.org/pull/11069#discussion_r1515799263 --- .../projects/tests/test_version_handling.py | 18 +++++++++--------- readthedocs/projects/version_handling.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/readthedocs/projects/tests/test_version_handling.py b/readthedocs/projects/tests/test_version_handling.py index 97ca98a9bd5..a83d24e47b2 100644 --- a/readthedocs/projects/tests/test_version_handling.py +++ b/readthedocs/projects/tests/test_version_handling.py @@ -39,10 +39,10 @@ def test_sort_versions_python_packaging(self): expected = [ # `latest` and `stable` are at the beginning "latest", - "v1.0", + "2.5.3", "1.1", "1.1.0", - "2.5.3", + "v1.0", # Invalid versions are at the end sorted alphabetically. "another-invalid", "invalid", @@ -72,10 +72,10 @@ def test_sort_versions_python_packaging_latest_stable_not_at_beginning(self): ] expected = [ - "v1.0", + "2.5.3", "1.1", "1.1.0", - "2.5.3", + "v1.0", # Invalid versions are at the end sorted alphabetically. "another-invalid", "invalid", @@ -116,10 +116,10 @@ def test_sort_versions_calver(self): # `latest` and `stable` are at the beginning "latest", "stable", - "2021.01.22", - "2022.01.22", - "2022.05.02", "2023.04.22", + "2022.05.02", + "2022.01.22", + "2021.01.22", # invalid ones (alphabetically) "1.1", "1.1.0", @@ -171,9 +171,9 @@ def test_sort_versions_custom_pattern(self): # `latest` and `stable` are at the beginning "latest", "stable", - "v1.0", - "v1.1", "v2.3", + "v1.1", + "v1.0", # invalid ones (alphabetically) "1.1", "2.5.3", diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index b5edc91e902..2059e2a818a 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -204,7 +204,7 @@ def sort_versions_python_packaging(version_list, latest_stable_at_beginning): all_versions = ( initial_versions - + sorted(valid_versions, key=operator.itemgetter(1)) + + sorted(valid_versions, key=operator.itemgetter(1), reverse=True) + invalid_versions ) @@ -265,7 +265,7 @@ def sort_versions_custom_pattern(version_list, raw_pattern, latest_stable_at_beg all_versions = ( initial_versions - + sorted(valid_versions, key=operator.itemgetter(1)) + + sorted(valid_versions, key=operator.itemgetter(1), reverse=True) + invalid_versions )