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,16 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2020-2024 CERN.
4
+ # Copyright (C) 2024-2025 Graz University of Technology.
5
+ # Copyright (C) 2025 KTH Royal Institute of Technology.
6
+ #
7
+ # Invenio-Banners is free software; you can redistribute it and/or modify it
8
+ # under the terms of the MIT License; see LICENSE file for more details.
9
+
10
+ """Create and show banners with useful messages to users."""
11
+
12
+ from .ext import InvenioBanners
13
+
14
+ __version__ = "5.2.1"
15
+
16
+ __all__ = ("__version__", "InvenioBanners")
@@ -0,0 +1,8 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 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
+ """Administration banners module."""
@@ -0,0 +1,183 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2023 CERN.
4
+ # Copyright (C) 2024 KTH Royal Institute 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
+ """Invenio administration banners view module."""
10
+
11
+ from invenio_administration.views.base import (
12
+ AdminResourceCreateView,
13
+ AdminResourceDetailView,
14
+ AdminResourceEditView,
15
+ AdminResourceListView,
16
+ )
17
+ from invenio_i18n import lazy_gettext as _
18
+
19
+
20
+ class BannerListView(AdminResourceListView):
21
+ """Search admin view."""
22
+
23
+ api_endpoint = "/banners"
24
+ name = "banners"
25
+ resource_config = "banners_resource"
26
+ title = _("Banners")
27
+ menu_label = _("Banners")
28
+ category = _("Site management")
29
+ pid_path = "id"
30
+ icon = "newspaper"
31
+
32
+ display_search = True
33
+ display_delete = True
34
+ display_create = True
35
+ display_edit = True
36
+
37
+ item_field_list = {
38
+ "id": {"text": _("Id"), "order": 1, "width": 1},
39
+ "start_datetime": {"text": _("Start time (UTC)"), "order": 2, "width": 2},
40
+ "end_datetime": {"text": _("End time (UTC)"), "order": 3, "width": 2},
41
+ "message": {"text": _("Message"), "order": 4, "width": 7},
42
+ "active": {"text": _("Active"), "order": 5, "width": 1},
43
+ "url_path": {"text": _("URL path"), "order": 6, "width": 2},
44
+ "category": {"text": _("Category"), "order": 7, "width": 1},
45
+ }
46
+
47
+ create_view_name = "banner_create"
48
+
49
+ search_config_name = "BANNERS_SEARCH"
50
+ search_sort_config_name = "BANNERS_SORT_OPTIONS"
51
+
52
+
53
+ common_form_fields = {
54
+ "start_datetime": {
55
+ "order": 1,
56
+ "text": _("Start time"),
57
+ "description": _(
58
+ "Date/time to make the banner active. "
59
+ "Input format: yyyy-mm-dd hh:mm:ss. "
60
+ "Set to future date/time to delay the banner. "
61
+ "Note: specify time in UTC time standard."
62
+ ),
63
+ "placeholder": _("YYYY-MM-DD hh:mm:ss"),
64
+ },
65
+ "end_datetime": {
66
+ "order": 2,
67
+ "text": _("End time"),
68
+ "description": _(
69
+ "Date/time to make the banner inactive. "
70
+ "Input format: yyyy-mm-dd hh:mm:ss. An empty value makes "
71
+ "the banner active until manually disabled via the active flag. "
72
+ "Note: specify time in UTC time standard."
73
+ ),
74
+ "placeholder": _("YYYY-MM-DD hh:mm:ss"),
75
+ },
76
+ "message": {
77
+ "order": 3,
78
+ "text": _("Message"),
79
+ "description": _(
80
+ "Message to be displayed on the banner. HTML format is supported."
81
+ ),
82
+ "rows": 10,
83
+ },
84
+ "url_path": {
85
+ "order": 4,
86
+ "text": _("URL path"),
87
+ "description": _(
88
+ "URL path prefix (including the first /) to define where "
89
+ "the message will be active on the site. For "
90
+ "example, if you enter `/records`, any URL starting with "
91
+ "`/records` will return an active banner (`/records`, "
92
+ "`/records/1234`, etc.). An empty value makes the banner "
93
+ "active for any URL."
94
+ ),
95
+ },
96
+ "category": {
97
+ "order": 5,
98
+ "text": _("Category"),
99
+ "description": _(
100
+ "Banner category. `Info` option displays a blue banner. "
101
+ "`Warning` option displays an orange banner. "
102
+ "`Other` option displays a gray banner."
103
+ ),
104
+ "options": [
105
+ {"title_l10n": "Info", "id": "info"},
106
+ {"title_l10n": "Warning", "id": "warning"},
107
+ {"title_l10n": "Other", "id": "other"},
108
+ ],
109
+ "placeholder": "Select a category",
110
+ },
111
+ "active": {
112
+ "order": 6,
113
+ "text": _("Active"),
114
+ "description": _(
115
+ "Tick it to activate the banner: banner will be "
116
+ "displayed according to start/end times. If not "
117
+ "activated, start/end times will be ignored."
118
+ ),
119
+ },
120
+ }
121
+
122
+
123
+ class BannerEditView(AdminResourceEditView):
124
+ """Configuration for Banner edit view."""
125
+
126
+ name = "banner_edit"
127
+ url = "/banners/<pid_value>/edit"
128
+ resource_config = "banners_resource"
129
+ pid_path = "id"
130
+ api_endpoint = "/banners"
131
+ title = _("Edit Banner")
132
+
133
+ list_view_name = "banners"
134
+
135
+ form_fields = {
136
+ **common_form_fields,
137
+ "created": {"order": 7},
138
+ "updated": {"order": 8},
139
+ }
140
+
141
+
142
+ class BannerCreateView(AdminResourceCreateView):
143
+ """Configuration for Banner create view."""
144
+
145
+ name = "banner_create"
146
+ url = "/banners/create"
147
+ resource_config = "banners_resource"
148
+ pid_path = "id"
149
+ api_endpoint = "/banners"
150
+ title = _("Create Banner")
151
+
152
+ list_view_name = "banners"
153
+
154
+ form_fields = {
155
+ **common_form_fields,
156
+ }
157
+
158
+
159
+ class BannerDetailView(AdminResourceDetailView):
160
+ """Admin banner detail view."""
161
+
162
+ url = "/banners/<pid_value>"
163
+ api_endpoint = "/banners"
164
+ name = "banner-details"
165
+ resource_config = "banners_resource"
166
+ title = _("Banner Details")
167
+
168
+ display_delete = True
169
+ display_edit = True
170
+
171
+ list_view_name = "banners"
172
+ pid_path = "id"
173
+
174
+ item_field_list = {
175
+ "start_datetime": {"text": _("Start time"), "order": 1},
176
+ "end_datetime": {"text": _("End time"), "order": 2},
177
+ "message": {"text": _("Message"), "order": 3},
178
+ "url_path": {"text": _("URL path"), "order": 4},
179
+ "category": {"text": _("Category"), "order": 5},
180
+ "active": {"text": _("Active"), "order": 6},
181
+ "created": {"text": _("Created"), "order": 7},
182
+ "updated": {"text": _("Updated"), "order": 8},
183
+ }
@@ -0,0 +1,43 @@
1
+ #
2
+ # This file is part of Invenio.
3
+ # Copyright (C) 2016-2018 CERN.
4
+ #
5
+ # Invenio 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
+ """Create invenio-banners db table."""
9
+
10
+ import sqlalchemy as sa
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "5e02314da32e"
15
+ down_revision = "e40d93d99040"
16
+ branch_labels = ()
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ """Upgrade database."""
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.create_table(
24
+ "banners",
25
+ sa.Column("created", sa.DateTime(), nullable=False),
26
+ sa.Column("updated", sa.DateTime(), nullable=False),
27
+ sa.Column("id", sa.Integer(), nullable=False),
28
+ sa.Column("message", sa.Text(), nullable=False),
29
+ sa.Column("url_path", sa.String(length=255), nullable=True),
30
+ sa.Column("category", sa.String(length=20), nullable=False),
31
+ sa.Column("start_datetime", sa.DateTime(), nullable=False),
32
+ sa.Column("end_datetime", sa.DateTime(), nullable=True),
33
+ sa.Column("active", sa.Boolean(name="active"), nullable=False),
34
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_banners")),
35
+ )
36
+ # ### end Alembic commands ###
37
+
38
+
39
+ def downgrade():
40
+ """Downgrade database."""
41
+ # ### commands auto generated by Alembic - please adjust! ###
42
+ op.drop_table("banners")
43
+ # ### end Alembic commands ###
@@ -0,0 +1,27 @@
1
+ #
2
+ # This file is part of Invenio.
3
+ # Copyright (C) 2016-2018 CERN.
4
+ #
5
+ # Invenio 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
+ """Create invenio-banners branch."""
9
+
10
+ import sqlalchemy as sa
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "e40d93d99040"
15
+ down_revision = None
16
+ branch_labels = ("invenio_banners",)
17
+ depends_on = "dbdbc1b19cf2"
18
+
19
+
20
+ def upgrade():
21
+ """Upgrade database."""
22
+ pass
23
+
24
+
25
+ def downgrade():
26
+ """Downgrade database."""
27
+ pass
@@ -0,0 +1,54 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2020-2023 CERN.
4
+ # Copyright (C) 2024 KTH Royal Institute 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
+ """Configuration variables."""
10
+
11
+ from invenio_i18n import lazy_gettext as _
12
+
13
+ from invenio_banners.utils import style_category
14
+
15
+ BANNERS_CATEGORIES = [
16
+ ("info", _("Info")),
17
+ ("warning", _("Warning")),
18
+ ("other", _("Other")),
19
+ ]
20
+ """Categories to define different types of messages. List of (id, label)."""
21
+
22
+ BANNERS_CATEGORIES_TO_STYLE = style_category
23
+ """Function to transform the banner category to a specific Semantic-UI class."""
24
+
25
+ BANNERS_SEARCH = {
26
+ "facets": [],
27
+ "sort": [
28
+ "url_path",
29
+ "start_datetime",
30
+ "end_datetime",
31
+ "active",
32
+ ],
33
+ }
34
+ """Banner search configuration (i.e list of banners)"""
35
+
36
+ BANNERS_SORT_OPTIONS = {
37
+ "url_path": dict(
38
+ title=_("URL path"),
39
+ fields=["url_path"],
40
+ ),
41
+ "start_datetime": dict(
42
+ title=_("Start DateTime"),
43
+ fields=["start_datetime"],
44
+ ),
45
+ "end_datetime": dict(
46
+ title=_("End DateTime"),
47
+ fields=["end_datetime"],
48
+ ),
49
+ "active": dict(
50
+ title=_("Active"),
51
+ fields=["active"],
52
+ ),
53
+ }
54
+ """Definitions of available Banners sort options. """
invenio_banners/ext.py ADDED
@@ -0,0 +1,60 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2020-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 extension app."""
9
+
10
+ from flask import Blueprint
11
+
12
+ from . import config
13
+ from .resources import BannerResource, BannerResourceConfig
14
+ from .services import BannerService, BannerServiceConfig
15
+ from .utils import get_active_banners_for_request
16
+
17
+ blueprint = Blueprint(
18
+ "invenio_banners",
19
+ __name__,
20
+ template_folder="templates",
21
+ static_folder="static",
22
+ )
23
+
24
+
25
+ class InvenioBanners(object):
26
+ """Invenio-Banners extension."""
27
+
28
+ def __init__(self, app=None):
29
+ """Extension initialization."""
30
+ if app:
31
+ self.init_app(app)
32
+
33
+ def init_app(self, app):
34
+ """Flask application initialization."""
35
+ self.init_config(app)
36
+ self.init_services(app)
37
+ self.init_resources(app)
38
+ app.extensions["invenio-banners"] = self
39
+ app.register_blueprint(blueprint)
40
+ app.jinja_env.globals["get_active_banners"] = get_active_banners_for_request
41
+ app.jinja_env.filters["style_banner_category"] = app.config[
42
+ "BANNERS_CATEGORIES_TO_STYLE"
43
+ ]
44
+
45
+ def init_config(self, app):
46
+ """Initialize configuration."""
47
+ for k in dir(config):
48
+ if k.startswith("BANNERS_"):
49
+ app.config.setdefault(k, getattr(config, k))
50
+
51
+ def init_services(self, app):
52
+ """Initialize the services for banners."""
53
+ self.banners_service = BannerService(config=BannerServiceConfig)
54
+
55
+ def init_resources(self, app):
56
+ """Initialize the resources for banners."""
57
+ self.banners_resource = BannerResource(
58
+ service=self.banners_service,
59
+ config=BannerResourceConfig,
60
+ )
@@ -0,0 +1,19 @@
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
+ """Proxies for accessing the current Banners extension."""
9
+
10
+ from flask import current_app
11
+ from werkzeug.local import LocalProxy
12
+
13
+ current_banners = LocalProxy(lambda: current_app.extensions["invenio-banners"])
14
+ """Proxy for the instantiated Banners extension."""
15
+
16
+ current_banners_service = LocalProxy(
17
+ lambda: current_app.extensions["invenio-banners"].banners_service
18
+ )
19
+ """Proxy for the currently instantiated banners service."""
@@ -0,0 +1,13 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2020-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
+ """Records directory."""
9
+
10
+
11
+ from .models import BannerModel
12
+
13
+ __all__ = ("BannerModel",)
@@ -0,0 +1,143 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2020-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
+ """Models."""
10
+
11
+ from datetime import datetime, timezone
12
+
13
+ import sqlalchemy as sa
14
+ from flask import current_app
15
+ from invenio_db import db
16
+ from sqlalchemy import or_
17
+ from sqlalchemy.sql import text
18
+ from sqlalchemy_utils.models import Timestamp
19
+
20
+ from ..resources.errors import BannerNotExistsError
21
+
22
+
23
+ class BannerModel(db.Model, Timestamp):
24
+ """Defines a message to show to users."""
25
+
26
+ __tablename__ = "banners"
27
+
28
+ id = db.Column(db.Integer, primary_key=True)
29
+
30
+ message = db.Column(db.Text, nullable=False)
31
+ """The message content."""
32
+
33
+ url_path = db.Column(db.String(255), nullable=True)
34
+ """Define in which URL /path the message will be visible."""
35
+
36
+ category = db.Column(db.String(20), nullable=False)
37
+ """Category of the message, for styling messages per category."""
38
+
39
+ start_datetime = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
40
+ """Start date and time (UTC), can be immediate or delayed."""
41
+
42
+ end_datetime = db.Column(db.DateTime, nullable=True)
43
+ """End date and time (UTC), must be after `start` or forever if null."""
44
+
45
+ active = db.Column(db.Boolean(name="active"), nullable=False, default=True)
46
+ """Defines if the message is active, only one at the same time."""
47
+
48
+ @classmethod
49
+ def create(cls, data):
50
+ """Create a new banner."""
51
+ _categories = [t[0] for t in current_app.config["BANNERS_CATEGORIES"]]
52
+ assert data.get("category") in _categories
53
+
54
+ with db.session.begin_nested():
55
+ obj = cls(
56
+ message=data.get("message"),
57
+ category=data.get("category"),
58
+ url_path=data.get("url_path"),
59
+ start_datetime=data.get("start_datetime"),
60
+ end_datetime=data.get("end_datetime"),
61
+ active=data.get("active"),
62
+ )
63
+ db.session.add(obj)
64
+
65
+ return obj
66
+
67
+ @classmethod
68
+ def update(cls, data, id):
69
+ """Update an existing banner."""
70
+ with db.session.begin_nested():
71
+ # NOTE:
72
+ # with db.session.get(cls, id) the model itself would be
73
+ # returned and this classmethod would be called
74
+ db.session.query(cls).filter_by(id=id).update(data)
75
+
76
+ @classmethod
77
+ def get(cls, id):
78
+ """Get banner by its id."""
79
+ if banner := db.session.get(cls, id):
80
+ return banner
81
+
82
+ raise BannerNotExistsError(id)
83
+
84
+ @classmethod
85
+ def delete(cls, banner):
86
+ """Delete banner by its id."""
87
+ with db.session.begin_nested():
88
+ db.session.delete(banner)
89
+
90
+ @classmethod
91
+ def get_active(cls, url_path):
92
+ """Return active banners."""
93
+ now = datetime.now(timezone.utc).replace(tzinfo=None)
94
+
95
+ query = (
96
+ db.session.query(cls)
97
+ .filter(cls.active.is_(True))
98
+ .filter(cls.start_datetime <= now)
99
+ .filter((cls.end_datetime.is_(None)) | (now <= cls.end_datetime))
100
+ )
101
+
102
+ # filter by url_path
103
+ active_banners = query.filter(
104
+ sa.or_(
105
+ cls.url_path.is_(None),
106
+ sa.literal(url_path).startswith(cls.url_path),
107
+ )
108
+ )
109
+
110
+ return active_banners.all()
111
+
112
+ @classmethod
113
+ def search(cls, search_params, filters=None):
114
+ """Filter banners accordingly to query params."""
115
+ if filters:
116
+ filtered = db.session.query(BannerModel).filter(or_(*filters))
117
+ else:
118
+ filtered = db.session.query(BannerModel).filter()
119
+
120
+ banners = filtered.order_by(
121
+ search_params["sort_direction"](text(",".join(search_params["sort"])))
122
+ ).paginate(
123
+ page=search_params["page"],
124
+ per_page=search_params["size"],
125
+ error_out=False,
126
+ )
127
+
128
+ return banners
129
+
130
+ @classmethod
131
+ def disable_expired(cls):
132
+ """Disable any old still active messages to keep everything clean."""
133
+ now = datetime.now(timezone.utc).replace(tzinfo=None)
134
+
135
+ query = (
136
+ db.session.query(cls)
137
+ .filter(cls.active.is_(True))
138
+ .filter(cls.end_datetime.isnot(None))
139
+ .filter(cls.end_datetime < now)
140
+ )
141
+
142
+ for old in query.all():
143
+ old.active = False
@@ -0,0 +1,16 @@
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
+ """Invenio Resources module to create REST APIs."""
9
+
10
+ from .config import BannerResourceConfig
11
+ from .resource import BannerResource
12
+
13
+ __all__ = (
14
+ "BannerResource",
15
+ "BannerResourceConfig",
16
+ )
@@ -0,0 +1,51 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2022-2025 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
+ """Banner Resource Configuration."""
9
+
10
+ import marshmallow as ma
11
+ from flask_resources import JSONDeserializer, RequestBodyParser
12
+ from invenio_records_resources.resources import (
13
+ RecordResourceConfig,
14
+ SearchRequestArgsSchema,
15
+ )
16
+
17
+
18
+ class BannerServerSearchRequestArgsSchema(SearchRequestArgsSchema):
19
+ """Banner request parameters."""
20
+
21
+ sort_direction = ma.fields.Str()
22
+ active = ma.fields.Bool()
23
+ url_path = ma.fields.Str()
24
+
25
+
26
+ class BannerResourceConfig(RecordResourceConfig):
27
+ """Banner resource config."""
28
+
29
+ # Blueprint configuration
30
+ blueprint_name = "banners"
31
+ url_prefix = "/banners"
32
+ routes = {
33
+ "item": "/<banner_id>",
34
+ "list": "/",
35
+ }
36
+
37
+ request_view_args = {
38
+ "banner_id": ma.fields.String(),
39
+ }
40
+
41
+ request_search_args = BannerServerSearchRequestArgsSchema
42
+
43
+ request_body_parsers = {"application/json": RequestBodyParser(JSONDeserializer())}
44
+ default_content_type = "application/json"
45
+
46
+ response_handlers = {
47
+ "application/vnd.inveniordm.v1+json": RecordResourceConfig.response_handlers[
48
+ "application/json"
49
+ ],
50
+ **RecordResourceConfig.response_handlers,
51
+ }