Skip to content

Commit

Permalink
Merge branch 'main' into flexible-file
Browse files Browse the repository at this point in the history
  • Loading branch information
koopmant committed Feb 7, 2025
2 parents 09b1bc7 + b784381 commit d7808e1
Show file tree
Hide file tree
Showing 16 changed files with 520 additions and 33 deletions.
1 change: 1 addition & 0 deletions app/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,7 @@ def sentry_before_send(event, hint):
###############################################################################

CHALLENGES_DEFAULT_ACTIVE_MONTHS = 12
CHALLENGE_ONBOARDING_TASKS_OVERDUE_SOON_CUTOFF = timedelta(hours=72)

###############################################################################
#
Expand Down
16 changes: 15 additions & 1 deletion app/config/urls/challenge_subdomain.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
from django.urls import include, path
from django.views.generic import TemplateView

from grandchallenge.challenges.views import ChallengeUpdate
from grandchallenge.challenges.views import (
ChallengeUpdate,
OnboardingTaskComplete,
OnboardingTaskList,
)

handler500 = "grandchallenge.core.views.handler500"

Expand All @@ -26,6 +30,16 @@
),
path("admins/", include("grandchallenge.admins.urls", namespace="admins")),
path("update/", ChallengeUpdate.as_view(), name="challenge-update"),
path(
"onboarding-tasks/",
OnboardingTaskList.as_view(),
name="challenge-onboarding-task-list",
),
path(
"onboarding-tasks/<uuid:pk>/complete/",
OnboardingTaskComplete.as_view(),
name="challenge-onboarding-task-complete",
),
path("markdownx/", include("markdownx.urls")),
path("", include("grandchallenge.pages.urls", namespace="pages")),
]
Expand Down
19 changes: 4 additions & 15 deletions app/grandchallenge/challenges/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
from django.contrib import admin, messages
from django.contrib.admin import ModelAdmin
from django.core.exceptions import ValidationError
from django.db.models import BooleanField, Case, F, Value, When
from django.db.models import F
from django.utils.html import format_html
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _

from grandchallenge.challenges.emails import send_challenge_status_update_email
Expand Down Expand Up @@ -150,9 +149,9 @@ def lookups(self, *_, **__):

def queryset(self, request, queryset):
if self.value() == "yes":
queryset = queryset.filter(overdue=False)
queryset = queryset.filter(is_overdue=False)
elif self.value() == "no":
queryset = queryset.filter(overdue=True)
queryset = queryset.filter(is_overdue=True)
return queryset


Expand Down Expand Up @@ -224,19 +223,9 @@ class OnboardingTaskAdmin(ModelAdmin):
move_task_deadline_4_weeks,
)

def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.annotate(
overdue=Case(
When(complete=False, deadline__lt=now(), then=Value(True)),
default=Value(False),
output_field=BooleanField(),
)
)

@admin.display(boolean=True)
def on_time(self, obj):
return not obj.overdue
return not obj.is_overdue


admin.site.register(ChallengeUserObjectPermission, UserObjectPermissionAdmin)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 4.2.18 on 2025-02-06 15:26

from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
(
"challenges",
"0048_onboardingtask_onboardingtaskgroupobjectpermission",
),
]

operations = [
migrations.AlterModelOptions(
name="onboardingtask",
options={},
),
]
71 changes: 64 additions & 7 deletions app/grandchallenge/challenges/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,19 @@
validate_slug,
)
from django.db import models
from django.db.models import ExpressionWrapper, F, OuterRef, Q, Subquery, Sum
from django.db.models import (
BooleanField,
Case,
Count,
ExpressionWrapper,
F,
OuterRef,
Q,
Subquery,
Sum,
Value,
When,
)
from django.db.models.signals import post_delete, pre_delete
from django.db.transaction import on_commit
from django.dispatch import receiver
Expand Down Expand Up @@ -52,6 +64,7 @@
GPUTypeChoices,
get_default_gpu_type_choices,
)
from grandchallenge.core.guardian import filter_by_permission
from grandchallenge.core.models import FieldChangeMixin, UUIDModel
from grandchallenge.core.storage import (
get_banner_path,
Expand Down Expand Up @@ -1410,13 +1423,51 @@ class TaskResponsiblePartyChoices(models.TextChoices):
CHALLENGE_ORGANIZERS = "ORG", "Challenge Organizers"


class OnboardingTaskQuerySet(models.QuerySet):
def with_overdue_status(self):
_now = now()
soon_cutoff = (
_now + settings.CHALLENGE_ONBOARDING_TASKS_OVERDUE_SOON_CUTOFF
)

return self.annotate(
is_overdue=Case(
When(complete=False, deadline__lt=_now, then=Value(True)),
default=Value(False),
output_field=BooleanField(),
),
is_overdue_soon=Case(
When(
complete=False,
is_overdue=False,
deadline__lt=soon_cutoff,
then=Value(True),
),
default=Value(False),
output_field=BooleanField(),
),
)

def updatable_by(self, user):
return filter_by_permission(
queryset=self,
user=user,
codename="change_onboardingtask",
accept_user_perms=False,
)

@property
def status_aggregates(self):
return self.aggregate(
num_is_overdue=Count("pk", filter=Q(is_overdue=True)),
num_is_overdue_soon=Count("pk", filter=Q(is_overdue_soon=True)),
)


class OnboardingTask(FieldChangeMixin, UUIDModel):
ResponsiblePartyChoices = TaskResponsiblePartyChoices

class Meta:
permissions = [
("complete_onboaringtask", "Can mark this task as completed")
]
objects = OnboardingTaskQuerySet.as_manager()

created = models.DateTimeField(editable=False)
challenge = models.ForeignKey(
Expand Down Expand Up @@ -1472,11 +1523,17 @@ def assign_permissions(self):
== self.ResponsiblePartyChoices.CHALLENGE_ORGANIZERS
):
assign_perm(
"complete_onboaringtask", self.challenge.admins_group, self
"change_onboardingtask", self.challenge.admins_group, self
)
assign_perm(
"view_onboardingtask", self.challenge.admins_group, self
)
else:
remove_perm(
"complete_onboaringtask", self.challenge.admins_group, self
"change_onboardingtask", self.challenge.admins_group, self
)
remove_perm(
"view_onboardingtask", self.challenge.admins_group, self
)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
$(document).ready(() => {
$("#onboardingTasksTable").DataTable({
order: [
[0, "asc"],
[5, "asc"],
],
paging: false,
info: false,
searching: false,
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@
</li>
{% if "change_challenge" in challenge_perms %}
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.view_name in 'challenge-update,pages:content-update,pages:metadata-update,pages:delete,pages:create,update,pages:list,participants:list,participants:registration-list,evaluation:phase-create,evaluation:phase-update,evaluation:create,evaluation:method-list,evaluation:method-create,evaluation:evaluation-admin-list,evaluation:method-detail,evaluation:combined-leaderboard-update,evaluation:combined-leaderboard-create,evaluation:combined-leaderboard-delete' or request.resolver_match.app_name == 'admins' %}active{% endif %}"
<a class="nav-link {% if request.resolver_match.view_name in 'challenge-update,challenge-onboarding-task-list,pages:content-update,pages:metadata-update,pages:delete,pages:create,update,pages:list,participants:list,participants:registration-list,evaluation:phase-create,evaluation:phase-update,evaluation:create,evaluation:method-list,evaluation:method-create,evaluation:evaluation-admin-list,evaluation:method-detail,evaluation:combined-leaderboard-update,evaluation:combined-leaderboard-create,evaluation:combined-leaderboard-delete' or request.resolver_match.app_name == 'admins' %}active{% endif %}"
href="{% url 'challenge-update' challenge_short_name=challenge.short_name %}">
<i class="fas fa-cog fa-fw"></i>
Admin
{% include "challenges/partials/challenge_onboardingtask_overdue.html" with aggregates=onboardingtask_aggregates only %}
</a>
</li>
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
{% extends "pages/challenge_settings_base.html" %}
{% load user_profile_link from profiles %}
{% load url %}
{% load static %}
{% load dict_lookup %}
{% load humanize %}
{% load guardian_tags %}

{% block title %}
Onboarding Tasks - {% 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">Onboarding Tasks</li>
</ol>
{% endblock %}

{% block content %}


<h2>Onboarding Tasks for {{ challenge.short_name }}</h2>

{% if all_tasks_are_complete %}
<p>
<h4>All onboarding tasks are complete.</h4>
</p>
{% endif %}

<div class="table-responsive">
<table class="table table-hover table-borderless table-sm w-100" id="onboardingTasksTable">
<thead class="thead-light">
<tr>
<th class="text-center">Status</th>
<th class="nonSortable">Title</th>
<th class="nonSortable">Description</th>
<th class="text-right">Due</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for task in object_list %}

<tr>

<td data-order="{{ task.complete|yesno:'1,0' }}" class="align-middle text-center">
{% if task.complete %}
<i class="fas fa-check-circle fa-fw text-success" title="Completed!"></i>
{% elif task.is_overdue_soon %}
<i class="far fa-circle fa-fw text-warning" title="Overdue soon!"></i>
{% elif task.is_overdue %}
<i class="far fa-circle fa-fw text-danger" title="Overdue!"></i>
{% else %}
<i class="far fa-circle fa-fw text-muted" title="Not yet completed"></i>
{% endif %}
</td>
<td class="align-middle">
{{ task.title }}
</td>
<td class="align-middle">

{{ task.description }}
</td>
<td title="{{ task.deadline }}"
data-order="{{ task.deadline|date:"U" }}"
class="align-middle text-nowrap text-right"
style="{{ task.complete|yesno:'text-decoration: line-through;,' }}"
>
{{ task.deadline|naturaltime }}
{% if task.is_overdue_soon %}
<i class="fas fa-exclamation-triangle text-warning"></i>
{% elif task.is_overdue %}
<i class="fas fa-exclamation-triangle text-danger"></i>
{% endif %}

</td>
<td class="align-middle text-nowrap">
<form method="post"
action="{% url 'challenge-onboarding-task-complete' challenge_short_name=challenge.short_name pk=task.pk %}">
{% csrf_token %}
<input type="hidden"
name="complete"
value="{{ task.complete|yesno:'false,true' }}"
>
{% if task.complete %}
<button type="submit"
class="btn btn-xs btn-danger"
title="Mark as incomplete">
<i class="fas fa-undo fa-fw"></i> Undo Complete
</button>
{% else %}
<button type="submit" class="btn btn-success">
<i class="fas fa-check-circle fa-fw"></i> Mark Complete
</button>
{% endif %}
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

{% block script %}
{{ block.super }}
<script type="module" src="{% static 'js/challenges/challengeonboardingtask_list_table.mjs' %}"></script>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% if aggregates.num_is_overdue > 0 %}
<span class="badge badge-pill badge-danger align-middle" title="Onboarding tasks overdue">
{{ aggregates.num_is_overdue }}
</span>
{% endif %}

{% if aggregates.num_is_overdue_soon > 0 %}
<span class="badge badge-pill badge-warning align-middle" title="Onboarding tasks due soon">
{{ aggregates.num_is_overdue_soon }}
</span>
{% endif %}
Loading

0 comments on commit d7808e1

Please sign in to comment.