Skip to content

Commit

Permalink
Managements views for algorithm interfaces for phases (#3812)
Browse files Browse the repository at this point in the history
This adds the management views for algorithm interfaces for phases. 
The views are only accessible to staff users, and only for algorithm
submission phases that are not external.
The logic behind the views is the same as for the algorithm interfaces,
but it only made sense to refactor the create view logic, and parts of
the templates.


![image](https://github.com/user-attachments/assets/b9c3d86b-fd2e-4ee4-a558-7bef40f15ab5)


Part of DIAGNijmegen/rse-roadmap#153
  • Loading branch information
amickan authored Feb 5, 2025
1 parent c75cd76 commit d840750
Show file tree
Hide file tree
Showing 18 changed files with 564 additions and 113 deletions.
4 changes: 4 additions & 0 deletions app/config/urls/challenge_subdomain.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
),
name="subdomain_robots_txt",
),
path(
"components/",
include("grandchallenge.components.urls", namespace="components"),
),
path(
"evaluation/",
include("grandchallenge.evaluation.urls", namespace="evaluation"),
Expand Down
14 changes: 14 additions & 0 deletions app/grandchallenge/algorithms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,20 @@ def algorithm_interface_manager(self):
def algorithm_interface_through_model_manager(self):
return AlgorithmAlgorithmInterface.objects.filter(algorithm=self)

@property
def algorithm_interface_create_url(self):
return reverse(
"algorithms:interface-create", kwargs={"slug": self.slug}
)

@property
def algorithm_interface_delete_viewname(self):
return "algorithms:interface-delete"

@property
def algorithm_interface_list_url(self):
return reverse("algorithms:interface-list", kwargs={"slug": self.slug})

def is_editor(self, user):
return user.groups.filter(pk=self.editors_group.pk).exists()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
<a class="nav-link"
href="{% url 'algorithms:interface-list' slug=object.slug %}"
><i class="fas fa-sliders-h fa-fw"></i>&nbsp;Interfaces
{% if not object.interfaces.all %}&nbsp;
<i class="fas fa-exclamation-triangle text-danger"></i>
{% endif %}
</a>
<a class="nav-link" id="v-pills-templates-tab" data-toggle="pill"
href="#templates" role="tab"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,5 @@
{% endblock %}

{% block content %}

<h2>Confirm Algorithm Interface Deletion</h2>
<form action="" method="post">
{% csrf_token %}
<p>Are you sure that you want to delete the following algorithm interface from your algorithm?</p>
<ul>
<li>Inputs: {{ object.interface.inputs.all|oxford_comma }}</li>
<li>Outputs: {{ object.interface.outputs.all|oxford_comma }}</li>
</ul>
<p>
<b class="text-danger">WARNING: You are not able to undo this action. Once the interface is deleted, it is deleted forever.</b>
</p>
<a href="{% url 'algorithms:interface-list' slug=algorithm.slug %}"
type="button"
class="btn btn-secondary">Cancel</a>
<input type="submit"
value="I understand, delete interface"
class="btn btn-danger"/>
</form>

{% include 'algorithms/partials/algorithminterface_confirm_delete.html' with base_obj=algorithm %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load remove_whitespace %}

{% block title %}
Interfaces - {{ algorithm.title }} - {{ block.super }}
Expand All @@ -22,36 +20,5 @@
{% endblock %}

{% block content %}
<h2>Algorithm Interfaces for {{ algorithm }}</h2>

<p>
The following interfaces are configured for your algorithm:
</p>
<p><a class="btn btn-primary" href="{% url 'algorithms:interface-create' slug=algorithm.slug %}">Add new interface</a></p>

<div class="table-responsive">
<table class="table table-hover table-borderless">
<thead class="thead-light">
<th>Inputs</th>
<th>Outputs</th>
<th>Delete</th>
</thead>
<tbody>
{% for object in object_list %}
<tr>
<td>{{ object.interface.inputs.all|oxford_comma }}</td>
<td>{{ object.interface.outputs.all|oxford_comma }}</td>
<td>
<a class="btn btn-sm btn-danger m-0"
href="{% url 'algorithms:interface-delete' slug=algorithm.slug interface_pk=object.interface.pk %}">
<i class="fas fa-trash"></i>
</a>
</td>
</tr>
{% empty %}
<tr><td colspan="100%" class="text-center">This algorithm does not have any interfaces defined yet.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% include 'algorithms/partials/algorithminterface_list.html' with base_obj=algorithm %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
{% extends "base.html" %}
{% load crispy_forms_tags %}

{% block title %}
Create Interface - {{ algorithm.title }} - {{ block.super }}
Create Interface - {{ base_obj.title }} - {{ block.super }}
{% endblock %}

{% block breadcrumbs %}
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'algorithms:list' %}">Algorithms</a>
</li>
<li class="breadcrumb-item"><a
href="{{ algorithm.get_absolute_url }}">{{ algorithm.title }}
href="{{ algorithm.get_absolute_url }}">{{ base_obj.title }}
</a></li>
<li class="breadcrumb-item"><a
href="{% url 'algorithms:interface-list' slug=algorithm.slug %}">Interfaces
href="{% url 'algorithms:interface-list' slug=base_obj.slug %}">Interfaces
</a></li>
<li class="breadcrumb-item active"
aria-current="page">Create Interface
Expand All @@ -22,21 +21,5 @@
{% endblock %}

{% block content %}

<h2>Create An Algorithm Interface</h2>
<br>
<p>
Create an interface for your algorithm: define any combination of inputs and outputs, and optionally mark the interface as default for the algorithm.
</p>
<p>
Please see the <a href="{% url 'components:component-interface-list-input' %}">list of input options</a> and the <a href="{% url 'components:component-interface-list-output' %}">
list of output options
</a> for more information and examples.
</p>
<p>
If you cannot find suitable inputs or outputs, please contact <a href="mailto:[email protected]">[email protected]</a>.
</p>

{% crispy form %}

{% include 'algorithms/partials/algorithminterface_form.html' %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{% load remove_whitespace %}

<h2>Confirm Algorithm Interface Deletion</h2>
<form action="" method="post">
{% csrf_token %}
<p>Are you sure that you want to delete the following algorithm interface from this {{ base_obj.verbose_name }}?</p>
<ul>
<li>Inputs: {{ object.interface.inputs.all|oxford_comma }}</li>
<li>Outputs: {{ object.interface.outputs.all|oxford_comma }}</li>
</ul>
<p>
<b class="text-danger">WARNING: You are not able to undo this action. Once the interface is deleted, it is deleted forever.</b>
</p>
<a href="{{ base_obj.algorithm_interface_list_url }}"
type="button"
class="btn btn-secondary">Cancel</a>
<input type="submit"
value="I understand, delete interface"
class="btn btn-danger"/>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% load crispy_forms_tags %}
{% load meta_attr %}

<h2>Create An Algorithm Interface</h2>
<p>
Create an interface: define any combination of inputs and outputs, and optionally mark the interface as default for the {{ base_obj|verbose_name }}.</p>
<p>
Please see the <a href="{% url 'components:component-interface-list-input' %}">list of input options</a> and the <a href="{% url 'components:component-interface-list-output' %}">
list of output options </a> for more information and examples.
</p>
<p>
If you cannot find suitable inputs or outputs, please contact <a href="mailto:[email protected]">[email protected]</a>.
</p>

{% crispy form %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{% load crispy_forms_tags %}
{% load remove_whitespace %}
{% load meta_attr %}

<h2>Algorithm Interfaces for {{ base_obj }}</h2>

<p>The following interfaces are configured for your {{ base_obj|verbose_name }}:</p>
<p>
<a class="btn btn-primary" href="{{ base_obj.algorithm_interface_create_url }}">Add new interface</a>
</p>

<div class="table-responsive">
<table class="table table-hover table-borderless">
<thead class="thead-light">
<th>Inputs</th>
<th>Outputs</th>
<th>Delete</th>
</thead>
<tbody>
{% for object in object_list %}
<tr>
<td>{{ object.interface.inputs.all|oxford_comma }}</td>
<td>{{ object.interface.outputs.all|oxford_comma }}</td>
<td>
<a class="btn btn-sm btn-danger m-0"
href="{% url base_obj.algorithm_interface_delete_viewname slug=base_obj.slug interface_pk=object.interface.pk %}">
<i class="fas fa-trash"></i>
</a>
</td>
</tr>
{% empty %}
<tr><td colspan="100%" class="text-center">This {{ base_obj|verbose_name }} does not have any interfaces defined yet.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
31 changes: 22 additions & 9 deletions app/grandchallenge/algorithms/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1175,30 +1175,43 @@ def dispatch(self, request, *args, **kwargs):
return self.handle_no_permission()


class AlgorithmInterfaceForAlgorithmCreate(
AlgorithmInterfacePermissionMixin, CreateView
):
class AlgorithmInterfaceCreateBase(CreateView):
model = AlgorithmInterface
form_class = AlgorithmInterfaceForm
success_message = "Algorithm interface successfully added"

def get_success_url(self):
return reverse(
"algorithms:interface-list",
kwargs={"slug": self.algorithm.slug},
)
return NotImplementedError

@property
def base_obj(self):
return NotImplementedError

def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context.update({"algorithm": self.algorithm})
context.update({"base_obj": self.base_obj})
return context

def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({"base_obj": self.algorithm})
kwargs.update({"base_obj": self.base_obj})
return kwargs


class AlgorithmInterfaceForAlgorithmCreate(
AlgorithmInterfacePermissionMixin, AlgorithmInterfaceCreateBase
):
@property
def base_obj(self):
return self.algorithm

def get_success_url(self):
return reverse(
"algorithms:interface-list",
kwargs={"slug": self.algorithm.slug},
)


class AlgorithmInterfacesForAlgorithmList(
AlgorithmInterfacePermissionMixin, ListView
):
Expand Down
18 changes: 18 additions & 0 deletions app/grandchallenge/evaluation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1212,6 +1212,24 @@ def algorithm_interface_manager(self):
def algorithm_interface_through_model_manager(self):
return PhaseAlgorithmInterface.objects.filter(phase=self)

@property
def algorithm_interface_create_url(self):
return reverse(
"evaluation:interface-create",
kwargs={"challenge_short_name": self.challenge, "slug": self.slug},
)

@property
def algorithm_interface_delete_viewname(self):
return "evaluation:interface-delete"

@property
def algorithm_interface_list_url(self):
return reverse(
"evaluation:interface-list",
kwargs={"challenge_short_name": self.challenge, "slug": self.slug},
)


class PhaseUserObjectPermission(UserObjectPermissionBase):
content_object = models.ForeignKey(Phase, on_delete=models.CASCADE)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% extends "pages/challenge_settings_base.html" %}
{% load url %}
{% load static %}

{% block title %}
Create Interface - {{ base_obj.title }} - {% firstof challenge.title challenge.short_name %} - {{ block.super }}
{% endblock %}

{% block breadcrumbs %}
<ol class="breadcrumb">
<li class="breadcrumb-item"><a
href="{% url 'challenges:list' %}">Challenges</a>
</li>
<li class="breadcrumb-item"><a
href="{{ challenge.get_absolute_url }}">{% firstof challenge.title challenge.short_name %}</a></li>
<li class="breadcrumb-item"><a
href="{% url 'evaluation:interface-list' challenge_short_name=challenge.short_name slug=base_obj.slug %}">Interfaces
</a></li>
<li class="breadcrumb-item active"
aria-current="page">Create interface</li>
</ol>
{% endblock %}

{% block content %}
{% include 'algorithms/partials/algorithminterface_form.html' %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% extends "pages/challenge_settings_base.html" %}
{% load url %}
{% load static %}

{% block title %}
Delete interface - {{ phase.title }} - {% firstof challenge.title challenge.short_name %} - {{ block.super }}
{% endblock %}

{% block breadcrumbs %}
<ol class="breadcrumb">
<li class="breadcrumb-item"><a
href="{% url 'challenges:list' %}">Challenges</a>
</li>
<li class="breadcrumb-item"><a
href="{{ challenge.get_absolute_url }}">{% firstof challenge.title challenge.short_name %}</a></li>
<li class="breadcrumb-item"><a
href="{% url 'evaluation:interface-list' challenge_short_name=challenge.short_name slug=phase.slug %}">Interfaces
</a></li>
<li class="breadcrumb-item active"
aria-current="page">Delete interface</li>
</ol>
{% endblock %}

{% block content %}
{% include 'algorithms/partials/algorithminterface_confirm_delete.html' with base_obj=phase %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% extends "pages/challenge_settings_base.html" %}
{% load url %}
{% load static %}

{% block title %}
Algorithm interfaces for {{ phase.title }} - {% firstof challenge.title challenge.short_name %} - {{ block.super }}
{% endblock %}

{% block breadcrumbs %}
<ol class="breadcrumb">
<li class="breadcrumb-item"><a
href="{% url 'challenges:list' %}">Challenges</a>
</li>
<li class="breadcrumb-item"><a
href="{{ challenge.get_absolute_url }}">{% firstof challenge.title challenge.short_name %}</a></li>
<li class="breadcrumb-item active"
aria-current="page">Algorithm Interfaces for {{ phase.title }}</li>
</ol>
{% endblock %}

{% block content %}
{% include 'algorithms/partials/algorithminterface_list.html' with base_obj=phase %}
{% endblock %}
Loading

0 comments on commit d840750

Please sign in to comment.