Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: default template import export #208

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
"author": "Frappe Technologies Pvt. Ltd.",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@interactjs/interact": "^1.10.17",
"@interactjs/actions": "^1.10.17",
"@interactjs/auto-start": "^1.10.17",
"@interactjs/interact": "^1.10.17",
"@interactjs/modifiers": "^1.10.17",
"html2canvas": "^1.4.1",
"pdfjs-dist": "v3.4.120"
}
}
14 changes: 14 additions & 0 deletions print_designer/custom_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,19 @@
"fieldtype": "JSON",
"label": "Print Designer Settings",
},
{
"fieldname": "print_designer_preview_img",
"hidden": 1,
"fieldtype": "Attach Image",
"label": "Print Designer Preview Image",
},
{
"depends_on": "eval:doc.print_designer && doc.standard == 'Yes'",
"fieldname": "print_designer_template_app",
"fieldtype": "Select",
"label": "Print Designer Template Location",
"default": "print_designer",
"insert_after": "standard",
},
]
}
117 changes: 117 additions & 0 deletions print_designer/default_formats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import os
import shutil
from pathlib import Path

import frappe
from frappe.modules.import_file import import_file_by_path
from frappe.utils import get_files_path

"""
features:
- Print Designer App can have default formats for all installed apps.
- Any Custom/Standard App can have default formats for any installed apps
( This will only install formats if print_designer is installed ).
- This will be useful when we have standalone formats that can be used without print designer app.

when print_designer app is installed
- get hooks from all installed apps including pd and load default formats from defined folders.

when any new app is installed
- if exists in print_designer/default_templates, load default formats for newly installed app.
- get hooks from new app and load default formats for all installed apps from app's format dir.
"""

# TODO: handle override of default formats from different apps or even Custom Formats with same name.
maharshivpatel marked this conversation as resolved.
Show resolved Hide resolved

# add default formats for all installed apps.
def on_print_designer_install():
for app in frappe.get_installed_apps():
install_default_formats(app=app, load_pd_formats=False)


def get_preview_image_folder_path(print_format):
app = frappe.scrub(frappe.get_doctype_app(print_format.doc_type))
pd_folder = frappe.get_hooks(
"pd_standard_format_folder", app_name=print_format.print_designer_template_app
)
if len(pd_folder) == 0:
pd_folder = ["default_templates"]
return os.path.join(
frappe.get_app_path(print_format.print_designer_template_app), os.path.join(pd_folder[0], app)
)


def update_preview_img(file):
print_format = frappe.get_doc(file.attached_to_doctype, file.attached_to_name)
folder = get_preview_image_folder_path(print_format)
file_name = print_format.print_designer_preview_img.split("/")[-1]
org_path = os.path.join(folder, file_name)
target_path = get_files_path(file_name, is_private=1)
shutil.copy2(org_path, target_path)


# called after install of any new app.
def install_default_formats(app, filter_by="", load_pd_formats=True):
if load_pd_formats:
# load formats from print_designer app if some new app is installed and have default formats
install_default_formats(app="print_designer", filter_by=app, load_pd_formats=False)

# get dir path and load formats from installed app
pd_folder = frappe.get_hooks("pd_standard_format_folder", app_name=app)
if len(pd_folder) == 0:
return

print_formats = get_filtered_formats_by_app(
app=app, templates_folder=pd_folder[0], filter_by=filter_by
)

# preview_files = [f for f in print_formats if f.name.endswith("-preview.json")]
print_formats = [f for f in print_formats if not f.name.endswith("-preview.json")]

for json_file_path in print_formats:
import_file_by_path(path=json_file_path)
frappe.db.commit()
# TODO: enable this after this is released in v15 https://github.com/frappe/frappe/pull/25779
# for json_file_path in preview_files:
# import_file_by_path(path=json_file_path, pre_process=update_preview_img)
# frappe.db.commit()

# for pf in frappe.db.get_all("Print Format", filters={"standard": "Yes", "print_designer": 1}):
# updated_url = frappe.db.get_value(
# "File",
# {
# "attached_to_doctype": "Print Format",
# "attached_to_name": pf.name,
# "attached_to_field": "print_designer_preview_img",
# },
# "file_url",
# )
# if updated_url:
# frappe.set_value("Print Format", pf.name, "print_designer_preview_img", updated_url)


def get_filtered_formats_by_app(app, templates_folder, filter_by=""):
app_path = frappe.get_app_path(app)
if filter_by == "":
folders = Path(os.path.join(app_path, templates_folder))
return get_formats_from_folders(folders=folders)
else:
folder = Path(os.path.join(app_path, templates_folder, filter_by))
return get_json_files(folder)


def get_formats_from_folders(folders):
formats = set()
if not folders.exists():
return formats
for folder in folders.iterdir():
if folder.is_dir() and folder.name in frappe.get_installed_apps():
formats.update(get_json_files(folder))
return formats


def get_json_files(folder):
formats = set()
for json_file in folder.glob("*.json"):
formats.add(json_file)
return formats
9 changes: 6 additions & 3 deletions print_designer/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@

before_install = "print_designer.install.before_install"
after_install = "print_designer.install.after_install"
after_app_install = "print_designer.install.after_app_install"

# Uninstallation
# ------------
Expand Down Expand Up @@ -110,10 +111,12 @@
# ---------------
# Override standard doctype classes

# override_doctype_class = {
# "ToDo": "custom_app.overrides.CustomToDo"
# }
override_doctype_class = {
"Print Format": "print_designer.print_designer.overrides.print_format.PDPrintFormat",
}

# Path Relative to the app folder where default templates should be stored
pd_standard_format_folder = "default_templates"
# Document Events
# ---------------
# Hook on document methods and events
Expand Down
7 changes: 7 additions & 0 deletions print_designer/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields

from print_designer.custom_fields import CUSTOM_FIELDS
from print_designer.default_formats import install_default_formats, on_print_designer_install


def check_frappe_version():
Expand All @@ -27,3 +28,9 @@ def before_install():

def after_install():
create_custom_fields(CUSTOM_FIELDS, ignore_validate=True)
on_print_designer_install()


def after_app_install(app):
if app != "print_designer":
install_default_formats(app)
1 change: 1 addition & 0 deletions print_designer/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ print_designer.patches.introduce_suffix_dynamic_content
print_designer.patches.introduce_dynamic_containers
print_designer.patches.introduce_dynamic_height
print_designer.patches.remove_unused_rectangle_gs_properties
execute:from print_designer.patches.create_custom_fields import custom_field_patch; custom_field_patch()
7 changes: 7 additions & 0 deletions print_designer/patches/create_custom_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields

from print_designer.custom_fields import CUSTOM_FIELDS


def custom_field_patch():
create_custom_fields(CUSTOM_FIELDS, ignore_validate=True)
10 changes: 10 additions & 0 deletions print_designer/print_designer/client_scripts/print_format.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
const set_template_app_options = (frm) => {
frappe.xcall("frappe.core.doctype.module_def.module_def.get_installed_apps").then((r) => {
frm.set_df_property("print_designer_template_app", "options", JSON.parse(r));
if (!frm.doc.print_designer_template_app) {
frm.set_value("print_designer_template_app", "print_designer");
}
});
};

frappe.ui.form.on("Print Format", {
refresh: function (frm) {
frm.trigger("render_buttons");
set_template_app_options(frm);
},
render_buttons: function (frm) {
frm.page.clear_inner_toolbar();
Expand Down
84 changes: 84 additions & 0 deletions print_designer/print_designer/overrides/print_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import shutil

import frappe
from frappe.modules.utils import scrub
from frappe.printing.doctype.print_format.print_format import PrintFormat


class PDPrintFormat(PrintFormat):
def export_doc(self):
if (
not self.standard == "Yes"
or not frappe.conf.developer_mode
or frappe.flags.in_patch
or frappe.flags.in_install
or frappe.flags.in_migrate
or frappe.flags.in_import
or frappe.flags.in_setup_wizard
):
return

if not self.print_designer:
return super().export_doc()

self.write_document_file()

def write_document_file(self):
doc = self
doc_export = doc.as_dict(no_nulls=True)
doc.run_method("before_export", doc_export)

# create folder
folder = self.create_folder(doc.doc_type, doc.name)

fname = scrub(doc.name)

# write the data file
path = os.path.join(folder, f"{fname}.json")
with open(path, "w+") as json_file:
json_file.write(frappe.as_json(doc_export))
print(f"Wrote document file for {doc.doctype} {doc.name} at {path}")
self.export_preview(folder=folder, fname=fname)

def create_folder(self, dt, dn):
app = scrub(frappe.get_doctype_app(dt))
dn = scrub(dn)
pd_folder = frappe.get_hooks(
"pd_standard_format_folder", app_name=self.print_designer_template_app
)
if len(pd_folder) == 0:
pd_folder = ["default_templates"]
folder = os.path.join(
frappe.get_app_path(self.print_designer_template_app), os.path.join(pd_folder[0], app)
)
frappe.create_folder(folder)
return folder

def export_preview(self, folder, fname):
if self.print_designer_preview_img:
try:
file = frappe.get_doc(
"File",
{
"file_url": self.print_designer_preview_img,
"attached_to_doctype": self.doctype,
"attached_to_name": self.name,
"attached_to_field": "print_designer_preview_img",
},
)
except frappe.DoesNotExistError:
file = None
if not file:
return
file_export = file.as_dict(no_nulls=True)
file.run_method("before_export", file_export)
org_path = file.get_full_path()
target_path = os.path.join(folder, org_path.split("/")[-1])
shutil.copy2(org_path, target_path)
print(f"Wrote preview file for {self.doctype} {self.name} at {target_path}")
# write the data file
path = os.path.join(folder, f"print_designer-{fname}-preview.json")
with open(path, "w+") as json_file:
json_file.write(frappe.as_json(file_export))
print(f"Wrote document file for {file.doctype} {file.name} at {path}")
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
{% macro image(element) -%}
{%- if element.image.file_url -%}
{%- set value = element.image.file_url -%}
{%- elif element.image.fieldname -%}
{%- if element.image.parent == doc.doctype -%}
{%- set value = doc.get(element.image.fieldname) -%}
{%- else -%}
{%- set value = frappe.db.get_value(element.image.doctype, doc[element.image.parentField], element.image.fieldname) -%}
{%- endif -%}
{%- else -%}
{%- set value = "" -%}
{%- endif -%}

{%- if value -%}
<div
style="position: absolute; top:{{ element.startY }}px; left:{{ element.startX }}px;width:{{ element.width }}px;height:{{ element.height }}px;
{{convert_css(element.style)}}"
class="image {{ element.classes | join(' ') }}"
>
<div
style="width:100%;height:100%;
background-image: url('{{frappe.get_url()}}{%if element.isDynamic %}{{element.image.value}}{% elif not element.isDynamic%}{{element.image.file_url}}{% endif %}');
"
style="width:100%; height:100%; background-image: url('{{frappe.get_url()}}{{value}}');"
class="image {{ element.classes | join(' ') }}"
></div>
</div>
{%- endif -%}
{%- endmacro %}
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,33 @@
</div>
{%- endmacro %}
{% macro render_image(element) -%}

{%- if element.image.file_url -%}
{%- set value = element.image.file_url -%}
{%- elif element.image.fieldname -%}
{%- if element.image.parent == doc.doctype -%}
{%- set value = doc.get(element.image.fieldname) -%}
{%- else -%}
{%- set value = frappe.db.get_value(element.image.doctype, doc[element.image.parentField], element.image.fieldname) -%}
{%- endif -%}
{%- else -%}
{%- set value = "" -%}
{%- endif -%}

{%- if value -%}
<div
style="position: absolute; top:{% if 'printY' in element %}{{ element.printY }}{% else %}{{ element.startY }}{% endif %}px; left:{% if 'printX' in element %}{{ element.printX }}{% else %}{{ element.startX }}{% endif %}px;width:{{ element.width }}px;height:{{ element.height }}px;
{{convert_css(element.style)}}"
class="image {{ element.classes | join(' ') }}"
>
<div
style="width:100%;height:100%;
background-image: url('{{frappe.get_url()}}{%if element.isDynamic %}{{element.image.value}}{% elif not element.isDynamic%}{{element.image.file_url}}{% endif %}');
background-image: url('{{frappe.get_url()}}{{value}}');
"
class="image {{ element.classes | join(' ') }}"
></div>
</div>
{%- endif -%}
{%- endmacro %}
{% macro render_barcode(element, send_to_jinja) -%}
{%- set field = element.dynamicContent[0] -%}
Expand Down
Loading
Loading