diff --git a/app/factory.py b/app/factory.py index dab36078edd83224fcbf8a29ed5f326f011009ab..f420f557ccc4688c3e75ced9204061a1d1500c3b 100644 --- a/app/factory.py +++ b/app/factory.py @@ -108,6 +108,7 @@ def create_app(config=None): admin.add_view(AdminModelView(models.Location, db.session)) admin.add_view(AdminModelView(models.Status, db.session)) admin.add_view(ItemAdmin(models.Item, db.session)) + admin.add_view(AdminModelView(models.ItemComment, db.session)) admin.add_view(AdminModelView(models.NetworkScope, db.session)) admin.add_view(NetworkAdmin(models.Network, db.session)) admin.add_view(AdminModelView(models.Interface, db.session)) diff --git a/app/inventory/forms.py b/app/inventory/forms.py index 008c72e5d89329228039633a73bdad17223b5c1b..6ce5ff4fc92c33dacda835faf5555c2accec5b0e 100644 --- a/app/inventory/forms.py +++ b/app/inventory/forms.py @@ -11,7 +11,7 @@ This module defines the inventory blueprint forms. """ import re from flask_wtf import FlaskForm -from wtforms import SelectField, StringField, validators +from wtforms import SelectField, StringField, TextAreaField, validators from .. import utils, models MAC_ADDRESS_RE = re.compile('^(?:[0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$') @@ -50,3 +50,7 @@ class ItemForm(FlaskForm): self.location_id.choices = utils.get_model_choices(models.Location, allow_blank=True) self.status_id.choices = utils.get_model_choices(models.Status, allow_blank=True) self.parent_id.choices = utils.get_model_choices(models.Item, allow_blank=True, attr='ics_id') + + +class CommentForm(FlaskForm): + text = TextAreaField('Comment', validators=[validators.DataRequired()]) diff --git a/app/inventory/views.py b/app/inventory/views.py index c87bb6997a640958686e198506dc1084d7eb5f13..6457d57aae5fb8b382261eb2d7025b2e251127e3 100644 --- a/app/inventory/views.py +++ b/app/inventory/views.py @@ -12,8 +12,8 @@ This module implements the inventory blueprint. import sqlalchemy as sa from flask import (Blueprint, render_template, jsonify, session, request, redirect, url_for, flash, current_app) -from flask_login import login_required -from .forms import AttributeForm, ItemForm +from flask_login import login_required, current_user +from .forms import AttributeForm, ItemForm, CommentForm from ..extensions import db from ..decorators import login_groups_accepted from .. import utils, models @@ -82,7 +82,22 @@ def create_item(): @login_required def view_item(ics_id): item = models.Item.query.filter_by(ics_id=ics_id).first_or_404() - return render_template('inventory/view_item.html', item=item.to_dict(long=True)) + return render_template('inventory/view_item.html', item=item) + + +@bp.route('/items/comment/<ics_id>', methods=('GET', 'POST')) +@login_groups_accepted('admin', 'create') +def comment_item(ics_id): + item = models.Item.query.filter_by(ics_id=ics_id).first_or_404() + form = CommentForm() + if form.validate_on_submit(): + comment = models.ItemComment(text=form.text.data, + user_id=current_user.id, + item_id=item.id) + db.session.add(comment) + db.session.commit() + return redirect(url_for('inventory.view_item', ics_id=ics_id)) + return render_template('inventory/comment_item.html', item=item, form=form) @bp.route('/items/edit/<ics_id>', methods=('GET', 'POST')) diff --git a/app/models.py b/app/models.py index f9a3b0724c6d58881de6edc5d387c0a978845876..f9996be3d0f2cd090aef5aecc6a3476a2c4eb96f 100644 --- a/app/models.py +++ b/app/models.py @@ -98,6 +98,7 @@ class User(db.Model, UserMixin): groups = association_proxy('grp', 'name', creator=find_or_create_group) tokens = db.relationship("Token", backref="user") + comments = db.relationship('ItemComment', backref='user') def get_id(self): """Return the user id as unicode @@ -235,6 +236,7 @@ class Item(db.Model): status = db.relationship('Status', back_populates='items') children = db.relationship('Item', backref=db.backref('parent', remote_side=[id])) macs = db.relationship('Mac', backref='item') + comments = db.relationship('ItemComment', backref='item') def __init__(self, **kwargs): # Automatically convert manufacturer/model/location/status to an @@ -297,6 +299,14 @@ class Item(db.Model): return versions +class ItemComment(db.Model): + id = db.Column(db.Integer, primary_key=True) + timestamp = db.Column(db.DateTime, default=db.func.now(), index=True) + text = db.Column(db.Text, nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user_account.id'), nullable=False) + item_id = db.Column(db.Integer, db.ForeignKey('item.id'), nullable=False) + + class Network(db.Model): id = db.Column(db.Integer, primary_key=True) vlan_name = db.Column(CIText, nullable=False, unique=True) diff --git a/app/templates/_helpers.html b/app/templates/_helpers.html index 431cb816d26571e0f9423dd52ba965bf35404cd2..30a990c858617deff3c1b2082300dad0f331c32b 100644 --- a/app/templates/_helpers.html +++ b/app/templates/_helpers.html @@ -40,3 +40,7 @@ </div> </div> {%- endmacro %} + +{% macro format_datetime(dt) -%} + {{ dt.strftime("%Y-%m-%d %H:%M") }} +{%- endmacro %} diff --git a/app/templates/inventory/comment_item.html b/app/templates/inventory/comment_item.html new file mode 100644 index 0000000000000000000000000000000000000000..ffe50dc13d98dfd6f8e51daa2a139d2d48e951ca --- /dev/null +++ b/app/templates/inventory/comment_item.html @@ -0,0 +1,100 @@ +{% extends "base-fluid.html" %} +{% from "_helpers.html" import link_to_item, link_to_items, format_datetime %} + +{% block title %}View Item - CSEntry{% endblock %} + +{% block main %} + <ul class="nav nav-tabs"> + <li class="nav-item"> + <a class="nav-link" href="{{ url_for('inventory.list_items') }}">List items</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="{{ url_for('inventory.create_item') }}">Register new item</a> + </li> + <li class="nav-item"> + <a class="nav-link active" href="{{ url_for('inventory.view_item', ics_id=item.ics_id) }}">View item</a> + </li> + <li class="nav-item"> + <a class="nav-link" href="{{ url_for('inventory.edit_item', ics_id=item.ics_id) }}">Edit item</a> + </li> + </ul> + + <br> + + <dl class="row"> + <dt class="col-sm-3">ICS id</dt> + <dd class="col-sm-9">{{ item.ics_id }}</dd> + <dt class="col-sm-3">Created</dt> + <dd class="col-sm-9">{{ format_datetime(item._created) }}</dd> + <dt class="col-sm-3">Updated</dt> + <dd class="col-sm-9">{{ format_datetime(item._updated) }}</dd> + <dt class="col-sm-3">Serial number</dt> + <dd class="col-sm-9">{{ item.serial_number }}</dd> + <dt class="col-sm-3">Manufacturer</dt> + <dd class="col-sm-9">{{ item.manufacturer }}</dd> + <dt class="col-sm-3">Model</dt> + <dd class="col-sm-9">{{ item.model }}</dd> + <dt class="col-sm-3">Location</dt> + <dd class="col-sm-9">{{ item.location }}</dd> + <dt class="col-sm-3">Status</dt> + <dd class="col-sm-9">{{ item.status }}</dd> + {% for mac in item.macs %} + {% set macloop = loop %} + <dt class="col-sm-3">MAC Address{{ loop.index }}</dt> + <dd class="col-sm-9">{{ mac.address }}</dd> + {% for interface in mac.interfaces %} + <dt class="col-sm-3">Interface{{ macloop.index }}-{{ loop.index }}</dt> + <dd class="col-sm-9">IP: {{ interface.ip }} / name: {{ interface.name }}</dd> + {% endfor %} + {% endfor %} + <dt class="col-sm-3">Parent</dt> + <dd class="col-sm-9">{{ link_to_item(item.parent) }}</dd> + <dt class="col-sm-3">Children</dt> + <dd class="col-sm-9">{{ link_to_items(item.children) }}</dd> + </dl> + + <h4>Comments</h4> + {% for comment in item.comments %} + <div class="card border-light mb-3"> + <div class="card-header"> + {{ comment.user }} commented on {{ format_datetime(comment.timestamp) }} + </div> + <div class="card-body"> + <p class="card-text">{{ comment.text }}</p> + </div> + </div> + {% endfor %} + <form id="CommentForm" method="POST"> + {{ form.hidden_tag() }} + <div class="form-group"> + {{ form.text.label() }} + {{ form.text(class_="form-control", required=True) }} + </div> + <button type="submit" class="btn btn-primary">Submit</button> + </form> + + <hr> + + <h4>History</h4> + <table id="item_version_table" class="table 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 %} diff --git a/app/templates/inventory/view_item.html b/app/templates/inventory/view_item.html index 39c979f8242c2edf13408440c1fe0758e3102b3a..8dbf310998b9d36caa8c50a73ca1450d8686ee09 100644 --- a/app/templates/inventory/view_item.html +++ b/app/templates/inventory/view_item.html @@ -1,5 +1,5 @@ {% extends "base-fluid.html" %} -{% from "_helpers.html" import link_to_item, link_to_items %} +{% from "_helpers.html" import link_to_item, link_to_items, format_datetime %} {% block title %}View Item - CSEntry{% endblock %} @@ -12,10 +12,10 @@ <a class="nav-link" href="{{ url_for('inventory.create_item') }}">Register new item</a> </li> <li class="nav-item"> - <a class="nav-link active" href="{{ url_for('inventory.view_item', ics_id=item['ics_id']) }}">View item</a> + <a class="nav-link active" href="{{ url_for('inventory.view_item', ics_id=item.ics_id) }}">View item</a> </li> <li class="nav-item"> - <a class="nav-link" href="{{ url_for('inventory.edit_item', ics_id=item['ics_id']) }}">Edit item</a> + <a class="nav-link" href="{{ url_for('inventory.edit_item', ics_id=item.ics_id) }}">Edit item</a> </li> </ul> @@ -23,22 +23,22 @@ <dl class="row"> <dt class="col-sm-3">ICS id</dt> - <dd class="col-sm-9">{{ item['ics_id'] }}</dd> + <dd class="col-sm-9">{{ item.ics_id }}</dd> <dt class="col-sm-3">Created</dt> - <dd class="col-sm-9">{{ item['created'] }}</dd> + <dd class="col-sm-9">{{ format_datetime(item._created) }}</dd> <dt class="col-sm-3">Updated</dt> - <dd class="col-sm-9">{{ item['updated'] }}</dd> + <dd class="col-sm-9">{{ format_datetime(item._updated) }}</dd> <dt class="col-sm-3">Serial number</dt> - <dd class="col-sm-9">{{ item['serial_number'] }}</dd> + <dd class="col-sm-9">{{ item.serial_number }}</dd> <dt class="col-sm-3">Manufacturer</dt> - <dd class="col-sm-9">{{ item['manufacturer'] }}</dd> + <dd class="col-sm-9">{{ item.manufacturer }}</dd> <dt class="col-sm-3">Model</dt> - <dd class="col-sm-9">{{ item['model'] }}</dd> + <dd class="col-sm-9">{{ item.model }}</dd> <dt class="col-sm-3">Location</dt> - <dd class="col-sm-9">{{ item['location'] }}</dd> + <dd class="col-sm-9">{{ item.location }}</dd> <dt class="col-sm-3">Status</dt> - <dd class="col-sm-9">{{ item['status'] }}</dd> - {% for mac in item['macs'] %} + <dd class="col-sm-9">{{ item.status }}</dd> + {% for mac in item.macs %} {% set macloop = loop %} <dt class="col-sm-3">MAC Address{{ loop.index }}</dt> <dd class="col-sm-9">{{ mac.address }}</dd> @@ -48,13 +48,27 @@ {% endfor %} {% endfor %} <dt class="col-sm-3">Parent</dt> - <dd class="col-sm-9">{{ link_to_item(item['parent']) }}</dd> + <dd class="col-sm-9">{{ link_to_item(item.parent) }}</dd> <dt class="col-sm-3">Children</dt> - <dd class="col-sm-9">{{ link_to_items(item['children']) }}</dd> + <dd class="col-sm-9">{{ link_to_items(item.children) }}</dd> </dl> - <h4>History</h4> + <h4>Comments</h4> + {% for comment in item.comments %} + <div class="card border-light mb-3"> + <div class="card-header"> + {{ comment.user }} commented on {{ format_datetime(comment.timestamp) }} + </div> + <div class="card-body"> + <p class="card-text">{{ comment.text }}</p> + </div> + </div> + {% endfor %} + <a class="btn btn-primary" href="{{ url_for('inventory.comment_item', ics_id=item.ics_id) }}">Comment</a> + + <hr> + <h4>History</h4> <table id="item_version_table" class="table table-hover table-sm"> <thead> <tr> @@ -65,7 +79,7 @@ </tr> </thead> <tbody> - {% for version in item['history'] %} + {% for version in item.history() %} <tr> <td>{{ version['updated'] }}</td> <td>{{ version['location'] }}</td>