diff --git a/.gitignore b/.gitignore index ffb938552..76c89512d 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,52 @@ pip-wheel-metadata !/src/* dummy_file.db dummy_file.db.blobs + +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b1ba735d1..c1028c21b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,7 +4,9 @@ CHANGELOG 7.0.3 (unreleased) ------------------ -- Nothing changed yet. +- Calling register and notify when sorting OrderedDicts +- Calling register and notify when deleting file keys + [nilbacardit26] 7.0.2 (2024-01-23) diff --git a/guillotina/api/files.py b/guillotina/api/files.py index 3e458aad8..a024867ec 100644 --- a/guillotina/api/files.py +++ b/guillotina/api/files.py @@ -4,6 +4,8 @@ from guillotina.api.service import DownloadService from guillotina.api.service import TraversableFieldService from guillotina.component import get_multi_adapter +from guillotina.event import notify +from guillotina.events import ObjectModifiedEvent from guillotina.exceptions import FileNotFoundException from guillotina.interfaces import IAsyncBehavior from guillotina.interfaces import IFileManager @@ -138,6 +140,8 @@ async def __call__(self): # for the field to save there by chunks adapter = get_multi_adapter((self.context, self.request, self.field), IFileManager) result = await adapter.delete() + self.context.register() + await notify(ObjectModifiedEvent(self.context)) return result diff --git a/guillotina/contrib/image/api.py b/guillotina/contrib/image/api.py index 14d827d3b..80274ab30 100644 --- a/guillotina/contrib/image/api.py +++ b/guillotina/contrib/image/api.py @@ -7,6 +7,8 @@ from guillotina.contrib.image.interfaces import IImagingSettings from guillotina.contrib.image.preview import CloudPreviewImageFileField from guillotina.contrib.image.scale import scaleImage +from guillotina.event import notify +from guillotina.events import ObjectModifiedEvent from guillotina.interfaces import IFileManager from guillotina.interfaces.content import IResource from guillotina.response import HTTPNoContent @@ -153,3 +155,7 @@ async def __call__(self): if IOrderedDict.providedBy(self.field): data = await self.request.json() self.field.reorder_images(data) + self.context.register() + await notify( + ObjectModifiedEvent(self.context, payload={self.request.matchdict["field_name"]: data}) + ) diff --git a/guillotina/directives.py b/guillotina/directives.py index 2a615e38f..56d60860f 100644 --- a/guillotina/directives.py +++ b/guillotina/directives.py @@ -141,7 +141,7 @@ def factory(self, name, **kw): kw.setdefault("type", "text") if kw.get("type") not in self.allowed_types: raise Exception( - "Invalid index type {}. Avilable types are: {}".format(name, ", ".join(self.allowed_types)) + "Invalid index type {}. Available types are: {}".format(name, ", ".join(self.allowed_types)) ) return {name: kw} diff --git a/guillotina/tests/image/test_field.py b/guillotina/tests/image/test_field.py index 36ff2fb77..d664aaee5 100644 --- a/guillotina/tests/image/test_field.py +++ b/guillotina/tests/image/test_field.py @@ -1,7 +1,10 @@ from guillotina.contrib.image.behaviors import IImageAttachment from guillotina.contrib.image.behaviors import IMultiImageAttachment from guillotina.contrib.image.behaviors import IMultiImageOrderedAttachment +from guillotina.directives import index_field +from guillotina.test_package import IExample from guillotina.tests.image import TEST_DATA_LOCATION +from guillotina.utils import get_behavior import json import os @@ -10,6 +13,18 @@ pytestmark = pytest.mark.asyncio +NOT_POSTGRES = os.environ.get("DATABASE", "DUMMY") in ("cockroachdb", "DUMMY") +PG_CATALOG_SETTINGS = { + "applications": ["guillotina", "guillotina.contrib.catalog.pg", "guillotina.contrib.image"], + "cloud_datamanager": "db", + "load_utilities": { + "catalog": { + "provides": "guillotina.interfaces.ICatalogUtility", + "factory": "guillotina.contrib.catalog.pg.utility.PGSearchUtility", + } + }, +} + @pytest.mark.app_settings( {"applications": ["guillotina", "guillotina.contrib.image"], "cloud_datamanager": "db"} @@ -111,7 +126,7 @@ async def test_multiimage_field_with_behavior(redis_container, container_request "POST", "/db/guillotina/", data=json.dumps( - {"@type": "Item", "@behaviors": [IMultiImageAttachment.__identifier__], "id": "foobar"} + {"@type": "Example", "@behaviors": [IMultiImageAttachment.__identifier__], "id": "foobar"} ), ) assert status == 201 @@ -138,6 +153,33 @@ async def test_multiimage_field_with_behavior(redis_container, container_request assert status == 200 +@index_field.with_accessor(IExample, "images") +async def index_images(obj): + behavior = await get_behavior(obj, IMultiImageOrderedAttachment) + result = [] + if behavior.images: + for key in behavior.images.keys(): + result.append(key) + return result + + +@index_field.with_accessor( + IExample, + "images", + behavior="guillotina.contrib.image.behaviors.IMultiImageOrderedAttachment", + type="object", +) +async def index_images_behavior(obj): + behavior = await get_behavior(obj, IMultiImageOrderedAttachment) + result = [] + if behavior.images: + for key in behavior.images.keys(): + result.append(key) + return result + + +@pytest.mark.app_settings(PG_CATALOG_SETTINGS) +@pytest.mark.skipif(NOT_POSTGRES, reason="Only PG") @pytest.mark.app_settings( {"applications": ["guillotina", "guillotina.contrib.image"], "cloud_datamanager": "db"} ) @@ -145,12 +187,15 @@ async def test_multiimage_ordered_field_with_behavior(redis_container, container async with container_requester as requester: _, status = await requester("POST", "/db/guillotina/@addons", data=json.dumps({"id": "image"})) assert status == 200 - response, status = await requester( "POST", "/db/guillotina/", data=json.dumps( - {"@type": "Item", "@behaviors": [IMultiImageOrderedAttachment.__identifier__], "id": "foobar"} + { + "@type": "Example", + "@behaviors": [IMultiImageOrderedAttachment.__identifier__], + "id": "foobar", + } ), ) assert status == 201 @@ -209,6 +254,10 @@ async def test_multiimage_ordered_field_with_behavior(redis_container, container response, status = await requester("DELETE", "/db/guillotina/foobar/@delete/images/key1") assert status == 200 + response, status = await requester("GET", "/db/guillotina/@search?id=foobar") + assert status == 200 + assert response["items"][0]["images"] == ["key2", "key0", "key3"] + response, status = await requester("GET", "/db/guillotina/foobar") behavior = response["guillotina.contrib.image.behaviors.IMultiImageOrderedAttachment"] keys_ordered = {"key2": 0, "key0": 1, "key3": 2} @@ -217,6 +266,9 @@ async def test_multiimage_ordered_field_with_behavior(redis_container, container assert count == keys_ordered[image] count += 1 + response, status = await requester("GET", "/db/guillotina/@search?id=foobar") + assert status == 200 + response, status = await requester( "PATCH", "/db/guillotina/foobar/@sort/images", data=json.dumps(["key3", "key2", "key0"]) ) @@ -229,3 +281,6 @@ async def test_multiimage_ordered_field_with_behavior(redis_container, container for image in behavior["images"].keys(): assert count == keys_ordered[image] count += 1 + response, status = await requester("GET", "/db/guillotina/@search?id=foobar") + assert status == 200 + assert response["items"][0]["images"] == ["key3", "key2", "key0"]