plumdog / flask_table

Because writing HTML is fiddly and all of your tables are basically the same
http://flask-table.readthedocs.org/
BSD 3-Clause "New" or "Revised" License
210 stars 45 forks source link

additional header <tr> and summarized values in <tfood> #97

Closed elgaucho79 closed 6 years ago

elgaucho79 commented 6 years ago

Hi Andrew,

In my target table, I use a seperate <tr> inside the header (above the column description) to specify a factor for the SUM values later in the <tfoot> tag.

  1. Is there a way to specify an additional <tr> tag inside the header as coded below?
  2. how do I specify a <tfood> tag that summarises the values of the tables for each column, using also the "factor" given from the header <input> field?

would like to get a result something like this:

image

<table>
  <thead>
    <tr>
      <th>factor SUM</th>
      <th><input type="number" name="factor_savings" value=2></th>
    </tr>
    <tr>
      <th>Month</th>
      <th>Savings</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>January</td>
      <td>$100</td>
    </tr>
    <tr>
      <td>February</td>
      <td>$80</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td>Sum</td>
      <td>$360</td>
    </tr>
  </tfoot>
</table>

thanks Michael

plumdog commented 6 years ago

Both of these are possible, but you have to do much of the work yourself - they aren't built into flask table.

Firstly, the extra th:

Something like:

from __future__ import unicode_literals
from flask_table import Table, Col
from flask_table.html import element

class ExtraTHTable(Table):

    def thead(self):
        normal_ths = ''.join(
            self.th(col_key, col)
            for col_key, col in self._cols.items()
            if col.show)
        extra_ths = ''.join(
            self.extra_th(col_key, col)
            for col_key, col in self._cols.items()
            if col.show)
        content = '\n'.join([
            element('tr', content=normal_ths, escape_content=False),
            element('tr', content=extra_ths, escape_content=False),
        ])
        return element(
            'thead',
            attrs=self.get_thead_attrs(),
            content=content,
            escape_content=False,
        )

    def extra_th(self, key, col):
        # With the key and column, return the additional th for that
        # column
        return element('th', content='Extra th for {}'.format(key))

class MyExtraTHTable(ExtraTHTable):
    name = Col('Name')
    desc = Col('Desc')

data = [
    dict(name='name1', desc='desc1'),
]
print MyExtraTHTable(data).__html__()

Secondly, the tfoot:

from __future__ import unicode_literals
from flask_table import Table, Col
from flask_table.html import element

class WithTFootTable(Table):

    def __html__(self):
        tbody = self.tbody()
        if tbody or self.allow_empty:
            content = '\n{thead}\n{tbody}\n{tfoot}\n'.format(
                thead=self.thead(),
                tbody=tbody,
                tfoot=self.tfoot(),
            )
            return element(
                'table',
                attrs=self.get_html_attrs(),
                content=content,
                escape_content=False)
        else:
            return element('p', content=self.no_items)

    def tfoot(self):
        tr_content = ''.join(self.tfoot_td(col_key, col) for col_key, col in self._cols.items() if col.show)
        content = element('tr', content=tr_content, escape_content=False)
        return element('tfoot', content=content, escape_content=False)

    def tfoot_td(self, key, col):
        # note that here you have access to the items with self.items
        return element('td', content='tfoot td for {}'.format(key))

class MyTFootTable(WithTFootTable):
    name = Col('Name')
    desc = Col('Desc')

data = [
    dict(name='name1', desc='desc1'),
]
print MyTFootTable(data).__html__()

Hopefully, you should be able to run those and work out what is going on. For both, I poked into the code for the Table class in https://github.com/plumdog/flask_table/blob/master/flask_table/table.py and have overridden the relevant methods. (The current state of the code doesn't make this sort of thing super easy, but it is possible).

Also, you should be able to use those two together without a problem (though I haven't actually tested it!)

Hope that's useful!

elgaucho79 commented 6 years ago

@app.route('/analyse', methods=['GET', 'POST']) def analyse(): SELECT2_FM_TEMPLATE = '''

'''
def get_select_options(*args): # Erzeigung des Select2 FM Felder mit Default Wert Selection
    SELECT_TEMPLATE_STRING = '''
    <select class="js-example-basic-single" id="fm_ist_01" name="fm_auswahl" style="width: 220px" onchange="update_form_fields(this.form)">
        <option value=0>- Futtermittel wählen -</option>
            {% for entry in options|groupby('klassifizierung') %}
                  <optgroup label={{ entry.grouper }}>
                        {% for fm in entry.list %}
                            {% if fm.fm_id == ''' + str(args[0]) + ''' %}
                                <option value={{ fm.fm_id }} selected="selected">{{ fm.name }}</option>
                            {% else %}
                                <option value={{ fm.fm_id }}>{{ fm.name }}</option>
                            {% endif %}    
                        {% endfor %}
                  </optgroup>
            {% endfor %}
    </select>
    '''
    return SELECT_TEMPLATE_STRING

class SelectCol(Col):

    def __init__(self, *args, **kwargs):
        self.options = kwargs.pop('options')
        self.fm_id = kwargs.pop('fm_id')
        super(SelectCol, self).__init__(*args, **kwargs)

    def td_contents(self, item, attr_list):
        SELECT_TEMPLATE_STRING=get_select_options(self.fm_id) # Hier wird dynamisch der gewählte FM Formularwert reingeladen und später als default wert genereirt.
        return render_template_string(
            SELECT_TEMPLATE_STRING,
            options=self.options)

s = session()
form = Analyse_Form()
fm_select = s.query(Futtermittel.fm_id, Futtermittel.name, Futtermittel.klassifizierung).all()
fm_pos = [] # Erzeugung leeres Liste zur Befüllung mit DICT aus fm_values für /analyse
fm_values = {} # Erzeugung leeres DICT für /analyse Werte pro FM
fm_pos.append(fm_values) # dict der Liste zuweisen
#Erzeugung der Ausgabetabellenstruktur
TableCls = (create_table('TableCls', options=dict(classes=['table table-hover table-striped table-sm table-bordered']))) # dyntable Erzeugung
TableCls.add_column('pos_id', Col('Pos_ID', column_html_attrs={'style':"display:none"}))
TableCls.add_column('button', ButtonCol('Akt.', 'is_list', url_kwargs=dict(id='pos_id'), text_fallback='-', button_attrs={'class': 'btn btn-danger btn-sm', 'title':'Futtermittel entfernen'}, th_html_attrs={'class': 'center-small-white align-middle'})) # fixe Spalte
TableCls.add_column('selectcol', SelectCol('Futtermittel', options=fm_select, th_html_attrs={'class': 'center-small-white align-middle'}, fm_id=form.fm_id_01.data))
TableCls.add_column('menge', RawCol('Menge/[g]', th_html_attrs={'class': 'center-small-white align-middle'})) # fixe Spalte

for entry in s.query(Inhaltsstoff).order_by(Inhaltsstoff.anzeigestelle).all():
    if entry.klassifizierung == 'Nährwerte': # Kontrollstruktur für farbige Ausgabedefinition verschiedener IS-Klassifizierungen
        TableCls.add_column(str(entry.name), Col(str(entry.name) + ' [' + entry.einheit + ']', th_html_attrs={'class': 'center-small-others align-middle'}, td_html_attrs={'class': 'right-others'}))
    elif entry.klassifizierung == 'Mengenelemente':
        TableCls.add_column(str(entry.name), Col(str(entry.name) + ' [' + entry.einheit + ']', th_html_attrs={'class': 'center-small-green align-middle'}, td_html_attrs={'class': 'right-green'}))
    elif entry.klassifizierung == 'Spurenelemente':
        TableCls.add_column(str(entry.name), Col(str(entry.name) + ' [' + entry.einheit + ']', th_html_attrs={'class': 'center-small-brown align-middle'}, td_html_attrs={'class': 'right-brown'}))
    elif entry.klassifizierung == 'Vitamine':
        TableCls.add_column(str(entry.name), Col(str(entry.name) + ' [' + entry.einheit + ']', th_html_attrs={'class': 'center-small-blue align-middle'}, td_html_attrs={'class': 'right-blue'}))
    elif entry.klassifizierung == 'Aminosäuren':
        TableCls.add_column(str(entry.name), Col(str(entry.name) + ' [' + entry.einheit + ']', th_html_attrs={'class': 'center-small-orange align-middle'}, td_html_attrs={'class': 'right-orange'}))
    elif entry.klassifizierung == 'Sonstige':
        TableCls.add_column(str(entry.name), Col(str(entry.name) + ' [' + entry.einheit + ']', th_html_attrs={'class': 'center-small-thistle align-middle'}, td_html_attrs={'class': 'right-thistle'}))
    else:  # für alle anderen Klassifizierungen folgende Farbdefinition:
        TableCls.add_column(str(entry.name), Col(str(entry.name) + ' [' + entry.einheit + ']', th_html_attrs={'class': 'center-small-others align-middle'}, td_html_attrs={'class': 'right-others'}))
    for entry in s.query(Inhaltsstoff).all():  # IS-Werte zuweisen
        # istwert = s.query(Futtermittel_Inhalt).join(Inhaltsstoff).filter(Inhaltsstoff.is_id == entry.is_id, Futtermittel_Inhalt.fm_id == form.fm_id.data).first()
        # if istwert:  # formatierte Wertausgabe '#.###,00'
        #     fm_values[entry.name] = '{:,.2f}'.format(istwert.wert).replace(",", "X").replace(".", ",").replace("X", ".")
        # else:
            fm_values[entry.name] = '-'
        # zur Gesamtliste hinzufügen
    fm_values['pos_id'] = 1
    fm_values['menge'] = '<input type="text" name="menge" id="menge_ist_01" size="6", onkeypress="return validInput(event)", onchange="update_form_fields(this.form)", value=100>'
    dyntable = TableCls(fm_pos) # Zuweisung der Liste zur dyn. Table

if form.is_submitted(): #bei Formularaktualisierung
    for entry in s.query(Inhaltsstoff).all():  # IS-Werte zuweisen und mit Menge und IS-Faktoren berücksichtigen
        istwert = s.query(Futtermittel_Inhalt).join(Inhaltsstoff).filter(Inhaltsstoff.is_id == entry.is_id, Futtermittel_Inhalt.fm_id == form.fm_id_01.data).first()
        if istwert:  # formatierte Wertausgabe '#.###,00'
            istwert = istwert.wert/100*decimal.Decimal(form.futtermenge_01.data) # ToDo IS Faktor fehlt noch
            fm_values[entry.name] = '{:,.2f}'.format(istwert).replace(",", "X").replace(".", ",").replace("X", ".")
        else:
            fm_values[entry.name] = '-'

    fm_values['menge'] = '<input type="text" name="menge" id="menge_ist_01" size="6", onkeypress="return validInput(event)", onchange="update_form_fields(this.form)", value=' + form.futtermenge_01.data +'>'
    dyntable = TableCls(fm_pos)  # Zuweisung der Liste zur dyn. Table
    return render_template('analyse.html', dyntable=dyntable, form=form)

if  request.args.get('fm_add'): # neue FM Zeile hinzufügen
    # ziemlich buggy hier alles. :-(
    fm_values_neu = {} # leeres DICT zur Erzeugung einer neuen Position
    fm_pos.append(fm_values_neu) # zur Gesamtliste hinzufügen
    fm_values_neu['pos_id'] = len(fm_pos)+1 # Pos_ID der neuen Zeile
    flash(fm_values_neu['pos_id'])
    fm_values_neu['selectcol'] = SELECT2_FM_TEMPLATE
    fm_values_neu['menge'] = '<input type="text" name="menge" id="menge_ist_02" size="6", onkeypress="return validInput(event)", onchange="update_form_fields(this.form)", value=100>'

    for entry in s.query(Inhaltsstoff).all():  # IS-Werte zuweisen
        # formatierte Wertausgabe '-'
        fm_values_neu[entry.name] = '-'

    dyntable = TableCls(fm_pos)  # Zuweisung der Liste zur dyn. Table

    return render_template('analyse.html', dyntable=dyntable, form=form)

if not dyntable:
    dyntable = TableCls()

s.close()
return render_template('analyse.html',  form=form, dyntable = dyntable)
plumdog commented 6 years ago

Hi,

There's quite a lot going on in the code you posted, and I'm not exactly sure what it is that you're having trouble with.

If you could create a simple, runnable example, and explain what it is that you're trying to fix, I'd be happy to try to help.

elgaucho79 commented 6 years ago

import os, datetime, decimal from flask import * from flask_bootstrap import Bootstrap from flask_sqlalchemy import SQLAlchemy from sqlalchemy import Column, DateTime, String, Integer, ForeignKey, func, Numeric, Text, Enum, asc, desc from sqlalchemy.orm import backref, relationship, sessionmaker from sqlalchemy.ext.declarative import declarative_base from flask_table import Table, Col, create_table, LinkCol, ButtonCol, BoolCol from sqlalchemy import create_engine from flask_wtf import RecaptchaField, Form from wtforms import StringField, PasswordField, SelectField, FieldList, FormField, TextField,HiddenField, RadioField, FloatField, widgets, TextAreaField from wtforms.fields.html5 import DateField, TelField, IntegerField, DecimalField, DateTimeField from wtforms.validators import InputRequired, Length, AnyOf from money import Money from wtforms_sqlalchemy.fields import QuerySelectField

app = Flask(name) app.secret_key = os.urandom(24) basedir = os.path.abspath(os.path.dirname(file)) Base = declarative_base() bootstrap = Bootstrap(app)

class Analyse_Form(Form): fm_id_01 = HiddenField() # wird indirekt über .js gefüllt futtermenge_01 = HiddenField() # wird indirekt über .js gefüllt erg_fm_id_01 = HiddenField() # erste Ergänzungsposition in Analyse, wird über .js gefüllt erg_futtermenge_01 = HiddenField() # erste Ergänzungsposition in Analyse, wird über .js gefüllt

@classmethod # zur dynamischen Formularerweiterung pro weitere FM Zeile
def append_field(cls, name, field):
    setattr(cls, name, field)
    return cls

@app.route('/analyse_simplyfied', methods=['GET', 'POST']) def analyse_simplyfied(): SELECT2_FM_TEMPLATE = '''

'''
def get_select_options(*args): # Erzeigung des Select2 FM Felder mit Default Wert Selection
    SELECT_TEMPLATE_STRING = '''
    <select class="js-example-basic-single" id="fm_ist_01" name="fm_auswahl" style="width: 220px" onchange="update_form_fields(this.form)">
        <option value=0>- select Food -</option>
            {% for entry in options|groupby('klassifizierung') %}
                  <optgroup label={{ entry.grouper }}>
                        {% for fm in entry.list %}
                            {% if fm.fm_id == ''' + str(args[0]) + ''' %}
                                <option value={{ fm.fm_id }} selected="selected">{{ fm.name }}</option>
                            {% else %}
                                <option value={{ fm.fm_id }}>{{ fm.name }}</option>
                            {% endif %}    
                        {% endfor %}
                  </optgroup>
            {% endfor %}
    </select>
    '''
    return SELECT_TEMPLATE_STRING

class SelectCol(Col):

    def __init__(self, *args, **kwargs):
        self.options = kwargs.pop('options')
        self.fm_id = kwargs.pop('fm_id')
        super(SelectCol, self).__init__(*args, **kwargs)

    def td_contents(self, item, attr_list):
        SELECT_TEMPLATE_STRING=get_select_options(self.fm_id) # load form value to set default value after submit
        return render_template_string(
            SELECT_TEMPLATE_STRING,
            options=self.options)

s = session()
form = Analyse_Form()
fm_select = s.query(Futtermittel.fm_id, Futtermittel.name, Futtermittel.klassifizierung).all() # food values from DB
pos_id_counter = 1
fm_pos = [] # Erzeugung leeres Liste zur Befüllung mit DICT aus fm_values für /analyse
fm_values = {} # Erzeugung leeres DICT für /analyse Werte pro FM
fm_pos.append(fm_values) # assignment of dict to list

TableCls = (create_table('TableCls')) # dyntable
TableCls.add_column('pos_id', Col('Pos_ID'))
TableCls.add_column('button', ButtonCol('Action', 'is_list', url_kwargs=dict(id='pos_id'), text_fallback='-', button_attrs={'class': 'btn btn-danger btn-sm', 'title':'delete line'}))
TableCls.add_column('selectcol', SelectCol('Food Selector', options=fm_select, fm_id=form.fm_id_01.data))
TableCls.add_column('amount', RawCol('amount/[g]'))
TableCls.add_column('energy', Col('Energy calculation', th_html_attrs={'class': 'center-small-others align-middle'}, td_html_attrs={'class': 'right-others'}))
fm_values['energy'] = '-'
fm_values['pos_id'] = pos_id_counter
fm_values['amount'] = '<input type="text" name="menge" id="menge_ist_01" size="6", onkeypress="return validInput(event)", onchange="update_form_fields(this.form)", value=100>'
dyntable = TableCls(fm_pos) # assignment of list values to dyn. Table

if form.is_submitted(): #bei Formularaktualisierung
    fm_values['energy'] = int(form.futtermenge_01.data) * 2 # some calculation
    fm_values['amount'] = '<input type="text" name="menge" id="menge_ist_01" size="6", onkeypress="return validInput(event)", onchange="update_form_fields(this.form)", value=' + form.futtermenge_01.data +'>'
    dyntable = TableCls(fm_pos)  # Zuweisung der Liste zur dyn. Table
    return render_template('analyse_simplyfied.html', dyntable=dyntable, form=form)

if  request.args.get('fm_add'): # insert new line into table

    fm_values_new = {} # empty dict to generate a new pos
    fm_pos.append(fm_values_new) # add dict to table list
    pos_id_counter += 1 # increase pos_id counter
    fm_values_new['pos_id'] = pos_id_counter # pos_id of new line
    fm_values_new['selectcol'] = SELECT2_FM_TEMPLATE
    fm_values_new['amount'] = '<input type="text" name="menge" id="menge_ist_02" size="6", onkeypress="return validInput(event)", onchange="update_form_fields(this.form)", value=100>'
    fm_values_new['energy'] = '-' # default

    dyntable = TableCls(fm_pos)  # Zuweisung der Liste zur dyn. Table

    return render_template('analyse_simplyfied.html', dyntable=dyntable, form=form)

if not dyntable:
    dyntable = TableCls()

s.close()
return render_template('analyse_simplyfied.html',  form=form, dyntable = dyntable)
plumdog commented 6 years ago

Hi,

Same as before, really. I'm not sure what it is that you would like help with.

Can you perhaps explain the problem that you're having with the code that you posted? Can you tell me what it is doing wrong?

plumdog commented 6 years ago

Closing due to inactivity.

Let me know if there's still outstanding questions for this.

If so, if you can post a small runnable example, and explain the problem you're having, that would be great.

giuliohome commented 3 years ago

@plumdog Thank yuo for the footer implementation! I've re-used it today in my project to work with DataTables, to filter columns with multiple values (where a footer like that is required in fact)