From 8c5d8187a2b31f444b68c85e71b77b4152ea45a4 Mon Sep 17 00:00:00 2001 From: Bryan Larson Date: Fri, 24 Mar 2023 23:04:14 -0600 Subject: [PATCH 01/25] HYP-292 - Added 4CE challenge; refactored navigation; added Groups to organize DataProjects --- app/hypatio/settings.py | 1 + app/hypatio/urls.py | 2 + app/hypatio/views.py | 36 +++++++++++++- app/projects/admin.py | 8 ++++ .../0098_group_dataproject_group.py | 31 ++++++++++++ app/projects/models.py | 26 ++++++++++ app/projects/views.py | 41 ++++++++++++++++ .../agreementforms/4ce-research-purpose.html | 6 +++ .../4ce-obesity-submissions.html | 47 +++++++++++++++++++ app/templates/base.html | 5 ++ app/templates/projects/group.html | 22 +++++++++ 11 files changed, 224 insertions(+), 1 deletion(-) create mode 100755 app/projects/migrations/0098_group_dataproject_group.py create mode 100644 app/static/agreementforms/4ce-research-purpose.html create mode 100644 app/static/submissionforms/4ce-obesity-submissions.html create mode 100644 app/templates/projects/group.html diff --git a/app/hypatio/settings.py b/app/hypatio/settings.py index fdd870c4..e2aaf35a 100644 --- a/app/hypatio/settings.py +++ b/app/hypatio/settings.py @@ -86,6 +86,7 @@ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'hypatio.views.navigation_context', ], }, }, diff --git a/app/hypatio/urls.py b/app/hypatio/urls.py index 28b9ffef..17ac072a 100644 --- a/app/hypatio/urls.py +++ b/app/hypatio/urls.py @@ -6,6 +6,7 @@ from projects.views import list_data_projects from projects.views import list_data_challenges from projects.views import list_software_projects +from projects.views import GroupView urlpatterns = [ @@ -18,5 +19,6 @@ re_path(r'^data-challenges/$', list_data_challenges, name='data-challenges'), re_path(r'^software-projects/$', list_software_projects, name='software-projects'), re_path(r'^healthcheck/?', include('health_check.urls')), + re_path(r'^groups/(?P[^/]+)/?', GroupView.as_view(), name="group"), re_path(r'^', index, name='index'), ] diff --git a/app/hypatio/views.py b/app/hypatio/views.py index 6da9267e..ebbd2f30 100644 --- a/app/hypatio/views.py +++ b/app/hypatio/views.py @@ -1,7 +1,9 @@ +import os from django.shortcuts import render +from django.utils.functional import SimpleLazyObject from hypatio.auth0authenticate import public_user_auth_and_jwt - +from projects.models import Group, DataProject @public_user_auth_and_jwt def index(request, template_name='index.html'): @@ -12,3 +14,35 @@ def index(request, template_name='index.html'): context = {} return render(request, template_name, context=context) + +def navigation_context(request): + """ + Includes global navigation context in all requests. + + This method is enabled by including it in settings.TEMPLATES as + a context processor. + + :param request: The current HttpRequest + :type request: HttpRequest + :return: The context that should be included in the response's context + :rtype: dict + """ + def group_context(): + + # Check for an active project and determine its group + groups = Group.objects.all() + active_group = None + project = DataProject.objects.filter(project_key=os.path.basename(os.path.normpath(request.path))).first() + if project: + + # Check for group + active_group = next((g for g in groups if project in g.dataproject_set.all()), None) + + return { + "groups": groups, + "active_group": active_group, + } + + return { + "navigation": SimpleLazyObject(group_context) + } diff --git a/app/projects/admin.py b/app/projects/admin.py index 42a66ef4..1beec8cf 100644 --- a/app/projects/admin.py +++ b/app/projects/admin.py @@ -2,6 +2,7 @@ from django.urls import reverse from django.utils.html import escape, mark_safe +from projects.models import Group from projects.models import DataProject from projects.models import AgreementForm from projects.models import SignedAgreementForm @@ -21,6 +22,12 @@ from projects.models import MAYOSignedAgreementFormFields from projects.models import MIMIC3SignedAgreementFormFields + +class GroupAdmin(admin.ModelAdmin): + list_display = ('title', 'key', 'created', 'modified', ) + readonly_fields = ('created', 'modified', ) + + class DataProjectAdmin(admin.ModelAdmin): list_display = ('name', 'project_key', 'informational_only', 'registration_open', 'requires_authorization', 'is_challenge', 'order', 'created', 'modified', ) list_filter = ('informational_only', 'registration_open', 'requires_authorization', 'is_challenge') @@ -140,6 +147,7 @@ class MIMIC3SignedAgreementFormFieldsAdmin(SignedAgreementFormFieldsAdmin): pass +admin.site.register(Group, GroupAdmin) admin.site.register(DataProject, DataProjectAdmin) admin.site.register(AgreementForm, AgreementformAdmin) admin.site.register(SignedAgreementForm, SignedagreementformAdmin) diff --git a/app/projects/migrations/0098_group_dataproject_group.py b/app/projects/migrations/0098_group_dataproject_group.py new file mode 100755 index 00000000..947cb370 --- /dev/null +++ b/app/projects/migrations/0098_group_dataproject_group.py @@ -0,0 +1,31 @@ +# Generated by Django 4.1.7 on 2023-03-24 20:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0097_participant_created_participant_modified'), + ] + + operations = [ + migrations.CreateModel( + name='Group', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(max_length=100, unique=True)), + ('title', models.CharField(max_length=255)), + ('description', models.TextField(blank=True)), + ('navigation_title', models.CharField(blank=True, max_length=20, null=True)), + ('created', models.DateTimeField(auto_now_add=True)), + ('modified', models.DateTimeField(auto_now=True)), + ], + ), + migrations.AddField( + model_name='dataproject', + name='group', + field=models.ForeignKey(blank=True, help_text='Set this to manage where this project is shown in the navigation and interface.', null=True, on_delete=django.db.models.deletion.PROTECT, to='projects.group'), + ), + ] diff --git a/app/projects/models.py b/app/projects/models.py index f096aaa8..b5c5107b 100644 --- a/app/projects/models.py +++ b/app/projects/models.py @@ -187,6 +187,14 @@ class DataProject(models.Model): order = models.IntegerField(blank=True, null=True, help_text="Indicate an order (lowest number = highest order) for how the DataProjects should be listed.") + group = models.ForeignKey( + to="Group", + on_delete=models.PROTECT, + blank=True, + null=True, + help_text="Set this to manage where this project is shown in the navigation and interface." + ) + # Meta created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) @@ -672,3 +680,21 @@ class ChallengeTaskSubmissionDownload(models.Model): user = models.ForeignKey(User, on_delete=models.PROTECT) submission = models.ForeignKey(ChallengeTaskSubmission, on_delete=models.PROTECT) download_date = models.DateTimeField(auto_now_add=True) + + +class Group(models.Model): + """ + An optional grouping for projects. + """ + + key = models.CharField(max_length=100, blank=False, null=False, unique=True) + title = models.CharField(max_length=255, blank=False, null=False) + description = models.TextField(blank=True) + navigation_title = models.CharField(max_length=20, blank=True, null=True) + + # Meta + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.title diff --git a/app/projects/views.py b/app/projects/views.py index ed536bef..7640f190 100644 --- a/app/projects/views.py +++ b/app/projects/views.py @@ -27,6 +27,7 @@ from projects.models import HostedFile from projects.models import Participant from projects.models import SignedAgreementForm +from projects.models import Group from projects.panels import SIGNUP_STEP_COMPLETED_STATUS from projects.panels import SIGNUP_STEP_CURRENT_STATUS from projects.panels import SIGNUP_STEP_FUTURE_STATUS @@ -113,6 +114,46 @@ def list_software_projects(request, template_name='projects/list-software-projec return render(request, template_name, context=context) +@method_decorator(public_user_auth_and_jwt, name='dispatch') +class GroupView(TemplateView): + """ + Builds and renders screens related to Groups. + """ + + group = None + template_name = 'projects/group.html' + + def dispatch(self, request, *args, **kwargs): + """ + Sets up the instance. + """ + + # Get the project key from the URL. + group_key = self.kwargs['group_key'] + + # If this project does not exist, display a 404 Error. + try: + self.group = Group.objects.get(key=group_key) + except ObjectDoesNotExist: + error_message = "The group you searched for does not exist." + return render(request, '404.html', {'error_message': error_message}) + + return super(GroupView, self).dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + """ + Dynamically builds the context for rendering the view based on information + about the user and the Group. + """ + # Get super's context. This is the dictionary of variables for the base template being rendered. + context = super(GroupView, self).get_context_data(**kwargs) + + # Add the project to the context. + context['group'] = self.group + + return context + + @method_decorator(public_user_auth_and_jwt, name='dispatch') class DataProjectView(TemplateView): """ diff --git a/app/static/agreementforms/4ce-research-purpose.html b/app/static/agreementforms/4ce-research-purpose.html new file mode 100644 index 00000000..2ba163c1 --- /dev/null +++ b/app/static/agreementforms/4ce-research-purpose.html @@ -0,0 +1,6 @@ +
+
+ + +
+
diff --git a/app/static/submissionforms/4ce-obesity-submissions.html b/app/static/submissionforms/4ce-obesity-submissions.html new file mode 100644 index 00000000..79a847b7 --- /dev/null +++ b/app/static/submissionforms/4ce-obesity-submissions.html @@ -0,0 +1,47 @@ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+
+ +
+ +
+ +
diff --git a/app/templates/base.html b/app/templates/base.html index fb545e7e..b392ff55 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -212,6 +212,11 @@ {% url 'data-challenges' as data_challenges_url %} + {% for group in navigation.groups %} + {% url 'group' group.key as group_url %} + + {% endfor %} +