Browse Source

Add tags support, removes and add from parts, various parts switch cat to tags

master
Dashie der otter 3 years ago
parent
commit
fbf9a7115c

+ 1
- 1
barcode_utils.py View File

@@ -34,7 +34,7 @@ def generate_ean13(part_type, part_id, dir_out):
opts = dict(module_width=0.2, module_height=size[0], quiet_zone=1, font_size=size[1], text_distance=1)
ean = barcode.get('ean13', barcode_str, writer=ImageWriter())

out = os.path.join(dir_output, _type, "{0}_{1}".format(txt, barcode_str))
out = os.path.join(dir_output, _type, "{0}_{1}.png".format(txt, barcode_str))

if not os.path.isdir(os.path.join(dir_output, _type)):
os.makedirs(os.path.join(dir_output, _type))

+ 18
- 0
controllers/ajax_parts.py View File

@@ -9,6 +9,24 @@ import os
bp_ajax_parts = Blueprint('bp_ajax_parts', __name__)


@bp_ajax_parts.route('/ajax/parts/tags/new', methods=['POST'])
@login_required
def ajax_parts_tags_new():
name = request.json['name']

if not name or name == "":
response = jsonify({"Result": "ERROR", "Message": "Empty name"})
response.status_code = 409 # HTTP Conflict
return response

tag = Tag()
tag.name = name
db.session.add(tag)
db.session.commit()

return jsonify({"Result": "OK", "id": tag.id})


@bp_ajax_parts.route('/ajax/parts/from_category/<int:cid>/public')
@login_required
def ajax_parts_from_category_public(cid):

+ 7
- 28
controllers/parts.py View File

@@ -27,15 +27,6 @@ def parts_new():

form = PartForm()

if 'current_part_category_id' in request.form:
category_id = request.form['current_part_category_id']
if category_id:
category = PartCategory.query.filter(PartCategory.id == category_id).one()
else:
category = None
else:
category_id, category = None, None

if form.validate_on_submit():
a = Part()
a.name = form.name.data
@@ -51,11 +42,8 @@ def parts_new():

a.can_be_sold = form.can_be_sold.data

# Force category to uncategorized, idiot
if not category:
category = PartCategory.query.filter(PartCategory.name == "# Uncategorized").one()
a.tags = form.tags.data

a.part_category = category
a.footprint_id = None if form.footprint.raw_data[0] == '__None' else form.footprint.raw_data[0] # FIXME ewwww
a.part_measurement_unit_id = form.part_measurement_unit.raw_data[0] # FIXME ewwww
a.storage_id = None if form.storage.raw_data[0] == '__None' else form.storage.raw_data[0] # FIXME ewwwww
@@ -71,7 +59,7 @@ def parts_new():
return redirect(url_for('bp_parts.parts_edit', pid=a.id))

return render_template('parts/new.jinja2', pcfg=pcfg, form=form,
current_category=category_id, action=url_for('bp_parts.parts_new'))
action=url_for('bp_parts.parts_new'))


@bp_parts.route('/parts/<int:pid>/edit', methods=['GET', 'POST'])
@@ -81,18 +69,9 @@ def parts_edit(pid):

a = Part.query.get_or_404(pid)

form = PartForm(obj=a)
form = PartForm(request.form, a)
attachments_form = PartAttachmentForm()

if 'current_part_category_id' in request.form:
category_id = request.form['current_part_category_id']
if category_id == '' or category_id == u'':
category = None
else:
category = PartCategory.query.filter(PartCategory.id == category_id).one()
else:
category_id, category = a.part_category.id, a.part_category

if form.validate_on_submit():
a.name = form.name.data
a.description = form.description.data
@@ -106,7 +85,8 @@ def parts_edit(pid):
a.can_be_sold = form.can_be_sold.data
a.private = form.private.data

a.part_category = category
a.tags = form.tags.data

a.footprint_id = None if form.footprint.raw_data[0] == '__None' else form.footprint.raw_data[0] # FIXME ewwww
a.part_measurement_unit_id = form.part_measurement_unit.raw_data[0] # FIXME ewwww
a.storage_id = None if form.storage.raw_data[0] == '__None' else form.storage.raw_data[0] # FIXME ewwwww
@@ -126,7 +106,7 @@ def parts_edit(pid):

return render_template('parts/edit.jinja2', pcfg=pcfg,
form=form, attachments_form=attachments_form,
current_category=category, action=url_for('bp_parts.parts_edit', pid=a.id),
action=url_for('bp_parts.parts_edit', pid=a.id),
part=a, units=units,
distributors=distributors, manufacturers=manufacturers
)
@@ -140,8 +120,7 @@ def parts_view(pid):
a = Part.query.get_or_404(pid)
attachments_form = PartAttachmentForm()

return render_template('parts/view.jinja2', pcfg=pcfg,
current_category=a.part_category, part=a,
return render_template('parts/view.jinja2', pcfg=pcfg, part=a,
attachments_form=attachments_form)



+ 7
- 2
forms.py View File

@@ -4,9 +4,10 @@ from wtforms import StringField, SubmitField, BooleanField, IntegerField
from flask_wtf.file import FileField
from wtforms.validators import DataRequired, NumberRange
from flask_security import RegisterForm
from models import db, Part, PartMeasurementUnit, Storage, Footprint, PartAttachment, Manufacturer
from models import db, Part, PartMeasurementUnit, Storage, Footprint, PartAttachment, Manufacturer, Tag
from wtforms_alchemy import model_form_factory
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField

BaseModelForm = model_form_factory(Form)

@@ -41,6 +42,10 @@ def default_pmu():
return PartMeasurementUnit.query.filter(PartMeasurementUnit.name == 'Pieces').first()


def get_tags():
return Tag.query.all()


class PartForm(ModelForm):
class Meta:
model = Part
@@ -59,7 +64,7 @@ class PartForm(ModelForm):
allow_blank=False, label='Part unit')
storage = QuerySelectField(query_factory=get_storages, allow_blank=True, blank_text='None')
footprint = QuerySelectField(query_factory=get_footprints, allow_blank=True, blank_text='None')
# category not managed by WTF
tags = QuerySelectMultipleField('Tags', query_factory=get_tags)

add_new = BooleanField('Save and add new part')


+ 3
- 0
models.py View File

@@ -65,6 +65,9 @@ class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True)

def __repr__(self):
return self.name


class Unit(db.Model):
id = db.Column(db.Integer, primary_key=True)

+ 2
- 1
stockazng.py View File

@@ -81,6 +81,7 @@ if os.path.isdir(gitpath):
git_version = git_version.strip()



@app.before_request
def before_request():
g.cfg = {
@@ -99,7 +100,7 @@ def before_request():
'STOCKAZ_PUBLIC': app.config['STOCKAZ_PUBLIC']
}
g.current_user = current_user
g.tags = Tag.query.all()
g.tags = db.session.query(Tag).filter(Tag.parts.any())


app.register_blueprint(bp_main)

+ 6
- 1
templates/layout.jinja2 View File

@@ -24,6 +24,7 @@
<link rel="stylesheet" href="{{ url_for('static', filename='jtable/themes/lightcolor/gray/jtable.min.css') }}" type="text/css" />
<link rel="stylesheet" href="{{ url_for('static', filename='jtable/Scripts/validationEngine/validationEngine.jquery.css') }}" type="text/css" />
<link rel="stylesheet" href="{{ url_for('static', filename='datatables/media/css/dataTables.bootstrap.min.css') }}" type="text/css" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/chosen.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">

<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
@@ -122,7 +123,11 @@
<div class="container-fluid" id="contentBody">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<div id="PartCategoriesJSTree"></div>
<div id="cloudOfTags">
{% for i in g.tags %}
<button class="btn btn-xs">{{ i.name }} <small>({{ i.parts.count() }})</small></button>
{% endfor %}
</div>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
{% block content %}{% endblock %}

+ 6
- 12
templates/parts/_sidebar.jinja2 View File

@@ -18,18 +18,12 @@
<div class="tab-pane active" id="tabSidebarDatas">
<table class="table table-striped table-condensed tablePartsSidebar">
<tr>
<td>Category:</td>
{% if g.current_user.is_authenticated() %}
<td>
{{ " > ".join(part.part_category.categories_path(False)) }} >
<a title="Switch to category" href="" data-id="{{ part.part_category.id }}" class="revealCatForPart">{{ part.part_category }}</a>
</td>
{% else %}
<td>
{{ " > ".join(part.part_category.categories_path(False)) }} >
{{ part.part_category }}
</td>
{% endif %}
<td>Tags:</td>
<td>
{% for i in part.tags %}
<span class="btn btn-info btn-xs">{{ i.name }}</span>
{% endfor %}
</td>
</tr>
<tr>
<td>Stock level:</td>

+ 43
- 3
templates/parts/edit.jinja2 View File

@@ -5,7 +5,6 @@
<h3>
<a href="{{ url_for("bp_parts.parts_edit", pid=part.id) }}" title="View part"><i class="fa fa-eye"></i></a>
Manage a part
<small><span id="CurPartCategory">{% if current_category %}({{ current_category.name }}){% endif %}</span></small>
</h3>

<ul class="nav nav-tabs" role="tablist">
@@ -51,6 +50,16 @@
{{ wtf.form_field(form.part_measurement_unit, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
{{ wtf.form_field(form.storage, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
{{ wtf.form_field(form.comment, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
<div class="row">
<div style="text-align: right;" class="col-lg-2 col-lg-offset-1"><strong>Add tag</strong></div>
<div class="col-lg-4">
<input name="add_a_tag_txt" id="add_a_tag_txt">&nbsp;<span id="add_a_tag_btn" class="btn btn-success btn-xs">Add</span>
</div>
</div>

<br />

{{ wtf.form_field(form.tags, 'horizontal', horizontal_columns=('lg', 3, 9), data_placeholder='Select tags') }}

{{ wtf.form_field(form.submit, button_map={'submit': 'success'}) }}
</div>
@@ -59,8 +68,6 @@
<hr/>
{{ wtf.form_field(form.min_stock_level, 'horizontal', horizontal_columns=('lg', 3, 9)) }}

<input type="hidden" value="{{ current_category.id }}" id="current_part_category_id" name="current_part_category_id"/>

{{ wtf.form_field(form.footprint, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
{{ wtf.form_field(form.status, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
{{ wtf.form_field(form.condition, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
@@ -94,7 +101,11 @@
{% endblock %}

{% block scripts %}
<script src="{{url_for('static', filename='js/chosen.jquery.min.js')}}"></script>

<script>
$('#tags').chosen({width: "100%"});

$('#tableOfParameters').on("click", ".del", function() {
var item = $(this);
var id = $(this).parent().parent().data().id;
@@ -253,6 +264,35 @@
return false;
});

$('#add_a_tag_btn').on("click", "", function () {
var txt = $('#add_a_tag_txt').val();

if (txt === "") {
console.log("Empty tag");
return;
}

$.ajax({
url: '/ajax/parts/tags/new',
type: 'POST',
data: JSON.stringify({'name': txt}, null, '\t'),
contentType: 'application/json;charset=UTF-8',
success: function(data) {
var opt = document.createElement('option');
opt.value = data.id;
opt.innerHTML = txt;
document.getElementById('tags').appendChild(opt);
$('#tags').trigger('chosen:updated');
},
error: function(request, error) {
show_alert("error", "Add tag", "Error or already exists");
}
}); // ajax

// Call chosen to reload
$('#add_a_tag_txt').val("");
});

var wc_data_uri = null;
Webcam.set({
width: 320,

+ 44
- 5
templates/parts/new.jinja2 View File

@@ -2,9 +2,7 @@
{% import "bootstrap/wtf.html" as wtf %}

{% block content %}
<h3>Add a part
<small><span id="CurPartCategory">{% if current_category %}({{ current_category.name }}){% else %}(please select a category){% endif %}</span></small>
</h3>
<h3>Add a part</h3>

<div role="tabpanel" class="tab-pane active" id="AddPartTabDatas">
<br/>
@@ -28,6 +26,16 @@
{{ wtf.form_field(form.part_measurement_unit, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
{{ wtf.form_field(form.storage, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
{{ wtf.form_field(form.comment, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
<div class="row">
<div style="text-align: right;" class="col-lg-2 col-lg-offset-1"><strong>Add tag</strong></div>
<div class="col-lg-4">
<input name="add_a_tag_txt" id="add_a_tag_txt">&nbsp;<span id="add_a_tag_btn" class="btn btn-success btn-xs">Add</span>
</div>
</div>

<br />

{{ wtf.form_field(form.tags, 'horizontal', horizontal_columns=('lg', 3, 9), data_placeholder='Select tags') }}

{{ wtf.form_field(form.submit, button_map={'submit': 'success'}) }}
</div>
@@ -36,8 +44,6 @@
<hr/>
{{ wtf.form_field(form.min_stock_level, 'horizontal', horizontal_columns=('lg', 3, 9)) }}

<input type="hidden" value="{{ current_category.id }}" id="current_part_category_id" name="current_part_category_id"/>

{{ wtf.form_field(form.footprint, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
{{ wtf.form_field(form.status, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
{{ wtf.form_field(form.condition, 'horizontal', horizontal_columns=('lg', 3, 9)) }}
@@ -57,5 +63,38 @@
{% endblock %}

{% block scripts %}
<script src="{{url_for('static', filename='js/chosen.jquery.min.js')}}"></script>

<script>
$('#tags').chosen({width: "100%"});
$('#add_a_tag_btn').on("click", "", function () {
var txt = $('#add_a_tag_txt').val();

if (txt === "") {
console.log("Empty tag");
return;
}

$.ajax({
url: '/ajax/parts/tags/new',
type: 'POST',
data: JSON.stringify({'name': txt}, null, '\t'),
contentType: 'application/json;charset=UTF-8',
success: function(data) {
var opt = document.createElement('option');
opt.value = data.id;
opt.innerHTML = txt;
document.getElementById('tags').appendChild(opt);
$('#tags').trigger('chosen:updated');
},
error: function(request, error) {
show_alert("error", "Add tag", "Error or already exists");
}
}); // ajax

// Call chosen to reload
$('#add_a_tag_txt').val("");
});
</script>
<script src="{{url_for('static', filename='js/autocompletes_parts.js')}}"></script>
{% endblock %}

+ 11
- 3
templates/parts/view.jinja2 View File

@@ -5,7 +5,11 @@
<h3>
<a href="{{ url_for("bp_parts.parts_edit", pid=part.id) }}" title="Edit part"><i class="fa fa-edit"></i></a>
{{ part.name }}
<small><span id="CurPartCategory">{% if current_category %}({{ current_category.name }}){% endif %}</span></small>
<small>
{% for i in part.tags %}
<span class="btn btn-xs btn-info">{{ i.name }}</span>
{% endfor %}
</small>
</h3>
{% if part.description %}<h5>{{ part.description }}</h5>{% endif %}

@@ -34,8 +38,12 @@
<br/>
<table class="table table-striped table-condensed tablePartsSidebar">
<tr>
<td>Category:</td>
<td><a title="Switch to category" href="" data-id="{{ part.part_category.id }}" class="revealCatForPart">{{ part.part_category }}</a></td>
<td>Tags:</td>
<td>
{% for i in part.tags %}
<span class="btn btn-xs btn-info">{{ i.name }}</span>
{% endfor %}
</td>
</tr>
<tr>
<td>Stock level:</td>