Compare commits

...
This repository has been archived on 2024-05-31. You can view files and clone it, but cannot push or open issues or pull requests.

17 Commits

Author SHA1 Message Date
Cayo Puigdefabregas 6dc033281d new version 2023-08-07 09:20:27 +02:00
Cayo Puigdefabregas 5bcf3b2a63 fix reset_check_constraint_of_placeholders 2023-08-02 19:05:16 +02:00
Cayo Puigdefabregas fa55ac017d fix check_constraint in database 2023-08-02 17:16:55 +02:00
cayop e2c04e3e48
Merge pull request #462 from eReuse/bugfix/4474-limits-form-newdevice
remove restrictions in form for new placeholder
2023-08-02 12:25:29 +02:00
Cayo Puigdefabregas 6bf91d3362 remove restrictions in form for new placeholder 2023-08-02 11:39:17 +02:00
cayop 4b7bf24d86
Merge pull request #461 from eReuse/bugfix/4464-export-hdd
Bugfix/4464 export hdd
2023-08-01 17:58:03 +02:00
Cayo Puigdefabregas 3a1f135310 fix bug drop parents 2023-08-01 15:00:31 +02:00
Cayo Puigdefabregas 1941f28ab3 use get_datastorage instead of get_erasure_datawipe_mobile for hdds 2023-08-01 13:43:14 +02:00
Cayo Puigdefabregas 25fd9c3b6f add solar panel icon 2023-07-27 12:19:23 +02:00
cayop 07e506f999
Merge pull request #460 from eReuse/feature/4463-solar-panel
Feature/4463 solar panel
2023-07-26 16:35:48 +02:00
Cayo Puigdefabregas 72e4481b5d fix test 2023-07-26 13:39:24 +02:00
Cayo Puigdefabregas 9209e3cfaa Merge branch 'testing' into feature/4463-solar-panel 2023-07-26 13:16:52 +02:00
Cayo Puigdefabregas 3408af33a8 add solar panel to the interface 2023-07-26 12:58:07 +02:00
Cayo Puigdefabregas d5e93f3a52 add solar panel module and migration 2023-07-26 09:36:33 +02:00
Cayo Puigdefabregas b405f715e0 fix devices per page 2023-06-19 17:54:35 +02:00
Cayo Puigdefabregas c79cfd05fa fix appearance in snapshots device 2023-06-19 12:24:06 +02:00
Cayo Puigdefabregas ae310ece6d edit appearance and functionality in placeholder 2023-06-19 11:31:40 +02:00
17 changed files with 278 additions and 66 deletions

View File

@ -7,6 +7,12 @@ ml).
## testing ## testing
## [2.5.4] - 2023-08-7
- [added] #460 new device Solar Panel
- [fixed] #459 Deploy without Dummy
- [fixed] #461 Fix some behavior of orphans components (DataStorage)
- [fixed] #462 Remove constraints in weight in placeholder
## [2.5.3] - 2023-05-13 ## [2.5.3] - 2023-05-13
- [added] #450 add new datawipe in csv. - [added] #450 add new datawipe in csv.
- [changed] #447 Share a lot between 2 users, one is owner the other is read only. - [changed] #447 Share a lot between 2 users, one is owner the other is read only.

View File

@ -1 +1 @@
__version__ = "2.5.3" __version__ = "2.5.4"

View File

@ -41,7 +41,7 @@ from ereuse_devicehub.inventory.models import (
from ereuse_devicehub.parser.models import PlaceholdersLog, SnapshotsLog from ereuse_devicehub.parser.models import PlaceholdersLog, SnapshotsLog
from ereuse_devicehub.parser.parser import ParseSnapshotLsHw from ereuse_devicehub.parser.parser import ParseSnapshotLsHw
from ereuse_devicehub.parser.schemas import Snapshot_lite from ereuse_devicehub.parser.schemas import Snapshot_lite
from ereuse_devicehub.resources.action.models import Snapshot, Trade from ereuse_devicehub.resources.action.models import Snapshot, Trade, VisualTest
from ereuse_devicehub.resources.action.schemas import Snapshot as SnapshotSchema from ereuse_devicehub.resources.action.schemas import Snapshot as SnapshotSchema
from ereuse_devicehub.resources.action.views.snapshot import ( from ereuse_devicehub.resources.action.views.snapshot import (
SnapshotMixin, SnapshotMixin,
@ -68,6 +68,7 @@ from ereuse_devicehub.resources.device.models import (
Projector, Projector,
Server, Server,
Smartphone, Smartphone,
SolarPanel,
SolidStateDrive, SolidStateDrive,
Tablet, Tablet,
TelevisionSet, TelevisionSet,
@ -113,7 +114,7 @@ DEVICES = {
"SAI", "SAI",
"Keyboard", "Keyboard",
], ],
"Other Devices": ["Other"], "Other Devices": ["SolarPanel", "Other"],
} }
TYPES_DOCUMENTS = [ TYPES_DOCUMENTS = [
@ -131,7 +132,7 @@ MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"]
MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"] MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"]
STORAGE = ["HardDrive", "SolidStateDrive"] STORAGE = ["HardDrive", "SolidStateDrive"]
ACCESSORIES = ["Mouse", "MemoryCardReader", "SAI", "Keyboard"] ACCESSORIES = ["Mouse", "MemoryCardReader", "SAI", "Keyboard"]
OTHERS = ["Other"] OTHERS = ["Other", "SolarPanel"]
DATASTORAGE = ['HardDrive', 'SolidStateDrive'] DATASTORAGE = ['HardDrive', 'SolidStateDrive']
@ -433,6 +434,7 @@ class NewDeviceForm(FlaskForm):
"Keyboard": Keyboard, "Keyboard": Keyboard,
"SAI": SAI, "SAI": SAI,
"MemoryCardReader": MemoryCardReader, "MemoryCardReader": MemoryCardReader,
"SolarPanel": SolarPanel,
"Other": Other, "Other": Other,
} }
@ -480,6 +482,10 @@ class NewDeviceForm(FlaskForm):
if self._obj.type in ['HardDrive', 'SolidStateDrive']: if self._obj.type in ['HardDrive', 'SolidStateDrive']:
if self._obj.size: if self._obj.size:
self.data_storage_size.data = self._obj.size / 1000 self.data_storage_size.data = self._obj.size / 1000
if self._obj.appearance():
self.appearance.data = self._obj.appearance().name
if self._obj.functionality():
self.functionality.data = self._obj.functionality().name
if self._obj.placeholder.is_abstract: if self._obj.placeholder.is_abstract:
self.type.render_kw = disabled self.type.render_kw = disabled
@ -515,23 +521,23 @@ class NewDeviceForm(FlaskForm):
error = ["Not a correct value"] error = ["Not a correct value"]
is_valid = super().validate(extra_validators) is_valid = super().validate(extra_validators)
if self.weight.data and not (0.1 <= self.weight.data <= 5): if self.weight.data and not (0.1 <= self.weight.data) or self.weight.data == 0:
txt = ["Supported values between 0.1 and 5"] txt = ["Supported values greater than 0.1"]
self.weight.errors = txt self.weight.errors = txt
is_valid = False is_valid = False
if self.height.data and not (0.1 <= self.height.data <= 5): if self.height.data and not (0.1 <= self.height.data) or self.height.data == 0:
txt = ["Supported values between 0.1 and 5"] txt = ["Supported values greater than 0.1"]
self.height.errors = txt self.height.errors = txt
is_valid = False is_valid = False
if self.width.data and not (0.1 <= self.width.data <= 5): if self.width.data and not (0.1 <= self.width.data) or self.width.data == 0:
txt = ["Supported values between 0.1 and 5"] txt = ["Supported values greater than 0.1"]
self.width.errors = txt self.width.errors = txt
is_valid = False is_valid = False
if self.depth.data and not (0.1 <= self.depth.data <= 5): if self.depth.data and not (0.1 <= self.depth.data) or self.depth.data == 0:
txt = ["Supported values between 0.1 and 5"] txt = ["Supported values greater than 0.1"]
self.depth.errors = txt self.depth.errors = txt
is_valid = False is_valid = False
@ -698,17 +704,7 @@ class NewDeviceForm(FlaskForm):
if self.data_storage_size.data: if self.data_storage_size.data:
self._obj.size = self.data_storage_size.data * 1000 self._obj.size = self.data_storage_size.data * 1000
if ( self.edit_visual_test(self._obj)
self.appearance.data
and self.appearance.data != self._obj.appearance().name
):
self._obj.set_appearance(self.appearance.data)
if (
self.functionality.data
and self.functionality.data != self._obj.functionality().name
):
self._obj.set_functionality(self.functionality.data)
else: else:
self._obj.placeholder.id_device_supplier = ( self._obj.placeholder.id_device_supplier = (
@ -718,11 +714,33 @@ class NewDeviceForm(FlaskForm):
self.id_device_internal.data or None self.id_device_internal.data or None
) )
self._obj.placeholder.pallet = self.pallet.data or None self._obj.placeholder.pallet = self.pallet.data or None
pl_dev = self._obj.placeholder.device
self.edit_visual_test(pl_dev)
placeholder_log = PlaceholdersLog( placeholder_log = PlaceholdersLog(
type="Update", source='Web form', placeholder=self._obj.placeholder type="Update", source='Web form', placeholder=self._obj.placeholder
) )
db.session.add(placeholder_log) db.session.add(placeholder_log)
def edit_visual_test(self, dev):
if not dev.appearance() or not dev.functionality():
visual_test = VisualTest(
appearance_range=self.appearance.data,
functionality_range=self.functionality.data,
device=dev,
)
db.session.add(visual_test)
else:
if self.appearance.data and self.appearance.data != dev.appearance().name:
dev.set_appearance(self.appearance.data)
if (
self.functionality.data
and self.functionality.data != dev.functionality().name
):
dev.set_functionality(self.functionality.data)
class TagDeviceForm(FlaskForm): class TagDeviceForm(FlaskForm):
tag = SelectField( tag = SelectField(
@ -1211,7 +1229,6 @@ class TradeForm(ActionFormMixin):
or email_to == email_from or email_to == email_from
or g.user.email not in [email_from, email_to] or g.user.email not in [email_from, email_to]
): ):
errors = ["If you want confirm, you need a correct email"] errors = ["If you want confirm, you need a correct email"]
self.user_to.errors = errors self.user_to.errors = errors
self.user_from.errors = errors self.user_from.errors = errors
@ -1917,7 +1934,6 @@ class UploadPlaceholderForm(FlaskForm):
return True return True
def save(self, commit=True): def save(self, commit=True):
for device, placeholder_log in self.placeholders: for device, placeholder_log in self.placeholders:
db.session.add(device) db.session.add(device)
db.session.add(placeholder_log) db.session.add(placeholder_log)
@ -1946,7 +1962,6 @@ class EditPlaceholderForm(FlaskForm):
return True return True
def save(self, commit=True): def save(self, commit=True):
for device in self.placeholders: for device in self.placeholders:
db.session.add(device) db.session.add(device)

View File

@ -5,7 +5,7 @@ Revises: ${down_revision | comma,n}
Create Date: ${create_date} Create Date: ${create_date}
""" """
from alembic import op from alembic import op, context
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy_utils import sqlalchemy_utils
import citext import citext

View File

@ -0,0 +1,76 @@
"""reset check_constraint of placeholders
Revision ID: 57e6201f280c
Revises: 8ccba3cb37c2
Create Date: 2023-08-02 15:56:12.484340
"""
import sqlalchemy as sa
from alembic import context, op
# revision identifiers, used by Alembic.
revision = '57e6201f280c'
down_revision = '8ccba3cb37c2'
branch_labels = None
depends_on = None
def get_inv():
INV = context.get_x_argument(as_dictionary=True).get('inventory')
if not INV:
raise ValueError("Inventory value is not specified")
return INV
def upgrade():
sql = "select constraint_name from information_schema.table_constraints "
sql += "where table_name='device' and constraint_type='CHECK';"
con = op.get_bind()
constraints = []
for c in con.execute(sql):
constraints.append(c.constraint_name)
if 'device_depth_check' in constraints:
op.drop_constraint(
'device_depth_check', "device", type_="check", schema=f'{get_inv()}'
)
if 'device_height_check' in constraints:
op.drop_constraint(
'device_height_check', "device", type_="check", schema=f'{get_inv()}'
)
if 'device_width_check' in constraints:
op.drop_constraint(
'device_width_check', "device", type_="check", schema=f'{get_inv()}'
)
if 'device_weight_check' in constraints:
op.drop_constraint(
'device_weight_check', "device", type_="check", schema=f'{get_inv()}'
)
op.create_check_constraint(
"device_depth_check",
"device",
sa.Column("depth") >= (0.1),
schema=f'{get_inv()}',
)
op.create_check_constraint(
"device_height_check",
"device",
sa.Column("depth") >= (0.1),
schema=f'{get_inv()}',
)
op.create_check_constraint(
"device_width_check",
"device",
sa.Column("depth") >= (0.1),
schema=f'{get_inv()}',
)
op.create_check_constraint(
"device_weight_check",
"device",
sa.Column("depth") >= (0.1),
schema=f'{get_inv()}',
)
def downgrade():
pass

View File

@ -0,0 +1,41 @@
"""add solar panel
Revision ID: 8ccba3cb37c2
Revises: 5169765e2653
Create Date: 2023-07-26 09:23:21.326465
"""
import sqlalchemy as sa
from alembic import context, op
# revision identifiers, used by Alembic.
revision = '8ccba3cb37c2'
down_revision = '5169765e2653'
branch_labels = None
depends_on = None
def get_inv():
INV = context.get_x_argument(as_dictionary=True).get('inventory')
if not INV:
raise ValueError("Inventory value is not specified")
return INV
def upgrade():
# creating Solar panel device.
op.create_table(
'solar_panel',
sa.Column('id', sa.BigInteger(), nullable=False),
sa.ForeignKeyConstraint(
['id'],
[f'{get_inv()}.device.id'],
),
sa.PrimaryKeyConstraint('id'),
schema=f'{get_inv()}',
)
def downgrade():
op.drop_table('solar_panel', schema=f'{get_inv()}')

View File

@ -726,3 +726,34 @@ class OtherDef(DeviceDef):
root_path, root_path,
cli_commands, cli_commands,
) )
class SolarPanelDef(DeviceDef):
VIEW = None
SCHEMA = schemas.SolarPanel
def __init__(
self,
app,
import_name=__name__,
static_folder=None,
static_url_path=None,
template_folder=None,
url_prefix=None,
subdomain=None,
url_defaults=None,
root_path=None,
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple(),
):
super().__init__(
app,
import_name,
static_folder,
static_url_path,
template_folder,
url_prefix,
subdomain,
url_defaults,
root_path,
cli_commands,
)

View File

@ -150,13 +150,13 @@ class Device(Thing):
generation.comment = """The generation of the device.""" generation.comment = """The generation of the device."""
version = db.Column(db.CIText()) version = db.Column(db.CIText())
version.comment = """The version code of this device, like v1 or A001.""" version.comment = """The version code of this device, like v1 or A001."""
weight = Column(Float(decimal_return_scale=4), check_range('weight', 0.1, 5)) weight = Column(Float(decimal_return_scale=4))
weight.comment = """The weight of the device in Kg.""" weight.comment = """The weight of the device in Kg."""
width = Column(Float(decimal_return_scale=4), check_range('width', 0.1, 5)) width = Column(Float(decimal_return_scale=4))
width.comment = """The width of the device in meters.""" width.comment = """The width of the device in meters."""
height = Column(Float(decimal_return_scale=4), check_range('height', 0.1, 5)) height = Column(Float(decimal_return_scale=4))
height.comment = """The height of the device in meters.""" height.comment = """The height of the device in meters."""
depth = Column(Float(decimal_return_scale=4), check_range('depth', 0.1, 5)) depth = Column(Float(decimal_return_scale=4))
depth.comment = """The depth of the device in meters.""" depth.comment = """The depth of the device in meters."""
color = Column(ColorType) color = Column(ColorType)
color.comment = """The predominant color of the device.""" color.comment = """The predominant color of the device."""
@ -476,7 +476,8 @@ class Device(Thing):
"""The trading state, or None if no Trade action has """The trading state, or None if no Trade action has
ever been performed to this device. This extract the posibilities for to do. ever been performed to this device. This extract the posibilities for to do.
This method is performed for show in the web. This method is performed for show in the web.
If you need to do one simple and generic response you can put simple=True for that.""" If you need to do one simple and generic response you can put simple=True for that.
"""
if not hasattr(lot, 'trade'): if not hasattr(lot, 'trade'):
return return
@ -928,6 +929,7 @@ class Device(Thing):
"Cellphone": "bi bi-telephone", "Cellphone": "bi bi-telephone",
"HardDrive": "bi bi-hdd-stack", "HardDrive": "bi bi-hdd-stack",
"SolidStateDrive": "bi bi-hdd", "SolidStateDrive": "bi bi-hdd",
"SolarPanel": "bi-solar-panel",
} }
return types.get(self.type, '') return types.get(self.type, '')
@ -1986,3 +1988,11 @@ class Other(Device):
""" """
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True) id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
class SolarPanel(Device):
"""
Used solar panels devices.
"""
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)

View File

@ -67,18 +67,10 @@ class Device(Thing):
validate=Range(1, 100), description=m.Device.generation.comment validate=Range(1, 100), description=m.Device.generation.comment
) )
version = SanitizedStr(description=m.Device.version) version = SanitizedStr(description=m.Device.version)
weight = Float( weight = Float(unit=UnitCodes.kgm, description=m.Device.weight.comment)
validate=Range(0.1, 5), unit=UnitCodes.kgm, description=m.Device.weight.comment width = Float(unit=UnitCodes.m, description=m.Device.width.comment)
) height = Float(unit=UnitCodes.m, description=m.Device.height.comment)
width = Float( depth = Float(unit=UnitCodes.m, description=m.Device.depth.comment)
validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.width.comment
)
height = Float(
validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.height.comment
)
depth = Float(
validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.depth.comment
)
# TODO TimeOut 2. Comment actions and lots if there are time out. # TODO TimeOut 2. Comment actions and lots if there are time out.
actions = NestedOn( actions = NestedOn(
'Action', many=True, dump_only=True, description=m.Device.actions.__doc__ 'Action', many=True, dump_only=True, description=m.Device.actions.__doc__
@ -590,5 +582,9 @@ class Racket(Recreation):
pass pass
class SolarPanel(Device):
pass
class Other(Device): class Other(Device):
pass pass

View File

@ -92,10 +92,21 @@ class Sync:
# We only want to perform Add/Remove to not new components # We only want to perform Add/Remove to not new components
actions = self.add_remove(db_device, not_new_components) actions = self.add_remove(db_device, not_new_components)
db_device.components = db_components db_device.components = db_components
self.clean_parent_orphans_components(db_device)
self.create_placeholder(db_device) self.create_placeholder(db_device)
return db_device, actions return db_device, actions
def clean_parent_orphans_components(self, device):
all_components = Component.query.filter_by(parent_id=device.id)
for _c in all_components:
if _c not in device.components:
_c.parent = None
if _c.binding:
_c.binding.device.parent = None
if _c.placeholder and _c.placeholder.binding:
_c.placeholder.binding.parent = None
def execute_register_component(self, component: Component): def execute_register_component(self, component: Component):
"""Synchronizes one component to the DB. """Synchronizes one component to the DB.

View File

@ -176,7 +176,7 @@ class DeviceView(View):
"""Gets many devices.""" """Gets many devices."""
# Compute query # Compute query
query = self.query(args) query = self.query(args)
devices = query.paginate(page=args['page'], per_page=30) # type: Pagination devices = query.paginate(page=args['page'], per_page=100) # type: Pagination
return things_response( return things_response(
self.schema.dump(devices.items, many=True, nested=1), self.schema.dump(devices.items, many=True, nested=1),
devices.page, devices.page,

View File

@ -422,30 +422,37 @@ class DeviceRow(BaseDeviceRow):
self['{} {} Speed (MHz)'.format(ctype, i)] = none2str(component.speed) self['{} {} Speed (MHz)'.format(ctype, i)] = none2str(component.speed)
def get_erasure_datawipe_mobile(self, device): def get_erasure_datawipe_mobile(self, device):
if isinstance(device, d.DataStorage):
if device.placeholder and device.placeholder.binding:
binding = device.placeholder.binding
return self.get_datastorage('DataStorage', 1, binding)
return self.get_datastorage('DataStorage', 1, device)
if not isinstance(device, d.Mobile):
return
actions = sorted(device.actions) actions = sorted(device.actions)
erasures = [a for a in actions if a.type == 'EraseDataWipe'] erasures = [a for a in actions if a.type == 'EraseDataWipe']
erasure = erasures[-1] if erasures else None erasure = erasures[-1] if erasures else None
if erasure:
self['Erasure DataStorage 1'] = none2str(device.chid)
if isinstance(device, d.Mobile):
serial_number = none2str(device.imei)
size = device.data_storage_size
size = size * 1000 if size else 0
storage_size = none2str(size)
if isinstance(device, d.DataStorage): if not erasure:
serial_number = none2str(device.serial_number) return
storage_size = none2str(device.size)
self['Erasure DataStorage 1 Serial Number'] = serial_number self['Erasure DataStorage 1'] = none2str(device.chid)
self['Erasure DataStorage 1 Size (MB)'] = storage_size serial_number = none2str(device.imei)
self['Erasure DataStorage 1 Software'] = erasure.document.software size = device.data_storage_size
self['Erasure DataStorage 1 Result'] = get_result(erasure) size = size * 1000 if size else 0
self['Erasure DataStorage 1 Type'] = erasure.type storage_size = none2str(size)
self['Erasure DataStorage 1 Date'] = format(erasure.document.date or '')
self['Erasure DataStorage 1 Certificate URL'] = ( self['Erasure DataStorage 1 Serial Number'] = serial_number
erasure.document.url and erasure.document.url.to_text() or '' self['Erasure DataStorage 1 Size (MB)'] = storage_size
) self['Erasure DataStorage 1 Result'] = get_result(erasure)
self['Erasure DataStorage 1 Type'] = erasure.type
self['Erasure DataStorage 1 Software'] = erasure.document.software
self['Erasure DataStorage 1 Date'] = format(erasure.document.date or '')
self['Erasure DataStorage 1 Certificate URL'] = (
erasure.document.url and erasure.document.url.to_text() or ''
)
def get_datastorage(self, ctype, i, component): def get_datastorage(self, ctype, i, component):
"""Particular fields for component DataStorage. """Particular fields for component DataStorage.

View File

@ -43,3 +43,14 @@
.printLabelForm { .printLabelForm {
margin-left: 10px; margin-left: 10px;
} }
.bi-solar-panel {
background-image: url("/static/img/solar-panel.png");
background-repeat:no-repeat;
background-size: cover;
padding-left: 10px;
padding-right: 10px;
margin-right: 5px;
line-height: 17px;
font-size: 15px;
vertical-align: middle;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -79,6 +79,8 @@
{% if form.type.data == 'Keyboard' %} selected="selected"{% endif %}>Keyboard</option> {% if form.type.data == 'Keyboard' %} selected="selected"{% endif %}>Keyboard</option>
</optgroup> </optgroup>
<optgroup label="Other Type of Device"> <optgroup label="Other Type of Device">
<option value="SolarPanel"
{% if form.type.data == 'SolarPanel' %} selected="selected"{% endif %}>Solar Panel</option>
<option value="Other" <option value="Other"
{% if form.type.data == 'Other' %} selected="selected"{% endif %}>Other</option> {% if form.type.data == 'Other' %} selected="selected"{% endif %}>Other</option>
</optgroup> </optgroup>

View File

@ -72,6 +72,12 @@
<option value="Keyboard" <option value="Keyboard"
{% if form.type.data == 'Keyboard' %} selected="selected"{% endif %}>Keyboard</option> {% if form.type.data == 'Keyboard' %} selected="selected"{% endif %}>Keyboard</option>
</optgroup> </optgroup>
<optgroup label="Other Type of Device">
<option value="SolarPanel"
{% if form.type.data == 'SolarPanel' %} selected="selected"{% endif %}>Solar Panel</option>
<option value="Other"
{% if form.type.data == 'Other' %} selected="selected"{% endif %}>Other</option>
</optgroup>
</select> </select>
<small class="text-muted form-text">Type of devices</small> <small class="text-muted form-text">Type of devices</small>
{% if form.type.errors %} {% if form.type.errors %}

View File

@ -125,4 +125,4 @@ def test_api_docs(client: Client):
'scheme': 'basic', 'scheme': 'basic',
'name': 'Authorization', 'name': 'Authorization',
} }
assert len(docs['definitions']) == 135 assert len(docs['definitions']) == 136