invenio-banners 5.2.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. invenio_banners/__init__.py +16 -0
  2. invenio_banners/administration/__init__.py +8 -0
  3. invenio_banners/administration/banners.py +183 -0
  4. invenio_banners/alembic/5e02314da32e_create_invenio_banners_db_table.py +43 -0
  5. invenio_banners/alembic/e40d93d99040_create_invenio_banners_branch.py +27 -0
  6. invenio_banners/config.py +54 -0
  7. invenio_banners/ext.py +60 -0
  8. invenio_banners/proxies.py +19 -0
  9. invenio_banners/records/__init__.py +13 -0
  10. invenio_banners/records/models.py +143 -0
  11. invenio_banners/resources/__init__.py +16 -0
  12. invenio_banners/resources/config.py +51 -0
  13. invenio_banners/resources/errors.py +52 -0
  14. invenio_banners/resources/resource.py +107 -0
  15. invenio_banners/services/__init__.py +20 -0
  16. invenio_banners/services/config.py +85 -0
  17. invenio_banners/services/permissions.py +23 -0
  18. invenio_banners/services/results.py +124 -0
  19. invenio_banners/services/schemas.py +39 -0
  20. invenio_banners/services/service.py +170 -0
  21. invenio_banners/templates/semantic-ui/invenio_banners/banner.html +26 -0
  22. invenio_banners/translations/ar/LC_MESSAGES/messages.mo +0 -0
  23. invenio_banners/translations/ar/LC_MESSAGES/messages.po +193 -0
  24. invenio_banners/translations/bg/LC_MESSAGES/messages.mo +0 -0
  25. invenio_banners/translations/bg/LC_MESSAGES/messages.po +177 -0
  26. invenio_banners/translations/ca/LC_MESSAGES/messages.mo +0 -0
  27. invenio_banners/translations/ca/LC_MESSAGES/messages.po +178 -0
  28. invenio_banners/translations/cs/LC_MESSAGES/messages.mo +0 -0
  29. invenio_banners/translations/cs/LC_MESSAGES/messages.po +198 -0
  30. invenio_banners/translations/da/LC_MESSAGES/messages.mo +0 -0
  31. invenio_banners/translations/da/LC_MESSAGES/messages.po +173 -0
  32. invenio_banners/translations/de/LC_MESSAGES/messages.mo +0 -0
  33. invenio_banners/translations/de/LC_MESSAGES/messages.po +202 -0
  34. invenio_banners/translations/el/LC_MESSAGES/messages.mo +0 -0
  35. invenio_banners/translations/el/LC_MESSAGES/messages.po +178 -0
  36. invenio_banners/translations/es/LC_MESSAGES/messages.mo +0 -0
  37. invenio_banners/translations/es/LC_MESSAGES/messages.po +197 -0
  38. invenio_banners/translations/et/LC_MESSAGES/messages.mo +0 -0
  39. invenio_banners/translations/et/LC_MESSAGES/messages.po +177 -0
  40. invenio_banners/translations/fa/LC_MESSAGES/messages.mo +0 -0
  41. invenio_banners/translations/fa/LC_MESSAGES/messages.po +173 -0
  42. invenio_banners/translations/fi/LC_MESSAGES/messages.mo +0 -0
  43. invenio_banners/translations/fi/LC_MESSAGES/messages.po +194 -0
  44. invenio_banners/translations/fr/LC_MESSAGES/messages.mo +0 -0
  45. invenio_banners/translations/fr/LC_MESSAGES/messages.po +180 -0
  46. invenio_banners/translations/hr/LC_MESSAGES/messages.mo +0 -0
  47. invenio_banners/translations/hr/LC_MESSAGES/messages.po +177 -0
  48. invenio_banners/translations/hu/LC_MESSAGES/messages.mo +0 -0
  49. invenio_banners/translations/hu/LC_MESSAGES/messages.po +196 -0
  50. invenio_banners/translations/it/LC_MESSAGES/messages.mo +0 -0
  51. invenio_banners/translations/it/LC_MESSAGES/messages.po +179 -0
  52. invenio_banners/translations/ja/LC_MESSAGES/messages.mo +0 -0
  53. invenio_banners/translations/ja/LC_MESSAGES/messages.po +177 -0
  54. invenio_banners/translations/ka/LC_MESSAGES/messages.mo +0 -0
  55. invenio_banners/translations/ka/LC_MESSAGES/messages.po +177 -0
  56. invenio_banners/translations/ko/LC_MESSAGES/messages.mo +0 -0
  57. invenio_banners/translations/ko/LC_MESSAGES/messages.po +173 -0
  58. invenio_banners/translations/lt/LC_MESSAGES/messages.mo +0 -0
  59. invenio_banners/translations/lt/LC_MESSAGES/messages.po +177 -0
  60. invenio_banners/translations/messages.pot +174 -0
  61. invenio_banners/translations/no/LC_MESSAGES/messages.mo +0 -0
  62. invenio_banners/translations/no/LC_MESSAGES/messages.po +177 -0
  63. invenio_banners/translations/pl/LC_MESSAGES/messages.mo +0 -0
  64. invenio_banners/translations/pl/LC_MESSAGES/messages.po +177 -0
  65. invenio_banners/translations/pt/LC_MESSAGES/messages.mo +0 -0
  66. invenio_banners/translations/pt/LC_MESSAGES/messages.po +177 -0
  67. invenio_banners/translations/ro/LC_MESSAGES/messages.mo +0 -0
  68. invenio_banners/translations/ro/LC_MESSAGES/messages.po +179 -0
  69. invenio_banners/translations/ru/LC_MESSAGES/messages.mo +0 -0
  70. invenio_banners/translations/ru/LC_MESSAGES/messages.po +179 -0
  71. invenio_banners/translations/sk/LC_MESSAGES/messages.mo +0 -0
  72. invenio_banners/translations/sk/LC_MESSAGES/messages.po +178 -0
  73. invenio_banners/translations/sv/LC_MESSAGES/messages.mo +0 -0
  74. invenio_banners/translations/sv/LC_MESSAGES/messages.po +197 -0
  75. invenio_banners/translations/tr/LC_MESSAGES/messages.mo +0 -0
  76. invenio_banners/translations/tr/LC_MESSAGES/messages.po +196 -0
  77. invenio_banners/translations/uk/LC_MESSAGES/messages.mo +0 -0
  78. invenio_banners/translations/uk/LC_MESSAGES/messages.po +177 -0
  79. invenio_banners/translations/zh_CN/LC_MESSAGES/messages.mo +0 -0
  80. invenio_banners/translations/zh_CN/LC_MESSAGES/messages.po +179 -0
  81. invenio_banners/translations/zh_TW/LC_MESSAGES/messages.mo +0 -0
  82. invenio_banners/translations/zh_TW/LC_MESSAGES/messages.po +177 -0
  83. invenio_banners/utils.py +52 -0
  84. invenio_banners/views.py +14 -0
  85. invenio_banners-5.2.1.dist-info/METADATA +157 -0
  86. invenio_banners-5.2.1.dist-info/RECORD +91 -0
  87. invenio_banners-5.2.1.dist-info/WHEEL +6 -0
  88. invenio_banners-5.2.1.dist-info/entry_points.txt +23 -0
  89. invenio_banners-5.2.1.dist-info/licenses/AUTHORS.rst +12 -0
  90. invenio_banners-5.2.1.dist-info/licenses/LICENSE +21 -0
  91. invenio_banners-5.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,52 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2023 CERN.
4
+ # Copyright (C) 2024-2025 Graz University of Technology.
5
+ #
6
+ # Invenio-Banners is free software; you can redistribute it and/or modify it
7
+ # under the terms of the MIT License; see LICENSE file for more details.
8
+
9
+ """Errors."""
10
+
11
+ import marshmallow as ma
12
+ from flask_resources import HTTPJSONException, create_error_handler
13
+ from invenio_records_resources.errors import validation_error_to_list_errors
14
+
15
+
16
+ class BannerNotExistsError(Exception):
17
+ """Banner not found exception."""
18
+
19
+ def __init__(self, banner_id):
20
+ """Constructor."""
21
+ self.banner_id = banner_id
22
+
23
+ @property
24
+ def description(self):
25
+ """Exception's description."""
26
+ return f"Banner with id {self.banner_id} is not found."
27
+
28
+
29
+ class HTTPJSONValidationException(HTTPJSONException):
30
+ """HTTP exception serializing to JSON and reflecting Marshmallow errors."""
31
+
32
+ description = "A validation error occurred."
33
+
34
+ def __init__(self, exception):
35
+ """Constructor."""
36
+ super().__init__(code=400, errors=validation_error_to_list_errors(exception))
37
+
38
+
39
+ class ErrorHandlersMixin:
40
+ """Mixin to define error handlers."""
41
+
42
+ error_handlers = {
43
+ BannerNotExistsError: create_error_handler(
44
+ lambda e: HTTPJSONException(
45
+ code=404,
46
+ description=e.description,
47
+ )
48
+ ),
49
+ ma.ValidationError: create_error_handler(
50
+ lambda e: HTTPJSONValidationException(e)
51
+ ),
52
+ }
@@ -0,0 +1,107 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2022-2023 CERN.
4
+ #
5
+ # Invenio-Banners is free software; you can redistribute it and/or modify it
6
+ # under the terms of the MIT License; see LICENSE file for more details.
7
+
8
+ """Invenio Banners module to create REST APIs."""
9
+
10
+ from flask import g
11
+ from flask_resources import Resource, resource_requestctx, response_handler, route
12
+ from invenio_records_resources.resources.records.resource import (
13
+ request_data,
14
+ request_headers,
15
+ request_search_args,
16
+ request_view_args,
17
+ )
18
+
19
+ from .errors import ErrorHandlersMixin
20
+
21
+
22
+ #
23
+ # Resource
24
+ #
25
+ class BannerResource(ErrorHandlersMixin, Resource):
26
+ """Banner resource."""
27
+
28
+ def __init__(self, config, service):
29
+ """Constructor."""
30
+ super(BannerResource, self).__init__(config)
31
+ self.service = service
32
+
33
+ def create_url_rules(self):
34
+ """Create the URL rules for the record resource."""
35
+ routes = self.config.routes
36
+ return [
37
+ route("POST", routes["list"], self.create),
38
+ route("GET", routes["item"], self.read),
39
+ route("GET", routes["list"], self.search),
40
+ route("DELETE", routes["item"], self.delete),
41
+ route("PUT", routes["item"], self.update),
42
+ ]
43
+
44
+ @request_view_args
45
+ @request_data
46
+ @response_handler()
47
+ def update(self):
48
+ """Update a banner."""
49
+ banner = self.service.update(
50
+ id=resource_requestctx.view_args["banner_id"],
51
+ identity=g.identity,
52
+ data=resource_requestctx.data,
53
+ )
54
+
55
+ # disable expired banners
56
+ self.service.disable_expired(identity=g.identity)
57
+
58
+ return banner.to_dict(), 200
59
+
60
+ @request_view_args
61
+ @response_handler()
62
+ def read(self):
63
+ """Read a banner."""
64
+ banner_id = resource_requestctx.view_args["banner_id"]
65
+ banner = self.service.read(
66
+ id=banner_id,
67
+ identity=g.identity,
68
+ )
69
+
70
+ return banner.to_dict(), 200
71
+
72
+ @request_search_args
73
+ @response_handler(many=True)
74
+ def search(self):
75
+ """Perform a search over the banners."""
76
+ banners = self.service.search(
77
+ identity=g.identity,
78
+ params=resource_requestctx.args,
79
+ )
80
+
81
+ return banners.to_dict(), 200
82
+
83
+ @request_data
84
+ @response_handler()
85
+ def create(self):
86
+ """Create a banner."""
87
+ banner = self.service.create(
88
+ g.identity,
89
+ resource_requestctx.data or {},
90
+ )
91
+
92
+ # disable expired banners
93
+ self.service.disable_expired(identity=g.identity)
94
+
95
+ return banner.to_dict(), 201
96
+
97
+ @request_headers
98
+ @request_view_args
99
+ def delete(self):
100
+ """Delete a banner."""
101
+ banner_id = resource_requestctx.view_args["banner_id"]
102
+ banner = self.service.delete(
103
+ id=banner_id,
104
+ identity=g.identity,
105
+ )
106
+
107
+ return banner.to_dict(), 204
@@ -0,0 +1,20 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2022 CERN.
4
+ #
5
+ # Invenio-Banners is free software; you can redistribute it and/or modify it
6
+ # under the terms of the MIT License; see LICENSE file for more details.
7
+
8
+ """Banners Service API."""
9
+
10
+ from .config import BannerServiceConfig, BannersLink
11
+ from .results import BannerItem, BannerList
12
+ from .service import BannerService
13
+
14
+ __all__ = (
15
+ "BannerService",
16
+ "BannerServiceConfig",
17
+ "BannerList",
18
+ "BannerItem",
19
+ "BannersLink",
20
+ )
@@ -0,0 +1,85 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2022-2023 CERN.
4
+ #
5
+ # Invenio-Banners is free software; you can redistribute it and/or modify it
6
+ # under the terms of the MIT License; see LICENSE file for more details.
7
+
8
+ """Banners Service configuration."""
9
+
10
+ from invenio_i18n import gettext as _
11
+ from invenio_records_resources.services import Link, RecordServiceConfig
12
+ from invenio_records_resources.services.records.links import pagination_links
13
+ from sqlalchemy import asc, desc
14
+
15
+ from ..records.models import BannerModel
16
+ from .permissions import BannersPermissionPolicy
17
+ from .results import BannerItem, BannerList
18
+ from .schemas import BannerSchema
19
+
20
+
21
+ class BannersLink(Link):
22
+ """Link variables setter for Banner links."""
23
+
24
+ @staticmethod
25
+ def vars(banner, vars):
26
+ """Variables for the URI template."""
27
+ vars.update({"id": banner.id})
28
+
29
+
30
+ class SearchOptions:
31
+ """Search options."""
32
+
33
+ sort_direction_default = "asc"
34
+ sort_direction_options = {
35
+ "asc": dict(
36
+ title=_("Ascending"),
37
+ fn=asc,
38
+ ),
39
+ "desc": dict(
40
+ title=_("Descending"),
41
+ fn=desc,
42
+ ),
43
+ }
44
+
45
+ sort_default = "start_datetime"
46
+ sort_options = {
47
+ "url_path": dict(
48
+ title=_("Url path"),
49
+ fields=["url_path"],
50
+ ),
51
+ "start_datetime": dict(
52
+ title=_("Start time"),
53
+ fields=["start_datetime"],
54
+ ),
55
+ "end_datetime": dict(
56
+ title=_("End time"),
57
+ fields=["end_datetime"],
58
+ ),
59
+ "active": dict(
60
+ title=_("Active"),
61
+ fields=["active"],
62
+ ),
63
+ }
64
+ pagination_options = {
65
+ "default_results_per_page": 25,
66
+ }
67
+
68
+
69
+ class BannerServiceConfig(RecordServiceConfig):
70
+ """Service factory configuration."""
71
+
72
+ result_item_cls = BannerItem
73
+ result_list_cls = BannerList
74
+ permission_policy_cls = BannersPermissionPolicy
75
+ schema = BannerSchema
76
+
77
+ # Search configuration
78
+ search = SearchOptions
79
+
80
+ # links configuration
81
+ links_item = {
82
+ "self": BannersLink("{+api}/banners/{id}"),
83
+ }
84
+ links_search = pagination_links("{+api}/banners{?args*}")
85
+ record_cls = BannerModel
@@ -0,0 +1,23 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2022 CERN.
4
+ #
5
+ # Invenio-Banners is free software; you can redistribute it and/or modify it
6
+ # under the terms of the MIT License; see LICENSE file for more details.
7
+
8
+ """Banners permissions."""
9
+
10
+ from invenio_administration.generators import Administration
11
+ from invenio_records_permissions import BasePermissionPolicy
12
+ from invenio_records_permissions.generators import AnyUser, SystemProcess
13
+
14
+
15
+ class BannersPermissionPolicy(BasePermissionPolicy):
16
+ """Permission policy for banners."""
17
+
18
+ can_create = [Administration(), SystemProcess()]
19
+ can_read = [AnyUser(), SystemProcess()]
20
+ can_search = [AnyUser(), SystemProcess()]
21
+ can_update = [Administration(), SystemProcess()]
22
+ can_delete = [Administration(), SystemProcess()]
23
+ can_disable = [Administration(), SystemProcess()]
@@ -0,0 +1,124 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2022-2023 CERN.
4
+ # Copyright (C) 2024 Graz University of Technology.
5
+ #
6
+ # Invenio-Banners is free software; you can redistribute it and/or modify it
7
+ # under the terms of the MIT License; see LICENSE file for more details.
8
+
9
+ """Service results."""
10
+
11
+ from invenio_records_resources.services.records.results import RecordItem, RecordList
12
+
13
+ try:
14
+ # flask_sqlalchemy<3.0.0
15
+ from flask_sqlalchemy import Pagination
16
+ except ImportError:
17
+ # flask_sqlalchemy>=3.0.0
18
+ from flask_sqlalchemy.pagination import Pagination
19
+
20
+
21
+ class BannerItem(RecordItem):
22
+ """Single banner result."""
23
+
24
+ def __init__(
25
+ self,
26
+ service,
27
+ identity,
28
+ banner,
29
+ links_tpl=None,
30
+ errors=None,
31
+ schema=None,
32
+ ):
33
+ """Constructor."""
34
+ super().__init__(service, identity, banner, errors, links_tpl, schema)
35
+
36
+ @property
37
+ def data(self):
38
+ """Property to get the banner."""
39
+ if self._data:
40
+ return self._data
41
+
42
+ self._data = self._schema.dump(
43
+ self._obj,
44
+ context={
45
+ "identity": self._identity,
46
+ "record": self._record,
47
+ },
48
+ )
49
+
50
+ if self._links_tpl:
51
+ self._data["links"] = self.links
52
+
53
+ return self._data
54
+
55
+
56
+ class BannerList(RecordList):
57
+ """List of banner results."""
58
+
59
+ def __init__(
60
+ self,
61
+ service,
62
+ identity,
63
+ banners,
64
+ params=None,
65
+ links_tpl=None,
66
+ links_item_tpl=None,
67
+ schema=None,
68
+ ):
69
+ """Constructor."""
70
+ super().__init__(
71
+ service, identity, banners, params, links_tpl, links_item_tpl, schema
72
+ )
73
+
74
+ @property
75
+ def hits(self):
76
+ """Iterator over the hits."""
77
+ for record in self.banners_result():
78
+ # Project the record
79
+ projection = self._schema.dump(
80
+ record,
81
+ context=dict(
82
+ identity=self._identity,
83
+ record=record,
84
+ ),
85
+ )
86
+
87
+ if self._links_item_tpl:
88
+ projection["links"] = self._links_item_tpl.expand(
89
+ self._identity, record
90
+ )
91
+
92
+ yield projection
93
+
94
+ def to_dict(self):
95
+ """Return result as a dictionary."""
96
+ res = {
97
+ "hits": {
98
+ "hits": list(self.hits),
99
+ "total": self.total,
100
+ }
101
+ }
102
+
103
+ if self._params:
104
+ if self._links_tpl:
105
+ res["links"] = self._links_tpl.expand(self._identity, self.pagination)
106
+
107
+ return res
108
+
109
+ @property
110
+ def total(self):
111
+ """Get total number of banners."""
112
+ return (
113
+ self._results.total
114
+ if isinstance(self._results, Pagination)
115
+ else len(self._results)
116
+ )
117
+
118
+ def banners_result(self):
119
+ """Get iterable banners list."""
120
+ return (
121
+ self._results.items
122
+ if isinstance(self._results, Pagination)
123
+ else self._results
124
+ )
@@ -0,0 +1,39 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2022-2023 CERN.
4
+ #
5
+ # Invenio-Banners is free software; you can redistribute it and/or modify it
6
+ # under the terms of the MIT License; see LICENSE file for more details.
7
+
8
+ """Banners schema."""
9
+
10
+ from datetime import datetime, timezone
11
+
12
+ from invenio_records_resources.services.records.schema import BaseRecordSchema
13
+ from marshmallow import fields, pre_load
14
+ from marshmallow_utils.fields import SanitizedHTML, TZDateTime
15
+
16
+
17
+ class BannerSchema(BaseRecordSchema):
18
+ """Schema for banners."""
19
+
20
+ message = SanitizedHTML(required=True)
21
+ url_path = fields.String(allow_none=True)
22
+ category = fields.String(required=True, metadata={"default": "info"})
23
+ start_datetime = fields.DateTime(
24
+ required=True,
25
+ metadata={"default": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")},
26
+ )
27
+ end_datetime = fields.DateTime(allow_none=True)
28
+ active = fields.Boolean(required=True, metadata={"default": True})
29
+ created = TZDateTime(timezone=timezone.utc, format="iso", dump_only=True)
30
+ updated = TZDateTime(timezone=timezone.utc, format="iso", dump_only=True)
31
+
32
+ @pre_load
33
+ def change_none_to_string(self, data, **kwargs):
34
+ """Fix for empty strings not in line with allow_none=True."""
35
+ for field in data:
36
+ if field == "end_datetime" or field == "category":
37
+ if data[field] == "":
38
+ data[field] = None
39
+ return data
@@ -0,0 +1,170 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2022-2025 CERN.
4
+ # Copyright (C) 2024-2025 Graz University of Technology.
5
+ #
6
+ # Invenio-Banners is free software; you can redistribute it and/or modify it
7
+ # under the terms of the MIT License; see LICENSE file for more details.
8
+
9
+ """Banner Service API."""
10
+
11
+
12
+ import arrow
13
+ from invenio_db.uow import unit_of_work
14
+ from invenio_records_resources.services import RecordService
15
+ from invenio_records_resources.services.base import LinksTemplate
16
+ from invenio_records_resources.services.base.utils import map_search_params
17
+ from sqlalchemy import and_, func, literal, or_
18
+
19
+ from ..records.models import BannerModel
20
+ from ..utils import strtobool
21
+
22
+
23
+ class BannerService(RecordService):
24
+ """Banner Service."""
25
+
26
+ def read(self, identity, id):
27
+ """Retrieve a banner."""
28
+ self.require_permission(identity, "read")
29
+
30
+ banner = self.record_cls.get(id)
31
+
32
+ return self.result_item(
33
+ self,
34
+ identity,
35
+ banner,
36
+ links_tpl=self.links_item_tpl,
37
+ )
38
+
39
+ def search(self, identity, params):
40
+ """Search for banners with multiple filter options.
41
+
42
+ Supports filtering by:
43
+ - active: boolean filter
44
+ - url_path: prefix matching (empty paths match all, specific paths match as prefixes)
45
+ - q: text or date search across multiple fields
46
+
47
+ active and url_path filters are combined with AND logic, while OR is used while combining them with the q filters.
48
+ """
49
+ self.require_permission(identity, "search")
50
+
51
+ active_filter_param = params.pop("active", None)
52
+ url_path_filter_param = params.pop("url_path", None)
53
+ search_params = map_search_params(self.config.search, params)
54
+
55
+ and_filters = []
56
+ if active_filter_param is not None:
57
+ and_filters.append(BannerModel.active.is_(active_filter_param))
58
+
59
+ if url_path_filter_param is not None:
60
+ and_filters.append(
61
+ or_(
62
+ BannerModel.url_path == "",
63
+ literal(url_path_filter_param).like(BannerModel.url_path + "%"),
64
+ )
65
+ )
66
+
67
+ filters = [and_(*and_filters)] if and_filters else []
68
+ query_param = search_params["q"]
69
+ if query_param:
70
+ filters.extend(
71
+ [
72
+ BannerModel.url_path.ilike(f"%{query_param}%"),
73
+ BannerModel.message.ilike(f"%{query_param}%"),
74
+ BannerModel.category.ilike(f"%{query_param}%"),
75
+ ]
76
+ )
77
+
78
+ datetime_value = self._validate_datetime(query_param)
79
+ if datetime_value is not None:
80
+ filters.extend(
81
+ [
82
+ func.date(BannerModel.start_datetime) == datetime_value,
83
+ func.date(BannerModel.end_datetime) == datetime_value,
84
+ func.date(BannerModel.created) == datetime_value,
85
+ func.date(BannerModel.updated) == datetime_value,
86
+ ]
87
+ )
88
+
89
+ banners = self.record_cls.search(search_params, filters)
90
+
91
+ return self.result_list(
92
+ self,
93
+ identity,
94
+ banners,
95
+ params=search_params,
96
+ links_tpl=LinksTemplate(self.config.links_search, context={"args": params}),
97
+ links_item_tpl=self.links_item_tpl,
98
+ )
99
+
100
+ @unit_of_work()
101
+ def create(self, identity, data, raise_errors=True, uow=None):
102
+ """Create a banner."""
103
+ self.require_permission(identity, "create")
104
+
105
+ # validate data
106
+ valid_data, errors = self.schema.load(
107
+ data,
108
+ context={"identity": identity},
109
+ raise_errors=raise_errors,
110
+ )
111
+
112
+ # create the banner with the specified data
113
+ banner = self.record_cls.create(valid_data)
114
+
115
+ return self.result_item(
116
+ self, identity, banner, links_tpl=self.links_item_tpl, errors=errors
117
+ )
118
+
119
+ @unit_of_work()
120
+ def delete(self, identity, id, uow=None):
121
+ """Delete a banner from database."""
122
+ self.require_permission(identity, "delete")
123
+
124
+ banner = self.record_cls.get(id)
125
+ self.record_cls.delete(banner)
126
+
127
+ return self.result_item(self, identity, banner, links_tpl=self.links_item_tpl)
128
+
129
+ @unit_of_work()
130
+ def update(self, identity, id, data, uow=None):
131
+ """Update a banner."""
132
+ self.require_permission(identity, "update")
133
+
134
+ banner = self.record_cls.get(id)
135
+
136
+ # validate data
137
+ valid_data, errors = self.schema.load(
138
+ data,
139
+ context={"identity": identity},
140
+ raise_errors=True,
141
+ )
142
+
143
+ self.record_cls.update(valid_data, id)
144
+
145
+ return self.result_item(
146
+ self,
147
+ identity,
148
+ banner,
149
+ links_tpl=self.links_item_tpl,
150
+ )
151
+
152
+ @unit_of_work()
153
+ def disable_expired(self, identity, uow=None):
154
+ """Disable expired banners."""
155
+ self.require_permission(identity, "disable")
156
+ self.record_cls.disable_expired()
157
+
158
+ def _validate_bool(self, value):
159
+ try:
160
+ bool_value = strtobool(value)
161
+ except ValueError:
162
+ return None
163
+ return bool(bool_value)
164
+
165
+ def _validate_datetime(self, value):
166
+ try:
167
+ date_value = arrow.get(value).date()
168
+ except ValueError:
169
+ return None
170
+ return date_value
@@ -0,0 +1,26 @@
1
+ {# -*- coding: utf-8 -*-
2
+
3
+ This file is part of Invenio.
4
+ Copyright (C) 2020-2023 CERN.
5
+
6
+ Invenio-Banners is free software; you can redistribute it and/or modify it
7
+ under the terms of the MIT License; see LICENSE file for more details.
8
+ #}
9
+
10
+ {%- macro banner(ui_classes) -%}
11
+ {%- block banner %}
12
+ {%- set banners = get_active_banners() %}
13
+ {% if not ui_classes %}
14
+ {% set ui_classes = "top attached m-0" %}
15
+ {% endif %}
16
+ {% if banners %}
17
+ {% for banner in banners %}
18
+ <div class="ui {{ banner.category|style_banner_category }} message {{ ui_classes }} inv-banner" id="banner-{{ banner.id }}">
19
+ <div class="ui container">
20
+ {{ banner.message|safe }}
21
+ </div>
22
+ </div>
23
+ {% endfor %}
24
+ {% endif %}
25
+ {%- endblock banner %}
26
+ {%- endmacro %}