Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions django/curator/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from nltk.tokenize import word_tokenize
from taggit.models import Tag

from library.models import ProgrammingLanguage, CodebaseReleasePlatformTag
from library.models import ProgrammingLanguageTag, CodebaseReleasePlatformTag

logger = logging.getLogger(__name__)

Expand All @@ -42,10 +42,10 @@ def get_through_tables():
class TagCuratorProxyQuerySet(models.QuerySet):
def with_comma(self):
return self.filter(name__icontains=",")

def programming_languages(self):
def programming_language_tags(self):
return self.filter(
id__in=ProgrammingLanguage.objects.values_list("tag_id", flat=True)
id__in=ProgrammingLanguageTag.objects.values_list("tag_id", flat=True)
)

def platforms(self):
Expand Down
2 changes: 1 addition & 1 deletion django/home/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ def get_release_programming_language_timeseries(self, start_year):
programming_language_metrics = list(
CodebaseRelease.objects.public()
.values(
programming_language_names=F("programming_languages__name"),
programming_language_names=F("release_languages__programming_language__name"),
year=F("first_published_at__year"),
)
.annotate(count=Count("year"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,10 @@
</div>
<b class='card-title'>Programming Language</b>
<div class="card-text mb-3">
{% for pl in release.programming_languages.all() %}
{{ search_tag_href(pl, category='codebases') }}
{% for pl in release.release_languages.all() %}
<a href='/codebases/?programmingLanguages={{ pl.programming_language.name }}'>
{{ pl.programming_language.name }}
</a>
{% endfor %}
</div>
<b class='card-title'>Software Framework</b>
Expand Down
131 changes: 131 additions & 0 deletions django/library/management/commands/convert_language_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import logging

from django.core.management.base import BaseCommand
from django.core.exceptions import ObjectDoesNotExist

from library.models import (
ProgrammingLanguageTag,
ProgrammingLanguage,
ReleaseLanguage,
CodebaseRelease,
Codebase,
)


logger = logging.getLogger(__name__)

programming_languages = [
{"name": "ABS", "url": "https://www.abs-lang.org", "is_pinned": False},
{
"name": "Assembly",
"url": "https://en.wikipedia.org/wiki/Assembly_language",
"is_pinned": False,
},
{"name": "C", "url": "https://www.c-language.org", "is_pinned": False},
{
"name": "C#",
"url": "https://dotnet.microsoft.com/en-us/languages/csharp",
"is_pinned": False,
},
{"name": "C++", "url": "https://isocpp.org", "is_pinned": False},
{"name": "Common Lisp", "url": "https://common-lisp.net", "is_pinned": False},
{"name": "Dart", "url": "https://dart.dev", "is_pinned": False},
{"name": "Fortran", "url": "https://fortran-lang.org", "is_pinned": False},
{"name": "Go", "url": "https://golang.org", "is_pinned": False},
{"name": "Groovy", "url": "https://groovy-lang.org", "is_pinned": False},
{"name": "Haskell", "url": "https://www.haskell.org", "is_pinned": False},
{"name": "Java", "url": "https://www.oracle.com/java/", "is_pinned": True},
{
"name": "JavaScript",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript",
"is_pinned": False,
},
{"name": "Julia", "url": "https://julialang.org", "is_pinned": False},
{"name": "Kotlin", "url": "https://kotlinlang.org", "is_pinned": False},
{
"name": "Logo",
"url": "http://el.media.mit.edu/logo-foundation/logo/",
"is_pinned": True,
},
{"name": "Lisp", "url": "https://lisp-lang.org", "is_pinned": False},
{
"name": "NetLogo",
"url": "https://ccl.northwestern.edu/netlogo/",
"is_pinned": True,
},
{
"name": "Objective-C",
"url": "https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html",
"is_pinned": False,
},
{"name": "PHP", "url": "https://www.php.net", "is_pinned": False},
{"name": "Perl", "url": "https://www.perl.org", "is_pinned": False},
{"name": "Python", "url": "https://www.python.org", "is_pinned": True},
{"name": "R", "url": "https://www.r-project.org", "is_pinned": True},
{"name": "Ruby", "url": "https://www.ruby-lang.org/en", "is_pinned": False},
{"name": "Rust", "url": "https://www.rust-lang.org", "is_pinned": False},
{"name": "Scala", "url": "https://www.scala-lang.org", "is_pinned": False},
{"name": "Shell", "url": "https://www.gnu.org/software/bash/", "is_pinned": False},
{"name": "Smalltalk", "url": "https://st.cs.uni-saarland.de", "is_pinned": False},
{
"name": "SQL",
"url": "https://www.iso.org/standard/63555.html",
"is_pinned": False,
},
{"name": "Swift", "url": "https://swift.org", "is_pinned": False},
{"name": "TypeScript", "url": "https://www.typescriptlang.org", "is_pinned": False},
{
"name": "Visual Basic",
"url": "https://docs.microsoft.com/en-us/dotnet/visual-basic/",
"is_pinned": False,
},
{
"name": "Wolfram Language",
"url": "https://www.wolfram.com/language/",
"is_pinned": False,
},
]


class Command(BaseCommand):
help = "Convert programming language tags to use the ReleaseLanguage model."

def handle(self, *args, **options):
ProgrammingLanguage.objects.all().delete()
for lang in programming_languages:
ProgrammingLanguage.objects.create(**lang)

ReleaseLanguage.objects.all().delete()
tags = ProgrammingLanguageTag.objects.all()
aliases = {}
for tag in tags:
version = ""
if tag.tag.name.lower().startswith("netlogo"):
version = tag.tag.name[7:].strip()

programming_language_name = tag.tag.name
if tag.tag.name in aliases:
programming_language_name = aliases[tag.tag.name]

try:
programming_language = ProgrammingLanguage.objects.get(name__iexact=programming_language_name)
except ObjectDoesNotExist:
programming_language = None

if programming_language is None:
programming_language_name = input("Enter the programming language name for tag '{}' (leave blank to skip): ".format(tag.tag.name))
if programming_language_name == "":
logger.info("Skipping tag '{}'".format(tag.tag.name))
continue
aliases[tag.tag.name] = programming_language_name

programming_language = ProgrammingLanguage.objects.get_or_create(
name__iexact=programming_language_name,
defaults={"name": programming_language_name, "is_user_defined": True},
)[0]

ReleaseLanguage.objects.create(
programming_language=programming_language,
release=tag.content_object,
version=version,
)
6 changes: 4 additions & 2 deletions django/library/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,10 @@ def _convert_release(cls, release) -> CodeMeta:
),
programmingLanguage=[
# FIXME: this can include "version" when langs are refactored
{"@type": "ComputerLanguage", "name": pl.name}
for pl in release.programming_languages.all().order_by("name")
{"@type": "ComputerLanguage", "name": rl.programming_language.name}
for rl in release.release_languages.all().order_by(
"programming_language__name"
)
],
runtimePlatform=[
tag.name for tag in release.platform_tags.all().order_by("name")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Generated by Django 4.2.22 on 2025-09-29 23:02

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("library", "0033_codebaseimage_description"),
]

operations = [
migrations.RenameModel(
old_name="ProgrammingLanguage",
new_name="ProgrammingLanguageTag",
),
migrations.RenameField(
model_name="codebaserelease",
old_name="programming_languages",
new_name="programming_language_tags",
),
migrations.CreateModel(
name="ProgrammingLanguage",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100, unique=True)),
("url", models.URLField(blank=True)),
("is_pinned", models.BooleanField(default=False)),
("is_user_defined", models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name="ReleaseLanguage",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("version", models.CharField(max_length=20)),
(
"programming_language",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="release_languages",
to="library.programminglanguage",
),
),
(
"release",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="release_languages",
to="library.codebaserelease",
),
),
],
),
]
55 changes: 43 additions & 12 deletions django/library/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,38 @@ class CodebaseTag(TaggedItemBase):
content_object = ParentalKey("library.Codebase", related_name="tagged_codebases")


class ProgrammingLanguage(TaggedItemBase):
class ProgrammingLanguageTag(TaggedItemBase):
content_object = ParentalKey(
"library.CodebaseRelease", related_name="tagged_release_languages"
)


@register_snippet
class ProgrammingLanguage(models.Model):
name = models.CharField(max_length=100, unique=True)
url = models.URLField(blank=True)
is_pinned = models.BooleanField(default=False)
is_user_defined = models.BooleanField(default=False)


class ReleaseLanguage(models.Model):
programming_language = models.ForeignKey(
"library.ProgrammingLanguage",
related_name="release_languages",
on_delete=models.CASCADE,
)
release = models.ForeignKey(
"library.CodebaseRelease",
related_name="release_languages",
on_delete=models.CASCADE,
)
version = models.CharField(max_length=20)

@property
def name(self):
return self.programming_language.name


class CodebaseReleasePlatformTag(TaggedItemBase):
content_object = ParentalKey(
"library.CodebaseRelease", related_name="tagged_release_platforms"
Expand Down Expand Up @@ -818,9 +844,9 @@ def all_release_frameworks(self):
@property
def all_release_programming_languages(self):
return list(
self.releases.exclude(programming_languages__isnull=True).values_list(
"programming_languages__name", flat=True
)
self.releases.exclude(release_languages__isnull=True)
.values_list("release_languages__programming_language__name", flat=True)
.distinct()
)

def download_count(self):
Expand Down Expand Up @@ -986,15 +1012,15 @@ def create_release_from_source(self, source_release, release_metadata):
# cache these before removing source release id to copy it over
contributors = ReleaseContributor.objects.filter(release_id=source_release.id)
platform_tags = source_release.platform_tags.all()
programming_languages = source_release.programming_languages.all()
release_languages = source_release.release_languages.all()
# set source_release.id to None to create a new release
# see https://docs.djangoproject.com/en/4.2/topics/db/queries/#copying-model-instances
source_release.id = None
source_release._state.adding = True
source_release.__dict__.update(**release_metadata)
source_release.save()
source_release.platform_tags.add(*platform_tags)
source_release.programming_languages.add(*programming_languages)
source_release.release_languages.add(*release_languages)
contributors.copy_to(source_release)
return source_release

Expand Down Expand Up @@ -1143,7 +1169,12 @@ def with_platforms(self):
return self.prefetch_related("tagged_release_platforms__tag")

def with_programming_languages(self):
return self.prefetch_related("tagged_release_languages__tag")
return self.prefetch_related(
Prefetch(
"release_languages",
ReleaseLanguage.objects.prefetch_related("programming_language"),
)
)

def with_codebase(self):
return self.prefetch_related(
Expand Down Expand Up @@ -1285,8 +1316,8 @@ class Status(models.TextChoices):
through=CodebaseReleasePlatformTag, related_name="platform_codebase_releases"
)
platforms = models.ManyToManyField(Platform)
programming_languages = ClusterTaggableManager(
through=ProgrammingLanguage, related_name="pl_codebase_releases"
programming_language_tags = ClusterTaggableManager(
through=ProgrammingLanguageTag, related_name="pl_codebase_releases"
)
codebase = models.ForeignKey(
Codebase, related_name="releases", on_delete=models.PROTECT
Expand Down Expand Up @@ -1341,7 +1372,7 @@ class Status(models.TextChoices):
],
),
index.RelatedFields(
"programming_languages",
"release_languages",
[
index.SearchField("name"),
],
Expand Down Expand Up @@ -1463,7 +1494,7 @@ def validate_metadata(self):
# naive check for metadata being present (i.e., None or false-y values)
if not self.license:
errors.append(ValidationError(_("Please specify a software license.")))
if not self.programming_languages.exists():
if not self.release_languages.exists():
errors.append(
ValidationError(
_(
Expand Down Expand Up @@ -2733,7 +2764,7 @@ def __init__(self, release: CodebaseRelease):
self.description = codebase.description.raw
self.release_notes = release.release_notes.raw if release.release_notes else ""
self.version = release.version_number
self.programming_languages = release.programming_languages.all()
self.release_languages = release.release_languages.all()
self.os = release.os
self.identifier = release.permanent_url
self.url = release.permanent_url
Expand Down
Loading
Loading