crudkit 0.2.3__tar.gz → 0.3.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.
- {crudkit-0.2.3 → crudkit-0.3.0}/CHANGELOG.md +16 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/PKG-INFO +1 -1
- {crudkit-0.2.3 → crudkit-0.3.0}/pyproject.toml +1 -1
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/models.py +2 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/metadata.py +5 -2
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/tests/test_metadata.py +12 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/views.py +1 -1
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_frontend/context_processors.py +3 -1
- crudkit-0.2.3/src/crudkit_frontend/static/crudkit_frontend/assets/index-CmafPWf5.js → crudkit-0.3.0/src/crudkit_frontend/static/crudkit_frontend/assets/index-BYJWd3yY.js +179 -177
- crudkit-0.3.0/src/crudkit_frontend/static/crudkit_frontend/assets/index-D_4ufdxB.css +1 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_frontend/templates/crudkit_frontend/index.html +2 -2
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_frontend/tests/test_frontend.py +3 -1
- crudkit-0.2.3/src/crudkit_frontend/static/crudkit_frontend/assets/index-EHRF7Jzw.css +0 -1
- {crudkit-0.2.3 → crudkit-0.3.0}/.gitignore +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/README.md +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/manage.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/admin.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/ai_backend.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/apps.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/authorization.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/checks.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/decorators.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/fields.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/llm.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/management/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/management/commands/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/management/commands/add_default_exchange_rates.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/management/commands/backfill_ai_fields.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/management/commands/set_exchange_rate.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/migrations/0001_squashed.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/migrations/0002_workspace.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/migrations/0003_enforce_data_invariants.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/migrations/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/profile.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/signals.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tasks.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/templatetags/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/templatetags/crm_forms.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/templatetags/crm_time.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/templatetags/gravatar.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/templatetags/model_magic.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tests/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tests/test_ai_backend.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tests/test_ai_fields.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tests/test_ai_tasks.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tests/test_backfill_command.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tests/test_fields.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tests/test_integrity.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tests/test_llm.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tests/test_money.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tests/test_tasks.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/tests/test_workspace.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/utils.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit/widgets.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/authentication.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/filters.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/pagination.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/permissions.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/serializers.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/tests/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/tests/test_authorization.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/tests/test_email_id_serialization.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/tests/test_filters.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/tests/test_list_serialization.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/tests/test_merge.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/tests/test_user_profile.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_api/urls.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/admin.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/agent.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/apps.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/consumers.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/deps.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/execution.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/migrations/0001_initial.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/migrations/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/models.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/routing.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/runner.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/tests/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/tests/test_proposals.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/tools.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_assistant/utils.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_frontend/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_frontend/apps.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_frontend/static/crudkit_frontend/assets/favicon-Cit_9t8D.svg +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_frontend/tests/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_frontend/tests/templates/crudkit_frontend/index.html +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_frontend/tests/urls.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_frontend/urls.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/src/crudkit_frontend/views.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/celery.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/settings.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/testapp/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/testapp/adapters.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/testapp/ai.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/testapp/apps.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/testapp/migrations/0001_initial.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/testapp/migrations/0002_customer_currency.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/testapp/migrations/__init__.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/testapp/models.py +0 -0
- {crudkit-0.2.3 → crudkit-0.3.0}/tests/urls.py +0 -0
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 (2026-09-21)
|
|
4
|
+
|
|
5
|
+
- `crudkit_frontend`: related-record pickers offer `+ Create <model> "<typed text>"`, which
|
|
6
|
+
opens the target's create form in a modal (prefilled from the first `search_fields` entry)
|
|
7
|
+
and selects the new record without leaving the form. Modals nest; Esc/Enter only affect the
|
|
8
|
+
topmost one. (#34)
|
|
9
|
+
- `crudkit_api`: the metadata endpoint also returns `search_fields`, `can_create` (add
|
|
10
|
+
permission for the requesting user) and `inline_create`. `build_model_metadata` takes an
|
|
11
|
+
optional `user`. (#34)
|
|
12
|
+
- `crudkit`: new `CrudKitSettings.inline_create` (default `True`); set `False` to hide the
|
|
13
|
+
inline create option for a model. (#34)
|
|
14
|
+
- `crudkit_frontend`: a failed Kanban/Swimlane drop shows the server's validation error
|
|
15
|
+
instead of a generic message. (#31)
|
|
16
|
+
- `crudkit_frontend`: inline email images are hidden behind a toggle; image attachments
|
|
17
|
+
preview on hover. (#32)
|
|
18
|
+
|
|
3
19
|
## 0.2.3 (2026-09-21)
|
|
4
20
|
|
|
5
21
|
- `crudkit_api`: `GET /api/v1/` no longer returns 403 under `CrudKitModelPermissions`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: crudkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A metadata-driven CRUD framework for Django: typed object IDs, soft delete, merge, change log, activity feed, saved views, AI fields and a generic REST API.
|
|
5
5
|
Project-URL: Homepage, https://github.com/fenerum/crudkit
|
|
6
6
|
Project-URL: Changelog, https://github.com/fenerum/crudkit/blob/main/backend/CHANGELOG.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "crudkit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "A metadata-driven CRUD framework for Django: typed object IDs, soft delete, merge, change log, activity feed, saved views, AI fields and a generic REST API."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -367,6 +367,8 @@ class BaseCrudKitModel(models.Model):
|
|
|
367
367
|
class CrudKitSettings:
|
|
368
368
|
allowed_prefills = []
|
|
369
369
|
search_fields = []
|
|
370
|
+
# Offer "Create new…" in related-object pickers targeting this model.
|
|
371
|
+
inline_create = True
|
|
370
372
|
ai_trigger_children = []
|
|
371
373
|
# Playbook prompt for the per-object AI assistant. Empty → generic
|
|
372
374
|
# fallback prompt is used. Subclasses override to teach the assistant
|
|
@@ -14,7 +14,7 @@ from django.apps import apps
|
|
|
14
14
|
from django.conf import settings
|
|
15
15
|
from django.db.models import Model
|
|
16
16
|
|
|
17
|
-
from crudkit.authorization import get_authorized_queryset
|
|
17
|
+
from crudkit.authorization import get_authorized_queryset, has_model_permission
|
|
18
18
|
from crudkit.fields import ModelField
|
|
19
19
|
from crudkit.models import ExternalObject, FeedItem
|
|
20
20
|
from crudkit.utils import get_model_types
|
|
@@ -108,7 +108,7 @@ def _generic_relations() -> list[dict[str, Any]]:
|
|
|
108
108
|
return relations
|
|
109
109
|
|
|
110
110
|
|
|
111
|
-
def build_model_metadata(model) -> dict[str, Any]:
|
|
111
|
+
def build_model_metadata(model, user=None) -> dict[str, Any]:
|
|
112
112
|
"""Return the model-level metadata payload served at /api/v1/<type>/metadata/."""
|
|
113
113
|
mfc = _model_field_choices()
|
|
114
114
|
fields = OrderedDict((field.name, _field_metadata(field, mfc)) for field in model._meta.fields)
|
|
@@ -132,6 +132,9 @@ def build_model_metadata(model) -> dict[str, Any]:
|
|
|
132
132
|
"allowed_prefills": list(
|
|
133
133
|
getattr(model.CrudKitSettings, "allowed_prefills", []),
|
|
134
134
|
),
|
|
135
|
+
"search_fields": list(getattr(model.CrudKitSettings, "search_fields", [])),
|
|
136
|
+
"can_create": has_model_permission(user, model, "add"),
|
|
137
|
+
"inline_create": getattr(model.CrudKitSettings, "inline_create", True),
|
|
135
138
|
"fields": fields,
|
|
136
139
|
"relations": relations,
|
|
137
140
|
"actions": [{"verbose_name": func.verbose_name, "action": action} for action, func in model()._actions.items()],
|
|
@@ -121,6 +121,18 @@ class BuildModelMetadataTests(TestCase):
|
|
|
121
121
|
self.assertEqual(by_name["worklog"]["related_model_type"], "WLG")
|
|
122
122
|
self.assertEqual(by_name["worklog"]["field_name"], "related_object")
|
|
123
123
|
|
|
124
|
+
def test_can_create_reflects_add_permission(self):
|
|
125
|
+
superuser = User.objects.create_superuser("root", password="x")
|
|
126
|
+
plain = User.objects.create_user("plain", password="x")
|
|
127
|
+
self.assertTrue(build_model_metadata(Customer, superuser)["can_create"])
|
|
128
|
+
self.assertFalse(build_model_metadata(Customer, plain)["can_create"])
|
|
129
|
+
self.assertFalse(build_model_metadata(Customer)["can_create"])
|
|
130
|
+
|
|
131
|
+
def test_search_fields_and_inline_create_exposed(self):
|
|
132
|
+
md = build_model_metadata(Customer)
|
|
133
|
+
self.assertEqual(md["search_fields"], list(Customer.CrudKitSettings.search_fields))
|
|
134
|
+
self.assertTrue(md["inline_create"])
|
|
135
|
+
|
|
124
136
|
def test_extra_generic_relations_absent_by_default(self):
|
|
125
137
|
md = build_model_metadata(Ticket)
|
|
126
138
|
self.assertNotIn("worklog", {rel["name"] for rel in md["relations"]})
|
|
@@ -215,7 +215,7 @@ class GenericViewSet(viewsets.ModelViewSet):
|
|
|
215
215
|
|
|
216
216
|
@action(detail=False)
|
|
217
217
|
def metadata(self, request):
|
|
218
|
-
return Response(build_model_metadata(self.queryset.model))
|
|
218
|
+
return Response(build_model_metadata(self.queryset.model, request.user))
|
|
219
219
|
|
|
220
220
|
|
|
221
221
|
CRM_TYPE_REGEX = re.compile(r"[A-Z]{3}")
|
|
@@ -5,9 +5,11 @@ import json
|
|
|
5
5
|
from django.conf import settings
|
|
6
6
|
from django.utils.safestring import mark_safe
|
|
7
7
|
|
|
8
|
+
from crudkit.fields import DEFAULT_CURRENCY
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
def crudkit_config(request):
|
|
10
|
-
config = getattr(settings, "CRUDKIT_FRONTEND_CONFIG", {})
|
|
12
|
+
config = {"default_currency": DEFAULT_CURRENCY, **getattr(settings, "CRUDKIT_FRONTEND_CONFIG", {})}
|
|
11
13
|
# Escape "<" so "</script>" in values cannot break out of the script tag.
|
|
12
14
|
payload = json.dumps(config).replace("<", "\\u003c")
|
|
13
15
|
# settings.STATIC_URL is already "/"-prefixed by Django on access.
|