diff --git a/app/inventory/forms.py b/app/inventory/forms.py index be25524f4504ea1b4dd654ecf7e2df4087d41350..f3d7bde3a99c4178f2431c031471ac5db8d13a18 100644 --- a/app/inventory/forms.py +++ b/app/inventory/forms.py @@ -51,7 +51,7 @@ class ItemForm(CSEntryForm): parent_id = SelectField("Parent", coerce=utils.coerce_to_str_or_none) host_id = SelectField("Host", coerce=utils.coerce_to_str_or_none) stack_member = NoValidateSelectField( - "Stack member", coerce=utils.coerce_to_str_or_none, choices=[] + "Stack member", coerce=utils.coerce_to_int_or_none, choices=[] ) mac_addresses = StringField( "MAC addresses", diff --git a/app/models.py b/app/models.py index dd0413e862cfa98cfdfd8b580ad5f7661dc5674f..2cd79652f1e167f81cc171b05a0c529e74f7287c 100644 --- a/app/models.py +++ b/app/models.py @@ -689,7 +689,7 @@ class Item(CreatedMixin, SearchableMixin, db.Model): "children": {"type": "text", "fields": {"keyword": {"type": "keyword"}}}, "macs": {"type": "text", "fields": {"keyword": {"type": "keyword"}}}, "host": {"type": "text", "fields": {"keyword": {"type": "keyword"}}}, - "stack_member": {"type": "text", "fields": {"keyword": {"type": "keyword"}}}, + "stack_member": {"type": "byte"}, "history": {"enabled": False}, "comments": {"type": "text"}, } @@ -1293,7 +1293,7 @@ class Host(CreatedMixin, SearchableMixin, db.Model): # This function replaces None by Inf so it is set at the end of the list # items are sorted by stack_member and then ics_id def none_to_inf(nb): - return float("inf") if nb is None else nb + return float("inf") if nb is None else int(nb) d = super().to_dict() d.update( diff --git a/app/utils.py b/app/utils.py index b053226d0bf42317b1b44def33fbfc9530ecf185..cf52ea12f579f23595e4315fa1be67704ffa8e65 100644 --- a/app/utils.py +++ b/app/utils.py @@ -192,7 +192,7 @@ def lowercase_field(value): return value -# coerce function to use with SelectField that can accept a None value +# coerce functions to use with SelectField that can accept a None value # wtforms always coerce to string by default # Values returned from the form are usually strings but if a field is disabled # None is returned @@ -204,6 +204,14 @@ def coerce_to_str_or_none(value): return str(value) +def coerce_to_int_or_none(value): + """Return None if the value is not an integer""" + try: + return int(value) + except ValueError: + return None + + def parse_to_utc(string): """Convert a string to a datetime object with no timezone""" d = dateutil.parser.parse(string) diff --git a/tests/functional/test_web.py b/tests/functional/test_web.py index 8827f2d0a086ecf67c063bb0e07f3e2201ed8513..8a7fd562c0f3fcb6f3c36d3dbd8934611c68df08 100644 --- a/tests/functional/test_web.py +++ b/tests/functional/test_web.py @@ -709,3 +709,42 @@ def test_create_item_invalid_ics_id(logged_rw_client): assert response.status_code == 200 assert b"Register new item" in response.data assert b"The ICS id shall be composed of 3 letters and 3 digits" in response.data + + +def test_create_item_with_stack_member( + host_factory, device_type_factory, item_factory, logged_rw_client +): + # Test for JIRA INFRA-1648 + network_type = device_type_factory(name="NETWORK") + host = host_factory(device_type=network_type) + item1 = item_factory(ics_id="AAA001", host=host, stack_member=0) + ics_id = "AAA042" + form = { + "ics_id": ics_id, + "serial_number": "12345", + "host_id": host.id, + "stack_member": 1, + } + response = logged_rw_client.post(f"/inventory/items/create", data=form) + assert response.status_code == 302 + item2 = models.Item.query.filter_by(ics_id=ics_id).first() + assert host.stack_members() == [item1, item2] + + +def test_create_item_with_host_and_no_stack_member( + host_factory, device_type_factory, item_factory, logged_rw_client +): + network_type = device_type_factory(name="NETWORK") + host = host_factory(device_type=network_type) + ics_id = "AAA042" + form = { + "ics_id": ics_id, + "serial_number": "12345", + "host_id": host.id, + "stack_member": "", + } + response = logged_rw_client.post(f"/inventory/items/create", data=form) + assert response.status_code == 302 + item = models.Item.query.filter_by(ics_id=ics_id).first() + assert item.host == host + assert item.stack_member is None