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()} + + + +${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()} + + + +${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)