forked from enviPath/enviPy
Compare commits
14 Commits
99c796813a
...
3114e2df4c
| Author | SHA1 | Date | |
|---|---|---|---|
| 3114e2df4c | |||
| a23cb5bd5f | |||
| d52d3b0a65 | |||
| 2b1d0099cd | |||
| 31b4784ecd | |||
| 19840edc1e | |||
| be5ee1d1d7 | |||
| 20fd949dfd | |||
| c9b643fe6e | |||
| 1a9f1cf9af | |||
| c7c7e17e43 | |||
| 674e10c7fa | |||
| 8079b80d57 | |||
| 76e63fda2c |
26
epapi/v1/endpoints/joblogs.py
Normal file
26
epapi/v1/endpoints/joblogs.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from django.conf import settings as s
|
||||||
|
from ninja import Router
|
||||||
|
from ninja_extra.pagination import paginate
|
||||||
|
|
||||||
|
from epdb.models import JobLog
|
||||||
|
from ..pagination import EnhancedPageNumberPagination
|
||||||
|
from ..schemas import JobLogOutSchema
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/joblog/", response=EnhancedPageNumberPagination.Output[JobLogOutSchema])
|
||||||
|
@paginate(
|
||||||
|
EnhancedPageNumberPagination,
|
||||||
|
page_size=s.API_PAGINATION_DEFAULT_PAGE_SIZE,
|
||||||
|
)
|
||||||
|
def list_all_joblogs(request):
|
||||||
|
"""
|
||||||
|
List all JobLogs from reviewed packages.
|
||||||
|
"""
|
||||||
|
current_user = request.user
|
||||||
|
|
||||||
|
if current_user.is_superuser:
|
||||||
|
return JobLog.objects.all().order_by("-created")
|
||||||
|
else:
|
||||||
|
return JobLog.objects.filter(user=current_user).order_by("-created")
|
||||||
@ -15,6 +15,7 @@ from .endpoints import (
|
|||||||
additional_information,
|
additional_information,
|
||||||
settings,
|
settings,
|
||||||
groups,
|
groups,
|
||||||
|
joblogs,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Main router with authentication
|
# Main router with authentication
|
||||||
@ -37,6 +38,7 @@ router.add_router("", structure.router)
|
|||||||
router.add_router("", additional_information.router)
|
router.add_router("", additional_information.router)
|
||||||
router.add_router("", settings.router)
|
router.add_router("", settings.router)
|
||||||
router.add_router("", groups.router)
|
router.add_router("", groups.router)
|
||||||
|
router.add_router("", joblogs.router)
|
||||||
|
|
||||||
if s.IUCLID_EXPORT_ENABLED:
|
if s.IUCLID_EXPORT_ENABLED:
|
||||||
from epiuclid.api import router as iuclid_router
|
from epiuclid.api import router as iuclid_router
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
from ninja import FilterSchema, FilterLookup, Schema
|
from datetime import datetime
|
||||||
from typing import Annotated, Optional, List, Dict, Any
|
from typing import Annotated, Optional, List, Dict, Any
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
from ninja import Field, FilterSchema, FilterLookup, Schema
|
||||||
|
|
||||||
|
|
||||||
# Filter schema for query parameters
|
# Filter schema for query parameters
|
||||||
class ReviewStatusFilter(FilterSchema):
|
class ReviewStatusFilter(FilterSchema):
|
||||||
@ -133,3 +136,23 @@ class GroupOutSchema(Schema):
|
|||||||
url: str = ""
|
url: str = ""
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleUserOutSchema(Schema):
|
||||||
|
uuid: UUID
|
||||||
|
url: str
|
||||||
|
name: str = Field(alias="username")
|
||||||
|
|
||||||
|
|
||||||
|
class JobLogOutSchema(Schema):
|
||||||
|
user: SimpleUserOutSchema
|
||||||
|
id: UUID = Field(alias="task_id")
|
||||||
|
url: str
|
||||||
|
name: str = Field(alias="job_name")
|
||||||
|
created: datetime = Field(alias="created")
|
||||||
|
status: str = Field(alias="status")
|
||||||
|
done: Optional[datetime] = Field(None, alias="done_at")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_url(obj):
|
||||||
|
return reverse("job detail", kwargs={"job_uuid": obj.task_id})
|
||||||
|
|||||||
@ -439,23 +439,50 @@ class PackageSchema(Schema):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_readers(obj: Package):
|
def resolve_readers(obj: Package):
|
||||||
users = User.objects.filter(
|
readers = []
|
||||||
id__in=UserPackagePermission.objects.filter(
|
|
||||||
package=obj, permission=UserPackagePermission.READ[0]
|
|
||||||
).values_list("user", flat=True)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
return [{u.id: u.get_name()} for u in users]
|
user_ids = UserPackagePermission.objects.filter(package=obj).values_list("user", flat=True)
|
||||||
|
|
||||||
|
users = User.objects.filter(id__in=user_ids).distinct()
|
||||||
|
|
||||||
|
for u in users:
|
||||||
|
readers.append({"id": str(u.url), "identifier": "user", "name": u.get_name()})
|
||||||
|
|
||||||
|
group_ids = GroupPackagePermission.objects.filter(package=obj).values_list(
|
||||||
|
"group", flat=True
|
||||||
|
)
|
||||||
|
|
||||||
|
groups = Group.objects.filter(id__in=group_ids).distinct()
|
||||||
|
|
||||||
|
for g in groups:
|
||||||
|
readers.append({"id": str(g.url), "identifier": "group", "name": g.get_name()})
|
||||||
|
|
||||||
|
return readers
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_writers(obj: Package):
|
def resolve_writers(obj: Package):
|
||||||
users = User.objects.filter(
|
writers = []
|
||||||
id__in=UserPackagePermission.objects.filter(
|
|
||||||
package=obj, permission=UserPackagePermission.WRITE[0]
|
|
||||||
).values_list("user", flat=True)
|
|
||||||
).distinct()
|
|
||||||
|
|
||||||
return [{u.id: u.get_name()} for u in users]
|
user_ids = UserPackagePermission.objects.filter(
|
||||||
|
package=obj,
|
||||||
|
permission__in=[UserPackagePermission.WRITE[0], UserPackagePermission.ALL[0]],
|
||||||
|
).values_list("user", flat=True)
|
||||||
|
|
||||||
|
users = User.objects.filter(id__in=user_ids).distinct()
|
||||||
|
|
||||||
|
for u in users:
|
||||||
|
writers.append({"id": str(u.url), "identifier": "user", "name": u.get_name()})
|
||||||
|
|
||||||
|
group_ids = GroupPackagePermission.objects.filter(
|
||||||
|
package=obj, permission=[UserPackagePermission.WRITE[0], UserPackagePermission.ALL[0]]
|
||||||
|
).values_list("group", flat=True)
|
||||||
|
|
||||||
|
groups = Group.objects.filter(id__in=group_ids).distinct()
|
||||||
|
|
||||||
|
for g in groups:
|
||||||
|
writers.append({"id": str(g.url), "identifier": "group", "name": g.get_name()})
|
||||||
|
|
||||||
|
return writers
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_review_comment(obj):
|
def resolve_review_comment(obj):
|
||||||
@ -872,6 +899,27 @@ def delete_compound(request, package_uuid, compound_uuid):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateCompoundStructure(Schema):
|
||||||
|
smiles: str
|
||||||
|
name: str | None = None
|
||||||
|
description: str | None = None
|
||||||
|
inchi: str | None = None
|
||||||
|
molfile: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure")
|
||||||
|
def create_package_compound_structure(
|
||||||
|
request, package_uuid, compound_uuid, structure: Form[CreateCompoundStructure]
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
p = get_package_for_write(request.user, package_uuid)
|
||||||
|
c = Compound.objects.get(package=p, uuid=compound_uuid)
|
||||||
|
cs = CompoundStructure.create(c, structure.smiles, structure.name, structure.description)
|
||||||
|
return redirect(cs.url)
|
||||||
|
except ValueError as e:
|
||||||
|
return 400, {"message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure/{uuid:structure_uuid}"
|
"/package/{uuid:package_uuid}/compound/{uuid:compound_uuid}/structure/{uuid:structure_uuid}"
|
||||||
)
|
)
|
||||||
@ -1398,6 +1446,7 @@ class ScenarioSchema(Schema):
|
|||||||
aliases: List[str] = Field([], alias="aliases")
|
aliases: List[str] = Field([], alias="aliases")
|
||||||
collection: Dict["str", List[Dict[str, Any]]] = Field([], alias="collection")
|
collection: Dict["str", List[Dict[str, Any]]] = Field([], alias="collection")
|
||||||
collectionID: Optional[str] = None
|
collectionID: Optional[str] = None
|
||||||
|
date: str = Field(None, alias="scenario_date")
|
||||||
description: str = Field(None, alias="description")
|
description: str = Field(None, alias="description")
|
||||||
id: str = Field(None, alias="url")
|
id: str = Field(None, alias="url")
|
||||||
identifier: str = "scenario"
|
identifier: str = "scenario"
|
||||||
@ -1541,6 +1590,7 @@ def create_package_additional_information(request, package_uuid):
|
|||||||
scen = request.POST.get("scenario")
|
scen = request.POST.get("scenario")
|
||||||
scenario = Scenario.objects.get(package=p, url=scen)
|
scenario = Scenario.objects.get(package=p, url=scen)
|
||||||
|
|
||||||
|
if request.POST.get("adInfoTypes[]"):
|
||||||
url_parser = EPDBURLParser(request.POST.get("attach_obj"))
|
url_parser = EPDBURLParser(request.POST.get("attach_obj"))
|
||||||
attach_obj = url_parser.get_object()
|
attach_obj = url_parser.get_object()
|
||||||
|
|
||||||
@ -1564,6 +1614,33 @@ def create_package_additional_information(request, package_uuid):
|
|||||||
content_object=attach_obj,
|
content_object=attach_obj,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
elif request.POST.get("ais"):
|
||||||
|
import json
|
||||||
|
|
||||||
|
parsed_ais = json.loads(request.POST.get("ais"))
|
||||||
|
|
||||||
|
for ai_type, ais in parsed_ais.items():
|
||||||
|
for ai in ais:
|
||||||
|
attach_obj = None
|
||||||
|
if ai.get("related"):
|
||||||
|
url_parser = EPDBURLParser(ai.get("related").get("url"))
|
||||||
|
attach_obj = url_parser.get_object()
|
||||||
|
|
||||||
|
if not hasattr(attach_obj, "additional_information"):
|
||||||
|
raise ValueError("Can't attach additional information to this object!")
|
||||||
|
|
||||||
|
if not attach_obj.url.startswith(p.url):
|
||||||
|
raise ValueError(
|
||||||
|
"Additional Information can only be set to objects stored in the same package!"
|
||||||
|
)
|
||||||
|
|
||||||
|
AdditionalInformation.create(
|
||||||
|
p,
|
||||||
|
AdditionalInformation.from_dict(ai_type, ai),
|
||||||
|
scenario=scenario,
|
||||||
|
content_object=attach_obj,
|
||||||
|
)
|
||||||
|
|
||||||
# TODO implement additional information endpoint ?
|
# TODO implement additional information endpoint ?
|
||||||
return redirect(f"{scenario.url}")
|
return redirect(f"{scenario.url}")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -1767,6 +1844,29 @@ def create_package_pathway(
|
|||||||
return 403, {"message": str(e)}
|
return 403, {"message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}")
|
||||||
|
def update_pathway(request, package_uuid, pathway_uuid):
|
||||||
|
try:
|
||||||
|
p = get_package_for_write(request.user, package_uuid)
|
||||||
|
|
||||||
|
if request.POST.get("scenario"):
|
||||||
|
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||||
|
scen = Scenario.objects.get(package=p, url=request.POST.get("scenario"))
|
||||||
|
|
||||||
|
pw.scenarios.add(scen)
|
||||||
|
pw.save()
|
||||||
|
|
||||||
|
return redirect(f"{pw.url}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
return 400, {"message": "No scenario specified!"}
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
return 403, {
|
||||||
|
"message": f"Deleting Pathway with id {pathway_uuid} failed due to insufficient rights!"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}")
|
@router.delete("/package/{uuid:package_uuid}/pathway/{uuid:pathway_uuid}")
|
||||||
def delete_pathway(request, package_uuid, pathway_uuid):
|
def delete_pathway(request, package_uuid, pathway_uuid):
|
||||||
try:
|
try:
|
||||||
@ -2078,6 +2178,10 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
|
|||||||
for pr in e.products.split(","):
|
for pr in e.products.split(","):
|
||||||
products.append(Node.objects.get(pathway=pw, url=pr.strip()))
|
products.append(Node.objects.get(pathway=pw, url=pr.strip()))
|
||||||
|
|
||||||
|
multi_step = None
|
||||||
|
if e.multistep and e.multistep.strip() == "true":
|
||||||
|
multi_step = True
|
||||||
|
|
||||||
new_e = Edge.create(
|
new_e = Edge.create(
|
||||||
pathway=pw,
|
pathway=pw,
|
||||||
start_nodes=educts,
|
start_nodes=educts,
|
||||||
@ -2085,6 +2189,7 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
|
|||||||
rule=None,
|
rule=None,
|
||||||
name=None,
|
name=None,
|
||||||
description=e.edgeReason,
|
description=e.edgeReason,
|
||||||
|
multi_step=multi_step,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update depths as sideeffect of above operation
|
# Update depths as sideeffect of above operation
|
||||||
|
|||||||
@ -1703,7 +1703,7 @@ class Reaction(
|
|||||||
if name is not None and name.strip() != "":
|
if name is not None and name.strip() != "":
|
||||||
r.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
r.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
if description is not None and name.strip() != "":
|
if description is not None and description.strip() != "":
|
||||||
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip()
|
||||||
|
|
||||||
r.multi_step = multi_step
|
r.multi_step = multi_step
|
||||||
@ -2200,6 +2200,7 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
|
|||||||
|
|
||||||
depth_map = {}
|
depth_map = {}
|
||||||
depth_map[0] = list()
|
depth_map[0] = list()
|
||||||
|
processed = set()
|
||||||
|
|
||||||
for n in self.nodes:
|
for n in self.nodes:
|
||||||
num_parents = in_count[str(n.uuid)]
|
num_parents = in_count[str(n.uuid)]
|
||||||
@ -2222,9 +2223,11 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
|
|||||||
|
|
||||||
unique_next_level = set()
|
unique_next_level = set()
|
||||||
for n in level_nodes:
|
for n in level_nodes:
|
||||||
|
processed.add(n)
|
||||||
for e in self.edges:
|
for e in self.edges:
|
||||||
if n in e.start_nodes.all():
|
if n in e.start_nodes.all():
|
||||||
for p in e.end_nodes.all():
|
for p in e.end_nodes.all():
|
||||||
|
if p not in processed:
|
||||||
unique_next_level.add(p)
|
unique_next_level.add(p)
|
||||||
|
|
||||||
if len(unique_next_level) > 0:
|
if len(unique_next_level) > 0:
|
||||||
@ -2463,6 +2466,8 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
|||||||
rule: Optional[Rule] = None,
|
rule: Optional[Rule] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
):
|
):
|
||||||
e = Edge()
|
e = Edge()
|
||||||
e.pathway = pathway
|
e.pathway = pathway
|
||||||
@ -2492,7 +2497,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
|||||||
educts=[n.default_node_label for n in e.start_nodes.all()],
|
educts=[n.default_node_label for n in e.start_nodes.all()],
|
||||||
products=[n.default_node_label for n in e.end_nodes.all()],
|
products=[n.default_node_label for n in e.end_nodes.all()],
|
||||||
rules=rule,
|
rules=rule,
|
||||||
multi_step=False,
|
multi_step=kwargs.get("multi_step", False),
|
||||||
)
|
)
|
||||||
|
|
||||||
e.edge_label = r
|
e.edge_label = r
|
||||||
@ -4500,18 +4505,22 @@ class AdditionalInformation(models.Model):
|
|||||||
|
|
||||||
return f"{self.scenario.url}/additional-information/{self.uuid}"
|
return f"{self.scenario.url}/additional-information/{self.uuid}"
|
||||||
|
|
||||||
def get(self) -> "EnviPyModel":
|
@staticmethod
|
||||||
|
def from_dict(ai_type: str, ai_data: Dict[str, Any]):
|
||||||
from envipy_additional_information import registry
|
from envipy_additional_information import registry
|
||||||
|
|
||||||
MAPPING = {c.__name__: c for c in registry.list_models().values()}
|
MAPPING = {c.__name__: c for c in registry.list_models().values()}
|
||||||
try:
|
try:
|
||||||
inst = MAPPING[self.type](**self.data)
|
inst = MAPPING[ai_type](**ai_data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading {self.type}: {e}")
|
print(f"Error loading {ai_type}: {e}")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
inst.__dict__["uuid"] = str(self.uuid)
|
return inst
|
||||||
|
|
||||||
|
def get(self) -> "EnviPyModel":
|
||||||
|
inst = AdditionalInformation.from_dict(self.type, self.data)
|
||||||
|
inst.__dict__["uuid"] = str(self.uuid)
|
||||||
return inst
|
return inst
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|||||||
@ -3113,12 +3113,21 @@ def jobs(request):
|
|||||||
{"Home": s.SERVER_URL},
|
{"Home": s.SERVER_URL},
|
||||||
{"Jobs": s.SERVER_URL + "/jobs"},
|
{"Jobs": s.SERVER_URL + "/jobs"},
|
||||||
]
|
]
|
||||||
if current_user.is_superuser:
|
# if current_user.is_superuser:
|
||||||
context["jobs"] = JobLog.objects.all().order_by("-created")
|
# context["jobs"] = JobLog.objects.all().order_by("-created")
|
||||||
else:
|
# else:
|
||||||
context["jobs"] = JobLog.objects.filter(user=current_user).order_by("-created")
|
# context["jobs"] = JobLog.objects.filter(user=current_user).order_by("-created")
|
||||||
|
|
||||||
return render(request, "collections/joblog.html", context)
|
# Context for paginated template
|
||||||
|
context["entity_type"] = "joblog"
|
||||||
|
context["api_endpoint"] = f"{s.SERVER_PATH}/api/v1/joblog/"
|
||||||
|
context["per_page"] = s.API_PAGINATION_DEFAULT_PAGE_SIZE
|
||||||
|
context["list_title"] = "joblog"
|
||||||
|
context["list_mode"] = "combined"
|
||||||
|
|
||||||
|
return render(request, "collections/joblog_paginated.html", context)
|
||||||
|
|
||||||
|
# return render(request, "collections/joblog.html", context)
|
||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
job_name = request.POST.get("job-name")
|
job_name = request.POST.get("job-name")
|
||||||
|
|||||||
@ -463,7 +463,7 @@ function draw(pathway, elem) {
|
|||||||
// TODO needs to be generic once we store it as AddInf
|
// TODO needs to be generic once we store it as AddInf
|
||||||
for (var s of n.predicted_properties["PepperPrediction"]) {
|
for (var s of n.predicted_properties["PepperPrediction"]) {
|
||||||
if (s["mean"] != null) {
|
if (s["mean"] != null) {
|
||||||
tempContent += "<b>DT50 predicted via Pepper:</b> " + s["mean"].toFixed(2) + "<br>"
|
tempContent += "<b>DT50 predicted via Pepper:</b> " + s["mean"].toFixed(2) + " days<br>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
102
templates/collections/_joblog_paginated_list_partial.html
Normal file
102
templates/collections/_joblog_paginated_list_partial.html
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
{# Partial for paginated list content - expects to be inside a remotePaginatedList Alpine.js context #}
|
||||||
|
{# Variables: empty_text (string), show_review_badge (bool), always_show_badge (bool) #}
|
||||||
|
{% load envipytags %}
|
||||||
|
{# Loading state #}
|
||||||
|
<div
|
||||||
|
x-show="isLoading"
|
||||||
|
class="mx-auto flex h-32 w-32 items-center justify-center"
|
||||||
|
>
|
||||||
|
{% include "components/loading-spinner.html" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Error state #}
|
||||||
|
<div
|
||||||
|
x-show="!isLoading && error"
|
||||||
|
class="alert alert-error/50 text-sm"
|
||||||
|
x-text="error"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
{# Content #}
|
||||||
|
<template x-if="!isLoading && !error">
|
||||||
|
<div>
|
||||||
|
{# Empty state #}
|
||||||
|
<div
|
||||||
|
x-show="totalItems === 0"
|
||||||
|
class="text-base-content/70 py-8 text-center"
|
||||||
|
>
|
||||||
|
<p>No {{ empty_text|default:"items" }} found.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Items list #}
|
||||||
|
<ul class="menu bg-base-100 rounded-box w-full" x-show="totalItems > 0">
|
||||||
|
<table class="table-zebra table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>User</th>
|
||||||
|
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Queued At</th>
|
||||||
|
<th>Done At</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<template x-for="obj in paginatedItems" :key="obj.url">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a :href="obj.user.url"><span x-text="obj.user.name"></span></a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a :href="obj.url"><span x-text="obj.id"></span></a>
|
||||||
|
</td>
|
||||||
|
<td><span x-text="obj.name"></span></td>
|
||||||
|
<td><span x-text="obj.status"></span></td>
|
||||||
|
<td><span x-text="obj.created"></span></td>
|
||||||
|
<td><span x-text="obj.done"></span></td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{# Pagination controls #}
|
||||||
|
<div
|
||||||
|
x-show="totalPages > 1"
|
||||||
|
class="mt-4 flex items-center justify-between px-2"
|
||||||
|
>
|
||||||
|
<span class="text-base-content/70 text-sm">
|
||||||
|
Showing <span x-text="showingStart"></span>-<span
|
||||||
|
x-text="showingEnd"
|
||||||
|
></span>
|
||||||
|
of <span x-text="totalItems"></span>
|
||||||
|
</span>
|
||||||
|
<div class="join">
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:disabled="currentPage === 1"
|
||||||
|
@click="prevPage()"
|
||||||
|
>
|
||||||
|
«
|
||||||
|
</button>
|
||||||
|
<template x-for="item in pageNumbers" :key="item.key">
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:class="{ 'btn-active': item.page === currentPage }"
|
||||||
|
:disabled="item.isEllipsis"
|
||||||
|
@click="!item.isEllipsis && goToPage(item.page)"
|
||||||
|
x-text="item.page"
|
||||||
|
></button>
|
||||||
|
</template>
|
||||||
|
<button
|
||||||
|
class="join-item btn btn-sm"
|
||||||
|
:disabled="currentPage === totalPages"
|
||||||
|
@click="nextPage()"
|
||||||
|
>
|
||||||
|
»
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
13
templates/collections/joblog_paginated.html
Normal file
13
templates/collections/joblog_paginated.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{% extends "collections/paginated_base.html" %}
|
||||||
|
|
||||||
|
{% block page_title %}Jobs{% endblock %}
|
||||||
|
|
||||||
|
{% block action_button %}
|
||||||
|
{% endblock action_button %}
|
||||||
|
|
||||||
|
{% block action_modals %}
|
||||||
|
{% endblock action_modals %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
<p>List of Jobs submitted.</p>
|
||||||
|
{% endblock description %}
|
||||||
@ -37,7 +37,11 @@
|
|||||||
perPage: {{ per_page|default:50 }}
|
perPage: {{ per_page|default:50 }}
|
||||||
})"
|
})"
|
||||||
>
|
>
|
||||||
|
{% if entity_type == 'joblog' %}
|
||||||
|
{% include "collections/_joblog_paginated_list_partial.html" with empty_text=list_title|default:"items" show_review_badge=True %}
|
||||||
|
{% else %}
|
||||||
{% include "collections/_paginated_list_partial.html" with empty_text=list_title|default:"items" show_review_badge=True %}
|
{% include "collections/_paginated_list_partial.html" with empty_text=list_title|default:"items" show_review_badge=True %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{# ===== TABBED MODE: Reviewed/Unreviewed tabs (default) ===== #}
|
{# ===== TABBED MODE: Reviewed/Unreviewed tabs (default) ===== #}
|
||||||
|
|||||||
@ -125,6 +125,7 @@ from envipy_additional_information.parsers import (
|
|||||||
LiquidMatrixSourceParser,
|
LiquidMatrixSourceParser,
|
||||||
OxygenUptakeRateParser,
|
OxygenUptakeRateParser,
|
||||||
InitiatingOrganismParser,
|
InitiatingOrganismParser,
|
||||||
|
PFASConfidenceParser,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -141,7 +142,7 @@ def get_parameter(request, paramname):
|
|||||||
res = request.POST.get(paramname)
|
res = request.POST.get(paramname)
|
||||||
if res is not None and res.strip() != "":
|
if res is not None and res.strip() != "":
|
||||||
return res
|
return res
|
||||||
return ValueError("Not all parameters are set!")
|
raise ValueError("Not all parameters are set!")
|
||||||
|
|
||||||
|
|
||||||
def get_parameter_or_empty_string(request, paramname):
|
def get_parameter_or_empty_string(request, paramname):
|
||||||
@ -673,7 +674,8 @@ def build_additional_information_from_request(request, type_):
|
|||||||
elif type_ == "studywst":
|
elif type_ == "studywst":
|
||||||
# study_wst_cond = get_parameter(request, "studywstcond")
|
# study_wst_cond = get_parameter(request, "studywstcond")
|
||||||
raise ValueError("studywstcond is not yet implemented")
|
raise ValueError("studywstcond is not yet implemented")
|
||||||
|
elif type_ == "pfasconfidence":
|
||||||
|
return PFASConfidenceParser.from_string(get_parameter(request, "level"))
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"No corresponding AdditionalInformation for {type_} found!")
|
raise ValueError(f"No corresponding AdditionalInformation for {type_} found!")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user