Skip to content

Commit d2744ff

Browse files
committed
refactor: Use new model instead of tags for Release Programming Languages
1 parent a3c294f commit d2744ff

File tree

18 files changed

+465
-39
lines changed

18 files changed

+465
-39
lines changed

django/curator/models.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from nltk.tokenize import word_tokenize
1616
from taggit.models import Tag
1717

18-
from library.models import ProgrammingLanguage, CodebaseReleasePlatformTag
18+
from library.models import ProgrammingLanguageTag, CodebaseReleasePlatformTag
1919

2020
logger = logging.getLogger(__name__)
2121

@@ -42,10 +42,10 @@ def get_through_tables():
4242
class TagCuratorProxyQuerySet(models.QuerySet):
4343
def with_comma(self):
4444
return self.filter(name__icontains=",")
45-
46-
def programming_languages(self):
45+
46+
def programming_language_tags(self):
4747
return self.filter(
48-
id__in=ProgrammingLanguage.objects.values_list("tag_id", flat=True)
48+
id__in=ProgrammingLanguageTag.objects.values_list("tag_id", flat=True)
4949
)
5050

5151
def platforms(self):

django/library/metadata.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ def _convert_release(cls, release) -> CodeMeta:
196196
),
197197
programmingLanguage=[
198198
# FIXME: this can include "version" when langs are refactored
199-
{"@type": "ComputerLanguage", "name": pl.name}
200-
for pl in release.programming_languages.all().order_by("name")
199+
{"@type": "ComputerLanguage", "name": rl.programming_language.name}
200+
for rl in release.release_languages.all().order_by("programming_language__name")
201201
],
202202
runtimePlatform=[
203203
tag.name for tag in release.platform_tags.all().order_by("name")
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Generated by Django 4.2.22 on 2025-09-29 23:02
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
import modelcluster.contrib.taggit
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
("taggit", "0005_auto_20220424_2025"),
12+
("library", "0032_license_text_codemeta_snapshot"),
13+
]
14+
15+
operations = [
16+
migrations.RenameModel(
17+
old_name="ProgrammingLanguage",
18+
new_name="ProgrammingLanguageTag",
19+
),
20+
migrations.RenameField(
21+
model_name="codebaserelease",
22+
old_name="programming_languages",
23+
new_name="programming_language_tags",
24+
),
25+
migrations.CreateModel(
26+
name="ProgrammingLanguage",
27+
fields=[
28+
(
29+
"id",
30+
models.AutoField(
31+
auto_created=True,
32+
primary_key=True,
33+
serialize=False,
34+
verbose_name="ID",
35+
),
36+
),
37+
("name", models.CharField(max_length=100, unique=True)),
38+
("url", models.URLField(blank=True)),
39+
("is_pinned", models.BooleanField(default=False)),
40+
("is_user_defined", models.BooleanField(default=False)),
41+
],
42+
),
43+
migrations.CreateModel(
44+
name="ReleaseLanguage",
45+
fields=[
46+
(
47+
"id",
48+
models.AutoField(
49+
auto_created=True,
50+
primary_key=True,
51+
serialize=False,
52+
verbose_name="ID",
53+
),
54+
),
55+
("version", models.CharField(max_length=20)),
56+
(
57+
"programming_language",
58+
models.ForeignKey(
59+
on_delete=django.db.models.deletion.CASCADE,
60+
related_name="release_languages",
61+
to="library.programminglanguage",
62+
),
63+
),
64+
(
65+
"release",
66+
models.ForeignKey(
67+
on_delete=django.db.models.deletion.CASCADE,
68+
related_name="release_languages",
69+
to="library.codebaserelease",
70+
),
71+
),
72+
],
73+
),
74+
]

django/library/models.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,34 @@ class CodebaseTag(TaggedItemBase):
9292
content_object = ParentalKey("library.Codebase", related_name="tagged_codebases")
9393

9494

95-
class ProgrammingLanguage(TaggedItemBase):
95+
class ProgrammingLanguageTag(TaggedItemBase):
9696
content_object = ParentalKey(
9797
"library.CodebaseRelease", related_name="tagged_release_languages"
9898
)
9999

100100

101+
@register_snippet
102+
class ProgrammingLanguage(models.Model):
103+
name = models.CharField(max_length=100, unique=True)
104+
url = models.URLField(blank=True)
105+
is_pinned = models.BooleanField(default=False)
106+
is_user_defined = models.BooleanField(default=False)
107+
108+
109+
class ReleaseLanguageQuerySet(models.QuerySet):
110+
def for_release(self, release):
111+
return self.select_related('programming_language').filter(release=release)
112+
113+
class ReleaseLanguage(models.Model):
114+
programming_language = models.ForeignKey(
115+
"library.ProgrammingLanguage", related_name="release_languages", on_delete=models.CASCADE
116+
)
117+
release = models.ForeignKey(
118+
"library.CodebaseRelease", related_name="release_languages", on_delete=models.CASCADE
119+
)
120+
version = models.CharField(max_length=20)
121+
122+
101123
class CodebaseReleasePlatformTag(TaggedItemBase):
102124
content_object = ParentalKey(
103125
"library.CodebaseRelease", related_name="tagged_release_platforms"
@@ -989,15 +1011,15 @@ def create_release_from_source(self, source_release, release_metadata):
9891011
# cache these before removing source release id to copy it over
9901012
contributors = ReleaseContributor.objects.filter(release_id=source_release.id)
9911013
platform_tags = source_release.platform_tags.all()
992-
programming_languages = source_release.programming_languages.all()
1014+
release_languages = source_release.release_languages.all()
9931015
# set source_release.id to None to create a new release
9941016
# see https://docs.djangoproject.com/en/4.2/topics/db/queries/#copying-model-instances
9951017
source_release.id = None
9961018
source_release._state.adding = True
9971019
source_release.__dict__.update(**release_metadata)
9981020
source_release.save()
9991021
source_release.platform_tags.add(*platform_tags)
1000-
source_release.programming_languages.add(*programming_languages)
1022+
source_release.release_languages.add(*release_languages)
10011023
contributors.copy_to(source_release)
10021024
return source_release
10031025

@@ -1287,8 +1309,8 @@ class Status(models.TextChoices):
12871309
through=CodebaseReleasePlatformTag, related_name="platform_codebase_releases"
12881310
)
12891311
platforms = models.ManyToManyField(Platform)
1290-
programming_languages = ClusterTaggableManager(
1291-
through=ProgrammingLanguage, related_name="pl_codebase_releases"
1312+
programming_language_tags = ClusterTaggableManager(
1313+
through=ProgrammingLanguageTag, related_name="pl_codebase_releases"
12921314
)
12931315
codebase = models.ForeignKey(
12941316
Codebase, related_name="releases", on_delete=models.PROTECT
@@ -1462,7 +1484,7 @@ def validate_metadata(self):
14621484
# naive check for metadata being present (i.e., None or false-y values)
14631485
if not self.license:
14641486
errors.append(ValidationError(_("Please specify a software license.")))
1465-
if not self.programming_languages.exists():
1487+
if not self.release_languages.exists():
14661488
errors.append(
14671489
ValidationError(
14681490
_(
@@ -2725,7 +2747,7 @@ def __init__(self, release: CodebaseRelease):
27252747
self.description = codebase.description.raw
27262748
self.release_notes = release.release_notes.raw if release.release_notes else ""
27272749
self.version = release.version_number
2728-
self.programming_languages = release.programming_languages.all()
2750+
self.release_languages = release.release_languages.all()
27292751
self.os = release.os
27302752
self.identifier = release.permanent_url
27312753
self.url = release.permanent_url

django/library/serializers.py

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
CodebaseReleaseDownload,
3838
Contributor,
3939
License,
40+
ProgrammingLanguage,
41+
ReleaseLanguage,
4042
CodebaseImage,
4143
PeerReviewerFeedback,
4244
PeerReviewInvitation,
@@ -63,6 +65,39 @@ class Meta:
6365
)
6466

6567

68+
class ProgrammingLanguageSerializer(serializers.ModelSerializer):
69+
class Meta:
70+
model = ProgrammingLanguage
71+
fields = (
72+
"id",
73+
"name",
74+
"url",
75+
"is_pinned",
76+
"is_user_defined",
77+
)
78+
79+
class ReleaseLanguageSerializer(serializers.ModelSerializer):
80+
programming_language = ProgrammingLanguageSerializer(read_only=True)
81+
82+
# def create(self, validated_data):
83+
# programming_language_data = self.initial_data.pop("programming_language")
84+
# programming_language, created = ProgrammingLanguage.objects.get_or_create(
85+
# name=programming_language_data['name']
86+
# )
87+
# validated_data["programming_language"] = programming_language
88+
# instance = ReleaseLanguage(**validated_data)
89+
# instance.save()
90+
# return instance
91+
92+
class Meta:
93+
model = ReleaseLanguage
94+
fields = (
95+
"programming_language",
96+
"release",
97+
"version",
98+
)
99+
100+
66101
class ContributorSerializer(serializers.ModelSerializer):
67102
# Need an ID for Vue-Multiselect
68103
id = serializers.IntegerField(read_only=True)
@@ -563,7 +598,8 @@ class CodebaseReleaseSerializer(serializers.ModelSerializer):
563598
can_edit_originals = serializers.ReadOnlyField()
564599
os_display = serializers.ReadOnlyField(source="get_os_display")
565600
platforms = TagSerializer(many=True, source="platform_tags")
566-
programming_languages = TagSerializer(many=True)
601+
programming_language_tags = TagSerializer(many=True)
602+
release_languages = ReleaseLanguageSerializer(read_only=True, many=True)
567603
submitter = RelatedUserSerializer(read_only=True, label="Submitter")
568604
version_number = serializers.ReadOnlyField()
569605
release_notes = MarkdownField(max_length=2048)
@@ -609,7 +645,7 @@ class Meta:
609645
"os_display",
610646
"peer_reviewed",
611647
"platforms",
612-
"programming_languages",
648+
"release_languages",
613649
"submitted_package",
614650
"submitter",
615651
"codebase",
@@ -634,17 +670,41 @@ def get_possible_licenses(self, instance):
634670
)
635671
return serialized.data
636672

673+
def resolve_language(self, language_name):
674+
programming_language = ProgrammingLanguage.objects.filter(name__iexact=language_name).first()
675+
if not programming_language:
676+
programming_language = ProgrammingLanguage.objects.create(
677+
name=language_name,
678+
is_user_defined=True
679+
)
680+
return programming_language
681+
637682
def update(self, instance, validated_data):
638-
programming_languages = TagSerializer(
639-
many=True, data=validated_data.pop("programming_languages")
640-
)
641683
platform_tags = TagSerializer(
642684
many=True, data=validated_data.pop("platform_tags")
643685
)
644686

645-
set_tags(instance, programming_languages, "programming_languages")
646687
set_tags(instance, platform_tags, "platform_tags")
647688

689+
# Handle programming languages
690+
release_languages_data = self.initial_data.pop("release_languages")
691+
if release_languages_data:
692+
# Clear existing programming languages
693+
instance.release_languages.all().delete()
694+
695+
696+
# Create new release languages
697+
for release_language_data in release_languages_data:
698+
language_data = release_language_data.get("programming_language")
699+
if not language_data or "name" not in language_data:
700+
raise ValidationError("Malformed programming language data")
701+
programming_language = self.resolve_language(language_data["name"])
702+
ReleaseLanguage.objects.create(
703+
programming_language=programming_language,
704+
release=instance,
705+
version=release_language_data.get("version", ""),
706+
)
707+
648708
raw_license = validated_data.pop("license")
649709
existing_license = License.objects.get(name=raw_license["name"])
650710
instance.license = existing_license

django/library/tests/base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
CodebaseRelease,
1111
License,
1212
Role,
13+
ProgrammingLanguage,
14+
ReleaseLanguage,
1315
Contributor,
1416
PeerReviewInvitation,
1517
PeerReview,
@@ -183,7 +185,10 @@ def setUpPublishableDraftRelease(cls, codebase):
183185
)
184186
draft_release.license, created = License.objects.get_or_create(name="MIT")
185187
draft_release.os = "Any"
186-
draft_release.programming_languages.add("Python")
188+
ReleaseLanguage.objects.create(
189+
programming_language=ProgrammingLanguage.objects.get_or_create(name="Python"),
190+
release=draft_release,
191+
)
187192
contributor_factory = ContributorFactory(user=draft_release.submitter)
188193
release_contributor_factory = ReleaseContributorFactory(draft_release)
189194
contributor = contributor_factory.create()

django/library/tests/test_models.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
ReleaseContributorFactory,
1515
ReleaseSetup,
1616
)
17-
from ..models import Codebase, CodebaseRelease, License
17+
from ..models import ProgrammingLanguage, ReleaseLanguage, Codebase, CodebaseRelease, License
1818

1919
logger = logging.getLogger(__name__)
2020

@@ -248,7 +248,11 @@ def test_metadata_completeness(self):
248248
ValidationError, lambda: self.codebase_release.validate_publishable()
249249
)
250250

251-
self.codebase_release.programming_languages.add("Java")
251+
ReleaseLanguage.objects.create(
252+
programming_language=ProgrammingLanguage.objects.get_or_create(name="Java"),
253+
release=self.codebase_release,
254+
version="8"
255+
)
252256
self.assertRaises(
253257
ValidationError, lambda: self.codebase_release.validate_publishable()
254258
)

django/library/tests/test_views.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
)
2020
from library.forms import PeerReviewerFeedbackReviewerForm
2121
from library.fs import FileCategoryDirectories
22-
from library.models import Codebase, CodebaseRelease, License, PeerReview
22+
from library.models import ProgrammingLanguage, ReleaseLanguage, Codebase, CodebaseRelease, License, PeerReview
2323
from library.tests.base import ReviewSetup
2424
from .base import (
2525
CodebaseFactory,
@@ -561,7 +561,11 @@ def test_publish_codebaserelease(self):
561561
)
562562
self.assertRaises(ValidationError, lambda: self.codebase_release.publish())
563563

564-
self.codebase_release.programming_languages.add("Java")
564+
ReleaseLanguage.objects.create(
565+
programming_language=ProgrammingLanguage.objects.get_or_create(name="Java"),
566+
release=self.codebase_release,
567+
version="8"
568+
)
565569
self.codebase_release.publish()
566570

567571
download_response = self.client.get(

django/library/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
r"codebases/(?P<identifier>[\w\-.]+)/releases", views.CodebaseReleaseViewSet
1616
)
1717
router.register(r"reviewers", views.PeerReviewerViewSet),
18+
router.register(r"programming-languages", views.ProgrammingLanguageViewSet),
1819
router.register(
1920
r"reviews/(?P<slug>[\da-f\-]+)/editor/invitations",
2021
views.PeerReviewInvitationViewSet,

0 commit comments

Comments
 (0)