diff --git a/.github/workflows/flask.yml b/.github/workflows/flask.yml index 151cc26d..328533a8 100644 --- a/.github/workflows/flask.yml +++ b/.github/workflows/flask.yml @@ -47,9 +47,6 @@ jobs: sudo apt-get update -qy sudo apt-get -y install postgresql-client python -m pip install --upgrade pip - pip install virtualenv - virtualenv env - source env/bin/activate pip install flake8 pytest coverage pip install -r requirements.txt @@ -65,6 +62,17 @@ jobs: psql -h "localhost" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "CREATE EXTENSION citext SCHEMA public;" psql -h "localhost" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "CREATE EXTENSION pg_trgm SCHEMA public;" + - name: Lint with flake8 + run: | + # stop the build if: + # - E9,F63,F7,F82: Python syntax errors or undefined names + # - E501: line longer than 120 characters + # - C901: complexity greater than 10 + # - F401: modules imported but unused + # See: https://flake8.pycqa.org/en/latest/user/error-codes.html + flake8 . --select=E9,F63,F7,F82,E501,C901,F401 + flake8 . --exit-zero + - name: Run Tests run: | export SECRET_KEY=`python3 -c 'import secrets; print(secrets.token_hex())'` diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index e9430e63..8959ae93 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -1,28 +1,29 @@ +import copy import json from json.decoder import JSONDecodeError -from flask import g, request -from flask_wtf import FlaskForm -from sqlalchemy.util import OrderedSet -from wtforms import (DateField, FloatField, HiddenField, IntegerField, - MultipleFileField, SelectField, StringField, - TextAreaField, validators) - +from boltons.urlutils import URL from ereuse_devicehub.db import db -from ereuse_devicehub.resources.action.models import (Action, RateComputer, - Snapshot, VisualTest) +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.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) + 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.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.user.exceptions import InsufficientPermission @@ -472,10 +473,18 @@ class TagDeviceForm(FlaskForm): class NewActionForm(FlaskForm): - name = StringField(u'Name', [validators.length(max=50)]) + name = StringField(u'Name', [validators.length(max=50)], + description="A name or title of the event. Something to look for.") devices = HiddenField() - date = DateField(u'Date', validators=(validators.Optional(),)) - severity = SelectField(u'Severity', choices=[(v.name, v.name) for v in Severity]) + date = DateField(u'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], + description="""An indicator that evaluates the execution of the event. + For example, failed events are set to Error""") description = TextAreaField(u'Description') lot = HiddenField() type = HiddenField() @@ -537,3 +546,71 @@ class AllocateForm(NewActionForm): is_valid = False return is_valid + + +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()], + 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 validate(self, extra_validators=None): + is_valid = super().validate(extra_validators) + + 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 = DataWipeDocument( + document_type='DataWipeDocument', + ) + self.populate_obj(self._obj) + self._obj.file_name = file_name + self._obj.file_hash = file_hash + db.session.add(self._obj) + if commit: + db.session.commit() + + return self._obj + + +class DataWipeForm(NewActionForm): + document = FormField(DataWipeDocumentForm) + + def save(self): + self.document.form.save(commit=False) + + Model = db.Model._decl_class_registry.data[self.type.data]() + self.instance = Model() + devices = self.devices.data + severity = self.severity.data + self.devices.data = self._devices + self.severity.data = Severity[self.severity.data] + + document = copy.copy(self.document) + del self.document + self.populate_obj(self.instance) + self.instance.document = document.form._obj + db.session.add(self.instance) + db.session.commit() + + self.devices.data = devices + self.severity.data = severity + self.document = document + + return self.instance diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index bb809ec7..1eae4de2 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -8,7 +8,7 @@ from ereuse_devicehub.inventory.forms import (AllocateForm, LotDeviceForm, LotForm, NewActionForm, NewDeviceForm, TagDeviceForm, TagForm, TagUnnamedForm, - UploadSnapshotForm) + UploadSnapshotForm, DataWipeForm) from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.tag.model import Tag @@ -36,6 +36,7 @@ class DeviceListMix(View): devices = sorted(devices, key=lambda x: x.updated, reverse=True) form_new_action = NewActionForm(lot=lot.id) form_new_allocate = AllocateForm(lot=lot.id) + form_new_datawipe = DataWipeForm(lot=lot.id) else: devices = Device.query.filter( Device.owner_id == current_user.id).filter( @@ -43,6 +44,7 @@ class DeviceListMix(View): Device.updated.desc()) form_new_action = NewActionForm() form_new_allocate = AllocateForm() + form_new_datawipe = DataWipeForm() action_devices = form_new_action.devices.data list_devices = [] @@ -56,6 +58,7 @@ class DeviceListMix(View): 'form_tag_device': TagDeviceForm(), 'form_new_action': form_new_action, 'form_new_allocate': form_new_allocate, + 'form_new_datawipe': form_new_datawipe, 'lot': lot, 'tags': tags, 'list_devices': list_devices @@ -331,8 +334,29 @@ class NewAllocateView(NewActionView, DeviceListMix): return flask.render_template(self.template_name, **self.context) +class NewDataWipeView(NewActionView, DeviceListMix): + methods = ['POST'] + form_class = DataWipeForm + + 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(instance.type)) + + 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_datawipe'] = self.form + return flask.render_template(self.template_name, **self.context) + + devices.add_url_rule('/action/add/', view_func=NewActionView.as_view('action_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('/device/', view_func=DeviceListView.as_view('devicelist')) devices.add_url_rule('/device//', view_func=DeviceDetailView.as_view('device_details')) devices.add_url_rule('/lot//device/', view_func=DeviceListView.as_view('lotdevicelist')) diff --git a/ereuse_devicehub/resources/hash_reports.py b/ereuse_devicehub/resources/hash_reports.py index f44a79be..1cb7f491 100644 --- a/ereuse_devicehub/resources/hash_reports.py +++ b/ereuse_devicehub/resources/hash_reports.py @@ -26,12 +26,15 @@ class ReportHash(db.Model): hash3.comment = """The normalized name of the hash.""" -def insert_hash(bfile): +def insert_hash(bfile, commit=True): hash3 = hashlib.sha3_256(bfile).hexdigest() db_hash = ReportHash(hash3=hash3) db.session.add(db_hash) - db.session.commit() - db.session.flush() + if commit: + db.session.commit() + db.session.flush() + + return hash3 def verify_hash(bfile): diff --git a/ereuse_devicehub/static/js/main_inventory.js b/ereuse_devicehub/static/js/main_inventory.js index 05833fe5..1eb680fd 100644 --- a/ereuse_devicehub/static/js/main_inventory.js +++ b/ereuse_devicehub/static/js/main_inventory.js @@ -1,8 +1,12 @@ $(document).ready(function() { - var show_action_form = $("#allocateModal").data('show-action-form'); - if (show_action_form != "None") { + var show_allocate_form = $("#allocateModal").data('show-action-form'); + var show_datawipe_form = $("#datawipeModal").data('show-action-form'); + if (show_allocate_form != "None") { $("#allocateModal .btn-primary").show(); - newAllocate(show_action_form); + newAllocate(show_allocate_form); + } else if (show_datawipe_form != "None") { + $("#datawipeModal .btn-primary").show(); + newDataWipe(show_datawipe_form); } else { $(".deviceSelect").on("change", deviceSelect); } @@ -26,6 +30,9 @@ function deviceSelect() { $("#allocateModal .pol").show(); $("#allocateModal .btn-primary").hide(); + + $("#datawipeModal .pol").show(); + $("#datawipeModal .btn-primary").hide(); } else { $("#addingLotModal .pol").hide(); $("#addingLotModal .btn-primary").show(); @@ -39,6 +46,9 @@ function deviceSelect() { $("#allocateModal .pol").hide(); $("#allocateModal .btn-primary").show(); + $("#datawipeModal .pol").hide(); + $("#datawipeModal .btn-primary").show(); + $("#addingTagModal .pol").hide(); } } @@ -69,11 +79,20 @@ function newAllocate(action) { $("#activeAllocateModal").click(); } +function newDataWipe(action) { + $("#datawipeModal #type").val(action); + $("#datawipeModal #title-action").html(action); + get_device_list(); + deviceSelect(); + $("#activeDatawipeModal").click(); +} + function get_device_list() { var devices = $(".deviceSelect").filter(':checked'); /* Insert the correct count of devices in actions form */ var devices_count = devices.length; + $("#datawipeModal .devices-count").html(devices_count); $("#allocateModal .devices-count").html(devices_count); $("#actionModal .devices-count").html(devices_count); diff --git a/ereuse_devicehub/templates/inventory/data_wipe.html b/ereuse_devicehub/templates/inventory/data_wipe.html new file mode 100644 index 00000000..a44130ec --- /dev/null +++ b/ereuse_devicehub/templates/inventory/data_wipe.html @@ -0,0 +1,85 @@ + diff --git a/ereuse_devicehub/templates/inventory/device_list.html b/ereuse_devicehub/templates/inventory/device_list.html index 97291791..a165e387 100644 --- a/ereuse_devicehub/templates/inventory/device_list.html +++ b/ereuse_devicehub/templates/inventory/device_list.html @@ -80,6 +80,7 @@ +