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.
Files changed (49) hide show
  1. conjunto/__init__.py +3 -0
  2. conjunto/admin.py +19 -0
  3. conjunto/api/interfaces.py +400 -0
  4. conjunto/apps.py +9 -0
  5. conjunto/cms/__init__.py +1 -0
  6. conjunto/cms/admin.py +28 -0
  7. conjunto/cms/apps.py +14 -0
  8. conjunto/cms/migrations/0001_initial.py +92 -0
  9. conjunto/cms/migrations/0002_delete_staticversionedpage_alter_licensepage_options_and_more.py +36 -0
  10. conjunto/cms/migrations/0003_rename_licensepage_termsconditionspage.py +16 -0
  11. conjunto/cms/migrations/0004_alter_termsconditionspage_options.py +19 -0
  12. conjunto/cms/migrations/0005_alter_privacypage_version_and_more.py +23 -0
  13. conjunto/cms/migrations/0006_staticpage_name.py +22 -0
  14. conjunto/cms/migrations/__init__.py +0 -0
  15. conjunto/cms/models.py +44 -0
  16. conjunto/cms/tests.py +3 -0
  17. conjunto/cms/views.py +3 -0
  18. conjunto/components/__init__.py +353 -0
  19. conjunto/context_processors.py +21 -0
  20. conjunto/fields.py +15 -0
  21. conjunto/files/storage.py +15 -0
  22. conjunto/forms/__init__.py +213 -0
  23. conjunto/forms/widgets.py +23 -0
  24. conjunto/htmx.py +153 -0
  25. conjunto/http/__init__.py +22 -0
  26. conjunto/management/__init__.py +0 -0
  27. conjunto/management/commands/__init__.py +0 -0
  28. conjunto/management/commands/update_libraries.py +203 -0
  29. conjunto/management/commands/update_permissions.py +53 -0
  30. conjunto/menu/__init__.py +293 -0
  31. conjunto/menus.py +17 -0
  32. conjunto/middleware.py +116 -0
  33. conjunto/migrations/0001_initial.py +92 -0
  34. conjunto/migrations/0002_delete_licensepage_delete_privacypage_and_more.py +24 -0
  35. conjunto/migrations/0003_initial.py +43 -0
  36. conjunto/migrations/__init__.py +0 -0
  37. conjunto/models.py +169 -0
  38. conjunto/tabler.py +41 -0
  39. conjunto/tables.py +227 -0
  40. conjunto/templatetags/__init__.py +0 -0
  41. conjunto/templatetags/conjunto.py +74 -0
  42. conjunto/tools.py +177 -0
  43. conjunto/urls.py +17 -0
  44. conjunto/views.py +788 -0
  45. conjunto-0.0.19.dist-info/LICENSE +7 -0
  46. conjunto-0.0.19.dist-info/METADATA +75 -0
  47. conjunto-0.0.19.dist-info/RECORD +49 -0
  48. conjunto-0.0.19.dist-info/WHEEL +5 -0
  49. conjunto-0.0.19.dist-info/top_level.txt +1 -0
conjunto/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ "Django application framework/helpers using HTMX, tabler.io, tables2, crispy-forms & more."
2
+
3
+ __version__ = "0.0.19"
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
@@ -0,0 +1,9 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class ConjuntoConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "conjunto"
7
+
8
+ def ready(self):
9
+ from . import components # noqa
@@ -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
+ ]