From 1fbdf91eb55a9dfec357db73b27772d5469e88d0 Mon Sep 17 00:00:00 2001 From: Benjamin Bertrand <benjamin.bertrand@esss.se> Date: Wed, 20 Dec 2017 15:52:48 +0100 Subject: [PATCH] Automatically assign temporary ics_id if not given This allows to easily register items via the API without giving an ICS id. --- app/api/inventory.py | 11 ++++++----- app/helpers.py | 27 --------------------------- app/models.py | 28 +++++++++++++++++++++++++++- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/app/api/inventory.py b/app/api/inventory.py index af34fa3..f9cf9a1 100644 --- a/app/api/inventory.py +++ b/app/api/inventory.py @@ -9,7 +9,7 @@ This module implements the inventory API. :license: BSD 2-Clause, see LICENSE for more details. """ -from flask import Blueprint, jsonify, request +from flask import Blueprint, jsonify, request, current_app from flask_jwt_extended import jwt_required from .. import utils, models from ..decorators import jwt_groups_accepted @@ -59,6 +59,7 @@ def create_item(): # an item so ics_id should also be a mandatory field. # But there are existing items (in confluence and JIRA) that we want to # import and associate after they have been created. + # In that case a temporary id is automatically assigned. return create_generic_model(models.Item, mandatory_fields=('serial_number',)) @@ -70,7 +71,7 @@ def patch_item(id_): id_ can be the primary key or the ics_id field Fields allowed to update are: - - ics_id ONLY if currently null (422 returned otherwise) + - ics_id ONLY if current is temporary (422 returned otherwise) - manufacturer - model - location @@ -89,9 +90,9 @@ def patch_item(id_): 'location', 'status', 'parent'): raise utils.CSEntryError(f"Invalid field '{key}'", status_code=422) item = get_item_by_id_or_ics_id(id_) - # Only allow to set ICS id if it's null - if item.ics_id is None: - item.ics_id = data.get('ics_id') + # Only allow to set ICS id if the current id is a temporary one + if item.ics_id.startswith(current_app.config['TEMPORARY_ICS_ID']): + item.ics_id = data.get('ics_id', item.ics_id) elif 'ics_id' in data: raise utils.CSEntryError("'ics_id' can't be changed", status_code=422) item.manufacturer = utils.convert_to_model(data.get('manufacturer', item.manufacturer), models.Manufacturer) diff --git a/app/helpers.py b/app/helpers.py index 3c4b20c..92db026 100644 --- a/app/helpers.py +++ b/app/helpers.py @@ -9,10 +9,7 @@ This module implements helpers functions for the models. :license: BSD 2-Clause, see LICENSE for more details. """ -import string -from flask import current_app from flask_wtf import FlaskForm -from . import models class CSEntryForm(FlaskForm): @@ -28,27 +25,3 @@ class CSEntryForm(FlaskForm): super().__init__(obj=obj, **kwargs) else: super().__init__(formdata=formdata, obj=obj, **kwargs) - - -def temporary_ics_ids(): - """Generator that returns the full list of temporary ICS ids""" - return (f'{current_app.config["TEMPORARY_ICS_ID"]}{letter}{number:0=3d}' - for letter in string.ascii_uppercase - for number in range(0, 1000)) - - -def used_temporary_ics_ids(): - """Generator that returns the list of temporary ICS ids used""" - temporary_items = models.Item.query.filter( - models.Item.ics_id.startswith( - current_app.config['TEMPORARY_ICS_ID'])).all() - return (item.ics_id for item in temporary_items) - - -def get_temporary_ics_id(): - """Return a temporary ICS id that is available""" - for ics_id in temporary_ics_ids(): - if ics_id not in used_temporary_ics_ids(): - return ics_id - else: - raise ValueError('No temporary ICS id available') diff --git a/app/models.py b/app/models.py index 39dc16f..023c8b7 100644 --- a/app/models.py +++ b/app/models.py @@ -10,6 +10,7 @@ This module implements the models used in the app. """ import ipaddress +import string import qrcode import sqlalchemy as sa from sqlalchemy.ext.declarative import declared_attr @@ -40,6 +41,30 @@ def pg_utcnow(element, compiler, **kw): return "TIMEZONE('utc', CURRENT_TIMESTAMP)" +def temporary_ics_ids(): + """Generator that returns the full list of temporary ICS ids""" + return (f'{current_app.config["TEMPORARY_ICS_ID"]}{letter}{number:0=3d}' + for letter in string.ascii_uppercase + for number in range(0, 1000)) + + +def used_temporary_ics_ids(): + """Generator that returns the list of temporary ICS ids used""" + temporary_items = Item.query.filter( + Item.ics_id.startswith( + current_app.config['TEMPORARY_ICS_ID'])).all() + return (item.ics_id for item in temporary_items) + + +def get_temporary_ics_id(): + """Return a temporary ICS id that is available""" + for ics_id in temporary_ics_ids(): + if ics_id not in used_temporary_ics_ids(): + return ics_id + else: + raise ValueError('No temporary ICS id available') + + @login_manager.user_loader def load_user(user_id): """User loader callback for flask-login @@ -247,7 +272,8 @@ class Item(CreatedMixin, db.Model): # WARNING! Inheriting id from CreatedMixin doesn't play well with # SQLAlchemy-Continuum. It has to be defined here. id = db.Column(db.Integer, primary_key=True) - ics_id = db.Column(db.Text, unique=True, nullable=False, index=True) + ics_id = db.Column(db.Text, unique=True, nullable=False, + index=True, default=get_temporary_ics_id) serial_number = db.Column(db.Text, nullable=False) manufacturer_id = db.Column(db.Integer, db.ForeignKey('manufacturer.id')) model_id = db.Column(db.Integer, db.ForeignKey('model.id')) -- GitLab