djmvc 0.0.1__tar.gz

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 (73) hide show
  1. djmvc-0.0.1/.gitignore +7 -0
  2. djmvc-0.0.1/PKG-INFO +167 -0
  3. djmvc-0.0.1/README.md +136 -0
  4. djmvc-0.0.1/manage.py +23 -0
  5. djmvc-0.0.1/pyproject.toml +85 -0
  6. djmvc-0.0.1/setup.cfg +4 -0
  7. djmvc-0.0.1/src/djmvc/__init__.py +4 -0
  8. djmvc-0.0.1/src/djmvc/admin.py +3 -0
  9. djmvc-0.0.1/src/djmvc/apps.py +5 -0
  10. djmvc-0.0.1/src/djmvc/clonable.py +11 -0
  11. djmvc-0.0.1/src/djmvc/controller.py +83 -0
  12. djmvc-0.0.1/src/djmvc/management/commands/show_urls.py +211 -0
  13. djmvc-0.0.1/src/djmvc/model.py +39 -0
  14. djmvc-0.0.1/src/djmvc/models.py +3 -0
  15. djmvc-0.0.1/src/djmvc/route.py +42 -0
  16. djmvc-0.0.1/src/djmvc/templatetags/eval.py +154 -0
  17. djmvc-0.0.1/src/djmvc/tests.py +3 -0
  18. djmvc-0.0.1/src/djmvc/view.py +40 -0
  19. djmvc-0.0.1/src/djmvc/views/create.py +13 -0
  20. djmvc-0.0.1/src/djmvc/views/delete.py +15 -0
  21. djmvc-0.0.1/src/djmvc/views/detail.py +93 -0
  22. djmvc-0.0.1/src/djmvc/views/form.py +28 -0
  23. djmvc-0.0.1/src/djmvc/views/generic.py +7 -0
  24. djmvc-0.0.1/src/djmvc/views/list.py +19 -0
  25. djmvc-0.0.1/src/djmvc/views/modelform.py +6 -0
  26. djmvc-0.0.1/src/djmvc/views/tables2.py +106 -0
  27. djmvc-0.0.1/src/djmvc/views/template.py +42 -0
  28. djmvc-0.0.1/src/djmvc/views/update.py +14 -0
  29. djmvc-0.0.1/src/djmvc.egg-info/PKG-INFO +167 -0
  30. djmvc-0.0.1/src/djmvc.egg-info/SOURCES.txt +71 -0
  31. djmvc-0.0.1/src/djmvc.egg-info/dependency_links.txt +1 -0
  32. djmvc-0.0.1/src/djmvc.egg-info/requires.txt +14 -0
  33. djmvc-0.0.1/src/djmvc.egg-info/scm_file_list.json +67 -0
  34. djmvc-0.0.1/src/djmvc.egg-info/scm_version.json +8 -0
  35. djmvc-0.0.1/src/djmvc.egg-info/top_level.txt +4 -0
  36. djmvc-0.0.1/src/djmvc_auth/__init__.py +0 -0
  37. djmvc-0.0.1/src/djmvc_auth/admin.py +3 -0
  38. djmvc-0.0.1/src/djmvc_auth/apps.py +5 -0
  39. djmvc-0.0.1/src/djmvc_auth/controller.py +64 -0
  40. djmvc-0.0.1/src/djmvc_auth/models.py +3 -0
  41. djmvc-0.0.1/src/djmvc_auth/tests.py +3 -0
  42. djmvc-0.0.1/src/djmvc_auth/views.py +40 -0
  43. djmvc-0.0.1/src/djmvc_bulma/__init__.py +0 -0
  44. djmvc-0.0.1/src/djmvc_bulma/admin.py +3 -0
  45. djmvc-0.0.1/src/djmvc_bulma/apps.py +14 -0
  46. djmvc-0.0.1/src/djmvc_bulma/models.py +3 -0
  47. djmvc-0.0.1/src/djmvc_bulma/static/djmvc_bulma/css/style.css +30 -0
  48. djmvc-0.0.1/src/djmvc_bulma/static/djmvc_bulma/js/hamburger.js +70 -0
  49. djmvc-0.0.1/src/djmvc_bulma/static/djmvc_bulma/js/hamburger.test.js +226 -0
  50. djmvc-0.0.1/src/djmvc_bulma/templates/djmvc/_actions_column.html +48 -0
  51. djmvc-0.0.1/src/djmvc_bulma/templates/djmvc/_tables2.html +142 -0
  52. djmvc-0.0.1/src/djmvc_bulma/templates/djmvc/base.html +85 -0
  53. djmvc-0.0.1/src/djmvc_bulma/templates/djmvc/detail.html +49 -0
  54. djmvc-0.0.1/src/djmvc_bulma/templates/djmvc/form.html +74 -0
  55. djmvc-0.0.1/src/djmvc_bulma/templates/djmvc/list.html +44 -0
  56. djmvc-0.0.1/src/djmvc_bulma/templates/home.html +32 -0
  57. djmvc-0.0.1/src/djmvc_bulma/tests.py +3 -0
  58. djmvc-0.0.1/src/djmvc_bulma/views.py +3 -0
  59. djmvc-0.0.1/src/djmvc_example/__init__.py +0 -0
  60. djmvc-0.0.1/src/djmvc_example/asgi.py +16 -0
  61. djmvc-0.0.1/src/djmvc_example/example_urls.py +35 -0
  62. djmvc-0.0.1/src/djmvc_example/migrations/0001_initial.py +132 -0
  63. djmvc-0.0.1/src/djmvc_example/migrations/0002_test_users.py +27 -0
  64. djmvc-0.0.1/src/djmvc_example/migrations/__init__.py +0 -0
  65. djmvc-0.0.1/src/djmvc_example/models.py +5 -0
  66. djmvc-0.0.1/src/djmvc_example/settings.py +129 -0
  67. djmvc-0.0.1/src/djmvc_example/urls.py +44 -0
  68. djmvc-0.0.1/src/djmvc_example/wsgi.py +16 -0
  69. djmvc-0.0.1/tests/test_controller.py +37 -0
  70. djmvc-0.0.1/tests/test_eval.py +20 -0
  71. djmvc-0.0.1/tests/test_routing.py +45 -0
  72. djmvc-0.0.1/tests/test_view.py +19 -0
  73. djmvc-0.0.1/tests/views/test_template.py +18 -0
djmvc-0.0.1/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ __pycache__
2
+ db.sqlite3
3
+ .claude/
4
+ build/
5
+ dist/
6
+ node_modules/
7
+ *egg-info*
djmvc-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: djmvc
3
+ Version: 0.0.1
4
+ Summary: Django CRUD utilities
5
+ Author-email: Your Name <your.email@example.com>
6
+ License: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.8
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: django>=5.1
20
+ Requires-Dist: django-tables2>=2.7
21
+ Requires-Dist: django-crispy-forms>=2.0
22
+ Provides-Extra: bulma
23
+ Requires-Dist: django-crispy-forms>=2.0; extra == "bulma"
24
+ Requires-Dist: crispy-bulma>=0.1; extra == "bulma"
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.0; extra == "dev"
27
+ Requires-Dist: pytest-django>=4.5; extra == "dev"
28
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
29
+ Requires-Dist: black>=23.0; extra == "dev"
30
+ Requires-Dist: ruff>=0.1; extra == "dev"
31
+
32
+ # Faster Django development
33
+
34
+ Get more out of less with a few design patterns:
35
+
36
+ - Skip manual URL routing with MVC pattern and sane defaults
37
+ - Dynamic navigation menus with tagged views
38
+ - Skip `get_context_data` overrides by consuming the `view` object directly in
39
+ templates
40
+ - `{% eval %}` template tag to skip making template tags every time you want to
41
+ call a function or method (like in Jinja2)
42
+ - Secure by default: views allow only superusers by default, it's up to you to
43
+ open permissions as-needed.
44
+
45
+ Install with:
46
+
47
+ ```
48
+ pip install djmvc[bulma]
49
+ ```
50
+
51
+ ## Routing
52
+
53
+ Skip manual URL routing definition by nesting views and controllers:
54
+
55
+ ```python
56
+ from djmvc.controller import Controller
57
+ from djmvc.view import View
58
+
59
+
60
+ # definition by sub-classing
61
+ class SubController(Controller):
62
+ name = 'sub-controller'
63
+ routes = [
64
+ View.clone(
65
+ name='sub-view',
66
+ )
67
+ ]
68
+
69
+
70
+ # or just define by cloning
71
+ Site = Controller.clone(
72
+ name='controller',
73
+ routes=[
74
+ View.clone(
75
+ name='view',
76
+ ),
77
+ SubController,
78
+ ]
79
+ )
80
+
81
+ # define some importable url patterns to include
82
+ urlpatterns = Site().urlpatterns
83
+
84
+ # it will define reverseable urls
85
+ assert reverse('controller:view') == '/controller/view/'
86
+ assert reverse('controller:sub-controller:sub-view') == '/controller/sub-controller/sub-view/'
87
+ ```
88
+
89
+ ## Templates
90
+
91
+ Ain't no way I'm defining get_context_data for everything I add to a view, I
92
+ consider that adding a method or property to the view should make it directly
93
+ accessible from the template, as such, the default TemplateMixin provides a
94
+ `get_context_data` which already returns the `view` as context object.
95
+
96
+ ```python
97
+ from djmvc.views import generic
98
+
99
+ class MyView(generic.TemplateView):
100
+ @property
101
+ def something(self):
102
+ return Some.thing()
103
+ ```
104
+
105
+ Allows directly that in the template: `{{ view.something }}`.
106
+
107
+ Also, ain't no way I'm defining a templatetag for everything, loved the liberty
108
+ Jinja2 gave me, taking it back with Django engine with the eval tag provided by
109
+ djmvc.
110
+
111
+ ```python
112
+ class MyView(generic.TemplateView):
113
+ def something(self, some_var, user):
114
+ return Some.thing(some_var, user)
115
+ ```
116
+
117
+ Becomes available as:
118
+
119
+ ```jinja2
120
+ {% load eval %}
121
+ {% eval view.some_method "some test var" view.request.user as result %}
122
+ {{ result }}
123
+ ```
124
+
125
+ ## Dynamic menus
126
+
127
+ Add any tags you like to your views:
128
+
129
+ ```python
130
+ class YourView(generic.TemplateView):
131
+ tags = ['topbar']
132
+
133
+ def has_permission(self):
134
+ return self.request.user.is_authenticated # the default
135
+ ```
136
+
137
+ And use the `get_tagged_view()` method of the controller to get all views
138
+ tagged `"topbar"` as such:
139
+
140
+ ```jinja2
141
+ {% eval view.root_controller.get_tagged_views 'topbar' request=request as topbar_menu %}
142
+ ```
143
+
144
+ Here, we're passing the `request` kwarg to `get_tagged_views()` which will
145
+ instanciate the view object with passed kwargs and call `has_permission()`
146
+ prior to returning the tagged view.
147
+
148
+ It also works with per-object permissions as such:
149
+
150
+ ```python
151
+ class YourModelDetailView(generic.DetailView):
152
+ tags = ['object']
153
+
154
+ def has_permission(self):
155
+ return (
156
+ self.request.user.is_superuser
157
+ or self.object.owner == self.request.user
158
+ )
159
+ ```
160
+
161
+ Which will make that view show conditionnaly based on the request user, so that
162
+ you can get all the views authorized for a user on a given object in the
163
+ "object" tag as such:
164
+
165
+ ```jinja2
166
+ {% eval view.root_controller.get_tagged_views 'object' request=request object=object as object_menu %}
167
+ ```
djmvc-0.0.1/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # Faster Django development
2
+
3
+ Get more out of less with a few design patterns:
4
+
5
+ - Skip manual URL routing with MVC pattern and sane defaults
6
+ - Dynamic navigation menus with tagged views
7
+ - Skip `get_context_data` overrides by consuming the `view` object directly in
8
+ templates
9
+ - `{% eval %}` template tag to skip making template tags every time you want to
10
+ call a function or method (like in Jinja2)
11
+ - Secure by default: views allow only superusers by default, it's up to you to
12
+ open permissions as-needed.
13
+
14
+ Install with:
15
+
16
+ ```
17
+ pip install djmvc[bulma]
18
+ ```
19
+
20
+ ## Routing
21
+
22
+ Skip manual URL routing definition by nesting views and controllers:
23
+
24
+ ```python
25
+ from djmvc.controller import Controller
26
+ from djmvc.view import View
27
+
28
+
29
+ # definition by sub-classing
30
+ class SubController(Controller):
31
+ name = 'sub-controller'
32
+ routes = [
33
+ View.clone(
34
+ name='sub-view',
35
+ )
36
+ ]
37
+
38
+
39
+ # or just define by cloning
40
+ Site = Controller.clone(
41
+ name='controller',
42
+ routes=[
43
+ View.clone(
44
+ name='view',
45
+ ),
46
+ SubController,
47
+ ]
48
+ )
49
+
50
+ # define some importable url patterns to include
51
+ urlpatterns = Site().urlpatterns
52
+
53
+ # it will define reverseable urls
54
+ assert reverse('controller:view') == '/controller/view/'
55
+ assert reverse('controller:sub-controller:sub-view') == '/controller/sub-controller/sub-view/'
56
+ ```
57
+
58
+ ## Templates
59
+
60
+ Ain't no way I'm defining get_context_data for everything I add to a view, I
61
+ consider that adding a method or property to the view should make it directly
62
+ accessible from the template, as such, the default TemplateMixin provides a
63
+ `get_context_data` which already returns the `view` as context object.
64
+
65
+ ```python
66
+ from djmvc.views import generic
67
+
68
+ class MyView(generic.TemplateView):
69
+ @property
70
+ def something(self):
71
+ return Some.thing()
72
+ ```
73
+
74
+ Allows directly that in the template: `{{ view.something }}`.
75
+
76
+ Also, ain't no way I'm defining a templatetag for everything, loved the liberty
77
+ Jinja2 gave me, taking it back with Django engine with the eval tag provided by
78
+ djmvc.
79
+
80
+ ```python
81
+ class MyView(generic.TemplateView):
82
+ def something(self, some_var, user):
83
+ return Some.thing(some_var, user)
84
+ ```
85
+
86
+ Becomes available as:
87
+
88
+ ```jinja2
89
+ {% load eval %}
90
+ {% eval view.some_method "some test var" view.request.user as result %}
91
+ {{ result }}
92
+ ```
93
+
94
+ ## Dynamic menus
95
+
96
+ Add any tags you like to your views:
97
+
98
+ ```python
99
+ class YourView(generic.TemplateView):
100
+ tags = ['topbar']
101
+
102
+ def has_permission(self):
103
+ return self.request.user.is_authenticated # the default
104
+ ```
105
+
106
+ And use the `get_tagged_view()` method of the controller to get all views
107
+ tagged `"topbar"` as such:
108
+
109
+ ```jinja2
110
+ {% eval view.root_controller.get_tagged_views 'topbar' request=request as topbar_menu %}
111
+ ```
112
+
113
+ Here, we're passing the `request` kwarg to `get_tagged_views()` which will
114
+ instanciate the view object with passed kwargs and call `has_permission()`
115
+ prior to returning the tagged view.
116
+
117
+ It also works with per-object permissions as such:
118
+
119
+ ```python
120
+ class YourModelDetailView(generic.DetailView):
121
+ tags = ['object']
122
+
123
+ def has_permission(self):
124
+ return (
125
+ self.request.user.is_superuser
126
+ or self.object.owner == self.request.user
127
+ )
128
+ ```
129
+
130
+ Which will make that view show conditionnaly based on the request user, so that
131
+ you can get all the views authorized for a user on a given object in the
132
+ "object" tag as such:
133
+
134
+ ```jinja2
135
+ {% eval view.root_controller.get_tagged_views 'object' request=request object=object as object_menu %}
136
+ ```
djmvc-0.0.1/manage.py ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env python
2
+ """Django's command-line utility for administrative tasks."""
3
+
4
+ import os
5
+ import sys
6
+
7
+
8
+ def main():
9
+ """Run administrative tasks."""
10
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djmvc_example.settings")
11
+ try:
12
+ from django.core.management import execute_from_command_line
13
+ except ImportError as exc:
14
+ raise ImportError(
15
+ "Couldn't import Django. Are you sure it's installed and "
16
+ "available on your PYTHONPATH environment variable? Did you "
17
+ "forget to activate a virtual environment?"
18
+ ) from exc
19
+ execute_from_command_line(sys.argv)
20
+
21
+
22
+ if __name__ == "__main__":
23
+ main()
@@ -0,0 +1,85 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "setuptools-scm>=8"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "djmvc"
7
+ dynamic = ["version"]
8
+ description = "Django CRUD utilities"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Your Name", email = "your.email@example.com"}
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.14",
26
+ ]
27
+ dependencies = [
28
+ "django>=5.1",
29
+ "django-tables2>=2.7",
30
+ "django-crispy-forms>=2.0",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ bulma = [
35
+ "django-crispy-forms>=2.0",
36
+ "crispy-bulma>=0.1",
37
+ ]
38
+ dev = [
39
+ "pytest>=7.0",
40
+ "pytest-django>=4.5",
41
+ "pytest-cov>=4.0",
42
+ "black>=23.0",
43
+ "ruff>=0.1",
44
+ ]
45
+
46
+ [tool.setuptools]
47
+ package-dir = {"" = "src"}
48
+
49
+ [tool.setuptools.packages.find]
50
+ where = ["src"]
51
+ include = ["*"]
52
+ namespaces = false
53
+
54
+ [tool.setuptools_scm]
55
+ # Git-based versioning configuration
56
+ # This will automatically determine the version from git tags
57
+ # Tag format should be: v1.0.0, v0.1.0, etc.
58
+ fallback_version = "0.0.0"
59
+
60
+ [tool.pytest.ini_options]
61
+ testpaths = ["tests"]
62
+ python_files = ["test_*.py"]
63
+ python_classes = ["Test*"]
64
+ python_functions = ["test_*"]
65
+ DJANGO_SETTINGS_MODULE = "djmvc_example.settings"
66
+ pythonpath = ["src/djmvc_example"]
67
+ addopts = [
68
+ "--reuse-db",
69
+ "--strict-markers",
70
+ "-ra",
71
+ ]
72
+ markers = [
73
+ "bulma: marks tests specific to the Bulma frontend",
74
+ "bootstrap: marks tests specific to the Bootstrap frontend",
75
+ "django_db: mark test that requires Django database",
76
+ ]
77
+
78
+ [tool.black]
79
+ line-length = 88
80
+ target-version = ['py38']
81
+ include = '\.pyi?$'
82
+
83
+ [tool.ruff]
84
+ line-length = 88
85
+ target-version = "py38"
djmvc-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from .controller import Controller
2
+ from .view import View
3
+ from .model import ModelController, ModelMixin
4
+ from .views import generic
@@ -0,0 +1,3 @@
1
+ from django.contrib import admin
2
+
3
+ # Register your models here.
@@ -0,0 +1,5 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjmvcConfig(AppConfig):
5
+ name = "djmvc"
@@ -0,0 +1,11 @@
1
+
2
+
3
+ class Clonable:
4
+ @classmethod
5
+ def clone(cls, *mixins, **attributes):
6
+ """Return a subclass with the given attributes.
7
+
8
+ If a model is found, it will prefix the class name with the model.
9
+ """
10
+ name = cls.__name__
11
+ return type(name, (cls,) + mixins, attributes)
@@ -0,0 +1,83 @@
1
+ from django.urls import include, path
2
+
3
+ from .clonable import Clonable
4
+ from .route import Route
5
+
6
+
7
+ class Routes(list):
8
+ def __init__(self, controller, routes):
9
+ self.controller = controller
10
+
11
+ for route in routes:
12
+ route.controller = self.controller
13
+
14
+ routes = [
15
+ route() if isinstance(route, type) else route
16
+ for route in routes
17
+ ]
18
+
19
+ super().__init__(routes)
20
+
21
+ def __getitem__(self, codename_or_index):
22
+ try:
23
+ return super().__getitem__(codename_or_index)
24
+ except TypeError:
25
+ for route in self:
26
+ if route.codename == codename_or_index:
27
+ return route
28
+ raise Exception()
29
+
30
+
31
+ class Controller(Clonable, Route):
32
+ def __init__(self, *args, **kwargs):
33
+ super().__init__(*args, **kwargs)
34
+ if not isinstance(self.routes, Routes):
35
+ self.routes = Routes(self, self.routes)
36
+
37
+ @property
38
+ def codename(self):
39
+ return super().codename.replace('controller', '')
40
+
41
+ def get_tagged_views(self, tag, **kwargs):
42
+ def process(controller):
43
+ views = []
44
+ for route in controller.routes:
45
+ if isinstance(route, Controller):
46
+ views += process(route)
47
+ continue
48
+
49
+ if tag not in getattr(route, 'tags', []):
50
+ continue
51
+
52
+ view = type(route)(**kwargs)
53
+ if view.has_permission():
54
+ views.append(view)
55
+ return views
56
+ return process(self)
57
+
58
+ @property
59
+ def root(self):
60
+ controller = getattr(self, 'controller', None)
61
+ if not controller:
62
+ return self
63
+
64
+ while hasattr(controller, 'controller'):
65
+ controller = controller.controller
66
+ return controller
67
+
68
+ @property
69
+ def urlpatterns(self):
70
+ patterns = []
71
+
72
+ for route in self.routes:
73
+ patterns += route.urlpatterns
74
+
75
+ return [
76
+ path(
77
+ self.urlpath,
78
+ include(
79
+ (patterns, self.urlname),
80
+ namespace=self.urlname,
81
+ ),
82
+ )
83
+ ]