From 32d95db949af732e4dd43e26e83c311e1b107fef Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Thu, 6 Jun 2024 15:44:11 +0200 Subject: [PATCH 01/17] add: first meta --- djangoldp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangoldp/__init__.py b/djangoldp/__init__.py index efc86af2..a99e40eb 100644 --- a/djangoldp/__init__.py +++ b/djangoldp/__init__.py @@ -5,4 +5,4 @@ __version__ = '0.0.0' options.DEFAULT_NAMES += ( 'lookup_field', 'rdf_type', 'rdf_context', 'auto_author', 'owner_field', 'owner_urlid_field', 'view_set', 'container_path', 'permission_classes', 'serializer_fields', 'serializer_fields_exclude', 'empty_containers', - 'nested_fields', 'depth', 'permission_roles', 'inherit_permissions', 'public_field') + 'nested_fields', 'depth', 'permission_roles', 'inherit_permissions', 'public_field', 'static_version') -- GitLab From 69d0b962866061e3eaf32050021c4143af6854bd Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Fri, 7 Jun 2024 11:25:47 +0200 Subject: [PATCH 02/17] feature: generate models static version by command --- djangoldp/__init__.py | 2 +- .../commands/generate_static_content.py | 74 +++++++++++++++++++ djangoldp/urls.py | 6 +- djangoldp/views.py | 20 ++++- 4 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 djangoldp/management/commands/generate_static_content.py diff --git a/djangoldp/__init__.py b/djangoldp/__init__.py index a99e40eb..7f7595fa 100644 --- a/djangoldp/__init__.py +++ b/djangoldp/__init__.py @@ -5,4 +5,4 @@ __version__ = '0.0.0' options.DEFAULT_NAMES += ( 'lookup_field', 'rdf_type', 'rdf_context', 'auto_author', 'owner_field', 'owner_urlid_field', 'view_set', 'container_path', 'permission_classes', 'serializer_fields', 'serializer_fields_exclude', 'empty_containers', - 'nested_fields', 'depth', 'permission_roles', 'inherit_permissions', 'public_field', 'static_version') + 'nested_fields', 'depth', 'permission_roles', 'inherit_permissions', 'public_field', 'static_version', 'static_params') diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py new file mode 100644 index 00000000..667ab55c --- /dev/null +++ b/djangoldp/management/commands/generate_static_content.py @@ -0,0 +1,74 @@ +import os +import requests +import json +from django.core.management.base import BaseCommand +from django.conf import settings +from django.apps import apps +from urllib.parse import urlparse, urlunparse + +class Command(BaseCommand): + help = 'Generate static content for models with a specific meta attribute' + + def handle(self, *args, **kwargs): + output_dir = 'ssr' + if not os.path.exists(output_dir): + os.makedirs(output_dir, exist_ok=True) + + base_uri = getattr(settings, 'BASE_URL', '') + + for model in apps.get_models(): + if hasattr(model._meta, 'static_version'): + print(f"model: {model}") + container_path = model.get_container_path() + url = f'{base_uri}{container_path}' + print(f"current request url before adding params: {url}") + + if hasattr(model._meta, 'static_params'): + # static_params which is a json must be decomposed and added to the url as query parameters, first with ? then with & + url += '?' + for key, value in model._meta.static_params.items(): + url += f'{key}={value}&' + url = url[:-1] + + print(f"current request url after adding params: {url}") + response = requests.get(url) + + if response.status_code == 200: + content = response.text + content = self.update_ids(content, base_uri) + + filename = container_path[1:-1] + file_path = os.path.join(output_dir, f'{filename}.json') + + print(f"file_path: {file_path}") + with open(file_path, 'w') as f: + f.write(content) + self.stdout.write(self.style.SUCCESS(f'Successfully fetched and saved content for {model._meta.model_name} from {url}')) + else: + self.stdout.write(self.style.ERROR(f'Failed to fetch content from {url}: {response.status_code}')) + + def update_ids(self, content, base_uri): + try: + data = json.loads(content) + if isinstance(data, list): + for item in data: + self.update_item_id(item, base_uri) + elif isinstance(data, dict): + self.update_item_id(data, base_uri) + return json.dumps(data) + except json.JSONDecodeError as e: + self.stdout.write(self.style.ERROR(f'Failed to decode JSON: {e}')) + return content + + def update_item_id(self, item, base_uri): + if '@id' in item: + parsed_url = urlparse(item['@id']) + path = f'/ssr{parsed_url.path}' + item['@id'] = urlunparse((parsed_url.scheme, parsed_url.netloc, path, parsed_url.params, parsed_url.query, parsed_url.fragment)) + for key, value in item.items(): + if isinstance(value, dict): + self.update_item_id(value, base_uri) + elif isinstance(value, list): + for sub_item in value: + if isinstance(sub_item, dict): + self.update_item_id(sub_item, base_uri) \ No newline at end of file diff --git a/djangoldp/urls.py b/djangoldp/urls.py index 86682c5f..1cc3f17f 100644 --- a/djangoldp/urls.py +++ b/djangoldp/urls.py @@ -7,7 +7,8 @@ from django.urls import path, re_path, include from djangoldp.models import LDPSource, Model from djangoldp.permissions import ReadOnly from djangoldp.views import LDPSourceViewSet, WebFingerView, InboxView -from djangoldp.views import LDPViewSet +from djangoldp.views import LDPViewSet, serve_static_content + def __clean_path(path): '''ensures path is Django-friendly''' @@ -35,7 +36,8 @@ urlpatterns = [ re_path(r'^sources/(?P<federation>\w+)/', LDPSourceViewSet.urls(model=LDPSource, fields=['federation', 'urlid'], permission_classes=[ReadOnly], )), re_path(r'^\.well-known/webfinger/?$', WebFingerView.as_view()), - path('inbox/', InboxView.as_view()) + path('inbox/', InboxView.as_view()), + path('ssr/<str:filename>/', serve_static_content, name='serve_static_content'), ] if settings.ENABLE_SWAGGER_DOCUMENTATION: diff --git a/djangoldp/views.py b/djangoldp/views.py index 27f0400a..fa5cf7d4 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -5,7 +5,7 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist from django.db import IntegrityError, transaction -from django.http import JsonResponse, Http404 +from django.http import JsonResponse, Http404, HttpResponseNotFound from django.shortcuts import get_object_or_404 from django.urls import include, re_path, path from django.urls.resolvers import get_resolver @@ -31,6 +31,7 @@ from djangoldp.utils import is_authenticated_user from djangoldp.activities import ActivityQueueService, as_activitystream, ACTIVITY_SAVING_SETTING, ActivityPubService from djangoldp.activities.errors import ActivityStreamDecodeError, ActivityStreamValidationError import logging +import os logger = logging.getLogger('djangoldp') get_user_model()._meta.rdf_context = {"get_full_name": "rdfs:label"} @@ -615,3 +616,20 @@ class WebFingerView(View): def post(self, request, *args, **kwargs): return self.on_request(request) + + +def serve_static_content(request, filename): + file_path = os.path.join('ssr', filename) + # Transform file_path to valid filename by adding .json extension + if not file_path.endswith('.json'): + file_path += '.json' + + print(f"file_path: {file_path}") + if os.path.exists(file_path): + with open(file_path, 'r') as file: + content = file.read() + + json_content = json.loads(content) + return JsonResponse(json_content, safe=False, status=200, content_type='application/ld+json') + else: + return HttpResponseNotFound('File not found') -- GitLab From cb55310d4d3a87294590370ae825fccc1614be30 Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Fri, 7 Jun 2024 11:53:44 +0200 Subject: [PATCH 03/17] push: fix --- djangoldp/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangoldp/views.py b/djangoldp/views.py index fa5cf7d4..013c025d 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -630,6 +630,6 @@ def serve_static_content(request, filename): content = file.read() json_content = json.loads(content) - return JsonResponse(json_content, safe=False, status=200, content_type='application/ld+json') + return JsonResponse(json_content, safe=False, status=200, content_type='application/ld+json', headers={'Access-Control-Allow-Origin': '*'}) else: return HttpResponseNotFound('File not found') -- GitLab From 781acff75c8f46a54fa0734c26c89c8580b70b1e Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Fri, 7 Jun 2024 12:06:21 +0200 Subject: [PATCH 04/17] fix: simplify --- .../commands/generate_static_content.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py index 667ab55c..fb83d8ef 100644 --- a/djangoldp/management/commands/generate_static_content.py +++ b/djangoldp/management/commands/generate_static_content.py @@ -29,13 +29,13 @@ class Command(BaseCommand): for key, value in model._meta.static_params.items(): url += f'{key}={value}&' url = url[:-1] - + print(f"current request url after adding params: {url}") response = requests.get(url) if response.status_code == 200: content = response.text - content = self.update_ids(content, base_uri) + content = self.update_ids(content, base_uri, model._meta.model_name.lower()) filename = container_path[1:-1] file_path = os.path.join(output_dir, f'{filename}.json') @@ -47,28 +47,28 @@ class Command(BaseCommand): else: self.stdout.write(self.style.ERROR(f'Failed to fetch content from {url}: {response.status_code}')) - def update_ids(self, content, base_uri): + def update_ids(self, content, base_uri, model_name): try: data = json.loads(content) if isinstance(data, list): for item in data: - self.update_item_id(item, base_uri) + self.update_item_id(item, base_uri, model_name) elif isinstance(data, dict): - self.update_item_id(data, base_uri) + self.update_item_id(data, base_uri, model_name) return json.dumps(data) except json.JSONDecodeError as e: self.stdout.write(self.style.ERROR(f'Failed to decode JSON: {e}')) return content - def update_item_id(self, item, base_uri): - if '@id' in item: + def update_item_id(self, item, base_uri, model_name): + if '@id' in item and model_name in item['@id']: parsed_url = urlparse(item['@id']) path = f'/ssr{parsed_url.path}' item['@id'] = urlunparse((parsed_url.scheme, parsed_url.netloc, path, parsed_url.params, parsed_url.query, parsed_url.fragment)) for key, value in item.items(): if isinstance(value, dict): - self.update_item_id(value, base_uri) + self.update_item_id(value, base_uri, model_name) elif isinstance(value, list): for sub_item in value: if isinstance(sub_item, dict): - self.update_item_id(sub_item, base_uri) \ No newline at end of file + self.update_item_id(sub_item, base_uri, model_name) \ No newline at end of file -- GitLab From b00f39891fe83a6539403445a1cfe2340954505c Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Fri, 7 Jun 2024 12:21:07 +0200 Subject: [PATCH 05/17] revert: prev --- djangoldp/management/commands/generate_static_content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py index fb83d8ef..6e42515f 100644 --- a/djangoldp/management/commands/generate_static_content.py +++ b/djangoldp/management/commands/generate_static_content.py @@ -61,7 +61,7 @@ class Command(BaseCommand): return content def update_item_id(self, item, base_uri, model_name): - if '@id' in item and model_name in item['@id']: + if '@id' in item: parsed_url = urlparse(item['@id']) path = f'/ssr{parsed_url.path}' item['@id'] = urlunparse((parsed_url.scheme, parsed_url.netloc, path, parsed_url.params, parsed_url.query, parsed_url.fragment)) -- GitLab From 88ee5842b21cbf912dcfa99bdb234fcc292994f6 Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Fri, 7 Jun 2024 17:42:22 +0200 Subject: [PATCH 06/17] fix: proper recursion support and usage of paths --- .../commands/generate_static_content.py | 46 +++++++++++++++---- djangoldp/urls.py | 3 +- djangoldp/views.py | 4 +- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py index 6e42515f..1f032fb9 100644 --- a/djangoldp/management/commands/generate_static_content.py +++ b/djangoldp/management/commands/generate_static_content.py @@ -15,6 +15,7 @@ class Command(BaseCommand): os.makedirs(output_dir, exist_ok=True) base_uri = getattr(settings, 'BASE_URL', '') + max_depth = getattr(settings, 'MAX_RECURSION_DEPTH', 3) # Ad for model in apps.get_models(): if hasattr(model._meta, 'static_version'): @@ -31,11 +32,11 @@ class Command(BaseCommand): url = url[:-1] print(f"current request url after adding params: {url}") - response = requests.get(url) + response = requests.get(url, timeout=settings.SSR_REQUEST_TIMEOUT) if response.status_code == 200: content = response.text - content = self.update_ids(content, base_uri, model._meta.model_name.lower()) + content = self.update_ids_and_fetch_associated(content, base_uri, output_dir, 0, max_depth) filename = container_path[1:-1] file_path = os.path.join(output_dir, f'{filename}.json') @@ -47,28 +48,57 @@ class Command(BaseCommand): else: self.stdout.write(self.style.ERROR(f'Failed to fetch content from {url}: {response.status_code}')) - def update_ids(self, content, base_uri, model_name): + def update_ids_and_fetch_associated(self, content, base_uri, output_dir, depth, max_depth): + if depth > max_depth: + return content + try: data = json.loads(content) if isinstance(data, list): for item in data: - self.update_item_id(item, base_uri, model_name) + self.update_and_fetch_id(item, base_uri, output_dir, depth, max_depth) elif isinstance(data, dict): - self.update_item_id(data, base_uri, model_name) + self.update_and_fetch_id(data, base_uri, output_dir, depth, max_depth) return json.dumps(data) except json.JSONDecodeError as e: self.stdout.write(self.style.ERROR(f'Failed to decode JSON: {e}')) return content - def update_item_id(self, item, base_uri, model_name): + def update_and_fetch_id(self, item, base_uri, output_dir, depth, max_depth): if '@id' in item: parsed_url = urlparse(item['@id']) path = f'/ssr{parsed_url.path}' item['@id'] = urlunparse((parsed_url.scheme, parsed_url.netloc, path, parsed_url.params, parsed_url.query, parsed_url.fragment)) + associated_url = urlunparse((parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, parsed_url.query, parsed_url.fragment)) + + # Fetch associated content + try: + response = requests.get(associated_url, timeout=settings.SSR_REQUEST_TIMEOUT) + if response.status_code == 200: + associated_content = response.text + associated_content = self.update_ids_and_fetch_associated(associated_content, base_uri, output_dir, depth + 1, max_depth) + # associated_content = self.update_ids_and_fetch_associated(response.text, base_uri, output_dir) + # associated_content = self.update_and_fetch_id(response.text, base_uri, output_dir) + associated_file_path = path[1:-1] + '.json' + print(f"associated_file_path: {associated_file_path}") + associated_file_dir = os.path.dirname(associated_file_path) + print(f"associated_file_path: {associated_file_dir}") + if not os.path.exists(associated_file_dir): + os.makedirs(associated_file_dir) + with open(associated_file_path, 'w') as f: + f.write(associated_content) + self.stdout.write(self.style.SUCCESS(f'Successfully fetched and saved associated content for {associated_url}')) + else: + self.stdout.write(self.style.ERROR(f'Failed to fetch associated content from {associated_url}: {response.status_code}')) + except requests.exceptions.Timeout: + self.stdout.write(self.style.ERROR(f'Request to {associated_url} timed out')) + except requests.exceptions.RequestException as e: + self.stdout.write(self.style.ERROR(f'An error occurred: {e}')) + for key, value in item.items(): if isinstance(value, dict): - self.update_item_id(value, base_uri, model_name) + self.update_and_fetch_id(value, base_uri, output_dir, depth, max_depth) elif isinstance(value, list): for sub_item in value: if isinstance(sub_item, dict): - self.update_item_id(sub_item, base_uri, model_name) \ No newline at end of file + self.update_and_fetch_id(sub_item, base_uri, output_dir, depth, max_depth) \ No newline at end of file diff --git a/djangoldp/urls.py b/djangoldp/urls.py index 1cc3f17f..075dddec 100644 --- a/djangoldp/urls.py +++ b/djangoldp/urls.py @@ -37,7 +37,8 @@ urlpatterns = [ permission_classes=[ReadOnly], )), re_path(r'^\.well-known/webfinger/?$', WebFingerView.as_view()), path('inbox/', InboxView.as_view()), - path('ssr/<str:filename>/', serve_static_content, name='serve_static_content'), + # path('ssr/<str:filename>/', serve_static_content, name='serve_static_content'), + re_path(r'^ssr/(?P<path>.*)$', serve_static_content, name='serve_static_content'), ] if settings.ENABLE_SWAGGER_DOCUMENTATION: diff --git a/djangoldp/views.py b/djangoldp/views.py index 013c025d..867a658a 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -618,8 +618,8 @@ class WebFingerView(View): return self.on_request(request) -def serve_static_content(request, filename): - file_path = os.path.join('ssr', filename) +def serve_static_content(request, path): + file_path = os.path.join('ssr', path[:-1]) # Transform file_path to valid filename by adding .json extension if not file_path.endswith('.json'): file_path += '.json' -- GitLab From f055b560dd41dcf3c26376e9f1919d5091b2a034 Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Mon, 10 Jun 2024 10:51:37 +0200 Subject: [PATCH 07/17] fix: ids update --- .../commands/generate_static_content.py | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py index 1f032fb9..3b9151b6 100644 --- a/djangoldp/management/commands/generate_static_content.py +++ b/djangoldp/management/commands/generate_static_content.py @@ -15,7 +15,7 @@ class Command(BaseCommand): os.makedirs(output_dir, exist_ok=True) base_uri = getattr(settings, 'BASE_URL', '') - max_depth = getattr(settings, 'MAX_RECURSION_DEPTH', 3) # Ad + max_depth = getattr(settings, 'MAX_RECURSION_DEPTH', 5) # Ad for model in apps.get_models(): if hasattr(model._meta, 'static_version'): @@ -57,8 +57,10 @@ class Command(BaseCommand): if isinstance(data, list): for item in data: self.update_and_fetch_id(item, base_uri, output_dir, depth, max_depth) - elif isinstance(data, dict): + else: self.update_and_fetch_id(data, base_uri, output_dir, depth, max_depth) + + print(f"Content: {data}") return json.dumps(data) except json.JSONDecodeError as e: self.stdout.write(self.style.ERROR(f'Failed to decode JSON: {e}')) @@ -70,30 +72,34 @@ class Command(BaseCommand): path = f'/ssr{parsed_url.path}' item['@id'] = urlunparse((parsed_url.scheme, parsed_url.netloc, path, parsed_url.params, parsed_url.query, parsed_url.fragment)) associated_url = urlunparse((parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, parsed_url.query, parsed_url.fragment)) + associated_file_path = path[1:-1] + '.json' + print(f"associated_file_path: {associated_file_path}") + associated_file_dir = os.path.dirname(associated_file_path) + if not os.path.exists(associated_file_dir): + os.makedirs(associated_file_dir) - # Fetch associated content - try: - response = requests.get(associated_url, timeout=settings.SSR_REQUEST_TIMEOUT) - if response.status_code == 200: - associated_content = response.text - associated_content = self.update_ids_and_fetch_associated(associated_content, base_uri, output_dir, depth + 1, max_depth) - # associated_content = self.update_ids_and_fetch_associated(response.text, base_uri, output_dir) - # associated_content = self.update_and_fetch_id(response.text, base_uri, output_dir) - associated_file_path = path[1:-1] + '.json' - print(f"associated_file_path: {associated_file_path}") - associated_file_dir = os.path.dirname(associated_file_path) - print(f"associated_file_path: {associated_file_dir}") - if not os.path.exists(associated_file_dir): - os.makedirs(associated_file_dir) - with open(associated_file_path, 'w') as f: - f.write(associated_content) - self.stdout.write(self.style.SUCCESS(f'Successfully fetched and saved associated content for {associated_url}')) - else: - self.stdout.write(self.style.ERROR(f'Failed to fetch associated content from {associated_url}: {response.status_code}')) - except requests.exceptions.Timeout: - self.stdout.write(self.style.ERROR(f'Request to {associated_url} timed out')) - except requests.exceptions.RequestException as e: - self.stdout.write(self.style.ERROR(f'An error occurred: {e}')) + if not os.path.exists(associated_file_path): + # Fetch associated content + try: + response = requests.get(associated_url, timeout=settings.SSR_REQUEST_TIMEOUT) + if response.status_code == 200: + associated_content = self.update_ids_and_fetch_associated(response.text, base_uri, output_dir, depth + 1, max_depth) + print(f"associated_file_path: {associated_file_path}") + associated_file_dir = os.path.dirname(associated_file_path) + print(f"associated_file_path: {associated_file_dir}") + if not os.path.exists(associated_file_dir): + os.makedirs(associated_file_dir) + with open(associated_file_path, 'w') as f: + f.write(associated_content) + self.stdout.write(self.style.SUCCESS(f'Successfully fetched and saved associated content for {associated_url}')) + else: + self.stdout.write(self.style.ERROR(f'Failed to fetch associated content from {associated_url}: {response.status_code}')) + except requests.exceptions.Timeout: + self.stdout.write(self.style.ERROR(f'Request to {associated_url} timed out')) + except requests.exceptions.RequestException as e: + self.stdout.write(self.style.ERROR(f'An error occurred: {e}')) + else: + self.stdout.write(self.style.SUCCESS(f'Associated file already exists for {associated_url}, skipping fetch')) for key, value in item.items(): if isinstance(value, dict): -- GitLab From 09491ed51e28787d8f1c0245eb479f9fbf1819ed Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Mon, 10 Jun 2024 10:58:39 +0200 Subject: [PATCH 08/17] fix: timeout settings --- djangoldp/management/commands/generate_static_content.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py index 3b9151b6..a91ed244 100644 --- a/djangoldp/management/commands/generate_static_content.py +++ b/djangoldp/management/commands/generate_static_content.py @@ -15,7 +15,8 @@ class Command(BaseCommand): os.makedirs(output_dir, exist_ok=True) base_uri = getattr(settings, 'BASE_URL', '') - max_depth = getattr(settings, 'MAX_RECURSION_DEPTH', 5) # Ad + max_depth = getattr(settings, 'MAX_RECURSION_DEPTH', 5) + request_timeout = getattr(settings, 'SSR_REQUEST_TIMEOUT', 10) for model in apps.get_models(): if hasattr(model._meta, 'static_version'): @@ -32,7 +33,7 @@ class Command(BaseCommand): url = url[:-1] print(f"current request url after adding params: {url}") - response = requests.get(url, timeout=settings.SSR_REQUEST_TIMEOUT) + response = requests.get(url, timeout=request_timeout) if response.status_code == 200: content = response.text @@ -81,7 +82,7 @@ class Command(BaseCommand): if not os.path.exists(associated_file_path): # Fetch associated content try: - response = requests.get(associated_url, timeout=settings.SSR_REQUEST_TIMEOUT) + response = requests.get(associated_url, timeout=request_timeout) if response.status_code == 200: associated_content = self.update_ids_and_fetch_associated(response.text, base_uri, output_dir, depth + 1, max_depth) print(f"associated_file_path: {associated_file_path}") -- GitLab From 7d243012adfab240937202145fd9b94fd9777eb6 Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Mon, 10 Jun 2024 11:00:25 +0200 Subject: [PATCH 09/17] fix: prev --- djangoldp/management/commands/generate_static_content.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py index a91ed244..7b553797 100644 --- a/djangoldp/management/commands/generate_static_content.py +++ b/djangoldp/management/commands/generate_static_content.py @@ -6,6 +6,10 @@ from django.conf import settings from django.apps import apps from urllib.parse import urlparse, urlunparse +base_uri = getattr(settings, 'BASE_URL', '') +max_depth = getattr(settings, 'MAX_RECURSION_DEPTH', 5) +request_timeout = getattr(settings, 'SSR_REQUEST_TIMEOUT', 10) + class Command(BaseCommand): help = 'Generate static content for models with a specific meta attribute' @@ -14,9 +18,6 @@ class Command(BaseCommand): if not os.path.exists(output_dir): os.makedirs(output_dir, exist_ok=True) - base_uri = getattr(settings, 'BASE_URL', '') - max_depth = getattr(settings, 'MAX_RECURSION_DEPTH', 5) - request_timeout = getattr(settings, 'SSR_REQUEST_TIMEOUT', 10) for model in apps.get_models(): if hasattr(model._meta, 'static_version'): -- GitLab From 1140f26ca1d9cb022757153057ddb88560386fdc Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Mon, 10 Jun 2024 11:55:13 +0200 Subject: [PATCH 10/17] force: refetch --- .../commands/generate_static_content.py | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py index 7b553797..a668a542 100644 --- a/djangoldp/management/commands/generate_static_content.py +++ b/djangoldp/management/commands/generate_static_content.py @@ -80,28 +80,28 @@ class Command(BaseCommand): if not os.path.exists(associated_file_dir): os.makedirs(associated_file_dir) - if not os.path.exists(associated_file_path): + # if not os.path.exists(associated_file_path): # Fetch associated content - try: - response = requests.get(associated_url, timeout=request_timeout) - if response.status_code == 200: - associated_content = self.update_ids_and_fetch_associated(response.text, base_uri, output_dir, depth + 1, max_depth) - print(f"associated_file_path: {associated_file_path}") - associated_file_dir = os.path.dirname(associated_file_path) - print(f"associated_file_path: {associated_file_dir}") - if not os.path.exists(associated_file_dir): - os.makedirs(associated_file_dir) - with open(associated_file_path, 'w') as f: - f.write(associated_content) - self.stdout.write(self.style.SUCCESS(f'Successfully fetched and saved associated content for {associated_url}')) - else: - self.stdout.write(self.style.ERROR(f'Failed to fetch associated content from {associated_url}: {response.status_code}')) - except requests.exceptions.Timeout: - self.stdout.write(self.style.ERROR(f'Request to {associated_url} timed out')) - except requests.exceptions.RequestException as e: - self.stdout.write(self.style.ERROR(f'An error occurred: {e}')) - else: - self.stdout.write(self.style.SUCCESS(f'Associated file already exists for {associated_url}, skipping fetch')) + try: + response = requests.get(associated_url, timeout=request_timeout) + if response.status_code == 200: + associated_content = self.update_ids_and_fetch_associated(response.text, base_uri, output_dir, depth + 1, max_depth) + print(f"associated_file_path: {associated_file_path}") + associated_file_dir = os.path.dirname(associated_file_path) + print(f"associated_file_path: {associated_file_dir}") + if not os.path.exists(associated_file_dir): + os.makedirs(associated_file_dir) + with open(associated_file_path, 'w') as f: + f.write(associated_content) + self.stdout.write(self.style.SUCCESS(f'Successfully fetched and saved associated content for {associated_url}')) + else: + self.stdout.write(self.style.ERROR(f'Failed to fetch associated content from {associated_url}: {response.status_code}')) + except requests.exceptions.Timeout: + self.stdout.write(self.style.ERROR(f'Request to {associated_url} timed out')) + except requests.exceptions.RequestException as e: + self.stdout.write(self.style.ERROR(f'An error occurred: {e}')) + # else: + # self.stdout.write(self.style.SUCCESS(f'Associated file already exists for {associated_url}, skipping fetch')) for key, value in item.items(): if isinstance(value, dict): -- GitLab From 47ac4f980f489e1eb402ff88794d8fe7e43ff285 Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Mon, 10 Jun 2024 17:46:52 +0200 Subject: [PATCH 11/17] fix: cache-control --- djangoldp/views.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/djangoldp/views.py b/djangoldp/views.py index 867a658a..f88516df 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -630,6 +630,11 @@ def serve_static_content(request, path): content = file.read() json_content = json.loads(content) - return JsonResponse(json_content, safe=False, status=200, content_type='application/ld+json', headers={'Access-Control-Allow-Origin': '*'}) + return JsonResponse(json_content, safe=False, status=200, + content_type='application/ld+json', + headers={ + 'Access-Control-Allow-Origin': '*', + 'Cache-Control': 'public, max-age=3600', + }) else: return HttpResponseNotFound('File not found') -- GitLab From 388ab16c658b1dbadad49dc1aa675a69d3fd47c2 Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Fri, 21 Jun 2024 15:18:44 +0200 Subject: [PATCH 12/17] cleaning: views and urls --- .../management/commands/generate_static_content.py | 11 +++-------- djangoldp/urls.py | 1 - djangoldp/views.py | 1 - 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py index a668a542..3023a2db 100644 --- a/djangoldp/management/commands/generate_static_content.py +++ b/djangoldp/management/commands/generate_static_content.py @@ -62,7 +62,6 @@ class Command(BaseCommand): else: self.update_and_fetch_id(data, base_uri, output_dir, depth, max_depth) - print(f"Content: {data}") return json.dumps(data) except json.JSONDecodeError as e: self.stdout.write(self.style.ERROR(f'Failed to decode JSON: {e}')) @@ -73,22 +72,20 @@ class Command(BaseCommand): parsed_url = urlparse(item['@id']) path = f'/ssr{parsed_url.path}' item['@id'] = urlunparse((parsed_url.scheme, parsed_url.netloc, path, parsed_url.params, parsed_url.query, parsed_url.fragment)) + associated_url = urlunparse((parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, parsed_url.query, parsed_url.fragment)) associated_file_path = path[1:-1] + '.json' - print(f"associated_file_path: {associated_file_path}") associated_file_dir = os.path.dirname(associated_file_path) + if not os.path.exists(associated_file_dir): os.makedirs(associated_file_dir) - # if not os.path.exists(associated_file_path): - # Fetch associated content try: response = requests.get(associated_url, timeout=request_timeout) if response.status_code == 200: associated_content = self.update_ids_and_fetch_associated(response.text, base_uri, output_dir, depth + 1, max_depth) - print(f"associated_file_path: {associated_file_path}") associated_file_dir = os.path.dirname(associated_file_path) - print(f"associated_file_path: {associated_file_dir}") + if not os.path.exists(associated_file_dir): os.makedirs(associated_file_dir) with open(associated_file_path, 'w') as f: @@ -100,8 +97,6 @@ class Command(BaseCommand): self.stdout.write(self.style.ERROR(f'Request to {associated_url} timed out')) except requests.exceptions.RequestException as e: self.stdout.write(self.style.ERROR(f'An error occurred: {e}')) - # else: - # self.stdout.write(self.style.SUCCESS(f'Associated file already exists for {associated_url}, skipping fetch')) for key, value in item.items(): if isinstance(value, dict): diff --git a/djangoldp/urls.py b/djangoldp/urls.py index 075dddec..defa166b 100644 --- a/djangoldp/urls.py +++ b/djangoldp/urls.py @@ -37,7 +37,6 @@ urlpatterns = [ permission_classes=[ReadOnly], )), re_path(r'^\.well-known/webfinger/?$', WebFingerView.as_view()), path('inbox/', InboxView.as_view()), - # path('ssr/<str:filename>/', serve_static_content, name='serve_static_content'), re_path(r'^ssr/(?P<path>.*)$', serve_static_content, name='serve_static_content'), ] diff --git a/djangoldp/views.py b/djangoldp/views.py index f88516df..c90eef1e 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -624,7 +624,6 @@ def serve_static_content(request, path): if not file_path.endswith('.json'): file_path += '.json' - print(f"file_path: {file_path}") if os.path.exists(file_path): with open(file_path, 'r') as file: content = file.read() -- GitLab From 1507e34593d6fcb2d198d4d5c31cd227c11a4d79 Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Fri, 21 Jun 2024 16:09:39 +0200 Subject: [PATCH 13/17] adding: command documentation --- docs/commands.md | 41 +++++++++++++++++++++++++++++++++++++++++ docs/create_model.md | 3 ++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 docs/commands.md diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 00000000..357305d4 --- /dev/null +++ b/docs/commands.md @@ -0,0 +1,41 @@ +# Available commands + +## generate_static_content + +You can generate and make available at a /ssr/xxx URI a static copy of the AnonymousUser view of given models. +Those models need to be configured with the `static_version` and `static_params` Meta options like: + +```python +class Location(Model): + name = models.CharField(max_length=255) + address = models.CharField(max_length=255) + zip_code = models.IntegerField() + visible = models.BooleanField(default=False) + + class Meta: + # Allow generating a static version of the container view + static_version = 1 + + # Add some GET parameters to configure the selection of data + static_params = { + "search-fields": "visible", + "search-terms": True, + "search-method": "exact" + } +``` + +You will need additional settings defined either in your settings.yml or settings.py file: + +```yml +BASE_URL: 'http://localhost:8000/' +MAX_RECURSION_DEPTH: 10 # Default value: 5 +SSR_REQUEST_TIME: 20 # Default value 10 (seconds) +``` + +Then you can try it out by executing the following command: + +```sh +python manage.py generate_static_content +``` + +You can also set a cron task or a celery Task to launch this command in a regular basis. \ No newline at end of file diff --git a/docs/create_model.md b/docs/create_model.md index 9297ba52..50fffc9f 100644 --- a/docs/create_model.md +++ b/docs/create_model.md @@ -1,5 +1,5 @@ -### User model requirements +# User model requirements When implementing authentication in your own application, you have two options: @@ -469,3 +469,4 @@ MIDDLEWARE = [ ``` Notice that it will return only HTTP 200 Code. + -- GitLab From f973b5bdb7bc3df9bb821deac97d7d5d5ae0f2d7 Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Fri, 21 Jun 2024 16:14:56 +0200 Subject: [PATCH 14/17] update: comments --- .../management/commands/generate_static_content.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py index 3023a2db..9e2fe36e 100644 --- a/djangoldp/management/commands/generate_static_content.py +++ b/djangoldp/management/commands/generate_static_content.py @@ -21,19 +21,19 @@ class Command(BaseCommand): for model in apps.get_models(): if hasattr(model._meta, 'static_version'): - print(f"model: {model}") + print(f"Generating content for model: {model}") container_path = model.get_container_path() url = f'{base_uri}{container_path}' - print(f"current request url before adding params: {url}") + print(f"Current request url before adding params: {url}") if hasattr(model._meta, 'static_params'): - # static_params which is a json must be decomposed and added to the url as query parameters, first with ? then with & + # static_params are added to the url as query parameters url += '?' for key, value in model._meta.static_params.items(): url += f'{key}={value}&' url = url[:-1] - print(f"current request url after adding params: {url}") + print(f"Current request url after adding params: {url}") response = requests.get(url, timeout=request_timeout) if response.status_code == 200: @@ -43,7 +43,7 @@ class Command(BaseCommand): filename = container_path[1:-1] file_path = os.path.join(output_dir, f'{filename}.json') - print(f"file_path: {file_path}") + print(f"Output file_path: {file_path}") with open(file_path, 'w') as f: f.write(content) self.stdout.write(self.style.SUCCESS(f'Successfully fetched and saved content for {model._meta.model_name} from {url}')) -- GitLab From fb0968a43afc682c6b5e963a521aca15bc151b53 Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Fri, 21 Jun 2024 16:15:49 +0200 Subject: [PATCH 15/17] fix: comment --- djangoldp/management/commands/generate_static_content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py index 9e2fe36e..cdb44479 100644 --- a/djangoldp/management/commands/generate_static_content.py +++ b/djangoldp/management/commands/generate_static_content.py @@ -11,7 +11,7 @@ max_depth = getattr(settings, 'MAX_RECURSION_DEPTH', 5) request_timeout = getattr(settings, 'SSR_REQUEST_TIMEOUT', 10) class Command(BaseCommand): - help = 'Generate static content for models with a specific meta attribute' + help = 'Generate static content for models having the static_version meta attribute set to 1/true' def handle(self, *args, **kwargs): output_dir = 'ssr' -- GitLab From 21d0605c7e460f69eee3e6e6cf0a8d32ec9df0e5 Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Fri, 21 Jun 2024 16:22:53 +0200 Subject: [PATCH 16/17] switch: to jsonld extension --- djangoldp/management/commands/generate_static_content.py | 4 ++-- djangoldp/views.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/djangoldp/management/commands/generate_static_content.py b/djangoldp/management/commands/generate_static_content.py index cdb44479..2cce19e3 100644 --- a/djangoldp/management/commands/generate_static_content.py +++ b/djangoldp/management/commands/generate_static_content.py @@ -41,7 +41,7 @@ class Command(BaseCommand): content = self.update_ids_and_fetch_associated(content, base_uri, output_dir, 0, max_depth) filename = container_path[1:-1] - file_path = os.path.join(output_dir, f'{filename}.json') + file_path = os.path.join(output_dir, f'{filename}.jsonld') print(f"Output file_path: {file_path}") with open(file_path, 'w') as f: @@ -74,7 +74,7 @@ class Command(BaseCommand): item['@id'] = urlunparse((parsed_url.scheme, parsed_url.netloc, path, parsed_url.params, parsed_url.query, parsed_url.fragment)) associated_url = urlunparse((parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, parsed_url.query, parsed_url.fragment)) - associated_file_path = path[1:-1] + '.json' + associated_file_path = path[1:-1] + '.jsonld' associated_file_dir = os.path.dirname(associated_file_path) if not os.path.exists(associated_file_dir): diff --git a/djangoldp/views.py b/djangoldp/views.py index c90eef1e..4a644752 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -621,8 +621,8 @@ class WebFingerView(View): def serve_static_content(request, path): file_path = os.path.join('ssr', path[:-1]) # Transform file_path to valid filename by adding .json extension - if not file_path.endswith('.json'): - file_path += '.json' + if not file_path.endswith('.jsonld'): + file_path += '.jsonld' if os.path.exists(file_path): with open(file_path, 'r') as file: -- GitLab From 8292e068288a550e1b111d334d3ed5983c45ab15 Mon Sep 17 00:00:00 2001 From: autodeploy <benoit@startinblox.com> Date: Fri, 21 Jun 2024 16:39:30 +0200 Subject: [PATCH 17/17] fix: remove useless message --- djangoldp/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/djangoldp/views.py b/djangoldp/views.py index 4a644752..40897690 100644 --- a/djangoldp/views.py +++ b/djangoldp/views.py @@ -620,7 +620,6 @@ class WebFingerView(View): def serve_static_content(request, path): file_path = os.path.join('ssr', path[:-1]) - # Transform file_path to valid filename by adding .json extension if not file_path.endswith('.jsonld'): file_path += '.jsonld' -- GitLab