Skip to content

Commit

Permalink
Merge pull request #703 from hms-dbmi/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
b32147 authored Sep 4, 2024
2 parents ed5c96a + 2119b7b commit 523a280
Show file tree
Hide file tree
Showing 43 changed files with 1,990 additions and 350 deletions.
63 changes: 63 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,66 @@
# [1.1.0-rc.8](https://github.com/hms-dbmi/hypatio-app/compare/v1.1.0-rc.7...v1.1.0-rc.8) (2024-09-04)


### Bug Fixes

* **requirements:** Updated Python requirements ([3296c04](https://github.com/hms-dbmi/hypatio-app/commit/3296c042fe2253343d300597dc54d00b005d9a5c))

# [1.1.0-rc.7](https://github.com/hms-dbmi/hypatio-app/compare/v1.1.0-rc.6...v1.1.0-rc.7) (2024-08-29)


### Bug Fixes

* **hypatio:** Refactored how active project is determined for navigation to prevent error when scanners attempt to load non-existent paths ([f956917](https://github.com/hms-dbmi/hypatio-app/commit/f9569176c7ba7a0232f38b7d5f9d1f4173ad626a))

# [1.1.0-rc.6](https://github.com/hms-dbmi/hypatio-app/compare/v1.1.0-rc.5...v1.1.0-rc.6) (2024-08-29)


### Features

* **projects:** Setup ability to use a Form class to manage rendering/processing of AgreementForms' data ([c60b9e0](https://github.com/hms-dbmi/hypatio-app/commit/c60b9e0208a451f9c0f07c86cec66143c7fe44d4))

# [1.1.0-rc.5](https://github.com/hms-dbmi/hypatio-app/compare/v1.1.0-rc.4...v1.1.0-rc.5) (2024-08-27)


### Bug Fixes

* **projects:** Refactored how institutional officials/members are stored; reworked agreement form workflow for institutional members ([88fa76e](https://github.com/hms-dbmi/hypatio-app/commit/88fa76ef27e5a8254e18d3e52c63c78d0b2f09d5))

# [1.1.0-rc.4](https://github.com/hms-dbmi/hypatio-app/compare/v1.1.0-rc.3...v1.1.0-rc.4) (2024-08-26)


### Bug Fixes

* **requirements:** Updated Python requirements ([4cc23e4](https://github.com/hms-dbmi/hypatio-app/commit/4cc23e48e6bd85d8e72b4c0ab296f5e492e7b8c8))

# [1.1.0-rc.3](https://github.com/hms-dbmi/hypatio-app/compare/v1.1.0-rc.2...v1.1.0-rc.3) (2024-08-21)


### Bug Fixes

* **projects:** Added missing JS library ([f722f7b](https://github.com/hms-dbmi/hypatio-app/commit/f722f7bf0d45a13575e9c91753742daf5dfbe2af))

# [1.1.0-rc.2](https://github.com/hms-dbmi/hypatio-app/compare/v1.1.0-rc.1...v1.1.0-rc.2) (2024-08-21)


### Bug Fixes

* **projects:** Fixed an issue on DataProject view with un-authenticated user ([c10b074](https://github.com/hms-dbmi/hypatio-app/commit/c10b074888b37b30596703549f34a58b6d2f9e82))

# [1.1.0-rc.1](https://github.com/hms-dbmi/hypatio-app/compare/v1.0.1...v1.1.0-rc.1) (2024-08-21)


### Bug Fixes

* **projects:** Allows automatic access for user covered by an institutional signer that has access ([7f4685c](https://github.com/hms-dbmi/hypatio-app/commit/7f4685c877f07d99cd9a230911cf0da745da6f0e))


### Features

* **pdf:** Added an app for rendering PDFs ([dd98208](https://github.com/hms-dbmi/hypatio-app/commit/dd98208d4717265fdf68f2dc6124de20276d994d))
* **projects:** Added a model for institutional officials/members for blanket DUAs; fixed older fixture that accessed models through ORM causing issues ([6f6667b](https://github.com/hms-dbmi/hypatio-app/commit/6f6667be9724b4185ef6a6c596b4969ce2c5966f))
* **projects:** Added view and API support for institutional officials; added 4CE DUA ([9538168](https://github.com/hms-dbmi/hypatio-app/commit/95381687e422e576a2a3f98048daa26dbad75fc8))

## [1.0.1](https://github.com/hms-dbmi/hypatio-app/compare/v1.0.0...v1.0.1) (2024-08-01)


Expand Down
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Set arch
ARG BUILD_ARCH=amd64

FROM hmsdbmitc/dbmisvc:debian12-slim-python3.11-0.6.2 AS builder

ARG BUILD_ARCH

# Install requirements
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
Expand All @@ -10,8 +15,12 @@ RUN apt-get update \
default-libmysqlclient-dev \
libssl-dev \
pkg-config \
libfontconfig \
&& rm -rf /var/lib/apt/lists/*

# Install requirements for PDF generation
ADD phantomjs-2.1.1-${BUILD_ARCH}.tar.gz /tmp/

# Add requirements
ADD requirements.* /

Expand All @@ -27,6 +36,7 @@ ARG APP_CODENAME="hypatio"
ARG VERSION
ARG COMMIT
ARG DATE
ARG BUILD_ARCH

LABEL org.label-schema.schema-version=1.0 \
org.label-schema.vendor="HMS-DBMI" \
Expand All @@ -38,12 +48,16 @@ LABEL org.label-schema.schema-version=1.0 \
org.label-schema.vcs-url="https://github.com/hms-dbmi/hypatio-app" \
org.label-schema.vcf-ref=${COMMIT}

# Copy PhantomJS binary
COPY --from=builder /tmp/phantomjs /usr/local/bin/phantomjs

# Copy Python wheels from builder
COPY --from=builder /root/wheels /root/wheels

# Install requirements
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libfontconfig \
default-libmysqlclient-dev \
libmagic1 \
&& rm -rf /var/lib/apt/lists/*
Expand Down
40 changes: 10 additions & 30 deletions app/hypatio/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
'django_jsonfield_backport',
'django_q',
'django_ses',
'pdf',
]

MIDDLEWARE = [
Expand Down Expand Up @@ -149,7 +150,9 @@
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_S3_SIGNATURE_VERSION = 's3v4'
AWS_STORAGE_BUCKET_NAME = environment.get_str('S3_BUCKET', required=True)
AWS_LOCATION = 'upload'

PROJECTS_UPLOADS_PREFIX = "upload"
PROJECTS_DOCUMENTS_PREFIX = "documents"

##########

Expand Down Expand Up @@ -316,35 +319,12 @@
)

# Output the standard logging configuration
LOGGING = config('HYPATIO', root_level=logging.DEBUG)

# Disable warning level for 4xx request logging
LOGGING['loggers'].update({
'django.request': {
'handlers': ['console'],
'level': 'ERROR',
'propagate': True,
},
'boto3': {
'handlers': ['console'],
'level': 'INFO',
'propagate': True,
},
'botocore': {
'handlers': ['console'],
'level': 'INFO',
'propagate': True,
},
's3transfer': {
'handlers': ['console'],
'level': 'INFO',
'propagate': True,
},
'urllib3': {
'handlers': ['console'],
'level': 'INFO',
'propagate': True,
},
LOGGING = config('HYPATIO', root_level=logging.DEBUG, logger_levels={
"django.request": "ERROR",
"boto3": "INFO",
"botocore": "INFO",
"s3transfer": "INFO",
"urllib3": "INFO",
})

#####################################################################################
4 changes: 2 additions & 2 deletions app/hypatio/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +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<group_key>[^/]+)/?', GroupView.as_view(), name="group"),
re_path(r'^', index, name='index'),
re_path(r'^groups/(?P<group_key>[^/]+)/?$', GroupView.as_view(), name="group"),
re_path(r'^/?$', index, name='index'),
]
29 changes: 24 additions & 5 deletions app/hypatio/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import os

from django.shortcuts import render
from django.utils.functional import SimpleLazyObject
from django.urls import resolve, Resolver404

from hypatio.auth0authenticate import public_user_auth_and_jwt
from projects.apps import ProjectsConfig
from projects.models import Group, DataProject

import logging
logger = logging.getLogger(__name__)


@public_user_auth_and_jwt
def index(request, template_name='index.html'):
"""
Expand Down Expand Up @@ -32,11 +38,24 @@ def group_context():
# Check for an active project and determine its group
groups = Group.objects.filter(dataproject__isnull=False, dataproject__visible=True).distinct()
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)
# Attempt to resolve the current URL
try:
match = resolve(request.path)

# Check if projects
if match and match.app_name == ProjectsConfig.name and "project_key" in match.kwargs:
project = DataProject.objects.filter(project_key=match.kwargs["project_key"]).first()
if project:

# Check for group
active_group = next((g for g in groups if project in g.dataproject_set.all()), None)

except Resolver404:
logger.debug(f"Path could not be resolved: {request.path}")

except Exception as e:
logger.exception(f"Group context error: {e}", exc_info=True)

# Pull out top-level groups
parent_groups_keys = groups.filter(parent__isnull=False).values_list('parent', flat=True)
Expand Down
19 changes: 19 additions & 0 deletions app/pdf/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
""" ______ __
____ ____/ / __/ ____ ____ ____ ___ _________ _/ /_____ _____
/ __ \/ __ / /_ / __ `/ _ \/ __ \/ _ \/ ___/ __ `/ __/ __ \/ ___/
/ /_/ / /_/ / __/ / /_/ / __/ / / / __/ / / /_/ / /_/ /_/ / /
/ .___/\__,_/_/_____\__, /\___/_/ /_/\___/_/ \__,_/\__/\____/_/
/_/ /_____/____/
"""

__title__ = 'PDF Generator'
__version__ = '0.1.3'
__author__ = 'Charles TISSIER'
__license__ = 'MIT'
__copyright__ = 'Copyright 2017 Charles TISSIER'

# Version synonym
VERSION = __version__


default_app_config = 'pdf.apps.PdfGeneratorConfig'
7 changes: 7 additions & 0 deletions app/pdf/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from __future__ import unicode_literals

from django.apps import AppConfig


class PdfGeneratorConfig(AppConfig):
name = 'pdf'
80 changes: 80 additions & 0 deletions app/pdf/generators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import subprocess
import os
import random

from .settings import pdf_settings
from django.http import HttpResponse
from django.core.files.base import ContentFile


class PDFGenerator(object):
def __init__(self, html, paperformat='A4', zoom=1, script=pdf_settings.DEFAULT_RASTERIZE_SCRIPT,
temp_dir=pdf_settings.DEFAULT_TEMP_DIR):
self.script = script
self.temp_dir = temp_dir
self.html = html
self.html_file = self.__get_html_filepath()
self.pdf_file = self.__get_pdf_filepath()
self.paperformat = paperformat
self.zoom = zoom
self.pdf_data = None

self.__write_html()
self.__generate()
self.__set_pdf_data()
self.__remove_source_file()

def __write_html(self):
with open(self.html_file, 'w') as f:
f.write(self.html)
f.close()

def __get_html_filepath(self):
return os.path.join(self.temp_dir, '{}.html'.format(PDFGenerator.get_random_filename(25)))

def __get_pdf_filepath(self):
return os.path.join(self.temp_dir, '{}.pdf'.format(PDFGenerator.get_random_filename(25)))

def __generate(self):
"""
call the following command:
phantomjs rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]
"""
phantomjs_env = os.environ.copy()
phantomjs_env["OPENSSL_CONF"] = "/etc/openssl/"
command = [
pdf_settings.PHANTOMJS_BIN_PATH,
'--ssl-protocol=any',
'--ignore-ssl-errors=yes',
self.script,
self.html_file,
self.pdf_file,
self.paperformat,
str(self.zoom)
]
return subprocess.call(command, env=phantomjs_env)

def __set_pdf_data(self):
with open(self.pdf_file, "rb") as pdf:
self.pdf_data = pdf.read()

def get_content_file(self, filename):
return ContentFile(self.pdf_data, name=filename)

def get_data(self):
return self.pdf_data

def get_http_response(self, filename):
response = HttpResponse(self.pdf_data, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(filename)
return response

def __remove_source_file(self):
html_rm = subprocess.call(['rm', self.html_file])
pdf_rm = subprocess.call(['rm', self.pdf_file])
return html_rm & pdf_rm

@staticmethod
def get_random_filename(nb=50):
choices = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return "".join([random.choice(choices) for _ in range(nb)])
50 changes: 50 additions & 0 deletions app/pdf/rasterize.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// https://github.com/ariya/phantomjs/blob/master/examples/rasterize.js
"use strict";
var page = require('webpage').create(),
system = require('system'),
address, output, size, pageWidth, pageHeight;

if (system.args.length < 3 || system.args.length > 5) {
console.log('Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]');
console.log(' paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
console.log(' image (png/jpg output) examples: "1920px" entire page, window width 1920px');
console.log(' "800px*600px" window, clipped to 800x600');
phantom.exit(1);
} else {
address = system.args[1];
output = system.args[2];
page.viewportSize = { width: 600, height: 600 };
if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") {
size = system.args[3].split('*');
page.paperSize = size.length === 2 ? { width: size[0], height: size[1], margin: '0px' }
: { format: system.args[3], orientation: 'portrait', margin: '1.5cm' };
} else if (system.args.length > 3 && system.args[3].substr(-2) === "px") {
size = system.args[3].split('*');
if (size.length === 2) {
pageWidth = parseInt(size[0], 10);
pageHeight = parseInt(size[1], 10);
page.viewportSize = { width: pageWidth, height: pageHeight };
page.clipRect = { top: 0, left: 0, width: pageWidth, height: pageHeight };
} else {
console.log("size:", system.args[3]);
pageWidth = parseInt(system.args[3], 10);
pageHeight = parseInt(pageWidth * 3/4, 10); // it's as good an assumption as any
console.log ("pageHeight:",pageHeight);
page.viewportSize = { width: pageWidth, height: pageHeight };
}
}
if (system.args.length > 4) {
page.zoomFactor = system.args[4];
}
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
phantom.exit(1);
} else {
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
}
Loading

0 comments on commit 523a280

Please sign in to comment.