forked from enviPath/enviPy
Compare commits
4 Commits
enhancemen
...
fix/search
| Author | SHA1 | Date | |
|---|---|---|---|
| eb6c5ade29 | |||
| 305fdc41fb | |||
| 9deca8867e | |||
| df6056fb86 |
@ -105,7 +105,7 @@ jobs:
|
|||||||
until pg_isready -h postgres -U postgres; do sleep 2; done
|
until pg_isready -h postgres -U postgres; do sleep 2; done
|
||||||
# until redis-cli -h redis ping; do sleep 2; done
|
# until redis-cli -h redis ping; do sleep 2; done
|
||||||
|
|
||||||
- name: Run Django migrations
|
- name: Run Django Migrations
|
||||||
run: |
|
run: |
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
python manage.py migrate --noinput
|
python manage.py migrate --noinput
|
||||||
|
|||||||
@ -21,6 +21,7 @@ from .models import (
|
|||||||
ExternalDatabase,
|
ExternalDatabase,
|
||||||
ExternalIdentifier,
|
ExternalIdentifier,
|
||||||
JobLog,
|
JobLog,
|
||||||
|
License,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -62,6 +63,10 @@ class EnviFormerAdmin(EPAdmin):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["cc_string", "link", "image_link"]
|
||||||
|
|
||||||
|
|
||||||
class CompoundAdmin(EPAdmin):
|
class CompoundAdmin(EPAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -118,6 +123,7 @@ admin.site.register(JobLog, JobLogAdmin)
|
|||||||
admin.site.register(Package, PackageAdmin)
|
admin.site.register(Package, PackageAdmin)
|
||||||
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
|
||||||
admin.site.register(EnviFormer, EnviFormerAdmin)
|
admin.site.register(EnviFormer, EnviFormerAdmin)
|
||||||
|
admin.site.register(License, LicenseAdmin)
|
||||||
admin.site.register(Compound, CompoundAdmin)
|
admin.site.register(Compound, CompoundAdmin)
|
||||||
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
||||||
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
||||||
|
|||||||
@ -12,10 +12,16 @@ from epdb.models import (
|
|||||||
Permission,
|
Permission,
|
||||||
User,
|
User,
|
||||||
ExternalDatabase,
|
ExternalDatabase,
|
||||||
|
License,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
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):
|
def create_users(self):
|
||||||
# Anonymous User
|
# Anonymous User
|
||||||
if not User.objects.filter(email="anon@envipath.com").exists():
|
if not User.objects.filter(email="anon@envipath.com").exists():
|
||||||
@ -83,6 +89,17 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
return anon, admin, g, user0
|
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):
|
def import_package(self, data, owner):
|
||||||
return PackageManager.import_legacy_package(
|
return PackageManager.import_legacy_package(
|
||||||
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
||||||
@ -157,6 +174,10 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
# Create licenses
|
||||||
|
self.create_licenses()
|
||||||
|
if options.get("only_licenses", False):
|
||||||
|
return
|
||||||
# Create users
|
# Create users
|
||||||
anon, admin, g, user0 = self.create_users()
|
anon, admin, g, user0 = self.create_users()
|
||||||
|
|
||||||
|
|||||||
18
epdb/migrations/0010_license_cc_string.py
Normal file
18
epdb/migrations/0010_license_cc_string.py
Normal 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,
|
||||||
|
),
|
||||||
|
]
|
||||||
59
epdb/migrations/0011_auto_20251111_1413.py
Normal file
59
epdb/migrations/0011_auto_20251111_1413.py
Normal 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)]
|
||||||
@ -655,6 +655,7 @@ class ScenarioMixin(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class License(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")
|
link = models.URLField(blank=False, null=False, verbose_name="link")
|
||||||
image_link = models.URLField(blank=False, null=False, verbose_name="Image link")
|
image_link = models.URLField(blank=False, null=False, verbose_name="Image link")
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
|
||||||
from typing import Any, Callable, List, Optional
|
from typing import Any, Callable, List, Optional
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from celery.utils.functional import LRUCache
|
from celery.utils.functional import LRUCache
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from epdb.logic import SPathway
|
from epdb.logic import SPathway
|
||||||
from epdb.models import EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User, Edge
|
from epdb.models import Edge, EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
|
||||||
@ -29,7 +29,7 @@ def dispatch_eager(user: "User", job: Callable, *args, **kwargs):
|
|||||||
log.task_id = uuid4()
|
log.task_id = uuid4()
|
||||||
log.job_name = job.__name__
|
log.job_name = job.__name__
|
||||||
log.status = "SUCCESS"
|
log.status = "SUCCESS"
|
||||||
log.done_at = datetime.now()
|
log.done_at = timezone.now()
|
||||||
log.task_result = str(x) if x else None
|
log.task_result = str(x) if x else None
|
||||||
log.save()
|
log.save()
|
||||||
|
|
||||||
|
|||||||
@ -955,6 +955,12 @@ def package_model(request, package_uuid, model_uuid):
|
|||||||
]
|
]
|
||||||
dispatch(current_user, evaluate_model, current_model.pk, multigen, eval_package_ids)
|
dispatch(current_user, evaluate_model, current_model.pk, multigen, eval_package_ids)
|
||||||
|
|
||||||
|
return redirect(current_model.url)
|
||||||
|
elif hidden == "retrain":
|
||||||
|
from .tasks import dispatch, retrain
|
||||||
|
|
||||||
|
dispatch(current_user, retrain, current_model.pk)
|
||||||
|
|
||||||
return redirect(current_model.url)
|
return redirect(current_model.url)
|
||||||
else:
|
else:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
@ -1084,9 +1090,7 @@ def package(request, package_uuid):
|
|||||||
write = request.POST.get("write") == "on"
|
write = request.POST.get("write") == "on"
|
||||||
owner = request.POST.get("owner") == "on"
|
owner = request.POST.get("owner") == "on"
|
||||||
|
|
||||||
license = request.POST.get("license")
|
cc_string = request.POST.get("license")
|
||||||
license_link = request.POST.get("license-link")
|
|
||||||
license_image_link = request.POST.get("license-image-link")
|
|
||||||
|
|
||||||
if new_package_name:
|
if new_package_name:
|
||||||
current_package.name = new_package_name
|
current_package.name = new_package_name
|
||||||
@ -1114,24 +1118,15 @@ def package(request, package_uuid):
|
|||||||
|
|
||||||
PackageManager.update_permissions(current_user, current_package, grantee, max_perm)
|
PackageManager.update_permissions(current_user, current_package, grantee, max_perm)
|
||||||
return redirect(current_package.url)
|
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.license = None
|
||||||
current_package.save()
|
current_package.save()
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
else:
|
else: # Get the license and assign it to the package
|
||||||
if current_package.license is not None:
|
current_package.license = License.objects.get(cc_string=cc_string)
|
||||||
current_package.license.delete()
|
|
||||||
|
|
||||||
license = License()
|
|
||||||
license.link = license_link
|
|
||||||
license.image_link = license_image_link
|
|
||||||
license.save()
|
|
||||||
|
|
||||||
current_package.license = license
|
|
||||||
current_package.save()
|
current_package.save()
|
||||||
|
|
||||||
return redirect(current_package.url)
|
return redirect(current_package.url)
|
||||||
|
|||||||
@ -178,6 +178,23 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Open search modal function
|
||||||
|
function openSearchModal() {
|
||||||
|
const searchModal = document.getElementById("search_modal");
|
||||||
|
if (searchModal) {
|
||||||
|
searchModal.showModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click handler for search badge
|
||||||
|
const searchTrigger = document.getElementById("search-trigger");
|
||||||
|
if (searchTrigger) {
|
||||||
|
searchTrigger.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
openSearchModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Global keyboard shortcut for search (Cmd+K on Mac, Ctrl+K on Windows/Linux)
|
// Global keyboard shortcut for search (Cmd+K on Mac, Ctrl+K on Windows/Linux)
|
||||||
document.addEventListener("keydown", function (event) {
|
document.addEventListener("keydown", function (event) {
|
||||||
// Check if user is typing in an input field
|
// Check if user is typing in an input field
|
||||||
@ -198,7 +215,7 @@
|
|||||||
|
|
||||||
if (isCorrectModifier && event.key === "k") {
|
if (isCorrectModifier && event.key === "k") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
search_modal.showModal();
|
openSearchModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
{% if not public_mode %}
|
{% if not public_mode %}
|
||||||
<a href="/search" role="button">
|
<a id="search-trigger" role="button" class="cursor-pointer">
|
||||||
<div
|
<div
|
||||||
class="flex items-center badge badge-dash space-x-1 bg-base-200 text-base-content/50 p-2 m-1"
|
class="flex items-center badge badge-dash space-x-1 bg-base-200 text-base-content/50 p-2 m-1"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,43 +1,55 @@
|
|||||||
<div class="modal fade" tabindex="-1" id="retrain_model_modal" role="dialog" aria-labelledby="retrain_model_modal"
|
<div
|
||||||
aria-hidden="true">
|
class="modal fade"
|
||||||
<div class="modal-dialog modal-lg">
|
tabindex="-1"
|
||||||
<div class="modal-content">
|
id="retrain_model_modal"
|
||||||
<div class="modal-header">
|
role="dialog"
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
aria-labelledby="retrain_model_modal"
|
||||||
<span aria-hidden="true">×</span>
|
aria-hidden="true"
|
||||||
<span class="sr-only">Close</span>
|
>
|
||||||
</button>
|
<div class="modal-dialog modal-lg">
|
||||||
<h4 class="modal-title">Retrain Model</h4>
|
<div class="modal-content">
|
||||||
</div>
|
<div class="modal-header">
|
||||||
<div class="modal-body">
|
<button type="button" class="close" data-dismiss="modal">
|
||||||
<form id="retrain_model_form" accept-charset="UTF-8" action="{{ meta.current_package.url }}/model"
|
<span aria-hidden="true">×</span>
|
||||||
data-remote="true" method="post">
|
<span class="sr-only">Close</span>
|
||||||
<div class="jumbotron">
|
</button>
|
||||||
To reflect changes in the rule or data packages, you can use the "Retrain" button,
|
<h4 class="modal-title">Retrain Model</h4>
|
||||||
to let the model reflect the changes without creating a new model.
|
</div>
|
||||||
While the model is retraining, it will be unavailable for prediction.
|
<div class="modal-body">
|
||||||
</div>
|
<form
|
||||||
{% csrf_token %}
|
id="retrain_model_form"
|
||||||
<input type="hidden" name="action" value="retrain">
|
accept-charset="UTF-8"
|
||||||
</form>
|
action="{{ meta.current_object.url }}"
|
||||||
</div>
|
data-remote="true"
|
||||||
<div class="modal-footer">
|
method="post"
|
||||||
<a id="retrain_model_form_submit" class="btn btn-primary" href="#">Retrain</a>
|
>
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
<div class="jumbotron">
|
||||||
</div>
|
To reflect changes in the rule or data packages, you can use the
|
||||||
</div>
|
"Retrain" button, to let the model reflect the changes without
|
||||||
|
creating a new model. While the model is retraining, it will be
|
||||||
|
unavailable for prediction.
|
||||||
|
</div>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="hidden" value="retrain" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a id="retrain_model_form_submit" class="btn btn-primary" href="#"
|
||||||
|
>Retrain</a
|
||||||
|
>
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
$(function () {
|
||||||
$(function () {
|
$("#retrain_model_form_submit").on("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
$('#retrain_model_form_submit').on('click', function (e) {
|
$("#retrain_model_form").submit();
|
||||||
e.preventDefault();
|
|
||||||
$('#retrain_model_form').submit();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -52,8 +52,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" id="license" name="license">
|
<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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -128,8 +126,6 @@ function cc() {
|
|||||||
|
|
||||||
$('#ccfig').append(img_tpl);
|
$('#ccfig').append(img_tpl);
|
||||||
$('#license').val(ccstr);
|
$('#license').val(ccstr);
|
||||||
$('#license-link').val(link);
|
|
||||||
$('#license-image-link').val(imageLink);
|
|
||||||
} else {
|
} else {
|
||||||
$('#ccfig').empty();
|
$('#ccfig').empty();
|
||||||
$('#set_license_form_submit').prop('disabled', true);
|
$('#set_license_form_submit').prop('disabled', true);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,197 +0,0 @@
|
|||||||
{% extends "framework.html" %}
|
|
||||||
{% load static %}
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<div id=searchContent>
|
|
||||||
<div id="packSelector">
|
|
||||||
<label>Select Packages</label><br>
|
|
||||||
<select id="selPackages" name="selPackages" data-actions-box='true' class="selPackages" multiple
|
|
||||||
data-width='100%'>
|
|
||||||
{% if unreviewed_objects %}
|
|
||||||
<option disabled>Reviewed Packages</option>
|
|
||||||
{% endif %}
|
|
||||||
{% for obj in reviewed_objects %}
|
|
||||||
<option value="{{ obj.url }}" selected>{{ obj.name|safe }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
{% if unreviewed_objects %}
|
|
||||||
<option disabled>Unreviewed Packages</option>
|
|
||||||
{% endif %}
|
|
||||||
{% for obj in unreviewed_objects %}
|
|
||||||
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<p></p>
|
|
||||||
<div>
|
|
||||||
<label>Search Term</label><br>
|
|
||||||
<div class="input-group" id="index-form-bar">
|
|
||||||
<input type="text" class="form-control" id='searchbar' placeholder="Benfuracarb">
|
|
||||||
<div class="input-group-btn">
|
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
|
|
||||||
id="mode-button"
|
|
||||||
aria-haspopup="true" aria-expanded="false">Text <span class="caret"></span></button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li class="dropdown-header">Text</li>
|
|
||||||
<li><a id="dropdown-predict-text-text">Text</a></li>
|
|
||||||
<li class="dropdown-header">SMILES</li>
|
|
||||||
<li><a id="dropdown-search-smiles-default" data-toggle="tooltip">Default</a></li>
|
|
||||||
<li><a id="dropdown-search-smiles-canonical">Canonical</a></li>
|
|
||||||
<li><a id="dropdown-search-smiles-exact">Exact</a></li>
|
|
||||||
<li class="dropdown-header">InChI</li>
|
|
||||||
<li><a id="dropdown-search-inchi-inchikey">InChIKey</a></li>
|
|
||||||
</ul>
|
|
||||||
<button class="btn" style="background-color:#222222;color:#9d9d9d" type="button" id="search-button">
|
|
||||||
Go!
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p></p>
|
|
||||||
<div id="results"></div>
|
|
||||||
<p></p>
|
|
||||||
<div id="loading"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function modeDropdownClicked() {
|
|
||||||
var suffix = ' <span class="caret"></span>';
|
|
||||||
var dropdownVal = $(this).text();
|
|
||||||
$('#mode-button').html(dropdownVal + suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSearchResponse(id, data) {
|
|
||||||
content = `
|
|
||||||
<div class='panel-group' id='search-accordion'>
|
|
||||||
<div class='panel panel-default'>
|
|
||||||
<div class='panel-heading' id='headingPanel' style='font-size:2rem;height: 46px'>
|
|
||||||
Results
|
|
||||||
</div>
|
|
||||||
<div id='descDiv'></div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
function makeContent(objs) {
|
|
||||||
links = "";
|
|
||||||
for (idx in objs) {
|
|
||||||
obj = objs[idx];
|
|
||||||
links += `<a class='list-group-item' href='${obj.url}'>${obj.name}</a>`
|
|
||||||
}
|
|
||||||
return links;
|
|
||||||
}
|
|
||||||
|
|
||||||
allEmpty = true;
|
|
||||||
for (key in data) {
|
|
||||||
if (key === 'searchterm') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data[key].length < 1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
allEmpty = false;
|
|
||||||
content += `
|
|
||||||
<div class='panel panel-default panel-heading list-group-item' style='background-color:silver'>
|
|
||||||
<h4 class='panel-title'>
|
|
||||||
<a id='${key}_link' data-toggle='collapse' data-parent='#search-accordion' href='#${key}_panel'>
|
|
||||||
${key}
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id='${key}_panel' class='panel-collapse collapse in'>
|
|
||||||
<div class='panel-body list-group-item'>
|
|
||||||
${makeContent(data[key])}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
if (allEmpty) {
|
|
||||||
$('#' + id).append('<div class="alert alert-danger" role="alert"><p>' + "No results..." + '</p></div>');
|
|
||||||
} else {
|
|
||||||
$('#' + id).append(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function search(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
query = $("#searchbar").val()
|
|
||||||
|
|
||||||
if (!query) {
|
|
||||||
// Nothing to search...
|
|
||||||
console.log("Search phrase empty, won't do search")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var selPacks = [];
|
|
||||||
$("#selPackages :selected").each(function () {
|
|
||||||
var id = this.value;
|
|
||||||
selPacks.push(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (selPacks.length < 1) {
|
|
||||||
console.log("No package selected, won't do search")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mode = $('#mode-button').text().trim().toLowerCase();
|
|
||||||
|
|
||||||
var par = {};
|
|
||||||
par['packages'] = selPacks;
|
|
||||||
par['search'] = query;
|
|
||||||
par['mode'] = mode;
|
|
||||||
|
|
||||||
console.log(par);
|
|
||||||
|
|
||||||
var queryString = $.param(par, true);
|
|
||||||
|
|
||||||
makeLoadingGif("#loading", "{% static '/images/wait.gif' %}");
|
|
||||||
|
|
||||||
$("#results").empty();
|
|
||||||
|
|
||||||
$.getJSON("{{ SERVER_BASE }}/search?" + queryString, function (result) {
|
|
||||||
handleSearchResponse("results", result);
|
|
||||||
$("#loading").empty();
|
|
||||||
}).fail(function (d) {
|
|
||||||
$("#loading").empty();
|
|
||||||
console.log(d.responseText);
|
|
||||||
handleError(JSON.parse(d.responseText));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
|
|
||||||
tooltips = {
|
|
||||||
'dropdown-predict-text-text': 'The inserted pattern will be searched on all enviPath object names and descriptions',
|
|
||||||
'dropdown-search-smiles-default': 'Search by SMILES, stereochemistry and charge are ignored',
|
|
||||||
'dropdown-search-smiles-canonical': 'Search by SMILES, stereochemistry is ignored but charge is preserved',
|
|
||||||
'dropdown-search-smiles-exact': 'Search by SMILES, exact match for stereochemistry and charge',
|
|
||||||
'dropdown-search-inchi-inchikey': 'Search by InChIKey',
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(tooltips).forEach(key => {
|
|
||||||
$('#' + key).tooltip({
|
|
||||||
placement: "top",
|
|
||||||
title: tooltips[key]
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#' + key).on('click', modeDropdownClicked);
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#selPackages").selectpicker();
|
|
||||||
$("#search-button").on("click", search);
|
|
||||||
|
|
||||||
$("#searchbar").on("keydown", function (e) {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
search(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
{% if search_result %}
|
|
||||||
$('#searchbar').val('{{ search_result.searchterm }}')
|
|
||||||
handleSearchResponse("results", {{ search_result|safe }});
|
|
||||||
{% endif %}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock content %}
|
|
||||||
@ -4,7 +4,14 @@ from django.test import TestCase, tag
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from epdb.logic import UserManager
|
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):
|
class PackageViewTest(TestCase):
|
||||||
@ -29,6 +36,15 @@ class PackageViewTest(TestCase):
|
|||||||
add_to_group=True,
|
add_to_group=True,
|
||||||
is_active=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):
|
def setUp(self):
|
||||||
self.client.force_login(self.user1)
|
self.client.force_login(self.user1)
|
||||||
@ -188,7 +204,28 @@ class PackageViewTest(TestCase):
|
|||||||
self.client.post(package_url, {"license": "no-license"})
|
self.client.post(package_url, {"license": "no-license"})
|
||||||
|
|
||||||
self.assertIsNone(p.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):
|
def test_delete_package(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
|
|||||||
@ -714,6 +714,7 @@ class PackageImporter:
|
|||||||
license_obj, _ = License.objects.get_or_create(
|
license_obj, _ = License.objects.get_or_create(
|
||||||
name=license_data["name"],
|
name=license_data["name"],
|
||||||
defaults={
|
defaults={
|
||||||
|
"cc_string": license_data.get("cc_string", ""),
|
||||||
"link": license_data.get("link", ""),
|
"link": license_data.get("link", ""),
|
||||||
"image_link": license_data.get("image_link", ""),
|
"image_link": license_data.get("image_link", ""),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user