resolve conflict
This commit is contained in:
commit
c9cd68b817
|
@ -30,6 +30,7 @@ from wtforms.fields import FormField
|
|||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.inventory.models import DeliveryNote, ReceiverNote, Transfer
|
||||
from ereuse_devicehub.parser.models import PlaceholdersLog
|
||||
from ereuse_devicehub.parser.parser import ParseSnapshotLsHw
|
||||
from ereuse_devicehub.parser.schemas import Snapshot_lite
|
||||
from ereuse_devicehub.resources.action.models import Snapshot, Trade
|
||||
|
@ -540,6 +541,11 @@ class NewDeviceForm(FlaskForm):
|
|||
device.placeholder = self.get_placeholder()
|
||||
db.session.add(device)
|
||||
|
||||
placeholder_log = PlaceholdersLog(
|
||||
type="New device", source='Web form', placeholder=device.placeholder
|
||||
)
|
||||
db.session.add(placeholder_log)
|
||||
|
||||
def reset_ids(self):
|
||||
if self.amount.data > 1:
|
||||
self.phid.data = None
|
||||
|
@ -595,6 +601,10 @@ class NewDeviceForm(FlaskForm):
|
|||
and self.functionality.data != self._obj.functionality().name
|
||||
):
|
||||
self._obj.set_functionality(self.functionality.data)
|
||||
placeholder_log = PlaceholdersLog(
|
||||
type="Update", source='Web form', placeholder=self._obj.placeholder
|
||||
)
|
||||
db.session.add(placeholder_log)
|
||||
|
||||
|
||||
class TagDeviceForm(FlaskForm):
|
||||
|
@ -1445,6 +1455,7 @@ class UploadPlaceholderForm(FlaskForm):
|
|||
|
||||
_file = files[0]
|
||||
if _file.content_type == 'text/csv':
|
||||
self.source = "CSV File: {}".format(_file.filename)
|
||||
delimiter = ';'
|
||||
data = pd.read_csv(_file).to_dict()
|
||||
head = list(data.keys())[0].split(delimiter)
|
||||
|
@ -1458,6 +1469,7 @@ class UploadPlaceholderForm(FlaskForm):
|
|||
for k, v in x.items():
|
||||
data[head[i]][k] = v[i]
|
||||
else:
|
||||
self.source = "Excel File: {}".format(_file.filename)
|
||||
try:
|
||||
data = pd.read_excel(_file).to_dict()
|
||||
except ValueError:
|
||||
|
@ -1514,7 +1526,10 @@ class UploadPlaceholderForm(FlaskForm):
|
|||
placeholder.pallet = "{}".format(data['Pallet'][i])
|
||||
placeholder.info = "{}".format(data['Info'][i])
|
||||
|
||||
self.placeholders.append(device)
|
||||
placeholder_log = PlaceholdersLog(
|
||||
type="Update", source=self.source, placeholder=device.placeholder
|
||||
)
|
||||
self.placeholders.append((device, placeholder_log))
|
||||
continue
|
||||
|
||||
# create a new one
|
||||
|
@ -1540,14 +1555,18 @@ class UploadPlaceholderForm(FlaskForm):
|
|||
device = snapshot_json['device']
|
||||
device.placeholder = Placeholder(**json_placeholder)
|
||||
|
||||
self.placeholders.append(device)
|
||||
placeholder_log = PlaceholdersLog(
|
||||
type="New device", source=self.source, placeholder=device.placeholder
|
||||
)
|
||||
self.placeholders.append((device, placeholder_log))
|
||||
|
||||
return True
|
||||
|
||||
def save(self, commit=True):
|
||||
|
||||
for device in self.placeholders:
|
||||
for device, placeholder_log in self.placeholders:
|
||||
db.session.add(device)
|
||||
db.session.add(placeholder_log)
|
||||
|
||||
if commit:
|
||||
db.session.commit()
|
||||
|
|
|
@ -34,7 +34,7 @@ from ereuse_devicehub.inventory.forms import (
|
|||
UploadSnapshotForm,
|
||||
)
|
||||
from ereuse_devicehub.labels.forms import PrintLabelsForm
|
||||
from ereuse_devicehub.parser.models import SnapshotsLog
|
||||
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.documents.device_row import ActionRow, DeviceRow
|
||||
|
@ -880,7 +880,7 @@ class UploadPlaceholderView(GenericMixin):
|
|||
if lot_id:
|
||||
lots = self.context['lots']
|
||||
lot = lots.filter(Lot.id == lot_id).one()
|
||||
for device in snapshots:
|
||||
for device, p in snapshots:
|
||||
lot.devices.add(device)
|
||||
db.session.add(lot)
|
||||
db.session.commit()
|
||||
|
@ -889,6 +889,24 @@ class UploadPlaceholderView(GenericMixin):
|
|||
return flask.render_template(self.template_name, **self.context)
|
||||
|
||||
|
||||
class PlaceholderLogListView(GenericMixin):
|
||||
template_name = 'inventory/placeholder_log_list.html'
|
||||
|
||||
def dispatch_request(self):
|
||||
self.get_context()
|
||||
self.context['page_title'] = "Placeholder Logs"
|
||||
self.context['placeholders_log'] = self.get_placeholders_log()
|
||||
|
||||
return flask.render_template(self.template_name, **self.context)
|
||||
|
||||
def get_placeholders_log(self):
|
||||
placeholder_log = PlaceholdersLog.query.filter(
|
||||
PlaceholdersLog.owner == g.user
|
||||
).order_by(PlaceholdersLog.created.desc())
|
||||
|
||||
return placeholder_log
|
||||
|
||||
|
||||
devices.add_url_rule('/action/add/', view_func=NewActionView.as_view('action_add'))
|
||||
devices.add_url_rule('/action/trade/add/', view_func=NewTradeView.as_view('trade_add'))
|
||||
devices.add_url_rule(
|
||||
|
@ -970,3 +988,6 @@ devices.add_url_rule(
|
|||
'/lot/<string:lot_id>/upload-placeholder/',
|
||||
view_func=UploadPlaceholderView.as_view('lot_upload_placeholder'),
|
||||
)
|
||||
devices.add_url_rule(
|
||||
'/placeholder-logs/', view_func=PlaceholderLogListView.as_view('placeholder_logs')
|
||||
)
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
"""placeholder log
|
||||
|
||||
Revision ID: 3e3a67f62972
|
||||
Revises: aeca9fb50cc6
|
||||
Create Date: 2022-07-06 18:23:54.267003
|
||||
|
||||
"""
|
||||
import citext
|
||||
import sqlalchemy as sa
|
||||
from alembic import context, op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3e3a67f62972'
|
||||
down_revision = 'aeca9fb50cc6'
|
||||
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():
|
||||
op.create_table(
|
||||
'placeholders_log',
|
||||
sa.Column(
|
||||
'updated',
|
||||
sa.TIMESTAMP(timezone=True),
|
||||
server_default=sa.text('CURRENT_TIMESTAMP'),
|
||||
nullable=False,
|
||||
comment='The last time Devicehub recorded a change for \n this thing.\n ',
|
||||
),
|
||||
sa.Column(
|
||||
'created',
|
||||
sa.TIMESTAMP(timezone=True),
|
||||
server_default=sa.text('CURRENT_TIMESTAMP'),
|
||||
nullable=False,
|
||||
comment='When Devicehub created this.',
|
||||
),
|
||||
sa.Column('id', sa.BigInteger(), nullable=False),
|
||||
sa.Column('source', citext.CIText(), nullable=True),
|
||||
sa.Column('type', citext.CIText(), nullable=True),
|
||||
sa.Column('severity', sa.SmallInteger(), nullable=False),
|
||||
sa.Column('placeholder_id', sa.BigInteger(), nullable=True),
|
||||
sa.Column('owner_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
['placeholder_id'],
|
||||
[f'{get_inv()}.placeholder.id'],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
['owner_id'],
|
||||
['common.user.id'],
|
||||
),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.execute("CREATE SEQUENCE placeholders_log_seq START 1;")
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('placeholders_log')
|
||||
op.execute("DROP SEQUENCE placeholders_log_seq;")
|
|
@ -5,6 +5,7 @@ from sqlalchemy.dialects.postgresql import UUID
|
|||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.action.models import Snapshot
|
||||
from ereuse_devicehub.resources.device.models import Placeholder
|
||||
from ereuse_devicehub.resources.enums import Severity
|
||||
from ereuse_devicehub.resources.models import Thing
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
|
@ -43,3 +44,48 @@ class SnapshotsLog(Thing):
|
|||
return self.snapshot.device.devicehub_id
|
||||
|
||||
return ''
|
||||
|
||||
|
||||
class PlaceholdersLog(Thing):
|
||||
"""A Placeholder log."""
|
||||
|
||||
__table_args__ = {'schema': ''}
|
||||
id = Column(BigInteger, Sequence('placeholders_log_seq'), primary_key=True)
|
||||
source = Column(CIText(), default='', nullable=True)
|
||||
type = Column(CIText(), default='', nullable=True)
|
||||
severity = Column(SmallInteger, default=Severity.Info, nullable=False)
|
||||
|
||||
placeholder_id = Column(BigInteger, db.ForeignKey(Placeholder.id), nullable=True)
|
||||
placeholder = db.relationship(
|
||||
Placeholder, primaryjoin=placeholder_id == Placeholder.id
|
||||
)
|
||||
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)
|
||||
|
||||
def save(self, commit=False):
|
||||
db.session.add(self)
|
||||
|
||||
if commit:
|
||||
db.session.commit()
|
||||
|
||||
@property
|
||||
def phid(self):
|
||||
if self.placeholder:
|
||||
return self.placeholder.phid
|
||||
|
||||
return ''
|
||||
|
||||
@property
|
||||
def dhid(self):
|
||||
if self.placeholder:
|
||||
return self.placeholder.device.devicehub_id
|
||||
|
||||
return ''
|
||||
|
||||
def get_status(self):
|
||||
return Severity(self.severity)
|
||||
|
|
|
@ -148,6 +148,15 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-heading">Placeholders</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="{{ url_for('inventory.placeholder_logs') }}">
|
||||
<i class="bi-menu-button-wide"></i>
|
||||
<span>Uploaded Placeholders</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-heading">Devices</li>
|
||||
|
||||
<li class="nav-item">
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
{% extends "ereuse_devicehub/base_site.html" %}
|
||||
{% block main %}
|
||||
|
||||
<div class="pagetitle">
|
||||
<h1>{{ page_title }}</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ url_for('inventory.devicelist')}}">Inventory</a></li>
|
||||
<li class="breadcrumb-item active">Placeholders</li>
|
||||
</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 pt-3" style="min-height: 650px;">
|
||||
<!-- Bordered Tabs -->
|
||||
<div class="tab-content pt-5">
|
||||
<div id="devices-list" class="tab-pane fade devices-list active show">
|
||||
<div class="tab-content pt-2">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">PHID</th>
|
||||
<th scope="col">Placeholder source</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">DHID</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in placeholders_log %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ log.phid }}
|
||||
</td>
|
||||
<td>
|
||||
{{ log.source }}
|
||||
</td>
|
||||
<td>
|
||||
{{ log.type }}
|
||||
</td>
|
||||
<td>
|
||||
{% if log.dhid %}
|
||||
<a href="{{ url_for('inventory.device_details', id=log.dhid)}}">{{ log.dhid }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ log.get_status() }}
|
||||
</td>
|
||||
<td>{{ log.created.strftime('%H:%M %d-%m-%Y') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- End Bordered Tabs -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="NotificationsContainer" style="position: absolute; bottom: 0; right: 0; margin: 10px; margin-top: 70px; width: calc(100% - 310px);"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Custom Code -->
|
||||
<script>
|
||||
const table = new simpleDatatables.DataTable("table")
|
||||
</script>
|
||||
{% endblock main %}
|
|
@ -0,0 +1,4 @@
|
|||
Phid;Model;Manufacturer;Serial Number;Id device Supplier;Pallet;Info
|
||||
a123;Vaio;Sony;12345678;TTT;24A;Good conditions
|
||||
a124;Vaio;Sony;12345679;TTT;24A;Good conditions
|
||||
a125;Vaio;Sony;12345680;TTT;24A;Good conditions
|
|
Binary file not shown.
Binary file not shown.
|
@ -1781,3 +1781,160 @@ def test_edit_laptop(user3: UserClientFlask):
|
|||
assert dev.placeholder.id_device_supplier == 'a2'
|
||||
assert dev.serial_number == 'aaaac'
|
||||
assert dev.model == 'lc27t56'
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_placeholder_log_manual_new(user3: UserClientFlask):
|
||||
uri = '/inventory/device/add/'
|
||||
user3.get(uri)
|
||||
data = {
|
||||
'csrf_token': generate_csrf(),
|
||||
'type': "Laptop",
|
||||
'phid': 'ace',
|
||||
'serial_number': "AAAAB",
|
||||
'model': "LC27T55",
|
||||
'manufacturer': "Samsung",
|
||||
'generation': 1,
|
||||
'weight': 0.1,
|
||||
'height': 0.1,
|
||||
'depth': 0.1,
|
||||
'id_device_supplier': "b2",
|
||||
}
|
||||
user3.post(uri, data=data)
|
||||
|
||||
uri = '/inventory/placeholder-logs/'
|
||||
body, status = user3.get(uri)
|
||||
assert status == '200 OK'
|
||||
assert "Placeholder Logs" in body
|
||||
assert "Web form" in body
|
||||
assert "ace" in body
|
||||
assert "New device" in body
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_placeholder_log_manual_edit(user3: UserClientFlask):
|
||||
uri = '/inventory/device/add/'
|
||||
user3.get(uri)
|
||||
data = {
|
||||
'csrf_token': generate_csrf(),
|
||||
'type': "Laptop",
|
||||
'phid': 'ace',
|
||||
'serial_number': "AAAAB",
|
||||
'model': "LC27T55",
|
||||
'manufacturer': "Samsung",
|
||||
'generation': 1,
|
||||
'weight': 0.1,
|
||||
'height': 0.1,
|
||||
'depth': 0.1,
|
||||
'id_device_supplier': "b2",
|
||||
}
|
||||
user3.post(uri, data=data)
|
||||
dev = Device.query.one()
|
||||
|
||||
uri = '/inventory/device/edit/{}/'.format(dev.devicehub_id)
|
||||
user3.get(uri)
|
||||
|
||||
data = {
|
||||
'csrf_token': generate_csrf(),
|
||||
'type': "Laptop",
|
||||
'serial_number': "AAAAC",
|
||||
'model': "LC27T56",
|
||||
'manufacturer': "Samsung",
|
||||
'generation': 1,
|
||||
'weight': 0.1,
|
||||
'height': 0.1,
|
||||
'depth': 0.1,
|
||||
'id_device_supplier': "a2",
|
||||
}
|
||||
user3.post(uri, data=data)
|
||||
|
||||
uri = '/inventory/placeholder-logs/'
|
||||
body, status = user3.get(uri)
|
||||
assert status == '200 OK'
|
||||
assert "Placeholder Logs" in body
|
||||
assert "Web form" in body
|
||||
assert "ace" in body
|
||||
assert "Update" in body
|
||||
assert dev.devicehub_id in body
|
||||
assert "✓" in body
|
||||
assert "CSV" not in body
|
||||
assert "Excel" not in body
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_placeholder_log_excel_new(user3: UserClientFlask):
|
||||
|
||||
uri = '/inventory/upload-placeholder/'
|
||||
body, status = user3.get(uri)
|
||||
|
||||
file_path = Path(__file__).parent.joinpath('files').joinpath('placeholder_test.xls')
|
||||
with open(file_path, 'rb') as excel:
|
||||
data = {
|
||||
'csrf_token': generate_csrf(),
|
||||
'type': "Laptop",
|
||||
'placeholder_file': excel,
|
||||
}
|
||||
user3.post(uri, data=data, content_type="multipart/form-data")
|
||||
dev = Device.query.first()
|
||||
assert dev.placeholder.phid == 'a123'
|
||||
|
||||
uri = '/inventory/placeholder-logs/'
|
||||
body, status = user3.get(uri)
|
||||
assert status == '200 OK'
|
||||
assert "Placeholder Logs" in body
|
||||
assert dev.placeholder.phid in body
|
||||
assert dev.devicehub_id in body
|
||||
assert "Web form" not in body
|
||||
assert "Update" not in body
|
||||
assert "New device" in body
|
||||
assert "✓" in body
|
||||
assert "CSV" not in body
|
||||
assert "Excel" in body
|
||||
assert "placeholder_test.xls" in body
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_placeholder_log_excel_update(user3: UserClientFlask):
|
||||
|
||||
uri = '/inventory/upload-placeholder/'
|
||||
body, status = user3.get(uri)
|
||||
|
||||
file_path = Path(__file__).parent.joinpath('files').joinpath('placeholder_test.xls')
|
||||
with open(file_path, 'rb') as excel:
|
||||
data = {
|
||||
'csrf_token': generate_csrf(),
|
||||
'type': "Laptop",
|
||||
'placeholder_file': excel,
|
||||
}
|
||||
user3.post(uri, data=data, content_type="multipart/form-data")
|
||||
|
||||
file_path = Path(__file__).parent.joinpath('files').joinpath('placeholder_test.csv')
|
||||
with open(file_path, 'rb') as excel:
|
||||
data = {
|
||||
'csrf_token': generate_csrf(),
|
||||
'type': "Laptop",
|
||||
'placeholder_file': excel,
|
||||
}
|
||||
user3.post(uri, data=data, content_type="multipart/form-data")
|
||||
|
||||
dev = Device.query.first()
|
||||
assert dev.placeholder.phid == 'a123'
|
||||
|
||||
uri = '/inventory/placeholder-logs/'
|
||||
body, status = user3.get(uri)
|
||||
assert status == '200 OK'
|
||||
assert "Placeholder Logs" in body
|
||||
assert dev.placeholder.phid in body
|
||||
assert dev.devicehub_id in body
|
||||
assert "Web form" not in body
|
||||
assert "Update" in body
|
||||
assert "New device" in body
|
||||
assert "✓" in body
|
||||
assert "CSV" in body
|
||||
assert "Excel" in body
|
||||
assert "placeholder_test.xls" in body
|
||||
assert "placeholder_test.csv" in body
|
||||
|
|
Reference in New Issue