This is an automated email from the ASF dual-hosted git repository. machristie pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/airavata-cookiecutter-django-output-view.git
commit 4a20d70be78b1f7202b87686520ec92b96d0aacc Author: Marcus Christie <[email protected]> AuthorDate: Fri May 28 15:11:21 2021 -0400 initial commit --- README.md | 10 +++ cookiecutter.json | 9 +++ hooks/post_gen_project.py | 72 ++++++++++++++++++++++ .../{{cookiecutter.project_slug}}.py | 49 +++++++++++++++ 4 files changed, 140 insertions(+) diff --git a/README.md b/README.md new file mode 100644 index 0000000..515c7a5 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ + +# Airavata Django Portal Output View Provider Cookiecutter + +## Quickstart + + + cd custom_django_app/custom_django_app/ + cookiecutter https://github.com/machristie/cookiecutter-airavata-django-output-view.git -f + +`-f` is needed because output_views/ directory should already exist diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..7765419 --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,9 @@ +{ + "project_name": "My Custom Output View", + "project_slug": "{{ cookiecutter.project_name | slugify(separator='_')}}", + "project_short_description": "{{ cookiecutter.project_name }} generates data for an output view in the Airavata Django Portal", + "output_view_provider_class_name": "{{ cookiecutter.project_name | title | replace(' ', '') }}Provider", + "output_views_directory_name": "output_views", + "output_view_display_type": ["image", "link", "html"], + "number_of_output_files": ["single (URI)", "multiple (URI_COLLECTION)"] +} diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py new file mode 100644 index 0000000..919288f --- /dev/null +++ b/hooks/post_gen_project.py @@ -0,0 +1,72 @@ +import os +import sys + + +def find_setup_cfg_file(): + currdir = os.getcwd() + while currdir != os.path.dirname(currdir): + setupcfg_path = os.path.join(currdir, "setup.cfg") + if os.path.isfile(setupcfg_path): + return setupcfg_path + else: + currdir = os.path.dirname(currdir) + # Couldn't find it, returning None + return None + +def find_last_line_of_output_view_providers(setup_cfg_lines): + output_vps_start = -1 + output_vps_end = -1 + for index, line in enumerate(setup_cfg_lines): + if line.strip().startswith('airavata.output_view_providers'): + output_vps_start = index + # If the line is non-blank and has a dangling item, update the end index + elif line.startswith(' ') and line.strip() != '' and output_vps_start > 0: + output_vps_end = index + elif output_vps_end > 0: + break + # Return None if we couldn't find it + return output_vps_end if output_vps_end > 0 else None + +def find_last_line_of_entry_points(setup_cfg_lines): + entry_points_start = -1 + entry_points_end = -1 + for index, line in enumerate(setup_cfg_lines): + if line.strip() == '[options.entry_points]': + entry_points_start = index + # If the line is non-blank and doesn't start a new section, update the end index + elif line.strip() != '' and not line.startswith('[') and entry_points_start > 0: + entry_points_end = index + elif entry_points_end > 0: + break + # Return None if we couldn't find it + return entry_points_end if entry_points_end > 0 else None + +def insert_output_view_provider(setup_cfg_lines, index, insert_entry_point_group=False): + updated_lines = setup_cfg_lines.copy() + updated_lines.insert(index+1, " {{cookiecutter.project_slug}} = FIXME.{{cookiecutter.output_views_directory_name}}:{{cookiecutter.output_view_provider_class_name}}" + os.linesep) + if insert_entry_point_group: + updated_lines.insert(index+1, "airavata.output_view_providers =" + os.linesep) + return updated_lines + +setup_cfg_file = find_setup_cfg_file() +if not setup_cfg_file: + print("Could not find setup.cfg file! Are you running this from within a custom Django app?", file=sys.stderr) + sys.exit(1) + +with open(setup_cfg_file, 'r+') as f: + lines = f.readlines() + end_of_output_view_providers = find_last_line_of_output_view_providers(lines) + updated_lines = None + if end_of_output_view_providers is not None: + updated_lines = insert_output_view_provider(lines, end_of_output_view_providers) + else: + end_of_entry_points = find_last_line_of_entry_points(lines) + if end_of_entry_points is not None: + updated_lines = insert_output_view_provider(lines, end_of_entry_points, insert_entry_point_group=True) + if updated_lines is None: + print("Could not find insertion point for output view provider entry point!", file=sys.stderr) + sys.exit(1) + else: + f.seek(0) + f.writelines(updated_lines) + diff --git a/{{cookiecutter.output_views_directory_name}}/{{cookiecutter.project_slug}}.py b/{{cookiecutter.output_views_directory_name}}/{{cookiecutter.project_slug}}.py new file mode 100644 index 0000000..3077383 --- /dev/null +++ b/{{cookiecutter.output_views_directory_name}}/{{cookiecutter.project_slug}}.py @@ -0,0 +1,49 @@ +{% if cookiecutter.output_view_display_type == "image" %} +import io +{% elif cookiecutter.output_view_display_type == "html" %} +from django.template.loader import render_to_string +{% endif %} + +class {{ cookiecutter.output_view_provider_class_name }}: + display_type = "{{ cookiecutter.output_view_display_type }}" + # As a performance optimization, the output view provider can be invoked + # immediately instead of only after being selected by the user in the + # portal. Set to True to invoke immediately. Only use this with simple + # output view providers that return quickly + immediate = False + name = "{{ cookiecutter.project_name }}" + + def generate_data(self, request, experiment_output, experiment,{% if "single" in cookiecutter.number_of_output_files %} output_file=None,{% else %} output_files=None,{% endif %} **kwargs): + {% if cookiecutter.output_view_display_type == "link" %} + label = "Link to Google" + url = "https://google.com" + return { + "label": label, + "url": url + } + {% elif cookiecutter.output_view_display_type == "image" %} + # Typical thing is to write an image to an in-memory BytesIO object and + # then return its bytes + buffer = io.BytesIO() + # Example: say you have a figure object, which is an instance of + # matplotlib's Figure. Then you can write it to the BytesIO object + # figure.savefig(buffer, format='png') + image_bytes = buffer.getvalue() + buffer.close() + return { + 'image': image_bytes, + 'mime-type': 'image/png' + } + {% elif cookiecutter.output_view_display_type == "html" %} + # Return a dictionary with 'output' as the HTML string and 'js' as the + # absolute URL to a JavaScript file to load for the view + # In the example code, the HTML is produced from a Django template, but + # you don't have to do it that way. + html_context = {} # extra context + html_string = render_to_string('path/to/template.html', html_context) + js_abs_path = "/static/path/to/script.js" + return { + 'output': html_string, + 'js': js_abs_path + } + {% endif %}
