-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
31 changed files
with
876 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import datetime | ||
import logging | ||
import os | ||
|
||
from django.db import transaction | ||
from django.conf import settings | ||
|
||
from ..models import Project | ||
from ..models import Dashboard | ||
from ..models import User | ||
from ..models import database_qs | ||
from ..schema import DashboardListSchema | ||
from ..schema import DashboardDetailSchema | ||
from ..schema import parse | ||
from ..schema.components.dashboard import dashboard_fields as fields | ||
|
||
from ._base_views import BaseListView | ||
from ._base_views import BaseDetailView | ||
from ._permissions import ProjectExecutePermission | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
class DashboardListAPI(BaseListView): | ||
schema = DashboardListSchema() | ||
permission_classes = [ProjectExecutePermission] | ||
http_method_names = ['get', 'post'] | ||
|
||
def _get(self, params: dict) -> dict: | ||
qs = Dashboard.objects.filter(project=params['project']).order_by('id') | ||
return database_qs(qs) | ||
|
||
def get_queryset(self) -> dict: | ||
params = parse(self.request) | ||
qs = Dashboard.objects.filter(project__id=params['project']) | ||
return qs | ||
|
||
def _post(self, params: dict) -> dict: | ||
# Does the project ID exist? | ||
project_id = params[fields.project] | ||
try: | ||
project = Project.objects.get(pk=project_id) | ||
except Exception as exc: | ||
log_msg = f'Provided project ID ({project_id}) does not exist' | ||
logger.error(log_msg) | ||
raise exc | ||
|
||
# Gather the dashboard file and verify it exists on the server in the right project | ||
dashboard_file = os.path.basename(params[fields.html_file]) | ||
dashboard_url = os.path.join(str(project_id), dashboard_file) | ||
dashboard_path = os.path.join(settings.MEDIA_ROOT, dashboard_url) | ||
if not os.path.exists(dashboard_path): | ||
log_msg = f'Provided dashboard ({dashboard_file}) does not exist in {settings.MEDIA_ROOT}' | ||
logging.error(log_msg) | ||
raise ValueError(log_msg) | ||
|
||
# Get the optional fields and to null if need be | ||
description = params.get(fields.description, None) | ||
categories = params.get(fields.categories, None) | ||
|
||
new_dashboard = Dashboard.objects.create( | ||
categories=categories, | ||
description=description, | ||
html_file=dashboard_path, | ||
name=params[fields.name], | ||
project=project) | ||
|
||
return {"message": f"Successfully created dashboard {new_dashboard.id}!", "id": new_dashboard.id} | ||
|
||
class DashboardDetailAPI(BaseDetailView): | ||
schema = DashboardDetailSchema() | ||
permission_classes = [ProjectExecutePermission] | ||
http_method_names = ['get', 'patch', 'delete'] | ||
|
||
def safe_delete(self, path: str) -> None: | ||
try: | ||
logger.info(f"Deleting {path}") | ||
os.remove(path) | ||
except: | ||
logger.warning(f"Could not remove {path}") | ||
|
||
def _delete(self, params: dict) -> dict: | ||
# Grab the dashboard object and delete it from the database | ||
dashboard = Dashboard.objects.get(pk=params['id']) | ||
html_file = dashboard.html_file | ||
dashboard.delete() | ||
|
||
# Delete the correlated file | ||
path = os.path.join(settings.MEDIA_ROOT, html_file.name) | ||
self.safe_delete(path=path) | ||
|
||
msg = 'Registered dashboard deleted successfully!' | ||
return {'message': msg} | ||
|
||
def _get(self, params): | ||
return database_qs(Dashboard.objects.filter(pk=params['id']))[0] | ||
|
||
@transaction.atomic | ||
def _patch(self, params) -> dict: | ||
dashboard_id = params["id"] | ||
obj = Dashboard.objects.get(pk=dashboard_id) | ||
|
||
name = params.get(fields.name, None) | ||
if name is not None: | ||
obj.name = name | ||
|
||
description = params.get(fields.description, None) | ||
if description is not None: | ||
obj.description = description | ||
|
||
categories = params.get(fields.categories, None) | ||
if categories is not None: | ||
obj.categories = categories | ||
|
||
html_file = params.get(fields.html_file, None) | ||
if html_file is not None: | ||
dashboard_file = os.path.basename(html_file) | ||
dashboard_url = os.path.join(str(obj.project.id), dashboard_file) | ||
dashboard_path = os.path.join(settings.MEDIA_ROOT, dashboard_url) | ||
if not os.path.exists(dashboard_path): | ||
log_msg = f'Provided dashboard ({dashboard_path}) does not exist' | ||
logging.error(log_msg) | ||
raise ValueError("Dashboard file does not exist in expected location.") | ||
|
||
delete_path = os.path.join(settings.MEDIA_ROOT, obj.html_file.name) | ||
self.safe_delete(path=delete_path) | ||
obj.html_file = dashboard_path | ||
|
||
obj.save() | ||
|
||
return {'message': f'Dashboard {dashboard_id} successfully updated!'} | ||
|
||
def get_queryset(self): | ||
""" Returns a queryset of all registered dashboard files | ||
""" | ||
return Dashboard.objects.all() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import logging | ||
import os | ||
import shutil | ||
|
||
from django.conf import settings | ||
|
||
from ..schema import SaveHTMLFileSchema | ||
from ..schema.components.html_file import html_file_fields as fields | ||
from ..download import download_file | ||
|
||
from ._base_views import BaseListView | ||
from ._permissions import ProjectExecutePermission | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
class SaveHTMLFileAPI(BaseListView): | ||
""" Saves a HTML file used for reports and dashboards | ||
""" | ||
|
||
schema = SaveHTMLFileSchema() | ||
permission_clases = [ProjectExecutePermission] | ||
http_method_names = ['post'] | ||
|
||
def _post(self, params: dict) -> dict: | ||
""" Saves the uploaded report file into the project's permanent storage | ||
""" | ||
|
||
# Verify the provided file has been uploaded | ||
upload_url = params[fields.upload_url] | ||
|
||
# Move the file to the right location using the provided name | ||
project_id = str(params[fields.project]) | ||
basename = os.path.basename(params[fields.name]) | ||
filename, extension = os.path.splitext(basename) | ||
new_filename = filename + extension | ||
final_path = os.path.join(settings.MEDIA_ROOT, project_id, new_filename) | ||
|
||
# Make sure there's not a duplicate of this file. | ||
file_index = -1 | ||
while os.path.exists(final_path): | ||
file_index += 1 | ||
new_filename = f'{filename}_{file_index}{extension}' | ||
final_path = os.path.join(settings.MEDIA_ROOT, project_id, new_filename) | ||
|
||
project_folder = os.path.dirname(final_path) | ||
os.makedirs(project_folder, exist_ok=True) | ||
|
||
# Download the file to the final path. | ||
download_file(upload_url, final_path) | ||
|
||
# Create the response back to the user | ||
new_url = os.path.join(project_id, new_filename) | ||
response = {fields.url: new_url} | ||
return response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
from types import SimpleNamespace | ||
|
||
dashboard_fields = SimpleNamespace( | ||
description="description", | ||
html_file="html_file", | ||
id="id", | ||
name="name", | ||
project="project", | ||
categories="categories") | ||
|
||
dashboard_post_properties = { | ||
dashboard_fields.name: { | ||
"type": "string", | ||
"description": "Name of dashboard" | ||
}, | ||
dashboard_fields.html_file: { | ||
"type": "string", | ||
"description": "Server URL to dashboard HTML file" | ||
}, | ||
dashboard_fields.description: { | ||
"type": "string", | ||
"description": "Description of dashboard" | ||
}, | ||
dashboard_fields.categories: { | ||
'type': 'array', | ||
'description': 'List of categories the dashboard belongs to', | ||
'items': {'type': 'string'}, | ||
}, | ||
} | ||
|
||
# Note: While project is required, it's part of the path parameter(s) | ||
dashboard_spec = { | ||
'type': 'object', | ||
'description': 'Register dashboard spec.', | ||
'properties': { | ||
**dashboard_post_properties, | ||
}, | ||
} | ||
|
||
dashboard = { | ||
"type": "object", | ||
"description": "Dashboard spec.", | ||
"properties": { | ||
dashboard_fields.id: { | ||
"type": "integer", | ||
"description": "Unique integer identifying the dashboard", | ||
}, | ||
dashboard_fields.project: { | ||
"type": "integer", | ||
"description": "Unique integer identifying the project associated with the dashboard", | ||
}, | ||
**dashboard_post_properties | ||
}, | ||
} |
Oops, something went wrong.