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 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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
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):
|
||||
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")
|
||||
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import csv
|
||||
import io
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, List, Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from celery import shared_task
|
||||
from celery.utils.functional import LRUCache
|
||||
from django.utils import timezone
|
||||
|
||||
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__)
|
||||
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.job_name = job.__name__
|
||||
log.status = "SUCCESS"
|
||||
log.done_at = datetime.now()
|
||||
log.done_at = timezone.now()
|
||||
log.task_result = str(x) if x else None
|
||||
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)
|
||||
|
||||
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)
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
@ -1084,9 +1090,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 +1118,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)
|
||||
|
||||
@ -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)
|
||||
document.addEventListener("keydown", function (event) {
|
||||
// Check if user is typing in an input field
|
||||
@ -198,7 +215,7 @@
|
||||
|
||||
if (isCorrectModifier && event.key === "k") {
|
||||
event.preventDefault();
|
||||
search_modal.showModal();
|
||||
openSearchModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
|
||||
<div class="navbar-end">
|
||||
{% if not public_mode %}
|
||||
<a href="/search" role="button">
|
||||
<a id="search-trigger" role="button" class="cursor-pointer">
|
||||
<div
|
||||
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"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span class="sr-only">Close</span>
|
||||
</button>
|
||||
<h4 class="modal-title">Retrain Model</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="retrain_model_form" accept-charset="UTF-8" action="{{ meta.current_package.url }}/model"
|
||||
data-remote="true" method="post">
|
||||
<div class="jumbotron">
|
||||
To reflect changes in the rule or data packages, you can use the "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="action" 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
|
||||
class="modal fade"
|
||||
tabindex="-1"
|
||||
id="retrain_model_modal"
|
||||
role="dialog"
|
||||
aria-labelledby="retrain_model_modal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
<span class="sr-only">Close</span>
|
||||
</button>
|
||||
<h4 class="modal-title">Retrain Model</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form
|
||||
id="retrain_model_form"
|
||||
accept-charset="UTF-8"
|
||||
action="{{ meta.current_object.url }}"
|
||||
data-remote="true"
|
||||
method="post"
|
||||
>
|
||||
<div class="jumbotron">
|
||||
To reflect changes in the rule or data packages, you can use the
|
||||
"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>
|
||||
|
||||
<script>
|
||||
|
||||
$(function () {
|
||||
|
||||
$('#retrain_model_form_submit').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$('#retrain_model_form').submit();
|
||||
});
|
||||
|
||||
$(function () {
|
||||
$("#retrain_model_form_submit").on("click", function (e) {
|
||||
e.preventDefault();
|
||||
$("#retrain_model_form").submit();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -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);
|
||||
|
||||
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 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(
|
||||
|
||||
@ -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", ""),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user