From 464e63a0d1024d2ee58dd631ae837d88e5dbf8a7 Mon Sep 17 00:00:00 2001
From: Benjamin Bertrand <benjamin.bertrand@esss.se>
Date: Thu, 21 Sep 2017 23:08:12 +0200
Subject: [PATCH] Display history in item view

---
 app/main/views.py            |  2 +-
 app/models.py                | 30 ++++++++++++++++++++---
 app/templates/_helpers.html  | 14 +++++------
 app/templates/view_item.html | 47 +++++++++++++++++++++++++++---------
 4 files changed, 70 insertions(+), 23 deletions(-)

diff --git a/app/main/views.py b/app/main/views.py
index 479ba27..f85bb39 100644
--- a/app/main/views.py
+++ b/app/main/views.py
@@ -68,7 +68,7 @@ def index():
 @login_required
 def view_item(ics_id):
     item = Item.query.filter_by(ics_id=ics_id).first_or_404()
-    return render_template('view_item.html', item=item)
+    return render_template('view_item.html', item=item.to_dict(extra=True))
 
 
 @bp.route('/qrcodes')
diff --git a/app/models.py b/app/models.py
index 4f639c0..588c9dd 100644
--- a/app/models.py
+++ b/app/models.py
@@ -14,7 +14,7 @@ import qrcode
 import sqlalchemy as sa
 from sqlalchemy.orm import validates
 from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy_continuum import make_versioned
+from sqlalchemy_continuum import make_versioned, version_class
 from citext import CIText
 from flask import current_app
 from flask_login import UserMixin
@@ -234,8 +234,8 @@ class Item(db.Model):
                 raise utils.InventoryError('ICS id shall match [A-Z]{3}[0-9]{3}', status_code=422)
         return string
 
-    def to_dict(self):
-        return {
+    def to_dict(self, extra=False):
+        d = {
             'id': self.id,
             'ics_id': self.ics_id,
             'serial_number': self.serial_number,
@@ -247,8 +247,32 @@ class Item(db.Model):
             'created': utils.format_field(self._created),
             'parent': utils.format_field(self.parent),
         }
+        if extra:
+            d['children'] = [utils.format_field(child) for child in self.children]
+            d['history'] = self.history()
+        return d
+
+    def history(self):
+        versions = []
+        for version in self.versions:
+            # parent is an attribute used by SQLAlchemy-Continuum
+            # version.parent refers to an ItemVersion instance (and has no link with
+            # the item parent_id)
+            # We need to retrieve the parent "manually"
+            if version.parent_id is None:
+                parent = None
+            else:
+                parent = Item.query.get(version.parent_id)
+            versions.append({
+                'updated': utils.format_field(version._updated),
+                'location': utils.format_field(version.location),
+                'status': utils.format_field(version.status),
+                'parent': utils.format_field(parent),
+            })
+        return versions
 
 
 # call configure_mappers after defining all the models
 # required by sqlalchemy_continuum
 sa.orm.configure_mappers()
+ItemVersion = version_class(Item)
diff --git a/app/templates/_helpers.html b/app/templates/_helpers.html
index ac555ca..8c761e0 100644
--- a/app/templates/_helpers.html
+++ b/app/templates/_helpers.html
@@ -2,16 +2,16 @@
 {% if active %}active{% endif %}
 {%- endmacro %}
 
-{% macro link_to_item(item) -%}
-  {% if item %}
-    <a href="{{ url_for('main.view_item', ics_id=item.ics_id) }}">{{ item.ics_id }}</a>
+{% macro link_to_item(ics_id) -%}
+  {% if ics_id %}
+    <a href="{{ url_for('main.view_item', ics_id=ics_id) }}">{{ ics_id }}</a>
   {% else %}
-    {{ item }}
+    {{ ics_id }}
   {% endif %}
 {%- endmacro %}
 
-{% macro link_to_children(item) -%}
-  {% for child in item.children %}
-    {{ link_to_item(child) }}
+{% macro link_to_items(ics_ids) -%}
+  {% for ics_id in ics_ids %}
+    {{ link_to_item(ics_id) }}
   {% endfor %}
 {%- endmacro %}
diff --git a/app/templates/view_item.html b/app/templates/view_item.html
index 2155365..645c387 100644
--- a/app/templates/view_item.html
+++ b/app/templates/view_item.html
@@ -1,32 +1,55 @@
 {%- extends "base.html" %}
-{% from "_helpers.html" import link_to_item, link_to_children %}
+{% from "_helpers.html" import link_to_item, link_to_items %}
 
 {% block title %}View Item - ICS Inventory{% endblock %}
 
 {% block main %}
-  <h2>Item {{ item.ics_id }}</h2>
+  <h2>Item {{ item['ics_id'] }}</h2>
 
   <dl class="row">
     <dt class="col-sm-3">ICS id</dt>
-    <dd class="col-sm-9">{{ item.ics_id }}</dt>
+    <dd class="col-sm-9">{{ item['ics_id'] }}</dt>
     <dt class="col-sm-3">Created</dt>
-    <dd class="col-sm-9">{{ item._created.strftime("%Y-%m-%d %H:%M") }}</dt>
+    <dd class="col-sm-9">{{ item['created'] }}</dt>
     <dt class="col-sm-3">Updated</dt>
-    <dd class="col-sm-9">{{ item._updated.strftime("%Y-%m-%d %H:%M") }}</dt>
+    <dd class="col-sm-9">{{ item['updated'] }}</dt>
     <dt class="col-sm-3">Serial number</dt>
-    <dd class="col-sm-9">{{ item.serial_number }}</dt>
+    <dd class="col-sm-9">{{ item['serial_number'] }}</dt>
     <dt class="col-sm-3">Manufacturer</dt>
-    <dd class="col-sm-9">{{ item.manufacturer }}</dt>
+    <dd class="col-sm-9">{{ item['manufacturer'] }}</dt>
     <dt class="col-sm-3">Model</dt>
-    <dd class="col-sm-9">{{ item.model }}</dt>
+    <dd class="col-sm-9">{{ item['model'] }}</dt>
     <dt class="col-sm-3">Location</dt>
-    <dd class="col-sm-9">{{ item.location }}</dt>
+    <dd class="col-sm-9">{{ item['location'] }}</dt>
     <dt class="col-sm-3">Status</dt>
-    <dd class="col-sm-9">{{ item.status }}</dt>
+    <dd class="col-sm-9">{{ item['status'] }}</dt>
     <dt class="col-sm-3">Parent</dt>
-    <dd class="col-sm-9">{{ link_to_item(item.parent) }}</dt>
+    <dd class="col-sm-9">{{ link_to_item(item['parent']) }}</dt>
     <dt class="col-sm-3">Children</dt>
-    <dd class="col-sm-9">{{ link_to_children(item) }}</dt>
+    <dd class="col-sm-9">{{ link_to_items(item['children']) }}</dt>
   </dl>
 
+  <h4>History</h4>
+
+  <table id="item_version_table" class="table table-bordered table-hover table-sm">
+    <thead>
+      <tr>
+        <th>Updated</th>
+        <th>Location</th>
+        <th>Status</th>
+        <th>Parent</th>
+      </tr>
+    </thead>
+    <tbody>
+      {% for version in item['history'] %}
+      <tr>
+        <td>{{ version['updated'] }}</td>
+        <td>{{ version['location'] }}</td>
+        <td>{{ version['status'] }}</td>
+        <td>{{ link_to_item(version['parent']) }}</td>
+      </tr>
+      {% endfor %}
+    </tbody>
+  </table>
+
 {%- endblock %}
-- 
GitLab