conjunto 0.0.19__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.
- conjunto/__init__.py +3 -0
- conjunto/admin.py +19 -0
- conjunto/api/interfaces.py +400 -0
- conjunto/apps.py +9 -0
- conjunto/cms/__init__.py +1 -0
- conjunto/cms/admin.py +28 -0
- conjunto/cms/apps.py +14 -0
- conjunto/cms/migrations/0001_initial.py +92 -0
- conjunto/cms/migrations/0002_delete_staticversionedpage_alter_licensepage_options_and_more.py +36 -0
- conjunto/cms/migrations/0003_rename_licensepage_termsconditionspage.py +16 -0
- conjunto/cms/migrations/0004_alter_termsconditionspage_options.py +19 -0
- conjunto/cms/migrations/0005_alter_privacypage_version_and_more.py +23 -0
- conjunto/cms/migrations/0006_staticpage_name.py +22 -0
- conjunto/cms/migrations/__init__.py +0 -0
- conjunto/cms/models.py +44 -0
- conjunto/cms/tests.py +3 -0
- conjunto/cms/views.py +3 -0
- conjunto/components/__init__.py +353 -0
- conjunto/context_processors.py +21 -0
- conjunto/fields.py +15 -0
- conjunto/files/storage.py +15 -0
- conjunto/forms/__init__.py +213 -0
- conjunto/forms/widgets.py +23 -0
- conjunto/htmx.py +153 -0
- conjunto/http/__init__.py +22 -0
- conjunto/management/__init__.py +0 -0
- conjunto/management/commands/__init__.py +0 -0
- conjunto/management/commands/update_libraries.py +203 -0
- conjunto/management/commands/update_permissions.py +53 -0
- conjunto/menu/__init__.py +293 -0
- conjunto/menus.py +17 -0
- conjunto/middleware.py +116 -0
- conjunto/migrations/0001_initial.py +92 -0
- conjunto/migrations/0002_delete_licensepage_delete_privacypage_and_more.py +24 -0
- conjunto/migrations/0003_initial.py +43 -0
- conjunto/migrations/__init__.py +0 -0
- conjunto/models.py +169 -0
- conjunto/tabler.py +41 -0
- conjunto/tables.py +227 -0
- conjunto/templatetags/__init__.py +0 -0
- conjunto/templatetags/conjunto.py +74 -0
- conjunto/tools.py +177 -0
- conjunto/urls.py +17 -0
- conjunto/views.py +788 -0
- conjunto-0.0.19.dist-info/LICENSE +7 -0
- conjunto-0.0.19.dist-info/METADATA +75 -0
- conjunto-0.0.19.dist-info/RECORD +49 -0
- conjunto-0.0.19.dist-info/WHEEL +5 -0
- conjunto-0.0.19.dist-info/top_level.txt +1 -0
conjunto/__init__.py
ADDED
conjunto/admin.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.contrib import admin
|
|
3
|
+
from django.apps import apps
|
|
4
|
+
|
|
5
|
+
from .models import Vendor
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SettingsAdmin(admin.ModelAdmin):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
settings_model = apps.get_model(settings.SETTINGS_MODEL)
|
|
13
|
+
admin.site.register(settings_model, SettingsAdmin)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@admin.register(Vendor)
|
|
17
|
+
class VendorAdmin(admin.ModelAdmin):
|
|
18
|
+
def has_add_permission(self, request):
|
|
19
|
+
return super().has_add_permission(request) and not Vendor.objects.exists()
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
from django.contrib.auth import get_user_model
|
|
2
|
+
from django.core.exceptions import PermissionDenied
|
|
3
|
+
from django.urls import URLPattern, path
|
|
4
|
+
|
|
5
|
+
from conjunto.tools import camel_case2snake
|
|
6
|
+
from gdaps.api import Interface, InterfaceRegistry
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
User = get_user_model()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HtmxRequestMixin:
|
|
13
|
+
"""Optionally checks if request originates from HTMX.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
enforce_htmx: if True, all requests that do not come from a HTMX
|
|
17
|
+
element are blocked.
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
PermissionDenied: if enforce_htmx==True and request origins from a
|
|
21
|
+
non-HTMX caller.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
enforce_htmx: bool = True
|
|
25
|
+
|
|
26
|
+
def dispatch(self, request, *args, **kwargs):
|
|
27
|
+
if self.enforce_htmx and not self.request.htmx:
|
|
28
|
+
raise PermissionDenied(
|
|
29
|
+
f"Permission denied: View {self.__class__.__name__} can only be "
|
|
30
|
+
"called by a HTMX request."
|
|
31
|
+
)
|
|
32
|
+
return super().dispatch(request, *args, **kwargs)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class IHtmxElementMixin(HtmxRequestMixin):
|
|
36
|
+
"""An interface mixin that describes an element and can be rendered as plugin.
|
|
37
|
+
|
|
38
|
+
1. Declare an interface for HTMX element
|
|
39
|
+
2. Declare Implementations of that interface which also inherit from `View`.
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
```
|
|
43
|
+
# in your plugin's api/interfaces.py, create an interface for your view
|
|
44
|
+
@Interface
|
|
45
|
+
class ISettingsSection(IHtmxElementMixin, ...):
|
|
46
|
+
params = ["pk"]
|
|
47
|
+
template_name = "conjunto/layouts/settings_section.html"
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
# in your plugin's views.py
|
|
51
|
+
def AccountSettingsView(ISettingsSection, TemplateView)
|
|
52
|
+
...
|
|
53
|
+
def OtherElementView(ISettingsSection, TemplateView)
|
|
54
|
+
...
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use them in your template:
|
|
58
|
+
```django
|
|
59
|
+
{% for element in elements.ISettingsSection %}
|
|
60
|
+
<a href="#"
|
|
61
|
+
hx-get="{% url element.get_url_name user.id %}"
|
|
62
|
+
hx-trigger="{% if element.selected %}load, {% endif %}click once"
|
|
63
|
+
{# hx-push-url="{{ element.name }}" #}
|
|
64
|
+
hx-target="#profile-content"
|
|
65
|
+
class="list-group-item list-group-item-action d-flex align-items-center
|
|
66
|
+
active">
|
|
67
|
+
<i class="bi bi-{{ element.icon }} me-2"></i>
|
|
68
|
+
{{ element.title }}
|
|
69
|
+
</a>
|
|
70
|
+
{% endfor %}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
You can also inherit from other mixins, e.g. PermissionRequiredMixin, etc.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
class UserprofilePasswordSection(
|
|
77
|
+
ISettingsSection, PermissionRequiredMixin, TemplateView
|
|
78
|
+
):
|
|
79
|
+
name = "password"
|
|
80
|
+
permission_required = "..."
|
|
81
|
+
template_name = "my_app/password_view.html
|
|
82
|
+
```
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
name: str = ""
|
|
86
|
+
"""The view name of the element.
|
|
87
|
+
Must be unique. Used in URLs. Can be used for HTML attributes."""
|
|
88
|
+
|
|
89
|
+
title: str = ""
|
|
90
|
+
"""The title this plugin is rendered with.
|
|
91
|
+
It's up to you how the plugin uses that title."""
|
|
92
|
+
|
|
93
|
+
icon: str = ""
|
|
94
|
+
"""The icon name, if the element is listed in a list, and icons are used."""
|
|
95
|
+
|
|
96
|
+
weight: int = 0
|
|
97
|
+
"""The weight this element is ranked like menu items.
|
|
98
|
+
The more weight, the more the element sinks "down" in the list."""
|
|
99
|
+
|
|
100
|
+
group = ""
|
|
101
|
+
"""The group this element lives under. Can be used to display group headers."""
|
|
102
|
+
|
|
103
|
+
params: list[str] = []
|
|
104
|
+
"""A list of params the view is using. These must be passed in the URL when calling
|
|
105
|
+
the view from a template, e.g. via hx-get."""
|
|
106
|
+
|
|
107
|
+
form_kwargs_request = False
|
|
108
|
+
"""Should the evtl. attached form receive a request?"""
|
|
109
|
+
|
|
110
|
+
# HTMX is always enforced for elements
|
|
111
|
+
enforce_htmx = True
|
|
112
|
+
|
|
113
|
+
# choose the model this element is using, if any.
|
|
114
|
+
model = None
|
|
115
|
+
|
|
116
|
+
def __init__(self, *args, **kwargs):
|
|
117
|
+
if not self.name:
|
|
118
|
+
raise AttributeError(
|
|
119
|
+
f"{self.__class__.__name__} does not have a 'name' attribute."
|
|
120
|
+
)
|
|
121
|
+
super().__init__(*args, **kwargs)
|
|
122
|
+
|
|
123
|
+
def get_name(self) -> str:
|
|
124
|
+
"""Returns the element's view (machine) name."""
|
|
125
|
+
return f"{self.name}"
|
|
126
|
+
|
|
127
|
+
def get_title(self) -> str:
|
|
128
|
+
"""returns the element's title.
|
|
129
|
+
|
|
130
|
+
Use it in templates like {{ element.get_title }}
|
|
131
|
+
"""
|
|
132
|
+
return self.title
|
|
133
|
+
|
|
134
|
+
def get_url_name(self) -> str:
|
|
135
|
+
"""Returns the element's url.'
|
|
136
|
+
|
|
137
|
+
Use it as `{% url element.get_url_name ... %}` in a template.
|
|
138
|
+
"""
|
|
139
|
+
return f"elements:{self.get_name()}"
|
|
140
|
+
|
|
141
|
+
def get_urlpattern(self) -> URLPattern:
|
|
142
|
+
"""Calculates the urlpattern where this element is accessible.
|
|
143
|
+
|
|
144
|
+
That can be used e.g. in hx-get attributes.
|
|
145
|
+
Returns:
|
|
146
|
+
a URLPattern that can be used in your urls.py
|
|
147
|
+
"""
|
|
148
|
+
params = [f"<{p}>" for p in self.params]
|
|
149
|
+
# if in production, hash element name, to hide it from prying eyes...
|
|
150
|
+
element_name = camel_case2snake(self.__class__.__name__)
|
|
151
|
+
if params:
|
|
152
|
+
url = f"{element_name}/{'/'.join(params)}/{self.name}/"
|
|
153
|
+
else:
|
|
154
|
+
url = f"{element_name}/{self.name}/"
|
|
155
|
+
return path(
|
|
156
|
+
url,
|
|
157
|
+
self.__class__.as_view(),
|
|
158
|
+
name=self.name,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def get_url_patterns(cls) -> list[URLPattern]:
|
|
163
|
+
"""Convenience method to add to your urls.py in an include section:
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
```python
|
|
167
|
+
url_patterns = [
|
|
168
|
+
path(...),
|
|
169
|
+
path("profile/",
|
|
170
|
+
include(
|
|
171
|
+
(ISettingsSectionView.get_url_patterns(), "profile"),
|
|
172
|
+
namespace="profile"
|
|
173
|
+
)
|
|
174
|
+
),
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
# or directly add the url_patterns to the main list:
|
|
178
|
+
|
|
179
|
+
url_patterns += ISettingsSectionView.get_url_patterns()
|
|
180
|
+
```
|
|
181
|
+
"""
|
|
182
|
+
patterns = []
|
|
183
|
+
for interface in InterfaceRegistry._interfaces:
|
|
184
|
+
if issubclass(interface, cls):
|
|
185
|
+
for plugin in interface:
|
|
186
|
+
patterns.append(plugin.get_urlpattern())
|
|
187
|
+
return patterns
|
|
188
|
+
|
|
189
|
+
def enabled(self) -> bool:
|
|
190
|
+
# FIXME: rename into "visible"
|
|
191
|
+
"""Hook for implementations to define if the element is enabled.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
True if the element is enabled, False if not.
|
|
195
|
+
"""
|
|
196
|
+
return True
|
|
197
|
+
|
|
198
|
+
def get_form_kwargs(self) -> dict:
|
|
199
|
+
kwargs = super().get_form_kwargs()
|
|
200
|
+
if self.form_kwargs_request:
|
|
201
|
+
kwargs.update({"request": self.request})
|
|
202
|
+
return kwargs
|
|
203
|
+
|
|
204
|
+
def get_success_url(self):
|
|
205
|
+
return self.request.path
|
|
206
|
+
|
|
207
|
+
def get_context_data(self, **kwargs):
|
|
208
|
+
context = super().get_context_data(**kwargs)
|
|
209
|
+
context.update({"element": self})
|
|
210
|
+
return context
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class IBaseSettingsSectionMixin(IHtmxElementMixin):
|
|
214
|
+
"""Common base mixin for settings sections and subsections."""
|
|
215
|
+
|
|
216
|
+
url_pattern = None
|
|
217
|
+
|
|
218
|
+
# always return to the current element after successful save
|
|
219
|
+
success_url = "."
|
|
220
|
+
reload_triggers: list[str] = []
|
|
221
|
+
|
|
222
|
+
def has_permission(self) -> bool:
|
|
223
|
+
"""
|
|
224
|
+
If this view inherits from PermissionRequiredMixin,
|
|
225
|
+
provide standard authentication checks. If this view doesn't inherit from
|
|
226
|
+
PermissionRequiredMixin, you can ignore this method.
|
|
227
|
+
|
|
228
|
+
1. only allow authenticated users
|
|
229
|
+
2. if there is an additional "permission_required" attribute, check for this
|
|
230
|
+
perm additionally. If not, allow the user to display the view.
|
|
231
|
+
"""
|
|
232
|
+
return self.request.user.is_authenticated and (
|
|
233
|
+
self.request.user.has_perm(self.permission_required)
|
|
234
|
+
if hasattr(self, "permission_required")
|
|
235
|
+
else True
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# def get_object(self):
|
|
239
|
+
# queryset = self.get_queryset()
|
|
240
|
+
# if queryset:
|
|
241
|
+
# return queryset.get(pk=self.kwargs.get("pk"))
|
|
242
|
+
# elif self.model:
|
|
243
|
+
# return self.model.objects.get(pk=self.kwargs.get("pk"))
|
|
244
|
+
# else:
|
|
245
|
+
# return None
|
|
246
|
+
|
|
247
|
+
def get_context_data(self, **kwargs):
|
|
248
|
+
context = super().get_context_data(**kwargs)
|
|
249
|
+
if isinstance(self.reload_triggers, str):
|
|
250
|
+
self.reload_triggers = [self.reload_triggers]
|
|
251
|
+
context.update(
|
|
252
|
+
{
|
|
253
|
+
"element": self,
|
|
254
|
+
"reload_trigger": ", ".join(
|
|
255
|
+
[f"{t} from:body" for t in self.reload_triggers]
|
|
256
|
+
),
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
return context
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class UseElementMixin:
|
|
263
|
+
"""A mixin that can be added to a View that uses HTMX "elements".
|
|
264
|
+
|
|
265
|
+
This can be used if e.g. a View has tabs, or variable parts of the page
|
|
266
|
+
that are extended by a element. The URL is pushed automatically with the new
|
|
267
|
+
element as configurable GET parameter, e.g.: `example.com/persons/42?tab=account`
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
```python
|
|
271
|
+
class MyView(UseElementMixin, DetailView):
|
|
272
|
+
elements = [IPersonAccountTab]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
Attributes:
|
|
277
|
+
elements: a list of Interfaces that are used in the template of this
|
|
278
|
+
view, and can be accessed there using `blocks.IFooInterface`.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
elements: list[IHtmxElementMixin] = []
|
|
282
|
+
default_element_name = ""
|
|
283
|
+
|
|
284
|
+
query_variable = "tab"
|
|
285
|
+
"""The query variable that is used to indicate the active element."""
|
|
286
|
+
|
|
287
|
+
def __init__(self, **kwargs):
|
|
288
|
+
if not self.elements:
|
|
289
|
+
raise AttributeError(
|
|
290
|
+
f"{self.__class__.__name__} must define a 'elements' attribute "
|
|
291
|
+
f"with a list of of used plugins."
|
|
292
|
+
)
|
|
293
|
+
super().__init__(**kwargs)
|
|
294
|
+
|
|
295
|
+
def get_context_data(self, **kwargs):
|
|
296
|
+
context = super().get_context_data(**kwargs)
|
|
297
|
+
element_dict = {}
|
|
298
|
+
active_element = None
|
|
299
|
+
for plugin_class in self.elements:
|
|
300
|
+
implementations = list(plugin_class)
|
|
301
|
+
element_dict[plugin_class.__name__] = implementations
|
|
302
|
+
for plugin in implementations:
|
|
303
|
+
if plugin.name == self.request.GET.get(self.query_variable, None):
|
|
304
|
+
active_element = plugin.name
|
|
305
|
+
# if no element is selected via GET, use the default one
|
|
306
|
+
if not active_element:
|
|
307
|
+
active_element = self.default_element_name
|
|
308
|
+
|
|
309
|
+
context.update({"elements": element_dict, "active_element": active_element})
|
|
310
|
+
return context
|
|
311
|
+
|
|
312
|
+
# @classmethod
|
|
313
|
+
# def element_urls(cls) -> list[URLPattern]:
|
|
314
|
+
# """Returns urlpatterns for all included plugins.
|
|
315
|
+
#
|
|
316
|
+
# This is for having easy-to-use URLs for a element
|
|
317
|
+
# use it in your urls.py:
|
|
318
|
+
# path("foo/", FooView.as_view(), name="foo),
|
|
319
|
+
# path("foo/", include(FooView.element_urls())),
|
|
320
|
+
# """
|
|
321
|
+
# patterns = []
|
|
322
|
+
# for elements in cls.elements:
|
|
323
|
+
# for element in elements:
|
|
324
|
+
# patterns.append(
|
|
325
|
+
# path(
|
|
326
|
+
# element.get_path(),
|
|
327
|
+
# element.__class__.as_view(),
|
|
328
|
+
# name=element.name,
|
|
329
|
+
# )
|
|
330
|
+
# )
|
|
331
|
+
# return patterns
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class ISettingsSubSectionMixin(IBaseSettingsSectionMixin):
|
|
335
|
+
"""Mixin class to create interfaces for plugin hooks that are rendered as HTMX
|
|
336
|
+
subsection in the settings page.
|
|
337
|
+
|
|
338
|
+
This is fo creating **interfaces**, not implementations. The name could match
|
|
339
|
+
the section name you are using, so that you can associate it with it.
|
|
340
|
+
Create a subsection interface with this mixin for one section. You can add this
|
|
341
|
+
interface then to your Section as `elements`.
|
|
342
|
+
|
|
343
|
+
Conjunto provides a standard template for subsections, but you can override this
|
|
344
|
+
using the `template_name` attribute.
|
|
345
|
+
|
|
346
|
+
Attributes:
|
|
347
|
+
template_name: the name of the template that is used to render the
|
|
348
|
+
subsection.
|
|
349
|
+
|
|
350
|
+
Example:
|
|
351
|
+
```python
|
|
352
|
+
@Interface
|
|
353
|
+
class IAccountSubSection(ISettingsSubSectionMixin):
|
|
354
|
+
pass
|
|
355
|
+
```
|
|
356
|
+
"""
|
|
357
|
+
|
|
358
|
+
template_name = "conjunto/layouts/settings_subsection.html"
|
|
359
|
+
# FIXME: this should not be necessary.
|
|
360
|
+
# But without it, a call to <host>/<path>/?section=account would fail.
|
|
361
|
+
enforce_htmx = False
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@Interface
|
|
365
|
+
class ISettingsSection(UseElementMixin, IBaseSettingsSectionMixin):
|
|
366
|
+
# FIXME: add PermissionRequiredMixin to this class.
|
|
367
|
+
"""
|
|
368
|
+
Plugin hook that is rendered as HTMX view in the settings page section.
|
|
369
|
+
|
|
370
|
+
Typically, you create a section for you settings (using the ISettingsSection
|
|
371
|
+
interface), and fill it's `elements` list with subsection interfaces you want to use
|
|
372
|
+
in this section.
|
|
373
|
+
|
|
374
|
+
Examples:
|
|
375
|
+
@Interface
|
|
376
|
+
class IAccountSubSection(ISettingsSubSectionMixin):
|
|
377
|
+
'''This is the interface for your subsections'''
|
|
378
|
+
|
|
379
|
+
class AccountSection(ISettingsSection, PermissionRequiredMixin, DetailView):
|
|
380
|
+
'''This is the main "account" section of your settings.'''
|
|
381
|
+
name = "account_general"
|
|
382
|
+
group = _("Account")
|
|
383
|
+
title = _("General Account settings")
|
|
384
|
+
elements = [IAccountSubSection]
|
|
385
|
+
permission_required = "<app>.view_<your_model>"
|
|
386
|
+
|
|
387
|
+
class PasswordSubSection(IAccountSubSection, PasswordChangeView):
|
|
388
|
+
'''This will be shown as subsection of "Account"'''
|
|
389
|
+
|
|
390
|
+
class SocialMediaSubSection(IAccountSubSection, PasswordChangeView):
|
|
391
|
+
'''This will be shown as subsection of "Account"'''
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
__sort_attribute__ = "group"
|
|
395
|
+
params = ["pk"]
|
|
396
|
+
template_name = "conjunto/layouts/settings_section.html"
|
|
397
|
+
|
|
398
|
+
# FIXME: this should not be necessary.
|
|
399
|
+
# But without it, a call to <host>/<path>/?section=account would fail.
|
|
400
|
+
enforce_htmx = False
|
conjunto/apps.py
ADDED
conjunto/cms/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""This is a django app for providing content management system functionality."""
|
conjunto/cms/admin.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from conjunto.cms.models import (
|
|
4
|
+
StaticPage,
|
|
5
|
+
TermsConditionsPage,
|
|
6
|
+
PrivacyPage,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Register your models here.
|
|
11
|
+
@admin.register(StaticPage)
|
|
12
|
+
class StaticPageAdmin(admin.ModelAdmin):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# @admin.register(StaticVersionedPage)
|
|
17
|
+
# class StaticVersionedPageAdmin(admin.ModelAdmin):
|
|
18
|
+
# pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@admin.register(TermsConditionsPage)
|
|
22
|
+
class TermsConditionsPageAdmin(admin.ModelAdmin):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@admin.register(PrivacyPage)
|
|
27
|
+
class PrivacyPageAdmin(admin.ModelAdmin):
|
|
28
|
+
pass
|
conjunto/cms/apps.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from django.apps import AppConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CmsConfig(AppConfig):
|
|
5
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
6
|
+
name = "conjunto.cms"
|
|
7
|
+
|
|
8
|
+
groups_permissions = {
|
|
9
|
+
"Content editor": {
|
|
10
|
+
"cms.StaticPage": ["view", "add", "change", "delete"],
|
|
11
|
+
"cms.TermsConditionsPage": ["view", "add", "change", "delete"],
|
|
12
|
+
"cms.PrivacyPage": ["view", "add", "change", "delete"],
|
|
13
|
+
},
|
|
14
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Generated by Django 4.2.8 on 2023-12-23 16:21
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import versionfield.fields
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
initial = True
|
|
9
|
+
|
|
10
|
+
dependencies = []
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.CreateModel(
|
|
14
|
+
name="LicensePage",
|
|
15
|
+
fields=[
|
|
16
|
+
(
|
|
17
|
+
"id",
|
|
18
|
+
models.BigAutoField(
|
|
19
|
+
auto_created=True,
|
|
20
|
+
primary_key=True,
|
|
21
|
+
serialize=False,
|
|
22
|
+
verbose_name="ID",
|
|
23
|
+
),
|
|
24
|
+
),
|
|
25
|
+
("title", models.CharField(max_length=100)),
|
|
26
|
+
("content", models.TextField()),
|
|
27
|
+
("version", versionfield.fields.VersionField(default="1.0.0")),
|
|
28
|
+
],
|
|
29
|
+
options={
|
|
30
|
+
"abstract": False,
|
|
31
|
+
},
|
|
32
|
+
),
|
|
33
|
+
migrations.CreateModel(
|
|
34
|
+
name="PrivacyPage",
|
|
35
|
+
fields=[
|
|
36
|
+
(
|
|
37
|
+
"id",
|
|
38
|
+
models.BigAutoField(
|
|
39
|
+
auto_created=True,
|
|
40
|
+
primary_key=True,
|
|
41
|
+
serialize=False,
|
|
42
|
+
verbose_name="ID",
|
|
43
|
+
),
|
|
44
|
+
),
|
|
45
|
+
("title", models.CharField(max_length=100)),
|
|
46
|
+
("content", models.TextField()),
|
|
47
|
+
("version", versionfield.fields.VersionField(default="1.0.0")),
|
|
48
|
+
],
|
|
49
|
+
options={
|
|
50
|
+
"abstract": False,
|
|
51
|
+
},
|
|
52
|
+
),
|
|
53
|
+
migrations.CreateModel(
|
|
54
|
+
name="StaticPage",
|
|
55
|
+
fields=[
|
|
56
|
+
(
|
|
57
|
+
"id",
|
|
58
|
+
models.BigAutoField(
|
|
59
|
+
auto_created=True,
|
|
60
|
+
primary_key=True,
|
|
61
|
+
serialize=False,
|
|
62
|
+
verbose_name="ID",
|
|
63
|
+
),
|
|
64
|
+
),
|
|
65
|
+
("title", models.CharField(max_length=100)),
|
|
66
|
+
("content", models.TextField()),
|
|
67
|
+
],
|
|
68
|
+
options={
|
|
69
|
+
"abstract": False,
|
|
70
|
+
},
|
|
71
|
+
),
|
|
72
|
+
migrations.CreateModel(
|
|
73
|
+
name="StaticVersionedPage",
|
|
74
|
+
fields=[
|
|
75
|
+
(
|
|
76
|
+
"id",
|
|
77
|
+
models.BigAutoField(
|
|
78
|
+
auto_created=True,
|
|
79
|
+
primary_key=True,
|
|
80
|
+
serialize=False,
|
|
81
|
+
verbose_name="ID",
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
("title", models.CharField(max_length=100)),
|
|
85
|
+
("content", models.TextField()),
|
|
86
|
+
("version", versionfield.fields.VersionField(default="1.0.0")),
|
|
87
|
+
],
|
|
88
|
+
options={
|
|
89
|
+
"abstract": False,
|
|
90
|
+
},
|
|
91
|
+
),
|
|
92
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Generated by Django 4.2.8 on 2023-12-29 22:57
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("cms", "0001_initial"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.DeleteModel(
|
|
13
|
+
name="StaticVersionedPage",
|
|
14
|
+
),
|
|
15
|
+
migrations.AlterModelOptions(
|
|
16
|
+
name="licensepage",
|
|
17
|
+
options={
|
|
18
|
+
"verbose_name": "License page",
|
|
19
|
+
"verbose_name_plural": "License pages",
|
|
20
|
+
},
|
|
21
|
+
),
|
|
22
|
+
migrations.AlterModelOptions(
|
|
23
|
+
name="privacypage",
|
|
24
|
+
options={
|
|
25
|
+
"verbose_name": "Privacy page",
|
|
26
|
+
"verbose_name_plural": "Privacy pages",
|
|
27
|
+
},
|
|
28
|
+
),
|
|
29
|
+
migrations.AlterModelOptions(
|
|
30
|
+
name="staticpage",
|
|
31
|
+
options={
|
|
32
|
+
"verbose_name": "Static page",
|
|
33
|
+
"verbose_name_plural": "Static pages",
|
|
34
|
+
},
|
|
35
|
+
),
|
|
36
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Generated by Django 4.2.9 on 2024-01-29 00:51
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("cms", "0002_delete_staticversionedpage_alter_licensepage_options_and_more"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.RenameModel(
|
|
13
|
+
old_name="LicensePage",
|
|
14
|
+
new_name="TermsConditionsPage",
|
|
15
|
+
),
|
|
16
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Generated by Django 4.2.9 on 2024-01-30 16:48
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("cms", "0003_rename_licensepage_termsconditionspage"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AlterModelOptions(
|
|
13
|
+
name="termsconditionspage",
|
|
14
|
+
options={
|
|
15
|
+
"verbose_name": "Terms and conditions page",
|
|
16
|
+
"verbose_name_plural": "Terms and conditions pages",
|
|
17
|
+
},
|
|
18
|
+
),
|
|
19
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Generated by Django 4.2.9 on 2024-02-06 07:37
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
import versionfield.fields
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
dependencies = [
|
|
9
|
+
("cms", "0004_alter_termsconditionspage_options"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name="privacypage",
|
|
15
|
+
name="version",
|
|
16
|
+
field=versionfield.fields.VersionField(default="1.0.0", unique=True),
|
|
17
|
+
),
|
|
18
|
+
migrations.AlterField(
|
|
19
|
+
model_name="termsconditionspage",
|
|
20
|
+
name="version",
|
|
21
|
+
field=versionfield.fields.VersionField(default="1.0.0", unique=True),
|
|
22
|
+
),
|
|
23
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Generated by Django 4.2.9 on 2024-02-28 13:01
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("cms", "0005_alter_privacypage_version_and_more"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AddField(
|
|
13
|
+
model_name="staticpage",
|
|
14
|
+
name="name",
|
|
15
|
+
field=models.CharField(
|
|
16
|
+
blank=True,
|
|
17
|
+
help_text="Internal name of the page. Leave empty of in doubt.",
|
|
18
|
+
max_length=255,
|
|
19
|
+
null=True,
|
|
20
|
+
),
|
|
21
|
+
),
|
|
22
|
+
]
|