Merge pull request #205 from eReuse/feature/server-side-render-UX-improvements

Feature/server side render ux improvements
This commit is contained in:
Santiago L 2022-03-07 18:34:42 +01:00 committed by GitHub
commit 7000b58c6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 167 additions and 98 deletions

View File

@ -1,8 +1,8 @@
import os
import click.testing
import ereuse_utils
import flask.cli
import ereuse_utils
from ereuse_devicehub.config import DevicehubConfig
from ereuse_devicehub.devicehub import Devicehub

View File

@ -1,7 +1,6 @@
import copy
import json
from json.decoder import JSONDecodeError
from boltons.urlutils import URL
from flask import g, request
from flask_wtf import FlaskForm
@ -63,7 +62,7 @@ class LotDeviceForm(FlaskForm):
return bool(self._devices)
def save(self):
def save(self, commit=True):
trade = self._lot.trade
if trade:
for dev in self._devices:
@ -73,12 +72,16 @@ class LotDeviceForm(FlaskForm):
if self._devices:
self._lot.devices.update(self._devices)
db.session.add(self._lot)
if commit:
db.session.commit()
def remove(self):
def remove(self, commit=True):
if self._devices:
self._lot.devices.difference_update(self._devices)
db.session.add(self._lot)
if commit:
db.session.commit()
@ -114,7 +117,7 @@ class LotForm(FlaskForm):
return self.id
def remove(self):
if self.instance and not self.instance.devices:
if self.instance and not self.instance.trade:
self.instance.delete()
db.session.commit()
return self.instance
@ -465,9 +468,9 @@ class TagDeviceForm(FlaskForm):
if self.delete:
tags = Tag.query.filter(Tag.owner_id == g.user.id).filter_by(
device_id=self.device_id
)
).order_by(Tag.id)
else:
tags = Tag.query.filter(Tag.owner_id == g.user.id).filter_by(device_id=None)
tags = Tag.query.filter(Tag.owner_id == g.user.id).filter_by(device_id=None).order_by(Tag.id)
self.tag.choices = [(tag.id, tag.id) for tag in tags]

View File

@ -1,13 +1,15 @@
import csv
import logging
from io import StringIO
import flask
import flask_weasyprint
from flask import Blueprint, g, make_response, request, url_for
from flask import Blueprint, g, make_response, request, url_for, app
from flask.views import View
from flask_login import current_user, login_required
from werkzeug.exceptions import NotFound
from sqlalchemy import or_
from requests.exceptions import ConnectionError
from ereuse_devicehub import messages
from ereuse_devicehub.inventory.forms import (
@ -30,10 +32,13 @@ from ereuse_devicehub.resources.documents.device_row import ActionRow, DeviceRow
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.db import db
# TODO(@slamora): rename base 'inventory.devices' --> 'inventory'
devices = Blueprint('inventory.devices', __name__, url_prefix='/inventory')
logger = logging.getLogger(__name__)
class DeviceListMix(View):
decorators = [login_required]
@ -51,7 +56,7 @@ class DeviceListMix(View):
tags = (
Tag.query.filter(Tag.owner_id == current_user.id)
.filter(Tag.device_id.is_(None))
.order_by(Tag.created.desc())
.order_by(Tag.id.asc())
)
if lot_id:
@ -134,10 +139,11 @@ class LotDeviceAddView(View):
def dispatch_request(self):
form = LotDeviceForm()
if form.validate_on_submit():
form.save()
form.save(commit=False)
messages.success(
'Add devices to lot "{}" successfully!'.format(form._lot.name)
)
db.session.commit()
else:
messages.error('Error adding devices to lot!')
@ -153,10 +159,11 @@ class LotDeviceDeleteView(View):
def dispatch_request(self):
form = LotDeviceForm()
if form.validate_on_submit():
form.remove()
form.remove(commit=False)
messages.success(
'Remove devices from lot "{}" successfully!'.format(form._lot.name)
)
db.session.commit()
else:
messages.error('Error removing devices from lot!')
@ -207,6 +214,12 @@ class LotDeleteView(View):
def dispatch_request(self, id):
form = LotForm(id=id)
if form.instance.trade:
msg = "Sorry, the lot cannot be deleted because have a trade action "
messages.error(msg)
next_url = url_for('inventory.devices.lotdevicelist', lot_id=id)
return flask.redirect(next_url)
form.remove()
next_url = url_for('inventory.devices.devicelist')
return flask.redirect(next_url)
@ -251,7 +264,7 @@ class TagListView(View):
def dispatch_request(self):
lots = Lot.query.filter(Lot.owner_id == current_user.id)
tags = Tag.query.filter(Tag.owner_id == current_user.id)
tags = Tag.query.filter(Tag.owner_id == current_user.id).order_by(Tag.id)
context = {
'lots': lots,
'tags': tags,
@ -287,7 +300,14 @@ class TagAddUnnamedView(View):
context = {'page_title': 'New Unnamed Tag', 'lots': lots}
form = TagUnnamedForm()
if form.validate_on_submit():
form.save()
try:
form.save()
except ConnectionError as e:
logger.error("Error while trying to connect to tag server: {}".format(e))
msg = ("Sorry, we cannot create the unnamed tags requested because "
"some error happens while connecting to the tag server!")
messages.error(msg)
next_url = url_for('inventory.devices.taglist')
return flask.redirect(next_url)

View File

@ -474,6 +474,13 @@ class Device(Thing):
key=attrgetter('type')) # last test of each type
return self._warning_actions(current_tests)
@property
def verbose_name(self):
type = self.type or ''
manufacturer = self.manufacturer or ''
model = self.model or ''
return f'{type} {manufacturer} {model}'
@declared_attr
def __mapper_args__(cls):
"""Defines inheritance.

View File

@ -59,18 +59,39 @@ function deviceSelect() {
}
}
function removeLot() {
var devices = $(".deviceSelect");
if (devices.length > 0) {
$("#btnRemoveLots .text-danger").show();
} else {
$("#btnRemoveLots .text-danger").hide();
}
$("#activeRemoveLotModal").click();
}
function removeTag() {
var devices = $(".deviceSelect").filter(':checked');
var devices_id = $.map(devices, function(x) { return $(x).attr('data')});
if (devices_id.length > 0) {
if (devices_id.length == 1) {
var url = "/inventory/tag/devices/"+devices_id[0]+"/del/";
window.location.href = url;
} else {
$("#unlinkTagAlertModal").click();
}
}
function addTag() {
deviceSelect();
$("#addingTagModal").click();
var devices = $(".deviceSelect").filter(':checked');
var devices_id = $.map(devices, function(x) { return $(x).attr('data')});
if (devices_id.length == 1) {
$("#addingTagModal .pol").hide();
$("#addingTagModal .btn-primary").show();
} else {
$("#addingTagModal .pol").show();
$("#addingTagModal .btn-primary").hide();
}
$("#addTagAlertModal").click();
}
function newTrade(action) {

View File

@ -65,5 +65,5 @@ function printpdf() {
min_tag_side = (Math.min(height, width)/2) + border;
pdf.addImage(imgData, 'PNG', border, border, img_side, img_side);
pdf.text(tag, max_tag_side, min_tag_side);
pdf.save('Tags.pdf');
pdf.save('Tag_'+tag+'.pdf');
}

View File

@ -17,9 +17,14 @@
<link href="https://fonts.gstatic.com" rel="preconnect">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Nunito:300,300i,400,400i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i" rel="stylesheet">
<!-- JS Files -->
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest"></script>
<!-- Vendor CSS Files -->
<link href="{{ url_for('static', filename='vendor/bootstrap/css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='vendor/bootstrap-icons/bootstrap-icons.css') }}" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" type="text/css">
<!-- Template Main CSS File -->

View File

@ -18,7 +18,7 @@
</select>
<input class="devicesList" type="hidden" name="device" />
<p class="text-danger pol">
You need select first some device for adding this in a tag
You need select first one device and only one for add this in a tag
</p>
</div>

View File

@ -0,0 +1,21 @@
<div class="modal fade" id="unlinkTagErrorModal" tabindex="-1" style="display: none;" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Error Unlink Tag</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p class="text-danger pol">
You need select first one device and only one for Unlink one Tag
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

View File

@ -31,7 +31,7 @@
<form method="post" class="row g-3 needs-validation" novalidate>
{{ form.csrf_token }}
<div class="col-12">
<div>
<div class="form-group has-validation mb-2">
<label for="name" class="form-label">Type *</label>
<select id="type" class="form-control" name="type" required="">
@ -369,7 +369,7 @@
</div>
<div class="col-12">
<div>
<a href="{{ url_for('inventory.devices.devicelist') }}" class="btn btn-danger">Cancel</a>
<button class="btn btn-primary" type="submit">Save</button>
</div>
@ -385,6 +385,5 @@
</div>
</div>
</section>
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/create_device.js') }}"></script>
{% endblock main %}

View File

@ -1,6 +1,5 @@
{% extends "ereuse_devicehub/base_site.html" %}
{% block main %}
<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" type="text/css">
<div class="pagetitle">
<h1>Inventory</h1>

View File

@ -1,6 +1,5 @@
{% extends "ereuse_devicehub/base_site.html" %}
{% block main %}
<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" type="text/css">
<div class="pagetitle">
<h1>Inventory</h1>
@ -33,9 +32,24 @@
<div class="card-body pt-3">
<!-- Bordered Tabs -->
<div class="col-12">
<h3><a href="{{ url_for('inventory.devices.lot_edit', id=lot.id) }}">{{ lot.name }}</a></h3>
<div class="d-flex align-items-center justify-content-between">
<h3><a href="{{ url_for('inventory.devices.lot_edit', id=lot.id) }}">{{ lot.name }}</a></h3>
<div><!-- lot actions -->
{% if lot.is_temporary %}
<span class="d-none" id="activeRemoveLotModal" data-bs-toggle="modal" data-bs-target="#btnRemoveLots"></span>
<a class="me-2" href="javascript:removeLot()">
<i class="bi bi-trash"></i> Remove Lot
</a>
<a class="me-2" href="javascript:newTrade('user_from')">
<i class="bi bi-arrow-down-right"></i> Add supplier
</a>
<a href="javascript:newTrade('user_to')">
<i class="bi bi-arrow-up-right"></i> Add receiver
</a>
{% endif %}
</div>
</div>
</div>
</div>
{% endif %}
@ -66,16 +80,6 @@
</button>
<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>
@ -88,29 +92,6 @@
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="">
@ -239,17 +220,19 @@
<i class="bi bi-tag"></i>
Tags
</button>
<span class="d-none" id="unlinkTagAlertModal" data-bs-toggle="modal" data-bs-target="#unlinkTagErrorModal"></span>
<span class="d-none" id="addTagAlertModal" data-bs-toggle="modal" data-bs-target="#addingTagModal"></span>
<ul class="dropdown-menu" aria-labelledby="btnTags">
<li>
<a href="javascript:addTag()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addingTagModal">
<a href="javascript:addTag()" class="dropdown-item">
<i class="bi bi-plus"></i>
Add Tag to selected Devices
Add Tag to selected Device
</a>
</li>
<li>
<a href="javascript:removeTag()" class="dropdown-item">
<i class="bi bi-x"></i>
Remove Tag from selected Devices
Remove Tag from selected Device
</a>
</li>
</ul>
@ -257,7 +240,7 @@
<div class="btn-group dropdown ml-1" uib-dropdown="">
<button id="btnSnapshot" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-tag"></i>
<i class="bi bi-laptop"></i>
New Device
</button>
<ul class="dropdown-menu" aria-labelledby="btnSnapshot">
@ -276,6 +259,24 @@
</ul>
</div>
{% if lot and not lot.is_temporary %}
<div class="btn-group dropdown ml-1" uib-dropdown="">
<button id="btnSnapshot" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-book"></i>
Documents
</button>
<ul class="dropdown-menu" aria-labelledby="btnSnapshot">
<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>
</ul>
</div>
{% endif %}
<div class="tab-content pt-2">
<h5 class="card-title">Computers</h5>
@ -304,7 +305,7 @@
</td>
<td>
<a href="{{ url_for('inventory.devices.device_details', id=dev.devicehub_id)}}">
{{ dev.type }} {{ dev.manufacturer }} {{ dev.model }}
{{ dev.verbose_name }}
</a>
</td>
<td>
@ -374,16 +375,11 @@
{% include "inventory/data_wipe.html" %}
{% include "inventory/trade.html" %}
{% include "inventory/alert_export_error.html" %}
{% include "inventory/alert_unlink_tag_error.html" %}
<!-- CDN -->
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest"></script>
<!-- Custom Code -->
<script>
const table = new simpleDatatables.DataTable("table")
</script>
<script>
</script>
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/main_inventory.js') }}"></script>
{% endblock main %}

View File

@ -34,7 +34,7 @@
<form method="post" class="row g-3 needs-validation" novalidate>
{{ form.csrf_token }}
<div class="col-12">
<div>
<label for="name" class="form-label">Name</label>
<div class="input-group has-validation">
<input type="text" name="name" class="form-control" required value="{{ form.name.data|default('', true) }}">
@ -42,8 +42,12 @@
</div>
</div>
<div class="col-12">
<a href="{{ url_for('inventory.devices.devicelist') }}" class="btn btn-danger">Cancel</a>
<div>
{% if form.id %}
<a href="{{ url_for('inventory.devices.lotdevicelist', lot_id=form.id) }}" class="btn btn-danger">Cancel</a>
{% else %}
<a href="{{ url_for('inventory.devices.devicelist') }}" class="btn btn-danger">Cancel</a>
{% endif %}
<button class="btn btn-primary" type="submit">Save</button>
</div>
</form>

View File

@ -9,12 +9,15 @@
<div class="modal-body">
Are you sure that you want to remove lot <strong>{{ lot.name }}</strong>?
<p class="text-danger">
There are devices in this lot, are you sure you want to delete it?
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<a href="{{ url_for('inventory.devices.lot_del', id=lot.id)}}" type="button" class="btn btn-primary">
Save changes
Confirm
</a>
</div>

View File

@ -33,7 +33,7 @@
<form method="post" class="row g-3 needs-validation" novalidate>
{{ form.csrf_token }}
<div class="col-12">
<div>
<label for="code" class="form-label">code</label>
<div class="input-group has-validation">
<input type="text" name="code" class="form-control" required value="{{ form.code.data|default('', true) }}">
@ -48,7 +48,7 @@
{% endif %}
</div>
<div class="col-12">
<div>
<a href="{{ url_for('inventory.devices.taglist') }}" class="btn btn-danger">Cancel</a>
<button class="btn btn-primary" type="submit">Save</button>
</div>

View File

@ -33,7 +33,7 @@
<form method="post" class="row g-3 needs-validation" novalidate>
{{ form.csrf_token }}
<div class="col-12">
<div>
<label for="code" class="form-label">Amount</label>
<div class="input-group has-validation">
{{ form.amount(class_="form-control") }}
@ -48,7 +48,7 @@
{% endif %}
</div>
<div class="col-12">
<div>
<a href="{{ url_for('inventory.devices.taglist') }}" class="btn btn-danger">Cancel</a>
<button class="btn btn-primary" type="submit">Save</button>
</div>

View File

@ -1,6 +1,5 @@
{% extends "ereuse_devicehub/base_site.html" %}
{% block main %}
<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" type="text/css">
<div class="pagetitle">
<h1>Inventory</h1>
@ -35,7 +34,7 @@
<div class="col-lg-9 col-md-8">
{% if tag.device %}
<a href="{{url_for('inventory.devices.device_details', id=tag.device.devicehub_id)}}">
{{ tag.device.manufacturer }} {{ tag.device.model }}
{{ tag.device.verbose_name }}
</a>
{% endif %}
</div>
@ -106,7 +105,6 @@
</div>
</div>
</section>
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/qrcode.js') }}"></script>
<script src="{{ url_for('static', filename='js/jspdf.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/print.pdf.js') }}"></script>

View File

@ -1,6 +1,5 @@
{% extends "ereuse_devicehub/base_site.html" %}
{% block main %}
<link href="https://cdn.jsdelivr.net/npm/simple-datatables@latest/dist/style.css" rel="stylesheet" type="text/css">
<div class="pagetitle">
<h1>Inventory</h1>
@ -59,7 +58,7 @@
<td>
{% if tag.device %}
<a href={{ url_for('inventory.devices.device_details', id=tag.device.devicehub_id)}}>
{{ tag.device.type }} {{ tag.device.manufacturer }} {{ tag.device.model }}
{{ tag.device.verbose_name }}
</a>
{% endif %}
</td>
@ -77,14 +76,8 @@
</div>
</section>
<!-- CDN -->
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest"></script>
<!-- Custom Code -->
<script>
const table = new simpleDatatables.DataTable("table")
</script>
<script>
</script>
<script src="{{ url_for('static', filename='js/jquery-3.6.0.min.js') }}"></script>
{% endblock main %}

View File

@ -32,7 +32,7 @@
<form method="post" class="row g-3 needs-validation" novalidate>
{{ form.csrf_token }}
<div class="col-12">
<div>
<label for="tag" class="form-label">Tag</label>
<div class="input-group has-validation">
{{ form.tag(class_="form-control") }}
@ -48,9 +48,9 @@
</div>
<input class="devicesList" type="hidden" name="device" />
<div class="col-12">
<div>
<a href="{{ referrer }}" class="btn btn-danger">Cancel</a>
<button class="btn btn-primary" type="submit">Save</button>
<button class="btn btn-primary" type="submit">Unlink</button>
</div>
</form>

View File

@ -35,22 +35,22 @@
{{ form.csrf_token }}
{% for field in form %}
{% if field != form.csrf_token %}
<div class="col-12">
<div>
{{ field.label(class_="form-label") }}
{{ field }}
<small class="text-muted">{{ field.description }}</small>
{% endif %}
{% if field.errors %}
{% if field.errors %}
<p class="text-danger">
{% for error in field.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
{% endif %}
</div>
{% endif %}
{% endfor %}
<div class="col-12">
<div>
<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>

View File

@ -33,7 +33,7 @@
<form method="post" enctype="multipart/form-data" class="row g-3 needs-validation" novalidate>
{{ form.csrf_token }}
<div class="col-12">
<div>
<label for="name" class="form-label">Select a Snapshot file</label>
<div class="input-group has-validation">
{{ form.snapshot }}
@ -54,7 +54,7 @@
{% endif %}
</div>
<div class="col-12">
<div>
<a href="{{ url_for('inventory.devices.devicelist') }}" class="btn btn-danger">Cancel</a>
<button class="btn btn-primary" type="submit">Send</button>
</div>