Преглед изворни кода

Merge branch 'develop' of github.com:frappe/frappe into owner-unchange

version-14
Gavin D'souza пре 3 година
родитељ
комит
6f2125fca6
47 измењених фајлова са 940 додато и 713 уклоњено
  1. +3
    -1
      cypress.json
  2. +2
    -2
      cypress/support/commands.js
  3. +1
    -1
      frappe/automation/doctype/assignment_rule/assignment_rule.py
  4. +14
    -14
      frappe/automation/doctype/assignment_rule/test_assignment_rule.py
  5. +551
    -541
      frappe/core/doctype/docfield/docfield.json
  6. +1
    -0
      frappe/core/doctype/file/test_file.py
  7. +10
    -2
      frappe/core/doctype/module_def/module_def.json
  8. +2
    -3
      frappe/core/doctype/translation/translation.json
  9. +1
    -1
      frappe/core/doctype/user/test_user.py
  10. +9
    -8
      frappe/core/doctype/user/user.json
  11. +1
    -1
      frappe/core/doctype/user/user.py
  12. +1
    -1
      frappe/core/doctype/user_permission/user_permission.js
  13. +7
    -6
      frappe/core/doctype/user_permission/user_permission.json
  14. +1
    -1
      frappe/core/notifications.py
  15. +1
    -0
      frappe/custom/doctype/customize_form/customize_form.py
  16. +13
    -3
      frappe/custom/doctype/customize_form_field/customize_form_field.json
  17. +2
    -0
      frappe/database/__init__.py
  18. +35
    -5
      frappe/database/database.py
  19. +1
    -0
      frappe/database/mariadb/framework_mariadb.sql
  20. +1
    -0
      frappe/database/postgres/framework_postgres.sql
  21. +1
    -1
      frappe/desk/doctype/event/test_event.py
  22. +11
    -11
      frappe/desk/doctype/todo/todo.json
  23. +9
    -9
      frappe/desk/doctype/todo/todo.py
  24. +15
    -14
      frappe/desk/form/assign_to.py
  25. +3
    -3
      frappe/desk/listview.py
  26. +1
    -10
      frappe/email/doctype/email_template/email_template.json
  27. +2
    -2
      frappe/email/doctype/notification/notification.py
  28. +1
    -4
      frappe/model/base_document.py
  29. +10
    -4
      frappe/model/document.py
  30. +1
    -1
      frappe/model/rename_doc.py
  31. +1
    -0
      frappe/patches.txt
  32. +12
    -0
      frappe/patches/v14_0/transform_todo_schema.py
  33. +1
    -0
      frappe/public/js/frappe/form/controls/multiselect_list.js
  34. +1
    -1
      frappe/public/js/frappe/form/form.js
  35. +5
    -0
      frappe/public/js/frappe/list/list_view.js
  36. +15
    -10
      frappe/public/js/frappe/views/reports/query_report.js
  37. +3
    -1
      frappe/public/js/frappe/views/treeview.js
  38. +1
    -1
      frappe/tests/test_assign.py
  39. +27
    -0
      frappe/tests/test_db.py
  40. +19
    -0
      frappe/tests/test_document.py
  41. +5
    -0
      frappe/utils/nestedset.py
  42. +28
    -12
      frappe/website/doctype/blog_post/blog_post.py
  43. +28
    -10
      frappe/website/doctype/blog_post/templates/blog_post_list.html
  44. +36
    -0
      frappe/website/doctype/blog_post/ui_test_blog_post.js
  45. +10
    -2
      frappe/website/doctype/blog_settings/blog_settings.json
  46. +2
    -10
      frappe/website/doctype/help_article/help_article.json
  47. +35
    -17
      frappe/website/doctype/web_page/web_page.json

+ 3
- 1
cypress.json Прегледај датотеку

@@ -9,5 +9,7 @@
"retries": {
"runMode": 2,
"openMode": 2
}
},
"integrationFolder": ".",
"testFiles": ["cypress/integration/*.js", "**/ui_test_*.js"]
}

+ 2
- 2
cypress/support/commands.js Прегледај датотеку

@@ -30,7 +30,7 @@ Cypress.Commands.add('login', (email, password) => {
email = 'Administrator';
}
if (!password) {
password = Cypress.config('adminPassword');
password = Cypress.env('adminPassword');
}
cy.request({
url: '/api/method/login',
@@ -161,7 +161,7 @@ Cypress.Commands.add('remove_doc', (doctype, name) => {

Cypress.Commands.add('create_records', doc => {
return cy
.call('frappe.tests.ui_test_helpers.create_if_not_exists', {doc})
.call('frappe.tests.ui_test_helpers.create_if_not_exists', {doc: JSON.stringify(doc)})
.then(r => r.message);
});



+ 1
- 1
frappe/automation/doctype/assignment_rule/assignment_rule.py Прегледај датотеку

@@ -123,7 +123,7 @@ class AssignmentRule(Document):
user = d.user,
count = frappe.db.count('ToDo', dict(
reference_type = self.document_type,
owner = d.user,
allocated_to = d.user,
status = "Open"))
))



+ 14
- 14
frappe/automation/doctype/assignment_rule/test_assignment_rule.py Прегледај датотеку

@@ -40,7 +40,7 @@ class TestAutoAssign(unittest.TestCase):
reference_type = 'Note',
reference_name = note.name,
status = 'Open'
), 'owner'), 'test@example.com')
), 'allocated_to'), 'test@example.com')

note = make_note(dict(public=1))

@@ -49,7 +49,7 @@ class TestAutoAssign(unittest.TestCase):
reference_type = 'Note',
reference_name = note.name,
status = 'Open'
), 'owner'), 'test1@example.com')
), 'allocated_to'), 'test1@example.com')

clear_assignments()

@@ -61,7 +61,7 @@ class TestAutoAssign(unittest.TestCase):
reference_type = 'Note',
reference_name = note.name,
status = 'Open'
), 'owner'), 'test2@example.com')
), 'allocated_to'), 'test2@example.com')

# check loop back to first user
note = make_note(dict(public=1))
@@ -70,7 +70,7 @@ class TestAutoAssign(unittest.TestCase):
reference_type = 'Note',
reference_name = note.name,
status = 'Open'
), 'owner'), 'test@example.com')
), 'allocated_to'), 'test@example.com')

def test_load_balancing(self):
self.assignment_rule.rule = 'Load Balancing'
@@ -81,11 +81,11 @@ class TestAutoAssign(unittest.TestCase):

# check if each user has 10 assignments (?)
for user in ('test@example.com', 'test1@example.com', 'test2@example.com'):
self.assertEqual(len(frappe.get_all('ToDo', dict(owner = user, reference_type = 'Note'))), 10)
self.assertEqual(len(frappe.get_all('ToDo', dict(allocated_to = user, reference_type = 'Note'))), 10)

# clear 5 assignments for first user
# can't do a limit in "delete" since postgres does not support it
for d in frappe.get_all('ToDo', dict(reference_type = 'Note', owner = 'test@example.com'), limit=5):
for d in frappe.get_all('ToDo', dict(reference_type = 'Note', allocated_to = 'test@example.com'), limit=5):
frappe.db.delete("ToDo", {"name": d.name})

# add 5 more assignments
@@ -94,7 +94,7 @@ class TestAutoAssign(unittest.TestCase):

# check if each user still has 10 assignments
for user in ('test@example.com', 'test1@example.com', 'test2@example.com'):
self.assertEqual(len(frappe.get_all('ToDo', dict(owner = user, reference_type = 'Note'))), 10)
self.assertEqual(len(frappe.get_all('ToDo', dict(allocated_to = user, reference_type = 'Note'))), 10)

def test_based_on_field(self):
self.assignment_rule.rule = 'Based on Field'
@@ -129,7 +129,7 @@ class TestAutoAssign(unittest.TestCase):
reference_type = 'Note',
reference_name = note.name,
status = 'Open'
), 'owner'), None)
), 'allocated_to'), None)

def test_clear_assignment(self):
note = make_note(dict(public=1))
@@ -142,7 +142,7 @@ class TestAutoAssign(unittest.TestCase):
), limit=1)[0]

todo = frappe.get_doc('ToDo', todo['name'])
self.assertEqual(todo.owner, 'test@example.com')
self.assertEqual(todo.allocated_to, 'test@example.com')

# test auto unassign
note.public = 0
@@ -164,7 +164,7 @@ class TestAutoAssign(unittest.TestCase):
), limit=1)[0]

todo = frappe.get_doc('ToDo', todo['name'])
self.assertEqual(todo.owner, 'test@example.com')
self.assertEqual(todo.allocated_to, 'test@example.com')

note.content="Closed"
note.save()
@@ -174,7 +174,7 @@ class TestAutoAssign(unittest.TestCase):
# check if todo is closed
self.assertEqual(todo.status, 'Closed')
# check if closed todo retained assignment
self.assertEqual(todo.owner, 'test@example.com')
self.assertEqual(todo.allocated_to, 'test@example.com')

def check_multiple_rules(self):
note = make_note(dict(public=1, notify_on_login=1))
@@ -184,7 +184,7 @@ class TestAutoAssign(unittest.TestCase):
reference_type = 'Note',
reference_name = note.name,
status = 'Open'
), 'owner'), 'test@example.com')
), 'allocated_to'), 'test@example.com')

def check_assignment_rule_scheduling(self):
frappe.db.delete("Assignment Rule")
@@ -202,7 +202,7 @@ class TestAutoAssign(unittest.TestCase):
reference_type = 'Note',
reference_name = note.name,
status = 'Open'
), 'owner'), ['test@example.com', 'test1@example.com', 'test2@example.com'])
), 'allocated_to'), ['test@example.com', 'test1@example.com', 'test2@example.com'])

frappe.flags.assignment_day = "Friday"
note = make_note(dict(public=1))
@@ -211,7 +211,7 @@ class TestAutoAssign(unittest.TestCase):
reference_type = 'Note',
reference_name = note.name,
status = 'Open'
), 'owner'), ['test3@example.com'])
), 'allocated_to'), ['test3@example.com'])

def test_assignment_rule_condition(self):
frappe.db.delete("Assignment Rule")


+ 551
- 541
frappe/core/doctype/docfield/docfield.json
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 1
- 0
frappe/core/doctype/file/test_file.py Прегледај датотеку

@@ -18,6 +18,7 @@ test_content2 = 'Hello World'
def make_test_doc():
d = frappe.new_doc('ToDo')
d.description = 'Test'
d.assigned_by = frappe.session.user
d.save()
return d.doctype, d.name



+ 10
- 2
frappe/core/doctype/module_def/module_def.json Прегледај датотеку

@@ -10,7 +10,8 @@
"custom",
"package",
"app_name",
"restrict_to_domain"
"restrict_to_domain",
"connections_tab"
],
"fields": [
{
@@ -50,6 +51,12 @@
"fieldtype": "Link",
"label": "Package",
"options": "Package"
},
{
"fieldname": "connections_tab",
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
}
],
"icon": "fa fa-sitemap",
@@ -116,7 +123,7 @@
"link_fieldname": "module"
}
],
"modified": "2021-09-05 21:58:40.253909",
"modified": "2022-01-03 13:56:52.817954",
"modified_by": "Administrator",
"module": "Core",
"name": "Module Def",
@@ -154,5 +161,6 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "ASC",
"states": [],
"track_changes": 1
}

+ 2
- 3
frappe/core/doctype/translation/translation.json Прегледај датотеку

@@ -43,8 +43,7 @@
{
"fieldname": "context",
"fieldtype": "Data",
"label": "Context",
"read_only": 1
"label": "Context"
},
{
"default": "0",
@@ -83,7 +82,7 @@
}
],
"links": [],
"modified": "2020-03-12 13:28:48.223409",
"modified": "2021-12-31 10:19:52.541055",
"modified_by": "Administrator",
"module": "Core",
"name": "Translation",


+ 1
- 1
frappe/core/doctype/user/test_user.py Прегледај датотеку

@@ -70,7 +70,7 @@ class TestUser(unittest.TestCase):
delete_contact("_test@example.com")
delete_doc("User", "_test@example.com")

self.assertTrue(not frappe.db.sql("""select * from `tabToDo` where owner=%s""",
self.assertTrue(not frappe.db.sql("""select * from `tabToDo` where allocated_to=%s""",
("_test@example.com",)))

from frappe.core.doctype.role.test_role import test_records as role_records


+ 9
- 8
frappe/core/doctype/user/user.json Прегледај датотеку

@@ -10,15 +10,15 @@
"enabled",
"section_break_3",
"email",
"first_name",
"middle_name",
"last_name",
"language",
"column_break0",
"first_name",
"full_name",
"time_zone",
"column_break_11",
"middle_name",
"username",
"column_break_11",
"language",
"time_zone",
"send_welcome_email",
"unsubscribed",
"user_image",
@@ -660,7 +660,7 @@
{
"group": "Activity",
"link_doctype": "ToDo",
"link_fieldname": "owner"
"link_fieldname": "allocated_to"
},
{
"group": "Integrations",
@@ -669,7 +669,7 @@
}
],
"max_attachments": 5,
"modified": "2021-11-17 17:17:16.098457",
"modified": "2022-01-03 11:53:25.250822",
"modified_by": "Administrator",
"module": "Core",
"name": "User",
@@ -702,6 +702,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "full_name",
"track_changes": 1
}
}

+ 1
- 1
frappe/core/doctype/user/user.py Прегледај датотеку

@@ -363,7 +363,7 @@ class User(Document):
frappe.local.login_manager.logout(user=self.name)

# delete todos
frappe.db.delete("ToDo", {"owner": self.name})
frappe.db.delete("ToDo", {"allocated_to": self.name})
todo_table = DocType("ToDo")
(
frappe.qb.update(todo_table)


+ 1
- 1
frappe/core/doctype/user_permission/user_permission.js Прегледај датотеку

@@ -44,7 +44,7 @@ frappe.ui.form.on('User Permission', {

set_applicable_for_constraint: frm => {
frm.toggle_reqd('applicable_for', !frm.doc.apply_to_all_doctypes);
if (frm.doc.apply_to_all_doctypes) {
if (frm.doc.apply_to_all_doctypes && frm.doc.applicable_for) {
frm.set_value('applicable_for', null);
}
},


+ 7
- 6
frappe/core/doctype/user_permission/user_permission.json Прегледај датотеку

@@ -8,8 +8,8 @@
"field_order": [
"user",
"allow",
"column_break_3",
"for_value",
"column_break_3",
"is_default",
"advanced_control_section",
"apply_to_all_doctypes",
@@ -37,10 +37,6 @@
"options": "DocType",
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "for_value",
"fieldtype": "Dynamic Link",
@@ -87,10 +83,14 @@
"fieldtype": "Check",
"hidden": 1,
"label": "Hide Descendants"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
}
],
"links": [],
"modified": "2021-01-21 18:14:10.839381",
"modified": "2022-01-03 11:25:03.726150",
"modified_by": "Administrator",
"module": "Core",
"name": "User Permission",
@@ -111,6 +111,7 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "user",
"track_changes": 1
}

+ 1
- 1
frappe/core/notifications.py Прегледај датотеку

@@ -23,7 +23,7 @@ def get_things_todo(as_list=False):
data = frappe.get_list("ToDo",
fields=["name", "description"] if as_list else "count(*)",
filters=[["ToDo", "status", "=", "Open"]],
or_filters=[["ToDo", "owner", "=", frappe.session.user],
or_filters=[["ToDo", "allocated_to", "=", frappe.session.user],
["ToDo", "assigned_by", "=", frappe.session.user]],
as_list=True)



+ 1
- 0
frappe/custom/doctype/customize_form/customize_form.py Прегледај датотеку

@@ -516,6 +516,7 @@ docfield_properties = {
'options': 'Text',
'fetch_from': 'Small Text',
'fetch_if_empty': 'Check',
'show_dashboard': 'Check',
'permlevel': 'Int',
'width': 'Data',
'print_width': 'Data',


+ 13
- 3
frappe/custom/doctype/customize_form_field/customize_form_field.json Прегледај датотеку

@@ -28,6 +28,7 @@
"options",
"fetch_from",
"fetch_if_empty",
"show_dashboard",
"permissions",
"depends_on",
"permlevel",
@@ -82,7 +83,7 @@
"label": "Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nTab Break",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime",
"reqd": 1,
"search_index": 1
},
@@ -422,18 +423,27 @@
"fieldname": "non_negative",
"fieldtype": "Check",
"label": "Non Negative"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Tab Break'",
"fieldname": "show_dashboard",
"fieldtype": "Check",
"label": "Show Dashboard"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-07-11 21:57:24.479749",
"modified": "2022-01-03 14:50:32.035768",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form Field",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "ASC"
"sort_order": "ASC",
"states": []
}

+ 2
- 0
frappe/database/__init__.py Прегледај датотеку

@@ -4,6 +4,8 @@
# Database Module
# --------------------

from frappe.database.database import savepoint

def setup_database(force, source_sql=None, verbose=None, no_mariadb_socket=False):
import frappe
if frappe.conf.db_type == 'postgres':


+ 35
- 5
frappe/database/database.py Прегледај датотеку

@@ -4,16 +4,18 @@
# Database Module
# --------------------

import datetime
import random
import re
import time
from typing import Dict, List, Union
import string
from contextlib import contextmanager
from time import time
from typing import Dict, List, Union, Tuple

import frappe
import datetime
import frappe.defaults
import frappe.model.meta

from frappe import _
from time import time
from frappe.utils import now, getdate, cast, get_datetime
from frappe.model.utils.link_count import flush_local_link_count
from frappe.query_builder.functions import Count
@@ -811,6 +813,9 @@ class Database(object):
Avoid using savepoints when writing to filesystem."""
self.sql(f"savepoint {save_point}")

def release_savepoint(self, save_point):
self.sql(f"release savepoint {save_point}")

def rollback(self, *, save_point=None):
"""`ROLLBACK` current transaction. Optionally rollback to a known save_point."""
if save_point:
@@ -1097,3 +1102,28 @@ def enqueue_jobs_after_commit():
q.enqueue_call(execute_job, timeout=job.get("timeout"),
kwargs=job.get("queue_args"))
frappe.flags.enqueue_after_commit = []

@contextmanager
def savepoint(catch: Union[type, Tuple[type, ...]] = Exception):
""" Wrapper for wrapping blocks of DB operations in a savepoint.

as contextmanager:

for doc in docs:
with savepoint(catch=DuplicateError):
doc.insert()

as decorator (wraps FULL function call):

@savepoint(catch=DuplicateError)
def process_doc(doc):
doc.insert()
"""
try:
savepoint = ''.join(random.sample(string.ascii_lowercase, 10))
frappe.db.savepoint(savepoint)
yield # control back to calling function
except catch:
frappe.db.rollback(save_point=savepoint)
else:
frappe.db.release_savepoint(savepoint)

+ 1
- 0
frappe/database/mariadb/framework_mariadb.sql Прегледај датотеку

@@ -25,6 +25,7 @@ CREATE TABLE `tabDocField` (
`oldfieldtype` varchar(255) DEFAULT NULL,
`options` text,
`search_index` int(1) NOT NULL DEFAULT 0,
`show_dashboard` int(1) NOT NULL DEFAULT 0,
`hidden` int(1) NOT NULL DEFAULT 0,
`set_only_once` int(1) NOT NULL DEFAULT 0,
`allow_in_quick_entry` int(1) NOT NULL DEFAULT 0,


+ 1
- 0
frappe/database/postgres/framework_postgres.sql Прегледај датотеку

@@ -27,6 +27,7 @@ CREATE TABLE "tabDocField" (
"search_index" smallint NOT NULL DEFAULT 0,
"hidden" smallint NOT NULL DEFAULT 0,
"set_only_once" smallint NOT NULL DEFAULT 0,
"show_dashboard" smallint NOT NULL DEFAULT 0,
"allow_in_quick_entry" smallint NOT NULL DEFAULT 0,
"print_hide" smallint NOT NULL DEFAULT 0,
"report_hide" smallint NOT NULL DEFAULT 0,


+ 1
- 1
frappe/desk/doctype/event/test_event.py Прегледај датотеку

@@ -93,7 +93,7 @@ class TestEvent(unittest.TestCase):

# Remove an assignment
todo = frappe.get_doc("ToDo", {"reference_type": ev.doctype, "reference_name": ev.name,
"owner": self.test_user})
"allocated_to": self.test_user})
todo.status = "Cancelled"
todo.save()



+ 11
- 11
frappe/desk/doctype/todo/todo.json Прегледај датотеку

@@ -13,7 +13,7 @@
"column_break_2",
"color",
"date",
"owner",
"allocated_to",
"description_section",
"description",
"section_break_6",
@@ -69,15 +69,6 @@
"oldfieldname": "date",
"oldfieldtype": "Date"
},
{
"fieldname": "owner",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"in_global_search": 1,
"in_standard_filter": 1,
"label": "Allocated To",
"options": "User"
},
{
"fieldname": "description_section",
"fieldtype": "Section Break"
@@ -153,12 +144,21 @@
"label": "Assignment Rule",
"options": "Assignment Rule",
"read_only": 1
},
{
"fieldname": "allocated_to",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"in_global_search": 1,
"in_standard_filter": 1,
"label": "Allocated To",
"options": "User"
}
],
"icon": "fa fa-check",
"idx": 2,
"links": [],
"modified": "2020-01-14 17:04:36.971002",
"modified": "2021-09-16 11:36:34.586898",
"modified_by": "Administrator",
"module": "Desk",
"name": "ToDo",


+ 9
- 9
frappe/desk/doctype/todo/todo.py Прегледај датотеку

@@ -16,10 +16,10 @@ class ToDo(Document):
self._assignment = None
if self.is_new():

if self.assigned_by == self.owner:
if self.assigned_by == self.allocated_to:
assignment_message = frappe._("{0} self assigned this task: {1}").format(get_fullname(self.assigned_by), self.description)
else:
assignment_message = frappe._("{0} assigned {1}: {2}").format(get_fullname(self.assigned_by), get_fullname(self.owner), self.description)
assignment_message = frappe._("{0} assigned {1}: {2}").format(get_fullname(self.assigned_by), get_fullname(self.allocated_to), self.description)

self._assignment = {
"text": assignment_message,
@@ -29,12 +29,12 @@ class ToDo(Document):
else:
# NOTE the previous value is only available in validate method
if self.get_db_value("status") != self.status:
if self.owner == frappe.session.user:
if self.allocated_to == frappe.session.user:
removal_message = frappe._("{0} removed their assignment.").format(
get_fullname(frappe.session.user))
else:
removal_message = frappe._("Assignment of {0} removed by {1}").format(
get_fullname(self.owner), get_fullname(frappe.session.user))
get_fullname(self.allocated_to), get_fullname(frappe.session.user))

self._assignment = {
"text": removal_message,
@@ -75,7 +75,7 @@ class ToDo(Document):
"reference_name": self.reference_name,
"status": ("!=", "Cancelled")
},
fields=["owner"], as_list=True)]
fields=["allocated_to"], as_list=True)]

assignments.reverse()
frappe.db.set_value(self.reference_type, self.reference_name,
@@ -98,8 +98,8 @@ class ToDo(Document):
def get_owners(cls, filters=None):
"""Returns list of owners after applying filters on todo's.
"""
rows = frappe.get_all(cls.DocType, filters=filters or {}, fields=['owner'])
return [parse_addr(row.owner)[1] for row in rows if row.owner]
rows = frappe.get_all(cls.DocType, filters=filters or {}, fields=['allocated_to'])
return [parse_addr(row.allocated_to)[1] for row in rows if row.allocated_to]

# NOTE: todo is viewable if a user is an owner, or set as assigned_to value, or has any role that is allowed to access ToDo doctype.
def on_doctype_update():
@@ -115,7 +115,7 @@ def get_permission_query_conditions(user):
if any(check in todo_roles for check in frappe.get_roles(user)):
return None
else:
return """(`tabToDo`.owner = {user} or `tabToDo`.assigned_by = {user})"""\
return """(`tabToDo`.allocated_to = {user} or `tabToDo`.assigned_by = {user})"""\
.format(user=frappe.db.escape(user))

def has_permission(doc, ptype="read", user=None):
@@ -127,7 +127,7 @@ def has_permission(doc, ptype="read", user=None):
if any(check in todo_roles for check in frappe.get_roles(user)):
return True
else:
return doc.owner==user or doc.assigned_by==user
return doc.allocated_to==user or doc.assigned_by==user

@frappe.whitelist()
def new_todo(description):


+ 15
- 14
frappe/desk/form/assign_to.py Прегледај датотеку

@@ -19,7 +19,7 @@ def get(args=None):
if not args:
args = frappe.local.form_dict

return frappe.get_all('ToDo', fields=['owner', 'name'], filters=dict(
return frappe.get_all('ToDo', fields=['allocated_to', 'name'], filters=dict(
reference_type = args.get('doctype'),
reference_name = args.get('name'),
status = ('!=', 'Cancelled')
@@ -48,7 +48,7 @@ def add(args=None):
"reference_type": args['doctype'],
"reference_name": args['name'],
"status": "Open",
"owner": assign_to
"allocated_to": assign_to
}

if frappe.get_all("ToDo", filters=filters):
@@ -61,7 +61,7 @@ def add(args=None):

d = frappe.get_doc({
"doctype": "ToDo",
"owner": assign_to,
"allocated_to": assign_to,
"reference_type": args['doctype'],
"reference_name": args['name'],
"description": args.get('description'),
@@ -87,7 +87,7 @@ def add(args=None):
follow_document(args['doctype'], args['name'], assign_to)

# notify
notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',
notify_assignment(d.assigned_by, d.allocated_to, d.reference_type, d.reference_name, action='ASSIGN',
description=args.get("description"))

if shared_with_users:
@@ -112,13 +112,13 @@ def add_multiple(args=None):
add(args)

def close_all_assignments(doctype, name):
assignments = frappe.db.get_all('ToDo', fields=['owner'], filters =
assignments = frappe.db.get_all('ToDo', fields=['allocated_to'], filters =
dict(reference_type = doctype, reference_name = name, status=('!=', 'Cancelled')))
if not assignments:
return False

for assign_to in assignments:
set_status(doctype, name, assign_to.owner, status="Closed")
set_status(doctype, name, assign_to.allocated_to, status="Closed")

return True

@@ -130,13 +130,13 @@ def set_status(doctype, name, assign_to, status="Cancelled"):
"""remove from todo"""
try:
todo = frappe.db.get_value("ToDo", {"reference_type":doctype,
"reference_name":name, "owner":assign_to, "status": ('!=', status)})
"reference_name":name, "allocated_to":assign_to, "status": ('!=', status)})
if todo:
todo = frappe.get_doc("ToDo", todo)
todo.status = status
todo.save(ignore_permissions=True)

notify_assignment(todo.assigned_by, todo.owner, todo.reference_type, todo.reference_name)
notify_assignment(todo.assigned_by, todo.allocated_to, todo.reference_type, todo.reference_name)
except frappe.DoesNotExistError:
pass

@@ -150,25 +150,26 @@ def clear(doctype, name):
'''
Clears assignments, return False if not assigned.
'''
assignments = frappe.db.get_all('ToDo', fields=['owner'], filters =
assignments = frappe.db.get_all('ToDo', fields=['allocated_to'], filters =
dict(reference_type = doctype, reference_name = name))
if not assignments:
return False

for assign_to in assignments:
set_status(doctype, name, assign_to.owner, "Cancelled")
set_status(doctype, name, assign_to.allocated_to, "Cancelled")

return True

def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE',
def notify_assignment(assigned_by, allocated_to, doc_type, doc_name, action='CLOSE',
description=None):
"""
Notify assignee that there is a change in assignment
"""
if not (assigned_by and owner and doc_type and doc_name): return
if not (assigned_by and allocated_to and doc_type and doc_name):
return

# return if self assigned or user disabled
if assigned_by == owner or not frappe.db.get_value('User', owner, 'enabled'):
if assigned_by == allocated_to or not frappe.db.get_value('User', allocated_to, 'enabled'):
return

# Search for email address in description -- i.e. assignee
@@ -194,7 +195,7 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE',
'email_content': description_html
}

enqueue_create_notification(owner, notification_doc)
enqueue_create_notification(allocated_to, notification_doc)

def format_message_for_assign_to(users):
return "<br><br>" + "<br>".join(users)

+ 3
- 3
frappe/desk/listview.py Прегледај датотеку

@@ -29,16 +29,16 @@ def get_group_by_count(doctype, current_filters, field):
subquery = frappe.get_all(doctype, filters=current_filters, run=False)
if field == 'assigned_to':
subquery_condition = ' and `tabToDo`.reference_name in ({subquery})'.format(subquery = subquery)
return frappe.db.sql("""select `tabToDo`.owner as name, count(*) as count
return frappe.db.sql("""select `tabToDo`.allocated_to as name, count(*) as count
from
`tabToDo`, `tabUser`
where
`tabToDo`.status!='Cancelled' and
`tabToDo`.owner = `tabUser`.name and
`tabToDo`.allocated_to = `tabUser`.name and
`tabUser`.user_type = 'System User'
{subquery_condition}
group by
`tabToDo`.owner
`tabToDo`.allocated_to
order by
count desc
limit 50""".format(subquery_condition = subquery_condition), as_dict=True)


+ 1
- 10
frappe/email/doctype/email_template/email_template.json Прегледај датотеку

@@ -12,7 +12,6 @@
"use_html",
"response_html",
"response",
"owner",
"section_break_4",
"email_reply_help"
],
@@ -32,14 +31,6 @@
"label": "Response",
"mandatory_depends_on": "eval:!doc.use_html"
},
{
"default": "user",
"fieldname": "owner",
"fieldtype": "Link",
"hidden": 1,
"label": "Owner",
"options": "User"
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break"
@@ -66,7 +57,7 @@
],
"icon": "fa fa-comment",
"links": [],
"modified": "2020-11-30 14:12:50.321633",
"modified": "2022-01-04 14:12:50.321633",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Template",


+ 2
- 2
frappe/email/doctype/notification/notification.py Прегледај датотеку

@@ -435,8 +435,8 @@ def get_context(doc):
def get_assignees(doc):
assignees = []
assignees = frappe.get_all('ToDo', filters={'status': 'Open', 'reference_name': doc.name,
'reference_type': doc.doctype}, fields=['owner'])
'reference_type': doc.doctype}, fields=['allocated_to'])

recipients = [d.owner for d in assignees]
recipients = [d.allocated_to for d in assignees]

return recipients

+ 1
- 4
frappe/model/base_document.py Прегледај датотеку

@@ -101,13 +101,10 @@ class BaseDocument(object):
"balance": 42000
})
"""
if "doctype" in d:
self.set("doctype", d.get("doctype"))

# first set default field values of base document
for key in default_fields:
if key in d:
self.set(key, d.get(key))
self.set(key, d[key])

for key, value in d.items():
self.set(key, value)


+ 10
- 4
frappe/model/document.py Прегледај датотеку

@@ -504,6 +504,7 @@ class Document(BaseDocument):
self._sanitize_content()
self._save_passwords()
self.validate_workflow()
self.validate_owner()

children = self.get_all_children()
for d in children:
@@ -546,6 +547,11 @@ class Document(BaseDocument):
if not self._action == 'save':
set_workflow_state_on_action(self, workflow, self._action)

def validate_owner(self):
"""Validate if the owner of the Document has changed"""
if not self.is_new() and self.has_value_changed('owner'):
frappe.throw(_('Document owner cannot be changed'))

def validate_set_only_once(self):
"""Validate that fields are not changed if not in insert"""
set_only_once_fields = self.meta.get_set_only_once_fields()
@@ -1348,15 +1354,15 @@ class Document(BaseDocument):
), frappe.exceptions.InvalidDates)

def get_assigned_users(self):
assignments = frappe.get_all('ToDo',
fields=['owner'],
assigned_users = frappe.get_all('ToDo',
fields=['allocated_to'],
filters={
'reference_type': self.doctype,
'reference_name': self.name,
'status': ('!=', 'Cancelled'),
})
}, pluck='allocated_to')

users = set([assignment.owner for assignment in assignments])
users = set(assigned_users)
return users

def add_tag(self, tag):


+ 1
- 1
frappe/model/rename_doc.py Прегледај датотеку

@@ -293,7 +293,7 @@ def update_link_field_values(link_fields, old, new, doctype):
if parent == new and doctype == "DocType":
parent = old

frappe.db.set_value(parent, {docfield: old}, docfield, new)
frappe.db.set_value(parent, {docfield: old}, docfield, new, update_modified=False)

# update cached link_fields as per new
if doctype=='DocType' and field['parent'] == old:


+ 1
- 0
frappe/patches.txt Прегледај датотеку

@@ -190,3 +190,4 @@ frappe.patches.v14_0.update_github_endpoints #08-11-2021
frappe.patches.v14_0.remove_db_aggregation
frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021
frappe.patches.v14_0.update_color_names_in_kanban_board_column
frappe.patches.v14_0.transform_todo_schema

+ 12
- 0
frappe/patches/v14_0/transform_todo_schema.py Прегледај датотеку

@@ -0,0 +1,12 @@
import frappe
from frappe.query_builder.utils import DocType


def execute():
# Email Template & Help Article have owner field that doesn't have any additional functionality
# Only ToDo has to be updated.

ToDo = DocType("ToDo")
frappe.reload_doctype("ToDo", force=True)

frappe.qb.update(ToDo).set(ToDo.allocated_to, ToDo.owner).run()

+ 1
- 0
frappe/public/js/frappe/form/controls/multiselect_list.js Прегледај датотеку

@@ -109,6 +109,7 @@ frappe.ui.form.ControlMultiSelectList = class ControlMultiSelectList extends fra
let value = decodeURIComponent($selectable_item.data().value);

if ($selectable_item.hasClass('selected')) {
this.values = this.values.slice();
this.values.push(value);
} else {
this.values = this.values.filter(val => val !== value);


+ 1
- 1
frappe/public/js/frappe/form/form.js Прегледај датотеку

@@ -215,7 +215,7 @@ frappe.ui.form.Form = class FrappeForm {

if (this.layout.tabs.length) {
this.layout.tabs.every(tab => {
if (tab.df.options === 'Dashboard') {
if (tab.df.show_dashboard) {
tab.wrapper.prepend(dashboard_parent);
dashboard_added = true;
return false;


+ 5
- 0
frappe/public/js/frappe/list/list_view.js Прегледај датотеку

@@ -1500,6 +1500,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
read_only: 1,
},
],
primary_action_label: __("Copy to clipboard"),
primary_action: () => {
frappe.utils.copy_to_clipboard(this.get_share_url());
d.hide();
},
});
d.show();
}


+ 15
- 10
frappe/public/js/frappe/views/reports/query_report.js Прегледај датотеку

@@ -105,15 +105,18 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
this.toggle_nothing_to_show(true);
return;
}
let route_options = {};
route_options = Object.assign(route_options, frappe.route_options);

if (this.report_name !== frappe.get_route()[1]) {
// different report
this.load_report();
this.load_report(route_options);
}
else if (frappe.has_route_options()) {
// filters passed through routes
// so refresh report again
this.refresh_report();
this.refresh_report(route_options);
} else {
// same report
// don't do anything to preserve state
@@ -121,7 +124,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
}
}

load_report() {
load_report(route_options) {
this.page.clear_inner_toolbar();
this.route = frappe.get_route();
this.page_name = frappe.get_route_str();
@@ -137,7 +140,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
() => this.get_report_settings(),
() => this.setup_progress_bar(),
() => this.setup_page_head(),
() => this.refresh_report(),
() => this.refresh_report(route_options),
() => this.add_chart_buttons_to_toolbar(true),
() => this.add_card_button_to_toolbar(true),
]);
@@ -343,13 +346,13 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
});
}

refresh_report() {
refresh_report(route_options) {
this.toggle_message(true);
this.toggle_report(false);

return frappe.run_serially([
() => this.setup_filters(),
() => this.set_route_filters(),
() => this.set_route_filters(route_options),
() => this.page.clear_custom_actions(),
() => this.report_settings.onload && this.report_settings.onload(this),
() => this.refresh()
@@ -525,15 +528,17 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
});
}

set_route_filters() {
if(frappe.route_options) {
const fields = Object.keys(frappe.route_options);
set_route_filters(route_options) {
if (!route_options) route_options = frappe.route_options;

if (route_options) {
const fields = Object.keys(route_options);

const filters_to_set = this.filters.filter(f => fields.includes(f.df.fieldname));

const promises = filters_to_set.map(f => {
return () => {
const value = frappe.route_options[f.df.fieldname];
const value = route_options[f.df.fieldname];
f.set_value(value);
};
});


+ 3
- 1
frappe/public/js/frappe/views/treeview.js Прегледај датотеку

@@ -409,7 +409,9 @@ frappe.views.TreeView = class TreeView {
},
];

if (frappe.user.has_role('System Manager')) {
if (frappe.user.has_role('System Manager') &&
frappe.meta.has_field(me.doctype, "lft") &&
frappe.meta.has_field(me.doctype, "rgt")) {
this.menu_items.push(
{
label: __('Rebuild Tree'),


+ 1
- 1
frappe/tests/test_assign.py Прегледај датотеку

@@ -13,7 +13,7 @@ class TestAssign(unittest.TestCase):

added = assign(todo, "test@example.com")

self.assertTrue("test@example.com" in [d.owner for d in added])
self.assertTrue("test@example.com" in [d.allocated_to for d in added])

removed = frappe.desk.form.assign_to.remove(todo.doctype, todo.name, "test@example.com")



+ 27
- 0
frappe/tests/test_db.py Прегледај датотеку

@@ -12,6 +12,7 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.utils import random_string
from frappe.utils.testutils import clear_custom_fields
from frappe.query_builder import Field
from frappe.database import savepoint

from .test_query_builder import run_only_if, db_type_is
from frappe.query_builder.functions import Concat_ws
@@ -267,6 +268,32 @@ class TestDB(unittest.TestCase):
for d in created_docs:
self.assertTrue(frappe.db.exists("ToDo", d))

def test_savepoints_wrapper(self):
frappe.db.rollback()

class SpecificExc(Exception):
pass

created_docs = []
failed_docs = []

for _ in range(5):
with savepoint(catch=SpecificExc):
doc_kept = frappe.get_doc(doctype="ToDo", description="nope").save()
created_docs.append(doc_kept.name)

with savepoint(catch=SpecificExc):
doc_gone = frappe.get_doc(doctype="ToDo", description="nope").save()
failed_docs.append(doc_gone.name)
raise SpecificExc

frappe.db.commit()

for d in failed_docs:
self.assertFalse(frappe.db.exists("ToDo", d))
for d in created_docs:
self.assertTrue(frappe.db.exists("ToDo", d))


@run_only_if(db_type_is.MARIADB)
class TestDDLCommandsMaria(unittest.TestCase):


+ 19
- 0
frappe/tests/test_document.py Прегледај датотеку

@@ -252,3 +252,22 @@ class TestDocument(unittest.TestCase):
'currency': 100000
})
self.assertEquals(d.get_formatted('currency', currency='INR', format="#,###.##"), '₹ 100,000.00')

def test_owner_changed(self):
frappe.delete_doc_if_exists("User", "hello@example.com")
frappe.set_user("Administrator")

d = frappe.get_doc({
"doctype": "User",
"email": "hello@example.com",
"first_name": "John"
})
d.insert()
self.assertEqual(frappe.db.get_value("User", d.owner), d.owner)

d.set("owner", "johndoe@gmail.com")
self.assertRaises(frappe.ValidationError, d.save)

d.reload()
d.save()
self.assertEqual(frappe.db.get_value("User", d.owner), d.owner)

+ 5
- 0
frappe/utils/nestedset.py Прегледај датотеку

@@ -144,6 +144,11 @@ def rebuild_tree(doctype, parent_field):
if frappe.request and frappe.local.form_dict.cmd == 'rebuild_tree':
frappe.only_for('System Manager')

meta = frappe.get_meta(doctype)
if not meta.has_field("lft") or not meta.has_field("rgt"):
frappe.throw(_("Rebuilding of tree is not supported for {}").format(frappe.bold(doctype)),
title=_("Invalid Action"))

# get all roots
right = 1
table = DocType(doctype)


+ 28
- 12
frappe/website/doctype/blog_post/blog_post.py Прегледај датотеку

@@ -159,10 +159,10 @@ class BlogPost(WebsiteGenerator):
like_count = 0

if frappe.db.count('Feedback'):
like_count = frappe.db.count('Feedback',
like_count = frappe.db.count('Feedback',
filters = dict(
reference_doctype = self.doctype,
reference_name = self.name,
reference_doctype = self.doctype,
reference_name = self.name,
like = True
)
)
@@ -183,7 +183,6 @@ def get_list_context(context=None):
get_list = get_blog_list,
no_breadcrumbs = True,
hide_filters = True,
children = get_children(),
# show_search = True,
title = _('Blog')
)
@@ -208,17 +207,34 @@ def get_list_context(context=None):
else:
list_context.parents = [{"name": _("Home"), "route": "/"}]

list_context.update(frappe.get_doc("Blog Settings").as_dict(no_default_fields=True))
blog_settings = frappe.get_doc("Blog Settings").as_dict(no_default_fields=True)
list_context.update(blog_settings)

if blog_settings.browse_by_category:
list_context.blog_categories = get_blog_categories()

return list_context

def get_children():
return frappe.db.sql("""select route as name,
title from `tabBlog Category`
where published = 1
and exists (select name from `tabBlog Post`
where `tabBlog Post`.blog_category=`tabBlog Category`.name and published=1)
order by title asc""", as_dict=1)

def get_blog_categories():
from pypika import Order
from pypika.terms import ExistsCriterion

post, category = frappe.qb.DocType("Blog Post"), frappe.qb.DocType("Blog Category")
return (
frappe.qb.from_(category)
.select(category.name, category.route, category.title)
.where(
(category.published == 1)
& ExistsCriterion(
frappe.qb.from_(post)
.select("name")
.where((post.published == 1) & (post.blog_category == category.name))
)
)
.orderby(category.title, order=Order.asc)
.run(as_dict=1)
)

def clear_blog_cache():
for blog in frappe.db.sql_list("""select route from


+ 28
- 10
frappe/website/doctype/blog_post/templates/blog_post_list.html Прегледај датотеку

@@ -4,16 +4,34 @@

{% block page_content %}

{{ web_block("Hero",
values={
'title': blog_title or _("Blog"),
'subtitle': blog_introduction or '',
},
add_container=0,
add_top_padding=0,
add_bottom_padding=0,
css_class="py-5"
) }}
<div class="row py-8">
<div class="col-md-8">
<div class="hero">
<div class="hero-content">
<h1 class="hero-title">{{ blog_title or _('Blog') }}</h1>
<p class="hero-subtitle mb-0">{{ blog_introduction or '' }}</p>
</div>
</div>
</div>
<div class="col-md-4 align-self-end">
{%- if browse_by_category -%}
<label for="category-select" class="sr-only">{{ _("Browse by category") }}</label>
<select id="category-select" class="custom-select" onchange="window.location.pathname = this.value">
<option value="" {{ not frappe.form_dict.category and "selected" or "" }} disabled>
{{ _("Browse by category") }}
</option>
{%- if frappe.form_dict.category -%}
<option value="blog">{{ _("Show all blogs") }}</option>
{%- endif -%}
{%- for category in blog_categories -%}
<option value="{{ category.route }}" {{ frappe.form_dict.category == category.name and "selected" or "" }}>
{{ _(category.title) }}
</option>
{%- endfor -%}
</select>
{%- endif -%}
</div>
</div>

<div class="blog-list-content">
<div class="website-list" data-doctype="{{ doctype }}" data-txt="{{ txt or '[notxt]' | e }}">


+ 36
- 0
frappe/website/doctype/blog_post/ui_test_blog_post.js Прегледај датотеку

@@ -0,0 +1,36 @@
context('Blog Post', () => {
before(() => {
cy.login();
cy.visit('/app');
});

it('Blog Category dropdown works as expected', () => {
cy.create_records([
{
doctype: 'Blog Category',
title: 'Category 1',
published: 1
},
{
doctype: 'Blogger',
short_name: 'John',
full_name: 'John Doe'
},
{
doctype: 'Blog Post',
title: 'Test Blog Post',
content: 'Test Blog Post Content',
blog_category: 'category-1',
blogger: 'John',
published: 1
}
]);
cy.set_value('Blog Settings', 'Blog Settings', {browse_by_category: 1});
cy.visit('/blog');
cy.findByLabelText('Browse by category').select('Category 1');
cy.location('pathname').should('eq', '/blog/category-1');
cy.set_value('Blog Settings', 'Blog Settings', {browse_by_category: 0});
cy.visit('/blog');
cy.findByLabelText('Browse by category').should('not.exist');
});
});

+ 10
- 2
frappe/website/doctype/blog_settings/blog_settings.json Прегледај датотеку

@@ -11,6 +11,7 @@
"enable_social_sharing",
"show_cta_in_blog",
"allow_guest_to_comment",
"browse_by_category",
"cta_section",
"title",
"subtitle",
@@ -110,14 +111,20 @@
"default": "1",
"fieldname": "allow_guest_to_comment",
"fieldtype": "Check",
"label": "Allow guest to comment"
"label": "Allow Guest to comment"
},
{
"default": "0",
"fieldname": "browse_by_category",
"fieldtype": "Check",
"label": "Browse by category"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"links": [],
"modified": "2021-10-28 20:44:44.143193",
"modified": "2021-12-20 13:40:32.312459",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Settings",
@@ -142,5 +149,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

+ 2
- 10
frappe/website/doctype/help_article/help_article.json Прегледај датотеку

@@ -15,8 +15,7 @@
"section_break_7",
"content",
"likes",
"route",
"owner"
"route"
],
"fields": [
{
@@ -79,13 +78,6 @@
"fieldtype": "Data",
"in_global_search": 1,
"label": "Route"
},
{
"default": "user",
"fieldname": "owner",
"fieldtype": "Link",
"label": "Owner",
"options": "User"
}
],
"has_web_view": 1,
@@ -93,7 +85,7 @@
"index_web_pages_for_search": 1,
"is_published_field": "published",
"links": [],
"modified": "2020-07-21 16:25:18.577325",
"modified": "2022-01-04 16:25:18.577325",
"modified_by": "Administrator",
"module": "Website",
"name": "Help Article",


+ 35
- 17
frappe/website/doctype/web_page/web_page.json Прегледај датотеку

@@ -11,12 +11,10 @@
"section_title",
"title",
"route",
"published",
"dynamic_route",
"cb1",
"published",
"module",
"start_date",
"end_date",
"sb1",
"content_type",
"slideshow",
@@ -25,6 +23,7 @@
"main_section_md",
"main_section_html",
"page_blocks",
"scripting_tab",
"context_section",
"context_script",
"custom_javascript",
@@ -34,28 +33,32 @@
"text_align",
"css",
"full_width",
"settings",
"show_title",
"settings",
"publishing_dates_section",
"start_date",
"column_break_30",
"end_date",
"metatags_section",
"meta_title",
"meta_description",
"meta_image",
"set_meta_tags",
"section_break_17",
"show_sidebar",
"idx",
"website_sidebar",
"column_break_20",
"enable_comments",
"idx",
"sb2",
"header",
"breadcrumbs",
"metatags_section",
"meta_title",
"meta_description",
"meta_image",
"set_meta_tags"
"breadcrumbs"
],
"fields": [
{
"fieldname": "section_title",
"fieldtype": "Section Break",
"label": "Title"
"fieldtype": "Tab Break",
"label": "Content"
},
{
"fieldname": "title",
@@ -161,7 +164,7 @@
"collapsible": 1,
"collapsible_depends_on": "insert_style",
"fieldname": "custom_css",
"fieldtype": "Section Break",
"fieldtype": "Tab Break",
"label": "Style"
},
{
@@ -185,7 +188,7 @@
},
{
"fieldname": "settings",
"fieldtype": "Section Break",
"fieldtype": "Tab Break",
"label": "Settings"
},
{
@@ -267,7 +270,6 @@
"label": "Full Width"
},
{
"collapsible": 1,
"fieldname": "metatags_section",
"fieldtype": "Section Break",
"label": "Meta Tags"
@@ -313,6 +315,21 @@
"fieldtype": "Link",
"label": "Module (for export)",
"options": "Module Def"
},
{
"fieldname": "scripting_tab",
"fieldtype": "Tab Break",
"label": "Scripting",
"show_dashboard": 1
},
{
"fieldname": "publishing_dates_section",
"fieldtype": "Section Break",
"label": "Publishing Dates"
},
{
"fieldname": "column_break_30",
"fieldtype": "Column Break"
}
],
"has_web_view": 1,
@@ -322,7 +339,7 @@
"is_published_field": "published",
"links": [],
"max_attachments": 20,
"modified": "2021-09-04 12:11:56.070994",
"modified": "2022-01-03 13:01:48.182645",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Page",
@@ -342,6 +359,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "ASC",
"states": [],
"title_field": "title",
"track_changes": 1
}

Loading…
Откажи
Сачувај