Skip to content

Commit

Permalink
feat: default template import export (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
maharshivpatel authored Apr 3, 2024
2 parents 1edee6a + f911516 commit bdf3e7c
Show file tree
Hide file tree
Showing 14 changed files with 582 additions and 66 deletions.
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.

# 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

0 comments on commit bdf3e7c

Please sign in to comment.