From 4e249ac6da0978bde0504f5420061a3d401ff6fb Mon Sep 17 00:00:00 2001 From: Lacey Williams Henschel Date: Mon, 30 Jan 2023 10:47:31 -0800 Subject: [PATCH 1/5] :bank: Add LibraryVersion M2M through model and tests --- .../migrations/0004_auto_20230130_1830.py | 56 +++++++++++++++++++ libraries/models.py | 18 ++++++ libraries/tests/fixtures.py | 15 +++++ 3 files changed, 89 insertions(+) create mode 100644 libraries/migrations/0004_auto_20230130_1830.py diff --git a/libraries/migrations/0004_auto_20230130_1830.py b/libraries/migrations/0004_auto_20230130_1830.py new file mode 100644 index 00000000..5a7a4726 --- /dev/null +++ b/libraries/migrations/0004_auto_20230130_1830.py @@ -0,0 +1,56 @@ +# Generated by Django 3.2.2 on 2023-01-30 18:30 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("versions", "0005_version_active"), + ("libraries", "0003_library_slug"), + ] + + operations = [ + migrations.CreateModel( + name="LibraryVersion", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "library", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="library_version", + to="libraries.library", + ), + ), + ( + "version", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="library_version", + to="versions.version", + ), + ), + ], + ), + migrations.AddField( + model_name="library", + name="versions", + field=models.ManyToManyField( + related_name="libraries", + through="libraries.LibraryVersion", + to="versions.Version", + ), + ), + ] diff --git a/libraries/models.py b/libraries/models.py index 8314041e..f9897d54 100644 --- a/libraries/models.py +++ b/libraries/models.py @@ -43,6 +43,9 @@ class Library(models.Model): blank=True, null=True, ) + versions = models.ManyToManyField( + "versions.Version", through="libraries.LibraryVersion", related_name="libraries" + ) cpp_standard_minimum = models.CharField(max_length=50, blank=True, null=True) active_development = models.BooleanField(default=True, db_index=True) @@ -89,6 +92,21 @@ class Library(models.Model): return self.github_properties()["repo"] +class LibraryVersion(models.Model): + version = models.ForeignKey( + "versions.Version", + related_name="library_version", + on_delete=models.SET_NULL, + null=True, + ) + library = models.ForeignKey( + "libraries.Library", + related_name="library_version", + on_delete=models.SET_NULL, + null=True, + ) + + class Issue(models.Model): """ Model that tracks Library repository issues in Github diff --git a/libraries/tests/fixtures.py b/libraries/tests/fixtures.py index 7d0f01d2..7e129a7c 100644 --- a/libraries/tests/fixtures.py +++ b/libraries/tests/fixtures.py @@ -18,6 +18,21 @@ def library(db): ) +@pytest.fixture +def library_version(library, version): + return baker.make("libraries.LibraryVersion", library=library, version=version) + + +@pytest.fixture +def issue(library): + return baker.make("libraries.Issue", library=library) + + +@pytest.fixture +def pull_request(library): + return baker.make("libraries.PullRequest", library=library) + + @pytest.fixture(autouse=True) def github_api_get_ref_response(db): """Returns a JSON example of GhApi().api.git.get_ref(owner=owner, repo=repo, ref=ref)""" From eccd44a1a530329c83b411bd15b7aaada9ca87dd Mon Sep 17 00:00:00 2001 From: Lacey Williams Henschel Date: Mon, 30 Jan 2023 10:47:56 -0800 Subject: [PATCH 2/5] :umbrella: Tests for Library models --- libraries/tests/test_models.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/libraries/tests/test_models.py b/libraries/tests/test_models.py index 2b1322a7..73e468d7 100644 --- a/libraries/tests/test_models.py +++ b/libraries/tests/test_models.py @@ -1,3 +1,6 @@ +from model_bakery import baker + + def test_github_properties(library): properties = library.github_properties() assert properties["owner"] == "boostorg" @@ -10,3 +13,40 @@ def test_github_owner(library): def test_github_repo(library): assert library.github_repo == "multi_array" + + +def test_category_creation(category): + assert category.name is not None + + +def test_library_creation(library): + assert library.versions.count() == 0 + + +def test_issue_creation(issue, library): + assert issue.library == library + + +def test_pull_request_creation(pull_request, library): + assert pull_request.library == library + + +def test_library_version_creation(library_version, library, version): + assert library_version.library == library + assert library_version.version == version + + +def test_library_version_multiple_versions(library, library_version): + assert library.versions.count() == 1 + assert library.versions.filter( + library_version__version=library_version.version + ).exists() + other_version = baker.make("versions.Version") + new_library_version = baker.make( + "libraries.LibraryVersion", library=library, version=other_version + ) + assert library.versions.count() == 2 + assert library.versions.filter( + library_version__version=library_version.version + ).exists() + assert library.versions.filter(library_version__version=other_version).exists() From e5b9612af53796274f492c57d2122b45604f0af8 Mon Sep 17 00:00:00 2001 From: Lacey Williams Henschel Date: Wed, 1 Feb 2023 15:14:36 -0800 Subject: [PATCH 3/5] :sparkles: Add LibraryVersion to the admin --- libraries/admin.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/admin.py b/libraries/admin.py index 8733b687..547411b8 100644 --- a/libraries/admin.py +++ b/libraries/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import Category, Issue, Library, PullRequest +from .models import Category, Issue, Library, LibraryVersion, PullRequest @admin.register(Category) @@ -26,6 +26,13 @@ class LibraryAdmin(admin.ModelAdmin): return obj.active_development +@admin.register(LibraryVersion) +class LibraryVersionAdmin(admin.ModelAdmin): + list_display = ["library", "version"] + list_filter = ["library", "version"] + search_fields = ["library__name", "version__name"] + + @admin.register(Issue) class IssueAdmin(admin.ModelAdmin): list_display = ["title", "number", "is_open", "closed"] From 2074f31c752b10a362387a05fe0d02d991a6c8c0 Mon Sep 17 00:00:00 2001 From: Lacey Williams Henschel Date: Wed, 1 Feb 2023 15:14:52 -0800 Subject: [PATCH 4/5] :sparkles: Add a str method --- libraries/models.py | 3 +++ libraries/tests/test_models.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/libraries/models.py b/libraries/models.py index f9897d54..442bc6bc 100644 --- a/libraries/models.py +++ b/libraries/models.py @@ -106,6 +106,9 @@ class LibraryVersion(models.Model): null=True, ) + def __str__(self): + return f"{self.library.name} ({self.version.name})" + class Issue(models.Model): """ diff --git a/libraries/tests/test_models.py b/libraries/tests/test_models.py index 73e468d7..643e3690 100644 --- a/libraries/tests/test_models.py +++ b/libraries/tests/test_models.py @@ -36,6 +36,10 @@ def test_library_version_creation(library_version, library, version): assert library_version.version == version +def test_library_version_str(library_version, library, version): + assert str(library_version) == f"{library.name} ({version.name})" + + def test_library_version_multiple_versions(library, library_version): assert library.versions.count() == 1 assert library.versions.filter( From f601b67613c7a9a5921fa432e78b98f86b72acfa Mon Sep 17 00:00:00 2001 From: Lacey Williams Henschel Date: Wed, 1 Feb 2023 16:13:41 -0800 Subject: [PATCH 5/5] :sparkles: Add command to generate test versions --- README.md | 16 ++++++ .../commands/generate_fake_versions.py | 55 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 libraries/management/commands/generate_fake_versions.py diff --git a/README.md b/README.md index cdd9c8c3..638f2075 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,22 @@ For production, execute: $ yarn build ``` +## Generating Fake Data + +### Versions and LibraryVersions + +First, make sure your `GITHUB_TOKEN` is set in you `.env` file and run `./manage.py update_libraries`. This takes a long time. See below. + +Run `./manage.py generate_fake_versions`. This will create 50 active Versions, and associate Libraries to them. + +The data created is realistic-looking in that each Library will contain a M2M relationship to every Version newer than the oldest one it's included in. (So if a Library's earliest LibraryVersion is 1.56.0, then there will be a LibraryVersion object for that Library for each Version since 1.56.0 was released.) + +This does not add VersionFile objects to the Versions. + +### Libraries, Pull Requests, and Issues + +There is not currently a way to generate fake Libraries, Issues, or Pull Requests. To generate those, use your GitHub token and run `./manage.py update_libraries` locally to pull in live GitHub data. This command takes a long time to run; you might consider editing `libraries/github.py` to add counters and breaks to shorten the runtime. + ## Deploying TDB diff --git a/libraries/management/commands/generate_fake_versions.py b/libraries/management/commands/generate_fake_versions.py new file mode 100644 index 00000000..e86480e9 --- /dev/null +++ b/libraries/management/commands/generate_fake_versions.py @@ -0,0 +1,55 @@ +import djclick as click +import random + +from datetime import timedelta +from django.utils import timezone +from faker import Faker + +from libraries.models import Library, LibraryVersion +from versions.models import Version + +fake = Faker() + + +@click.command() +def command(): + """ + Create Version objects and attach libraries to them. + """ + + # Create versions + release_date = timezone.now() - timedelta(days=3650) + for i in range(30, 80): + version, created = Version.objects.get_or_create( + name=f"1.{i}.0", + defaults={ + "release_date": release_date, + "description": fake.paragraph(nb_sentences=2), + "active": True, + }, + ) + if created: + click.secho(f"Version {version.name} created succcessfully", fg="green") + else: + click.secho(f"Version {version.name} already exists.") + + delta = random.choice(range(20, 50)) + release_date += timedelta(delta) + + for library in Library.objects.all(): + # Select the starting version randomly + start_version = Version.objects.order_by("?").first() + + # Add a LibraryVersion for each Version newer than the starting version + for version in Version.objects.filter( + release_date__gt=start_version.release_date + ): + lib_version, created = LibraryVersion.objects.get_or_create( + library=library, version=version + ) + if created: + click.secho(f"---{lib_version} created succcessfully", fg="green") + else: + click.secho(f"LibraryVersion {lib_version} already exists.") + + click.secho("All done!", fg="green")