simo 2.0.42__py3-none-any.whl → 2.1.2__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/__pycache__/asgi.cpython-38.pyc +0 -0
- simo/__pycache__/settings.cpython-38.pyc +0 -0
- simo/__pycache__/wsgi.cpython-38.pyc +0 -0
- simo/asgi.py +1 -1
- simo/core/__init__.py +1 -0
- simo/core/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/core/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/__pycache__/api.cpython-38.pyc +0 -0
- simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
- simo/core/__pycache__/app_widgets.cpython-38.pyc +0 -0
- simo/core/__pycache__/apps.cpython-38.pyc +0 -0
- simo/core/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/core/__pycache__/dynamic_settings.cpython-38.pyc +0 -0
- simo/core/__pycache__/form_fields.cpython-38.pyc +0 -0
- simo/core/__pycache__/forms.cpython-38.pyc +0 -0
- simo/core/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/core/__pycache__/managers.cpython-38.pyc +0 -0
- simo/core/__pycache__/models.cpython-38.pyc +0 -0
- simo/core/__pycache__/permissions.cpython-38.pyc +0 -0
- simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
- simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
- simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
- simo/core/__pycache__/views.cpython-38.pyc +0 -0
- simo/core/admin.py +26 -26
- simo/core/api.py +22 -2
- simo/core/api_meta.py +23 -13
- simo/core/app_widgets.py +6 -0
- simo/core/apps.py +13 -0
- simo/core/auto_urls.py +2 -3
- simo/core/base_types.py +1 -0
- simo/core/controllers.py +57 -0
- simo/core/dynamic_settings.py +0 -8
- simo/core/form_fields.py +93 -0
- simo/core/forms.py +16 -101
- simo/core/gateways.py +1 -1
- simo/core/managers.py +14 -1
- simo/core/migrations/0037_auto_20240606_1057.py +33 -0
- simo/core/migrations/0038_remove_instance_cover_image_and_more.py +30 -0
- simo/core/migrations/__pycache__/0037_auto_20240606_1057.cpython-38.pyc +0 -0
- simo/core/migrations/__pycache__/0038_remove_instance_cover_image_and_more.cpython-38.pyc +0 -0
- simo/core/models.py +30 -16
- simo/core/permissions.py +6 -3
- simo/core/serializers.py +77 -5
- simo/core/signal_receivers.py +25 -0
- simo/core/static/admin/css/simo.css +14 -0
- simo/core/tasks.py +82 -49
- simo/core/templates/admin/controller_widgets/button.html +8 -0
- simo/core/templates/admin/core/component_change_form.html +97 -0
- simo/core/templates/admin/formset_widget.html +88 -118
- simo/core/templates/admin/formset_widget_old.html +122 -0
- simo/core/templates/admin/user_tools.html +0 -3
- simo/core/templates/admin/wizard/wizard_add.html +16 -9
- simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/cache.cpython-38.pyc +0 -0
- simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
- simo/core/utils/admin.py +11 -0
- simo/core/utils/cache.py +15 -0
- simo/core/utils/formsets.py +11 -18
- simo/core/views.py +2 -85
- simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
- simo/fleet/auto_urls.py +7 -1
- simo/fleet/controllers.py +194 -31
- simo/fleet/forms.py +223 -87
- simo/fleet/gateways.py +53 -2
- simo/fleet/migrations/0036_auto_20240605_0702.py +68 -0
- simo/fleet/migrations/0037_alter_colonelpin_options_alter_colonelpin_no_and_more.py +27 -0
- simo/fleet/migrations/__pycache__/0036_auto_20240605_0702.cpython-38.pyc +0 -0
- simo/fleet/migrations/__pycache__/0037_alter_colonelpin_options_alter_colonelpin_no_and_more.cpython-38.pyc +0 -0
- simo/fleet/models.py +35 -6
- simo/fleet/socket_consumers.py +1 -1
- simo/fleet/templates/fleet/controllers_info/button.md +16 -0
- simo/fleet/utils.py +31 -1
- simo/fleet/views.py +45 -0
- simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
- simo/generic/controllers.py +61 -16
- simo/generic/forms.py +0 -3
- simo/generic/gateways.py +2 -0
- simo/generic/templates/admin/controller_widgets/blinds.html +2 -1
- simo/generic/templates/admin/controller_widgets/weather_forecast.html +1 -1
- simo/generic/templates/generic/controllers_info/dummy.md +3 -0
- simo/generic/templates/generic/controllers_info/stateselect.md +2 -0
- simo/management/__init__.py +0 -0
- simo/management/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/management/__pycache__/on_http_start.cpython-38.pyc +0 -0
- simo/{_hub_template → management/_hub_template}/hub/nginx.conf +2 -2
- simo/{auto_update.py → management/auto_update.py} +3 -0
- simo/{cli.py → management/copy_template.py} +3 -16
- simo/management/install.py +258 -0
- simo/{on_http_start.py → management/on_http_start.py} +22 -2
- simo/settings.py +20 -4
- simo/users/__init__.py +1 -0
- simo/users/__pycache__/__init__.cpython-38.pyc +0 -0
- simo/users/__pycache__/admin.cpython-38.pyc +0 -0
- simo/users/__pycache__/apps.cpython-38.pyc +0 -0
- simo/users/__pycache__/models.cpython-38.pyc +0 -0
- simo/users/apps.py +9 -0
- simo/users/migrations/__pycache__/0029_alter_instanceuser_options_instanceuser_order.cpython-38.pyc +0 -0
- simo/users/migrations/__pycache__/0030_alter_instanceuser_options_remove_instanceuser_order.cpython-38.pyc +0 -0
- simo/users/models.py +16 -3
- {simo-2.0.42.dist-info → simo-2.1.2.dist-info}/METADATA +5 -3
- {simo-2.0.42.dist-info → simo-2.1.2.dist-info}/RECORD +122 -95
- simo-2.1.2.dist-info/entry_points.txt +2 -0
- simo/__pycache__/on_http_start.cpython-38.pyc +0 -0
- simo/wsgi.py +0 -7
- /simo/{_hub_template → management/_hub_template}/hub/asgi.py +0 -0
- /simo/{_hub_template → management/_hub_template}/hub/celeryc.py +0 -0
- /simo/{_hub_template → management/_hub_template}/hub/manage.py +0 -0
- /simo/{_hub_template → management/_hub_template}/hub/settings.py +0 -0
- /simo/{_hub_template → management/_hub_template}/hub/supervisor.conf +0 -0
- /simo/{_hub_template → management/_hub_template}/hub/urls.py +0 -0
- {simo-2.0.42.dist-info → simo-2.1.2.dist-info}/LICENSE.md +0 -0
- {simo-2.0.42.dist-info → simo-2.1.2.dist-info}/WHEEL +0 -0
- {simo-2.0.42.dist-info → simo-2.1.2.dist-info}/top_level.txt +0 -0
simo/core/forms.py
CHANGED
|
@@ -1,27 +1,19 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import traceback
|
|
3
|
-
import requests
|
|
4
2
|
from dal import forward
|
|
5
|
-
from django import forms
|
|
6
3
|
from django.contrib.admin.forms import AdminAuthenticationForm as OrgAdminAuthenticationForm
|
|
7
4
|
from django.db import models
|
|
8
5
|
from django import forms
|
|
9
6
|
from django.forms import formset_factory
|
|
10
|
-
from django.conf import settings
|
|
11
7
|
from django.urls.base import get_script_prefix
|
|
12
8
|
from django.utils.safestring import mark_safe
|
|
13
9
|
from django.utils.translation import gettext_lazy as _
|
|
14
10
|
from django.contrib.contenttypes.models import ContentType
|
|
15
|
-
from
|
|
11
|
+
from actstream import action
|
|
16
12
|
from dal import autocomplete
|
|
17
13
|
from .models import (
|
|
18
14
|
Icon, Category, Gateway, Component
|
|
19
15
|
)
|
|
20
|
-
from .widgets import
|
|
21
|
-
from simo.conf import dynamic_settings
|
|
22
|
-
from .widgets import SVGFileWidget, PythonCode, LogOutputWidget
|
|
23
|
-
from .widgets import ImageWidget
|
|
24
|
-
from .utils.helpers import get_random_string
|
|
16
|
+
from .widgets import SVGFileWidget, LogOutputWidget
|
|
25
17
|
from .utils.formsets import FormsetField
|
|
26
18
|
from .utils.validators import validate_slaves
|
|
27
19
|
|
|
@@ -34,94 +26,6 @@ class HiddenField(forms.CharField):
|
|
|
34
26
|
super().__init__(widget=forms.HiddenInput())
|
|
35
27
|
|
|
36
28
|
|
|
37
|
-
class HubConfigForm(forms.Form):
|
|
38
|
-
name = forms.CharField(
|
|
39
|
-
label=_("Hub Name"), required=True,
|
|
40
|
-
widget=forms.TextInput(attrs={'placeholder': "Home Sweet Home"})
|
|
41
|
-
)
|
|
42
|
-
uid = forms.CharField(
|
|
43
|
-
label=_('Unique Identifier (UID)'), required=False,
|
|
44
|
-
widget=forms.TextInput(attrs={'placeholder': "Df5Hd8v1"}),
|
|
45
|
-
help_text="Leave blank if this is a new instance."
|
|
46
|
-
)
|
|
47
|
-
time_zone = forms.ChoiceField(
|
|
48
|
-
label=_("Time zone"), required=True,
|
|
49
|
-
choices=ALL_TIMEZONES_CHOICES
|
|
50
|
-
)
|
|
51
|
-
units_of_measure = forms.ChoiceField(
|
|
52
|
-
label=_("Units of Measure"), required=True,
|
|
53
|
-
choices=(('metric', 'Metric'), ('imperial', 'Imperial'))
|
|
54
|
-
)
|
|
55
|
-
cover_image = forms.FileField(
|
|
56
|
-
label=_("Cover image"), required=True, widget=ImageWidget
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
def __init__(self, *args, **kwargs):
|
|
60
|
-
self.user = kwargs.pop('user')
|
|
61
|
-
super().__init__(*args, **kwargs)
|
|
62
|
-
|
|
63
|
-
def clean(self):
|
|
64
|
-
cleaned_data = super().clean()
|
|
65
|
-
post_data = cleaned_data.copy()
|
|
66
|
-
post_data.pop('cover_image')
|
|
67
|
-
if not dynamic_settings['core__hub_secret']:
|
|
68
|
-
dynamic_settings['core__hub_secret'] = get_random_string(20)
|
|
69
|
-
post_data['secret'] = dynamic_settings['core__hub_secret']
|
|
70
|
-
post_data['email'] = self.user.email
|
|
71
|
-
try:
|
|
72
|
-
resp = requests.post(
|
|
73
|
-
'https://simo.io/hubs/sync-initial-config/', json=post_data,
|
|
74
|
-
)
|
|
75
|
-
except Exception as e:
|
|
76
|
-
raise forms.ValidationError(
|
|
77
|
-
"Connection error. "
|
|
78
|
-
"Make sure your hub can reach https://simo.io and try again."
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
if resp.status_code == 400:
|
|
82
|
-
resp_json = resp.json()
|
|
83
|
-
resp_json.pop('status', None)
|
|
84
|
-
for field_name, msg in resp_json.items():
|
|
85
|
-
self.add_error(field_name, msg)
|
|
86
|
-
elif resp.status_code == 200:
|
|
87
|
-
cleaned_data['uid'] = resp.json()['uid']
|
|
88
|
-
else:
|
|
89
|
-
raise forms.ValidationError(
|
|
90
|
-
"Bad response from https://simo.io. Please try again. "
|
|
91
|
-
)
|
|
92
|
-
return cleaned_data
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class CoordinatesForm(forms.Form):
|
|
96
|
-
location = forms.CharField(
|
|
97
|
-
label=_("Where is your hub located?"),
|
|
98
|
-
widget=LocationWidget(based_fields=[])
|
|
99
|
-
)
|
|
100
|
-
share_location = forms.BooleanField(
|
|
101
|
-
label="Share exact location with SIMO.io for "
|
|
102
|
-
"better accuracy of location related services.",
|
|
103
|
-
required=False
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
def __init__(self, *args, **kwargs):
|
|
107
|
-
super().__init__(*args, **kwargs)
|
|
108
|
-
if not self.initial['location']:
|
|
109
|
-
self.fields['location'].widget = LocationWidget(
|
|
110
|
-
based_fields=[], zoom=2
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
class TermsAndConditionsForm(forms.Form):
|
|
117
|
-
accept = forms.BooleanField(required=False)
|
|
118
|
-
|
|
119
|
-
def clean_accept(self):
|
|
120
|
-
if not self.cleaned_data['accept']:
|
|
121
|
-
raise forms.ValidationError(_("You must accept SIMO.io Terms & Conditions if you want to continue."))
|
|
122
|
-
return self.cleaned_data['accept']
|
|
123
|
-
|
|
124
|
-
|
|
125
29
|
class AdminAuthenticationForm(OrgAdminAuthenticationForm):
|
|
126
30
|
|
|
127
31
|
def confirm_login_allowed(self, user):
|
|
@@ -209,6 +113,19 @@ class ConfigFieldsMixin:
|
|
|
209
113
|
self.instance.config[field_name] = \
|
|
210
114
|
self.cleaned_data[field_name]
|
|
211
115
|
|
|
116
|
+
if commit:
|
|
117
|
+
from simo.users.middleware import get_current_user
|
|
118
|
+
actor = get_current_user()
|
|
119
|
+
if self.instance.pk:
|
|
120
|
+
verb = 'modified'
|
|
121
|
+
else:
|
|
122
|
+
verb = 'created'
|
|
123
|
+
action.send(
|
|
124
|
+
actor, target=self.instance, verb=verb,
|
|
125
|
+
instance_id=self.instance.zone.instance.id,
|
|
126
|
+
action_type='management_event'
|
|
127
|
+
)
|
|
128
|
+
|
|
212
129
|
return super().save(commit)
|
|
213
130
|
|
|
214
131
|
|
|
@@ -364,7 +281,7 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
364
281
|
'alarm_category', 'arm_status',
|
|
365
282
|
'notes'
|
|
366
283
|
)
|
|
367
|
-
base_fields = ['id', 'gateway', 'base_type', 'name']
|
|
284
|
+
base_fields = ['id', 'gateway', 'base_type', 'info', 'name']
|
|
368
285
|
if cls.has_icon:
|
|
369
286
|
base_fields.append('icon')
|
|
370
287
|
|
|
@@ -432,8 +349,6 @@ class ComponentAdminForm(forms.ModelForm):
|
|
|
432
349
|
return self.cleaned_data['instance_methods']
|
|
433
350
|
|
|
434
351
|
|
|
435
|
-
|
|
436
|
-
|
|
437
352
|
class BaseComponentForm(ConfigFieldsMixin, ComponentAdminForm):
|
|
438
353
|
pass
|
|
439
354
|
|
simo/core/gateways.py
CHANGED
|
@@ -74,7 +74,7 @@ class BaseObjectCommandsGatewayHandler(BaseGatewayHandler):
|
|
|
74
74
|
def _run_periodic_task(self, exit, task, period):
|
|
75
75
|
while not exit.is_set():
|
|
76
76
|
try:
|
|
77
|
-
print(f"Run periodic task {task}!")
|
|
77
|
+
#print(f"Run periodic task {task}!")
|
|
78
78
|
getattr(self, task)()
|
|
79
79
|
except Exception as e:
|
|
80
80
|
self.logger.error(e, exc_info=True)
|
simo/core/managers.py
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
import traceback
|
|
3
|
+
from actstream.managers import ActionManager as OrgActionManager
|
|
3
4
|
from .middleware import get_current_instance
|
|
4
5
|
from django.utils import timezone
|
|
5
6
|
from django.db import models
|
|
6
7
|
|
|
7
8
|
|
|
9
|
+
class ActionManager(OrgActionManager):
|
|
10
|
+
|
|
11
|
+
def get_queryset(self):
|
|
12
|
+
qs = super().get_queryset()
|
|
13
|
+
instance = get_current_instance()
|
|
14
|
+
if instance:
|
|
15
|
+
qs = qs.filter(data__instance_id=instance.id)
|
|
16
|
+
return qs
|
|
17
|
+
|
|
18
|
+
|
|
8
19
|
class ZonesManager(models.Manager):
|
|
9
20
|
|
|
10
21
|
def get_queryset(self):
|
|
@@ -32,7 +43,7 @@ class ComponentsManager(models.Manager):
|
|
|
32
43
|
instance = get_current_instance()
|
|
33
44
|
if instance:
|
|
34
45
|
qs = qs.filter(zone__instance=instance)
|
|
35
|
-
return qs
|
|
46
|
+
return qs.select_related('zone', 'zone__instance', 'gateway')
|
|
36
47
|
|
|
37
48
|
def bulk_send(self, data):
|
|
38
49
|
"""
|
|
@@ -50,6 +61,8 @@ class ComponentsManager(models.Manager):
|
|
|
50
61
|
|
|
51
62
|
gateway_components = {}
|
|
52
63
|
for comp, value in data.items():
|
|
64
|
+
if not comp.controller:
|
|
65
|
+
continue
|
|
53
66
|
try:
|
|
54
67
|
value = comp.controller._validate_val(value, BEFORE_SEND)
|
|
55
68
|
except:
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Generated by Django 4.2.10 on 2024-06-06 10:57
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
def forwards_func(apps, schema_editor):
|
|
6
|
+
|
|
7
|
+
Component = apps.get_model("core", "Component")
|
|
8
|
+
Gateway = apps.get_model('core', "Gateway")
|
|
9
|
+
|
|
10
|
+
generic_gateway = Gateway.objects.filter(
|
|
11
|
+
type='simo.generic.gateways.GenericGatewayHandler'
|
|
12
|
+
).first()
|
|
13
|
+
if not generic_gateway:
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
Component.objects.filter(
|
|
17
|
+
controller_uid='simo.generic.controllers.StateSelect'
|
|
18
|
+
).update(gateway=generic_gateway)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def reverse_func(apps, schema_editor):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Migration(migrations.Migration):
|
|
26
|
+
|
|
27
|
+
dependencies = [
|
|
28
|
+
('core', '0036_auto_20240521_0823'),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
operations = [
|
|
32
|
+
migrations.RunPython(forwards_func, reverse_func, elidable=True),
|
|
33
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Generated by Django 4.2.10 on 2024-06-12 08:14
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('core', '0037_auto_20240606_1057'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.RemoveField(
|
|
14
|
+
model_name='instance',
|
|
15
|
+
name='cover_image',
|
|
16
|
+
),
|
|
17
|
+
migrations.RemoveField(
|
|
18
|
+
model_name='instance',
|
|
19
|
+
name='cover_image_synced',
|
|
20
|
+
),
|
|
21
|
+
migrations.RemoveField(
|
|
22
|
+
model_name='instance',
|
|
23
|
+
name='secret_key',
|
|
24
|
+
),
|
|
25
|
+
migrations.AlterField(
|
|
26
|
+
model_name='instance',
|
|
27
|
+
name='timezone',
|
|
28
|
+
field=models.CharField(choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Asmera', 'Africa/Asmera'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Timbuktu', 'Africa/Timbuktu'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/ComodRivadavia', 'America/Argentina/ComodRivadavia'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Atka', 'America/Atka'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Buenos_Aires', 'America/Buenos_Aires'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Catamarca', 'America/Catamarca'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Ciudad_Juarez', 'America/Ciudad_Juarez'), ('America/Coral_Harbour', 'America/Coral_Harbour'), ('America/Cordoba', 'America/Cordoba'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Ensenada', 'America/Ensenada'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fort_Wayne', 'America/Fort_Wayne'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Indianapolis', 'America/Indianapolis'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Jujuy', 'America/Jujuy'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Knox_IN', 'America/Knox_IN'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Louisville', 'America/Louisville'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Mendoza', 'America/Mendoza'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montreal', 'America/Montreal'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Nuuk', 'America/Nuuk'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Acre', 'America/Porto_Acre'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Rosario', 'America/Rosario'), ('America/Santa_Isabel', 'America/Santa_Isabel'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Shiprock', 'America/Shiprock'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Virgin', 'America/Virgin'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/South_Pole', 'Antarctica/South_Pole'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Ashkhabad', 'Asia/Ashkhabad'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Calcutta', 'Asia/Calcutta'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Chongqing', 'Asia/Chongqing'), ('Asia/Chungking', 'Asia/Chungking'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Dacca', 'Asia/Dacca'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Harbin', 'Asia/Harbin'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Istanbul', 'Asia/Istanbul'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kashgar', 'Asia/Kashgar'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Katmandu', 'Asia/Katmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macao', 'Asia/Macao'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Rangoon', 'Asia/Rangoon'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Saigon', 'Asia/Saigon'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Tel_Aviv', 'Asia/Tel_Aviv'), ('Asia/Thimbu', 'Asia/Thimbu'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ujung_Pandang', 'Asia/Ujung_Pandang'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Ulan_Bator', 'Asia/Ulan_Bator'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faeroe', 'Atlantic/Faeroe'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Jan_Mayen', 'Atlantic/Jan_Mayen'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/ACT', 'Australia/ACT'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Canberra', 'Australia/Canberra'), ('Australia/Currie', 'Australia/Currie'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/LHI', 'Australia/LHI'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/NSW', 'Australia/NSW'), ('Australia/North', 'Australia/North'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Queensland', 'Australia/Queensland'), ('Australia/South', 'Australia/South'), ('Australia/Sydney', 'Australia/Sydney'), ('Australia/Tasmania', 'Australia/Tasmania'), ('Australia/Victoria', 'Australia/Victoria'), ('Australia/West', 'Australia/West'), ('Australia/Yancowinna', 'Australia/Yancowinna'), ('Brazil/Acre', 'Brazil/Acre'), ('Brazil/DeNoronha', 'Brazil/DeNoronha'), ('Brazil/East', 'Brazil/East'), ('Brazil/West', 'Brazil/West'), ('CET', 'CET'), ('CST6CDT', 'CST6CDT'), ('Canada/Atlantic', 'Canada/Atlantic'), ('Canada/Central', 'Canada/Central'), ('Canada/Eastern', 'Canada/Eastern'), ('Canada/Mountain', 'Canada/Mountain'), ('Canada/Newfoundland', 'Canada/Newfoundland'), ('Canada/Pacific', 'Canada/Pacific'), ('Canada/Saskatchewan', 'Canada/Saskatchewan'), ('Canada/Yukon', 'Canada/Yukon'), ('Chile/Continental', 'Chile/Continental'), ('Chile/EasterIsland', 'Chile/EasterIsland'), ('Cuba', 'Cuba'), ('EET', 'EET'), ('EST', 'EST'), ('EST5EDT', 'EST5EDT'), ('Egypt', 'Egypt'), ('Eire', 'Eire'), ('Etc/GMT', 'Etc/GMT'), ('Etc/GMT+0', 'Etc/GMT+0'), ('Etc/GMT+1', 'Etc/GMT+1'), ('Etc/GMT+10', 'Etc/GMT+10'), ('Etc/GMT+11', 'Etc/GMT+11'), ('Etc/GMT+12', 'Etc/GMT+12'), ('Etc/GMT+2', 'Etc/GMT+2'), ('Etc/GMT+3', 'Etc/GMT+3'), ('Etc/GMT+4', 'Etc/GMT+4'), ('Etc/GMT+5', 'Etc/GMT+5'), ('Etc/GMT+6', 'Etc/GMT+6'), ('Etc/GMT+7', 'Etc/GMT+7'), ('Etc/GMT+8', 'Etc/GMT+8'), ('Etc/GMT+9', 'Etc/GMT+9'), ('Etc/GMT-0', 'Etc/GMT-0'), ('Etc/GMT-1', 'Etc/GMT-1'), ('Etc/GMT-10', 'Etc/GMT-10'), ('Etc/GMT-11', 'Etc/GMT-11'), ('Etc/GMT-12', 'Etc/GMT-12'), ('Etc/GMT-13', 'Etc/GMT-13'), ('Etc/GMT-14', 'Etc/GMT-14'), ('Etc/GMT-2', 'Etc/GMT-2'), ('Etc/GMT-3', 'Etc/GMT-3'), ('Etc/GMT-4', 'Etc/GMT-4'), ('Etc/GMT-5', 'Etc/GMT-5'), ('Etc/GMT-6', 'Etc/GMT-6'), ('Etc/GMT-7', 'Etc/GMT-7'), ('Etc/GMT-8', 'Etc/GMT-8'), ('Etc/GMT-9', 'Etc/GMT-9'), ('Etc/GMT0', 'Etc/GMT0'), ('Etc/Greenwich', 'Etc/Greenwich'), ('Etc/UCT', 'Etc/UCT'), ('Etc/UTC', 'Etc/UTC'), ('Etc/Universal', 'Etc/Universal'), ('Etc/Zulu', 'Etc/Zulu'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belfast', 'Europe/Belfast'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Kyiv', 'Europe/Kyiv'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Nicosia', 'Europe/Nicosia'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Tiraspol', 'Europe/Tiraspol'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('GB', 'GB'), ('GB-Eire', 'GB-Eire'), ('GMT', 'GMT'), ('GMT+0', 'GMT+0'), ('GMT-0', 'GMT-0'), ('GMT0', 'GMT0'), ('Greenwich', 'Greenwich'), ('HST', 'HST'), ('Hongkong', 'Hongkong'), ('Iceland', 'Iceland'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Iran', 'Iran'), ('Israel', 'Israel'), ('Jamaica', 'Jamaica'), ('Japan', 'Japan'), ('Kwajalein', 'Kwajalein'), ('Libya', 'Libya'), ('MET', 'MET'), ('MST', 'MST'), ('MST7MDT', 'MST7MDT'), ('Mexico/BajaNorte', 'Mexico/BajaNorte'), ('Mexico/BajaSur', 'Mexico/BajaSur'), ('Mexico/General', 'Mexico/General'), ('NZ', 'NZ'), ('NZ-CHAT', 'NZ-CHAT'), ('Navajo', 'Navajo'), ('PRC', 'PRC'), ('PST8PDT', 'PST8PDT'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Johnston', 'Pacific/Johnston'), ('Pacific/Kanton', 'Pacific/Kanton'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Ponape', 'Pacific/Ponape'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Samoa', 'Pacific/Samoa'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Truk', 'Pacific/Truk'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('Pacific/Yap', 'Pacific/Yap'), ('Poland', 'Poland'), ('Portugal', 'Portugal'), ('ROC', 'ROC'), ('ROK', 'ROK'), ('Singapore', 'Singapore'), ('Turkey', 'Turkey'), ('UCT', 'UCT'), ('US/Alaska', 'US/Alaska'), ('US/Aleutian', 'US/Aleutian'), ('US/Arizona', 'US/Arizona'), ('US/Central', 'US/Central'), ('US/East-Indiana', 'US/East-Indiana'), ('US/Eastern', 'US/Eastern'), ('US/Hawaii', 'US/Hawaii'), ('US/Indiana-Starke', 'US/Indiana-Starke'), ('US/Michigan', 'US/Michigan'), ('US/Mountain', 'US/Mountain'), ('US/Pacific', 'US/Pacific'), ('US/Samoa', 'US/Samoa'), ('UTC', 'UTC'), ('Universal', 'Universal'), ('W-SU', 'W-SU'), ('WET', 'WET'), ('Zulu', 'Zulu')], db_index=True, max_length=50),
|
|
29
|
+
),
|
|
30
|
+
]
|
|
Binary file
|
|
Binary file
|
simo/core/models.py
CHANGED
|
@@ -12,9 +12,11 @@ from timezone_utils.choices import ALL_TIMEZONES_CHOICES
|
|
|
12
12
|
from location_field.models.plain import PlainLocationField
|
|
13
13
|
from model_utils import FieldTracker
|
|
14
14
|
from dirtyfields import DirtyFieldsMixin
|
|
15
|
+
from actstream import action
|
|
15
16
|
from simo.core.utils.mixins import SimoAdminMixin
|
|
16
17
|
from simo.core.storage import OverwriteStorage
|
|
17
18
|
from simo.core.utils.validators import validate_svg
|
|
19
|
+
from simo.core.utils.helpers import get_random_string
|
|
18
20
|
from simo.users.models import User
|
|
19
21
|
from .managers import ZonesManager, CategoriesManager, ComponentsManager
|
|
20
22
|
from .events import GatewayObjectCommand, OnChangeMixin
|
|
@@ -64,16 +66,10 @@ class Instance(models.Model, SimoAdminMixin):
|
|
|
64
66
|
# or something of that kind.
|
|
65
67
|
# Usually, there will be only one.
|
|
66
68
|
uid = models.CharField(
|
|
67
|
-
max_length=50, unique=True,
|
|
68
|
-
help_text="Issued by SIMO.io"
|
|
69
|
+
max_length=50, unique=True, help_text="Issued by SIMO.io"
|
|
69
70
|
)
|
|
70
71
|
name = models.CharField(max_length=100, db_index=True, unique=True)
|
|
71
72
|
slug = models.CharField(max_length=100, db_index=True, unique=True)
|
|
72
|
-
cover_image = models.ImageField(
|
|
73
|
-
name='cover_image', upload_to='hub_covers', null=True, blank=True
|
|
74
|
-
)
|
|
75
|
-
cover_image_synced = models.BooleanField(default=False)
|
|
76
|
-
secret_key = models.CharField(max_length=100, blank=True)
|
|
77
73
|
date_created = models.DateTimeField(auto_now_add=True)
|
|
78
74
|
location = PlainLocationField(null=True, blank=True, zoom=7)
|
|
79
75
|
timezone = models.CharField(
|
|
@@ -399,6 +395,8 @@ def is_in_alarm(self):
|
|
|
399
395
|
|
|
400
396
|
controller_cls = None
|
|
401
397
|
|
|
398
|
+
_controller_initiated = False
|
|
399
|
+
|
|
402
400
|
_mqtt_client = None
|
|
403
401
|
_on_change_function = None
|
|
404
402
|
_obj_ct_id = 0
|
|
@@ -408,15 +406,21 @@ def is_in_alarm(self):
|
|
|
408
406
|
verbose_name_plural = _("Components")
|
|
409
407
|
ordering = 'zone', 'base_type', 'name'
|
|
410
408
|
|
|
411
|
-
def __init__(self, *args, **kwargs):
|
|
412
|
-
super().__init__(*args, **kwargs)
|
|
413
|
-
self.prepare_controller()
|
|
414
|
-
|
|
415
409
|
def __str__(self):
|
|
416
410
|
if self.zone:
|
|
417
411
|
return '%s | %s' % (self.zone.name, self.name)
|
|
418
412
|
return self.name
|
|
419
413
|
|
|
414
|
+
def __getattribute__(self, attr):
|
|
415
|
+
try:
|
|
416
|
+
return super().__getattribute__(attr)
|
|
417
|
+
except Exception as e:
|
|
418
|
+
if not attr.startswith('_') and not self._controller_initiated:
|
|
419
|
+
self._controller_initiated = True
|
|
420
|
+
self.prepare_controller()
|
|
421
|
+
return super().__getattribute__(attr)
|
|
422
|
+
raise e
|
|
423
|
+
|
|
420
424
|
@cached_property
|
|
421
425
|
def controller(self):
|
|
422
426
|
from .utils.type_constants import (
|
|
@@ -425,13 +429,13 @@ def is_in_alarm(self):
|
|
|
425
429
|
)
|
|
426
430
|
self._meta.get_field('controller_uid').choices = CONTROLLER_TYPES_CHOICES
|
|
427
431
|
if self.controller_uid:
|
|
428
|
-
controller_cls = None
|
|
429
|
-
if not controller_cls:
|
|
430
|
-
controller_cls = CONTROLLERS_BY_GATEWAY.get(
|
|
432
|
+
self.controller_cls = None
|
|
433
|
+
if not self.controller_cls:
|
|
434
|
+
self.controller_cls = CONTROLLERS_BY_GATEWAY.get(
|
|
431
435
|
self.gateway.type, {}
|
|
432
436
|
).get(self.controller_uid)
|
|
433
|
-
if controller_cls:
|
|
434
|
-
return controller_cls(self)
|
|
437
|
+
if self.controller_cls:
|
|
438
|
+
return self.controller_cls(self)
|
|
435
439
|
|
|
436
440
|
def prepare_controller(self):
|
|
437
441
|
if self.controller:
|
|
@@ -483,6 +487,11 @@ def is_in_alarm(self):
|
|
|
483
487
|
component=self, type='value', value=self.value,
|
|
484
488
|
user=actor
|
|
485
489
|
)
|
|
490
|
+
action.send(
|
|
491
|
+
actor, target=self, verb="value change",
|
|
492
|
+
instance_id=self.zone.instance.id,
|
|
493
|
+
action_type='comp_value', value=self.value
|
|
494
|
+
)
|
|
486
495
|
action_performed = True
|
|
487
496
|
self.last_change = timezone.now()
|
|
488
497
|
if 'arm_status' in dirty_fields:
|
|
@@ -490,6 +499,11 @@ def is_in_alarm(self):
|
|
|
490
499
|
component=self, type='security',
|
|
491
500
|
value=self.arm_status, user=actor
|
|
492
501
|
)
|
|
502
|
+
action.send(
|
|
503
|
+
actor, target=self, verb="security event",
|
|
504
|
+
instance_id=self.zone.instance.id,
|
|
505
|
+
action_type='security', value=self.value
|
|
506
|
+
)
|
|
493
507
|
action_performed = True
|
|
494
508
|
self.last_change = timezone.now()
|
|
495
509
|
if action_performed:
|
simo/core/permissions.py
CHANGED
|
@@ -10,9 +10,12 @@ class InstancePermission(BasePermission):
|
|
|
10
10
|
if not request.user.is_active:
|
|
11
11
|
return False
|
|
12
12
|
|
|
13
|
-
instance =
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
instance = getattr(view, 'instance', None)
|
|
14
|
+
if not instance:
|
|
15
|
+
instance = Instance.objects.filter(
|
|
16
|
+
slug=request.resolver_match.kwargs.get('instance_slug')
|
|
17
|
+
).first()
|
|
18
|
+
|
|
16
19
|
if not instance:
|
|
17
20
|
raise Http404()
|
|
18
21
|
|
simo/core/serializers.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import datetime
|
|
3
|
-
import
|
|
3
|
+
import re
|
|
4
4
|
from django import forms
|
|
5
5
|
from collections import OrderedDict
|
|
6
|
-
from django.forms.utils import ErrorDict
|
|
7
6
|
from django.conf import settings
|
|
8
7
|
from collections.abc import Iterable
|
|
9
8
|
from easy_thumbnails.files import get_thumbnailer
|
|
@@ -11,7 +10,13 @@ from simo.core.middleware import get_current_request
|
|
|
11
10
|
from rest_framework import serializers
|
|
12
11
|
from rest_framework.fields import SkipField
|
|
13
12
|
from rest_framework.relations import Hyperlink, PKOnlyObject
|
|
13
|
+
from actstream.models import Action
|
|
14
14
|
from simo.core.forms import HiddenField, FormsetField
|
|
15
|
+
from simo.core.form_fields import (
|
|
16
|
+
Select2ListChoiceField, Select2ListChoiceField,
|
|
17
|
+
Select2ModelChoiceField, Select2ListMultipleChoiceField
|
|
18
|
+
)
|
|
19
|
+
from simo.core.models import Component
|
|
15
20
|
from rest_framework.relations import PrimaryKeyRelatedField, ManyRelatedField
|
|
16
21
|
from .drf_braces.serializers.form_serializer import (
|
|
17
22
|
FormSerializer, FormSerializerBase, reduce_attr_dict_from_instance,
|
|
@@ -106,6 +111,7 @@ class ComponentFormsetField(FormSerializer):
|
|
|
106
111
|
form = forms.Form
|
|
107
112
|
field_mapping = {
|
|
108
113
|
HiddenField: HiddenSerializerField,
|
|
114
|
+
Select2ListChoiceField: serializers.ChoiceField,
|
|
109
115
|
forms.ModelChoiceField: FormsetPrimaryKeyRelatedField,
|
|
110
116
|
forms.TypedChoiceField: serializers.ChoiceField,
|
|
111
117
|
forms.FloatField: serializers.FloatField,
|
|
@@ -249,6 +255,9 @@ class ComponentSerializer(FormSerializer):
|
|
|
249
255
|
arm_status = ObjectSerializerMethodField()
|
|
250
256
|
battery_level = ObjectSerializerMethodField()
|
|
251
257
|
controller_methods = serializers.SerializerMethodField()
|
|
258
|
+
info = serializers.SerializerMethodField()
|
|
259
|
+
|
|
260
|
+
_forms = {}
|
|
252
261
|
|
|
253
262
|
class Meta:
|
|
254
263
|
form = ComponentAdminForm
|
|
@@ -258,11 +267,27 @@ class ComponentSerializer(FormSerializer):
|
|
|
258
267
|
forms.FloatField: serializers.FloatField,
|
|
259
268
|
forms.SlugField: serializers.CharField,
|
|
260
269
|
forms.ModelChoiceField: ComponentPrimaryKeyRelatedField,
|
|
270
|
+
Select2ModelChoiceField: ComponentPrimaryKeyRelatedField,
|
|
261
271
|
forms.ModelMultipleChoiceField: ComponentManyToManyRelatedField,
|
|
272
|
+
Select2ListMultipleChoiceField: ComponentManyToManyRelatedField,
|
|
262
273
|
FormsetField: ComponentFormsetField,
|
|
263
274
|
}
|
|
264
275
|
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def __init__(self, *args, **kwargs):
|
|
279
|
+
super().__init__(*args, **kwargs)
|
|
280
|
+
# Set proper instance for OPTIONS request
|
|
281
|
+
if not self.instance:
|
|
282
|
+
res = re.findall(
|
|
283
|
+
r'.*\/core\/components\/(?P<component_id>[0-9]+)\/',
|
|
284
|
+
self.context['request'].path
|
|
285
|
+
)
|
|
286
|
+
if res:
|
|
287
|
+
self.instance = Component.objects.filter(id=res[0]).first()
|
|
288
|
+
|
|
265
289
|
def get_fields(self):
|
|
290
|
+
|
|
266
291
|
self.set_form_cls()
|
|
267
292
|
|
|
268
293
|
ret = OrderedDict()
|
|
@@ -322,6 +347,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
322
347
|
if name in ret:
|
|
323
348
|
continue
|
|
324
349
|
ret[name] = field
|
|
350
|
+
|
|
325
351
|
return ret
|
|
326
352
|
|
|
327
353
|
def _get_field_kwargs(self, form_field, serializer_field_class):
|
|
@@ -359,7 +385,15 @@ class ComponentSerializer(FormSerializer):
|
|
|
359
385
|
if controller:
|
|
360
386
|
self.Meta.form = controller.config_form
|
|
361
387
|
|
|
362
|
-
def get_form(self, data=None, **kwargs):
|
|
388
|
+
def get_form(self, data=None, instance=None, **kwargs):
|
|
389
|
+
form_key = None
|
|
390
|
+
if not data:
|
|
391
|
+
form_key = 0
|
|
392
|
+
if instance:
|
|
393
|
+
form_key = instance.id
|
|
394
|
+
if form_key in self._forms:
|
|
395
|
+
return self._forms[form_key]
|
|
396
|
+
|
|
363
397
|
self.set_form_cls()
|
|
364
398
|
if not self.instance or isinstance(self.instance, Iterable):
|
|
365
399
|
#controller_uid = 'simo.generic.controllers.AlarmClock'
|
|
@@ -368,7 +402,7 @@ class ComponentSerializer(FormSerializer):
|
|
|
368
402
|
controller_uid = self.instance.controller_uid
|
|
369
403
|
form = self.Meta.form(
|
|
370
404
|
data=data, request=self.context['request'],
|
|
371
|
-
controller_uid=controller_uid,
|
|
405
|
+
controller_uid=controller_uid, instance=instance,
|
|
372
406
|
**kwargs
|
|
373
407
|
)
|
|
374
408
|
if not self.context['request'].user.is_master:
|
|
@@ -380,6 +414,10 @@ class ComponentSerializer(FormSerializer):
|
|
|
380
414
|
if field_name not in form.basic_fields:
|
|
381
415
|
print("DELETE FIELD: ", field_name)
|
|
382
416
|
del form.fields[field_name]
|
|
417
|
+
|
|
418
|
+
if form_key is not None:
|
|
419
|
+
self._forms[form_key] = form
|
|
420
|
+
|
|
383
421
|
return form
|
|
384
422
|
|
|
385
423
|
def accomodate_formsets(self, form, data):
|
|
@@ -426,7 +464,6 @@ class ComponentSerializer(FormSerializer):
|
|
|
426
464
|
a_data = self.accomodate_formsets(form, validated_data)
|
|
427
465
|
form = self.get_form(instance=instance, data=a_data)
|
|
428
466
|
if form.is_valid():
|
|
429
|
-
print("FORM FIELDS", form.fields)
|
|
430
467
|
instance = form.save(commit=True)
|
|
431
468
|
return instance
|
|
432
469
|
raise serializers.ValidationError(form.errors)
|
|
@@ -450,6 +487,10 @@ class ComponentSerializer(FormSerializer):
|
|
|
450
487
|
c_methods.extend(['arm', 'disarm'])
|
|
451
488
|
return c_methods
|
|
452
489
|
|
|
490
|
+
def get_info(self, obj):
|
|
491
|
+
if obj.controller:
|
|
492
|
+
return obj.controller.info()
|
|
493
|
+
|
|
453
494
|
def get_read_only(self, obj):
|
|
454
495
|
user = self.context.get('user')
|
|
455
496
|
if not user:
|
|
@@ -507,3 +548,34 @@ class ComponentHistorySerializer(serializers.ModelSerializer):
|
|
|
507
548
|
class Meta:
|
|
508
549
|
model = ComponentHistory
|
|
509
550
|
fields = '__all__'
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
class ActionSerializer(serializers.ModelSerializer):
|
|
554
|
+
timestamp = TimestampField()
|
|
555
|
+
actor = serializers.SerializerMethodField()
|
|
556
|
+
target = serializers.SerializerMethodField()
|
|
557
|
+
action_type = serializers.SerializerMethodField()
|
|
558
|
+
value = serializers.SerializerMethodField()
|
|
559
|
+
|
|
560
|
+
class Meta:
|
|
561
|
+
model = Action
|
|
562
|
+
fields = (
|
|
563
|
+
'id', 'timestamp', 'actor', 'target', 'verb',
|
|
564
|
+
'action_type', 'value'
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
def get_actor(self, obj):
|
|
568
|
+
if obj.actor:
|
|
569
|
+
return str(obj.actor)
|
|
570
|
+
|
|
571
|
+
def get_target(self, obj):
|
|
572
|
+
if obj.target:
|
|
573
|
+
return str(obj.target)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def get_action_type(self, obj):
|
|
577
|
+
return obj.data.get('action_type')
|
|
578
|
+
|
|
579
|
+
def get_value(self, obj):
|
|
580
|
+
return obj.data.get('value')
|
|
581
|
+
|