11 Commits

Author SHA1 Message Date
ab43e0b5fe minor 2025-11-11 19:59:03 +01:00
91a52c254d minor 2025-11-11 19:57:50 +01:00
f7d88e4235 ..: 2025-11-11 19:26:23 +01:00
b627a3850b ... 2025-11-11 15:51:30 +01:00
1e5b8f5a63 ... 2025-11-11 15:47:03 +01:00
2a3e418f5b added missing migrations 2025-11-11 15:24:09 +01:00
393ec72523 add make migration step 2025-11-11 15:07:10 +01:00
932caa637b Merge branch 'develop' into enhancement/licenses 2025-11-12 03:00:54 +13:00
ac58d58479 Merge branch 'develop' into enhancement/licenses 2025-11-12 02:19:13 +13:00
94abfc90b5 comments 2025-11-10 14:57:31 +13:00
42b1e77fb9 refactor of license django model #119 2025-11-10 14:51:44 +13:00
10 changed files with 152 additions and 24 deletions

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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,
),
]

View File

@ -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)]

View File

@ -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")

View File

@ -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)

View File

@ -52,8 +52,6 @@
</div>
</div>
<input type="hidden" id="license" name="license">
<input type="hidden" id="license-link" name="license-link">
<input type="hidden" id="license-image-link" name="license-image-link">
</form>
</div>
<div class="modal-footer">
@ -128,8 +126,6 @@ function cc() {
$('#ccfig').append(img_tpl);
$('#license').val(ccstr);
$('#license-link').val(link);
$('#license-image-link').val(imageLink);
} else {
$('#ccfig').empty();
$('#set_license_form_submit').prop('disabled', true);

View File

@ -4,7 +4,14 @@ from django.test import TestCase, tag
from django.urls import reverse
from epdb.logic import UserManager
from epdb.models import Package, UserPackagePermission, Permission, GroupPackagePermission, Group
from epdb.models import (
Package,
UserPackagePermission,
Permission,
GroupPackagePermission,
Group,
License,
)
class PackageViewTest(TestCase):
@ -29,6 +36,15 @@ class PackageViewTest(TestCase):
add_to_group=True,
is_active=True,
)
# Create the default license set.
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 setUp(self):
self.client.force_login(self.user1)
@ -188,7 +204,28 @@ class PackageViewTest(TestCase):
self.client.post(package_url, {"license": "no-license"})
self.assertIsNone(p.license)
# TODO test others
# Test other possible licenses
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
for cc_string in cc_strings:
self.client.post(package_url, {"license": cc_string})
# Without this, the instance of p doesn't have the license. However, the one retrieved with get does
p = Package.objects.get(url=package_url)
self.assertEqual(
p.license.link, f"https://creativecommons.org/licenses/{cc_string}/4.0/"
)
# Test again to ensure that Licenses are reused
cc_strings = ["by", "by-nc", "by-nc-nd", "by-nc-sa", "by-nd", "by-sa"]
for cc_string in cc_strings:
self.client.post(package_url, {"license": cc_string})
# Without this, the instance of p doesn't have the license. However, the one retrieved with get does
p = Package.objects.get(url=package_url)
self.assertEqual(
p.license.link, f"https://creativecommons.org/licenses/{cc_string}/4.0/"
)
self.assertEqual(License.objects.count(), len(cc_strings))
def test_delete_package(self):
response = self.client.post(

View File

@ -714,6 +714,7 @@ class PackageImporter:
license_obj, _ = License.objects.get_or_create(
name=license_data["name"],
defaults={
"cc_string": license_data.get("cc_string", ""),
"link": license_data.get("link", ""),
"image_link": license_data.get("image_link", ""),
},