diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 863c0643..2ab32386 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -105,7 +105,7 @@ jobs: until pg_isready -h postgres -U postgres; do sleep 2; done # until redis-cli -h redis ping; do sleep 2; done - - name: Run Django migrations + - name: Run Django Migrations run: | source .venv/bin/activate python manage.py migrate --noinput diff --git a/epdb/admin.py b/epdb/admin.py index 88f851af..84c39199 100644 --- a/epdb/admin.py +++ b/epdb/admin.py @@ -21,6 +21,7 @@ from .models import ( ExternalDatabase, ExternalIdentifier, JobLog, + License, ) @@ -62,6 +63,10 @@ class EnviFormerAdmin(EPAdmin): pass +class LicenseAdmin(admin.ModelAdmin): + list_display = ["cc_string", "link", "image_link"] + + class CompoundAdmin(EPAdmin): pass @@ -118,6 +123,7 @@ admin.site.register(JobLog, JobLogAdmin) admin.site.register(Package, PackageAdmin) admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin) admin.site.register(EnviFormer, EnviFormerAdmin) +admin.site.register(License, LicenseAdmin) admin.site.register(Compound, CompoundAdmin) admin.site.register(CompoundStructure, CompoundStructureAdmin) admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin) diff --git a/epdb/management/commands/bootstrap.py b/epdb/management/commands/bootstrap.py index c01ec5eb..eb74385a 100644 --- a/epdb/management/commands/bootstrap.py +++ b/epdb/management/commands/bootstrap.py @@ -12,10 +12,16 @@ from epdb.models import ( Permission, User, ExternalDatabase, + License, ) class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument( + "-ol", "--only-licenses", action="store_true", help="Only create licenses." + ) + def create_users(self): # Anonymous User if not User.objects.filter(email="anon@envipath.com").exists(): @@ -83,6 +89,17 @@ class Command(BaseCommand): return anon, admin, g, user0 + def create_licenses(self): + """Create the six default licenses supported by enviPath""" + cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"] + for cc_string in cc_strings: + if not License.objects.filter(cc_string=cc_string).exists(): + new_license = License() + new_license.cc_string = cc_string + new_license.link = f"https://creativecommons.org/licenses/{cc_string}/4.0/" + new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png" + new_license.save() + def import_package(self, data, owner): return PackageManager.import_legacy_package( data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True @@ -157,6 +174,10 @@ class Command(BaseCommand): @transaction.atomic def handle(self, *args, **options): + # Create licenses + self.create_licenses() + if options.get("only_licenses", False): + return # Create users anon, admin, g, user0 = self.create_users() diff --git a/epdb/migrations/0010_license_cc_string.py b/epdb/migrations/0010_license_cc_string.py new file mode 100644 index 00000000..5594c756 --- /dev/null +++ b/epdb/migrations/0010_license_cc_string.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2025-11-11 14:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("epdb", "0009_joblog"), + ] + + operations = [ + migrations.AddField( + model_name="license", + name="cc_string", + field=models.TextField(default="by-nc-sa", verbose_name="CC string"), + preserve_default=False, + ), + ] diff --git a/epdb/migrations/0011_auto_20251111_1413.py b/epdb/migrations/0011_auto_20251111_1413.py new file mode 100644 index 00000000..d0a3463a --- /dev/null +++ b/epdb/migrations/0011_auto_20251111_1413.py @@ -0,0 +1,59 @@ +# Generated by Django 5.2.7 on 2025-11-11 14:13 + +import re + +from django.contrib.postgres.aggregates import ArrayAgg +from django.db import migrations +from django.db.models import Min + + +def set_cc(apps, schema_editor): + License = apps.get_model("epdb", "License") + + # For all existing licenses extract cc_string from link + for license in License.objects.all(): + pattern = r"/licenses/([^/]+)/4\.0" + match = re.search(pattern, license.link) + if match: + license.cc_string = match.group(1) + license.save() + else: + raise ValueError(f"Could not find license for {license.link}") + + # Ensure we have all licenses + cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"] + for cc_string in cc_strings: + if not License.objects.filter(cc_string=cc_string).exists(): + new_license = License() + new_license.cc_string = cc_string + new_license.link = f"https://creativecommons.org/licenses/{cc_string}/4.0/" + new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png" + new_license.save() + + # As we might have existing Licenses representing the same License, + # get min pk and all pks as a list + license_lookup_qs = License.objects.values("cc_string").annotate( + lowest_pk=Min("id"), all_pks=ArrayAgg("id", order_by=("id",)) + ) + + license_lookup = { + row["cc_string"]: (row["lowest_pk"], row["all_pks"]) for row in license_lookup_qs + } + + Packages = apps.get_model("epdb", "Package") + + for k, v in license_lookup.items(): + # Set min pk to all packages pointing to any of the duplicates + Packages.objects.filter(pk__in=v[1]).update(license_id=v[0]) + # remove the min pk from "other" pks as we use them for deletion + v[1].remove(v[0]) + # Delete redundant License objects + License.objects.filter(pk__in=v[1]).delete() + + +class Migration(migrations.Migration): + dependencies = [ + ("epdb", "0010_license_cc_string"), + ] + + operations = [migrations.RunPython(set_cc)] diff --git a/epdb/models.py b/epdb/models.py index 4b6d7500..eb2ef4cc 100644 --- a/epdb/models.py +++ b/epdb/models.py @@ -655,6 +655,7 @@ class ScenarioMixin(models.Model): class License(models.Model): + cc_string = models.TextField(blank=False, null=False, verbose_name="CC string") link = models.URLField(blank=False, null=False, verbose_name="link") image_link = models.URLField(blank=False, null=False, verbose_name="Image link") diff --git a/epdb/views.py b/epdb/views.py index 74406671..9af2ebe5 100644 --- a/epdb/views.py +++ b/epdb/views.py @@ -1084,9 +1084,7 @@ def package(request, package_uuid): write = request.POST.get("write") == "on" owner = request.POST.get("owner") == "on" - license = request.POST.get("license") - license_link = request.POST.get("license-link") - license_image_link = request.POST.get("license-image-link") + cc_string = request.POST.get("license") if new_package_name: current_package.name = new_package_name @@ -1114,24 +1112,15 @@ def package(request, package_uuid): PackageManager.update_permissions(current_user, current_package, grantee, max_perm) return redirect(current_package.url) - elif license is not None: - if license == "no-license": - if current_package.license is not None: - current_package.license.delete() + elif cc_string is not None: + cc_string = cc_string.strip() + if cc_string == "no-license": # Reset the package's license current_package.license = None current_package.save() return redirect(current_package.url) - else: - if current_package.license is not None: - current_package.license.delete() - - license = License() - license.link = license_link - license.image_link = license_image_link - license.save() - - current_package.license = license + else: # Get the license and assign it to the package + current_package.license = License.objects.get(cc_string=cc_string) current_package.save() return redirect(current_package.url) diff --git a/templates/modals/objects/set_license_modal.html b/templates/modals/objects/set_license_modal.html index c1771f57..5daa2c43 100644 --- a/templates/modals/objects/set_license_modal.html +++ b/templates/modals/objects/set_license_modal.html @@ -52,8 +52,6 @@ - -