Related
In my project there is a window for editing files. Each file has some properties that the user can change with several checkboxes. Also each file has an icon.
Now it is implemented through QScrollArea in which widgets are added. It looks like this:
Now I want to add folder support for files. I would like to support drag n drop and so on.
I thought it would be a good idea in my case to use QTreeView.
The widget for the files has already been drawn and it suits me completely:
I haven't worked with QT's model/view framework before. after a couple of days of trying to draw something acceptable, I broke my brain. That's what I did:
import sys
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *
from scanner.models import *
from gui.styles.settings import *
from gui.utils import setup_fonts
class BaseFileItem(QStandardItem):
def __init__(self, parent=None):
super(BaseFileItem, self).__init__(parent)
self.setEditable(False)
class TitleItem(QStandardItem):
file_type = 'title'
class FolderItem(QStandardItem):
file_type = 'folder'
class PreviewItem(BaseFileItem):
file_type = 'preview'
class FileItem(BaseFileItem):
file_type = 'file'
class Model(QStandardItemModel):
def supportedDropActions(self):
return Qt.MoveAction
class Delegate(QStyledItemDelegate):
checkbox_width = 50
checkboxes = [
'main',
'preview',
'use',
]
def sizeHint(self, option, index):
match index.data(1002):
case 'title':
height = 32
case 'folder':
height = 24
case _:
height = 48
return QSize(height, height)
def paint(self, painter, option, index):
super(Delegate, self).paint(painter, option, index)
match index.data(1002):
case 'title':
item_type = 'label'
case 'folder':
return
case _:
item_type = 'checkbox'
for column, name in enumerate(self.checkboxes):
self.add_checkbox_item(painter, option, column, name, item_type)
def add_checkbox_item(self, painter, option, column, text, item_type):
rect = option.rect
if item_type == 'checkbox':
item = QStyleOptionButton()
ce = QStyle.CE_CheckBox
else:
item = QStyleOptionHeader()
item.text = text
ce = QStyle.CE_HeaderLabel
item.item_type = item_type
x, y = rect.left() + rect.width() - (column + 1) * self.checkbox_width, rect.top()
item.rect = QRect(x, y, self.checkbox_width, rect.height())
QApplication.style().drawControl(ce, item, painter)
class AssetEditFilesView(QWidget):
rows = ['Asset files', 'Previews', 'Candidates']
def __init__(self, files, parent=None):
self.files = files
super(AssetEditFilesView, self).__init__(parent)
self.setContentsMargins(24, 24, 24, 24)
self.model = Model()
self.tree = QTreeView(self)
self.tree.setContentsMargins(4, 4, 4, 4)
self.tree.setItemDelegate(Delegate(self.tree))
self.tree.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.tree.setSortingEnabled(True)
self.tree.setDragEnabled(True)
self.tree.setAcceptDrops(True)
self.tree.setDropIndicatorShown(True)
self.tree.header().hide()
self.tree.setModel(self.model)
self.import_data(files)
self.tree.expandAll()
layout = QVBoxLayout(self)
layout.addWidget(self.tree)
def import_data(self, data):
self.model.setRowCount(0)
for group, files in zip(self.rows, self.prepare_files(data)):
if files:
self.add_files_to_group(group, files)
def add_files_to_group(self, group, files):
files = sorted(files, key=lambda x: str(x.data().asset_path))
data = [f.data().asset_path.parts for f in files]
max_len = max([len(x) for x in data])
prepared = [x for x in zip(*[[y[i] if i < len(y) else None for i in range(max_len)] for y in data])]
structure = self.create_structure(prepared, files)
root = self.add_file_row(self.model.invisibleRootItem(), group, TitleItem)
for folder, items in structure.items():
file_model = PreviewItem if group == 'Previews' else FileItem
self.add_items(root, folder, items, file_model=file_model)
def add_items(self, root, folder, items, file_model=FileItem):
if folder == '.':
for item in items:
self.add_file_row(root, item, file_model)
else:
new_root = self.add_file_row(root, folder, FolderItem)
for _folder, _items in items.items():
self.add_items(new_root, _folder, _items, file_model=file_model)
def add_file_row(self, root, item, file_model):
item_name = str(item.filename.name) if hasattr(item, 'filename') else item
item_model = file_model(item_name)
item_model.setData(item, 1001)
item_model.setData(file_model.file_type, 1002)
print(root)
root.appendRow(item_model)
return item_model
def create_structure(self, prepared, files):
structure = {'.': []}
for *parts, file in zip(*prepared, [f.data() for f in files]):
parts = list(filter(lambda x: x is not None, parts))
parent = structure
for part in parts:
if part not in parent:
parent[part] = {'.': []}
parent = parent[part]
parent['.'].append(file)
return structure
def prepare_files(self, data):
groups = [[], [], []]
for file in data:
if type(file) in [PreviewImage, PreviewVideo]:
group = 1
elif isinstance(file, BaseFileModel):
group = 0
else:
group = 2
item = QStandardItem(str(file.filename.name))
item.setData(file)
groups[group].append(item)
return groups
style = f'''
AssetEditFilesView QTreeView {{
{Fonts.normal}
border: 2px;
border-radius: 8px;
background-color: {Colors.asset_edit_bg};
}}
AssetEditFilesView {{
background-color: {Colors.popup_bg};
}}
AssetEditFilesView {{
background-color: {Colors.popup_bg};
'''
if __name__ == '__main__':
files = [
BaseFileModel(filename=r'c:\temp0036.png'),
BaseFileModel(filename=r'c:\temp0036.png'),
BaseFileModel(filename=r'c:\temp0036.png', asset_path=Path('./huita/huyatina')),
BaseFileModel(filename=r'c:\temp0048.png', asset_path=Path('./huita')),
BaseFileModel(filename=r'c:\temp0127.png', asset_path=Path('./ne_huita')),
BaseFileModel(filename=r'c:\temp0229.png', asset_path=Path('./ne_huita/huiyatina')),
PreviewVideo(filename=r'c:\references.gif', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp321.png', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp0267.png', asset_path=Path('./previews/generated')),
PreviewImage(filename=r'c:\temp3.png', asset_path=Path('./previews/generated/1/2')),
PreviewImage(filename=r'c:\temp.bin', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp.bin.png', asset_path=Path('./previews')),
PreviewImage(filename=r'c:\temp.png', asset_path=Path('./previews/generated/2/1')),
PreviewImage(filename=r'c:\temp.png', asset_path=Path('./previews/generated/1/2')),
PreviewImage(filename=r'c:\temp.png', asset_path=Path('./previews/generated/1')),
]
app = QApplication(sys.argv)
setup_fonts(app)
app.setStyleSheet(style)
view = AssetEditFilesView(files)
view.setGeometry(800, 500, 800, 640)
view.setWindowTitle('QTreeview Example')
view.show()
sys.exit(app.exec())
I have 3 entities:
file (should look like on the 1st screenshot (3 checkboxes and an icon))
folder (must contain a name)
title (in fact the folder only the font should be larger)
how do i implement all entities using 1 QStyledItemDelegate?
Can I use a ready-made custom QWidget as a file entity?
the only thing I managed to do was to implement a more or less working sizeHint, but I have no idea how to add a file widget.
I will be grateful for any help, as now I am close to implementing all this through QScrollArea instead of QTreeView
i need some thing like this:
I also can't figure out how to apply styles to a QStandardItem, QStyledItemDelegate,
I would like to make the on-click feature with a circle coming up when clicking on the marker.
So far I've developed the class which includes relevant elements as shown in the code below:
df = pd.read_csv("survey.csv")
class Circle(MacroElement):
def __init__(self):
for i,row in df.iterrows():
rad = int(df.at[i, 'radius'])
def __init__(self,
popup=None
):
super(Circle, self).__init__()
self._name = 'Circle',
self.radius = rad * 1560
self._template = Template(u"""
{% macro script(this, kwargs) %}
var circle_job = L.circle();
function newCircle(e){
circle_job.setLatLng(e.latlng).addTo({{this._parent.get_name()}});
circle_job.setRadius({{this.radius}});
circle_job.setStyle({
color: 'black',
fillcolor: 'black'
});
};
{{this._parent.get_name()}}.on('click', newCircle);
{% endmacro %}
""") # noqa
for i,row in df.iterrows():
lat =df.at[i, 'lat']
lng = df.at[i, 'lng']
sp = df.at[i, 'sp']
phone = df.at[i, 'phone']
role = df.at[i, 'role']
rad = int(df.at[i, 'radius'])
popup = '<b>Phone: </b>' + str(df.at[i,'phone'])
job_range = Circle()
if role == 'Contractor':
fs.add_child(
folium.Marker(location=[lat,lng],
tooltip=folium.map.Tooltip(
text='<strong>Contact surveyor</strong>',
style=("background-color: lightgreen;")),
popup=popup,
icon = folium.Icon(color='darkred', icon='glyphicon-user'
)
)
)
fs.add_child (
folium.Marker(location=[lat,lng],
popup=popup,
icon = folium.DivIcon(html="<b>" + sp + "</b>",
class_name="mapText_contractor",
icon_anchor=(30,5))
#click_action = js_f
)
)
fs.add_child(job_range)
which works but unfortunately takes into account only the very first record.
How could I make these pop-up circles adjusted to the radius of the given input (as presented in the CSV document?)?
You can change the marker template to include a click function. The click function will open the popup and draw a circle. See example below.
import pandas as pd
import numpy as np
import folium
from folium.map import Template, Marker
start_coords = (52.4972664,-2.0037126)
m = folium.Map(start_coords)
def generate_data(start_coords, n):
roles = {0: 'Full', 1: 'Contractor'}
df = pd.DataFrame()
df['lat'] = [start_coords[0]+i for i in np.random.uniform(-1, 1, n)]
df['lon'] = [start_coords[1]+i for i in np.random.uniform(-1, 1, n)]
df['phone'] = [''.join(map(str, i)) for i in np.random.randint(1, 10, (n, 11))]
df['radius'] = [10*i for i in np.random.randint(1, 9, n)]
df['role'] = [roles[i] for i in np.random.randint(0, 2, n)]
return df
# Modify Marker template to include the onClick event
click_template = """{% macro script(this, kwargs) %}
var {{ this.get_name() }} = L.marker(
{{ this.location|tojson }},
{{ this.options|tojson }}
).addTo({{ this._parent.get_name() }}).on('click', addCircle).on('popupclose', removeCircle);
{% endmacro %}"""
# Change template to custom template
Marker._template = Template(click_template)
map_id = m.get_name()
#Add javascript snippet to draw cricles
click_js = f"""function removeCircle() {{
{map_id}.eachLayer(function(layer){{
if (layer instanceof L.Circle)
{{ {map_id}.removeLayer(layer) }}
}});
}}
function addCircle(e) {{
coords = e.latlng
var radius = e.target.options.radius * 1560;
var color = e.target.options.circleColor;
removeCircle() //remove existing circles
var circle = L.circle([coords.lat,coords.lng], {{radius: radius}}).addTo({map_id})
circle.setStyle({{
color: color,
fillcolor: color
}});
}}"""
e = folium.Element(click_js)
html = m.get_root()
html.script.add_child(e)
n = 10 # number of markers
df = generate_data(start_coords, n)
for i, row in df.iterrows():
popup = f'<b>Phone: {row["phone"]}</b>'
icon_color = 'darkred' if row['role'] == 'Contractor' else 'black'
folium.Marker([row['lat'], row['lon']],
popup=popup,
icon = folium.Icon(color=icon_color, icon='glyphicon-user'),
radius=row['radius'],
circleColor=icon_color
).add_to(m)
m.fit_bounds(m.get_bounds())
m
I would like to use the folium.CLickFormarker macro more than once in my map. Unfortunately it doesn't work, as my function takes only the first one.
fs=folium.FeatureGroup(name="Surveyors")
df = pd.read_csv("survey.csv")
class Circle(folium.ClickForMarker):
_template = Template(u"""
{% macro script(this, kwargs) %}
var circle_job = L.circle();
function newMarker(e){
circle_job.setLatLng(e.latlng).addTo({{this._parent.get_name()}});
circle_job.setRadius(50000);
circle_job.bringToFront();
};
{{this._parent.get_name()}}.on('click', newMarker);
{% endmacro %}
""") # noqa
def __init__(self, popup=None):
super(Circle, self).__init__()
self._name = 'Circle'
job_range2 = Circle()
class ClickForOneMarker(folium.ClickForMarker):
_template = Template(u"""
{% macro script(this, kwargs) %}
var new_mark = L.marker();
function newMarker(e){
new_mark.setLatLng(e.latlng).addTo({{this._parent.get_name()}});
new_mark.setZIndexOffset(-1);
new_mark.on('dblclick', function(e){
{{this._parent.get_name()}}.removeLayer(e.target)})
var lat = e.latlng.lat.toFixed(4),
lng = e.latlng.lng.toFixed(4);
new_mark.bindPopup("<a href=https://www.google.com/maps?layer=c&cbll=" + lat + "," + lng + " target=blank><img src=img/streetview.svg width=150 title=StreetView></img></a>")//.openPopup();
};
{{this._parent.get_name()}}.on('click', newMarker);
{% endmacro %}
""") # noqa
def __init__(self, popup=None):
super(ClickForOneMarker, self).__init__()
self._name = 'ClickForOneMarker'
click_for_marker = ClickForOneMarker()
map.add_child(click_for_marker)
for i,row in df.iterrows():
lat =df.at[i, 'lat']
lng = df.at[i, 'lng']
sp = df.at[i, 'sp']
phone = df.at[i, 'phone']
role = df.at[i, 'role']
rad = int(df.at[i, 'radius'])
popup = '<b>Phone: </b>' + str(df.at[i,'phone'])
order_rng = folium.Circle([lat,lng],
radius=rad * 10.560,
popup= df.at[i, 'sp'],
tooltip = sp + ' - Job limits',
color='black',
fill=True,
fill_color='black',
opacity=0.1,
fill_opacity=0.1
)
if role == 'Contractor':
fs.add_child(
folium.Marker(location=[lat,lng],
tooltip=folium.map.Tooltip(
text='<strong>Contact surveyor</strong>',
style=("background-color: lightgreen;")),
popup=popup,
icon = folium.Icon(color='darkred', icon='glyphicon-user'
)
)
)
fs.add_child (
folium.Marker(location=[lat,lng],
popup=popup,
icon = folium.DivIcon(html="<b>" + sp + "</b>",
class_name="mapText_contractor",
icon_anchor=(30,5))
)
)
fs.add_child(job_range)
The first one is just what I want to include as the child for the existing feature group. The second one should be applicable to the entire map.
Both don't work when included together. Is it folium limited to one ClickForMarker macro or something?
The primary error was using the same JavaScript function newMarker(e), which created the conflict.
I've provided the other function for my second case and it has started working correctly.
class ClickForOneMarker(folium.ClickForMarker):
_template = Template(u"""
{% macro script(this, kwargs) %}
var new_mark = L.marker();
function newMarker(e){
new_mark.setLatLng(e.latlng).addTo({{this._parent.get_name()}});
new_mark.setZIndexOffset(-1);
new_mark.on('dblclick', function(e){
{{this._parent.get_name()}}.removeLayer(e.target)})
var lat = e.latlng.lat.toFixed(4),
lng = e.latlng.lng.toFixed(4);
new_mark.bindPopup("<a href=https://www.google.com/maps?layer=c&cbll=" + lat + "," + lng + " target=blank><img src=img/streetview.svg width=150 title=StreetView></img></a>")//.openPopup();
};
{{this._parent.get_name()}}.on('click', newMarker);
{% endmacro %}
""") # noqa
def __init__(self, popup=None):
super(ClickForOneMarker, self).__init__()
self._name = 'ClickForOneMarker'
click_for_marker = ClickForOneMarker()
map.add_child(click_for_marker)
class Circle(folium.ClickForMarker):
_template = Template(u"""
{% macro script(this, kwargs) %}
var circle_job = L.circle();
function newCircle(e){
circle_job.setLatLng(e.latlng).addTo({{this._parent.get_name()}});
circle_job.setRadius(50000);
circle_job.bringToFront();
};
{{this._parent.get_name()}}.on('click', newCircle);
{% endmacro %}
""") # noqa
def __init__(self, popup=None):
super(Circle, self).__init__()
self._name = 'Circle'
job_range = Circle()
When you hover over the legend, all lines except the corresponding line will be hidden. How to achieve this via bokeh, python or javascript. I have no idea what to do to achieve this function. It would be great if we could provide a simple example with three lines.Thanks for your help.My code example is as follows:
import bokeh.palettes as bp
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.models import LinearAxis, Range1d, NumeralTickFormatter, Legend
from bokeh.layouts import column
import numpy as np
if __name__ == '__main__':
num = 5
color_list2 = bp.magma(num)
color_list1 = bp.viridis(num)
plotTools = 'box_zoom, wheel_zoom, pan, tap, crosshair, hover, reset, save'
p = figure(plot_width=800, plot_height=400, x_range=(0, 1000), y_range=(-2.5, -1.5),
tools=plotTools, toolbar_location='right', active_scroll='wheel_zoom', )
p.title.text = 'Hover and Hide'
items_c1 = []
i = 0
pictures = []
labels = ['a', 'b', 'c', 'd', 'e']
for label in labels:
n = np.random.randint(low=3, high=6)
xs = np.random.random(n) * 1000
y1s = np.random.random(n) - 2.5
temp_line = p.line(xs, y1s, line_width=2, color=color_list1[i % num],
alpha=0.3, hover_color='red', hover_alpha=0.9) # , legend_label=label
items_c1.append((label + '_BER', [temp_line]))
i = i + 1
if i % num == 0:
legend_1 = Legend(items=items_c1)
p.add_layout(legend_1, 'left')
p.xaxis.axis_label = 'run_time'
p.yaxis[0].axis_label = 'BER'
p.legend[0].orientation = 'vertical'
p.legend[0].location = 'bottom_center'
p.legend[0].click_policy = 'hide'
pictures.append(p)
p = figure(plot_width=800, plot_height=400, x_range=(0, 1000), y_range=(-2.5, -1.5),
tools=plotTools, toolbar_location='right', active_scroll='wheel_zoom', )
items_c1 = []
file = "test_ask_5"
file_path = file + '.html'
output_file(file_path)
show(column(pictures))
The solution below hides all lines but the one that is being clicked (not hovered). This code works for Bokeh v1.3.4
import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import CustomJS
colors = ['orange', 'cyan', 'lightgreen']
p = figure()
lines = [p.line(np.arange(10), np.random.random(10), line_color = colors[i], line_width = 3, legend=colors[i], name=colors[i]) for i in range(3)]
code = '''if(Bokeh != 'undefined' && (Bokeh.active_line === 'undefined' || Bokeh.active_line == null))
{
Bokeh.active_line = cb_obj.name;
}'''
[line.js_on_change('visible', CustomJS(code = code)) for line in lines]
code = ''' for(i = 0; i < lines.length; i++) {
if (lines[i].name == Bokeh.active_line) {
lines[i].visible = true
}
else {
lines[i].visible = false
}
}
Bokeh.active_line = null'''
p.js_on_event('tap', CustomJS(args = {'lines' : lines}, code = code))
code = ''' for(i = 0; i < lines.length; i++) {
lines[i].visible = true
}
Bokeh.active_line = null'''
p.js_on_event('reset', CustomJS(args = dict(lines = lines), code = code))
p.legend.click_policy = 'hide'
show(p)
The first callback is applied to all glyph renderers (lines) and is being triggered when the line must be hidden, that is when a user clicks a legend item. The callback just sets the global variable Bokeh.active_line which remembers the renderer (line) name, e.g. "orange" or "cyan"
The second callback is attached to the plot canvas and is triggered every time the user clicks anywhere on the plot. What is basically does is inverting the glyphs (lines) visibility. It only shows the line specified by
Bokeh.active_line
The third callback is attached to the plot and is triggered when user clicks on the reset tool in the toolbar. It restores visibility of all lines.
I want to pass the data from user input in the template create_proposal.html. The form is inside a dynamically generated table by JavaScript. Each row has cells that have input. The table inside create_proposal.html looks like:
<table id="empTable" class="table-striped" border="1" cellmargin="100px" cellpadding="0px"cellspacing="5px">
<tr>
<th>
<h5></h5>
</th>
<th>
<h5>No.</h5>
</th>
<th>
<h5>Part no.</h5>
</th>
<th style="width:30vw">
<h5>Description</h5>
</th>
<th>
<h5>Quantity</h5>
</th>
<th>
<h5>Unit Market price</h5>
</th>
<th>
<h5>Markup percentage</h5>
</th>
</tr>
</table>
And the script that generates the rows:
<script>
// ARRAY FOR HEADER.
var arrHead = new Array();
arrHead = ['', 'No.', 'Part no.', 'Description', 'Quantity', 'Unit market price', 'Markup percentage'];
// ADD A NEW ROW TO THE TABLE.s
function addRow() {
var empTab = document.getElementById('empTable');
var rowCnt = empTab.rows.length; // GET TABLE ROW COUNT.
var tr = empTab.insertRow(rowCnt); // TABLE ROW.
tr = empTab.insertRow(rowCnt);
for (var c = 0; c < arrHead.length; c++) {
var td = document.createElement('td'); // TABLE DEFINITION.
td = tr.insertCell(c);
if (c == 0) { // FIRST COLUMN.
// ADD A BUTTON.
var button = document.createElement('input');
// SET INPUT ATTRIBUTE.
button.setAttribute('type', 'button');
button.setAttribute('value', 'Remove');
button.setAttribute('class', 'btn btn-danger');
// ADD THE BUTTON's 'onclick' EVENT.
button.setAttribute('onclick', 'removeRow(this)');
td.appendChild(button);
}
else if(c == 1){
var ele = document.createElement('input');
ele.setAttribute('type', 'number');
ele.setAttribute('id', 'id_item_no');
ele.setAttribute('name', 'item_no');
td.appendChild(ele);
}
else if(c == 2){
var ele = document.createElement('input');
ele.setAttribute('type', 'text');
ele.setAttribute('id', 'id_part_no');
ele.setAttribute('name', 'part_no');
td.appendChild(ele);
}
else if(c == 3){
var ele = document.createElement('input');
ele.setAttribute('type', 'text');
ele.setAttribute('id', 'id_description');
ele.setAttribute('name', 'description');
td.appendChild(ele);
}
else if(c == 4){
var ele = document.createElement('input');
ele.setAttribute('type', 'number');
ele.setAttribute('id', 'id_quantity');
ele.setAttribute('name', 'quantity');
td.appendChild(ele);
}
else if(c == 5){
var ele = document.createElement('input');
ele.setAttribute('type', 'number');
ele.setAttribute('id', 'id_unit_market_price');
ele.setAttribute('name', 'unit_market_price');
td.appendChild(ele);
}
else if(c == 6){
var ele = document.createElement('input');
ele.setAttribute('type', 'number');
ele.setAttribute('name', 'markup_percentage');
ele.setAttribute('id', 'id_markup_percentage');
td.appendChild(ele);
}
}
}
// DELETE TABLE ROW.
function removeRow(oButton) {
var empTab = document.getElementById('empTable');
empTab.deleteRow(oButton.parentNode.parentNode.rowIndex); // BUTTON -> TD -> TR.
}
</script>
in my views.py I call a function called merge() which accepts parameters from the user input. The function in view.py that calls the merge() function looks like:
def resultPage(request):
if request.method == 'POST':
form =createNewFinancial(request.POST)
if form.is_valid():
name = form.cleaned_data['file_name']
organization = form.cleaned_data['organization_name']
project_type = form.cleaned_data['project_type']
reference = form.cleaned_data['reference_number']
our_ref_no = form.cleaned_data['our_reference_number']
date = form.cleaned_data['date']
item_no = form.cleaned_data['item_no']
part_no = form.cleaned_data['part_no']
description = form.cleaned_data['description']
quantity = form.cleaned_data['quantity']
unit_market_price = form.cleaned_data['unit_market_price']
markup_percentage = form.cleaned_data['markup_percentage']
merge(name, organization, project_type, reference, our_ref_no, date, item_no, part_no, description, quantity, unit_market_price, markup_percentage)
return render(request, 'result.html')
and the merge() function:
def merge(name, organization, project_type, reference, our_reference, date, item_no, part_no, description, quantity, unit_market_price, markup_percentage):
template = 'master.docx'
document = MailMerge(template)
unit_price = calculateUitPrice(unit_market_price, markup_percentage)
total_price = quantity * unit_price
document.merge(
organization = organization,
project_type = project_type,
reference = reference,
our_ref_no = our_reference,
date = date,
)
item_table = [{
'item_no' : str(item_no),
'part_no' : str(part_no),
'description' : str(description),
'quantity' : str(quantity),
'unit_price' : str(unit_price),
'total_price' : str(total_price)
},]
document.merge_rows('item_no', item_table)
document.write(name+'.docx')
there are multiple rows that has input fields with the same id. I wanted to pass the values in an array but do not know how. Any working solution would also be nice.
Thanks