Merge pull request #203 from eReuse/feature/server-side-render-actions-trade
Feature/server side render actions trade
This commit is contained in:
commit
7895cf457a
|
@ -3,35 +3,39 @@ import json
|
|||
from json.decoder import JSONDecodeError
|
||||
|
||||
from boltons.urlutils import URL
|
||||
from flask import g, request
|
||||
from flask_wtf import FlaskForm
|
||||
from sqlalchemy.util import OrderedSet
|
||||
from wtforms import (
|
||||
BooleanField, DateField, FileField, FloatField, Form, HiddenField,
|
||||
IntegerField, MultipleFileField, SelectField, StringField, TextAreaField,
|
||||
URLField, validators)
|
||||
from wtforms.fields import FormField
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.action.models import RateComputer, Snapshot
|
||||
from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate
|
||||
from ereuse_devicehub.resources.action.schemas import \
|
||||
Snapshot as SnapshotSchema
|
||||
from ereuse_devicehub.resources.action.views.snapshot import move_json, save_json
|
||||
from ereuse_devicehub.resources.device.models import (SAI, Cellphone, Computer,
|
||||
Device, Keyboard, MemoryCardReader,
|
||||
Monitor, Mouse, Smartphone, Tablet)
|
||||
from flask import g, request
|
||||
from flask_wtf import FlaskForm
|
||||
from sqlalchemy.util import OrderedSet
|
||||
from wtforms import (BooleanField, DateField, FileField, FloatField, Form,
|
||||
HiddenField, IntegerField, MultipleFileField, SelectField,
|
||||
StringField, TextAreaField, URLField, validators)
|
||||
from wtforms.fields import FormField
|
||||
|
||||
from ereuse_devicehub.resources.action.views.snapshot import (
|
||||
move_json, save_json)
|
||||
from ereuse_devicehub.resources.device.models import (
|
||||
SAI, Cellphone, Computer, Device, Keyboard, MemoryCardReader, Monitor,
|
||||
Mouse, Smartphone, Tablet)
|
||||
from ereuse_devicehub.resources.device.sync import Sync
|
||||
from ereuse_devicehub.resources.documents.models import DataWipeDocument
|
||||
from ereuse_devicehub.resources.enums import Severity, SnapshotSoftware
|
||||
from ereuse_devicehub.resources.hash_reports import insert_hash
|
||||
from ereuse_devicehub.resources.lot.models import Lot
|
||||
from ereuse_devicehub.resources.tag.model import Tag
|
||||
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||
from ereuse_devicehub.resources.user.exceptions import InsufficientPermission
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
|
||||
|
||||
class LotDeviceForm(FlaskForm):
|
||||
lot = StringField(u'Lot', [validators.UUID()])
|
||||
devices = StringField(u'Devices', [validators.length(min=1)])
|
||||
lot = StringField('Lot', [validators.UUID()])
|
||||
devices = StringField('Devices', [validators.length(min=1)])
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
|
@ -39,16 +43,29 @@ class LotDeviceForm(FlaskForm):
|
|||
if not is_valid:
|
||||
return False
|
||||
|
||||
self._lot = Lot.query.filter(Lot.id == self.lot.data).filter(
|
||||
Lot.owner_id == g.user.id).one()
|
||||
self._lot = (
|
||||
Lot.query.filter(Lot.id == self.lot.data)
|
||||
.filter(Lot.owner_id == g.user.id)
|
||||
.one()
|
||||
)
|
||||
|
||||
devices = set(self.devices.data.split(","))
|
||||
self._devices = Device.query.filter(Device.id.in_(devices)).filter(
|
||||
Device.owner_id == g.user.id).distinct().all()
|
||||
self._devices = (
|
||||
Device.query.filter(Device.id.in_(devices))
|
||||
.filter(Device.owner_id == g.user.id)
|
||||
.distinct()
|
||||
.all()
|
||||
)
|
||||
|
||||
return bool(self._devices)
|
||||
|
||||
def save(self):
|
||||
trade = self._lot.trade
|
||||
if trade:
|
||||
for dev in self._devices:
|
||||
if trade not in dev.actions:
|
||||
trade.devices.add(dev)
|
||||
|
||||
self._lot.devices.update(self._devices)
|
||||
db.session.add(self._lot)
|
||||
db.session.commit()
|
||||
|
@ -60,14 +77,17 @@ class LotDeviceForm(FlaskForm):
|
|||
|
||||
|
||||
class LotForm(FlaskForm):
|
||||
name = StringField(u'Name', [validators.length(min=1)])
|
||||
name = StringField('Name', [validators.length(min=1)])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.id = kwargs.pop('id', None)
|
||||
self.instance = None
|
||||
if self.id:
|
||||
self.instance = Lot.query.filter(Lot.id == self.id).filter(
|
||||
Lot.owner_id == g.user.id).one()
|
||||
self.instance = (
|
||||
Lot.query.filter(Lot.id == self.id)
|
||||
.filter(Lot.owner_id == g.user.id)
|
||||
.one()
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.instance and not self.name.data:
|
||||
self.name.data = self.instance.name
|
||||
|
@ -95,7 +115,7 @@ class LotForm(FlaskForm):
|
|||
|
||||
|
||||
class UploadSnapshotForm(FlaskForm):
|
||||
snapshot = MultipleFileField(u'Select a Snapshot File', [validators.DataRequired()])
|
||||
snapshot = MultipleFileField('Select a Snapshot File', [validators.DataRequired()])
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
|
@ -163,8 +183,10 @@ class UploadSnapshotForm(FlaskForm):
|
|||
# this is a copy adaptated from ereuse_devicehub.resources.action.views.snapshot
|
||||
device = snapshot_json.pop('device') # type: Computer
|
||||
components = None
|
||||
if snapshot_json['software'] == (SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid):
|
||||
components = snapshot_json.pop('components', None) # type: List[Component]
|
||||
if snapshot_json['software'] == (
|
||||
SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid
|
||||
):
|
||||
components = snapshot_json.pop('components', None)
|
||||
if isinstance(device, Computer) and device.hid:
|
||||
device.add_mac_to_hid(components_snap=components)
|
||||
snapshot = Snapshot(**snapshot_json)
|
||||
|
@ -173,7 +195,9 @@ class UploadSnapshotForm(FlaskForm):
|
|||
actions_device = set(e for e in device.actions_one)
|
||||
device.actions_one.clear()
|
||||
if components:
|
||||
actions_components = tuple(set(e for e in c.actions_one) for c in components)
|
||||
actions_components = tuple(
|
||||
set(e for e in c.actions_one) for c in components
|
||||
)
|
||||
for component in components:
|
||||
component.actions_one.clear()
|
||||
|
||||
|
@ -219,38 +243,40 @@ class UploadSnapshotForm(FlaskForm):
|
|||
|
||||
|
||||
class NewDeviceForm(FlaskForm):
|
||||
type = StringField(u'Type', [validators.DataRequired()])
|
||||
label = StringField(u'Label')
|
||||
serial_number = StringField(u'Seria Number', [validators.DataRequired()])
|
||||
model = StringField(u'Model', [validators.DataRequired()])
|
||||
manufacturer = StringField(u'Manufacturer', [validators.DataRequired()])
|
||||
appearance = StringField(u'Appearance', [validators.Optional()])
|
||||
functionality = StringField(u'Functionality', [validators.Optional()])
|
||||
brand = StringField(u'Brand')
|
||||
generation = IntegerField(u'Generation')
|
||||
version = StringField(u'Version')
|
||||
weight = FloatField(u'Weight', [validators.DataRequired()])
|
||||
width = FloatField(u'Width', [validators.DataRequired()])
|
||||
height = FloatField(u'Height', [validators.DataRequired()])
|
||||
depth = FloatField(u'Depth', [validators.DataRequired()])
|
||||
variant = StringField(u'Variant', [validators.Optional()])
|
||||
sku = StringField(u'SKU', [validators.Optional()])
|
||||
image = StringField(u'Image', [validators.Optional(), validators.URL()])
|
||||
imei = IntegerField(u'IMEI', [validators.Optional()])
|
||||
meid = StringField(u'MEID', [validators.Optional()])
|
||||
resolution = IntegerField(u'Resolution width', [validators.Optional()])
|
||||
screen = FloatField(u'Screen size', [validators.Optional()])
|
||||
type = StringField('Type', [validators.DataRequired()])
|
||||
label = StringField('Label')
|
||||
serial_number = StringField('Seria Number', [validators.DataRequired()])
|
||||
model = StringField('Model', [validators.DataRequired()])
|
||||
manufacturer = StringField('Manufacturer', [validators.DataRequired()])
|
||||
appearance = StringField('Appearance', [validators.Optional()])
|
||||
functionality = StringField('Functionality', [validators.Optional()])
|
||||
brand = StringField('Brand')
|
||||
generation = IntegerField('Generation')
|
||||
version = StringField('Version')
|
||||
weight = FloatField('Weight', [validators.DataRequired()])
|
||||
width = FloatField('Width', [validators.DataRequired()])
|
||||
height = FloatField('Height', [validators.DataRequired()])
|
||||
depth = FloatField('Depth', [validators.DataRequired()])
|
||||
variant = StringField('Variant', [validators.Optional()])
|
||||
sku = StringField('SKU', [validators.Optional()])
|
||||
image = StringField('Image', [validators.Optional(), validators.URL()])
|
||||
imei = IntegerField('IMEI', [validators.Optional()])
|
||||
meid = StringField('MEID', [validators.Optional()])
|
||||
resolution = IntegerField('Resolution width', [validators.Optional()])
|
||||
screen = FloatField('Screen size', [validators.Optional()])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.devices = {"Smartphone": Smartphone,
|
||||
self.devices = {
|
||||
"Smartphone": Smartphone,
|
||||
"Tablet": Tablet,
|
||||
"Cellphone": Cellphone,
|
||||
"Monitor": Monitor,
|
||||
"Mouse": Mouse,
|
||||
"Keyboard": Keyboard,
|
||||
"SAI": SAI,
|
||||
"MemoryCardReader": MemoryCardReader}
|
||||
"MemoryCardReader": MemoryCardReader,
|
||||
}
|
||||
|
||||
if not self.generation.data:
|
||||
self.generation.data = 1
|
||||
|
@ -340,16 +366,18 @@ class NewDeviceForm(FlaskForm):
|
|||
'height': self.height.data,
|
||||
'depth': self.depth.data,
|
||||
'variant': self.variant.data,
|
||||
'image': self.image.data
|
||||
}
|
||||
'image': self.image.data,
|
||||
},
|
||||
}
|
||||
|
||||
if self.appearance.data or self.functionality.data:
|
||||
json_snapshot['device']['actions'] = [{
|
||||
json_snapshot['device']['actions'] = [
|
||||
{
|
||||
'type': 'VisualTest',
|
||||
'appearanceRange': self.appearance.data,
|
||||
'functionalityRange': self.functionality.data
|
||||
}]
|
||||
'functionalityRange': self.functionality.data,
|
||||
}
|
||||
]
|
||||
|
||||
upload_form = UploadSnapshotForm()
|
||||
upload_form.sync = Sync()
|
||||
|
@ -379,7 +407,7 @@ class NewDeviceForm(FlaskForm):
|
|||
|
||||
|
||||
class TagForm(FlaskForm):
|
||||
code = StringField(u'Code', [validators.length(min=1)])
|
||||
code = StringField('Code', [validators.length(min=1)])
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
error = ["This value is being used"]
|
||||
|
@ -407,7 +435,7 @@ class TagForm(FlaskForm):
|
|||
|
||||
|
||||
class TagUnnamedForm(FlaskForm):
|
||||
amount = IntegerField(u'amount')
|
||||
amount = IntegerField('amount')
|
||||
|
||||
def save(self):
|
||||
num = self.amount.data
|
||||
|
@ -419,8 +447,8 @@ class TagUnnamedForm(FlaskForm):
|
|||
|
||||
|
||||
class TagDeviceForm(FlaskForm):
|
||||
tag = SelectField(u'Tag', choices=[])
|
||||
device = StringField(u'Device', [validators.Optional()])
|
||||
tag = SelectField('Tag', choices=[])
|
||||
device = StringField('Device', [validators.Optional()])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.delete = kwargs.pop('delete', None)
|
||||
|
@ -429,9 +457,11 @@ class TagDeviceForm(FlaskForm):
|
|||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.delete:
|
||||
tags = Tag.query.filter(Tag.owner_id==g.user.id).filter(Tag.device_id==self.device_id)
|
||||
tags = Tag.query.filter(Tag.owner_id == g.user.id).filter_by(
|
||||
device_id=self.device_id
|
||||
)
|
||||
else:
|
||||
tags = Tag.query.filter(Tag.owner_id==g.user.id).filter(Tag.device_id==None)
|
||||
tags = Tag.query.filter(Tag.owner_id == g.user.id).filter_by(device_id=None)
|
||||
|
||||
self.tag.choices = [(tag.id, tag.id) for tag in tags]
|
||||
|
||||
|
@ -441,8 +471,11 @@ class TagDeviceForm(FlaskForm):
|
|||
if not is_valid:
|
||||
return False
|
||||
|
||||
self._tag = Tag.query.filter(Tag.id == self.tag.data).filter(
|
||||
Tag.owner_id == g.user.id).one()
|
||||
self._tag = (
|
||||
Tag.query.filter(Tag.id == self.tag.data)
|
||||
.filter(Tag.owner_id == g.user.id)
|
||||
.one()
|
||||
)
|
||||
|
||||
if not self.delete and self._tag.device_id:
|
||||
self.tag.errors = [("This tag is actualy in use.")]
|
||||
|
@ -456,8 +489,11 @@ class TagDeviceForm(FlaskForm):
|
|||
|
||||
if self.device_id or self.device.data:
|
||||
self.device_id = self.device_id or self.device.data
|
||||
self._device = Device.query.filter(Device.id == self.device_id).filter(
|
||||
Device.owner_id == g.user.id).one()
|
||||
self._device = (
|
||||
Device.query.filter(Device.id == self.device_id)
|
||||
.filter(Device.owner_id == g.user.id)
|
||||
.one()
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -473,38 +509,55 @@ class TagDeviceForm(FlaskForm):
|
|||
|
||||
|
||||
class NewActionForm(FlaskForm):
|
||||
name = StringField(u'Name', [validators.length(max=50)],
|
||||
description="A name or title of the event. Something to look for.")
|
||||
name = StringField(
|
||||
'Name',
|
||||
[validators.length(max=50)],
|
||||
description="A name or title of the event. Something to look for.",
|
||||
)
|
||||
devices = HiddenField()
|
||||
date = DateField(u'Date', [validators.Optional()],
|
||||
date = DateField(
|
||||
'Date',
|
||||
[validators.Optional()],
|
||||
description="""When the action ends. For some actions like booking
|
||||
the time when it expires, for others like renting the
|
||||
time that the end rents. For specific actions, it is the
|
||||
time in which they are carried out; differs from created
|
||||
in that created is where the system receives the action.""")
|
||||
severity = SelectField(u'Severity', choices=[(v.name, v.name) for v in Severity],
|
||||
in that created is where the system receives the action.""",
|
||||
)
|
||||
severity = SelectField(
|
||||
'Severity',
|
||||
choices=[(v.name, v.name) for v in Severity],
|
||||
description="""An indicator that evaluates the execution of the event.
|
||||
For example, failed events are set to Error""")
|
||||
description = TextAreaField(u'Description')
|
||||
For example, failed events are set to Error""",
|
||||
)
|
||||
description = TextAreaField('Description')
|
||||
lot = HiddenField()
|
||||
type = HiddenField()
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
is_valid = self.generic_validation(extra_validators=extra_validators)
|
||||
|
||||
if not is_valid:
|
||||
return False
|
||||
|
||||
self._devices = OrderedSet()
|
||||
if self.devices.data:
|
||||
devices = set(self.devices.data.split(","))
|
||||
self._devices = OrderedSet(Device.query.filter(Device.id.in_(devices)).filter(
|
||||
Device.owner_id == g.user.id).all())
|
||||
self._devices = OrderedSet(
|
||||
Device.query.filter(Device.id.in_(devices))
|
||||
.filter(Device.owner_id == g.user.id)
|
||||
.all()
|
||||
)
|
||||
|
||||
if not self._devices:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def generic_validation(self, extra_validators=None):
|
||||
# Some times we want check validations without devices list
|
||||
return super().validate(extra_validators)
|
||||
|
||||
def save(self):
|
||||
Model = db.Model._decl_class_registry.data[self.type.data]()
|
||||
self.instance = Model()
|
||||
|
@ -522,13 +575,20 @@ class NewActionForm(FlaskForm):
|
|||
|
||||
return self.instance
|
||||
|
||||
def check_valid(self):
|
||||
if self.type.data in ['', None]:
|
||||
return
|
||||
|
||||
if not self.validate():
|
||||
return self.type.data
|
||||
|
||||
|
||||
class AllocateForm(NewActionForm):
|
||||
start_time = DateField(u'Start time')
|
||||
end_time = DateField(u'End time')
|
||||
final_user_code = StringField(u'Final user code', [validators.length(max=50)])
|
||||
transaction = StringField(u'Transaction', [validators.length(max=50)])
|
||||
end_users = IntegerField(u'End users')
|
||||
start_time = DateField('Start time')
|
||||
end_time = DateField('End time')
|
||||
final_user_code = StringField('Final user code', [validators.length(max=50)])
|
||||
transaction = StringField('Transaction', [validators.length(max=50)])
|
||||
end_users = IntegerField('End users')
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
|
@ -549,19 +609,31 @@ class AllocateForm(NewActionForm):
|
|||
|
||||
|
||||
class DataWipeDocumentForm(Form):
|
||||
date = DateField(u'Date', [validators.Optional()],
|
||||
description="Date when was data wipe")
|
||||
url = URLField(u'Url', [validators.Optional()],
|
||||
description="Url where the document resides")
|
||||
success = BooleanField(u'Success', [validators.Optional()],
|
||||
description="The erase was success or not?")
|
||||
software = StringField(u'Software', [validators.Optional()],
|
||||
description="Which software has you use for erase the disks")
|
||||
id_document = StringField(u'Document Id', [validators.Optional()],
|
||||
description="Identification number of document")
|
||||
file_name = FileField(u'File', [validators.DataRequired()],
|
||||
date = DateField(
|
||||
'Date', [validators.Optional()], description="Date when was data wipe"
|
||||
)
|
||||
url = URLField(
|
||||
'Url', [validators.Optional()], description="Url where the document resides"
|
||||
)
|
||||
success = BooleanField(
|
||||
'Success', [validators.Optional()], description="The erase was success or not?"
|
||||
)
|
||||
software = StringField(
|
||||
'Software',
|
||||
[validators.Optional()],
|
||||
description="Which software has you use for erase the disks",
|
||||
)
|
||||
id_document = StringField(
|
||||
'Document Id',
|
||||
[validators.Optional()],
|
||||
description="Identification number of document",
|
||||
)
|
||||
file_name = FileField(
|
||||
'File',
|
||||
[validators.DataRequired()],
|
||||
description="""This file is not stored on our servers, it is only used to
|
||||
generate a digital signature and obtain the name of the file.""")
|
||||
generate a digital signature and obtain the name of the file.""",
|
||||
)
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
|
@ -614,3 +686,230 @@ class DataWipeForm(NewActionForm):
|
|||
self.document = document
|
||||
|
||||
return self.instance
|
||||
|
||||
|
||||
class TradeForm(NewActionForm):
|
||||
user_from = StringField(
|
||||
'Supplier',
|
||||
[validators.Optional()],
|
||||
description="Please enter the supplier's email address",
|
||||
render_kw={'data-email': ""},
|
||||
)
|
||||
user_to = StringField(
|
||||
'Receiver',
|
||||
[validators.Optional()],
|
||||
description="Please enter the receiver's email address",
|
||||
render_kw={'data-email': ""},
|
||||
)
|
||||
confirm = BooleanField(
|
||||
'Confirm',
|
||||
[validators.Optional()],
|
||||
default=True,
|
||||
description="I need confirmation from the other user for every device and document.",
|
||||
)
|
||||
code = StringField(
|
||||
'Code',
|
||||
[validators.Optional()],
|
||||
description="If you don't need confirm, you need put a code for trace the user in the statistics.",
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user_from.render_kw['data-email'] = g.user.email
|
||||
self.user_to.render_kw['data-email'] = g.user.email
|
||||
self._lot = (
|
||||
Lot.query.filter(Lot.id == self.lot.data)
|
||||
.filter(Lot.owner_id == g.user.id)
|
||||
.one()
|
||||
)
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = self.generic_validation(extra_validators=extra_validators)
|
||||
email_from = self.user_from.data
|
||||
email_to = self.user_to.data
|
||||
|
||||
if not self.confirm.data and not self.code.data:
|
||||
self.code.errors = ["If you don't want to confirm, you need a code"]
|
||||
is_valid = False
|
||||
|
||||
if (
|
||||
self.confirm.data
|
||||
and not (email_from and email_to)
|
||||
or email_to == email_from
|
||||
or g.user.email not in [email_from, email_to]
|
||||
):
|
||||
|
||||
errors = ["If you want confirm, you need a correct email"]
|
||||
self.user_to.errors = errors
|
||||
self.user_from.errors = errors
|
||||
|
||||
is_valid = False
|
||||
|
||||
if self.confirm.data and is_valid:
|
||||
user_to = User.query.filter_by(email=email_to).first() or g.user
|
||||
user_from = User.query.filter_by(email=email_from).first() or g.user
|
||||
if user_to == user_from:
|
||||
is_valid = False
|
||||
else:
|
||||
self.db_user_to = user_to
|
||||
self.db_user_from = user_from
|
||||
|
||||
self.has_errors = not is_valid
|
||||
return is_valid
|
||||
|
||||
def save(self, commit=True):
|
||||
if self.has_errors:
|
||||
raise ValueError(
|
||||
"The %s could not be saved because the data didn't validate."
|
||||
% (self.instance._meta.object_name)
|
||||
)
|
||||
if not self.confirm.data:
|
||||
self.create_phantom_account()
|
||||
self.prepare_instance()
|
||||
self.create_automatic_trade()
|
||||
|
||||
if commit:
|
||||
db.session.commit()
|
||||
|
||||
return self.instance
|
||||
|
||||
def prepare_instance(self):
|
||||
Model = db.Model._decl_class_registry.data['Trade']()
|
||||
self.instance = Model()
|
||||
self.instance.user_from = self.db_user_from
|
||||
self.instance.user_to = self.db_user_to
|
||||
self.instance.lot_id = self._lot.id
|
||||
self.instance.devices = self._lot.devices
|
||||
self.instance.code = self.code.data
|
||||
self.instance.confirm = self.confirm.data
|
||||
self.instance.date = self.date.data
|
||||
self.instance.name = self.name.data
|
||||
self.instance.description = self.description.data
|
||||
db.session.add(self.instance)
|
||||
|
||||
def create_phantom_account(self):
|
||||
"""
|
||||
If exist both users not to do nothing
|
||||
If exist from but not to:
|
||||
search if exist in the DB
|
||||
if exist use it
|
||||
else create new one
|
||||
The same if exist to but not from
|
||||
|
||||
"""
|
||||
user_from = self.user_from.data
|
||||
user_to = self.user_to.data
|
||||
code = self.code.data
|
||||
|
||||
if user_from and user_to:
|
||||
# both users exist, no further action is necessary
|
||||
return
|
||||
|
||||
# Create receiver (to) phantom account
|
||||
if user_from and not user_to:
|
||||
assert g.user.email == user_from
|
||||
|
||||
self.user_from = g.user
|
||||
self.user_to = self.get_or_create_user(code)
|
||||
return
|
||||
|
||||
# Create supplier (from) phantom account
|
||||
if not user_from and user_to:
|
||||
assert g.user.email == user_to
|
||||
|
||||
self.user_from = self.get_or_create_user(code)
|
||||
self.user_to = g.user
|
||||
|
||||
def get_or_create_user(self, code):
|
||||
email = "{}_{}@dhub.com".format(str(g.user.id), code)
|
||||
user = User.query.filter_by(email=email).first()
|
||||
if not user:
|
||||
user = User(email=email, password='', active=False, phantom=True)
|
||||
db.session.add(user)
|
||||
return user
|
||||
|
||||
def create_automatic_trade(self):
|
||||
# This method change the ownership of devices
|
||||
# do nothing if an explicit confirmation is required
|
||||
if self.confirm.data:
|
||||
return
|
||||
|
||||
# Change the owner for every devices
|
||||
for dev in self._lot.devices:
|
||||
dev.change_owner(self.db_user_to)
|
||||
|
||||
def check_valid(self):
|
||||
if self.user_from.data == self.user_to.data:
|
||||
return
|
||||
|
||||
if self.user_from.data == g.user.email:
|
||||
return 'user_to'
|
||||
|
||||
if self.user_to.data == g.user.email:
|
||||
return 'user_from'
|
||||
|
||||
|
||||
class TradeDocumentForm(FlaskForm):
|
||||
url = URLField(
|
||||
'Url',
|
||||
[validators.Optional()],
|
||||
render_kw={'class': "form-control"},
|
||||
description="Url where the document resides",
|
||||
)
|
||||
description = StringField(
|
||||
'Description',
|
||||
[validators.Optional()],
|
||||
render_kw={'class': "form-control"},
|
||||
description="",
|
||||
)
|
||||
id_document = StringField(
|
||||
'Document Id',
|
||||
[validators.Optional()],
|
||||
render_kw={'class': "form-control"},
|
||||
description="Identification number of document",
|
||||
)
|
||||
date = DateField(
|
||||
'Date',
|
||||
[validators.Optional()],
|
||||
render_kw={'class': "form-control"},
|
||||
description="",
|
||||
)
|
||||
file_name = FileField(
|
||||
'File',
|
||||
[validators.DataRequired()],
|
||||
render_kw={'class': "form-control"},
|
||||
description="""This file is not stored on our servers, it is only used to
|
||||
generate a digital signature and obtain the name of the file.""",
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
lot_id = kwargs.pop('lot')
|
||||
super().__init__(*args, **kwargs)
|
||||
self._lot = Lot.query.filter(Lot.id == lot_id).one()
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
|
||||
if g.user not in [self._lot.trade.user_from, self._lot.trade.user_to]:
|
||||
is_valid = False
|
||||
|
||||
return is_valid
|
||||
|
||||
def save(self, commit=True):
|
||||
file_name = ''
|
||||
file_hash = ''
|
||||
if self.file_name.data:
|
||||
file_name = self.file_name.data.filename
|
||||
file_hash = insert_hash(self.file_name.data.read(), commit=False)
|
||||
|
||||
self.url.data = URL(self.url.data)
|
||||
self._obj = TradeDocument(lot_id=self._lot.id)
|
||||
self.populate_obj(self._obj)
|
||||
self._obj.file_name = file_name
|
||||
self._obj.file_hash = file_hash
|
||||
db.session.add(self._obj)
|
||||
self._lot.trade.documents.add(self._obj)
|
||||
if commit:
|
||||
db.session.commit()
|
||||
|
||||
return self._obj
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import flask
|
||||
from flask import Blueprint, request, url_for
|
||||
from flask import Blueprint, g, request, url_for
|
||||
from flask.views import View
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
|
@ -14,6 +14,8 @@ from ereuse_devicehub.inventory.forms import (
|
|||
TagDeviceForm,
|
||||
TagForm,
|
||||
TagUnnamedForm,
|
||||
TradeDocumentForm,
|
||||
TradeForm,
|
||||
UploadSnapshotForm,
|
||||
)
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
|
@ -47,6 +49,11 @@ class DeviceListMix(View):
|
|||
form_new_action = NewActionForm(lot=lot.id)
|
||||
form_new_allocate = AllocateForm(lot=lot.id)
|
||||
form_new_datawipe = DataWipeForm(lot=lot.id)
|
||||
form_new_trade = TradeForm(
|
||||
lot=lot.id,
|
||||
user_to=g.user.email,
|
||||
user_from=g.user.email,
|
||||
)
|
||||
else:
|
||||
devices = (
|
||||
Device.query.filter(Device.owner_id == current_user.id)
|
||||
|
@ -57,7 +64,7 @@ class DeviceListMix(View):
|
|||
form_new_action = NewActionForm()
|
||||
form_new_allocate = AllocateForm()
|
||||
form_new_datawipe = DataWipeForm()
|
||||
|
||||
form_new_trade = ''
|
||||
action_devices = form_new_action.devices.data
|
||||
list_devices = []
|
||||
if action_devices:
|
||||
|
@ -71,6 +78,7 @@ class DeviceListMix(View):
|
|||
'form_new_action': form_new_action,
|
||||
'form_new_allocate': form_new_allocate,
|
||||
'form_new_datawipe': form_new_datawipe,
|
||||
'form_new_trade': form_new_trade,
|
||||
'lot': lot,
|
||||
'tags': tags,
|
||||
'list_devices': list_devices,
|
||||
|
@ -323,7 +331,9 @@ class NewActionView(View):
|
|||
|
||||
if self.form.validate_on_submit():
|
||||
instance = self.form.save()
|
||||
messages.success('Action "{}" created successfully!'.format(instance.type))
|
||||
messages.success(
|
||||
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||
)
|
||||
|
||||
next_url = self.get_next_url()
|
||||
return flask.redirect(next_url)
|
||||
|
@ -346,7 +356,9 @@ class NewAllocateView(NewActionView, DeviceListMix):
|
|||
|
||||
if self.form.validate_on_submit():
|
||||
instance = self.form.save()
|
||||
messages.success('Action "{}" created successfully!'.format(instance.type))
|
||||
messages.success(
|
||||
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||
)
|
||||
|
||||
next_url = self.get_next_url()
|
||||
return flask.redirect(next_url)
|
||||
|
@ -366,7 +378,9 @@ class NewDataWipeView(NewActionView, DeviceListMix):
|
|||
|
||||
if self.form.validate_on_submit():
|
||||
instance = self.form.save()
|
||||
messages.success('Action "{}" created successfully!'.format(instance.type))
|
||||
messages.success(
|
||||
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||
)
|
||||
|
||||
next_url = self.get_next_url()
|
||||
return flask.redirect(next_url)
|
||||
|
@ -377,13 +391,61 @@ class NewDataWipeView(NewActionView, DeviceListMix):
|
|||
return flask.render_template(self.template_name, **self.context)
|
||||
|
||||
|
||||
class NewTradeView(NewActionView, DeviceListMix):
|
||||
methods = ['POST']
|
||||
form_class = TradeForm
|
||||
|
||||
def dispatch_request(self):
|
||||
self.form = self.form_class()
|
||||
|
||||
if self.form.validate_on_submit():
|
||||
instance = self.form.save()
|
||||
messages.success(
|
||||
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||
)
|
||||
|
||||
next_url = self.get_next_url()
|
||||
return flask.redirect(next_url)
|
||||
|
||||
lot_id = self.form.lot.data
|
||||
self.get_context(lot_id)
|
||||
self.context['form_new_trade'] = self.form
|
||||
return flask.render_template(self.template_name, **self.context)
|
||||
|
||||
|
||||
class NewTradeDocumentView(View):
|
||||
methods = ['POST', 'GET']
|
||||
decorators = [login_required]
|
||||
template_name = 'inventory/trade_document.html'
|
||||
form_class = TradeDocumentForm
|
||||
title = "Add new document"
|
||||
|
||||
def dispatch_request(self, lot_id):
|
||||
self.form = self.form_class(lot=lot_id)
|
||||
|
||||
if self.form.validate_on_submit():
|
||||
self.form.save()
|
||||
messages.success('Document created successfully!')
|
||||
next_url = url_for('inventory.devices.lotdevicelist', lot_id=lot_id)
|
||||
return flask.redirect(next_url)
|
||||
|
||||
return flask.render_template(
|
||||
self.template_name, form=self.form, title=self.title
|
||||
)
|
||||
|
||||
|
||||
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(
|
||||
'/action/allocate/add/', view_func=NewAllocateView.as_view('allocate_add')
|
||||
)
|
||||
devices.add_url_rule(
|
||||
'/action/datawipe/add/', view_func=NewDataWipeView.as_view('datawipe_add')
|
||||
)
|
||||
devices.add_url_rule(
|
||||
'/lot/<string:lot_id>/trade-document/add/',
|
||||
view_func=NewTradeDocumentView.as_view('trade_document_add'),
|
||||
)
|
||||
devices.add_url_rule('/device/', view_func=DeviceListView.as_view('devicelist'))
|
||||
devices.add_url_rule(
|
||||
'/device/<string:id>/', view_func=DeviceDetailView.as_view('device_details')
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
$(document).ready(function() {
|
||||
var show_allocate_form = $("#allocateModal").data('show-action-form');
|
||||
var show_datawipe_form = $("#datawipeModal").data('show-action-form');
|
||||
var show_trade_form = $("#tradeLotModal").data('show-action-form');
|
||||
if (show_allocate_form != "None") {
|
||||
$("#allocateModal .btn-primary").show();
|
||||
newAllocate(show_allocate_form);
|
||||
} else if (show_datawipe_form != "None") {
|
||||
$("#datawipeModal .btn-primary").show();
|
||||
newDataWipe(show_datawipe_form);
|
||||
} else if (show_trade_form != "None") {
|
||||
$("#tradeLotModal .btn-primary").show();
|
||||
newTrade(show_trade_form);
|
||||
} else {
|
||||
$(".deviceSelect").on("change", deviceSelect);
|
||||
}
|
||||
|
@ -15,6 +19,7 @@ $(document).ready(function() {
|
|||
|
||||
function deviceSelect() {
|
||||
var devices_count = $(".deviceSelect").filter(':checked').length;
|
||||
get_device_list();
|
||||
if (devices_count == 0) {
|
||||
$("#addingLotModal .pol").show();
|
||||
$("#addingLotModal .btn-primary").hide();
|
||||
|
@ -50,23 +55,48 @@ function deviceSelect() {
|
|||
$("#datawipeModal .btn-primary").show();
|
||||
|
||||
$("#addingTagModal .pol").hide();
|
||||
$("#addingTagModal .btn-primary").show();
|
||||
}
|
||||
}
|
||||
|
||||
function removeTag() {
|
||||
var devices = $(".deviceSelect").filter(':checked');
|
||||
var devices_id = $.map(devices, function(x) { return $(x).attr('data')});
|
||||
console.log(devices_id);
|
||||
if (devices_id.length > 0) {
|
||||
var url = "/inventory/tag/devices/"+devices_id[0]+"/del/";
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
function addTag() {
|
||||
deviceSelect();
|
||||
$("#addingTagModal").click();
|
||||
}
|
||||
|
||||
function newTrade(action) {
|
||||
var title = "Trade "
|
||||
var user_to = $("#user_to").data("email");
|
||||
var user_from = $("#user_from").data("email");
|
||||
if (action == 'user_from') {
|
||||
title = 'Trade Incoming';
|
||||
$("#user_to").attr('readonly', 'readonly');
|
||||
$("#user_from").prop('readonly', false);
|
||||
$("#user_from").val('');
|
||||
$("#user_to").val(user_to);
|
||||
} else if (action == 'user_to') {
|
||||
title = 'Trade Outgoing';
|
||||
$("#user_from").attr('readonly', 'readonly');
|
||||
$("#user_to").prop('readonly', false);
|
||||
$("#user_to").val('');
|
||||
$("#user_from").val(user_from);
|
||||
}
|
||||
$("#tradeLotModal #title-action").html(title);
|
||||
$("#activeTradeModal").click();
|
||||
}
|
||||
|
||||
function newAction(action) {
|
||||
$("#actionModal #type").val(action);
|
||||
$("#actionModal #title-action").html(action);
|
||||
get_device_list();
|
||||
deviceSelect();
|
||||
$("#activeActionModal").click();
|
||||
}
|
||||
|
@ -74,7 +104,6 @@ function newAction(action) {
|
|||
function newAllocate(action) {
|
||||
$("#allocateModal #type").val(action);
|
||||
$("#allocateModal #title-action").html(action);
|
||||
get_device_list();
|
||||
deviceSelect();
|
||||
$("#activeAllocateModal").click();
|
||||
}
|
||||
|
@ -82,7 +111,6 @@ function newAllocate(action) {
|
|||
function newDataWipe(action) {
|
||||
$("#datawipeModal #type").val(action);
|
||||
$("#datawipeModal #title-action").html(action);
|
||||
get_device_list();
|
||||
deviceSelect();
|
||||
$("#activeDatawipeModal").click();
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<input type="submit" class="btn btn-primary d-none" value="Save changes" />
|
||||
<input type="submit" class="btn btn-primary" style="display: none;" value="Save changes" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="modal fade" id="allocateModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||
data-show-action-form="{{ form_new_allocate.type.data }}">
|
||||
data-show-action-form="{{ form_new_allocate.check_valid() }}">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="modal fade" id="datawipeModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||
data-show-action-form="{{ form_new_datawipe.type.data }}">
|
||||
data-show-action-form="{{ form_new_datawipe.check_valid() }}">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
|
||||
|
|
|
@ -42,22 +42,40 @@
|
|||
<div class="card">
|
||||
<div class="card-body pt-3" style="min-height: 650px;">
|
||||
<!-- Bordered Tabs -->
|
||||
{% if lot and not lot.is_temporary %}
|
||||
<ul class="nav nav-tabs nav-tabs-bordered">
|
||||
|
||||
<li class="nav-item">
|
||||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#devices-list">Devices</button>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#trade-documents-list">Documents</button>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div class="tab-content pt-5">
|
||||
<div id="devices-list" class="tab-pane fade devices-list active show">
|
||||
|
||||
<div class="btn-group dropdown ml-1">
|
||||
{% if lot and lot.is_temporary and not lot.devices %}
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="modal" data-bs-target="#btnRemoveLots">
|
||||
<i class="bi bi-trash"></i>
|
||||
Remove Lot
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
{% else %}
|
||||
<button id="btnLots" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-folder2"></i>
|
||||
Lots
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
<span class="d-none" id="activeTradeModal" data-bs-toggle="modal" data-bs-target="#tradeLotModal"></span>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnLots">
|
||||
{% if lot and lot.is_temporary and not lot.devices %}
|
||||
<li>
|
||||
<a href="javascript:newAction('Use')" class="dropdown-item"
|
||||
data-bs-toggle="modal" data-bs-target="#btnRemoveLots">
|
||||
<i class="bi bi-trash"></i>
|
||||
Remove Lot
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="javascript:void()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addingLotModal">
|
||||
<i class="bi bi-plus"></i>
|
||||
|
@ -70,9 +88,31 @@
|
|||
Remove selected devices from a lot
|
||||
</a>
|
||||
</li>
|
||||
{% if lot.is_temporary %}
|
||||
<li>
|
||||
<a href="javascript:newTrade('user_from')" class="dropdown-item">
|
||||
<i class="bi bi-plus"></i>
|
||||
Add supplier
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:newTrade('user_to')" class="dropdown-item">
|
||||
<i class="bi bi-plus"></i>
|
||||
Add receiver
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if lot and not lot.is_temporary %}
|
||||
<li>
|
||||
<a href="{{ url_for('inventory.devices.trade_document_add', lot_id=lot.id)}}" class="dropdown-item">
|
||||
<i class="bi bi-plus"></i>
|
||||
Add new document
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="btn-group dropdown ml-1" uib-dropdown="">
|
||||
<button id="btnActions" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-plus"></i>
|
||||
|
@ -181,7 +221,7 @@
|
|||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnTags">
|
||||
<li>
|
||||
<a href="javascript:void()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addingTagModal">
|
||||
<a href="javascript:addTag()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addingTagModal">
|
||||
<i class="bi bi-plus"></i>
|
||||
Add Tag to selected Devices
|
||||
</a>
|
||||
|
@ -218,8 +258,6 @@
|
|||
|
||||
<div class="tab-content pt-2">
|
||||
|
||||
<div class="tab-pane fade show active profile-overview" id="profile-overview">
|
||||
|
||||
<h5 class="card-title">Computers</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
|
@ -267,8 +305,40 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div><!-- End Bordered Tabs -->
|
||||
</div>
|
||||
</div>
|
||||
{% if lot and not lot.is_temporary %}
|
||||
<div id="trade-documents-list" class="tab-pane fade trade-documents-list">
|
||||
<h5 class="card-title">Documents</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">File</th>
|
||||
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Uploaded on</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for doc in lot.trade.documents %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if doc.url.to_text() %}
|
||||
<a href="{{ doc.url.to_text() }}" target="_blank">{{ doc.file_name}}</a>
|
||||
{% else %}
|
||||
{{ doc.file_name}}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ doc.created.strftime('%H:%M %d-%m-%Y')}}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div><!-- End Bordered Tabs -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -282,6 +352,7 @@
|
|||
{% include "inventory/actions.html" %}
|
||||
{% include "inventory/allocate.html" %}
|
||||
{% include "inventory/data_wipe.html" %}
|
||||
{% include "inventory/trade.html" %}
|
||||
|
||||
<!-- CDN -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest"></script>
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
{% if form_new_trade %}
|
||||
<div class="modal fade" id="tradeLotModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||
data-show-action-form="{{ form_new_trade.check_valid() }}">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">New Action <span id="title-action"></span></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form action="{{ url_for('inventory.devices.trade_add') }}" method="post">
|
||||
{{ form_new_trade.csrf_token }}
|
||||
<div class="modal-body">
|
||||
{% for field in form_new_trade %}
|
||||
{% if field != form_new_trade.csrf_token %}
|
||||
{% if field == form_new_trade.devices %}
|
||||
{{ field }}
|
||||
{% elif field == form_new_trade.lot %}
|
||||
{{ field }}
|
||||
{% elif field == form_new_trade.type %}
|
||||
{{ field }}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
{{ field.label(class_="form-label") }}
|
||||
{% if field == form_new_trade.confirm %}
|
||||
<div class="form-check form-switch">
|
||||
{{ field(class_="form-check-input") }}
|
||||
<small class="text-muted">{{ field.description }}</small>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ field(class_="form-control") }}
|
||||
<small class="text-muted">{{ field.description }}</small>
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<p class="text-danger">
|
||||
{% for error in field.errors %}
|
||||
{{ error }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<input type="submit" class="btn btn-primary" value="Create" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="modal fade" id="tradeLotModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||
data-show-action-form="None">
|
||||
</div>
|
||||
{% endif %}
|
|
@ -0,0 +1,65 @@
|
|||
{% 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 -->
|
||||
<li class="breadcrumb-item"><a href="#TODO-lot-list">Lots</a></li>
|
||||
<li class="breadcrumb-item">Trade Document</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section profile">
|
||||
<div class="row">
|
||||
<div class="col-xl-4">
|
||||
|
||||
<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>
|
||||
{% if form.form_errors %}
|
||||
<p class="text-danger">
|
||||
{% for error in form.form_errors %}
|
||||
{{ error }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form action="{{ url_for('inventory.devices.trade_document_add', lot_id=form._lot.id) }}" method="post"
|
||||
class="row g-3 needs-validation" enctype="multipart/form-data">
|
||||
{{ form.csrf_token }}
|
||||
{% for field in form %}
|
||||
{% if field != form.csrf_token %}
|
||||
<div class="col-12">
|
||||
{{ field.label(class_="form-label") }}
|
||||
{{ field }}
|
||||
<small class="text-muted">{{ field.description }}</small>
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<p class="text-danger">
|
||||
{% for error in field.errors %}
|
||||
{{ error }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="col-12">
|
||||
<a href="{{ url_for('inventory.devices.lotdevicelist', lot_id=form._lot.id) }}" class="btn btn-danger">Cancel</a>
|
||||
<button class="btn btn-primary" type="submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock main %}
|
Reference in New Issue