diff --git a/tailbone/templates/tempmon/probes/create.mako b/tailbone/templates/tempmon/probes/create.mako
new file mode 100644
index 00000000..95dbd3d6
--- /dev/null
+++ b/tailbone/templates/tempmon/probes/create.mako
@@ -0,0 +1,17 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/master/create.mako" />
+
+<%def name="head_tags()">
+ ${parent.head_tags()}
+
+%def>
+
+${parent.body()}
diff --git a/tailbone/templates/tempmon/probes/edit.mako b/tailbone/templates/tempmon/probes/edit.mako
new file mode 100644
index 00000000..b9f2a6b2
--- /dev/null
+++ b/tailbone/templates/tempmon/probes/edit.mako
@@ -0,0 +1,17 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/master/edit.mako" />
+
+<%def name="head_tags()">
+ ${parent.head_tags()}
+
+%def>
+
+${parent.body()}
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index 3b3b3c99..27d7a42d 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -26,8 +26,6 @@ Model Master View
from __future__ import unicode_literals, absolute_import
-import re
-
import sqlalchemy as sa
from sqlalchemy import orm
@@ -520,9 +518,7 @@ class MasterView(View):
"""
if hasattr(cls, 'model_title'):
return cls.model_title
- title = cls.get_model_class().__name__
- # convert "CamelCase" to "Camel Case"
- return re.sub(r'([a-z])([A-Z])', r'\g<1> \g<2>', title)
+ return cls.get_model_class().get_model_title()
@classmethod
def get_model_title_plural(cls):
diff --git a/tailbone/views/tempmon/__init__.py b/tailbone/views/tempmon/__init__.py
new file mode 100644
index 00000000..b9b8d1af
--- /dev/null
+++ b/tailbone/views/tempmon/__init__.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+################################################################################
+#
+# Rattail -- Retail Software Framework
+# Copyright © 2010-2016 Lance Edgar
+#
+# This file is part of Rattail.
+#
+# Rattail is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+# more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Rattail. If not, see .
+#
+################################################################################
+"""
+Views for tempmon
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+
+def includeme(config):
+ config.include('tailbone.views.tempmon.clients')
+ config.include('tailbone.views.tempmon.probes')
+ config.include('tailbone.views.tempmon.readings')
diff --git a/tailbone/views/tempmon/clients.py b/tailbone/views/tempmon/clients.py
new file mode 100644
index 00000000..e4211104
--- /dev/null
+++ b/tailbone/views/tempmon/clients.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+################################################################################
+#
+# Rattail -- Retail Software Framework
+# Copyright © 2010-2016 Lance Edgar
+#
+# This file is part of Rattail.
+#
+# Rattail is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+# more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Rattail. If not, see .
+#
+################################################################################
+"""
+Views for tempmon clients
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+from rattail.db import model
+
+import formalchemy as fa
+from webhelpers.html import HTML, tags
+
+from tailbone.db import Session
+from tailbone.views import MasterView
+
+
+class ProbesFieldRenderer(fa.FieldRenderer):
+
+ def render_readonly(self, **kwargs):
+ probes = self.raw_value
+ if not probes:
+ return ''
+ items = []
+ for probe in probes:
+ items.append(HTML.tag('li', c=tags.link_to(probe, self.request.route_url('tempmon.probes.view', uuid=probe.uuid))))
+ return HTML.tag('ul', c=items)
+
+
+def unique_config_key(value, field):
+ client = field.parent.model
+ query = Session.query(model.TempmonClient)\
+ .filter(model.TempmonClient.config_key == value)
+ if client.uuid:
+ query = query.filter(model.TempmonClient.uuid != client.uuid)
+ if query.count():
+ raise fa.ValidationError("Config key must be unique")
+
+
+class TempmonClientView(MasterView):
+ """
+ Master view for tempmon clients.
+ """
+ model_class = model.TempmonClient
+ route_prefix = 'tempmon.clients'
+ url_prefix = '/tempmon/clients'
+
+ def _preconfigure_grid(self, g):
+ g.filters['hostname'].default_active = True
+ g.filters['hostname'].default_verb = 'contains'
+ g.filters['location'].default_active = True
+ g.filters['location'].default_verb = 'contains'
+ g.default_sortkey = 'config_key'
+ g.config_key.set(label="Key")
+
+ def configure_grid(self, g):
+ g.configure(
+ include=[
+ g.config_key,
+ g.hostname,
+ g.location,
+ g.enabled,
+ g.online,
+ ],
+ readonly=True)
+
+ def _preconfigure_fieldset(self, fs):
+ fs.config_key.set(validate=unique_config_key)
+ fs.probes.set(renderer=ProbesFieldRenderer)
+
+ def configure_fieldset(self, fs):
+ fs.configure(
+ include=[
+ fs.config_key,
+ fs.hostname,
+ fs.location,
+ fs.probes,
+ fs.enabled,
+ fs.online,
+ ])
+ if self.creating or self.editing:
+ del fs.probes
+ del fs.online
+
+
+def includeme(config):
+ TempmonClientView.defaults(config)
diff --git a/tailbone/views/tempmon/probes.py b/tailbone/views/tempmon/probes.py
new file mode 100644
index 00000000..ea47d387
--- /dev/null
+++ b/tailbone/views/tempmon/probes.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+################################################################################
+#
+# Rattail -- Retail Software Framework
+# Copyright © 2010-2016 Lance Edgar
+#
+# This file is part of Rattail.
+#
+# Rattail is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+# more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Rattail. If not, see .
+#
+################################################################################
+"""
+Views for tempmon probes
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+from rattail.db import model
+
+from formalchemy.fields import SelectFieldRenderer
+from webhelpers.html import tags
+
+from tailbone import forms
+from tailbone.views import MasterView
+
+
+class ClientFieldRenderer(SelectFieldRenderer):
+
+ def render_readonly(self, **kwargs):
+ client = self.raw_value
+ if not client:
+ return ''
+ return tags.link_to(client, self.request.route_url('tempmon.clients.view', uuid=client.uuid))
+
+
+class TempmonProbeView(MasterView):
+ """
+ Master view for tempmon probes.
+ """
+ model_class = model.TempmonProbe
+ route_prefix = 'tempmon.probes'
+ url_prefix = '/tempmon/probes'
+
+ def _preconfigure_grid(self, g):
+ g.joiners['client'] = lambda q: q.join(model.TempmonClient)
+ g.sorters['client'] = g.make_sorter(model.TempmonClient.config_key)
+ g.default_sortkey = 'client'
+ g.config_key.set(label="Key")
+ g.appliance_type.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.TEMPMON_APPLIANCE_TYPE))
+
+ def configure_grid(self, g):
+ g.configure(
+ include=[
+ g.client,
+ g.config_key,
+ g.appliance_type,
+ g.description,
+ g.device_path,
+ g.enabled,
+ ],
+ readonly=True)
+
+ def _preconfigure_fieldset(self, fs):
+ fs.client.set(label="TempMon Client", renderer=ClientFieldRenderer)
+ fs.appliance_type.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.TEMPMON_APPLIANCE_TYPE))
+
+ def configure_fieldset(self, fs):
+ fs.configure(
+ include=[
+ fs.client,
+ fs.config_key,
+ fs.appliance_type,
+ fs.description,
+ fs.device_path,
+ fs.good_temp_min,
+ fs.good_temp_max,
+ fs.temp_warn,
+ fs.therm_status_timeout,
+ fs.alert_timeout,
+ fs.enabled,
+ ])
+
+
+def includeme(config):
+ TempmonProbeView.defaults(config)
diff --git a/tailbone/views/tempmon/readings.py b/tailbone/views/tempmon/readings.py
new file mode 100644
index 00000000..43e87285
--- /dev/null
+++ b/tailbone/views/tempmon/readings.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+################################################################################
+#
+# Rattail -- Retail Software Framework
+# Copyright © 2010-2016 Lance Edgar
+#
+# This file is part of Rattail.
+#
+# Rattail is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
+# more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Rattail. If not, see .
+#
+################################################################################
+"""
+Views for tempmon readings
+"""
+
+from __future__ import unicode_literals, absolute_import
+
+from rattail.db import model
+
+from formalchemy.fields import SelectFieldRenderer
+from webhelpers.html import tags
+
+from tailbone.db import Session
+from tailbone.views import MasterView
+from tailbone.views.tempmon.probes import ClientFieldRenderer
+
+
+class ProbeFieldRenderer(SelectFieldRenderer):
+
+ def render_readonly(self, **kwargs):
+ probe = self.raw_value
+ if not probe:
+ return ''
+ return tags.link_to(probe, self.request.route_url('tempmon.probes.view', uuid=probe.uuid))
+
+
+class TempmonReadingView(MasterView):
+ """
+ Master view for tempmon readings.
+ """
+ model_class = model.TempmonReading
+ route_prefix = 'tempmon.readings'
+ url_prefix = '/tempmon/readings'
+
+ def configure_grid(self, g):
+ g.configure(
+ include=[
+ g.client,
+ g.probe,
+ g.taken,
+ g.degrees_f,
+ ],
+ readonly=True)
+
+ def _preconfigure_fieldset(self, fs):
+ fs.client.set(label="TempMon Client", renderer=ClientFieldRenderer)
+ fs.probe.set(label="TempMon Probe", renderer=ProbeFieldRenderer)
+
+ def configure_fieldset(self, fs):
+ fs.configure(
+ include=[
+ fs.client,
+ fs.probe,
+ fs.taken,
+ fs.degrees_f,
+ ])
+ if self.creating:
+ del fs.taken
+
+ # TODO: this should not be so complicated....
+ def save_create_form(self, form):
+ with Session.no_autoflush:
+ form.fieldset.sync()
+ reading = form.fieldset.model
+ probe = Session.query(model.TempmonProbe).get(reading.probe_uuid)
+ reading.client = probe.client
+ Session.flush()
+
+
+def includeme(config):
+ TempmonReadingView.defaults(config)