statezero 0.1.0b14__tar.gz → 0.1.0b20__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.
- {statezero-0.1.0b14 → statezero-0.1.0b20}/PKG-INFO +1 -1
- {statezero-0.1.0b14 → statezero-0.1.0b20}/pyproject.toml +1 -1
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/actions.py +8 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/orm.py +9 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/schemas.py +17 -4
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/serializers.py +39 -24
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/urls.py +2 -1
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/views.py +101 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/ast_parser.py +2 -13
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/classes.py +1 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/config.py +0 -2
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/interfaces.py +10 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero.egg-info/PKG-INFO +1 -1
- {statezero-0.1.0b14 → statezero-0.1.0b20}/README.md +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/license.md +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/requirements.txt +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/setup.cfg +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/__init__.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/__init__.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/__init__.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/apps.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/config.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/context_manager.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/event_emitters.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/exception_handler.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/extensions/__init__.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/extensions/custom_field_serializers/__init__.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/extensions/custom_field_serializers/file_fields.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/extensions/custom_field_serializers/money_field.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/f_handler.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/helpers.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/middleware.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/migrations/0001_initial.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/migrations/0002_delete_modelviewsubscription.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/migrations/__init__.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/permissions.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/query_optimizer.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/search_providers/__init__.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/search_providers/basic_search.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/search_providers/postgres_search.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/__init__.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/actions.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/ast_validator.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/context_storage.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/event_bus.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/event_emitters.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/exceptions.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/hook_checks.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/process_request.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/core/types.py +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero.egg-info/SOURCES.txt +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero.egg-info/dependency_links.txt +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero.egg-info/requires.txt +0 -0
- {statezero-0.1.0b14 → statezero-0.1.0b20}/statezero.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: statezero
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0b20
|
|
4
4
|
Summary: Connect your Python backend to a modern JavaScript SPA frontend with 90% less complexity.
|
|
5
5
|
Author-email: Robert <robert.herring@statezero.dev>
|
|
6
6
|
Project-URL: homepage, https://www.statezero.dev
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "statezero"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.0b20"
|
|
8
8
|
description = "Connect your Python backend to a modern JavaScript SPA frontend with 90% less complexity."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -125,6 +125,14 @@ class DjangoActionSchemaGenerator:
|
|
|
125
125
|
return "string"
|
|
126
126
|
return "integer"
|
|
127
127
|
|
|
128
|
+
# Handle nested serializers (many=True creates a ListSerializer)
|
|
129
|
+
if isinstance(field, serializers.ListSerializer):
|
|
130
|
+
return "array"
|
|
131
|
+
|
|
132
|
+
# Handle nested serializers (single nested serializer)
|
|
133
|
+
if isinstance(field, serializers.Serializer):
|
|
134
|
+
return "object"
|
|
135
|
+
|
|
128
136
|
type_mapping = {
|
|
129
137
|
fields.BooleanField: "boolean",
|
|
130
138
|
fields.CharField: "string",
|
|
@@ -763,6 +763,7 @@ class DjangoORMAdapter(AbstractORMProvider):
|
|
|
763
763
|
def get_fields(self, model: models.Model) -> Set[str]:
|
|
764
764
|
"""
|
|
765
765
|
Return a set of the model fields.
|
|
766
|
+
Includes both database fields and additional_fields (computed fields).
|
|
766
767
|
"""
|
|
767
768
|
model_config = registry.get_config(model)
|
|
768
769
|
if model_config.fields and "__all__" != model_config.fields:
|
|
@@ -775,6 +776,14 @@ class DjangoORMAdapter(AbstractORMProvider):
|
|
|
775
776
|
resolved_fields = resolved_fields.union(additional_fields)
|
|
776
777
|
return resolved_fields
|
|
777
778
|
|
|
779
|
+
def get_db_fields(self, model: models.Model) -> Set[str]:
|
|
780
|
+
"""
|
|
781
|
+
Return only actual database fields for the model.
|
|
782
|
+
Excludes read-only additional_fields (computed fields).
|
|
783
|
+
Used for deserialization - hooks can write to any DB field.
|
|
784
|
+
"""
|
|
785
|
+
return set(field.name for field in model._meta.get_fields())
|
|
786
|
+
|
|
778
787
|
def build_model_graph(
|
|
779
788
|
self, model: Type[models.Model], model_graph: nx.DiGraph = None
|
|
780
789
|
) -> nx.DiGraph:
|
|
@@ -41,9 +41,9 @@ class DjangoSchemaGenerator(AbstractSchemaGenerator):
|
|
|
41
41
|
all_field_names: Set[str] = set()
|
|
42
42
|
db_field_names: Set[str] = set()
|
|
43
43
|
|
|
44
|
-
if model_config.
|
|
44
|
+
if model_config.fields != "__all__":
|
|
45
45
|
all_fields = [
|
|
46
|
-
field for field in all_fields if field.name in model_config.
|
|
46
|
+
field for field in all_fields if field.name in model_config.fields
|
|
47
47
|
]
|
|
48
48
|
|
|
49
49
|
for field in all_fields:
|
|
@@ -178,6 +178,7 @@ class DjangoSchemaGenerator(AbstractSchemaGenerator):
|
|
|
178
178
|
nullable=False,
|
|
179
179
|
format=FieldFormat.ID,
|
|
180
180
|
description=description,
|
|
181
|
+
read_only=True,
|
|
181
182
|
)
|
|
182
183
|
elif isinstance(field, models.UUIDField):
|
|
183
184
|
return SchemaFieldMetadata(
|
|
@@ -187,6 +188,7 @@ class DjangoSchemaGenerator(AbstractSchemaGenerator):
|
|
|
187
188
|
nullable=False,
|
|
188
189
|
format=FieldFormat.UUID,
|
|
189
190
|
description=description,
|
|
191
|
+
read_only=True,
|
|
190
192
|
)
|
|
191
193
|
elif isinstance(field, models.CharField):
|
|
192
194
|
return SchemaFieldMetadata(
|
|
@@ -197,6 +199,7 @@ class DjangoSchemaGenerator(AbstractSchemaGenerator):
|
|
|
197
199
|
format=FieldFormat.ID,
|
|
198
200
|
max_length=field.max_length,
|
|
199
201
|
description=description,
|
|
202
|
+
read_only=True,
|
|
200
203
|
)
|
|
201
204
|
else:
|
|
202
205
|
return SchemaFieldMetadata(
|
|
@@ -206,6 +209,7 @@ class DjangoSchemaGenerator(AbstractSchemaGenerator):
|
|
|
206
209
|
nullable=False,
|
|
207
210
|
format=FieldFormat.ID,
|
|
208
211
|
description=description,
|
|
212
|
+
read_only=True,
|
|
209
213
|
)
|
|
210
214
|
|
|
211
215
|
def get_field_metadata(
|
|
@@ -256,6 +260,9 @@ class DjangoSchemaGenerator(AbstractSchemaGenerator):
|
|
|
256
260
|
elif isinstance(field, models.DateField):
|
|
257
261
|
field_type = FieldType.STRING
|
|
258
262
|
field_format = FieldFormat.DATE
|
|
263
|
+
elif isinstance(field, models.TimeField):
|
|
264
|
+
field_type = FieldType.STRING
|
|
265
|
+
field_format = FieldFormat.TIME
|
|
259
266
|
elif isinstance(field, (models.ForeignKey, models.OneToOneField)):
|
|
260
267
|
field_type = self.get_pk_type(field)
|
|
261
268
|
field_format = self.get_relation_type(field)
|
|
@@ -286,6 +293,12 @@ class DjangoSchemaGenerator(AbstractSchemaGenerator):
|
|
|
286
293
|
elif callable(default):
|
|
287
294
|
default = default()
|
|
288
295
|
|
|
296
|
+
# Check if field should be read-only (auto_now or auto_now_add)
|
|
297
|
+
read_only = False
|
|
298
|
+
if isinstance(field, (models.DateTimeField, models.DateField, models.TimeField)):
|
|
299
|
+
if getattr(field, "auto_now", False) or getattr(field, "auto_now_add", False):
|
|
300
|
+
read_only = True
|
|
301
|
+
|
|
289
302
|
return SchemaFieldMetadata(
|
|
290
303
|
type=field_type,
|
|
291
304
|
title=title,
|
|
@@ -299,6 +312,7 @@ class DjangoSchemaGenerator(AbstractSchemaGenerator):
|
|
|
299
312
|
max_digits=max_digits,
|
|
300
313
|
decimal_places=decimal_places,
|
|
301
314
|
description=description,
|
|
315
|
+
read_only=read_only,
|
|
302
316
|
)
|
|
303
317
|
|
|
304
318
|
def get_field_title(self, field: models.Field) -> str:
|
|
@@ -309,8 +323,7 @@ class DjangoSchemaGenerator(AbstractSchemaGenerator):
|
|
|
309
323
|
@staticmethod
|
|
310
324
|
def is_field_required(field: models.Field) -> bool:
|
|
311
325
|
return (
|
|
312
|
-
not field.
|
|
313
|
-
and not field.null
|
|
326
|
+
not field.null
|
|
314
327
|
and field.default == models.fields.NOT_PROVIDED
|
|
315
328
|
)
|
|
316
329
|
|
|
@@ -6,7 +6,7 @@ from rest_framework import serializers
|
|
|
6
6
|
import contextvars
|
|
7
7
|
from contextlib import contextmanager
|
|
8
8
|
import logging
|
|
9
|
-
from cytoolz import pluck
|
|
9
|
+
from cytoolz import pluck, keyfilter
|
|
10
10
|
from zen_queries import queries_disabled
|
|
11
11
|
|
|
12
12
|
from statezero.adaptors.django.config import config, registry
|
|
@@ -484,33 +484,48 @@ class DRFDynamicSerializer(AbstractDataSerializer):
|
|
|
484
484
|
# Serious security issue if fields_map is None
|
|
485
485
|
assert fields_map is not None, "fields_map is required and cannot be None"
|
|
486
486
|
|
|
487
|
-
#
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
serializer_class = DynamicModelSerializer.for_model(model)
|
|
491
|
-
available_fields = set(serializer_class().fields.keys())
|
|
487
|
+
# Get model name and allowed fields from fields_map
|
|
488
|
+
model_name = config.orm_provider.get_model_name(model)
|
|
489
|
+
allowed_fields = fields_map.get(model_name, set())
|
|
492
490
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
491
|
+
# Filter user input to only allowed fields (security boundary)
|
|
492
|
+
data = dict(keyfilter(lambda k: k in allowed_fields, data))
|
|
493
|
+
|
|
494
|
+
try:
|
|
495
|
+
model_config = registry.get_config(model)
|
|
496
|
+
except ValueError:
|
|
497
|
+
# No model config available
|
|
498
|
+
model_config = None
|
|
499
|
+
|
|
500
|
+
# Run pre-hooks on filtered data (hooks can add any DB fields)
|
|
501
|
+
if model_config and model_config.pre_hooks:
|
|
502
|
+
for hook in model_config.pre_hooks:
|
|
503
|
+
hook_result = hook(data, request=request)
|
|
504
|
+
if settings.DEBUG:
|
|
505
|
+
# Note: available_fields check removed since hooks can add any DB field
|
|
506
|
+
data = hook_result or data
|
|
507
|
+
else:
|
|
508
|
+
data = hook_result or data
|
|
509
|
+
|
|
510
|
+
# Expand fields_map to include fields that hooks may have added
|
|
511
|
+
# For partial updates, only include allowed_fields + any fields in the data
|
|
512
|
+
# This prevents validation errors on required fields that were filtered out
|
|
513
|
+
if partial:
|
|
514
|
+
# For partial updates: only include fields that are either allowed or in the data
|
|
515
|
+
expanded_fields = allowed_fields | set(data.keys())
|
|
516
|
+
else:
|
|
517
|
+
# For creates: include all DB fields to allow hooks to add any field
|
|
518
|
+
expanded_fields = config.orm_provider.get_db_fields(model)
|
|
519
|
+
expanded_fields_map = {model_name: expanded_fields}
|
|
520
|
+
|
|
521
|
+
# Use the context manager with expanded fields map
|
|
522
|
+
with fields_map_context(expanded_fields_map):
|
|
523
|
+
# Create serializer class with all DB fields available
|
|
524
|
+
serializer_class = DynamicModelSerializer.for_model(model)
|
|
510
525
|
|
|
511
526
|
# Create serializer
|
|
512
527
|
serializer = serializer_class(
|
|
513
|
-
data=data,
|
|
528
|
+
data=data,
|
|
514
529
|
partial=partial,
|
|
515
530
|
request=request
|
|
516
531
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from django.urls import path
|
|
2
2
|
|
|
3
|
-
from .views import EventsAuthView, ModelListView, ModelView, SchemaView, FileUploadView, FastUploadView, ActionSchemaView, ActionView, ValidateView
|
|
3
|
+
from .views import EventsAuthView, ModelListView, ModelView, SchemaView, FileUploadView, FastUploadView, ActionSchemaView, ActionView, ValidateView, FieldPermissionsView
|
|
4
4
|
|
|
5
5
|
app_name = "statezero"
|
|
6
6
|
|
|
@@ -12,6 +12,7 @@ urlpatterns = [
|
|
|
12
12
|
path("actions/<str:action_name>/", ActionView.as_view(), name="action"),
|
|
13
13
|
path("actions-schema/", ActionSchemaView.as_view(), name="actions_schema"),
|
|
14
14
|
path("<str:model_name>/validate/", ValidateView.as_view(), name="validate"),
|
|
15
|
+
path("<str:model_name>/field-permissions/", FieldPermissionsView.as_view(), name="field_permissions"),
|
|
15
16
|
path("<str:model_name>/get-schema/", SchemaView.as_view(), name="schema_view"),
|
|
16
17
|
path("<str:model_name>/", ModelView.as_view(), name="model_view"),
|
|
17
18
|
]
|
|
@@ -485,3 +485,104 @@ class ValidateView(APIView):
|
|
|
485
485
|
except Exception as original_exception:
|
|
486
486
|
# Let StateZero's exception handler deal with ValidationError, PermissionDenied, etc.
|
|
487
487
|
return explicit_exception_handler(original_exception)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
class FieldPermissionsView(APIView):
|
|
491
|
+
"""
|
|
492
|
+
Returns user-specific field permissions for a given model.
|
|
493
|
+
Used by frontend forms to determine which fields to show/enable at runtime.
|
|
494
|
+
"""
|
|
495
|
+
|
|
496
|
+
permission_classes = [permission_class]
|
|
497
|
+
|
|
498
|
+
def get(self, request, model_name):
|
|
499
|
+
"""Get field permissions for the current user."""
|
|
500
|
+
try:
|
|
501
|
+
# Create processor following the same pattern as other views
|
|
502
|
+
processor = RequestProcessor(config=config, registry=registry)
|
|
503
|
+
|
|
504
|
+
# Get model using the processor's ORM provider
|
|
505
|
+
try:
|
|
506
|
+
model = processor.orm_provider.get_model_by_name(model_name)
|
|
507
|
+
except (LookupError, ValueError):
|
|
508
|
+
return Response({"error": f"Model {model_name} not found"}, status=404)
|
|
509
|
+
|
|
510
|
+
if not model:
|
|
511
|
+
return Response({"error": f"Model {model_name} not found"}, status=404)
|
|
512
|
+
|
|
513
|
+
try:
|
|
514
|
+
model_config = processor.registry.get_config(model)
|
|
515
|
+
except ValueError:
|
|
516
|
+
return Response(
|
|
517
|
+
{"error": f"Model {model_name} not registered"}, status=404
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
# Compute field permissions using the same logic as ASTParser._get_operation_fields
|
|
521
|
+
all_fields = processor.orm_provider.get_fields(model)
|
|
522
|
+
|
|
523
|
+
visible_fields = self._compute_operation_fields(
|
|
524
|
+
model, model_config, all_fields, request, "read"
|
|
525
|
+
)
|
|
526
|
+
creatable_fields = self._compute_operation_fields(
|
|
527
|
+
model, model_config, all_fields, request, "create"
|
|
528
|
+
)
|
|
529
|
+
editable_fields = self._compute_operation_fields(
|
|
530
|
+
model, model_config, all_fields, request, "update"
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
return Response(
|
|
534
|
+
{
|
|
535
|
+
"visible_fields": list(visible_fields),
|
|
536
|
+
"creatable_fields": list(creatable_fields),
|
|
537
|
+
"editable_fields": list(editable_fields),
|
|
538
|
+
},
|
|
539
|
+
status=200,
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
except Exception as original_exception:
|
|
543
|
+
# Let StateZero's exception handler deal with errors
|
|
544
|
+
return explicit_exception_handler(original_exception)
|
|
545
|
+
|
|
546
|
+
def _compute_operation_fields(self, model, model_config, all_fields, request, operation_type):
|
|
547
|
+
"""
|
|
548
|
+
Compute allowed fields for a specific operation type.
|
|
549
|
+
Replicates the logic from ASTParser._get_operation_fields.
|
|
550
|
+
"""
|
|
551
|
+
from typing import Union, Set, Literal
|
|
552
|
+
|
|
553
|
+
allowed_fields = set()
|
|
554
|
+
|
|
555
|
+
for permission_cls in model_config.permissions:
|
|
556
|
+
permission = permission_cls()
|
|
557
|
+
|
|
558
|
+
# Get the appropriate field set based on operation
|
|
559
|
+
if operation_type == "read":
|
|
560
|
+
fields = permission.visible_fields(request, model)
|
|
561
|
+
elif operation_type == "create":
|
|
562
|
+
fields = permission.create_fields(request, model)
|
|
563
|
+
elif operation_type == "update":
|
|
564
|
+
fields = permission.editable_fields(request, model)
|
|
565
|
+
else:
|
|
566
|
+
fields = set()
|
|
567
|
+
|
|
568
|
+
# If any permission allows all fields
|
|
569
|
+
if fields == "__all__":
|
|
570
|
+
# For read operations, default "__all__" to frontend_fields
|
|
571
|
+
if operation_type == "read":
|
|
572
|
+
# If frontend_fields is also "__all__", then return all fields
|
|
573
|
+
if model_config.fields == "__all__":
|
|
574
|
+
return all_fields
|
|
575
|
+
# Otherwise, use frontend_fields as the default for "__all__"
|
|
576
|
+
else:
|
|
577
|
+
fields = model_config.fields
|
|
578
|
+
fields &= all_fields # Ensure fields actually exist
|
|
579
|
+
allowed_fields |= fields
|
|
580
|
+
else:
|
|
581
|
+
# For create/update operations, "__all__" means truly all fields
|
|
582
|
+
return all_fields
|
|
583
|
+
else:
|
|
584
|
+
# Add allowed fields from this permission
|
|
585
|
+
fields &= all_fields # Ensure fields actually exist
|
|
586
|
+
allowed_fields |= fields
|
|
587
|
+
|
|
588
|
+
return allowed_fields
|
|
@@ -386,19 +386,8 @@ class ASTParser:
|
|
|
386
386
|
|
|
387
387
|
# If any permission allows all fields
|
|
388
388
|
if fields == "__all__":
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
# If frontend_fields is also "__all__", then return all fields
|
|
392
|
-
if model_config.frontend_fields == "__all__":
|
|
393
|
-
return all_fields
|
|
394
|
-
# Otherwise, use frontend_fields as the default for "__all__"
|
|
395
|
-
else:
|
|
396
|
-
fields = model_config.frontend_fields
|
|
397
|
-
fields &= all_fields # Ensure fields actually exist
|
|
398
|
-
allowed_fields |= fields
|
|
399
|
-
else:
|
|
400
|
-
# For create/update operations, "__all__" means truly all fields
|
|
401
|
-
return all_fields
|
|
389
|
+
return all_fields
|
|
390
|
+
|
|
402
391
|
# Add allowed fields from this permission
|
|
403
392
|
else: # Ensure we're not operating on the string "__all__"
|
|
404
393
|
fields &= all_fields # Ensure fields actually exist
|
|
@@ -181,7 +181,6 @@ class ModelConfig:
|
|
|
181
181
|
searchable_fields: Optional[Union[Set[str], Literal["__all__"]]] = None,
|
|
182
182
|
ordering_fields: Optional[Union[Set[str], Literal["__all__"]]] = None,
|
|
183
183
|
fields: Optional[Union[Set[str], Literal["__all__"]]] = None,
|
|
184
|
-
frontend_fields: Optional[Union[Set[str], Literal["__all__"]]] = None,
|
|
185
184
|
display: Optional[Any] = None,
|
|
186
185
|
DEBUG: bool = False,
|
|
187
186
|
):
|
|
@@ -196,7 +195,6 @@ class ModelConfig:
|
|
|
196
195
|
self.searchable_fields = searchable_fields or set()
|
|
197
196
|
self.ordering_fields = ordering_fields or set()
|
|
198
197
|
self.fields = fields or "__all__"
|
|
199
|
-
self.frontend_fields = frontend_fields or self.fields
|
|
200
198
|
self.display = display
|
|
201
199
|
self.DEBUG = DEBUG or False
|
|
202
200
|
|
|
@@ -70,6 +70,16 @@ class AbstractORMProvider(ABC):
|
|
|
70
70
|
def get_fields(self, model: ORMModel) -> Set[str]:
|
|
71
71
|
"""
|
|
72
72
|
Get all of the model fields - doesn't apply permissions check.
|
|
73
|
+
Includes both database fields and additional_fields (computed fields).
|
|
74
|
+
"""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def get_db_fields(self, model: ORMModel) -> Set[str]:
|
|
79
|
+
"""
|
|
80
|
+
Get only the actual database fields for a model.
|
|
81
|
+
Excludes read-only additional_fields (computed fields).
|
|
82
|
+
Used for deserialization - hooks can write to any DB field.
|
|
73
83
|
"""
|
|
74
84
|
pass
|
|
75
85
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: statezero
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0b20
|
|
4
4
|
Summary: Connect your Python backend to a modern JavaScript SPA frontend with 90% less complexity.
|
|
5
5
|
Author-email: Robert <robert.herring@statezero.dev>
|
|
6
6
|
Project-URL: homepage, https://www.statezero.dev
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/search_providers/__init__.py
RENAMED
|
File without changes
|
{statezero-0.1.0b14 → statezero-0.1.0b20}/statezero/adaptors/django/search_providers/basic_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|