humanoid-crud 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,172 @@
1
+ Metadata-Version: 2.4
2
+ Name: humanoid-crud
3
+ Version: 0.1.0
4
+ Summary: One-line CRUD registration for Django REST Framework. Built on top of DRF's mixins/generics so you never have to write a Serializer, ViewSet, or router.register() by hand for the common case.
5
+ Author: Your Name
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/yourname/humanoid-crud
8
+ Project-URL: Repository, https://github.com/yourname/humanoid-crud
9
+ Keywords: django,django-rest-framework,drf,crud,api,rest
10
+ Classifier: Framework :: Django
11
+ Classifier: Framework :: Django :: 4
12
+ Classifier: Framework :: Django :: 5
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: djangorestframework>=3.14
20
+ Provides-Extra: filters
21
+ Requires-Dist: django-filter>=23.0; extra == "filters"
22
+ Dynamic: license-file
23
+
24
+ # humanoid-crud
25
+
26
+ A one-line CRUD registrar for Django REST Framework. Built **on top of** DRF's
27
+ mixins/generics (it generates real `ModelSerializer` and `ModelViewSet`
28
+ classes for you) — the point isn't to avoid mixins, it's that you never have
29
+ to write or stack them by hand.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ pip install humanoid-crud
35
+ ```
36
+
37
+ If you want `filterset_fields` (exact-match filtering), install the extra:
38
+
39
+ ```bash
40
+ pip install humanoid-crud[filters]
41
+ ```
42
+
43
+ Note: the **package name on PyPI** is `humanoid-crud` (hyphen, matching PyPI
44
+ convention), but you **import it in Python** as `humanoid_crud` (underscore,
45
+ since hyphens aren't valid in Python identifiers). This is normal — the same
46
+ is true for packages like `django-rest-framework` → `import rest_framework`.
47
+
48
+ ## The problem this solves
49
+
50
+ The normal DRF CRUD recipe for one model is:
51
+
52
+ ```python
53
+ # serializers.py
54
+ class BookSerializer(serializers.ModelSerializer):
55
+ class Meta:
56
+ model = Book
57
+ fields = '__all__'
58
+
59
+ # views.py
60
+ class BookViewSet(viewsets.ModelViewSet):
61
+ queryset = Book.objects.all()
62
+ serializer_class = BookSerializer
63
+
64
+ # urls.py
65
+ router.register('books', BookViewSet)
66
+ ```
67
+
68
+ Three files, three pieces of boilerplate, for every single model.
69
+ `humanoid_crud` collapses that to:
70
+
71
+ ```python
72
+ import humanoid_crud
73
+ humanoid_crud.register(Book)
74
+ ```
75
+
76
+ ## Quick start
77
+
78
+ 1. `pip install humanoid-crud`
79
+ 2. Add `'rest_framework'` to `INSTALLED_APPS` in `settings.py` if it isn't
80
+ already there.
81
+ 3. Create a small file — e.g. `myapp/api.py` — where you register your
82
+ models:
83
+
84
+ ```python
85
+ # myapp/api.py
86
+ import humanoid_crud
87
+ from myapp.models import Book, Author
88
+
89
+ humanoid_crud.register(Author)
90
+ humanoid_crud.register(Book)
91
+ ```
92
+
93
+ 4. In your project's `urls.py`, import that file (so the registrations run)
94
+ and add the generated routes:
95
+
96
+ ```python
97
+ from django.contrib import admin
98
+ from django.urls import path
99
+
100
+ from myapp import api # running this import triggers registration
101
+ import humanoid_crud
102
+
103
+ urlpatterns = [
104
+ path('admin/', admin.site.urls),
105
+ ] + humanoid_crud.urls
106
+ ```
107
+
108
+ That's it. You now have, for each model:
109
+
110
+ | Method | URL | Action |
111
+ |--------|-------------------|-----------------|
112
+ | GET | `/books/` | list |
113
+ | POST | `/books/` | create |
114
+ | GET | `/books/<pk>/` | retrieve |
115
+ | PUT | `/books/<pk>/` | full update |
116
+ | PATCH | `/books/<pk>/` | partial update |
117
+ | DELETE | `/books/<pk>/` | delete |
118
+
119
+ All validation, 404 handling, and FK integrity checks are standard DRF
120
+ behavior — nothing is hidden or changed, just wired up for you.
121
+
122
+ ## Going beyond the default
123
+
124
+ Every option is optional — pass only what you need:
125
+
126
+ ```python
127
+ from rest_framework.permissions import IsAuthenticated
128
+
129
+ humanoid_crud.register(
130
+ Book,
131
+ fields=["title", "author", "price"], # restrict exposed fields
132
+ read_only_fields=["created_at"],
133
+ permissions=[IsAuthenticated], # default is AllowAny — lock this down!
134
+ search_fields=["title"], # enables ?search=
135
+ ordering_fields=["price", "created_at"], # enables ?ordering=
136
+ filterset_fields=["author"], # exact-match filtering (needs the [filters] extra)
137
+ lookup="slug", # use a non-pk field in the URL
138
+ url="library-books", # custom URL segment
139
+ )
140
+ ```
141
+
142
+ ### Escape hatch: bring your own serializer or viewset
143
+
144
+ If one model needs custom logic (e.g. a custom `create()`, extra validation,
145
+ nested writes), you don't lose `humanoid_crud`'s URL registration — just hand
146
+ it your own class:
147
+
148
+ ```python
149
+ class BookViewSet(viewsets.ModelViewSet):
150
+ queryset = Book.objects.all()
151
+ serializer_class = BookSerializer
152
+
153
+ def perform_create(self, serializer):
154
+ serializer.save(added_by=self.request.user)
155
+
156
+ humanoid_crud.register(Book, viewset_class=BookViewSet)
157
+ ```
158
+
159
+ The generated classes are ordinary `ModelSerializer` / `ModelViewSet`
160
+ subclasses — if you ever outgrow the one-liner for a specific model, you
161
+ write that one model the normal DRF way and `humanoid_crud` still handles
162
+ the routing.
163
+
164
+ ## Important: default permissions are wide open
165
+
166
+ If you don't pass `permissions=[...]`, every registered model defaults to
167
+ `AllowAny` — anyone can read and write it, no login required. Fine for local
168
+ prototyping. **Set explicit permissions before deploying anything real.**
169
+
170
+ ## License
171
+
172
+ MIT
@@ -0,0 +1,149 @@
1
+ # humanoid-crud
2
+
3
+ A one-line CRUD registrar for Django REST Framework. Built **on top of** DRF's
4
+ mixins/generics (it generates real `ModelSerializer` and `ModelViewSet`
5
+ classes for you) — the point isn't to avoid mixins, it's that you never have
6
+ to write or stack them by hand.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install humanoid-crud
12
+ ```
13
+
14
+ If you want `filterset_fields` (exact-match filtering), install the extra:
15
+
16
+ ```bash
17
+ pip install humanoid-crud[filters]
18
+ ```
19
+
20
+ Note: the **package name on PyPI** is `humanoid-crud` (hyphen, matching PyPI
21
+ convention), but you **import it in Python** as `humanoid_crud` (underscore,
22
+ since hyphens aren't valid in Python identifiers). This is normal — the same
23
+ is true for packages like `django-rest-framework` → `import rest_framework`.
24
+
25
+ ## The problem this solves
26
+
27
+ The normal DRF CRUD recipe for one model is:
28
+
29
+ ```python
30
+ # serializers.py
31
+ class BookSerializer(serializers.ModelSerializer):
32
+ class Meta:
33
+ model = Book
34
+ fields = '__all__'
35
+
36
+ # views.py
37
+ class BookViewSet(viewsets.ModelViewSet):
38
+ queryset = Book.objects.all()
39
+ serializer_class = BookSerializer
40
+
41
+ # urls.py
42
+ router.register('books', BookViewSet)
43
+ ```
44
+
45
+ Three files, three pieces of boilerplate, for every single model.
46
+ `humanoid_crud` collapses that to:
47
+
48
+ ```python
49
+ import humanoid_crud
50
+ humanoid_crud.register(Book)
51
+ ```
52
+
53
+ ## Quick start
54
+
55
+ 1. `pip install humanoid-crud`
56
+ 2. Add `'rest_framework'` to `INSTALLED_APPS` in `settings.py` if it isn't
57
+ already there.
58
+ 3. Create a small file — e.g. `myapp/api.py` — where you register your
59
+ models:
60
+
61
+ ```python
62
+ # myapp/api.py
63
+ import humanoid_crud
64
+ from myapp.models import Book, Author
65
+
66
+ humanoid_crud.register(Author)
67
+ humanoid_crud.register(Book)
68
+ ```
69
+
70
+ 4. In your project's `urls.py`, import that file (so the registrations run)
71
+ and add the generated routes:
72
+
73
+ ```python
74
+ from django.contrib import admin
75
+ from django.urls import path
76
+
77
+ from myapp import api # running this import triggers registration
78
+ import humanoid_crud
79
+
80
+ urlpatterns = [
81
+ path('admin/', admin.site.urls),
82
+ ] + humanoid_crud.urls
83
+ ```
84
+
85
+ That's it. You now have, for each model:
86
+
87
+ | Method | URL | Action |
88
+ |--------|-------------------|-----------------|
89
+ | GET | `/books/` | list |
90
+ | POST | `/books/` | create |
91
+ | GET | `/books/<pk>/` | retrieve |
92
+ | PUT | `/books/<pk>/` | full update |
93
+ | PATCH | `/books/<pk>/` | partial update |
94
+ | DELETE | `/books/<pk>/` | delete |
95
+
96
+ All validation, 404 handling, and FK integrity checks are standard DRF
97
+ behavior — nothing is hidden or changed, just wired up for you.
98
+
99
+ ## Going beyond the default
100
+
101
+ Every option is optional — pass only what you need:
102
+
103
+ ```python
104
+ from rest_framework.permissions import IsAuthenticated
105
+
106
+ humanoid_crud.register(
107
+ Book,
108
+ fields=["title", "author", "price"], # restrict exposed fields
109
+ read_only_fields=["created_at"],
110
+ permissions=[IsAuthenticated], # default is AllowAny — lock this down!
111
+ search_fields=["title"], # enables ?search=
112
+ ordering_fields=["price", "created_at"], # enables ?ordering=
113
+ filterset_fields=["author"], # exact-match filtering (needs the [filters] extra)
114
+ lookup="slug", # use a non-pk field in the URL
115
+ url="library-books", # custom URL segment
116
+ )
117
+ ```
118
+
119
+ ### Escape hatch: bring your own serializer or viewset
120
+
121
+ If one model needs custom logic (e.g. a custom `create()`, extra validation,
122
+ nested writes), you don't lose `humanoid_crud`'s URL registration — just hand
123
+ it your own class:
124
+
125
+ ```python
126
+ class BookViewSet(viewsets.ModelViewSet):
127
+ queryset = Book.objects.all()
128
+ serializer_class = BookSerializer
129
+
130
+ def perform_create(self, serializer):
131
+ serializer.save(added_by=self.request.user)
132
+
133
+ humanoid_crud.register(Book, viewset_class=BookViewSet)
134
+ ```
135
+
136
+ The generated classes are ordinary `ModelSerializer` / `ModelViewSet`
137
+ subclasses — if you ever outgrow the one-liner for a specific model, you
138
+ write that one model the normal DRF way and `humanoid_crud` still handles
139
+ the routing.
140
+
141
+ ## Important: default permissions are wide open
142
+
143
+ If you don't pass `permissions=[...]`, every registered model defaults to
144
+ `AllowAny` — anyone can read and write it, no login required. Fine for local
145
+ prototyping. **Set explicit permissions before deploying anything real.**
146
+
147
+ ## License
148
+
149
+ MIT
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "humanoid-crud"
7
+ version = "0.1.0"
8
+ description = "One-line CRUD registration for Django REST Framework. Built on top of DRF's mixins/generics so you never have to write a Serializer, ViewSet, or router.register() by hand for the common case."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Your Name" }
14
+ ]
15
+ keywords = ["django", "django-rest-framework", "drf", "crud", "api", "rest"]
16
+ classifiers = [
17
+ "Framework :: Django",
18
+ "Framework :: Django :: 4",
19
+ "Framework :: Django :: 5",
20
+ "Programming Language :: Python :: 3",
21
+ "Intended Audience :: Developers",
22
+ "Topic :: Software Development :: Libraries :: Python Modules",
23
+ ]
24
+ dependencies = [
25
+ "djangorestframework>=3.14",
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ filters = ["django-filter>=23.0"]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/yourname/humanoid-crud"
33
+ Repository = "https://github.com/yourname/humanoid-crud"
34
+
35
+ # Tell setuptools the package lives under src/, named humanoid_crud
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ from .registry import register, router, _registry as registry # noqa: F401
2
+
3
+ __version__ = "0.1.0"
4
+
5
+
6
+ def __getattr__(name):
7
+ # `humanoid_crud.urls` must be evaluated lazily, not at import time —
8
+ # otherwise it gets cached empty before any register() calls happen.
9
+ # This module-level __getattr__ (PEP 562) makes `import humanoid_crud;
10
+ # humanoid_crud.urls` always reflect the router's *current* state.
11
+ if name == "urls":
12
+ return router.urls
13
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,178 @@
1
+ """
2
+ HumanoidCRUD — a one-line CRUD registrar built on top of DRF's mixins/generics.
3
+
4
+ Goal: you should never have to write a Serializer, a ViewSet, or a router.register()
5
+ line by hand for the common case. Everything is inferred from the model, with
6
+ optional overrides for when you need more control.
7
+
8
+ Usage (the entire API, in your urls.py, api.py, or apps.py):
9
+
10
+ import humanoid_crud
11
+ humanoid_crud.register(Book)
12
+
13
+ That's it. GET/POST /books/, GET/PUT/PATCH/DELETE /books/<pk>/ all work.
14
+
15
+ Overrides when you need them:
16
+
17
+ humanoid_crud.register(
18
+ Book,
19
+ fields=["title", "author", "price"], # restrict serializer fields
20
+ read_only_fields=["created_at"],
21
+ permissions=[IsAuthenticatedOrReadOnly],
22
+ search_fields=["title", "author"],
23
+ ordering_fields=["price", "created_at"],
24
+ lookup="slug", # use a non-pk field in the URL
25
+ url="library-books", # custom URL segment
26
+ )
27
+ """
28
+ from __future__ import annotations
29
+
30
+ from rest_framework import serializers, viewsets, permissions as drf_permissions
31
+ from rest_framework.routers import DefaultRouter
32
+
33
+ # A single shared router. Every call to register() adds to this.
34
+ # The project's urls.py just does: urlpatterns += humanoid_crud.urls
35
+ router = DefaultRouter()
36
+
37
+ # Keep track of what's registered, mostly useful for introspection/debugging.
38
+ _registry: dict[str, dict] = {}
39
+
40
+
41
+ def _default_url_name(model) -> str:
42
+ """books, not book — DRF routers expect a plural-ish URL segment."""
43
+ name = model._meta.model_name # e.g. "book"
44
+ return name if name.endswith("s") else f"{name}s"
45
+
46
+
47
+ def _make_serializer(model, *, fields=None, read_only_fields=None, extra_kwargs=None):
48
+ """Build a ModelSerializer class on the fly. This is the auto-inferred part."""
49
+ meta_attrs = {
50
+ "model": model,
51
+ "fields": fields or "__all__",
52
+ }
53
+ if read_only_fields:
54
+ meta_attrs["read_only_fields"] = read_only_fields
55
+ if extra_kwargs:
56
+ meta_attrs["extra_kwargs"] = extra_kwargs
57
+
58
+ Meta = type("Meta", (), meta_attrs)
59
+ serializer_cls = type(
60
+ f"{model.__name__}AutoSerializer",
61
+ (serializers.ModelSerializer,),
62
+ {"Meta": Meta},
63
+ )
64
+ return serializer_cls
65
+
66
+
67
+ def _make_viewset(
68
+ model,
69
+ serializer_cls,
70
+ *,
71
+ queryset=None,
72
+ permissions=None,
73
+ search_fields=None,
74
+ ordering_fields=None,
75
+ filterset_fields=None,
76
+ lookup="pk",
77
+ ):
78
+ """Build a ModelViewSet — this is the part that replaces hand-stacked mixins."""
79
+ from rest_framework import filters as drf_filters
80
+
81
+ attrs = {
82
+ "queryset": queryset if queryset is not None else model.objects.all(),
83
+ "serializer_class": serializer_cls,
84
+ "lookup_field": lookup,
85
+ "permission_classes": permissions or [drf_permissions.AllowAny],
86
+ }
87
+
88
+ filter_backends = []
89
+
90
+ if search_fields:
91
+ attrs["search_fields"] = search_fields
92
+ filter_backends.append(drf_filters.SearchFilter)
93
+ if ordering_fields:
94
+ attrs["ordering_fields"] = ordering_fields
95
+ filter_backends.append(drf_filters.OrderingFilter)
96
+ if filterset_fields:
97
+ # django-filter is an optional dependency — only import if actually used.
98
+ try:
99
+ from django_filters.rest_framework import DjangoFilterBackend
100
+ except ImportError as exc:
101
+ raise ImportError(
102
+ "filterset_fields requires django-filter. "
103
+ "Install it with: pip install django-filter"
104
+ ) from exc
105
+ attrs["filterset_fields"] = filterset_fields
106
+ filter_backends.append(DjangoFilterBackend)
107
+
108
+ if filter_backends:
109
+ attrs["filter_backends"] = filter_backends
110
+
111
+ viewset_cls = type(
112
+ f"{model.__name__}AutoViewSet",
113
+ (viewsets.ModelViewSet,),
114
+ attrs,
115
+ )
116
+ return viewset_cls
117
+
118
+
119
+ def register(
120
+ model,
121
+ *,
122
+ fields=None,
123
+ read_only_fields=None,
124
+ extra_kwargs=None,
125
+ queryset=None,
126
+ permissions=None,
127
+ search_fields=None,
128
+ ordering_fields=None,
129
+ filterset_fields=None,
130
+ lookup="pk",
131
+ url=None,
132
+ serializer_class=None,
133
+ viewset_class=None,
134
+ ):
135
+ """
136
+ Register full CRUD for `model` in one call.
137
+
138
+ Required:
139
+ model: a Django model class.
140
+
141
+ Everything else is optional. If you don't pass anything, you get a
142
+ sensible default: all fields exposed, AllowAny permissions, pk lookup,
143
+ URL inferred from the model name.
144
+
145
+ Escape hatches:
146
+ serializer_class / viewset_class: pass your own hand-written class
147
+ if auto-inference isn't enough for this one model. HumanoidCRUD will
148
+ still handle the router registration for you.
149
+ """
150
+ url_name = url or _default_url_name(model)
151
+
152
+ serializer_cls = serializer_class or _make_serializer(
153
+ model,
154
+ fields=fields,
155
+ read_only_fields=read_only_fields,
156
+ extra_kwargs=extra_kwargs,
157
+ )
158
+
159
+ viewset_cls = viewset_class or _make_viewset(
160
+ model,
161
+ serializer_cls,
162
+ queryset=queryset,
163
+ permissions=permissions,
164
+ search_fields=search_fields,
165
+ ordering_fields=ordering_fields,
166
+ filterset_fields=filterset_fields,
167
+ lookup=lookup,
168
+ )
169
+
170
+ router.register(url_name, viewset_cls, basename=url_name)
171
+
172
+ _registry[url_name] = {
173
+ "model": model,
174
+ "serializer": serializer_cls,
175
+ "viewset": viewset_cls,
176
+ }
177
+
178
+ return viewset_cls # handy if caller wants to extend it further
@@ -0,0 +1,172 @@
1
+ Metadata-Version: 2.4
2
+ Name: humanoid-crud
3
+ Version: 0.1.0
4
+ Summary: One-line CRUD registration for Django REST Framework. Built on top of DRF's mixins/generics so you never have to write a Serializer, ViewSet, or router.register() by hand for the common case.
5
+ Author: Your Name
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/yourname/humanoid-crud
8
+ Project-URL: Repository, https://github.com/yourname/humanoid-crud
9
+ Keywords: django,django-rest-framework,drf,crud,api,rest
10
+ Classifier: Framework :: Django
11
+ Classifier: Framework :: Django :: 4
12
+ Classifier: Framework :: Django :: 5
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: djangorestframework>=3.14
20
+ Provides-Extra: filters
21
+ Requires-Dist: django-filter>=23.0; extra == "filters"
22
+ Dynamic: license-file
23
+
24
+ # humanoid-crud
25
+
26
+ A one-line CRUD registrar for Django REST Framework. Built **on top of** DRF's
27
+ mixins/generics (it generates real `ModelSerializer` and `ModelViewSet`
28
+ classes for you) — the point isn't to avoid mixins, it's that you never have
29
+ to write or stack them by hand.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ pip install humanoid-crud
35
+ ```
36
+
37
+ If you want `filterset_fields` (exact-match filtering), install the extra:
38
+
39
+ ```bash
40
+ pip install humanoid-crud[filters]
41
+ ```
42
+
43
+ Note: the **package name on PyPI** is `humanoid-crud` (hyphen, matching PyPI
44
+ convention), but you **import it in Python** as `humanoid_crud` (underscore,
45
+ since hyphens aren't valid in Python identifiers). This is normal — the same
46
+ is true for packages like `django-rest-framework` → `import rest_framework`.
47
+
48
+ ## The problem this solves
49
+
50
+ The normal DRF CRUD recipe for one model is:
51
+
52
+ ```python
53
+ # serializers.py
54
+ class BookSerializer(serializers.ModelSerializer):
55
+ class Meta:
56
+ model = Book
57
+ fields = '__all__'
58
+
59
+ # views.py
60
+ class BookViewSet(viewsets.ModelViewSet):
61
+ queryset = Book.objects.all()
62
+ serializer_class = BookSerializer
63
+
64
+ # urls.py
65
+ router.register('books', BookViewSet)
66
+ ```
67
+
68
+ Three files, three pieces of boilerplate, for every single model.
69
+ `humanoid_crud` collapses that to:
70
+
71
+ ```python
72
+ import humanoid_crud
73
+ humanoid_crud.register(Book)
74
+ ```
75
+
76
+ ## Quick start
77
+
78
+ 1. `pip install humanoid-crud`
79
+ 2. Add `'rest_framework'` to `INSTALLED_APPS` in `settings.py` if it isn't
80
+ already there.
81
+ 3. Create a small file — e.g. `myapp/api.py` — where you register your
82
+ models:
83
+
84
+ ```python
85
+ # myapp/api.py
86
+ import humanoid_crud
87
+ from myapp.models import Book, Author
88
+
89
+ humanoid_crud.register(Author)
90
+ humanoid_crud.register(Book)
91
+ ```
92
+
93
+ 4. In your project's `urls.py`, import that file (so the registrations run)
94
+ and add the generated routes:
95
+
96
+ ```python
97
+ from django.contrib import admin
98
+ from django.urls import path
99
+
100
+ from myapp import api # running this import triggers registration
101
+ import humanoid_crud
102
+
103
+ urlpatterns = [
104
+ path('admin/', admin.site.urls),
105
+ ] + humanoid_crud.urls
106
+ ```
107
+
108
+ That's it. You now have, for each model:
109
+
110
+ | Method | URL | Action |
111
+ |--------|-------------------|-----------------|
112
+ | GET | `/books/` | list |
113
+ | POST | `/books/` | create |
114
+ | GET | `/books/<pk>/` | retrieve |
115
+ | PUT | `/books/<pk>/` | full update |
116
+ | PATCH | `/books/<pk>/` | partial update |
117
+ | DELETE | `/books/<pk>/` | delete |
118
+
119
+ All validation, 404 handling, and FK integrity checks are standard DRF
120
+ behavior — nothing is hidden or changed, just wired up for you.
121
+
122
+ ## Going beyond the default
123
+
124
+ Every option is optional — pass only what you need:
125
+
126
+ ```python
127
+ from rest_framework.permissions import IsAuthenticated
128
+
129
+ humanoid_crud.register(
130
+ Book,
131
+ fields=["title", "author", "price"], # restrict exposed fields
132
+ read_only_fields=["created_at"],
133
+ permissions=[IsAuthenticated], # default is AllowAny — lock this down!
134
+ search_fields=["title"], # enables ?search=
135
+ ordering_fields=["price", "created_at"], # enables ?ordering=
136
+ filterset_fields=["author"], # exact-match filtering (needs the [filters] extra)
137
+ lookup="slug", # use a non-pk field in the URL
138
+ url="library-books", # custom URL segment
139
+ )
140
+ ```
141
+
142
+ ### Escape hatch: bring your own serializer or viewset
143
+
144
+ If one model needs custom logic (e.g. a custom `create()`, extra validation,
145
+ nested writes), you don't lose `humanoid_crud`'s URL registration — just hand
146
+ it your own class:
147
+
148
+ ```python
149
+ class BookViewSet(viewsets.ModelViewSet):
150
+ queryset = Book.objects.all()
151
+ serializer_class = BookSerializer
152
+
153
+ def perform_create(self, serializer):
154
+ serializer.save(added_by=self.request.user)
155
+
156
+ humanoid_crud.register(Book, viewset_class=BookViewSet)
157
+ ```
158
+
159
+ The generated classes are ordinary `ModelSerializer` / `ModelViewSet`
160
+ subclasses — if you ever outgrow the one-liner for a specific model, you
161
+ write that one model the normal DRF way and `humanoid_crud` still handles
162
+ the routing.
163
+
164
+ ## Important: default permissions are wide open
165
+
166
+ If you don't pass `permissions=[...]`, every registered model defaults to
167
+ `AllowAny` — anyone can read and write it, no login required. Fine for local
168
+ prototyping. **Set explicit permissions before deploying anything real.**
169
+
170
+ ## License
171
+
172
+ MIT
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/humanoid_crud/__init__.py
5
+ src/humanoid_crud/registry.py
6
+ src/humanoid_crud.egg-info/PKG-INFO
7
+ src/humanoid_crud.egg-info/SOURCES.txt
8
+ src/humanoid_crud.egg-info/dependency_links.txt
9
+ src/humanoid_crud.egg-info/requires.txt
10
+ src/humanoid_crud.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ djangorestframework>=3.14
2
+
3
+ [filters]
4
+ django-filter>=23.0
@@ -0,0 +1 @@
1
+ humanoid_crud