Skip to content

Commit

Permalink
Merge pull request #45 from geosolutions-it/subsite_exclusive
Browse files Browse the repository at this point in the history
Add the exclusive keyword
  • Loading branch information
mattiagiupponi authored Dec 9, 2024
2 parents 44263a0 + e2abe20 commit f1fcd4f
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 33 deletions.
54 changes: 42 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,11 @@ Add in the geonode settings the following code
```
ENABLE_SUBSITE_CUSTOM_THEMES = True
INSTALLED_APPS += ("subsites",)
ENABLE_CATALOG_HOME_REDIRECTS_TO = True/False
ENABLE_CATALOG_HOME_REDIRECTS_TO = False
SUBSITE_READ_ONLY = True/False # return download_resourcebase and view resourcebase as permissions
SUBSITE_HIDE_EXCLUSIVE_FROM_SPECIFIC_API = False # If TRUE will hide the `subsite_exclusive` resources also from the detailed endpoint `/documents`, `/maps`, `/datasets`, '/geoapps`
```

`ENABLE_SUBSITE_CUSTOM_THEMES:` Enable the subsite login inside the app

## Include URLS

include the following into the project URLs.py

```python
url(r"", include("subsites.urls")),
```


## How to configure a subsite

The subsite are configurable ONLY via django admin
Expand Down Expand Up @@ -70,6 +61,45 @@ Region selected for subsite1 -> Italy
means that only the resources with associated the keyword `key1` and as region `Italy` are going to be returned
```

## Exclusive keyword

During the app initialization the subsite will automatically generate a keyword named `subsite_exclusive`. Each resource with this keyword assigned, will be escluded from the global catalogue (this is valid also for the API/v2 `/resources`, `/datasets`, `/documents`, `/maps`, `/geoapps` )

**NOTE:** The `subsite_exclusive` keyword is used to exclude a resource from the global catalog. This keyword is commonly applied to all resources. If a resource needs to be accessible only within a specific subsite, utilize the additional configuration provided by that subsite to filter it out from other subsites.

For example:

```
resource1 -> no keyword
resource2 -> keyword1 assinged
resource3 -> subsite_exclusive keyword assigned
Call -> http://localhost:8000/#/
- will return resource1 and resource2
```

Via API/v2 `/resources`, `/datasets`, `/documents`, `/maps`, `/geoapps` to hide the resources marked as `subsite_exclusive` enable the following setting:
```
SUBSITE_HIDE_EXCLUSIVE_FROM_SPECIFIC_API = True
```

If enabled, is possible to return all the value even if the `subsite_exclusive` keyword is set
For example:

```
resource1 -> no keyword
resource2 -> keyword1 assinged
resource3 -> subsite_exclusive keyword assigned
Call -> http://localhost:8000/api/v2/resources/
- will return resource1 and resource2
Call -> http://localhost:8000/api/v2/resources/?return_all=true
- will return resource1, resource2 and resource3
```


# Override Subsite template

Follows an example folder about how-to organize the subsite template folder to be able to have a custom template for each subsite.
Expand Down
24 changes: 20 additions & 4 deletions subsites/apps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.apps import AppConfig
import urllib.parse
from django.conf import settings
from django.urls import include, re_path


class AppConfig(AppConfig):
Expand All @@ -10,17 +11,15 @@ def ready(self):
"""Finalize setup"""
run_setup_hooks()
super(AppConfig, self).ready()
post_ready_action()


def run_setup_hooks(*args, **kwargs):
"""
Run basic setup configuration for the importer app.
Here we are overriding the upload API url
"""

# from geonode.api.urls import router
import os
from django.conf import settings

LOCAL_ROOT = os.path.abspath(os.path.dirname(__file__))

Expand All @@ -30,8 +29,25 @@ def run_setup_hooks(*args, **kwargs):
"subsites.context_processors.resource_urls",
]


def post_ready_action():

from geonode.urls import urlpatterns
from subsites.core_api import core_api_router

urlpatterns += [re_path(r"", include("subsites.urls"))]
urlpatterns.insert(
0,
re_path(r"^api/v2/", include(core_api_router.urls)),
)
settings.CACHES["subsite_cache"] = {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"TIMEOUT": 300,
"OPTIONS": {"MAX_ENTRIES": 10000},
}

try:
from geonode.base.models import HierarchicalKeyword
HierarchicalKeyword.objects.get_or_create(name="subsite_exclusive", slug="subsite_exclusive", depth=1)
except:
pass
16 changes: 16 additions & 0 deletions subsites/core_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from subsites import views
from dynamic_rest import routers
from django.conf import settings

core_api_router = routers.DynamicRouter()


core_api_router.register(
r"resources", views.OverrideResourceBaseViewSet, "base-resources"
)

if getattr(settings, "SUBSITE_HIDE_EXCLUSIVE_FROM_SPECIFIC_API", False):
core_api_router.register(r"documents", views.OverrideDocumentViewSet, "documents")
core_api_router.register(r"datasets", views.OverrideDatasetViewSet, "datasets")
core_api_router.register(r"maps", views.OverrideMapViewSet, "maps")
core_api_router.register(r"geoapps", views.OverrideGeoAppViewSet, "geoapps")
35 changes: 31 additions & 4 deletions subsites/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from geonode.people.api.serializers import UserSerializer
from geonode.base.api.serializers import ResourceBaseSerializer
from geonode.base.api.serializers import (
ResourceBaseSerializer,
)
from subsites.utils import extract_subsite_slug_from_request
from geonode.documents.api.serializers import DocumentSerializer
from geonode.geoapps.api.serializers import GeoAppSerializer
Expand All @@ -11,6 +13,7 @@
OWNER_RIGHTS,
)
from geonode.base.models import ResourceBase
from geonode.utils import build_absolute_uri
import itertools
from rest_framework.exceptions import NotFound

Expand All @@ -21,6 +24,14 @@ def to_representation(self, instance):
return apply_subsite_changes(data, self.context["request"], instance)


def fixup_linked_resources(resources, subsite):
for resource in resources:
resource["detail_url"] = resource["detail_url"].replace(
"/catalogue/", f"/{subsite}/catalogue/"
)
return resources


def apply_subsite_changes(data, request, instance):
subsite = extract_subsite_slug_from_request(request)
if not subsite:
Expand All @@ -29,6 +40,11 @@ def apply_subsite_changes(data, request, instance):
data["detail_url"] = data["detail_url"].replace(
"catalogue/", f"{subsite}/catalogue/"
)
if "embed_url" in data:
data["embed_url"] = build_absolute_uri(
f"/{subsite}{instance.get_real_instance().embed_url}"
)

# checking users perms based on the subsite_one
if "perms" in data and isinstance(instance, ResourceBase):
if getattr(settings, "SUBSITE_READ_ONLY", False):
Expand Down Expand Up @@ -63,9 +79,20 @@ def apply_subsite_changes(data, request, instance):
data["download_url"] = None
data["download_urls"] = None

if not subsite.can_add_resource and data.get('perms', None):
_perms_list = list(data['perms'])
data['perms'] = [perm for perm in _perms_list if perm != 'add_resource']
if not subsite.can_add_resource and data.get("perms", None):
_perms_list = list(data["perms"])
data["perms"] = [perm for perm in _perms_list if perm != "add_resource"]

# fixup linked resources
if "linked_resources" in data:
data["linked_resources"] = {
"linked_to": fixup_linked_resources(
data["linked_resources"]["linked_to"], subsite=subsite
),
"linked_by": fixup_linked_resources(
data["linked_resources"]["linked_by"], subsite=subsite
),
}

return data

Expand Down
94 changes: 94 additions & 0 deletions subsites/templates/geonode-mapstore-client/_geonode_config.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,97 @@
createLayer = false
{% endif %}
{% endblock %}

{% block override_local_config %}

{% load_subsite_info request as subsite_slug%}

{% if subsite_slug %}
<script>
window.__GEONODE_CONFIG__.overrideLocalConfig = function(localConfig) {
// this function must return always a valid localConfig json object
// update services URL for subsite
localConfig.plugins.catalogue = localConfig.plugins.catalogue.map(
(item) => {
if (item.name == 'ResourcesGrid') {
let custom_menuitems = [
{
labelId: 'gnhome.addResource',
disableIf: "{(state('settings') && state('settings').isMobile) || !(state('user') && state('user').perms && state('user').perms.includes('add_resource'))}",
type: 'dropdown',
variant: 'primary',
responsive: true,
noCaret: true,
items: [
{
labelId: 'gnhome.uploadDataset',
value: 'layer',
type: 'link',
href: '{context.getCataloguePath("/catalogue/#/upload/dataset")}'
},
{
labelId: 'gnhome.uploadDocument',
value: 'document',
type: 'link',
href: '{context.getCataloguePath("/catalogue/#/upload/document")}'
},
{
labelId: 'gnhome.createDataset',
value: 'layer',
type: 'link',
href: '/createlayer/',
disableIf: "{(state('settings') && state('settings').createLayer) ? false : true}"
},
{
labelId: 'gnhome.createMap',
value: 'map',
type: 'link',
href: '{context.getCataloguePath("/catalogue/#/map/new")}'
},
{
labelId: 'gnhome.createGeostory',
value: 'geostory',
type: 'link',
href: '{context.getCataloguePath("/catalogue/#/geostory/new")}'
},
{
labelId: 'gnhome.createDashboard',
value: 'dashboard',
type: 'link',
href: '{context.getCataloguePath("/catalogue/#/dashboard/new")}'
},
{
labelId: 'gnhome.remoteServices',
value: 'remote',
type: 'link',
href: '/{{subsite}}/services/?limit=5'
}
]
},
{
type: 'divider'
}
]
item.cfg.allPage = {"menuItems": custom_menuitems}
item.cfg.menuItems = custom_menuitems
//debugger;
item.cfg.datasetsPage.menuItems = item.cfg.datasetsPage.menuItems.map((menuItem) => {
menuItem.items = menuItem.items.map((element) => {
if (!element['href'].includes("context.getCataloguePath")) {
//debugger;
element['href'] = "{context.getCataloguePath('/{{subsite_slug}}" + element['href'] + "')}"
//element['href'].replace("context.getCataloguePath('", "context.getCataloguePath('/{{subsite_slug}}")
}
return element
})
return menuItem
})
}
return item
});

return localConfig;
};
</script>
{% endif %}
{% endblock %}
45 changes: 42 additions & 3 deletions subsites/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ def test_subsite_can_add_resource_is_false(self):
self.client.login(username="admin", password="admin")
response = self.client.get(
reverse(
"subsite_users-detail",
"users-detail",
args=[self.subsite_datasets.slug, admin.id]
)
)
Expand All @@ -404,7 +404,7 @@ def test_subsite_can_add_resource_is_true(self):
self.client.login(username="admin", password="admin")
response = self.client.get(
reverse(
"subsite_users-detail",
"users-detail",
args=[self.subsite_datasets.slug, admin.id]
)
)
Expand Down Expand Up @@ -453,8 +453,47 @@ def test_perms_compact_for_subsite(self):
self.assertEqual(200, response.status_code)
perms = response.json().get('resource')['perms']
# only download and view are returned since the can_add_resource is FALSE by default
self.assertListEqual(['download_resourcebase', 'view_resourcebase'], perms)
self.assertSetEqual({'download_resourcebase', 'view_resourcebase'}, set(perms))

# updating the can_add_resource
self.subsite_japan.can_add_resource = True
self.subsite_japan.save()

def test_calling_home_should_return_all_resources(self):
"""
If no resources has the subsite_exclusive keyword, all the resources
should be returned in the catalog home
"""
url = reverse('base-resources-list')
response = self.client.get(url)
self.assertTrue(response.json()['total'] == 6)

def test_calling_home_should_exclude_subsite_only_resources(self):
"""
The resources with keyword subsite_exclusive should be removed from the
default catalog view
"""
dataset = create_single_dataset("this_will_be_exclusive")
kw, _ = HierarchicalKeyword.objects.get_or_create(slug="subsite_exclusive")
dataset.keywords.add(kw)
dataset.save()
url = reverse('base-resources-list')
response = self.client.get(url)
# should be invisible to the default base resource list
self.assertTrue(response.json()['total'] == 6)
dataset.delete()

def test_calling_home_should_return_even_the_exclusive_if_requested(self):
"""
The resources with keyword subsite_exclusive should be removed from the
default catalog view
"""
dataset = create_single_dataset("this_will_be_exclusive")
kw, _ = HierarchicalKeyword.objects.get_or_create(slug="subsite_exclusive")
dataset.keywords.add(kw)
dataset.save()
url = reverse('base-resources-list')
response = self.client.get(f"{url}?return_all=true")
# should be invisible to the default base resource list
self.assertTrue(response.json()['total'] == 7)
dataset.delete()
Loading

0 comments on commit f1fcd4f

Please sign in to comment.