Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend LOVE manager routing system for subpath app serving #196

Merged
merged 10 commits into from
Jul 18, 2023
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Version History
===============

v5.14.0
--------

* Extend LOVE manager routing system for subpath app serving `<https://github.com/lsst-ts/LOVE-manager/pull/196>`_

v5.13.0
--------

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ All these variables are initialized with default variables defined in :code:`.en
- `LOVE_SITE`: defines the site name of the LOVE system. This value is used to identify the LOVE system in the LOVE-manager.
- `REMOTE_STORAGE`: defines if remote storage is used. If this variable is defined, then the LOVE-manager will connect to the LFA to upload files. If not defined, then the LOVE-manager will store the files locally.
- `COMMANDING_PERMISSION_TYPE`: defines the type of permission to use for commanding. Currently two options are available: `user` and `location`. If `user` is used, then requests from users with `api.command.execute_command` permission are allowed. If `location` is used, then only requests from the configured location of control will be allowed. If not defined, then `user` will be used.
- `URL_SUBPATH`: defines the path where the LOVE-manager will be served. If not defined, then requests will be served from the root path `/`. Note: the application has its own routing system, so this variable must be thought of as a prefix to the application's routes.

# Local load for development

Expand Down
2 changes: 1 addition & 1 deletion manager/api/fixtures/initial_data_remote_tucson.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"update_timestamp": "2020-12-29T14:21:31.040Z",
"user": 1,
"file_name": "default.json",
"config_file": "http://love.tu.lsst.org/media/configs/default.json"
"config_file": "https://tucson-teststand.lsst.codes/love/manager/media/configs/default.json"
}
},
{
Expand Down
23 changes: 14 additions & 9 deletions manager/manager/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/

# Define the URL subpath for this application when deployed:
FORCE_SCRIPT_NAME = os.environ.get("URL_SUBPATH")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could set the default value here as "" so that the if FORCE_SCRIPT_NAME checks become unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to do that in a beginning, but then I realized this is a Django setting variable, thus it is thought to be None if no string is defined. I did't try this out, but I think if set "" as default value, some things could stop working.


# Define the type of storage to use for media files: remote or local
DEFAULT_FILE_STORAGE = (
"manager.utils.RemoteStorage"
if os.environ.get("REMOTE_STORAGE")
Expand Down Expand Up @@ -181,7 +185,11 @@

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = "/manager/static/"

STATIC_URL = (
f"{FORCE_SCRIPT_NAME}/manager/static/" if FORCE_SCRIPT_NAME else "/manager/static/"
)

"""URL to access Django static files (`string`)"""

STATIC_ROOT = os.path.join(BASE_DIR, "static")
Expand All @@ -197,15 +205,12 @@
os.path.join(BASE_DIR, "static_files"),
]

MEDIA_URL = "/media/"
MEDIA_URL = (
f"{FORCE_SCRIPT_NAME}/manager/media/" if FORCE_SCRIPT_NAME else "/manager/media/"
)
"""URL for media files access (`string`)"""

if TESTING:
MEDIA_BASE = os.path.join(BASE_DIR, "ui_framework", "tests")
MEDIA_ROOT = os.path.join(BASE_DIR, "ui_framework", "tests", "media")
else:
MEDIA_BASE = BASE_DIR
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_BASE = BASE_DIR
MEDIA_ROOT = os.path.join(BASE_DIR, "media")

# Channels
ASGI_APPLICATION = "manager.routing.application"
Expand Down
9 changes: 6 additions & 3 deletions manager/manager/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from rest_framework import permissions
from django.conf import settings

schema_view = get_schema_view(
openapi.Info(
Expand All @@ -38,7 +37,8 @@
path("manager/admin/", admin.site.urls),
path("manager/test/", TemplateView.as_view(template_name="test.html")),
path(
"manager/login/", TemplateView.as_view(template_name="registration/login.html")
"manager/login/",
TemplateView.as_view(template_name="registration/login.html"),
),
path("manager/api/", include("api.urls")),
path("manager/ui_framework/", include("ui_framework.urls")),
Expand All @@ -57,5 +57,8 @@
schema_view.with_ui("redoc", cache_timeout=0),
name="schema-redoc",
),
path("manager/schema_validation/", TemplateView.as_view(template_name="test.html")),
path(
"manager/schema_validation/",
TemplateView.as_view(template_name="test.html"),
),
]
24 changes: 21 additions & 3 deletions manager/manager/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ class RemoteStorage(Storage):
PREFIX_THUMBNAIL = "thumbnails/"
PREFIX_CONFIG = "configs/"

ALLOWED_FILE_TYPES = ["image/png", "image/jpeg", "image/jpg", "application/json"]
ALLOWED_FILE_TYPES = [
"image/png",
"image/jpeg",
"image/jpg",
"application/json",
"binary/octet-stream",
]

def __init__(self, location=None):
self.location = f"http://{os.environ.get('COMMANDER_HOSTNAME')}:{os.environ.get('COMMANDER_PORT')}/lfa"
Expand All @@ -78,21 +84,33 @@ def _open(self, name, mode="rb"):

# Validate name is a remote url
self._validate_LFA_url(name)

# Make request to remote server
response = requests.get(name)
if response.status_code != 200:
raise FileNotFoundError(f"Error requesting file at: {name}.")

# Create temporary file to store the response
tf = TemporaryFile()

# If request is for thumbnail (image file)
if response.headers.get("content-type") in RemoteStorage.ALLOWED_FILE_TYPES[:3]:
if (
response.headers.get("content-type") in RemoteStorage.ALLOWED_FILE_TYPES[:3]
) or response.headers.get("content-type") == RemoteStorage.ALLOWED_FILE_TYPES[
4
]:
byte_encoded_response = response.content
tf.write(byte_encoded_response)
# Before sending the file, we need to reset the file pointer to the beginning
tf.seek(0)
return tf

# If request is for config files (json file)
if response.headers.get("content-type") == RemoteStorage.ALLOWED_FILE_TYPES[3]:
if (
response.headers.get("content-type") == RemoteStorage.ALLOWED_FILE_TYPES[3]
or response.headers.get("content-type")
== RemoteStorage.ALLOWED_FILE_TYPES[4]
):
json_response = response.json()
byte_encoded_response = json.dumps(json_response).encode("ascii")
tf.write(byte_encoded_response)
Expand Down
6 changes: 5 additions & 1 deletion manager/runserver.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ fi
python manage.py loaddata ui_framework/fixtures/initial_data_${love_site}.json

echo -e "\nStarting server"
daphne -b 0.0.0.0 -p 8000 manager.asgi:application
if [ -z ${URL_SUBPATH} ]; then
daphne -b 0.0.0.0 -p 8000 manager.asgi:application
else
daphne --root-path=${URL_SUBPATH} -b 0.0.0.0 -p 8000 manager.asgi:application
fi
6 changes: 5 additions & 1 deletion manager/subscription/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
from django.conf.urls import url
from subscription.auth import TokenAuthMiddleware
from .consumers import SubscriptionConsumer
from django.conf import settings


URL_PREFIX = settings.FORCE_SCRIPT_NAME[1:] + "/" if settings.FORCE_SCRIPT_NAME else ""

websocket_urlpatterns = [
url(
r"^manager(.*?)/ws/subscription/?$",
rf"^{URL_PREFIX}manager(.*?)/ws/subscription/?$",
TokenAuthMiddleware(SubscriptionConsumer.as_asgi()),
),
]
Expand Down
9 changes: 6 additions & 3 deletions manager/ui_framework/tests/tests_view_thumbnail.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from ui_framework.models import View
from unittest import mock
from api.models import Token
from manager import settings
from django.conf import settings


@override_settings(DEBUG=True)
Expand Down Expand Up @@ -139,7 +139,9 @@ def test_new_view(self):

# - thumbnail url
view = View.objects.get(name="view name")
self.assertEqual(view.thumbnail.url, "/media/thumbnails/view_1.png")
self.assertEqual(
view.thumbnail.url, f"{settings.MEDIA_URL}thumbnails/view_1.png"
)

# - expected response data
expected_response = {
Expand All @@ -152,7 +154,8 @@ def test_new_view(self):
self.assertEqual(response.data, expected_response)

# - stored file content
file_url = settings.MEDIA_BASE + view.thumbnail.url
thumbnail_url = view.thumbnail.url.replace("/manager/media/", "/")
file_url = settings.MEDIA_ROOT + thumbnail_url
expected_url = mock_location + ".png"
self.assertTrue(
filecmp.cmp(file_url, expected_url),
Expand Down