Skip to content

Commit eaa77bd

Browse files
committed
refactor: Use new model instead of tags for Release Programming Languages
1 parent 0e2486c commit eaa77bd

File tree

22 files changed

+632
-56
lines changed

22 files changed

+632
-56
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/home/metrics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ def get_release_programming_language_timeseries(self, start_year):
352352
programming_language_metrics = list(
353353
CodebaseRelease.objects.public()
354354
.values(
355-
programming_language_names=F("programming_languages__name"),
355+
programming_language_names=F("release_languages__programming_language__name"),
356356
year=F("first_published_at__year"),
357357
)
358358
.annotate(count=Count("year"))

django/library/jinja2/library/codebases/releases/retrieve.jinja

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,10 @@
362362
</div>
363363
<b class='card-title'>Programming Language</b>
364364
<div class="card-text mb-3">
365-
{% for pl in release.programming_languages.all() %}
366-
{{ search_tag_href(pl, category='codebases') }}
365+
{% for pl in release.release_languages.all() %}
366+
<a href='/codebases/?programmingLanguages={{ pl.programming_language.name }}'>
367+
{{ pl.programming_language.name }}
368+
</a>
367369
{% endfor %}
368370
</div>
369371
<b class='card-title'>Software Framework</b>
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import logging
2+
3+
from django.core.management.base import BaseCommand
4+
5+
from library.models import (
6+
ProgrammingLanguageTag,
7+
ProgrammingLanguage,
8+
ReleaseLanguage,
9+
CodebaseRelease,
10+
Codebase,
11+
)
12+
13+
14+
logger = logging.getLogger(__name__)
15+
16+
programming_languages = [
17+
{"name": "ABS", "url": "https://www.abs-lang.org", "is_pinned": False},
18+
{
19+
"name": "Assembly",
20+
"url": "https://en.wikipedia.org/wiki/Assembly_language",
21+
"is_pinned": False,
22+
},
23+
{"name": "C", "url": "https://www.c-language.org", "is_pinned": False},
24+
{
25+
"name": "C#",
26+
"url": "https://dotnet.microsoft.com/en-us/languages/csharp",
27+
"is_pinned": False,
28+
},
29+
{"name": "C++", "url": "https://isocpp.org", "is_pinned": False},
30+
{"name": "Common Lisp", "url": "https://common-lisp.net", "is_pinned": False},
31+
{"name": "Dart", "url": "https://dart.dev", "is_pinned": False},
32+
{"name": "Fortran", "url": "https://fortran-lang.org", "is_pinned": False},
33+
{"name": "Go", "url": "https://golang.org", "is_pinned": False},
34+
{"name": "Groovy", "url": "https://groovy-lang.org", "is_pinned": False},
35+
{"name": "Haskell", "url": "https://www.haskell.org", "is_pinned": False},
36+
{"name": "Java", "url": "https://www.oracle.com/java/", "is_pinned": True},
37+
{
38+
"name": "JavaScript",
39+
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript",
40+
"is_pinned": False,
41+
},
42+
{"name": "Julia", "url": "https://julialang.org", "is_pinned": False},
43+
{"name": "Kotlin", "url": "https://kotlinlang.org", "is_pinned": False},
44+
{
45+
"name": "Logo",
46+
"url": "http://el.media.mit.edu/logo-foundation/logo/",
47+
"is_pinned": True,
48+
},
49+
{"name": "Lisp", "url": "https://lisp-lang.org", "is_pinned": False},
50+
{
51+
"name": "NetLogo",
52+
"url": "https://ccl.northwestern.edu/netlogo/",
53+
"is_pinned": True,
54+
},
55+
{
56+
"name": "Objective-C",
57+
"url": "https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html",
58+
"is_pinned": False,
59+
},
60+
{"name": "PHP", "url": "https://www.php.net", "is_pinned": False},
61+
{"name": "Perl", "url": "https://www.perl.org", "is_pinned": False},
62+
{"name": "Python", "url": "https://www.python.org", "is_pinned": True},
63+
{"name": "R", "url": "https://www.r-project.org", "is_pinned": True},
64+
{"name": "Ruby", "url": "https://www.ruby-lang.org/en", "is_pinned": False},
65+
{"name": "Rust", "url": "https://www.rust-lang.org", "is_pinned": False},
66+
{"name": "Scala", "url": "https://www.scala-lang.org", "is_pinned": False},
67+
{"name": "Shell", "url": "https://www.gnu.org/software/bash/", "is_pinned": False},
68+
{"name": "Smalltalk", "url": "https://st.cs.uni-saarland.de", "is_pinned": False},
69+
{
70+
"name": "SQL",
71+
"url": "https://www.iso.org/standard/63555.html",
72+
"is_pinned": False,
73+
},
74+
{"name": "Swift", "url": "https://swift.org", "is_pinned": False},
75+
{"name": "TypeScript", "url": "https://www.typescriptlang.org", "is_pinned": False},
76+
{
77+
"name": "Visual Basic",
78+
"url": "https://docs.microsoft.com/en-us/dotnet/visual-basic/",
79+
"is_pinned": False,
80+
},
81+
{
82+
"name": "Wolfram Language",
83+
"url": "https://www.wolfram.com/language/",
84+
"is_pinned": False,
85+
},
86+
]
87+
88+
89+
class Command(BaseCommand):
90+
help = "Convert programming language tags to use the ReleaseLanguage model."
91+
92+
def handle(self, *args, **options):
93+
ProgrammingLanguage.objects.all().delete()
94+
for lang in programming_languages:
95+
ProgrammingLanguage.objects.create(**lang)
96+
97+
ReleaseLanguage.objects.all().delete()
98+
tags = ProgrammingLanguageTag.objects.all()
99+
for tag in tags:
100+
version = ""
101+
if tag.tag.name.lower().startswith("netlogo"):
102+
version = tag.tag.name[7:].strip()
103+
104+
programming_language = ProgrammingLanguage.objects.get_or_create(
105+
name__istartswith=tag.tag.name,
106+
defaults={"name": tag.tag.name, "is_user_defined": True},
107+
)[0]
108+
ReleaseLanguage.objects.create(
109+
programming_language=programming_language,
110+
release=tag.content_object,
111+
version=version,
112+
)

django/library/metadata.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,10 @@ 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(
201+
"programming_language__name"
202+
)
201203
],
202204
runtimePlatform=[
203205
tag.name for tag in release.platform_tags.all().order_by("name")
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("library", "0033_codebaseimage_description"),
11+
]
12+
13+
operations = [
14+
migrations.RenameModel(
15+
old_name="ProgrammingLanguage",
16+
new_name="ProgrammingLanguageTag",
17+
),
18+
migrations.RenameField(
19+
model_name="codebaserelease",
20+
old_name="programming_languages",
21+
new_name="programming_language_tags",
22+
),
23+
migrations.CreateModel(
24+
name="ProgrammingLanguage",
25+
fields=[
26+
(
27+
"id",
28+
models.AutoField(
29+
auto_created=True,
30+
primary_key=True,
31+
serialize=False,
32+
verbose_name="ID",
33+
),
34+
),
35+
("name", models.CharField(max_length=100, unique=True)),
36+
("url", models.URLField(blank=True)),
37+
("is_pinned", models.BooleanField(default=False)),
38+
("is_user_defined", models.BooleanField(default=False)),
39+
],
40+
),
41+
migrations.CreateModel(
42+
name="ReleaseLanguage",
43+
fields=[
44+
(
45+
"id",
46+
models.AutoField(
47+
auto_created=True,
48+
primary_key=True,
49+
serialize=False,
50+
verbose_name="ID",
51+
),
52+
),
53+
("version", models.CharField(max_length=20)),
54+
(
55+
"programming_language",
56+
models.ForeignKey(
57+
on_delete=django.db.models.deletion.CASCADE,
58+
related_name="release_languages",
59+
to="library.programminglanguage",
60+
),
61+
),
62+
(
63+
"release",
64+
models.ForeignKey(
65+
on_delete=django.db.models.deletion.CASCADE,
66+
related_name="release_languages",
67+
to="library.codebaserelease",
68+
),
69+
),
70+
],
71+
),
72+
]

django/library/models.py

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,38 @@ class CodebaseTag(TaggedItemBase):
8989
content_object = ParentalKey("library.Codebase", related_name="tagged_codebases")
9090

9191

92-
class ProgrammingLanguage(TaggedItemBase):
92+
class ProgrammingLanguageTag(TaggedItemBase):
9393
content_object = ParentalKey(
9494
"library.CodebaseRelease", related_name="tagged_release_languages"
9595
)
9696

9797

98+
@register_snippet
99+
class ProgrammingLanguage(models.Model):
100+
name = models.CharField(max_length=100, unique=True)
101+
url = models.URLField(blank=True)
102+
is_pinned = models.BooleanField(default=False)
103+
is_user_defined = models.BooleanField(default=False)
104+
105+
106+
class ReleaseLanguage(models.Model):
107+
programming_language = models.ForeignKey(
108+
"library.ProgrammingLanguage",
109+
related_name="release_languages",
110+
on_delete=models.CASCADE,
111+
)
112+
release = models.ForeignKey(
113+
"library.CodebaseRelease",
114+
related_name="release_languages",
115+
on_delete=models.CASCADE,
116+
)
117+
version = models.CharField(max_length=20)
118+
119+
@property
120+
def name(self):
121+
return self.programming_language.name
122+
123+
98124
class CodebaseReleasePlatformTag(TaggedItemBase):
99125
content_object = ParentalKey(
100126
"library.CodebaseRelease", related_name="tagged_release_platforms"
@@ -818,9 +844,9 @@ def all_release_frameworks(self):
818844
@property
819845
def all_release_programming_languages(self):
820846
return list(
821-
self.releases.exclude(programming_languages__isnull=True).values_list(
822-
"programming_languages__name", flat=True
823-
)
847+
self.releases.exclude(release_languages__isnull=True)
848+
.values_list("release_languages__programming_language__name", flat=True)
849+
.distinct()
824850
)
825851

826852
def download_count(self):
@@ -986,15 +1012,15 @@ def create_release_from_source(self, source_release, release_metadata):
9861012
# cache these before removing source release id to copy it over
9871013
contributors = ReleaseContributor.objects.filter(release_id=source_release.id)
9881014
platform_tags = source_release.platform_tags.all()
989-
programming_languages = source_release.programming_languages.all()
1015+
release_languages = source_release.release_languages.all()
9901016
# set source_release.id to None to create a new release
9911017
# see https://docs.djangoproject.com/en/4.2/topics/db/queries/#copying-model-instances
9921018
source_release.id = None
9931019
source_release._state.adding = True
9941020
source_release.__dict__.update(**release_metadata)
9951021
source_release.save()
9961022
source_release.platform_tags.add(*platform_tags)
997-
source_release.programming_languages.add(*programming_languages)
1023+
source_release.release_languages.add(*release_languages)
9981024
contributors.copy_to(source_release)
9991025
return source_release
10001026

@@ -1143,7 +1169,12 @@ def with_platforms(self):
11431169
return self.prefetch_related("tagged_release_platforms__tag")
11441170

11451171
def with_programming_languages(self):
1146-
return self.prefetch_related("tagged_release_languages__tag")
1172+
return self.prefetch_related(
1173+
Prefetch(
1174+
"release_languages",
1175+
ReleaseLanguage.objects.prefetch_related("programming_language"),
1176+
)
1177+
)
11471178

11481179
def with_codebase(self):
11491180
return self.prefetch_related(
@@ -1285,8 +1316,8 @@ class Status(models.TextChoices):
12851316
through=CodebaseReleasePlatformTag, related_name="platform_codebase_releases"
12861317
)
12871318
platforms = models.ManyToManyField(Platform)
1288-
programming_languages = ClusterTaggableManager(
1289-
through=ProgrammingLanguage, related_name="pl_codebase_releases"
1319+
programming_language_tags = ClusterTaggableManager(
1320+
through=ProgrammingLanguageTag, related_name="pl_codebase_releases"
12901321
)
12911322
codebase = models.ForeignKey(
12921323
Codebase, related_name="releases", on_delete=models.PROTECT
@@ -1341,7 +1372,7 @@ class Status(models.TextChoices):
13411372
],
13421373
),
13431374
index.RelatedFields(
1344-
"programming_languages",
1375+
"release_languages",
13451376
[
13461377
index.SearchField("name"),
13471378
],
@@ -1463,7 +1494,7 @@ def validate_metadata(self):
14631494
# naive check for metadata being present (i.e., None or false-y values)
14641495
if not self.license:
14651496
errors.append(ValidationError(_("Please specify a software license.")))
1466-
if not self.programming_languages.exists():
1497+
if not self.release_languages.exists():
14671498
errors.append(
14681499
ValidationError(
14691500
_(
@@ -2733,7 +2764,7 @@ def __init__(self, release: CodebaseRelease):
27332764
self.description = codebase.description.raw
27342765
self.release_notes = release.release_notes.raw if release.release_notes else ""
27352766
self.version = release.version_number
2736-
self.programming_languages = release.programming_languages.all()
2767+
self.release_languages = release.release_languages.all()
27372768
self.os = release.os
27382769
self.identifier = release.permanent_url
27392770
self.url = release.permanent_url

0 commit comments

Comments
 (0)