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

[Fixes #12627] Include assets inside B/R #12628

Merged
merged 11 commits into from
Oct 7, 2024
1 change: 1 addition & 0 deletions geonode/assets/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def get_default_handler(self) -> AssetHandlerInterface:
return self._default_handler

def get_handler(self, asset):
asset = asset.get_real_instance() if isinstance(asset, Asset) else asset
asset_cls = asset if isinstance(asset, type) else asset.__class__
ret = self._registry.get(asset_cls, None)
if not ret:
Expand Down
16 changes: 16 additions & 0 deletions geonode/assets/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

class IndexLocalLinkUrlHandler:
def get_link_url(self, asset: LocalAsset):
asset = asset.get_real_instance()

Check warning on line 31 in geonode/assets/local.py

View check run for this annotation

Codecov / codecov/patch

geonode/assets/local.py#L31

Added line #L31 was not covered by tests
mattiagiupponi marked this conversation as resolved.
Show resolved Hide resolved
if not isinstance(asset, LocalAsset):
raise TypeError("Only localasset are allowed")

Check warning on line 33 in geonode/assets/local.py

View check run for this annotation

Codecov / codecov/patch

geonode/assets/local.py#L33

Added line #L33 was not covered by tests
return build_absolute_uri(reverse("assets-link", args=(asset.pk,))) + f"/{os.path.basename(asset.location[0])}"


Expand Down Expand Up @@ -76,6 +79,7 @@
Removes the files related to an Asset.
Only files within the Assets directory are removed
"""
asset = self.__force_real_instance(asset)
if self._are_files_managed(asset):
logger.info(f"Removing files for asset {asset.pk}")
base = self._get_managed_dir(asset)
Expand All @@ -85,6 +89,7 @@
logger.info(f"Not removing unmanaged files for asset {asset.pk}")

def replace_data(self, asset: LocalAsset, files: list):
asset = self.__force_real_instance(asset)
self.remove_data(asset)
asset.location = files
asset.save()
Expand Down Expand Up @@ -123,6 +128,7 @@

def clone(self, source: LocalAsset) -> LocalAsset:
# get a new asset instance to be edited and stored back
source = self.__force_real_instance(source)
asset = LocalAsset.objects.get(pk=source.pk)

# only copy files if they are managed
Expand Down Expand Up @@ -204,12 +210,22 @@

return managed_dir

@classmethod
def __force_real_instance(cls, asset):
asset = asset.get_real_instance()
if not isinstance(asset, LocalAsset):
raise TypeError(f"Real instance of asset {asset} is not {cls.handled_asset_class()}")

Check warning on line 217 in geonode/assets/local.py

View check run for this annotation

Codecov / codecov/patch

geonode/assets/local.py#L217

Added line #L217 was not covered by tests
return asset


class LocalAssetDownloadHandler(AssetDownloadHandlerInterface):

def create_response(
self, asset: LocalAsset, attachment: bool = False, basename: str = None, path: str = None
) -> HttpResponse:
asset = asset.get_real_instance()
mattiagiupponi marked this conversation as resolved.
Show resolved Hide resolved
if not isinstance(asset, LocalAsset):
raise TypeError("Only localasset are allowed")

Check warning on line 228 in geonode/assets/local.py

View check run for this annotation

Codecov / codecov/patch

geonode/assets/local.py#L228

Added line #L228 was not covered by tests
if not asset.location:
return HttpResponse("Asset does not contain any data", status=500)

Expand Down
14 changes: 13 additions & 1 deletion geonode/assets/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
from django.contrib.auth import get_user_model


class AssetPolymorphicManager(PolymorphicManager):
"""
This override is required for the dump procedure.
Otherwise django is not able to dump the base objects
and will be upcasted to polymorphic models
https://github.com/jazzband/django-polymorphic/blob/cfd49b26d580d99b00dcd43a02409ce439a2c78f/polymorphic/base.py#L161-L175
"""

def get_queryset(self):
return super().get_queryset().non_polymorphic()


class Asset(PolymorphicModel):
"""
A generic data linked to a ResourceBase
Expand All @@ -16,7 +28,7 @@ class Asset(PolymorphicModel):
owner = models.ForeignKey(get_user_model(), null=False, blank=False, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)

objects = PolymorphicManager()
objects = AssetPolymorphicManager()

class Meta:
verbose_name_plural = "Assets"
Expand Down
10 changes: 5 additions & 5 deletions geonode/assets/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def test_creation_and_delete_data_cloned(self):
asset.save()
self.assertIsInstance(asset, LocalAsset)

reloaded = Asset.objects.get(pk=asset.pk)
reloaded = LocalAsset.objects.get(pk=asset.pk)
self.assertIsNotNone(reloaded)
self.assertIsInstance(reloaded, LocalAsset)
file = reloaded.location[0]
Expand Down Expand Up @@ -103,7 +103,7 @@ def test_creation_and_delete_data_external(self):
asset.save()
self.assertIsInstance(asset, LocalAsset)

reloaded = Asset.objects.get(pk=asset.pk)
reloaded = LocalAsset.objects.get(pk=asset.pk)
self.assertIsNotNone(reloaded)
self.assertIsInstance(reloaded, LocalAsset)
file = reloaded.location[0]
Expand All @@ -128,7 +128,7 @@ def test_clone_and_delete_data_managed(self):
asset.save()
self.assertIsInstance(asset, LocalAsset)

reloaded = Asset.objects.get(pk=asset.pk)
reloaded = LocalAsset.objects.get(pk=asset.pk)
cloned = asset_handler.clone(reloaded)
self.assertNotEqual(reloaded.pk, cloned.pk)

Expand Down Expand Up @@ -161,7 +161,7 @@ def test_clone_and_delete_data_unmanaged(self):
asset.save()
self.assertIsInstance(asset, LocalAsset)

reloaded = Asset.objects.get(pk=asset.pk)
reloaded = LocalAsset.objects.get(pk=asset.pk)
cloned = asset_handler.clone(reloaded)

self.assertEqual(reloaded.location[0], cloned.location[0])
Expand Down Expand Up @@ -268,7 +268,7 @@ def _setup_test(self, u, _file=ONE_JSON):
asset.save()
self.assertIsInstance(asset, LocalAsset)

reloaded = Asset.objects.get(pk=asset.pk)
reloaded = LocalAsset.objects.get(pk=asset.pk)

# put two more files in the asset dir
asset_dir = os.path.dirname(reloaded.location[0])
Expand Down
5 changes: 3 additions & 2 deletions geonode/assets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ def get_default_asset(resource: ResourceBase, link_type=None) -> Asset or None:
filters = {"link__resource": resource}
if link_type:
filters["link__link_type"] = link_type

return Asset.objects.filter(**filters).first()
asset = Asset.objects.filter(**filters).first()
return asset.get_real_instance() if asset else None


DEFAULT_TYPES = {"image": ["jpg", "jpeg", "gif", "png", "bmp", "svg"]}
Expand All @@ -55,6 +55,7 @@ def find_type(ext):


def create_link(resource, asset, link_type=None, extension=None, name=None, mime=None, asset_handler=None, **kwargs):
asset = asset.get_real_instance()
asset_handler = asset_handler or asset_handler_registry.get_handler(asset)

if not link_type or not extension or not name:
Expand Down
36 changes: 24 additions & 12 deletions geonode/br/management/commands/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import re
import logging

from geonode.assets.local import LocalAssetHandler
from geonode.assets.models import LocalAsset

from .utils import utils

from requests.auth import HTTPBasicAuth
Expand Down Expand Up @@ -166,23 +169,21 @@

logger.info(f" - Dumping '{app_name}' into '{dump_name}.json'")
# Point stdout at a file for dumping data to.
with open(os.path.join(fixtures_target, f"{dump_name}.json"), "w") as output:
call_command("dumpdata", app_name, format="json", indent=2, stdout=output)
output_file = os.path.join(fixtures_target, f"{dump_name}.json")
call_command("dumpdata", app_name, output=output_file)

Check warning on line 173 in geonode/br/management/commands/backup.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/backup.py#L172-L173

Added lines #L172 - L173 were not covered by tests

# Store Media Root
logger.info("*** Dumping GeoNode media folder...")

media_root = settings.MEDIA_ROOT
media_folder = os.path.join(target_folder, utils.MEDIA_ROOT)
if not os.path.exists(media_folder):
os.makedirs(media_folder, exist_ok=True)
logger.info("*** Dumping GeoNode media folder...")
self.backup_folder(folder=media_folder, root=settings.MEDIA_ROOT, config=config)

Check warning on line 179 in geonode/br/management/commands/backup.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/backup.py#L178-L179

Added lines #L178 - L179 were not covered by tests

copy_tree(
media_root,
media_folder,
ignore=utils.ignore_time(config.gs_data_dt_filter[0], config.gs_data_dt_filter[1]),
)
logger.info(f"Saved media files from '{media_root}'")
logger.info("*** Dumping GeoNode assets folder...")
assets_folder = os.path.join(target_folder, utils.ASSETS_ROOT)
self.backup_folder(folder=assets_folder, root=settings.ASSETS_ROOT, config=config)

Check warning on line 183 in geonode/br/management/commands/backup.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/backup.py#L181-L183

Added lines #L181 - L183 were not covered by tests
for instance in LocalAsset.objects.iterator():
if not LocalAssetHandler._are_files_managed(instance):
logger.warning(f"The file for the asset with id {instance.pk} were not backup since is not managed by GeoNode")

Check warning on line 186 in geonode/br/management/commands/backup.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/backup.py#L186

Added line #L186 was not covered by tests

# Create Final ZIP Archive
logger.info("*** Creating final ZIP archive...")
Expand Down Expand Up @@ -214,6 +215,17 @@

return str(os.path.join(backup_dir, f"{dir_time_suffix}.zip"))

def backup_folder(self, folder, root, config):
if not os.path.exists(folder):
os.makedirs(root, exist_ok=True)

Check warning on line 220 in geonode/br/management/commands/backup.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/backup.py#L220

Added line #L220 was not covered by tests

copy_tree(

Check warning on line 222 in geonode/br/management/commands/backup.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/backup.py#L222

Added line #L222 was not covered by tests
root,
folder,
ignore=utils.ignore_time(config.gs_data_dt_filter[0], config.gs_data_dt_filter[1]),
)
logger.info(f"Saved files from '{root}'")

Check warning on line 227 in geonode/br/management/commands/backup.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/backup.py#L227

Added line #L227 was not covered by tests

def create_geoserver_backup(self, config, settings, target_folder, ignore_errors):
# Create GeoServer Backup
url = settings.OGC_SERVER["default"]["LOCATION"]
Expand Down
28 changes: 19 additions & 9 deletions geonode/br/management/commands/restore.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,16 @@
# Write Checks
media_root = settings.MEDIA_ROOT
media_folder = os.path.join(target_folder, utils.MEDIA_ROOT)

assets_root = settings.ASSETS_ROOT
assets_folder = os.path.join(target_folder, utils.ASSETS_ROOT)

Check warning on line 269 in geonode/br/management/commands/restore.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/restore.py#L268-L269

Added lines #L268 - L269 were not covered by tests
try:
logger.info("*** Performing some checks...")
logger.info(f"[Sanity Check] Full Write Access to restore folder: '{restore_folder}' ...")
chmod_tree(restore_folder)
logger.info(f"[Sanity Check] Full Write Access to media root: '{media_root}' ...")
chmod_tree(media_root)
logger.info(f"[Sanity Check] Full Write Access to assets root: '{assets_root}' ...")
chmod_tree(assets_root)

Check warning on line 277 in geonode/br/management/commands/restore.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/restore.py#L276-L277

Added lines #L276 - L277 were not covered by tests
except Exception as e:
if notify:
restore_notification.apply_async(
Expand Down Expand Up @@ -393,15 +396,11 @@

# Restore Media Root
logger.info("*** Restore media root...")
if config.gs_data_dt_filter[0] is None:
shutil.rmtree(media_root, ignore_errors=True)

if not os.path.exists(media_root):
os.makedirs(media_root, exist_ok=True)
self.restore_folder(config, media_root, media_folder)
logger.info("*** Restore assets root...")
self.restore_folder(config, assets_root, assets_folder)

Check warning on line 401 in geonode/br/management/commands/restore.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/restore.py#L399-L401

Added lines #L399 - L401 were not covered by tests

copy_tree(media_folder, media_root)
chmod_tree(media_root)
logger.info(f"Media files restored into '{media_root}'.")
# TODO improve this part, by saving the original asset_root path in a variable, then replace with the new one

# store backup info
restored_backup = RestoredBackup(
Expand Down Expand Up @@ -441,6 +440,17 @@
logger.info("*** Final filesystem cleanup ...")
shutil.rmtree(restore_folder)

def restore_folder(self, config, root, folder):
if config.gs_data_dt_filter[0] is None:
shutil.rmtree(root, ignore_errors=True)

Check warning on line 445 in geonode/br/management/commands/restore.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/restore.py#L445

Added line #L445 was not covered by tests

if not os.path.exists(root):
os.makedirs(root, exist_ok=True)

Check warning on line 448 in geonode/br/management/commands/restore.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/restore.py#L448

Added line #L448 was not covered by tests

copy_tree(folder, root)
chmod_tree(root)
logger.info(f"Files restored into '{root}'.")

Check warning on line 452 in geonode/br/management/commands/restore.py

View check run for this annotation

Codecov / codecov/patch

geonode/br/management/commands/restore.py#L450-L452

Added lines #L450 - L452 were not covered by tests

def validate_backup_file_options(self, **options) -> None:
"""
Method validating --backup-file and --backup-files-dir options
Expand Down
4 changes: 2 additions & 2 deletions geonode/br/management/commands/settings_docker_sample.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ dumprasterdata = yes
# data_layername_exclude_filter = {comma separated list of layernames, optionally with glob syntax} e.g.: tuscany_*,italy

[fixtures]
apps = contenttypes,auth,people,groups,account,guardian,admin,actstream,announcements,avatar,base,documents,geoserver,invitations,pinax_notifications,harvesting,services,layers,maps,oauth2_provider,sites,socialaccount,taggit,tastypie,upload,user_messages,geonode_themes,geoapps,favorite,geonode_client
dumps = contenttypes,auth,people,groups,account,guardian,admin,actstream,announcements,avatar,base,documents,geoserver,invitations,pinax_notifications,harvesting,services,layers,maps,oauth2_provider,sites,socialaccount,taggit,tastypie,upload,user_messages,geonode_themes,geoapps,favorite,geonode_client
apps = contenttypes,auth,people,groups,account,guardian,admin,actstream,announcements,avatar,assets,base,documents,geoserver,invitations,pinax_notifications,harvesting,services,layers,maps,oauth2_provider,sites,socialaccount,taggit,tastypie,upload,user_messages,geonode_themes,geoapps,favorite,geonode_client
dumps = contenttypes,auth,people,groups,account,guardian,admin,actstream,announcements,avatar,assets,base,documents,geoserver,invitations,pinax_notifications,harvesting,services,layers,maps,oauth2_provider,sites,socialaccount,taggit,tastypie,upload,user_messages,geonode_themes,geoapps,favorite,geonode_client

4 changes: 2 additions & 2 deletions geonode/br/management/commands/settings_sample.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ dumprasterdata = yes
# data_layername_exclude_filter = {comma separated list of layernames, optionally with glob syntax} e.g.: tuscany_*,italy

[fixtures]
apps = contenttypes,auth,people,groups,account,guardian,admin,actstream,announcements,avatar,base,documents,geoserver,invitations,pinax_notifications,harvesting,services,layers,maps,oauth2_provider,sites,socialaccount,taggit,tastypie,upload,user_messages,geonode_themes,geoapps,favorite,geonode_client
dumps = contenttypes,auth,people,groups,account,guardian,admin,actstream,announcements,avatar,base,documents,geoserver,invitations,pinax_notifications,harvesting,services,layers,maps,oauth2_provider,sites,socialaccount,taggit,tastypie,upload,user_messages,geonode_themes,geoapps,favorite,geonode_client
apps = contenttypes,auth,people,groups,account,guardian,admin,actstream,announcements,avatar,assets,base,documents,geoserver,invitations,pinax_notifications,harvesting,services,layers,maps,oauth2_provider,sites,socialaccount,taggit,tastypie,upload,user_messages,geonode_themes,geoapps,favorite,geonode_client
dumps = contenttypes,auth,people,groups,account,guardian,admin,actstream,announcements,avatar,assets,base,documents,geoserver,invitations,pinax_notifications,harvesting,services,layers,maps,oauth2_provider,sites,socialaccount,taggit,tastypie,upload,user_messages,geonode_themes,geoapps,favorite,geonode_client
1 change: 1 addition & 0 deletions geonode/br/management/commands/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
TEMPLATE_DIRS = "template_dirs"
LOCALE_PATHS = "locale_dirs"
EXTERNAL_ROOT = "external"
ASSETS_ROOT = "assets"


logger = logging.getLogger(__name__)
Expand Down
4 changes: 2 additions & 2 deletions geonode/documents/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from django.utils.functional import classproperty
from django.utils.translation import gettext_lazy as _

from geonode.assets.models import Asset
from geonode.assets.models import LocalAsset
from geonode.client.hooks import hookset
from geonode.base.models import ResourceBase
from geonode.groups.conf import settings as groups_settings
Expand Down Expand Up @@ -79,7 +79,7 @@ def compact_permission_labels(cls):

@property
def files(self):
asset = Asset.objects.filter(link__resource=self).first()
asset = LocalAsset.objects.filter(link__resource=self).first()
return asset.location if asset else []

@property
Expand Down
Loading