From 1c47aad05e988150c5c74d0e19d10c165e652f8d Mon Sep 17 00:00:00 2001
From: Benjamin Bertrand <benjamin.bertrand@esss.se>
Date: Thu, 20 Jul 2017 10:46:06 +0200
Subject: [PATCH] Add method to create an item via the API

---
 app/api/items.py | 40 +++++++++++++++++++++++++++++-----------
 app/models.py    | 22 ++++++++++++++++++----
 app/utils.py     | 18 ++++++++++++++++++
 3 files changed, 65 insertions(+), 15 deletions(-)

diff --git a/app/api/items.py b/app/api/items.py
index 7caa5cf..1eb8471 100644
--- a/app/api/items.py
+++ b/app/api/items.py
@@ -9,11 +9,13 @@ This module implements the items API.
 :license: BSD 2-Clause, see LICENSE for more details.
 
 """
+import sqlalchemy as sa
 from flask import (current_app, Blueprint, jsonify, request)
 from flask_jwt_extended import create_access_token, jwt_required
 from flask_ldap3_login import AuthenticationResponseStatus
 from ..extensions import ldap_manager, db
-from ..models import User
+from ..models import User, Item
+from .. import utils
 
 bp = Blueprint('api', __name__)
 
@@ -42,13 +44,29 @@ def login():
 @bp.route('/items')
 @jwt_required
 def get_items():
-    sql = """SELECT item.id, item.name, manufacturer.name, model.name, location.name, status.name
-            FROM item
-            INNER JOIN manufacturer ON manufacturer.id = item.manufacturer_id
-            INNER JOIN model ON model.id = item.model_id
-            INNER JOIN location ON location.id = item.location_id
-            INNER JOIN status ON status.id = item.status_id
-            """
-    result = db.engine.execute(sql)
-    data = [[item for item in row] for row in result]
-    return jsonify(data=data)
+    # TODO: add pagination
+    items = Item.query.order_by(Item._created)
+    data = [item.to_dict() for item in items]
+    return jsonify({'items': data})
+
+
+@bp.route('/items', methods=['POST'])
+@jwt_required
+def create_item():
+    data = request.get_json()
+    if data is None:
+        raise utils.InventoryError('Body should be a JSON object')
+    if 'serial_number' not in data:
+        raise utils.InventoryError('serial_number is mandatory')
+    try:
+        item = Item(**data)
+    except TypeError as e:
+        message = str(e).replace('__init__() got an ', '')
+        raise utils.InventoryError(message)
+    db.session.add(item)
+    try:
+        db.session.commit()
+    except sa.exc.IntegrityError as e:
+        db.session.rollback()
+        raise utils.InventoryError('IntegrityError', status_code=409)
+    return jsonify(item.to_dict()), 201
diff --git a/app/models.py b/app/models.py
index 02f3173..055de44 100644
--- a/app/models.py
+++ b/app/models.py
@@ -184,11 +184,25 @@ class Item(db.Model):
         # All arguments must be optional for this class to work with flask-admin!
         self.serial_number = serial_number
         self.name = name
-        self.manufacturer = manufacturer
-        self.model = model
-        self.location = location
-        self.status = status
+        self.manufacturer = utils.convert_to_model(manufacturer, Manufacturer)
+        self.model = utils.convert_to_model(model, Model)
+        self.location = utils.convert_to_model(location, Location)
+        self.status = utils.convert_to_model(status, Status)
         self.hash = utils.compute_hash(serial_number)
 
     def __str__(self):
         return self.serial_number
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'serial_number': self.serial_number,
+            'name': self.name,
+            'hash': self.hash,
+            'manufacturer': utils.format_field(self.manufacturer),
+            'model': utils.format_field(self.model),
+            'location': utils.format_field(self.location),
+            'status': utils.format_field(self.status),
+            'updated': utils.format_field(self._updated),
+            'created': utils.format_field(self._created),
+        }
diff --git a/app/utils.py b/app/utils.py
index dddef7b..f67893d 100644
--- a/app/utils.py
+++ b/app/utils.py
@@ -65,3 +65,21 @@ def format_field(field):
     if isinstance(field, datetime.datetime):
         return field.strftime('%Y-%m-%d %H:%M')
     return str(field)
+
+
+def convert_to_model(item, model):
+    """Convert item to an instance of model
+
+    Allow to convert a string to an instance of model
+    Raise an exception if the given name is not found
+
+    :returns: an instance of model
+    """
+    if item is None:
+        return None
+    if not isinstance(item, model):
+        instance = model.query.filter_by(name=item).first()
+        if instance is None:
+            raise InventoryError(f'{item} is not a valid {model.__name__.lower()}')
+        return instance
+    return item
-- 
GitLab