Adding Jquery for dynamic attributes

This commit is contained in:
Julian Knauer 2020-09-28 21:32:36 +02:00
parent f1f9e7fbcd
commit 69fe557259
12 changed files with 257 additions and 78 deletions

3
config.py Normal file
View File

@ -0,0 +1,3 @@
SECRET_KEY = 'Please do not guess or brute-force me!'
SQLALCHEMY_DATABASE_URI = 'sqlite:////homebench.sqlite'
SQLALCHEMY_TRACK_MODIFICATIONS = False

View File

@ -7,11 +7,16 @@ from flask_nav.elements import Navbar, View
from homebench import db, nav
from homebench.parts.models import Part, PartType, Package, Datasheet
from homebench.parts.models import (
Part, PartType, Package, Datasheet, PartAttribute
)
from homebench.parts.forms import (
PartForm, PartTypeForm, RestockForm, CheckoutForm
)
# from yaml import CLoader as Loader, load as yamload
topbar = Navbar(
'',
View('Overview', 'parts.index'),
@ -57,7 +62,7 @@ def create():
else:
datasheet_data = form.datasheet.data
part = Part(name=form.name.data,
description=form.description.data,
attributes=form.attributes.data,
value=form.value.data,
stock=form.stock.data,
package_id=form.package.data,
@ -129,6 +134,8 @@ def update(part_id):
flash('Requested part not found.')
return redirect(url_for('parts.index'))
# yaml_attributes = yamload(part.description, Loader=Loader)
form = PartForm(obj=part)
form.parttype.choices = [(0, 'Select')]
form.parttype.choices += [(_.id, _.label) for _ in PartType.query.all()]
@ -137,7 +144,15 @@ def update(part_id):
form.datasheet.choices = [(0, 'Select')]
form.datasheet.choices += [(_.id, _.label) for _ in Datasheet.query.all()]
print(form.attributes)
# attribute_fields = [(_.name, _.unit) for _ in PartAttribute.query.all()]
# print(attribute_fields)
if request.method == 'POST' and form.validate():
# for attr in form.attributes.data:
# new_attribute = PartAttribute(**attr)
# part.attributes.append(new_attribute)
part.name = form.name.data
part.description = form.description.data
part.value = form.value.data

View File

@ -1,14 +1,23 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileField
from wtforms import (
StringField, TextAreaField, IntegerField, SubmitField, SelectField
StringField, TextAreaField, IntegerField, SubmitField, SelectField,
FieldList, FormField, Form
)
from wtforms.validators import DataRequired, InputRequired
class AttributeForm(Form):
attribute = StringField('Attribute', validators=[DataRequired()])
value = StringField('Value', validators=[DataRequired()])
unit = StringField('Unit', validators=[DataRequired()])
class PartForm(FlaskForm):
name = StringField('Name')
description = TextAreaField('Description', validators=[DataRequired()])
attributes = FieldList(FormField(AttributeForm), 'Attribute',
min_entries=1)
value = StringField('Value')
parttype = SelectField('Type', validators=[DataRequired()], coerce=int)
package = SelectField('Package', validators=[DataRequired()], coerce=int)
@ -41,8 +50,3 @@ class RestockForm(FlaskForm):
class CheckoutForm(FlaskForm):
stock = IntegerField('Stock #', validators=[InputRequired()])
submit = SubmitField('Checkout')
class AttributeForm(FlaskForm):
attribute = StringField('Attribute', validators=[DataRequired()])
value = StringField('Value', validators=[DataRequired()])

View File

@ -8,7 +8,7 @@ class Part(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=True)
description = db.Column(db.String(300), nullable=False)
description = db.Column(db.String(600), nullable=False)
value = db.Column(db.String(20), nullable=True)
stock = db.Column(db.Integer, nullable=False, default=0)
last_update = db.Column(db.DateTime, nullable=False,
@ -46,19 +46,22 @@ class PartAttribute(db.Model):
__tablename__ = 'attributes'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
value = db.Column(db.Integer, nullable=False)
part = db.relationship('Part')
part_id = db.Column(db.Integer, db.ForeignKey('parts.id'),
nullable=False)
part_id = db.Column(db.Integer, db.ForeignKey('parts.id'))
attribute = db.Column(db.String(60), nullable=False)
value = db.Column(db.String(60), nullable=True)
unit = db.Column(db.String(60), nullable=True)
part = db.relationship(
'Part',
backref=db.backref('attributes', lazy='dynamic', collection_class=list)
)
@property
def serialize(self):
return {
'id': self.id,
'name': self.name,
'value': self.value
'attribute': self.attribute,
'value': self.value,
'unit': self.unit
}

View File

@ -77,3 +77,81 @@ function populateSelect(el, what, presel) {
}
});
}
/*
* Attribute management
*/
function attributesAdjustIndices(removedIndex) {
var $forms = $('.attr-subform');
$forms.each(function(i) {
var $form = $(this);
var index = parseInt($form.data('index'));
var newIndex = index - 1;
if (index < removedIndex) {
return true;
}
$form.attr('id', $form.attr('id').replace(index, newIndex));
$form.data('index', newIndex);
$form.find('input').each(function(j) {
var $item = $(this);
$item.attr('id', $item.attr('id').replace(index, newIndex));
$item.attr('name', $item.attr('name').replace(index, newIndex));
});
});
}
function attributesRemoveForm() {
var $removedForm = $(this).closest('.attr-subform');
var removedIndex = parseInt($removedForm.data('index'));
$removedForm.remove();
attributesAdjustIndices(removedIndex);
}
function attributesAddForm() {
var $templateForm = $('#attr-_-form');
if (!$templateForm) {
console.log('[ERROR] Connot find part attribute template');
return;
}
var $lastForm = $('.attr-subform').last();
var newIndex = 0;
if ($lastForm.length > 0) {
newIndex = parseInt($lastForm.data('index')) + 1;
}
// Uncomment if needed
/*if (newIndex > 20) {
console.log('[WARNING] Reached maximum number of attributes per part');
return;
}*/
var $newForm = $templateForm.clone();
$newForm.attr('id', $newForm.attr('id').replace('_', newIndex));
$newForm.data('index', newIndex);
$newForm.find('input').each(function(idx) {
var $item = $(this);
$item.attr('id', $item.attr('id').replace('_', newIndex));
$item.attr('name', $item.attr('name').replace('_', newIndex));
});
$('#attr-subform-container').append($newForm);
$newForm.addClass('attr-subform');
$newForm.removeClass('is-hidden');
//$newForm.find('.attribute-remove').click('attributesRemoveForm');
}
function attributesSaveForm() {
var $saveForm = $(this).closest('.attr-subform');
var saveIndex = parseInt($saveForm.data('index'));
}

View File

@ -28,7 +28,7 @@
} else if (filter.startsWith("DESC:")) {
filter_td = 4;
filter = filter.substr(5, filter.length).trim();
} else if (filter.startsWith("DESCRIPTION:")) {
} else if (filter.startsWith("ATTR:")) {
filter_td = 4;
filter = filter.substr(12, filter.length).trim();
} else if (filter.startsWith("VALUE:")) {
@ -65,7 +65,7 @@ function sortTable(column) {
col_id = 2;
else if (column === "package")
col_id = 3;
else if (column === "description")
else if (column === "Description")
col_id = 4;
else if (column === "value")
col_id = 5;

View File

@ -12,6 +12,7 @@
{{ nav.top.render(renderer='anchors') }}
{% endblock %}
{% block content %}
<form class="pa4 black-80" action="" method="post" novalidate>
{{ form.csrf_token }}
@ -37,6 +38,37 @@
{% endfor %}
</div>
<div id='attr-subform-container'>
<a id="attribute-add" class="f6 link dim ph3 pv2 mb2 dib white bg-blue" href="#">Add Attribute</a>
{% for subform in form.attributes %}
<div id="attr-{{ loop.index0 }}-form" class="attr-subform" data-index="{{ loop.index0 }}">
{{ subform.attribute.label }}
{{ subform.attribute }}
{{ subform.value.label }}
{{ subform.value }}
{{ subform.unit.label }}
{{ subform.unit }}
<a class="f6 link dim ph3 pv2 mb2 dib white bg-red removeAttribute">Remove</a>
</div>
{% endfor %}
<!-- Template -->
<div id="attr-_-form" class="is-hidden" data-index="_">
<label for="attributes-_-attribute">Attribute</label>
<input id="attributes-_-attribute" name="attributes-_-attribute" required="" type="text" value="">
<label for="attributes-_-value">Value</label>
<input id="attributes-_-value" name="attributes-_-value" required="" type="text" value="">
<label for="attributes-_-unit">Unit</label>
<input id="attributes-_-unit" name="attributes-_-unit" required="" type="text" value="">
<a class="f6 link dim ph3 pv2 mb2 dib white bg-green attribute-save">Save</a>
</div>
</div>
<div>
{{ form.parttype.label(class_="f6 b db mb2") }}
{{ form.parttype(class_="ba b--black-20 pa2 mb2 db w-100") }} <a href="#" onClick="toggleVisibility('parttype_form')" class="f6 link dim ph3 pv2 mb2 dib white bg-blue">Add</a>
@ -105,5 +137,11 @@
populateSelect("package", "packages", {{ part.package_id or -1 }});
populateSelect("parttype", "types", {{ part.parttype_id or -1 }});
populateSelect("datasheet", "datasheets", {{ part.datasheet_id or -1 }});
$(document).ready(function() {
$('#attribute-add').click(attributesAddForm);
$('.attribute-save').click(attributesSaveForm);
$('.attribute-remove').click(attributesRemoveForm);
});
</script>
{% endblock %}

2
homebench/static/jquery-3.5.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -9,3 +9,7 @@
padding: 2em;
z-index: 1000;
}
.is-hidden {
display: none;
}

View File

@ -3,9 +3,12 @@
<head>
<meta charset="utf-8" />
<title>{% block title %}{% endblock %} - Homebench</title>
<link rel="stylesheet" href="{{ url_for('static', filename='tachyons.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<script type="text/javascript" src="{{ url_for('static', filename='jquery-3.5.1.min.js') }}"></script>
{% block script %}{% endblock %}
<body>
<nav class="flex justify-between w-100 bb bg-white-10 bg-washed-blue fixed">
@ -27,4 +30,10 @@
{% block content %}{% endblock %}
</section>
</body>
<script type="text/javascript">
$(document).ready(function() {
$('.removeAttribute').click(attributesAdjustIndices);
});
</script>
</html>

140
poetry.lock generated
View File

@ -1,56 +1,56 @@
[[package]]
category = "main"
description = "Composable command line interface toolkit"
name = "click"
version = "7.1.2"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "7.1.2"
[[package]]
category = "main"
description = "Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API."
name = "dominate"
version = "2.5.1"
description = "Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.5.1"
[[package]]
category = "main"
description = "A simple framework for building complex web applications."
name = "flask"
version = "1.1.2"
description = "A simple framework for building complex web applications."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "1.1.2"
[package.dependencies]
Jinja2 = ">=2.10.1"
Werkzeug = ">=0.15"
click = ">=5.1"
itsdangerous = ">=0.24"
[package.extras]
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
dotenv = ["python-dotenv"]
[package.dependencies]
click = ">=5.1"
itsdangerous = ">=0.24"
Jinja2 = ">=2.10.1"
Werkzeug = ">=0.15"
[[package]]
category = "main"
description = "User session management for Flask"
name = "flask-login"
version = "0.5.0"
description = "User session management for Flask"
category = "main"
optional = false
python-versions = "*"
version = "0.5.0"
[package.dependencies]
Flask = "*"
[[package]]
category = "main"
description = "Easily create navigation for Flask applications."
name = "flask-nav"
version = "0.6"
description = "Easily create navigation for Flask applications."
category = "main"
optional = false
python-versions = "*"
version = "0.6"
[package.dependencies]
dominate = "*"
@ -58,67 +58,75 @@ flask = "*"
visitor = "*"
[[package]]
category = "main"
description = "Adds SQLAlchemy support to your Flask application."
name = "flask-sqlalchemy"
version = "2.4.3"
description = "Adds SQLAlchemy support to your Flask application."
category = "main"
optional = false
python-versions = ">= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*"
version = "2.4.3"
[package.dependencies]
Flask = ">=0.10"
SQLAlchemy = ">=0.8.0"
[[package]]
category = "main"
description = "Simple integration of Flask and WTForms."
name = "flask-wtf"
version = "0.14.3"
description = "Simple integration of Flask and WTForms."
category = "main"
optional = false
python-versions = "*"
version = "0.14.3"
[package.dependencies]
Flask = "*"
WTForms = "*"
itsdangerous = "*"
WTForms = "*"
[[package]]
category = "main"
description = "Various helpers to pass data to untrusted environments and back."
name = "itsdangerous"
version = "1.1.0"
description = "Various helpers to pass data to untrusted environments and back."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.1.0"
[[package]]
category = "main"
description = "A very fast and expressive template engine."
name = "jinja2"
version = "2.11.2"
description = "A very fast and expressive template engine."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.11.2"
[package.dependencies]
MarkupSafe = ">=0.23"
[package.extras]
i18n = ["Babel (>=0.8)"]
[[package]]
category = "main"
description = "Safely add untrusted strings to HTML/XML markup."
name = "markupsafe"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "1.1.1"
[package.dependencies]
MarkupSafe = ">=0.23"
[[package]]
name = "markupsafe"
version = "1.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
description = "Database Abstraction Library"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
[[package]]
name = "pyyaml"
version = "5.3.1"
description = "YAML parser and emitter for Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "sqlalchemy"
version = "1.3.18"
description = "Database Abstraction Library"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.3.18"
[package.extras]
mssql = ["pyodbc"]
@ -133,44 +141,45 @@ postgresql_psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql"]
[[package]]
category = "main"
description = "A tiny pythonic visitor implementation."
name = "visitor"
version = "0.1.3"
description = "A tiny pythonic visitor implementation."
category = "main"
optional = false
python-versions = "*"
version = "0.1.3"
[[package]]
category = "main"
description = "The comprehensive WSGI web application library."
name = "werkzeug"
version = "1.0.1"
description = "The comprehensive WSGI web application library."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "1.0.1"
[package.extras]
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
watchdog = ["watchdog"]
[[package]]
category = "main"
description = "A flexible forms validation and rendering library for Python web development."
name = "wtforms"
version = "2.3.1"
description = "A flexible forms validation and rendering library for Python web development."
category = "main"
optional = false
python-versions = "*"
version = "2.3.1"
[package.dependencies]
MarkupSafe = "*"
[package.extras]
email = ["email-validator"]
ipaddress = ["ipaddress"]
locale = ["Babel (>=1.3)"]
[package.dependencies]
MarkupSafe = "*"
[metadata]
content-hash = "36243474df6d821c18cf9c0ad22034282721171618c4a235ecab11ac2dcff97e"
lock-version = "1.0"
python-versions = "^3.8"
content-hash = "9fde71911494cad5cb5440b1f11ee2766fa974cc748f48c0892e2c2e09809406"
[metadata.files]
click = [
@ -243,6 +252,19 @@ markupsafe = [
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
]
pyyaml = [
{file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"},
{file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"},
{file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"},
{file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"},
{file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"},
{file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"},
{file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"},
{file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"},
{file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"},
{file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"},
{file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
]
sqlalchemy = [
{file = "SQLAlchemy-1.3.18-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:f11c2437fb5f812d020932119ba02d9e2bc29a6eca01a055233a8b449e3e1e7d"},
{file = "SQLAlchemy-1.3.18-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0ec575db1b54909750332c2e335c2bb11257883914a03bc5a3306a4488ecc772"},

View File

@ -12,6 +12,7 @@ flask-wtf = "^0.14.3"
flask-sqlalchemy = "^2.4.3"
flask-login = "^0.5.0"
flask-nav = "^0.6"
pyyaml = "^5.3.1"
[tool.poetry.dev-dependencies]