feat: Field Templates

- standard field templates for rendering custom templates for fields for e.g., taxes in invoice
- ability to create user defined reusable templates that show up in print format builder
This commit is contained in:
Faris Ansari 2021-10-19 19:12:15 +05:30
parent c31bf0aadc
commit 76b5ee8b9e
12 changed files with 237 additions and 4 deletions

View file

@ -11,6 +11,14 @@ from frappe.utils.weasyprint import get_html, download_pdf
from frappe.model.document import Document
class PrintFormat(Document):
def onload(self):
templates = frappe.db.get_all(
"Print Format Field Template",
fields=["template", "field", "name"],
filters={"document_type": self.doc_type},
)
self.set_onload("print_templates", templates)
def get_html(self, docname, letterhead=None):
return get_html(self.doc_type, docname, self.name, letterhead)

View file

@ -0,0 +1,8 @@
// Copyright (c) 2021, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Print Format Field Template', {
// refresh: function(frm) {
// }
});

View file

@ -0,0 +1,101 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "Prompt",
"creation": "2021-10-05 14:23:56.508499",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"document_type",
"field",
"template_file",
"column_break_3",
"module",
"standard",
"section_break_5",
"template"
],
"fields": [
{
"depends_on": "eval:!doc.multiple",
"fieldname": "document_type",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Document Type",
"mandatory_depends_on": "eval:!doc.multiple",
"options": "DocType",
"reqd": 1
},
{
"fieldname": "field",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Default Template For Field"
},
{
"depends_on": "eval:!doc.standard",
"fieldname": "template",
"fieldtype": "Code",
"label": "Template",
"mandatory_depends_on": "eval:!doc.standard",
"options": "HTML"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hide_border": 1
},
{
"depends_on": "standard",
"fieldname": "module",
"fieldtype": "Link",
"label": "Module",
"options": "Module Def"
},
{
"default": "0",
"fieldname": "standard",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Standard"
},
{
"depends_on": "eval:doc.standard",
"fieldname": "template_file",
"fieldtype": "Data",
"label": "Template File",
"mandatory_depends_on": "eval:doc.standard"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-10-19 17:47:59.577949",
"modified_by": "Administrator",
"module": "Printing",
"name": "Print Format Field Template",
"naming_rule": "Set by user",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View file

@ -0,0 +1,43 @@
# Copyright (c) 2021, Frappe Technologies and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from frappe import _
class PrintFormatFieldTemplate(Document):
def validate(self):
if self.standard and not (frappe.conf.developer_mode or frappe.flags.in_patch):
frappe.throw(_("Enable developer mode to create a standard Print Template"))
def before_insert(self):
self.validate_duplicate()
def on_update(self):
self.validate_duplicate()
self.export_doc()
def validate_duplicate(self):
if not self.standard:
return
if not self.field:
return
filters = {"document_type": self.document_type, "field": self.field}
if not self.is_new():
filters.update({"name": ("!=", self.name)})
result = frappe.db.get_all("Print Format Field Template", filters=filters, limit=1)
if result:
frappe.throw(
_("A template already exists for field {0} of {1}").format(
frappe.bold(self.field), frappe.bold(self.document_type)
),
frappe.DuplicateEntryError,
title=_("Duplicate Entry"),
)
def export_doc(self):
from frappe.modules.utils import export_module_json
export_module_json(self, self.standard, self.module)

View file

@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies and Contributors
# See license.txt
# import frappe
import unittest
class TestPrintFormatFieldTemplate(unittest.TestCase):
pass

View file

@ -7,6 +7,12 @@
v-if="df.fieldtype == 'HTML' && df.html"
v-html="df.html"
></div>
<div
class="custom-html"
v-if="df.fieldtype == 'Field Template'"
>
{{ df.label }}
</div>
<input
v-else-if="editing && df.fieldtype != 'HTML'"
ref="label-input"

View file

@ -132,7 +132,8 @@ export default {
"fieldtype",
"options",
"table_columns",
"html"
"html",
"field_template"
]);
if (cloned.custom) {
// generate unique fieldnames for custom blocks
@ -196,8 +197,35 @@ export default {
fieldname: "name",
fieldtype: "Data"
},
...this.print_templates,
...fields
];
},
print_templates() {
let templates = this.print_format.__onload.print_templates || {};
let out = [];
for (let template of templates) {
let df;
if (template.field) {
df = frappe.meta.get_docfield(
this.meta.name,
template.field
);
} else {
df = {
label: template.name,
fieldname: frappe.scrub(template.name)
};
}
out.push({
label: `${__(df.label)} (${__("Field Template")})`,
fieldname: df.fieldname + "_template",
fieldtype: "Field Template",
field_template: template.name
});
}
return out;
},
page_number_positions() {
return [
{ label: __("Hide"), value: "Hide" },

View file

@ -88,7 +88,8 @@ export function getStore(print_format_name) {
"fieldname",
"fieldtype",
"options",
"width"
"width",
"field_template",
]);
}
);
@ -99,7 +100,8 @@ export function getStore(print_format_name) {
"fieldtype",
"options",
"table_columns",
"html"
"html",
"field_template",
]);
});
return column;

View file

@ -63,6 +63,17 @@ export function create_default_layout(meta) {
options: df.options
};
let field_template = get_field_template(
print_format,
df.fieldname
);
if (field_template) {
field.label = `${__(df.label)} (${__("Field Template")})`;
field.fieldtype = "Field Template";
field.field_template = field_template.name;
field.fieldname = df.fieldname = "_template";
}
if (df.fieldtype === "Table") {
field.table_columns = get_table_columns(df);
}
@ -109,6 +120,15 @@ export function get_table_columns(df) {
return table_columns;
}
function get_field_template(print_format, fieldname) {
let templates = print_format.__onload.print_templates || {};
for (let template of templates) {
if (template.field === fieldname) {
return template;
}
}
return null;
}
export function pluck(object, keys) {
let out = {};
for (let key of keys) {

View file

@ -0,0 +1,4 @@
<div class="field-template" {{ field_attributes(df) }}>
{% set template = frappe.db.get_value('Print Format Field Template', df.field_template, ['template', 'template_file', 'standard'], as_dict=1) %}
{{ frappe.render_template(template.template_file if template.standard else template.template, {'doc': doc}) }}
</div>

View file

@ -198,12 +198,17 @@ class PrintFormatGenerator:
return layout
def set_field_renderers(self, layout):
renderers = {"HTML Editor": "HTML", "Markdown Editor": "Markdown"}
renderers = {
"HTML Editor": "HTML",
"Markdown Editor": "Markdown",
"Field Template": "FieldTemplate",
}
for section in layout["sections"]:
for column in section["columns"]:
for df in column["fields"]:
fieldtype = df["fieldtype"]
df["renderer"] = renderers.get(fieldtype) or fieldtype
df["section"] = section
return layout
def process_margin_texts(self, layout):