diff --git a/docs/README.md b/docs/README.md index b5b03d90..aae13026 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,7 @@ # Documentation for the Boost Website +- [API Documentation](./api.md) - We don't have many API endpoints, but the ones we do have are documented here +- [Dependency Management](./dependencies.md) - [Development Setup Notes](./development_setup_notes.md) - [Environment Variables](./env_vars.md) - [Example Files](./examples/README.md) - Contains samples of `libraries.json`. `.gitmodules`, and other files that Boost data depends on @@ -7,4 +9,3 @@ - [Management Commands](./commands.md) - [Retrieving Static Content from the Boost Amazon S3 Bucket](./static_content.md) - [Syncing Data about Boost Versions and Libraries with GitHub](./syncing_data_with_github.md) -- [API Documentation](./api.md) - We don't have many API endpoints, but the ones we do have are documented here diff --git a/docs/dependencies.md b/docs/dependencies.md new file mode 100644 index 00000000..20f77514 --- /dev/null +++ b/docs/dependencies.md @@ -0,0 +1,9 @@ +# Dependency Management + +## How to add a new Python dependency + +1. Run `just down` to kill your running containers +1. Add the package to `requirements.in` +1. Run `just pip-compile`, which will add the dependency to `requirements.txt` +1. Run `just rebuild` to rebuild your Docker image to include the new dependencies +2. Run `just up` and continue with development diff --git a/libraries/tests/test_views.py b/libraries/tests/test_views.py index 6c012f52..c6c57936 100644 --- a/libraries/tests/test_views.py +++ b/libraries/tests/test_views.py @@ -5,14 +5,14 @@ from dateutil.relativedelta import relativedelta from model_bakery import baker -from versions.models import Version - def test_library_list(library_version, tp, url_name="libraries"): """GET /libraries/""" # Create a version with a library last_year = library_version.version.release_date - datetime.timedelta(days=365) - v2 = baker.make("versions.Version", name="Version 1.78.0", release_date=last_year) + v2 = baker.make( + "versions.Version", name="boost-1.78.0", release_date=last_year, beta=False + ) lib2 = baker.make( "libraries.Library", name="sample", @@ -21,12 +21,9 @@ def test_library_list(library_version, tp, url_name="libraries"): # Create a version with no libraries v_no_libraries = baker.make( - "versions.Version", name="No Libraries", release_date=last_year + "versions.Version", name="boost-1.0.0", release_date=last_year, beta=False ) - # Confirm that you know which version is the most recent - assert library_version.version == Version.objects.most_recent() - res = tp.get(url_name) tp.response_200(res) assert "library_list" in res.context diff --git a/libraries/views.py b/libraries/views.py index 22782951..8b7af373 100644 --- a/libraries/views.py +++ b/libraries/views.py @@ -75,12 +75,12 @@ class LibraryList(VersionAlertMixin, ListView): """ Return a queryset of all versions to display in the version dropdown. """ - versions = Version.objects.active().order_by("-release_date") + versions = Version.objects.version_dropdown().order_by("-name") # Annotate each version with the number of libraries it has versions = versions.annotate( library_count=Count("library_version", distinct=True) - ).order_by("-release_date") + ).order_by("-name") # Filter out versions with no libraries versions = versions.filter(library_count__gt=0) diff --git a/requirements.txt b/requirements.txt index 5dfe089c..4abea5cc 100755 --- a/requirements.txt +++ b/requirements.txt @@ -92,7 +92,6 @@ django==4.2.2 # django-haystack # django-health-check # django-js-asset - # django-machina # django-redis # django-rest-auth # django-storages @@ -117,19 +116,13 @@ django-db-geventpool==4.0.1 django-extensions==3.2.3 # via -r ./requirements.in django-haystack==3.2.1 - # via - # -r ./requirements.in - # django-machina + # via -r ./requirements.in django-health-check==3.17.0 # via -r ./requirements.in django-js-asset==2.0.0 # via django-mptt -django-machina==1.3.0 +django-mptt==0.14 # via -r ./requirements.in -django-mptt==0.14.0 - # via - # -r ./requirements.in - # django-machina django-redis==5.3.0 # via -r ./requirements.in django-rest-auth==0.9.5 @@ -141,9 +134,7 @@ django-test-plus==2.2.1 django-tracer==0.9.3 # via -r ./requirements.in django-widget-tweaks==1.4.12 - # via - # -r ./requirements.in - # django-machina + # via -r ./requirements.in djangorestframework==3.14.0 # via # -r ./requirements.in @@ -186,8 +177,6 @@ jmespath==1.0.1 # botocore kombu==5.3.1 # via celery -markdown2==2.4.8 - # via django-machina marshmallow==3.19.0 # via environs matplotlib-inline==0.1.6 @@ -221,9 +210,7 @@ pexpect==4.8.0 pickleshare==0.7.5 # via ipython pillow==9.4.0 - # via - # -r ./requirements.in - # django-machina + # via -r ./requirements.in pip-tools==6.13.0 # via -r ./requirements.in platformdirs==3.5.3 diff --git a/versions/managers.py b/versions/managers.py index aa57efbe..83184ea3 100644 --- a/versions/managers.py +++ b/versions/managers.py @@ -34,6 +34,29 @@ class VersionManager(models.Manager): """Return most recent active beta version""" return self.get_queryset().most_recent_beta() + def version_dropdown(self): + """Return the versions that should show in the version drop-down""" + all_versions = self.active().filter(beta=False) + most_recent = self.most_recent() + most_recent_beta = self.most_recent_beta() + + def should_show_beta(most_recent, most_recent_beta): + """Returns bool for whether to show beta version in dropdown""" + if not most_recent_beta: + return False + + return ( + most_recent_beta.cleaned_version_parts + > most_recent.cleaned_version_parts + ) + + include_beta = should_show_beta(most_recent, most_recent_beta) + if include_beta: + beta_queryset = self.active().filter(models.Q(name=most_recent_beta.name)) + return (all_versions | beta_queryset).order_by("-name") + else: + return all_versions.order_by("-name") + class VersionFileQuerySet(models.QuerySet): def active(self): diff --git a/versions/models.py b/versions/models.py index 53fd6320..6b808629 100755 --- a/versions/models.py +++ b/versions/models.py @@ -1,3 +1,4 @@ +import re from django.db import models from django.utils.functional import cached_property from django.utils.text import slugify @@ -77,6 +78,16 @@ class Version(models.Model): slug = self.slug.replace("-", "_").replace(".", "_") return f"{site_path}{slug}/index.html" + @cached_property + def cleaned_version_parts(self): + """Returns only the release data from the name. Also omits "boost", "beta" + information from the name.""" + if not self.name: + return + + cleaned = re.sub(r"^[^0-9]*", "", self.name).split("beta")[0] + return [part for part in cleaned.split(".") if part] + class VersionFile(models.Model): Unix = "Unix" diff --git a/versions/tests/fixtures.py b/versions/tests/fixtures.py index 1404adf5..7a4da3e1 100644 --- a/versions/tests/fixtures.py +++ b/versions/tests/fixtures.py @@ -17,7 +17,7 @@ def beta_version(db): # Make version v = baker.make( "versions.Version", - name="Version 1.79.0-beta", + name="boost-1.79.0-beta", description="Some awesome description of the library", release_date=datetime.date.today(), beta=True, @@ -41,7 +41,7 @@ def version(db): yesterday = datetime.date.today() - datetime.timedelta(days=1) v = baker.make( "versions.Version", - name="Version 1.79.0", + name="boost-1.79.0", description="Some awesome description of the library", release_date=yesterday, ) @@ -64,7 +64,7 @@ def inactive_version(db): yesterday = datetime.date.today() - datetime.timedelta(days=1) v = baker.make( "versions.Version", - name="Version 1.0.0", + name="boost-1.0.0", description="Some old description of the library", release_date=yesterday, active=False, @@ -88,7 +88,7 @@ def old_version(db): last_year = datetime.date.today() - datetime.timedelta(days=365) v = baker.make( "versions.Version", - name="Version 1.70.0", + name="boost-1.70.0", description="Some awesome description of the library", release_date=last_year, ) @@ -112,7 +112,7 @@ def full_version_one(db): base_url_suffix = ".tar.gz" v = baker.make( "versions.Version", - name="1.79.0", + name="boost-1.79.0", description="Some old description of the library for v1.79.0", release_date=yesterday, active=False, diff --git a/versions/tests/test_managers.py b/versions/tests/test_managers.py index 40ac3d58..20fc6c2e 100644 --- a/versions/tests/test_managers.py +++ b/versions/tests/test_managers.py @@ -1,4 +1,6 @@ +import pytest from model_bakery import baker +from django.db.models import Q from versions.models import Version, VersionFile @@ -30,6 +32,54 @@ def test_most_recent_beta_manager(version, inactive_version, old_version, beta_v assert Version.objects.most_recent_beta() == version +@pytest.mark.django_db +@pytest.mark.parametrize( + "most_recent_name,most_recent_beta_name,should_show_beta", + [ + # Beta is newer than newest version + ("1.84.0", "1.85.0.beta1", True), + # Beta is newer minor version than newest version + ("1.84.0", "1.84.1.beta1", True), + # Beta is for the newest version + ("1.84.0", "1.84.0.beta1", False), + # Beta is older than newest version + ("1.84.0", "1.83.0.beta1", False), + # There is no beta + ("1.84.0", None, False), + ], +) +def test_version_dropdown( + most_recent_name, most_recent_beta_name, should_show_beta, version, beta_version +): + """Test the version_dropdown manager method""" + version.name = most_recent_name + version.save() + + if most_recent_beta_name: + beta_version.name = most_recent_beta_name + beta_version.save() + most_recent_beta = beta_version + else: + most_recent_beta = None + + queryset = Version.objects.version_dropdown() + + if should_show_beta: + beta_queryset = Version.objects.active().filter(Q(name=most_recent_beta.name)) + expected = list( + ( + Version.objects.active().filter(beta=False).order_by("-release_date") + | beta_queryset + ).order_by("-release_date") + ) + else: + expected = list( + Version.objects.active().filter(beta=False).order_by("-release_date") + ) + + assert list(queryset) == expected + + def test_active_file_manager(version, inactive_version): assert Version.objects.active().count() == 1 assert VersionFile.objects.active().count() == 1 diff --git a/versions/tests/test_models.py b/versions/tests/test_models.py index 9acc5eb4..8a5ba71e 100644 --- a/versions/tests/test_models.py +++ b/versions/tests/test_models.py @@ -53,5 +53,22 @@ def test_version_documentation_url(version): assert version.documentation_url == "/doc/libs/boost_1_81_0/index.html" +@pytest.mark.parametrize( + "name,expected_cleaned_parts", + [ + ("boost-1.80.0", ["1", "80", "0"]), + ("boost-1.79.0.beta1", ["1", "79", "0"]), + ("Boost 1.80.9.beta", ["1", "80", "9"]), + ("Version 1.82.0.beta1", ["1", "82", "0"]), + ], +) +def test_cleaned_version_parts(name, expected_cleaned_parts, version): + """Test the cleaned_version_parts property method""" + version.name = name + version.save() + + assert version.cleaned_version_parts == expected_cleaned_parts + + def test_version_file_creation(full_version_one): assert full_version_one.downloads.count() == 3 diff --git a/versions/views.py b/versions/views.py index 4bece21f..34694848 100755 --- a/versions/views.py +++ b/versions/views.py @@ -23,7 +23,7 @@ class VersionDetail(FormMixin, DetailView): def get_context_data(self, **kwargs): context = super().get_context_data() obj = self.get_object() - context["versions"] = Version.objects.active().order_by("-release_date") + context["versions"] = Version.objects.version_dropdown() downloads = obj.downloads.all().order_by("operating_system") context["downloads"] = { k: list(v)