Skip to content

Commit

Permalink
Elementary report template management.
Browse files Browse the repository at this point in the history
WIP

File uploads not handled properly yet.
  • Loading branch information
holtgrewe committed Apr 7, 2021
1 parent 7a0cbf6 commit 5c7aa77
Show file tree
Hide file tree
Showing 20 changed files with 739 additions and 0 deletions.
2 changes: 2 additions & 0 deletions config/settings/base.py
Expand Up @@ -111,6 +111,7 @@
"maintenance.apps.MaintenanceConfig",
"regmaps.apps.RegmapsConfig",
"beaconsite.apps.BeaconsiteConfig",
"reports.apps.ReportsConfig",
]

# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
Expand Down Expand Up @@ -690,6 +691,7 @@ def set_logging(debug):
# Default: None
DJANGO_SU_CUSTOM_LOGIN_ACTION = None

CRISPY_FAIL_SILENTLY = False

# STORAGE CONFIGURATION
# ------------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions config/urls.py
Expand Up @@ -56,6 +56,7 @@ def handler500(request, *args, **argv):
url(r"^su/", include("django_su.urls")),
url(r"^cohorts/", include("cohorts.urls")),
url(r"^clinvar-export/", include("clinvar_export.urls")),
url(r"^reports/", include("reports.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

# Augment with URLs for Beacon site.
Expand Down
Empty file added reports/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions reports/admin.py
@@ -0,0 +1,6 @@
from django.contrib import admin

# from .models import HgmdPublicLocus

# Register your models here.
# admin.site.register(HgmdPublicLocus)
5 changes: 5 additions & 0 deletions reports/apps.py
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ReportsConfig(AppConfig):
name = "reports"
80 changes: 80 additions & 0 deletions reports/forms.py
@@ -0,0 +1,80 @@
import hashlib

from django import forms
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator

from .models import ReportTemplate


class ReportTemplateForm(forms.ModelForm):
class Meta:
model = ReportTemplate
fields = ("title", "filename")

title = forms.CharField(
label="Template Title", help_text="Leave empty to use file name", required=False
)

payload = forms.FileField(
label="Template File", help_text="Upload a .docx file to use for the template."
)

filename = forms.CharField(
required=False,
help_text="Specify the file name to override the file name of the uploaded file",
validators=[
RegexValidator(r"(^$)|(\.docx$)", message="File name must be blank or end in .docx"),
RegexValidator(
r"^[a-zA-Z0-9\._-]*$",
message="File name must only consist of alphanumeric characters, spaces, dots, hyphens, and underscores",
),
],
)

def __init__(self, project, *args, **kwargs):
super().__init__(*args, **kwargs)
self.project = project
if self.instance.pk:
self.fields["payload"].required = False

def clean(self):
cleaned_data = self.cleaned_data
if "payload" in cleaned_data and cleaned_data["payload"]:
import pdb

pdb.set_trace()
cleaned_data["title"] = cleaned_data["title"] or cleaned_data["payload"].name
cleaned_data["filename"] = cleaned_data["filename"] or cleaned_data["payload"].name
ctx = hashlib.sha256()
if cleaned_data["payload"].multiple_chunks():
for data in cleaned_data["payload"].chunks():
ctx.update(data)
else:
ctx.update(cleaned_data["payload"].read())
cleaned_data["filehash"] = "sha256:%s" % ctx.hexdigest()
cleaned_data["filesize"] = cleaned_data["payload"].size

if not cleaned_data["filename"].endswith(".docx"):
raise ValidationError("The file name must end in .docx")

qs = ReportTemplate.objects.filter(project=self.project, filename=cleaned_data["filename"])
if self.instance.pk:
qs = qs.exclude(pk=self.instance.pk)
if qs:
raise ValidationError(
"There already exists a report template with file name %s in this project!"
% cleaned_data["filename"]
)

return cleaned_data

def save(self, commit=True):
super().save(commit=False)
if "filehash" in self.cleaned_data:
self.instance.filehash = self.cleaned_data["filehash"]
if "filesize" in self.cleaned_data:
self.instance.filesize = self.cleaned_data["filesize"]
self.instance.project = self.project
self.instance.save()
return self.instance
58 changes: 58 additions & 0 deletions reports/migrations/0001_initial.py
@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2021-04-07 15:27
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

initial = True

dependencies = [
("projectroles", "0017_project_full_title"),
]

operations = [
migrations.CreateModel(
name="ReportTemplate",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
(
"date_created",
models.DateTimeField(auto_now_add=True, help_text="DateTime of creation"),
),
(
"date_modified",
models.DateTimeField(auto_now=True, help_text="DateTime of last modification"),
),
("title", models.CharField(max_length=512)),
("filename", models.CharField(max_length=512)),
("filesize", models.IntegerField(default=0)),
("filehash", models.CharField(max_length=128)),
(
"sodar_uuid",
models.UUIDField(default=uuid.uuid4, help_text="Case SODAR UUID", unique=True),
),
(
"project",
models.ForeignKey(
help_text="Project in which this objects belongs",
on_delete=django.db.models.deletion.CASCADE,
to="projectroles.Project",
),
),
],
options={"ordering": ("title",),},
),
migrations.AlterUniqueTogether(
name="reporttemplate", unique_together=set([("project", "filename")]),
),
]
Empty file added reports/migrations/__init__.py
Empty file.
46 changes: 46 additions & 0 deletions reports/models.py
@@ -0,0 +1,46 @@
import uuid as uuid_object

from django.db import models
from django.conf import settings
from django.urls import reverse
from projectroles.models import Project

#: Django user model.
AUTH_USER_MODEL = getattr(settings, "AUTH_USER_MODEL", "auth.User")


class ReportTemplate(models.Model):
"""A report template."""

#: DateTime of creation
date_created = models.DateTimeField(auto_now_add=True, help_text="DateTime of creation")
#: DateTime of last modification
date_modified = models.DateTimeField(auto_now=True, help_text="DateTime of last modification")

#: UUID used for identification throughout SODAR.
sodar_uuid = models.UUIDField(
default=uuid_object.uuid4, unique=True, help_text="Case SODAR UUID"
)

#: The project containing this case.
project = models.ForeignKey(Project, help_text="Project in which this objects belongs")

#: Title of the report.
title = models.CharField(max_length=512)
#: File name of the report.
filename = models.CharField(max_length=512)

#: File size in bytes.
filesize = models.IntegerField(default=0)
#: File hash.
filehash = models.CharField(max_length=128)

class Meta:
unique_together = (("project", "filename"),)
ordering = ("title",)

def get_absolute_url(self):
return reverse(
"reports:template-view",
kwargs={"project": self.project.sodar_uuid, "template": self.sodar_uuid},
)
40 changes: 40 additions & 0 deletions reports/plugins.py
@@ -0,0 +1,40 @@
from projectroles.plugins import ProjectAppPluginPoint

from .urls import urlpatterns


class ProjectAppPlugin(ProjectAppPluginPoint):
"""Plugin for registering app with Projectroles"""

name = "reports"
title = "Reports"
urls = urlpatterns
# ...

icon = "file-text-o"

entry_point_url_id = "reports:template-list"

description = "Generate reports on word processing templates"

#: Required permission for accessing the app
app_permission = "reports.view_data"

#: Enable or disable general search from project title bar
search_enable = False

#: List of search object types for the app
search_types = []

#: No settings for this app.
app_settings = {}

def get_object_link(self, model_str, uuid):
"""
Return URL for referring to a object used by the app, along with a
label to be shown to the user for linking.
:param model_str: Object class (string)
:param uuid: sodar_uuid of the referred object
:return: Dict or None if not found
"""
return None
34 changes: 34 additions & 0 deletions reports/rules.py
@@ -0,0 +1,34 @@
import rules
from projectroles import rules as pr_rules


rules.add_perm(
"reports.view_data",
rules.is_superuser
| pr_rules.is_project_owner
| pr_rules.is_project_delegate
| pr_rules.is_project_contributor
| pr_rules.is_project_guest,
)

rules.add_perm(
"reports.add_data",
rules.is_superuser
| pr_rules.is_project_owner
| pr_rules.is_project_delegate
| pr_rules.is_project_contributor,
)
rules.add_perm(
"reports.delete_data",
rules.is_superuser
| pr_rules.is_project_owner
| pr_rules.is_project_delegate
| pr_rules.is_project_contributor,
)
rules.add_perm(
"reports.update_data",
rules.is_superuser
| pr_rules.is_project_owner
| pr_rules.is_project_delegate
| pr_rules.is_project_contributor,
)
29 changes: 29 additions & 0 deletions reports/templates/reports/_report_template_buttons.html
@@ -0,0 +1,29 @@
{% load rules %}

{% has_perm 'reports.update_data' request.user as can_update_data %}
{% has_perm 'reports.delete_data' request.user as can_delete_data %}

<div class="btn-group sodar-list-btn-group pull-right">
<button class="btn btn-secondary dropdown-toggle sodar-list-dropdown"
type="button" data-toggle="dropdown" aria-expanded="false">
<i class="fa fa-cog"></i>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="{% url 'reports:template-view' project=project.sodar_uuid template=object.sodar_uuid %}">
<i class="fa fa-fw fa-eye"></i>
View
</a>
{% if can_update_data %}
<a class="dropdown-item" href="{% url 'reports:template-update' project=project.sodar_uuid template=object.sodar_uuid %}">
<i class="fa fa-fw fa-pencil"></i>
Edit
</a>
{% endif %}
{% if can_delete_data %}
<a class="dropdown-item" href="{% url 'reports:template-delete' project=project.sodar_uuid template=object.sodar_uuid %}">
<i class="fa fa-fw fa-eraser"></i>
Delete
</a>
{% endif %}
</div>
</div>
35 changes: 35 additions & 0 deletions reports/templates/reports/reporttemplate_confirm_delete.html
@@ -0,0 +1,35 @@
{% extends 'projectroles/base.html' %}

{% load rules %}
{% load crispy_forms_filters %}
{% load projectroles_common_tags %}

{% block title %}Confirm Removal of Report Template{% endblock %}

{% block projectroles %}

<div class="row sodar-subtitle-container">
<h2>Confirm Removal of Report Template</h2>
</div>

<div class="container-fluid sodar-page-container">
<div class="alert alert-warning" role="alert">
Do you really want to remove the report template "<strong>{{ object.title }}</strong>" file file name "<strong>{{ object.filename }}</strong>"?
<strong>This action cannot be reverted!</strong>
</div>

<form method="post">
{% csrf_token %}
<div class="btn-group pull-right">
<a role="button" class="btn btn-secondary"
href="{% url 'reports:template-view' project=project.sodar_uuid template=object.sodar_uuid %}"
<i class="fa fa-arrow-circle-left"></i> Cancel
</a>
<button type="submit" class="btn btn-danger">
<i class="fa fa-times"></i> Remove
</button>
</div>
</form>
</div>

{% endblock projectroles %}

0 comments on commit 5c7aa77

Please sign in to comment.