Skip to content
Snippets Groups Projects
Commit fe73933d authored by Benjamin Bertrand's avatar Benjamin Bertrand
Browse files

Replace hash with specific ICS Id (3 letters + 3 digits)

The serial number might not be unique and can't be used
as the string to hash.
There are for example SD cards that all have the same SN.

Using an ICS id allows to pre-print labels that can be assigned to
hardware when scanning serial numbers.
This id shall be easy to remember (unlike UUID).
parent ccf0447d
No related branches found
No related tags found
No related merge requests found
......@@ -9,10 +9,10 @@ This module customizes the admin views.
:license: BSD 2-Clause, see LICENSE for more details.
"""
from wtforms import validators
from flask_admin.contrib import sqla
from flask_login import current_user
from ..models import Item, User, Group
from .. import utils
from ..models import Item, User, Group, ICS_ID_RE
class AdminModelView(sqla.ModelView):
......@@ -41,12 +41,12 @@ class UserAdmin(AdminModelView):
class ItemAdmin(AdminModelView):
form_args = {
'ics_id': {
'label': 'ICS id',
'validators': [validators.Regexp(ICS_ID_RE, message='ICS id shall match [A-Z]{3}[0-9]{3}')]
}
}
def __init__(self, session):
super().__init__(Item, session)
def on_model_change(self, form, model, is_created):
"""Update the hash"""
# The hash is supposed to be computed in the __init__ method
# of the Item class but flask-admin doesn't pass any parameter
# when creating a class - we do it here instead
model.hash = utils.compute_hash(model.serial_number)
......@@ -9,10 +9,9 @@ This module implements the models used in the app.
:license: BSD 2-Clause, see LICENSE for more details.
"""
import uuid
import re
import qrcode
from sqlalchemy.types import TypeDecorator, CHAR
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import validates
from sqlalchemy.ext.associationproxy import association_proxy
from citext import CIText
from flask import current_app
......@@ -21,39 +20,7 @@ from .extensions import db, login_manager, ldap_manager, jwt
from . import utils
class GUID(TypeDecorator):
"""Platform-independent GUID type.
Uses Postgresql's UUID type, otherwise uses
CHAR(32), storing as stringified hex values.
From http://docs.sqlalchemy.org/en/rel_0_9/core/custom_types.html?highlight=guid#backend-agnostic-guid-type
"""
impl = CHAR
def load_dialect_impl(self, dialect):
if dialect.name == 'postgresql':
return dialect.type_descriptor(UUID())
else:
return dialect.type_descriptor(CHAR(32))
def process_bind_param(self, value, dialect):
if value is None:
return value
elif dialect.name == 'postgresql':
return str(value)
else:
if not isinstance(value, uuid.UUID):
return "%.32x" % uuid.UUID(value).int
else:
# hexstring
return "%.32x" % value.int
def process_result_value(self, value, dialect):
if value is None:
return value
else:
return uuid.UUID(value)
ICS_ID_RE = re.compile('[A-Z]{3}[0-9]{3}')
@login_manager.user_loader
......@@ -219,8 +186,8 @@ class Item(db.Model):
id = db.Column(db.Integer, primary_key=True)
_created = db.Column(db.DateTime, default=db.func.now())
_updated = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now())
ics_id = db.Column(db.String(6), unique=True, index=True)
serial_number = db.Column(db.String(100), nullable=False)
hash = db.Column(GUID, unique=True)
manufacturer_id = db.Column(db.Integer, db.ForeignKey('manufacturer.id'))
model_id = db.Column(db.Integer, db.ForeignKey('model.id'))
location_id = db.Column(db.Integer, db.ForeignKey('location.id'))
......@@ -233,23 +200,30 @@ class Item(db.Model):
status = db.relationship('Status', back_populates='items')
children = db.relationship('Item', backref=db.backref('parent', remote_side=[id]))
def __init__(self, serial_number=None, manufacturer=None, model=None, location=None, status=None):
def __init__(self, ics_id=None, serial_number=None, manufacturer=None, model=None, location=None, status=None):
# All arguments must be optional for this class to work with flask-admin!
self.ics_id = ics_id
self.serial_number = serial_number
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
return str(self.ics_id)
@validates('ics_id')
def validate_ics_id(self, key, string):
"""Ensure the ICS id field matches the required format"""
if string is not None:
assert ICS_ID_RE.fullmatch(string) is not None
return string
def to_dict(self):
return {
'id': self.id,
'ics_id': self.ics_id,
'serial_number': self.serial_number,
'hash': self.hash,
'manufacturer': utils.format_field(self.manufacturer),
'model': utils.format_field(self.model),
'location': utils.format_field(self.location),
......
......@@ -12,8 +12,6 @@ This module implements utility functions.
import base64
import datetime
import io
import hashlib
import uuid
class InventoryError(Exception):
......@@ -39,13 +37,6 @@ class InventoryError(Exception):
return str(self.to_dict())
def compute_hash(string):
if string is None:
return None
md5 = hashlib.md5(string.encode()).hexdigest()
return uuid.UUID(md5)
def image_to_base64(img, format='PNG'):
"""Convert a Pillow image to a base64 string
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment