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.
- invenio_banners/__init__.py +16 -0
- invenio_banners/administration/__init__.py +8 -0
- invenio_banners/administration/banners.py +183 -0
- invenio_banners/alembic/5e02314da32e_create_invenio_banners_db_table.py +43 -0
- invenio_banners/alembic/e40d93d99040_create_invenio_banners_branch.py +27 -0
- invenio_banners/config.py +54 -0
- invenio_banners/ext.py +60 -0
- invenio_banners/proxies.py +19 -0
- invenio_banners/records/__init__.py +13 -0
- invenio_banners/records/models.py +143 -0
- invenio_banners/resources/__init__.py +16 -0
- invenio_banners/resources/config.py +51 -0
- invenio_banners/resources/errors.py +52 -0
- invenio_banners/resources/resource.py +107 -0
- invenio_banners/services/__init__.py +20 -0
- invenio_banners/services/config.py +85 -0
- invenio_banners/services/permissions.py +23 -0
- invenio_banners/services/results.py +124 -0
- invenio_banners/services/schemas.py +39 -0
- invenio_banners/services/service.py +170 -0
- invenio_banners/templates/semantic-ui/invenio_banners/banner.html +26 -0
- invenio_banners/translations/ar/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/ar/LC_MESSAGES/messages.po +193 -0
- invenio_banners/translations/bg/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/bg/LC_MESSAGES/messages.po +177 -0
- invenio_banners/translations/ca/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/ca/LC_MESSAGES/messages.po +178 -0
- invenio_banners/translations/cs/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/cs/LC_MESSAGES/messages.po +198 -0
- invenio_banners/translations/da/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/da/LC_MESSAGES/messages.po +173 -0
- invenio_banners/translations/de/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/de/LC_MESSAGES/messages.po +202 -0
- invenio_banners/translations/el/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/el/LC_MESSAGES/messages.po +178 -0
- invenio_banners/translations/es/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/es/LC_MESSAGES/messages.po +197 -0
- invenio_banners/translations/et/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/et/LC_MESSAGES/messages.po +177 -0
- invenio_banners/translations/fa/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/fa/LC_MESSAGES/messages.po +173 -0
- invenio_banners/translations/fi/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/fi/LC_MESSAGES/messages.po +194 -0
- invenio_banners/translations/fr/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/fr/LC_MESSAGES/messages.po +180 -0
- invenio_banners/translations/hr/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/hr/LC_MESSAGES/messages.po +177 -0
- invenio_banners/translations/hu/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/hu/LC_MESSAGES/messages.po +196 -0
- invenio_banners/translations/it/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/it/LC_MESSAGES/messages.po +179 -0
- invenio_banners/translations/ja/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/ja/LC_MESSAGES/messages.po +177 -0
- invenio_banners/translations/ka/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/ka/LC_MESSAGES/messages.po +177 -0
- invenio_banners/translations/ko/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/ko/LC_MESSAGES/messages.po +173 -0
- invenio_banners/translations/lt/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/lt/LC_MESSAGES/messages.po +177 -0
- invenio_banners/translations/messages.pot +174 -0
- invenio_banners/translations/no/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/no/LC_MESSAGES/messages.po +177 -0
- invenio_banners/translations/pl/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/pl/LC_MESSAGES/messages.po +177 -0
- invenio_banners/translations/pt/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/pt/LC_MESSAGES/messages.po +177 -0
- invenio_banners/translations/ro/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/ro/LC_MESSAGES/messages.po +179 -0
- invenio_banners/translations/ru/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/ru/LC_MESSAGES/messages.po +179 -0
- invenio_banners/translations/sk/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/sk/LC_MESSAGES/messages.po +178 -0
- invenio_banners/translations/sv/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/sv/LC_MESSAGES/messages.po +197 -0
- invenio_banners/translations/tr/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/tr/LC_MESSAGES/messages.po +196 -0
- invenio_banners/translations/uk/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/uk/LC_MESSAGES/messages.po +177 -0
- invenio_banners/translations/zh_CN/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/zh_CN/LC_MESSAGES/messages.po +179 -0
- invenio_banners/translations/zh_TW/LC_MESSAGES/messages.mo +0 -0
- invenio_banners/translations/zh_TW/LC_MESSAGES/messages.po +177 -0
- invenio_banners/utils.py +52 -0
- invenio_banners/views.py +14 -0
- invenio_banners-5.2.1.dist-info/METADATA +157 -0
- invenio_banners-5.2.1.dist-info/RECORD +91 -0
- invenio_banners-5.2.1.dist-info/WHEEL +6 -0
- invenio_banners-5.2.1.dist-info/entry_points.txt +23 -0
- invenio_banners-5.2.1.dist-info/licenses/AUTHORS.rst +12 -0
- invenio_banners-5.2.1.dist-info/licenses/LICENSE +21 -0
- 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 %}
|
|
Binary file
|