Merge pull request #8267 from gavindsouza/doctype-fix
refactor: improvement to doctype API
This commit is contained in:
commit
2eebc463db
3 changed files with 111 additions and 19 deletions
|
|
@ -1,14 +1,19 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
# imports - standard imports
|
||||
from __future__ import unicode_literals
|
||||
import re, copy, os, shutil
|
||||
import json
|
||||
|
||||
# imports - third party imports
|
||||
import six
|
||||
from six import iteritems
|
||||
|
||||
import re, copy, os, subprocess
|
||||
# imports - module imports
|
||||
import frappe
|
||||
import frappe.website.render
|
||||
from frappe import _
|
||||
|
||||
from frappe.utils import now, cint
|
||||
from frappe.model import no_value_fields, default_fields, data_fieldtypes, table_fields
|
||||
from frappe.model.document import Document
|
||||
|
|
@ -19,9 +24,7 @@ from frappe.modules import make_boilerplate, get_doc_path
|
|||
from frappe.database.schema import validate_column_name, validate_column_length
|
||||
from frappe.model.docfield import supports_translation
|
||||
from frappe.modules.import_file import get_file_path
|
||||
from six import iteritems
|
||||
import frappe.website.render
|
||||
import json
|
||||
|
||||
|
||||
class InvalidFieldNameError(frappe.ValidationError): pass
|
||||
class UniqueFieldnameError(frappe.ValidationError): pass
|
||||
|
|
@ -33,10 +36,12 @@ class NonUniqueError(frappe.ValidationError): pass
|
|||
class CannotIndexedError(frappe.ValidationError): pass
|
||||
class CannotCreateStandardDoctypeError(frappe.ValidationError): pass
|
||||
|
||||
|
||||
form_grid_templates = {
|
||||
"fields": "templates/form_grid/fields.html"
|
||||
}
|
||||
|
||||
|
||||
class DocType(Document):
|
||||
def get_feed(self):
|
||||
return self.name
|
||||
|
|
@ -90,6 +95,7 @@ class DocType(Document):
|
|||
if self.default_print_format and not self.custom:
|
||||
frappe.throw(_('Standard DocType cannot have default print format, use Customize Form'))
|
||||
|
||||
|
||||
def set_default_in_list_view(self):
|
||||
'''Set default in-list-view for first 4 mandatory fields'''
|
||||
if not [d.fieldname for d in self.fields if d.in_list_view]:
|
||||
|
|
@ -100,12 +106,14 @@ class DocType(Document):
|
|||
cnt += 1
|
||||
if cnt == 4: break
|
||||
|
||||
|
||||
def set_default_translatable(self):
|
||||
'''Ensure that non-translatable never will be translatable'''
|
||||
for d in self.fields:
|
||||
if d.translatable and not supports_translation(d.fieldtype):
|
||||
d.translatable = 0
|
||||
|
||||
|
||||
def check_developer_mode(self):
|
||||
"""Throw exception if not developer mode or via patch"""
|
||||
if frappe.flags.in_patch or frappe.flags.in_test:
|
||||
|
|
@ -114,6 +122,7 @@ class DocType(Document):
|
|||
if not frappe.conf.get("developer_mode") and not self.custom:
|
||||
frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType."), CannotCreateStandardDoctypeError)
|
||||
|
||||
|
||||
def setup_fields_to_fetch(self):
|
||||
'''Setup query to update values for newly set fetch values'''
|
||||
try:
|
||||
|
|
@ -158,18 +167,21 @@ class DocType(Document):
|
|||
)
|
||||
)
|
||||
|
||||
|
||||
def update_fields_to_fetch(self):
|
||||
'''Update fetch values based on queries setup'''
|
||||
if self.flags.update_fields_to_fetch_queries and not self.issingle:
|
||||
for query in self.flags.update_fields_to_fetch_queries:
|
||||
frappe.db.sql(query)
|
||||
|
||||
|
||||
def validate_document_type(self):
|
||||
if self.document_type=="Transaction":
|
||||
self.document_type = "Document"
|
||||
if self.document_type=="Master":
|
||||
self.document_type = "Setup"
|
||||
|
||||
|
||||
def validate_website(self):
|
||||
"""Ensure that website generator has field 'route'"""
|
||||
if self.has_web_view:
|
||||
|
|
@ -180,6 +192,7 @@ class DocType(Document):
|
|||
# clear website cache
|
||||
frappe.website.render.clear_cache()
|
||||
|
||||
|
||||
def change_modified_of_parent(self):
|
||||
"""Change the timestamp of parent DocType if the current one is a child to clear caches."""
|
||||
if frappe.flags.in_import:
|
||||
|
|
@ -189,6 +202,7 @@ class DocType(Document):
|
|||
for p in parent_list:
|
||||
frappe.db.sql('UPDATE `tabDocType` SET modified=%s WHERE `name`=%s', (now(), p.parent))
|
||||
|
||||
|
||||
def scrub_field_names(self):
|
||||
"""Sluggify fieldnames if not set from Label."""
|
||||
restricted = ('name','parent','creation','modified','modified_by',
|
||||
|
|
@ -218,6 +232,7 @@ class DocType(Document):
|
|||
# unique is automatically an index
|
||||
if d.unique: d.search_index = 0
|
||||
|
||||
|
||||
def validate_series(self, autoname=None, name=None):
|
||||
"""Validate if `autoname` property is correctly set."""
|
||||
if not autoname: autoname = self.autoname
|
||||
|
|
@ -254,6 +269,7 @@ class DocType(Document):
|
|||
if used_in:
|
||||
frappe.throw(_("Series {0} already used in {1}").format(prefix, used_in[0][0]))
|
||||
|
||||
|
||||
def on_update(self):
|
||||
"""Update database schema, make controller templates if `custom` is not set and clear cache."""
|
||||
self.delete_duplicate_custom_fields()
|
||||
|
|
@ -295,6 +311,7 @@ class DocType(Document):
|
|||
|
||||
clear_linked_doctype_cache()
|
||||
|
||||
|
||||
def delete_duplicate_custom_fields(self):
|
||||
if not (frappe.db.table_exists(self.name) and frappe.db.table_exists("Custom Field")):
|
||||
return
|
||||
|
|
@ -307,6 +324,7 @@ class DocType(Document):
|
|||
dt = {0} and fieldname in ({1})
|
||||
'''.format('%s', ', '.join(['%s'] * len(fields))), tuple([self.name] + fields), as_dict=True)
|
||||
|
||||
|
||||
def sync_global_search(self):
|
||||
'''If global search settings are changed, rebuild search properties for this table'''
|
||||
global_search_fields_before_update = [d.fieldname for d in
|
||||
|
|
@ -324,6 +342,7 @@ class DocType(Document):
|
|||
frappe.enqueue('frappe.utils.global_search.rebuild_for_doctype',
|
||||
now=now, doctype=self.name)
|
||||
|
||||
|
||||
def set_base_class_for_controller(self):
|
||||
'''Updates the controller class to subclass from `WebsiteGenertor`,
|
||||
if it is a subclass of `Document`'''
|
||||
|
|
@ -350,6 +369,7 @@ class DocType(Document):
|
|||
if hasattr(module, method):
|
||||
getattr(module, method)()
|
||||
|
||||
|
||||
def before_rename(self, old, new, merge=False):
|
||||
"""Throw exception if merge. DocTypes cannot be merged."""
|
||||
if not self.custom and frappe.session.user != "Administrator":
|
||||
|
|
@ -365,6 +385,7 @@ class DocType(Document):
|
|||
if not self.custom and not frappe.flags.in_test and not frappe.flags.in_patch:
|
||||
self.rename_files_and_folders(old, new)
|
||||
|
||||
|
||||
def after_rename(self, old, new, merge=False):
|
||||
"""Change table name using `RENAME TABLE` if table exists. Or update
|
||||
`doctype` property for Single type."""
|
||||
|
|
@ -375,20 +396,24 @@ class DocType(Document):
|
|||
else:
|
||||
frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new))
|
||||
|
||||
|
||||
def rename_files_and_folders(self, old, new):
|
||||
# move files
|
||||
new_path = get_doc_path(self.module, 'doctype', new)
|
||||
subprocess.check_output(['mv', get_doc_path(self.module, 'doctype', old), new_path])
|
||||
old_path = get_doc_path(self.module, 'doctype', old)
|
||||
shutil.move(old_path, new_path)
|
||||
|
||||
# rename files
|
||||
for fname in os.listdir(new_path):
|
||||
if frappe.scrub(old) in fname:
|
||||
subprocess.check_output(['mv', os.path.join(new_path, fname),
|
||||
os.path.join(new_path, fname.replace(frappe.scrub(old), frappe.scrub(new)))])
|
||||
old_file_name = os.path.join(new_path, fname)
|
||||
new_file_name = os.path.join(new_path, fname.replace(frappe.scrub(old), frappe.scrub(new)))
|
||||
shutil.move(old_file_name, new_file_name)
|
||||
|
||||
self.rename_inside_controller(new, old, new_path)
|
||||
frappe.msgprint(_('Renamed files and replaced code in controllers, please check!'))
|
||||
|
||||
|
||||
def rename_inside_controller(self, new, old, new_path):
|
||||
for fname in ('{}.js', '{}.py', '{}_list.js', '{}_calendar.js', 'test_{}.py', 'test_{}.js'):
|
||||
fname = os.path.join(new_path, fname.format(frappe.scrub(new)))
|
||||
|
|
@ -396,13 +421,25 @@ class DocType(Document):
|
|||
with open(fname, 'r') as f:
|
||||
code = f.read()
|
||||
with open(fname, 'w') as f:
|
||||
f.write(code.replace(frappe.scrub(old).replace(' ', ''), frappe.scrub(new).replace(' ', '')))
|
||||
file_content = code.replace(old, new) # replace str with full str (js controllers)
|
||||
file_content = file_content.replace(frappe.scrub(old), frappe.scrub(new)) # replace str with _ (py imports)
|
||||
file_content = file_content.replace(old.replace(' ', ''), new.replace(' ', '')) # replace str (py controllers)
|
||||
f.write(file_content)
|
||||
|
||||
# updating json file with new name
|
||||
doctype_json_path = os.path.join(new_path, '{}.json'.format(frappe.scrub(new)))
|
||||
current_data = frappe.get_file_json(doctype_json_path)
|
||||
current_data['name'] = new
|
||||
|
||||
with open(doctype_json_path, 'w') as f:
|
||||
json.dump(current_data, f, indent=1)
|
||||
|
||||
def before_reload(self):
|
||||
"""Preserve naming series changes in Property Setter."""
|
||||
if not (self.issingle and self.istable):
|
||||
self.preserve_naming_series_options_in_property_setter()
|
||||
|
||||
|
||||
def preserve_naming_series_options_in_property_setter(self):
|
||||
"""Preserve naming_series as property setter if it does not exist"""
|
||||
naming_series = self.get("fields", {"fieldname": "naming_series"})
|
||||
|
|
@ -422,6 +459,7 @@ class DocType(Document):
|
|||
if naming_series[0].default:
|
||||
make_property_setter(self.name, "naming_series", "default", naming_series[0].default, "Text", validate_fields_for_doctype=False)
|
||||
|
||||
|
||||
def before_export(self, docdict):
|
||||
# remove null and empty fields
|
||||
def remove_null_fields(o):
|
||||
|
|
@ -466,6 +504,7 @@ class DocType(Document):
|
|||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
@staticmethod
|
||||
def prepare_for_import(docdict):
|
||||
# set order of fields from field_order
|
||||
|
|
@ -488,16 +527,19 @@ class DocType(Document):
|
|||
if "field_order" in docdict:
|
||||
del docdict["field_order"]
|
||||
|
||||
|
||||
def export_doc(self):
|
||||
"""Export to standard folder `[module]/doctype/[name]/[name].json`."""
|
||||
from frappe.modules.export_file import export_to_files
|
||||
export_to_files(record_list=[['DocType', self.name]], create_init=True)
|
||||
|
||||
|
||||
def import_doc(self):
|
||||
"""Import from standard folder `[module]/doctype/[name]/[name].json`."""
|
||||
from frappe.modules.import_module import import_from_files
|
||||
import_from_files(record_list=[[self.module, 'doctype', self.name]])
|
||||
|
||||
|
||||
def make_controller_template(self):
|
||||
"""Make boilerplate controller template."""
|
||||
make_boilerplate("controller._py", self)
|
||||
|
|
@ -514,6 +556,7 @@ class DocType(Document):
|
|||
make_boilerplate('templates/controller.html', self.as_dict())
|
||||
make_boilerplate('templates/controller_row.html', self.as_dict())
|
||||
|
||||
|
||||
def make_amendable(self):
|
||||
"""If is_submittable is set, add amended_from docfields."""
|
||||
if self.is_submittable:
|
||||
|
|
@ -529,6 +572,7 @@ class DocType(Document):
|
|||
"no_copy": 1
|
||||
})
|
||||
|
||||
|
||||
def make_repeatable(self):
|
||||
"""If allow_auto_repeat is set, add auto_repeat custom field."""
|
||||
if self.allow_auto_repeat:
|
||||
|
|
@ -537,12 +581,14 @@ class DocType(Document):
|
|||
df = dict(fieldname='auto_repeat', label='Auto Repeat', fieldtype='Link', options='Auto Repeat', insert_after=insert_after, read_only=1, no_copy=1, print_hide=1)
|
||||
create_custom_field(self.name, df)
|
||||
|
||||
|
||||
def get_max_idx(self):
|
||||
"""Returns the highest `idx`"""
|
||||
max_idx = frappe.db.sql("""select max(idx) from `tabDocField` where parent = %s""",
|
||||
self.name)
|
||||
return max_idx and max_idx[0][0] or 0
|
||||
|
||||
|
||||
def validate_name(self, name=None):
|
||||
if not name:
|
||||
name = self.name
|
||||
|
|
@ -556,11 +602,13 @@ class DocType(Document):
|
|||
if not is_a_valid_name:
|
||||
frappe.throw(_("DocType's name should start with a letter and it can only consist of letters, numbers, spaces and underscores"), frappe.NameError)
|
||||
|
||||
|
||||
def validate_fields_for_doctype(doctype):
|
||||
doc = frappe.get_doc("DocType", doctype)
|
||||
doc.delete_duplicate_custom_fields()
|
||||
validate_fields(frappe.get_meta(doctype, cached=False))
|
||||
|
||||
|
||||
# this is separate because it is also called via custom field
|
||||
def validate_fields(meta):
|
||||
"""Validate doctype fields. Checks
|
||||
|
|
@ -580,21 +628,26 @@ def validate_fields(meta):
|
|||
14. `unique` cannot be checked if there exist non-unique values.
|
||||
|
||||
:param meta: `frappe.model.meta.Meta` object to check."""
|
||||
|
||||
def check_illegal_characters(fieldname):
|
||||
validate_column_name(fieldname)
|
||||
|
||||
|
||||
def check_unique_fieldname(docname, fieldname):
|
||||
duplicates = list(filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields)))
|
||||
if len(duplicates) > 1:
|
||||
frappe.throw(_("{0}: Fieldname {1} appears multiple times in rows {2}").format(docname, fieldname, ", ".join(duplicates)), UniqueFieldnameError)
|
||||
|
||||
|
||||
def check_fieldname_length(fieldname):
|
||||
validate_column_length(fieldname)
|
||||
|
||||
|
||||
def check_illegal_mandatory(docname, d):
|
||||
if (d.fieldtype in no_value_fields) and d.fieldtype not in table_fields and d.reqd:
|
||||
frappe.throw(_("{0}: Field {1} of type {2} cannot be mandatory").format(docname, d.label, d.fieldtype), IllegalMandatoryError)
|
||||
|
||||
|
||||
def check_link_table_options(docname, d):
|
||||
if frappe.flags.in_patch: return
|
||||
if d.fieldtype in ("Link",) + table_fields:
|
||||
|
|
@ -613,23 +666,28 @@ def validate_fields(meta):
|
|||
# fix case
|
||||
d.options = options
|
||||
|
||||
|
||||
def check_hidden_and_mandatory(docname, d):
|
||||
if d.hidden and d.reqd and not d.default:
|
||||
frappe.throw(_("{0}: Field {1} in row {2} cannot be hidden and mandatory without default").format(docname, d.label, d.idx), HiddenAndMandatoryWithoutDefaultError)
|
||||
|
||||
|
||||
def check_width(d):
|
||||
if d.fieldtype == "Currency" and cint(d.width) < 100:
|
||||
frappe.throw(_("Max width for type Currency is 100px in row {0}").format(d.idx))
|
||||
|
||||
|
||||
def check_in_list_view(d):
|
||||
if d.in_list_view and (d.fieldtype in not_allowed_in_list_view):
|
||||
frappe.throw(_("'In List View' not allowed for type {0} in row {1}").format(d.fieldtype, d.idx))
|
||||
|
||||
|
||||
def check_in_global_search(d):
|
||||
if d.in_global_search and d.fieldtype in no_value_fields:
|
||||
frappe.throw(_("'In Global Search' not allowed for type {0} in row {1}")
|
||||
.format(d.fieldtype, d.idx))
|
||||
|
||||
|
||||
def check_dynamic_link_options(d):
|
||||
if d.fieldtype=="Dynamic Link":
|
||||
doctype_pointer = list(filter(lambda df: df.fieldname==d.options, fields))
|
||||
|
|
@ -637,6 +695,7 @@ def validate_fields(meta):
|
|||
or (doctype_pointer[0].fieldtype=="Link" and doctype_pointer[0].options!="DocType"):
|
||||
frappe.throw(_("Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType'"))
|
||||
|
||||
|
||||
def check_illegal_default(d):
|
||||
if d.fieldtype == "Check" and not d.default:
|
||||
d.default = '0'
|
||||
|
|
@ -645,10 +704,12 @@ def validate_fields(meta):
|
|||
if d.fieldtype == "Select" and d.default and (d.default not in d.options.split("\n")):
|
||||
frappe.throw(_("Default for {0} must be an option").format(d.fieldname))
|
||||
|
||||
|
||||
def check_precision(d):
|
||||
if d.fieldtype in ("Currency", "Float", "Percent") and d.precision is not None and not (1 <= cint(d.precision) <= 6):
|
||||
frappe.throw(_("Precision should be between 1 and 6"))
|
||||
|
||||
|
||||
def check_unique_and_text(docname, d):
|
||||
if meta.issingle:
|
||||
d.unique = 0
|
||||
|
|
@ -670,6 +731,7 @@ def validate_fields(meta):
|
|||
if d.search_index and d.fieldtype in ("Text", "Long Text", "Small Text", "Code", "Text Editor"):
|
||||
frappe.throw(_("{0}:Fieldtype {1} for {2} cannot be indexed").format(docname, d.fieldtype, d.label), CannotIndexedError)
|
||||
|
||||
|
||||
def check_fold(fields):
|
||||
fold_exists = False
|
||||
for i, f in enumerate(fields):
|
||||
|
|
@ -684,6 +746,7 @@ def validate_fields(meta):
|
|||
else:
|
||||
frappe.throw(_("Fold can not be at the end of the form"))
|
||||
|
||||
|
||||
def check_search_fields(meta, fields):
|
||||
"""Throw exception if `search_fields` don't contain valid fields."""
|
||||
if not meta.search_fields:
|
||||
|
|
@ -700,6 +763,7 @@ def validate_fields(meta):
|
|||
(fieldname not in fieldname_list):
|
||||
frappe.throw(_("Search field {0} is not valid").format(fieldname))
|
||||
|
||||
|
||||
def check_title_field(meta):
|
||||
"""Throw exception if `title_field` isn't a valid fieldname."""
|
||||
if not meta.get("title_field"):
|
||||
|
|
@ -726,6 +790,7 @@ def validate_fields(meta):
|
|||
_validate_title_field_pattern(df.options)
|
||||
_validate_title_field_pattern(df.default)
|
||||
|
||||
|
||||
def check_image_field(meta):
|
||||
'''check image_field exists and is of type "Attach Image"'''
|
||||
if not meta.image_field:
|
||||
|
|
@ -737,6 +802,7 @@ def validate_fields(meta):
|
|||
if df[0].fieldtype != 'Attach Image':
|
||||
frappe.throw(_("Image field must be of type Attach Image"), InvalidFieldNameError)
|
||||
|
||||
|
||||
def check_is_published_field(meta):
|
||||
if not meta.is_published_field:
|
||||
return
|
||||
|
|
@ -744,6 +810,7 @@ def validate_fields(meta):
|
|||
if meta.is_published_field not in fieldname_list:
|
||||
frappe.throw(_("Is Published Field must be a valid fieldname"), InvalidFieldNameError)
|
||||
|
||||
|
||||
def check_timeline_field(meta):
|
||||
if not meta.timeline_field:
|
||||
return
|
||||
|
|
@ -755,6 +822,7 @@ def validate_fields(meta):
|
|||
if df.fieldtype not in ("Link", "Dynamic Link"):
|
||||
frappe.throw(_("Timeline field must be a Link or Dynamic Link"), InvalidFieldNameError)
|
||||
|
||||
|
||||
def check_sort_field(meta):
|
||||
'''Validate that sort_field(s) is a valid field'''
|
||||
if meta.sort_field:
|
||||
|
|
@ -767,6 +835,7 @@ def validate_fields(meta):
|
|||
frappe.throw(_("Sort field {0} must be a valid fieldname").format(fieldname),
|
||||
InvalidFieldNameError)
|
||||
|
||||
|
||||
def check_illegal_depends_on_conditions(docfield):
|
||||
''' assignment operation should not be allowed in the depends on condition.'''
|
||||
depends_on_fields = ["depends_on", "collapsible_depends_on"]
|
||||
|
|
@ -776,6 +845,7 @@ def validate_fields(meta):
|
|||
re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", depends_on):
|
||||
frappe.throw(_("Invalid {0} condition").format(frappe.unscrub(field)), frappe.ValidationError)
|
||||
|
||||
|
||||
def check_table_multiselect_option(docfield):
|
||||
'''check if the doctype provided in Option has atleast 1 Link field'''
|
||||
if not docfield.fieldtype == 'Table MultiSelect': return
|
||||
|
|
@ -788,6 +858,7 @@ def validate_fields(meta):
|
|||
frappe.throw(_('DocType <b>{0}</b> provided for the field <b>{1}</b> must have atleast one Link field')
|
||||
.format(doctype, docfield.fieldname), frappe.ValidationError)
|
||||
|
||||
|
||||
def scrub_options_in_select(field):
|
||||
"""Strip options for whitespaces"""
|
||||
|
||||
|
|
@ -799,6 +870,7 @@ def validate_fields(meta):
|
|||
options_list.append(_option)
|
||||
field.options = '\n'.join(options_list)
|
||||
|
||||
|
||||
def scrub_fetch_from(field):
|
||||
if hasattr(field, 'fetch_from') and getattr(field, 'fetch_from'):
|
||||
field.fetch_from = field.fetch_from.strip('\n').strip()
|
||||
|
|
@ -841,6 +913,7 @@ def validate_fields(meta):
|
|||
check_sort_field(meta)
|
||||
check_image_field(meta)
|
||||
|
||||
|
||||
def validate_permissions_for_doctype(doctype, for_remove=False):
|
||||
"""Validates if permissions are set correctly."""
|
||||
doctype = frappe.get_doc("DocType", doctype)
|
||||
|
|
@ -852,6 +925,7 @@ def validate_permissions_for_doctype(doctype, for_remove=False):
|
|||
|
||||
clear_permissions_cache(doctype.name)
|
||||
|
||||
|
||||
def clear_permissions_cache(doctype):
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
delete_notification_count_for(doctype)
|
||||
|
|
@ -866,6 +940,7 @@ def clear_permissions_cache(doctype):
|
|||
""", doctype):
|
||||
frappe.clear_cache(user=user)
|
||||
|
||||
|
||||
def validate_permissions(doctype, for_remove=False):
|
||||
permissions = doctype.get("permissions")
|
||||
if not permissions:
|
||||
|
|
@ -959,6 +1034,7 @@ def validate_permissions(doctype, for_remove=False):
|
|||
check_level_zero_is_set(d)
|
||||
remove_rights_for_single(d)
|
||||
|
||||
|
||||
def make_module_and_roles(doc, perm_fieldname="permissions"):
|
||||
"""Make `Module Def` and `Role` records if already not made. Called while installing."""
|
||||
try:
|
||||
|
|
@ -989,11 +1065,6 @@ def make_module_and_roles(doc, perm_fieldname="permissions"):
|
|||
else:
|
||||
raise
|
||||
|
||||
def init_list(doctype):
|
||||
"""Make boilerplate list views."""
|
||||
doc = frappe.get_meta(doctype)
|
||||
make_boilerplate("controller_list.js", doc)
|
||||
make_boilerplate("controller_list.html", doc)
|
||||
|
||||
def check_if_fieldname_conflicts_with_methods(doctype, fieldname):
|
||||
doc = frappe.get_doc({"doctype": doctype})
|
||||
|
|
@ -1002,5 +1073,6 @@ def check_if_fieldname_conflicts_with_methods(doctype, fieldname):
|
|||
if fieldname in method_list:
|
||||
frappe.throw(_("Fieldname {0} conflicting with meta object").format(fieldname))
|
||||
|
||||
|
||||
def clear_linked_doctype_cache():
|
||||
frappe.cache().delete_value('linked_doctypes_without_ignore_user_permissions_enabled')
|
||||
|
|
|
|||
|
|
@ -2,17 +2,21 @@
|
|||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
from six import string_types, integer_types
|
||||
import shutil
|
||||
|
||||
import frappe
|
||||
import frappe.model.meta
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
import frappe.defaults
|
||||
import frappe.model.meta
|
||||
from frappe import _
|
||||
from frappe import get_module_path
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from frappe.core.doctype.file.file import remove_all
|
||||
from frappe.utils.password import delete_all_passwords_for
|
||||
from frappe import _
|
||||
from frappe.model.naming import revert_series_if_last
|
||||
from frappe.utils.global_search import delete_for_document
|
||||
from six import string_types, integer_types
|
||||
|
||||
|
||||
doctypes_to_skip = ("Communication", "ToDo", "DocShare", "Email Unsubscribe", "Activity Log", "File", "Version", "Document Follow", "Comment" , "View Log")
|
||||
|
||||
|
|
@ -70,6 +74,13 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa
|
|||
|
||||
delete_from_table(doctype, name, ignore_doctypes, None)
|
||||
|
||||
if not (frappe.flags.in_migrate or frappe.flags.in_install or frappe.flags.in_test):
|
||||
try:
|
||||
delete_controllers(name, doc.module)
|
||||
except FileNotFoundError:
|
||||
# in case a doctype doesnt have any controller code
|
||||
pass
|
||||
|
||||
else:
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
|
||||
|
|
@ -323,3 +334,12 @@ def insert_feed(doc):
|
|||
"subject": "{0} {1}".format(_(doc.doctype), doc.name),
|
||||
"full_name": get_fullname(doc.owner)
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
def delete_controllers(doctype, module):
|
||||
"""
|
||||
Delete controller code in the doctype folder
|
||||
"""
|
||||
module_path = get_module_path(module)
|
||||
dir_path = os.path.join(module_path, 'doctype', frappe.scrub(doctype))
|
||||
|
||||
shutil.rmtree(dir_path)
|
||||
|
|
|
|||
|
|
@ -839,7 +839,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
}
|
||||
|
||||
rename_doc() {
|
||||
frappe.model.rename_doc(this.doctype, this.docname);
|
||||
frappe.model.rename_doc(this.doctype, this.docname, () => this.refresh_header());
|
||||
}
|
||||
|
||||
share_doc() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue