simo 3.1.6__py3-none-any.whl → 3.1.7__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.
Potentially problematic release.
This version of simo might be problematic. Click here for more details.
- simo/core/controllers.py +4 -1
- simo/core/forms.py +93 -19
- {simo-3.1.6.dist-info → simo-3.1.7.dist-info}/METADATA +1 -1
- {simo-3.1.6.dist-info → simo-3.1.7.dist-info}/RECORD +8 -8
- {simo-3.1.6.dist-info → simo-3.1.7.dist-info}/WHEEL +0 -0
- {simo-3.1.6.dist-info → simo-3.1.7.dist-info}/entry_points.txt +0 -0
- {simo-3.1.6.dist-info → simo-3.1.7.dist-info}/licenses/LICENSE.md +0 -0
- {simo-3.1.6.dist-info → simo-3.1.7.dist-info}/top_level.txt +0 -0
simo/core/controllers.py
CHANGED
|
@@ -647,7 +647,10 @@ class Button(ControllerBase):
|
|
|
647
647
|
default_value = 'up'
|
|
648
648
|
|
|
649
649
|
def _validate_val(self, value, occasion=None):
|
|
650
|
-
if value not in (
|
|
650
|
+
if value not in (
|
|
651
|
+
'down', 'up', 'hold',
|
|
652
|
+
'click', 'double-click', 'triple-click', 'quadruple-click', 'quintuple-click'
|
|
653
|
+
):
|
|
651
654
|
raise ValidationError("Bad button value!")
|
|
652
655
|
return value
|
|
653
656
|
|
simo/core/forms.py
CHANGED
|
@@ -71,16 +71,45 @@ class CategoryAdminForm(forms.ModelForm):
|
|
|
71
71
|
class ConfigFieldsMixin:
|
|
72
72
|
|
|
73
73
|
def __init__(self, *args, **kwargs):
|
|
74
|
+
"""Augment forms with dynamic controller fields and
|
|
75
|
+
persist non-model fields under component.config.
|
|
76
|
+
|
|
77
|
+
Dynamic fields are appended by controllers via
|
|
78
|
+
`controller._get_dynamic_config_fields()` and are excluded from
|
|
79
|
+
automatic component.config persistence. Controllers may handle
|
|
80
|
+
them in `_apply_dynamic_config()` during save.
|
|
81
|
+
"""
|
|
74
82
|
super().__init__(*args, **kwargs)
|
|
83
|
+
|
|
84
|
+
# Inject dynamic fields from controller (if any)
|
|
85
|
+
self._dynamic_fields = []
|
|
86
|
+
controller = getattr(self, 'controller', None)
|
|
87
|
+
if controller and hasattr(controller, '_get_dynamic_config_fields'):
|
|
88
|
+
try:
|
|
89
|
+
dyn_fields = controller._get_dynamic_config_fields() or {}
|
|
90
|
+
if isinstance(dyn_fields, dict):
|
|
91
|
+
for fname, field in dyn_fields.items():
|
|
92
|
+
if fname in self.fields:
|
|
93
|
+
continue
|
|
94
|
+
self.fields[fname] = field
|
|
95
|
+
self._dynamic_fields.append(fname)
|
|
96
|
+
except Exception:
|
|
97
|
+
# Never break the form if controller fails here
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
# Build config-backed field list (exclude model fields and dynamic fields)
|
|
75
101
|
self.model_fields = [
|
|
76
102
|
f.name for f in Component._meta.fields
|
|
77
103
|
] + ['slaves', ]
|
|
78
104
|
self.config_fields = []
|
|
79
|
-
for field_name
|
|
105
|
+
for field_name in list(self.fields.keys()):
|
|
80
106
|
if field_name in self.model_fields:
|
|
81
107
|
continue
|
|
108
|
+
if field_name in self._dynamic_fields:
|
|
109
|
+
continue
|
|
82
110
|
self.config_fields.append(field_name)
|
|
83
111
|
|
|
112
|
+
# Initialize config-backed fields from instance.config
|
|
84
113
|
for field_name in self.config_fields:
|
|
85
114
|
if field_name not in self.instance.config:
|
|
86
115
|
continue
|
|
@@ -106,38 +135,46 @@ class ConfigFieldsMixin:
|
|
|
106
135
|
|
|
107
136
|
|
|
108
137
|
def save(self, commit=True):
|
|
138
|
+
# Write config-backed fields under component.config
|
|
109
139
|
for field_name in self.config_fields:
|
|
110
140
|
# support for partial forms
|
|
111
141
|
if field_name not in self.cleaned_data:
|
|
112
142
|
continue
|
|
113
143
|
if isinstance(self.cleaned_data[field_name], models.Model):
|
|
114
|
-
self.instance.config[field_name] =
|
|
115
|
-
self.cleaned_data[field_name].pk
|
|
144
|
+
self.instance.config[field_name] = self.cleaned_data[field_name].pk
|
|
116
145
|
elif isinstance(self.cleaned_data[field_name], models.QuerySet):
|
|
117
|
-
self.instance.config[field_name] = [
|
|
118
|
-
obj.pk for obj in self.cleaned_data[field_name]
|
|
119
|
-
]
|
|
146
|
+
self.instance.config[field_name] = [obj.pk for obj in self.cleaned_data[field_name]]
|
|
120
147
|
else:
|
|
121
148
|
try:
|
|
122
|
-
self.instance.config[field_name] =
|
|
123
|
-
|
|
124
|
-
except:
|
|
149
|
+
self.instance.config[field_name] = json.loads(json.dumps(self.cleaned_data[field_name]))
|
|
150
|
+
except Exception:
|
|
125
151
|
continue
|
|
126
152
|
|
|
153
|
+
# Save component
|
|
127
154
|
if commit:
|
|
128
155
|
from simo.users.utils import get_current_user
|
|
129
156
|
actor = get_current_user()
|
|
130
|
-
if
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
157
|
+
if actor:
|
|
158
|
+
if self.instance.pk:
|
|
159
|
+
verb = 'modified'
|
|
160
|
+
else:
|
|
161
|
+
verb = 'created'
|
|
162
|
+
action.send(
|
|
163
|
+
actor, target=self.instance, verb=verb,
|
|
164
|
+
instance_id=self.instance.zone.instance.id,
|
|
165
|
+
action_type='management_event'
|
|
166
|
+
)
|
|
167
|
+
result = super().save(commit)
|
|
139
168
|
|
|
140
|
-
|
|
169
|
+
# Apply dynamic settings via controller hook
|
|
170
|
+
controller = getattr(self, 'controller', None)
|
|
171
|
+
if controller and hasattr(controller, '_apply_dynamic_config'):
|
|
172
|
+
try:
|
|
173
|
+
controller._apply_dynamic_config(self.cleaned_data)
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
return result
|
|
141
178
|
|
|
142
179
|
|
|
143
180
|
class BaseGatewayForm(ConfigFieldsMixin, forms.ModelForm):
|
|
@@ -329,10 +366,24 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
329
366
|
base_fields.append('category')
|
|
330
367
|
base_fields.append('show_in_app')
|
|
331
368
|
|
|
369
|
+
# Include statically declared fields first
|
|
332
370
|
for field_name in cls.declared_fields:
|
|
333
371
|
if field_name not in main_fields:
|
|
334
372
|
base_fields.append(field_name)
|
|
335
373
|
|
|
374
|
+
# If editing an existing object, include any dynamic fields
|
|
375
|
+
# that the form instance adds at runtime.
|
|
376
|
+
if obj is not None:
|
|
377
|
+
try:
|
|
378
|
+
tmp_form = cls(request=request, instance=obj, controller_uid=obj.controller_uid)
|
|
379
|
+
for fname in tmp_form.fields.keys():
|
|
380
|
+
if fname in base_fields or fname in main_fields:
|
|
381
|
+
continue
|
|
382
|
+
base_fields.append(fname)
|
|
383
|
+
except Exception:
|
|
384
|
+
# Ignore dynamic field detection issues
|
|
385
|
+
pass
|
|
386
|
+
|
|
336
387
|
base_fields.append('control')
|
|
337
388
|
base_fields.append('notes')
|
|
338
389
|
|
|
@@ -389,6 +440,29 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
389
440
|
|
|
390
441
|
|
|
391
442
|
class BaseComponentForm(ConfigFieldsMixin, ComponentAdminForm):
|
|
443
|
+
"""Base form for all components.
|
|
444
|
+
|
|
445
|
+
Dynamic controller-backed fields
|
|
446
|
+
--------------------------------
|
|
447
|
+
Controllers can expose device-scoped, dynamic options by implementing
|
|
448
|
+
the following optional hooks on the controller instance:
|
|
449
|
+
|
|
450
|
+
- _get_dynamic_config_fields(self) -> dict[str, django.forms.Field]
|
|
451
|
+
Return a mapping of additional form fields to append at runtime.
|
|
452
|
+
These fields are not persisted automatically to component.config.
|
|
453
|
+
Set each field's `initial` to reflect the current device value.
|
|
454
|
+
|
|
455
|
+
- _apply_dynamic_config(self, form_data: dict) -> None
|
|
456
|
+
Called after the form is saved. Receives the full cleaned form
|
|
457
|
+
data (including dynamic fields). Use this to apply device options
|
|
458
|
+
(e.g. send commands to a gateway or write to component.config if
|
|
459
|
+
your integration needs to persist something custom).
|
|
460
|
+
|
|
461
|
+
Notes
|
|
462
|
+
- Dynamic fields are excluded from `basic_fields`, so only higher-level
|
|
463
|
+
users (instance superusers/masters) will see and edit them.
|
|
464
|
+
- All existing static fields and behavior remain unchanged.
|
|
465
|
+
"""
|
|
392
466
|
pass
|
|
393
467
|
|
|
394
468
|
|
|
@@ -102,12 +102,12 @@ simo/core/auto_urls.py,sha256=fM9Tqzt0OfJ2FNnePGp7LcbJAWzgEwaNAJy7FNXHY-o,1299
|
|
|
102
102
|
simo/core/autocomplete_views.py,sha256=x3MKOZvXYS3xVQ-V1S7Liv_U5bxr-uc0gePa85wv5nA,4561
|
|
103
103
|
simo/core/base_types.py,sha256=FNIS9Y7wmdbVl-dISLdSBYvMEiV4zSLpBOBDYOVyam0,6580
|
|
104
104
|
simo/core/context.py,sha256=LKw1I4iIRnlnzoTCuSLLqDX7crHdBnMo3hjqYvVmzFc,1557
|
|
105
|
-
simo/core/controllers.py,sha256=
|
|
105
|
+
simo/core/controllers.py,sha256=uEC4hSeSpyHLZjojIdMPrzy2-JiK9DkMmCZpE3BcK-I,48651
|
|
106
106
|
simo/core/dynamic_settings.py,sha256=bUs58XEZOCIEhg1TigR3LmYggli13KMryBZ9pC7ugAQ,1872
|
|
107
107
|
simo/core/events.py,sha256=mjrYsByw0hGct4rXoLmnbBodViN8S1ReBIZG3g660Yo,6735
|
|
108
108
|
simo/core/filters.py,sha256=6wbn8C2WvKTTjtfMwwLBp2Fib1V0-DMpS4iqJd6jJQo,2540
|
|
109
109
|
simo/core/form_fields.py,sha256=b4wZ4n7OO0m0_BPPS9ILVrwBvhhjUB079YrroveFUWA,5222
|
|
110
|
-
simo/core/forms.py,sha256=
|
|
110
|
+
simo/core/forms.py,sha256=koGOIVt3gE1eejpjHFjAH-QZ1Zm2Woy51F5txh_AJVI,26191
|
|
111
111
|
simo/core/gateways.py,sha256=cM_du3VsHbSYUSWL5JHxXMV8yn6s-QrSzB3WreglGjw,4736
|
|
112
112
|
simo/core/loggers.py,sha256=EBdq23gTQScVfQVH-xeP90-wII2DQFDjoROAW6ggUP4,1645
|
|
113
113
|
simo/core/managers.py,sha256=Ampwe5K7gfE6IJULNCV35V8ysmMOdS_wz7mRzfaLZUw,3014
|
|
@@ -11034,9 +11034,9 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
|
11034
11034
|
simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11035
11035
|
simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11036
11036
|
simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11037
|
-
simo-3.1.
|
|
11038
|
-
simo-3.1.
|
|
11039
|
-
simo-3.1.
|
|
11040
|
-
simo-3.1.
|
|
11041
|
-
simo-3.1.
|
|
11042
|
-
simo-3.1.
|
|
11037
|
+
simo-3.1.7.dist-info/licenses/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
|
|
11038
|
+
simo-3.1.7.dist-info/METADATA,sha256=478oQ_ahEy-rp4E2VvKgkzhLPFYrTOMG7IJAttlmJXM,2224
|
|
11039
|
+
simo-3.1.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11040
|
+
simo-3.1.7.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
|
|
11041
|
+
simo-3.1.7.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
|
|
11042
|
+
simo-3.1.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|