add manual binding
This commit is contained in:
parent
d008bb2921
commit
497e29a2da
|
@ -455,7 +455,7 @@ class NewDeviceForm(FlaskForm):
|
|||
|
||||
if self.phid.data and self.amount.data == 1 and not self._obj:
|
||||
dev = Placeholder.query.filter(
|
||||
Placeholder.phid == self.phid.data, Device.owner == g.user
|
||||
Placeholder.phid == self.phid.data, Placeholder.owner == g.user
|
||||
).first()
|
||||
if dev:
|
||||
msg = "Sorry, exist one snapshot device with this HID"
|
||||
|
@ -568,6 +568,7 @@ class NewDeviceForm(FlaskForm):
|
|||
'id_device_supplier': self.id_device_supplier.data,
|
||||
'info': self.info.data,
|
||||
'pallet': self.pallet.data,
|
||||
'is_abstract': False,
|
||||
}
|
||||
)
|
||||
return self.placeholder
|
||||
|
@ -577,6 +578,7 @@ class NewDeviceForm(FlaskForm):
|
|||
self._obj.placeholder.id_device_supplier = self.id_device_supplier.data or None
|
||||
self._obj.placeholder.info = self.info.data or None
|
||||
self._obj.placeholder.pallet = self.pallet.data or None
|
||||
self._obj.placeholder.is_abstract = False
|
||||
self._obj.model = self.model.data
|
||||
self._obj.manufacturer = self.manufacturer.data
|
||||
self._obj.serial_number = self.serial_number.data
|
||||
|
@ -1555,6 +1557,7 @@ class UploadPlaceholderForm(FlaskForm):
|
|||
'id_device_supplier': data['Id device Supplier'][i],
|
||||
'pallet': data['Pallet'][i],
|
||||
'info': data['Info'][i],
|
||||
'is_abstract': False,
|
||||
}
|
||||
|
||||
snapshot_json = schema.load(json_snapshot)
|
||||
|
@ -1606,3 +1609,42 @@ class EditPlaceholderForm(FlaskForm):
|
|||
db.session.commit()
|
||||
|
||||
return self.placeholders
|
||||
|
||||
|
||||
class BindingForm(FlaskForm):
|
||||
phid = StringField('Phid', [validators.DataRequired()])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.device = kwargs.pop('device', None)
|
||||
self.placeholder = kwargs.pop('placeholder', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
|
||||
if not is_valid:
|
||||
txt = "This placeholder not exist."
|
||||
self.phid.errors = [txt]
|
||||
return False
|
||||
|
||||
if self.device.placeholder:
|
||||
txt = "This is not a device Workbench."
|
||||
self.phid.errors = [txt]
|
||||
return False
|
||||
|
||||
if not self.placeholder:
|
||||
self.placeholder = Placeholder.query.filter(
|
||||
Placeholder.phid == self.phid.data, Placeholder.owner == g.user
|
||||
).first()
|
||||
|
||||
if not self.placeholder:
|
||||
txt = "This placeholder not exist."
|
||||
self.phid.errors = [txt]
|
||||
return False
|
||||
|
||||
if self.placeholder.binding:
|
||||
txt = "This placeholder have a binding with other device. Before you need to do an unbinding with this other device."
|
||||
self.phid.errors = [txt]
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
@ -19,6 +19,7 @@ from ereuse_devicehub.db import db
|
|||
from ereuse_devicehub.inventory.forms import (
|
||||
AdvancedSearchForm,
|
||||
AllocateForm,
|
||||
BindingForm,
|
||||
DataWipeForm,
|
||||
EditTransferForm,
|
||||
FilterForm,
|
||||
|
@ -36,7 +37,12 @@ from ereuse_devicehub.inventory.forms import (
|
|||
from ereuse_devicehub.labels.forms import PrintLabelsForm
|
||||
from ereuse_devicehub.parser.models import PlaceholdersLog, SnapshotsLog
|
||||
from ereuse_devicehub.resources.action.models import Trade
|
||||
from ereuse_devicehub.resources.device.models import Computer, DataStorage, Device
|
||||
from ereuse_devicehub.resources.device.models import (
|
||||
Computer,
|
||||
DataStorage,
|
||||
Device,
|
||||
Placeholder,
|
||||
)
|
||||
from ereuse_devicehub.resources.documents.device_row import ActionRow, DeviceRow
|
||||
from ereuse_devicehub.resources.enums import SnapshotSoftware
|
||||
from ereuse_devicehub.resources.hash_reports import insert_hash
|
||||
|
@ -129,6 +135,7 @@ class AdvancedSearchView(DeviceListMixin):
|
|||
|
||||
|
||||
class DeviceDetailView(GenericMixin):
|
||||
methods = ['GET', 'POST']
|
||||
decorators = [login_required]
|
||||
template_name = 'inventory/device_detail.html'
|
||||
|
||||
|
@ -140,13 +147,73 @@ class DeviceDetailView(GenericMixin):
|
|||
.one()
|
||||
)
|
||||
|
||||
form_binding = BindingForm(device=device)
|
||||
|
||||
self.context.update(
|
||||
{
|
||||
'device': device,
|
||||
'placeholder': device.binding or device.placeholder,
|
||||
'page_title': 'Device {}'.format(device.devicehub_id),
|
||||
'form_binding': form_binding,
|
||||
'active_binding': False,
|
||||
}
|
||||
)
|
||||
|
||||
if form_binding.validate_on_submit():
|
||||
next_url = url_for(
|
||||
'inventory.binding',
|
||||
dhid=form_binding.device.devicehub_id,
|
||||
phid=form_binding.placeholder.phid,
|
||||
)
|
||||
return flask.redirect(next_url)
|
||||
elif form_binding.phid.data:
|
||||
self.context['active_binding'] = True
|
||||
|
||||
return flask.render_template(self.template_name, **self.context)
|
||||
|
||||
|
||||
class BindingView(GenericMixin):
|
||||
methods = ['GET', 'POST']
|
||||
decorators = [login_required]
|
||||
template_name = 'inventory/binding.html'
|
||||
|
||||
def dispatch_request(self, dhid, phid):
|
||||
self.get_context()
|
||||
device = (
|
||||
Device.query.filter(Device.owner_id == g.user.id)
|
||||
.filter(Device.devicehub_id == dhid)
|
||||
.one()
|
||||
)
|
||||
placeholder = (
|
||||
Placeholder.query.filter(Placeholder.owner_id == g.user.id)
|
||||
.filter(Placeholder.phid == phid)
|
||||
.one()
|
||||
)
|
||||
|
||||
if request.method == 'POST':
|
||||
old_placeholder = device.binding
|
||||
if old_placeholder.is_abstract:
|
||||
for plog in PlaceholdersLog.query.filter_by(
|
||||
placeholder_id=old_placeholder.id
|
||||
):
|
||||
db.session.delete(plog)
|
||||
db.session.delete(old_placeholder)
|
||||
device.binding = placeholder
|
||||
db.session.commit()
|
||||
next_url = url_for('inventory.device_details', id=dhid)
|
||||
messages.success(
|
||||
'Device "{}" bind successfully with {}!'.format(dhid, phid)
|
||||
)
|
||||
return flask.redirect(next_url)
|
||||
|
||||
self.context.update(
|
||||
{
|
||||
'device': device.binding.device,
|
||||
'placeholder': placeholder,
|
||||
'page_title': 'Binding confirm',
|
||||
}
|
||||
)
|
||||
|
||||
return flask.render_template(self.template_name, **self.context)
|
||||
|
||||
|
||||
|
@ -994,3 +1061,6 @@ devices.add_url_rule(
|
|||
devices.add_url_rule(
|
||||
'/placeholder-logs/', view_func=PlaceholderLogListView.as_view('placeholder_logs')
|
||||
)
|
||||
devices.add_url_rule(
|
||||
'/binding/<string:dhid>/<string:phid>/', view_func=BindingView.as_view('binding')
|
||||
)
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
"""add owner to placeholder
|
||||
|
||||
Revision ID: d7ea9a3b2da1
|
||||
Revises: 2b90b41a556a
|
||||
Create Date: 2022-07-27 14:40:15.513820
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import context, op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'd7ea9a3b2da1'
|
||||
down_revision = '2b90b41a556a'
|
||||
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_data():
|
||||
con = op.get_bind()
|
||||
sql = f"select {get_inv()}.placeholder.id, {get_inv()}.device.owner_id from {get_inv()}.placeholder"
|
||||
sql += f" join {get_inv()}.device on {get_inv()}.device.id={get_inv()}.placeholder.device_id;"
|
||||
|
||||
for c in con.execute(sql):
|
||||
id_placeholder = c.id
|
||||
id_owner = c.owner_id
|
||||
sql_update = f"update {get_inv()}.placeholder set owner_id='{id_owner}', is_abstract=False where id={id_placeholder};"
|
||||
con.execute(sql_update)
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column(
|
||||
'placeholder',
|
||||
sa.Column('is_abstract', sa.Boolean(), nullable=True),
|
||||
schema=f'{get_inv()}',
|
||||
)
|
||||
op.add_column(
|
||||
'placeholder',
|
||||
sa.Column('owner_id', postgresql.UUID(), nullable=True),
|
||||
schema=f'{get_inv()}',
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_placeholder_owner_id_user_id",
|
||||
"placeholder",
|
||||
"user",
|
||||
["owner_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
source_schema=f'{get_inv()}',
|
||||
referent_schema='common',
|
||||
)
|
||||
|
||||
upgrade_data()
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_constraint(
|
||||
"fk_placeholder_owner_id_user_id",
|
||||
"placeholder",
|
||||
type_="foreignkey",
|
||||
schema=f'{get_inv()}',
|
||||
)
|
||||
op.drop_column('placeholder', 'owner_id', schema=f'{get_inv()}')
|
||||
op.drop_column('placeholder', 'is_abstract', schema=f'{get_inv()}')
|
|
@ -852,6 +852,7 @@ class Placeholder(Thing):
|
|||
pallet.comment = "used for identification where from where is this placeholders"
|
||||
info = db.Column(CIText())
|
||||
info.comment = "more info of placeholders"
|
||||
is_abstract = db.Column(Boolean, default=False)
|
||||
id_device_supplier = db.Column(CIText())
|
||||
id_device_supplier.comment = (
|
||||
"Identification used for one supplier of one placeholders"
|
||||
|
@ -880,6 +881,13 @@ class Placeholder(Thing):
|
|||
primaryjoin=binding_id == Device.id,
|
||||
)
|
||||
binding_id.comment = "binding placeholder with workbench device"
|
||||
owner_id = db.Column(
|
||||
UUID(as_uuid=True),
|
||||
db.ForeignKey(User.id),
|
||||
nullable=False,
|
||||
default=lambda: g.user.id,
|
||||
)
|
||||
owner = db.relationship(User, primaryjoin=owner_id == User.id)
|
||||
|
||||
|
||||
class Computer(Device):
|
||||
|
|
|
@ -310,11 +310,15 @@ class Sync:
|
|||
c_placeholder = c.__class__(**c_dict)
|
||||
c_placeholder.parent = dev_placeholder
|
||||
c.parent = device
|
||||
component_placeholder = Placeholder(device=c_placeholder, binding=c)
|
||||
component_placeholder = Placeholder(
|
||||
device=c_placeholder, binding=c, is_abstract=True
|
||||
)
|
||||
db.session.add(c_placeholder)
|
||||
db.session.add(component_placeholder)
|
||||
|
||||
placeholder = Placeholder(device=dev_placeholder, binding=device)
|
||||
placeholder = Placeholder(
|
||||
device=dev_placeholder, binding=device, is_abstract=True
|
||||
)
|
||||
db.session.add(dev_placeholder)
|
||||
db.session.add(placeholder)
|
||||
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
{% extends "ereuse_devicehub/base_site.html" %}
|
||||
{% block main %}
|
||||
|
||||
<div class="pagetitle">
|
||||
<h1>{{ title }}</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<!-- TODO@slamora replace with lot list URL when exists -->
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section profile">
|
||||
<div class="row">
|
||||
<div class="col-xl-12">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<div class="pt-4 pb-2">
|
||||
<h5 class="card-title text-center pb-0 fs-4">{{ title }}</h5>
|
||||
<p class="text-center">Please check that the information is correct.</p>
|
||||
</div>
|
||||
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="text-center">
|
||||
<th scope="col">Basic Data</th>
|
||||
<th scope="col">Info to be Entered</th>
|
||||
<th scope="col">Info to be Decoupled</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">PHID:</th>
|
||||
<td class="table-success text-right">{{ placeholder.phid or '' }}</td>
|
||||
<td class="table-danger">{{ device.placeholder.phid or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Manufacturer:</th>
|
||||
<td class="table-success text-right">{{ placeholder.device.manufacturer or '' }}</td>
|
||||
<td class="table-danger">{{ device.manufacturer or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Model:</th>
|
||||
<td class="table-success">{{ placeholder.device.model or '' }}</td>
|
||||
<td class="table-danger">{{ device.model or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Serial Number:</th>
|
||||
<td class="table-success">{{ placeholder.device.serial_number or '' }}</td>
|
||||
<td class="table-danger">{{ device.serial_number or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Brand:</th>
|
||||
<td class="table-success">{{ placeholder.device.brand or '' }}</td>
|
||||
<td class="table-danger">{{ device.brand or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Sku:</th>
|
||||
<td class="table-success">{{ placeholder.device.sku or '' }}</td>
|
||||
<td class="table-danger">{{ device.sku or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Generation:</th>
|
||||
<td class="table-success">{{ placeholder.device.generation or '' }}</td>
|
||||
<td class="table-danger">{{ device.generation or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Version:</th>
|
||||
<td class="table-success">{{ placeholder.device.version or '' }}</td>
|
||||
<td class="table-danger">{{ device.version or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Weight:</th>
|
||||
<td class="table-success">{{ placeholder.device.weight or '' }}</td>
|
||||
<td class="table-danger">{{ device.weight or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Width:</th>
|
||||
<td class="table-success">{{ placeholder.device.width or '' }}</td>
|
||||
<td class="table-danger">{{ device.width or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Height:</th>
|
||||
<td class="table-success">{{ placeholder.device.height or '' }}</td>
|
||||
<td class="table-danger">{{ device.height or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Depth:</th>
|
||||
<td class="table-success">{{ placeholder.device.depth or '' }}</td>
|
||||
<td class="table-danger">{{ device.depth or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Color:</th>
|
||||
<td class="table-success">{{ placeholder.device.color or '' }}</td>
|
||||
<td class="table-danger">{{ device.color or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Production date:</th>
|
||||
<td class="table-success">{{ placeholder.device.production_date or '' }}</td>
|
||||
<td class="table-danger">{{ device.production_date or '' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Variant:</th>
|
||||
<td class="table-success">{{ placeholder.device.variant or '' }}</td>
|
||||
<td class="table-danger">{{ device.variant or '' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
{% if placeholder.device.components or device.components %}
|
||||
<h2>Components</h2>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="text-center">
|
||||
<th scope="col">Info to be Entered</th>
|
||||
<th scope="col">Info to be Decoupled</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="table-success text-right">
|
||||
{% for c in placeholder.device.components %}
|
||||
* {{ c.verbose_name }}<br />
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="table-danger">
|
||||
{% for c in device.components %}
|
||||
* {{ c.verbose_name }}<br />
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<br />
|
||||
|
||||
{% if placeholder.device.actions or device.actions %}
|
||||
<h2>Actions</h2>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="text-center">
|
||||
<th scope="col">Info to be Entered</th>
|
||||
<th scope="col">Info to be Decoupled</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="table-success text-right">
|
||||
{% for a in placeholder.device.actions %}
|
||||
* {{ a.t }}<br />
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="table-danger">
|
||||
{% for a in device.actions %}
|
||||
* {{ a.t }}<br />
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<form method="post">
|
||||
<a href="{{ url_for('inventory.device_details', id=device.placeholder.binding.devicehub_id) }}" class="btn btn-danger">Cancel</a>
|
||||
<button class="btn btn-primary" type="submit">Confirm</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-xl-8">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock main %}
|
|
@ -62,10 +62,16 @@
|
|||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#components">Components</button>
|
||||
</li>
|
||||
|
||||
{% if device.binding %}
|
||||
<li class="nav-item">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#binding">Binding</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
<div class="tab-content pt-2">
|
||||
|
||||
<div class="tab-pane fade show active" id="type">
|
||||
<div class="tab-pane fade {% if active_binding %}profile-overview{% else %}show active{% endif %}" id="type">
|
||||
<h5 class="card-title">Details</h5>
|
||||
{% if device.placeholder %}(<a href="{{ url_for('inventory.device_edit', id=device.devicehub_id)}}">edit</a>){% endif %}
|
||||
|
||||
|
@ -216,6 +222,42 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% if placeholder.binding %}
|
||||
<div class="tab-pane fade {% if active_binding %}show active{% else %}profile-overview{% endif %}" id="binding">
|
||||
<h5 class="card-title">Binding</h5>
|
||||
<div class="list-group col-6">
|
||||
<p>
|
||||
Be careful, binding implies changes in the data of a device that affect its
|
||||
traceability.
|
||||
</p>
|
||||
</div>
|
||||
<div class="list-group col-6">
|
||||
<form action="{{ url_for('inventory.device_details', id=placeholder.binding.devicehub_id) }}" method="post">
|
||||
{{ form_binding.csrf_token }}
|
||||
{% for field in form_binding %}
|
||||
{% if field != form_binding.csrf_token %}
|
||||
|
||||
<div class="col-12">
|
||||
{{ field.label(class_="form-label") }}:
|
||||
{{ field }}
|
||||
{% if field.errors %}
|
||||
<p class="text-danger">
|
||||
{% for error in field.errors %}
|
||||
{{ error }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="col-12 mt-2">
|
||||
<input type="submit" class="btn btn-primary" value="Search" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -401,6 +401,8 @@
|
|||
<th scope="col">Select</th>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col">DHID</th>
|
||||
<th scope="col">PHID</th>
|
||||
<th scope="col">Is Abstract</th>
|
||||
<th scope="col">Unique Identifiers</th>
|
||||
<th scope="col">Lifecycle Status</th>
|
||||
<th scope="col">Allocated Status</th>
|
||||
|
@ -442,6 +444,12 @@
|
|||
{{ dev.devicehub_id }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ dev.binding and dev.binding.phid or dev.placeholder and dev.placeholder.phid or '' }}
|
||||
</td>
|
||||
<td>
|
||||
{{ dev.binding and dev.binding.is_abstract and '✓' or dev.placeholder and dev.placeholder.is_abstract and '✓' or '' }}
|
||||
</td>
|
||||
<td>
|
||||
{% for t in dev.tags | sort(attribute="id") %}
|
||||
<a href="{{ url_for('labels.label_details', id=t.id)}}">{{ t.id }}</a>
|
||||
|
|
Reference in New Issue