Browse Source

Merge branch 'develop' into feat-frontmatter

version-14
Suraj Shetty 5 years ago
committed by GitHub
parent
commit
9a3c8aaee5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 2500 additions and 2716 deletions
  1. +1
    -1
      .eslintrc
  2. +0
    -16
      .github/workflows/backport.yml
  3. +4
    -3
      CODEOWNERS
  4. +18
    -16
      frappe/automation/desk_page/tools/tools.json
  5. +5
    -1
      frappe/contacts/doctype/contact/contact.json
  6. +4
    -1
      frappe/contacts/doctype/contact_phone/contact_phone.json
  7. +25
    -25
      frappe/core/desk_page/settings/settings.json
  8. +14
    -14
      frappe/core/desk_page/users/users.json
  9. +4
    -2
      frappe/core/doctype/user/user.json
  10. +2
    -2
      frappe/core/doctype/user/user.py
  11. +53
    -191
      frappe/core/doctype/user_email/user_email.json
  12. +7
    -1
      frappe/core/page/dashboard/dashboard.js
  13. +13
    -11
      frappe/custom/desk_page/customization/customization.json
  14. +2
    -2
      frappe/custom/doctype/customize_form_field/customize_form_field.json
  15. +165
    -125
      frappe/desk/desktop.py
  16. +3
    -2
      frappe/desk/doctype/dashboard_chart/dashboard_chart.json
  17. +15
    -12
      frappe/desk/doctype/desk_card/desk_card.json
  18. +1
    -1
      frappe/desk/doctype/desk_chart/desk_chart.json
  19. +14
    -10
      frappe/desk/doctype/desk_page/desk_page.js
  20. +21
    -5
      frappe/desk/doctype/desk_page/desk_page.json
  21. +12
    -10
      frappe/desk/doctype/desk_shortcut/desk_shortcut.json
  22. +9
    -0
      frappe/desk/doctype/notification_settings/notification_settings.js
  23. +8
    -1
      frappe/desk/doctype/notification_settings/notification_settings.py
  24. +31
    -0
      frappe/desk/doctype/todo/todo_calendar.js
  25. +11
    -5
      frappe/desk/page/setup_wizard/install_fixtures.py
  26. +2
    -1
      frappe/email/doctype/email_account/email_account.json
  27. +2
    -3
      frappe/email/doctype/email_group/email_group.py
  28. +15
    -14
      frappe/email/doctype/newsletter/newsletter.py
  29. +16
    -12
      frappe/integrations/desk_page/integrations/integrations.json
  30. +109
    -467
      frappe/integrations/doctype/dropbox_settings/dropbox_settings.json
  31. +17
    -34
      frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
  32. +10
    -0
      frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py
  33. +15
    -37
      frappe/integrations/doctype/google_drive/google_drive.py
  34. +478
    -478
      frappe/integrations/doctype/oauth_client/oauth_client.json
  35. +92
    -379
      frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json
  36. +19
    -35
      frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py
  37. +83
    -0
      frappe/integrations/offsite_backup_utils.py
  38. +1
    -1
      frappe/integrations/utils.py
  39. +5
    -1
      frappe/model/naming.py
  40. +1
    -0
      frappe/patches.txt
  41. +3
    -2
      frappe/patches/v8_0/update_gender_and_salutation.py
  42. +1
    -0
      frappe/public/js/frappe/form/grid_row.js
  43. +1
    -0
      frappe/public/js/frappe/form/grid_row_form.js
  44. +13
    -15
      frappe/public/js/frappe/form/workflow.js
  45. +6
    -3
      frappe/public/js/frappe/list/list_sidebar_group_by.js
  46. +4
    -1
      frappe/public/js/frappe/ui/field_group.js
  47. +1
    -0
      frappe/public/js/frappe/ui/toolbar/toolbar.js
  48. +8
    -6
      frappe/public/js/frappe/utils/utils.js
  49. +129
    -71
      frappe/public/js/frappe/views/desktop/desktop.js
  50. +120
    -12
      frappe/public/js/frappe/widgets/base_widget.js
  51. +26
    -24
      frappe/public/js/frappe/widgets/chart_widget.js
  52. +15
    -9
      frappe/public/js/frappe/widgets/links_widget.js
  53. +52
    -0
      frappe/public/js/frappe/widgets/new_widget.js
  54. +0
    -1
      frappe/public/js/frappe/widgets/onboarding_widget.js
  55. +46
    -29
      frappe/public/js/frappe/widgets/shortcut_widget.js
  56. +266
    -0
      frappe/public/js/frappe/widgets/widget_dialog.js
  57. +106
    -36
      frappe/public/js/frappe/widgets/widget_group.js
  58. +191
    -11
      frappe/public/less/desktop.less
  59. +5
    -0
      frappe/public/less/form_grid.less
  60. +1
    -0
      frappe/public/less/link_preview.less
  61. +2
    -1
      frappe/templates/emails/delete_data_confirmation.html
  62. +4
    -3
      frappe/templates/includes/breadcrumbs.html
  63. +26
    -0
      frappe/templates/includes/login/login.css
  64. +9
    -1
      frappe/templates/includes/login/login.js
  65. +1
    -1
      frappe/tests/test_document.py
  66. +2
    -2
      frappe/utils/data.py
  67. +21
    -18
      frappe/website/desk_page/website/website.json
  68. +133
    -550
      frappe/website/doctype/contact_us_settings/contact_us_settings.json
  69. +1
    -1
      requirements.txt

+ 1
- 1
.eslintrc View File

@@ -5,7 +5,7 @@
"es6": true
},
"parserOptions": {
"ecmaVersion": 8,
"ecmaVersion": 9,
"sourceType": "module"
},
"extends": "eslint:recommended",


+ 0
- 16
.github/workflows/backport.yml View File

@@ -1,16 +0,0 @@
name: Backport
on:
pull_request:
types:
- closed
- labeled

jobs:
backport:
runs-on: ubuntu-18.04
name: Backport
steps:
- name: Backport
uses: tibdex/backport@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

+ 4
- 3
CODEOWNERS View File

@@ -3,15 +3,16 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,

* @surajshetty3416, @netchampfaris
* @frappe/frappe-review-team
website/ @scmmishra
web_form/ @scmmishra
templates/ @scmmishra
www/ @scmmishra
integrations/ @Mangesh-Khairnar
patches/ @surajshetty3416 @sahil28297
patches/ @sahil28297
dashboard/ @prssanna
email/ @Thunderbottom
event_streaming/ @ruchamahabal
data_import* @netchampfaris
core/ @surajshetty3416
requirements.txt @gavindsouza
requirements.txt @gavindsouza

+ 18
- 16
frappe/automation/desk_page/tools/tools.json View File

@@ -1,22 +1,24 @@
{
"cards": [
{
"icon": "octicon octicon-briefcase",
"links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]",
"title": "Tools"
"hidden": 0,
"label": "Tools",
"links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]"
},
{
"links": "[\n {\n \"description\": \"Newsletters to contacts, leads.\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Group List\",\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n }\n]",
"title": "Email"
"hidden": 0,
"label": "Email",
"links": "[\n {\n \"description\": \"Newsletters to contacts, leads.\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Group List\",\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n }\n]"
},
{
"icon": "fa fa-cog",
"links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Assignment Rule\",\n \"description\": \"Set up rules for user assignments.\",\n \"label\": \"Assignment Rule\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Milestone\",\n \"description\": \"Tracks milestones on the lifecycle of a document if it undergoes multiple stages.\",\n \"label\": \"Milestone\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Auto Repeat\",\n \"description\": \"Automatically generates recurring documents.\",\n \"label\": \"Auto Repeat\"\n }\n]",
"title": "Automation"
"hidden": 0,
"label": "Automation",
"links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Assignment Rule\",\n \"description\": \"Set up rules for user assignments.\",\n \"label\": \"Assignment Rule\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Milestone\",\n \"description\": \"Tracks milestones on the lifecycle of a document if it undergoes multiple stages.\",\n \"label\": \"Milestone\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Auto Repeat\",\n \"description\": \"Automatically generates recurring documents.\",\n \"label\": \"Auto Repeat\"\n }\n]"
},
{
"links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Event Producer\",\n \"description\": \"The site you want to subscribe to for consuming events.\",\n \"label\": \"Event Producer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Consumer\",\n \"description\": \"The site which is consuming your events.\",\n \"label\": \"Event Consumer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Update Log\",\n \"description\": \"Maintains a Log of all inserts, updates and deletions on Event Producer site for documents that have consumers.\",\n \"label\": \"Event Update Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Sync Log\",\n \"description\": \"Maintains a log of every event consumed along with the status of the sync and a Resync button in case sync fails.\",\n \"label\": \"Event Sync Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Document Type Mapping\",\n \"description\": \"The mapping configuration between two doctypes.\",\n \"label\": \"Document Type Mapping\"\n }\n]",
"title": "Event Streaming"
"hidden": 0,
"label": "Event Streaming",
"links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Event Producer\",\n \"description\": \"The site you want to subscribe to for consuming events.\",\n \"label\": \"Event Producer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Consumer\",\n \"description\": \"The site which is consuming your events.\",\n \"label\": \"Event Consumer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Update Log\",\n \"description\": \"Maintains a Log of all inserts, updates and deletions on Event Producer site for documents that have consumers.\",\n \"label\": \"Event Update Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Sync Log\",\n \"description\": \"Maintains a log of every event consumed along with the status of the sync and a Resync button in case sync fails.\",\n \"label\": \"Event Sync Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Document Type Mapping\",\n \"description\": \"The mapping configuration between two doctypes.\",\n \"label\": \"Document Type Mapping\"\n }\n]"
}
],
"category": "Administration",
@@ -30,7 +32,7 @@
"idx": 0,
"is_standard": 1,
"label": "Tools",
"modified": "2020-03-12 16:30:41.841895",
"modified": "2020-04-01 11:24:40.804346",
"modified_by": "Administrator",
"module": "Automation",
"name": "Tools",
@@ -39,27 +41,27 @@
"pin_to_top": 0,
"shortcuts": [
{
"is_query_report": 0,
"label": "ToDo",
"link_to": "ToDo",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "Note",
"link_to": "Note",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "File",
"link_to": "File",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "Assignment Rule",
"link_to": "Assignment Rule",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "Auto Repeat",
"link_to": "Auto Repeat",
"type": "DocType"
}


+ 5
- 1
frappe/contacts/doctype/contact/contact.json View File

@@ -1,4 +1,5 @@
{
"actions": [],
"allow_events_in_timeline": 1,
"allow_import": 1,
"allow_rename": 1,
@@ -115,6 +116,7 @@
"label": "Phone",
"oldfieldname": "contact_no",
"oldfieldtype": "Data",
"options": "Phone",
"read_only": 1
},
{
@@ -200,6 +202,7 @@
"fieldname": "mobile_no",
"fieldtype": "Data",
"label": "Mobile No",
"options": "Phone",
"read_only": 1
},
{
@@ -245,7 +248,8 @@
"icon": "fa fa-user",
"idx": 1,
"image_field": "image",
"modified": "2019-10-10 22:04:41.070479",
"links": [],
"modified": "2020-04-06 18:25:28.223693",
"modified_by": "Administrator",
"module": "Contacts",
"name": "Contact",


+ 4
- 1
frappe/contacts/doctype/contact_phone/contact_phone.json View File

@@ -1,4 +1,5 @@
{
"actions": [],
"creation": "2019-08-02 13:10:37.890214",
"doctype": "DocType",
"editable_grid": 1,
@@ -14,6 +15,7 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Number",
"options": "Phone",
"reqd": 1
},
{
@@ -34,7 +36,8 @@
}
],
"istable": 1,
"modified": "2019-09-24 17:47:50.375326",
"links": [],
"modified": "2020-04-06 18:28:10.486220",
"modified_by": "Administrator",
"module": "Contacts",
"name": "Contact Phone",


+ 25
- 25
frappe/core/desk_page/settings/settings.json View File

@@ -1,37 +1,37 @@
{
"cards": [
{
"icon": "fa fa-th",
"links": "[\n {\n \"description\": \"Import Data from CSV / Excel files.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Import Data\",\n \"name\": \"Data Import\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Export Data in CSV / Excel format.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Export Data\",\n \"name\": \"Data Export\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update many values at one time.\",\n \"hide_count\": true,\n \"label\": \"Bulk Update\",\n \"name\": \"Bulk Update\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of backups available for download\",\n \"icon\": \"fa fa-download\",\n \"label\": \"Download Backups\",\n \"name\": \"backups\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restore or permanently delete a document.\",\n \"label\": \"Deleted Documents\",\n \"name\": \"Deleted Document\",\n \"type\": \"doctype\"\n }\n]",
"title": "Data"
"hidden": 0,
"label": "Data",
"links": "[\n {\n \"description\": \"Import Data from CSV / Excel files.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Import Data\",\n \"name\": \"Data Import\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Export Data in CSV / Excel format.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Export Data\",\n \"name\": \"Data Export\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update many values at one time.\",\n \"hide_count\": true,\n \"label\": \"Bulk Update\",\n \"name\": \"Bulk Update\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of backups available for download\",\n \"icon\": \"fa fa-download\",\n \"label\": \"Download Backups\",\n \"name\": \"backups\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restore or permanently delete a document.\",\n \"label\": \"Deleted Documents\",\n \"name\": \"Deleted Document\",\n \"type\": \"doctype\"\n }\n]"
},
{
"icon": "fa fa-envelope",
"links": "[\n {\n \"description\": \"Add / Manage Email Accounts.\",\n \"label\": \"Email Account\",\n \"name\": \"Email Account\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add / Manage Email Domains.\",\n \"label\": \"Email Domain\",\n \"name\": \"Email Domain\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Notifications based on various criteria.\",\n \"label\": \"Notification\",\n \"name\": \"Notification\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Templates for common queries.\",\n \"label\": \"Email Template\",\n \"name\": \"Email Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Reports to be emailed at regular intervals\",\n \"label\": \"Auto Email Report\",\n \"name\": \"Auto Email Report\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create and manage newsletter\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Configure notifications for mentions, assignments, energy points and more.\",\n \"label\": \"Notification Settings\",\n \"name\": \"Notification Settings\",\n \"route\": \"Form/Notification Settings/Administrator\",\n \"type\": \"doctype\"\n }\n]",
"title": "Email / Notifications"
"hidden": 0,
"label": "Email / Notifications",
"links": "[\n {\n \"description\": \"Add / Manage Email Accounts.\",\n \"label\": \"Email Account\",\n \"name\": \"Email Account\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add / Manage Email Domains.\",\n \"label\": \"Email Domain\",\n \"name\": \"Email Domain\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Notifications based on various criteria.\",\n \"label\": \"Notification\",\n \"name\": \"Notification\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Templates for common queries.\",\n \"label\": \"Email Template\",\n \"name\": \"Email Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Reports to be emailed at regular intervals\",\n \"label\": \"Auto Email Report\",\n \"name\": \"Auto Email Report\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create and manage newsletter\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Configure notifications for mentions, assignments, energy points and more.\",\n \"label\": \"Notification Settings\",\n \"name\": \"Notification Settings\",\n \"route\": \"Form/Notification Settings/Administrator\",\n \"type\": \"doctype\"\n }\n]"
},
{
"icon": "fa fa-globe",
"links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]",
"title": "Website"
"hidden": 0,
"label": "Website",
"links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"icon": "fa fa-wrench",
"links": "[\n {\n \"description\": \"Language, Date and Time settings\",\n \"hide_count\": true,\n \"label\": \"System Settings\",\n \"name\": \"System Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error on automated events (scheduler).\",\n \"label\": \"Error Log\",\n \"name\": \"Error Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error during requests.\",\n \"label\": \"Error Snapshot\",\n \"name\": \"Error Snapshot\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Enable / Disable Domains\",\n \"hide_count\": true,\n \"label\": \"Domain Settings\",\n \"name\": \"Domain Settings\",\n \"type\": \"doctype\"\n }\n]",
"title": "Core"
"hidden": 0,
"label": "Core",
"links": "[\n {\n \"description\": \"Language, Date and Time settings\",\n \"hide_count\": true,\n \"label\": \"System Settings\",\n \"name\": \"System Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error on automated events (scheduler).\",\n \"label\": \"Error Log\",\n \"name\": \"Error Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error during requests.\",\n \"label\": \"Error Snapshot\",\n \"name\": \"Error Snapshot\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Enable / Disable Domains\",\n \"hide_count\": true,\n \"label\": \"Domain Settings\",\n \"name\": \"Domain Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"icon": "fa fa-print",
"links": "[\n {\n \"description\": \"Drag and Drop tool to build and customize Print Formats.\",\n \"label\": \"Print Format Builder\",\n \"name\": \"print-format-builder\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Set default format, page size, print style etc.\",\n \"label\": \"Print Settings\",\n \"name\": \"Print Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customized HTML Templates for printing transactions.\",\n \"label\": \"Print Format\",\n \"name\": \"Print Format\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stylesheets for Print Formats\",\n \"label\": \"Print Style\",\n \"name\": \"Print Style\",\n \"type\": \"doctype\"\n }\n]",
"title": "Printing"
"hidden": 0,
"label": "Printing",
"links": "[\n {\n \"description\": \"Drag and Drop tool to build and customize Print Formats.\",\n \"label\": \"Print Format Builder\",\n \"name\": \"print-format-builder\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Set default format, page size, print style etc.\",\n \"label\": \"Print Settings\",\n \"name\": \"Print Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customized HTML Templates for printing transactions.\",\n \"label\": \"Print Format\",\n \"name\": \"Print Format\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stylesheets for Print Formats\",\n \"label\": \"Print Style\",\n \"name\": \"Print Style\",\n \"type\": \"doctype\"\n }\n]"
},
{
"icon": "fa fa-random",
"links": "[\n {\n \"description\": \"Define workflows for forms.\",\n \"label\": \"Workflow\",\n \"name\": \"Workflow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"States for workflow (e.g. Draft, Approved, Cancelled).\",\n \"label\": \"Workflow State\",\n \"name\": \"Workflow State\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Actions for workflow (e.g. Approve, Cancel).\",\n \"label\": \"Workflow Action\",\n \"name\": \"Workflow Action\",\n \"type\": \"doctype\"\n }\n]",
"title": "Workflow"
"hidden": 0,
"label": "Workflow",
"links": "[\n {\n \"description\": \"Define workflows for forms.\",\n \"label\": \"Workflow\",\n \"name\": \"Workflow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"States for workflow (e.g. Draft, Approved, Cancelled).\",\n \"label\": \"Workflow State\",\n \"name\": \"Workflow State\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Actions for workflow (e.g. Approve, Cancel).\",\n \"label\": \"Workflow Action\",\n \"name\": \"Workflow Action\",\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Administration",
"category": "Modules",
"charts": [],
"creation": "2020-03-02 15:09:40.527211",
"developer_mode_only": 0,
@@ -42,29 +42,29 @@
"idx": 0,
"is_standard": 1,
"label": "Settings",
"modified": "2020-03-12 16:30:43.510434",
"modified": "2020-04-01 11:24:40.636747",
"modified_by": "Administrator",
"module": "Core",
"name": "Settings",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 1,
"pin_to_bottom": 1,
"pin_to_top": 0,
"shortcuts": [
{
"icon": "octicon octicon-settings",
"is_query_report": 0,
"label": "System Settings",
"link_to": "System Settings",
"type": "DocType"
},
{
"icon": "fa fa-print",
"is_query_report": 0,
"label": "Print Settings",
"link_to": "Print Settings",
"type": "DocType"
},
{
"icon": "fa fa-globe",
"is_query_report": 0,
"label": "Website Settings",
"link_to": "Website Settings",
"type": "DocType"
}


+ 14
- 14
frappe/core/desk_page/users/users.json View File

@@ -1,19 +1,19 @@
{
"cards": [
{
"icon": "fa fa-group",
"links": "[\n {\n \"description\": \"System and Website Users\",\n \"label\": \"User\",\n \"name\": \"User\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"User Roles\",\n \"label\": \"Role\",\n \"name\": \"Role\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Role Profile\",\n \"label\": \"Role Profile\",\n \"name\": \"Role Profile\",\n \"type\": \"doctype\"\n }\n]",
"title": "Users"
"hidden": 0,
"label": "Users",
"links": "[\n {\n \"description\": \"System and Website Users\",\n \"label\": \"User\",\n \"name\": \"User\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"User Roles\",\n \"label\": \"Role\",\n \"name\": \"Role\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Role Profile\",\n \"label\": \"Role Profile\",\n \"name\": \"Role Profile\",\n \"type\": \"doctype\"\n }\n]"
},
{
"icon": "fa fa-group",
"links": "[\n {\n \"description\": \"Activity Log by \",\n \"label\": \"Activity Log\",\n \"name\": \"Activity Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"View Log of all print, download and export events\",\n \"label\": \"Access Log\",\n \"name\": \"Access Log\",\n \"type\": \"doctype\"\n }\n]",
"title": "Logs"
"hidden": 0,
"label": "Logs",
"links": "[\n {\n \"description\": \"Activity Log by \",\n \"label\": \"Activity Log\",\n \"name\": \"Activity Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"View Log of all print, download and export events\",\n \"label\": \"Access Log\",\n \"name\": \"Access Log\",\n \"type\": \"doctype\"\n }\n]"
},
{
"icon": "fa fa-lock",
"links": "[\n {\n \"description\": \"Set Permissions on Document Types and Roles\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"Role Permissions Manager\",\n \"name\": \"permission-manager\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restrict user for specific document\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"User Permissions\",\n \"name\": \"User Permission\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Set custom roles for page and report\",\n \"label\": \"Role Permission for Page and Report\",\n \"name\": \"Role Permission for Page and Report\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"User\"\n ],\n \"description\": \"Check which Documents are readable by a User\",\n \"doctype\": \"User\",\n \"icon\": \"fa fa-eye-open\",\n \"is_query_report\": true,\n \"label\": \"Permitted Documents For User\",\n \"name\": \"Permitted Documents For User\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"DocShare\"\n ],\n \"description\": \"Report of all document shares\",\n \"doctype\": \"DocShare\",\n \"icon\": \"fa fa-share\",\n \"label\": \"Document Share Report\",\n \"name\": \"Document Share Report\",\n \"type\": \"report\"\n }\n]",
"title": "Permissions"
"hidden": 0,
"label": "Permissions",
"links": "[\n {\n \"description\": \"Set Permissions on Document Types and Roles\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"Role Permissions Manager\",\n \"name\": \"permission-manager\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restrict user for specific document\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"User Permissions\",\n \"name\": \"User Permission\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Set custom roles for page and report\",\n \"label\": \"Role Permission for Page and Report\",\n \"name\": \"Role Permission for Page and Report\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"User\"\n ],\n \"description\": \"Check which Documents are readable by a User\",\n \"doctype\": \"User\",\n \"icon\": \"fa fa-eye-open\",\n \"is_query_report\": true,\n \"label\": \"Permitted Documents For User\",\n \"name\": \"Permitted Documents For User\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"DocShare\"\n ],\n \"description\": \"Report of all document shares\",\n \"doctype\": \"DocShare\",\n \"icon\": \"fa fa-share\",\n \"label\": \"Document Share Report\",\n \"name\": \"Document Share Report\",\n \"type\": \"report\"\n }\n]"
}
],
"category": "Administration",
@@ -27,7 +27,7 @@
"idx": 0,
"is_standard": 1,
"label": "Users",
"modified": "2020-03-12 16:30:42.483376",
"modified": "2020-04-01 11:24:40.767676",
"modified_by": "Administrator",
"module": "Core",
"name": "Users",
@@ -36,22 +36,22 @@
"pin_to_top": 0,
"shortcuts": [
{
"is_query_report": 0,
"label": "User",
"link_to": "User",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "Role",
"link_to": "Role",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "permission-manager",
"link_to": "permission-manager",
"type": "Page"
},
{
"is_query_report": 0,
"label": "user-profile",
"link_to": "user-profile",
"type": "Page"
}


+ 4
- 2
frappe/core/doctype/user/user.json View File

@@ -238,12 +238,14 @@
{
"fieldname": "phone",
"fieldtype": "Data",
"label": "Phone"
"label": "Phone",
"options": "Phone"
},
{
"fieldname": "mobile_no",
"fieldtype": "Data",
"label": "Mobile No",
"options": "Phone",
"unique": 1
},
{
@@ -588,7 +590,7 @@
"image_field": "user_image",
"links": [],
"max_attachments": 5,
"modified": "2020-03-23 22:59:26.154985",
"modified": "2020-04-08 12:27:36.716490",
"modified_by": "Administrator",
"module": "Core",
"name": "User",


+ 2
- 2
frappe/core/doctype/user/user.py View File

@@ -205,7 +205,7 @@ class User(Document):
_update_password(user=self.name, pwd=new_password,
logout_all_sessions=self.logout_all_sessions)

if not self.flags.no_welcome_mail and self.send_welcome_email:
if not self.flags.no_welcome_mail and cint(self.send_welcome_email):
self.send_welcome_mail_to_user()
self.flags.email_sent = 1
if frappe.session.user != 'Guest':
@@ -577,7 +577,7 @@ def update_password(new_password, logout_all_sessions=0, key=None, old_password=
return redirect_url if redirect_url else "/"

@frappe.whitelist(allow_guest=True)
def test_password_strength(new_password, key=None, old_password=None, user_data=[]):
def test_password_strength(new_password, key=None, old_password=None, user_data=None):
from frappe.utils.password_strength import test_password_strength as _test_password_strength

password_policy = frappe.db.get_value("System Settings", None,


+ 53
- 191
frappe/core/doctype/user_email/user_email.json View File

@@ -1,201 +1,63 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-03-30 10:04:25.828742",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"actions": [],
"creation": "2016-03-30 10:04:25.828742",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"email_account",
"email_id",
"column_break_3",
"awaiting_password",
"enable_outgoing"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Email Account",
"length": 0,
"no_copy": 0,
"options": "Email Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "email_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Email Account",
"options": "Email Account",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "email_account.email_id",
"fieldname": "email_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email ID",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "email_id",
"fieldtype": "Data",
"label": "Email ID",
"options": "Email",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "email_account.awaiting_password",
"fieldname": "awaiting_password",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Awaiting Password",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "0",
"fetch_from": "email_account.awaiting_password",
"fieldname": "awaiting_password",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Awaiting Password",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "email_account.enable_outgoing",
"fieldname": "enable_outgoing",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enable Outgoing",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"default": "0",
"fetch_from": "email_account.enable_outgoing",
"fieldname": "enable_outgoing",
"fieldtype": "Check",
"label": "Enable Outgoing",
"read_only": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-05-25 22:43:34.045787",
"modified_by": "Administrator",
"module": "Core",
"name": "User Email",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
],
"istable": 1,
"links": [],
"modified": "2020-04-06 19:19:12.130246",
"modified_by": "Administrator",
"module": "Core",
"name": "User Email",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

+ 7
- 1
frappe/core/page/dashboard/dashboard.js View File

@@ -97,7 +97,13 @@ class Dashboard {
container: this.container,
type: "chart",
columns: 2,
allow_sorting: false,
options: {
allow_sorting: false,
allow_create: false,
allow_delete: false,
allow_hiding: false,
allow_edit: false,
},
widgets: this.charts,
});
})


+ 13
- 11
frappe/custom/desk_page/customization/customization.json View File

@@ -1,17 +1,19 @@
{
"cards": [
{
"links": "[\n {\n \"label\": \"Dashboard\",\n \"name\": \"Dashboard\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart\",\n \"name\": \"Dashboard Chart\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart Source\",\n \"name\": \"Dashboard Chart Source\",\n \"type\": \"doctype\"\n }\n]",
"title": "Dashboards"
"hidden": 0,
"label": "Dashboards",
"links": "[\n {\n \"label\": \"Dashboard\",\n \"name\": \"Dashboard\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart\",\n \"name\": \"Dashboard Chart\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart Source\",\n \"name\": \"Dashboard Chart Source\",\n \"type\": \"doctype\"\n }\n]"
},
{
"icon": "fa fa-glass",
"links": "[\n {\n \"description\": \"Change field properties (hide, readonly, permission etc.)\",\n \"label\": \"Customize Form\",\n \"name\": \"Customize Form\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add fields to forms.\",\n \"label\": \"Custom Field\",\n \"name\": \"Custom Field\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom javascript to forms.\",\n \"label\": \"Custom Script\",\n \"name\": \"Custom Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom forms.\",\n \"label\": \"DocType\",\n \"name\": \"DocType\",\n \"type\": \"doctype\"\n }\n]",
"title": "Form Customization"
"hidden": 0,
"label": "Form Customization",
"links": "[\n {\n \"description\": \"Change field properties (hide, readonly, permission etc.)\",\n \"label\": \"Customize Form\",\n \"name\": \"Customize Form\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add fields to forms.\",\n \"label\": \"Custom Field\",\n \"name\": \"Custom Field\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom javascript to forms.\",\n \"label\": \"Custom Script\",\n \"name\": \"Custom Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom forms.\",\n \"label\": \"DocType\",\n \"name\": \"DocType\",\n \"type\": \"doctype\"\n }\n]"
},
{
"links": "[\n {\n \"description\": \"Add your own translations\",\n \"label\": \"Custom Translations\",\n \"name\": \"Translation\",\n \"type\": \"doctype\"\n }\n]",
"title": "Other"
"hidden": 0,
"label": "Other",
"links": "[\n {\n \"description\": \"Add your own translations\",\n \"label\": \"Custom Translations\",\n \"name\": \"Translation\",\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Administration",
@@ -25,7 +27,7 @@
"idx": 0,
"is_standard": 1,
"label": "Customization",
"modified": "2020-03-12 16:30:42.155206",
"modified": "2020-04-01 11:24:40.787109",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customization",
@@ -34,17 +36,17 @@
"pin_to_top": 0,
"shortcuts": [
{
"is_query_report": 0,
"label": "Customize Form",
"link_to": "Customize Form",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "Custom Role",
"link_to": "Custom Role",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "Custom Script",
"link_to": "Custom Script",
"type": "DocType"
}


+ 2
- 2
frappe/custom/doctype/customize_form_field/customize_form_field.json View File

@@ -358,7 +358,7 @@
"default": "0",
"fieldname": "allow_in_quick_entry",
"fieldtype": "Check",
"label": " Allow in Quick Entry "
"label": "Allow in Quick Entry"
},
{
"fieldname": "property_depends_on_section",
@@ -385,7 +385,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-03-16 14:53:40.619043",
"modified": "2020-04-07 14:53:40.619043",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form Field",


+ 165
- 125
frappe/desk/desktop.py View File

@@ -4,24 +4,34 @@

from __future__ import unicode_literals
import frappe
import json
from frappe import _, DoesNotExistError
from json import loads, dumps
from frappe import _, DoesNotExistError, ValidationError, _dict
from frappe.boot import get_allowed_pages, get_allowed_reports
from six import string_types
from frappe.cache_manager import build_domain_restriced_doctype_cache, build_domain_restriced_page_cache, build_table_count_cache
from frappe.cache_manager import (
build_domain_restriced_doctype_cache,
build_domain_restriced_page_cache,
build_table_count_cache
)

class Workspace:
def __init__(self, page_name):
self.page_name = page_name

def build_cache(self):
self.doc = frappe.get_doc("Desk Page", self.page_name)
self.get_pages_to_extend()
self.extended_cards = []
self.extended_charts = []
self.extended_shortcuts = []

user = frappe.get_user()
user.build_permissions()
self.user = user

user_doc = frappe.get_doc('User', frappe.session.user)
self.blocked_modules = user_doc.get_blocked_modules()
self.doc = self.get_page_for_user()

if self.doc.module in self.blocked_modules:
raise frappe.PermissionError

self.user = user
self.allowed_pages = get_allowed_pages()
self.allowed_reports = get_allowed_reports()

@@ -29,16 +39,27 @@ class Workspace:
self.restricted_doctypes = build_domain_restriced_doctype_cache()
self.restricted_pages = build_domain_restriced_page_cache()

def get_page_for_user(self):
filters = {
'extends': self.page_name,
'for_user': frappe.session.user
}
pages = frappe.get_list("Desk Page", filters=filters)
if pages:
return frappe.get_doc("Desk Page", pages[0])

self.get_pages_to_extend()
return frappe.get_doc("Desk Page", self.page_name)

def get_pages_to_extend(self):
pages = frappe.get_all("Desk Page", filters={
"extends": self.page_name,
'restrict_to_domain': ['in', frappe.get_active_domains()]
'restrict_to_domain': ['in', frappe.get_active_domains()],
'for_user': '',
'module': ['not in', self.blocked_modules]
})

pages = [frappe.get_doc("Desk Page", page['name']) for page in pages]
self.extended_cards = []
self.extended_charts = []
self.extended_shortcuts = []

for page in pages:
self.extended_cards = self.extended_cards + page.cards
@@ -61,17 +82,17 @@ class Workspace:

def build_workspace(self):
self.cards = {
'label': self.doc.cards_label,
'label': _(self.doc.cards_label),
'items': self.get_cards()
}

self.charts = {
'label': self.doc.charts_label,
'label': _(self.doc.charts_label),
'items': self.get_charts()
}

self.shortcuts = {
'label': self.doc.shortcuts_label,
'label': _(self.doc.shortcuts_label),
'items': self.get_shortcuts()
}

@@ -105,18 +126,21 @@ class Workspace:

item["count"] = count

# Translate label
item["label"] = _(item.label) if item.label else _(item.name)

return item

new_data = []
for section in cards:
new_items = []
if isinstance(section.links, string_types):
links = json.loads(section.links)
links = loads(section.links)
else:
links = section.links

for item in links:
item = frappe._dict(item)
item = _dict(item)

# Condition: based on country
if item.country and item.country != default_country:
@@ -125,15 +149,15 @@ class Workspace:
# Check if user is allowed to view
if self.is_item_allowed(item.name, item.type):
prepared_item = _prepare_item(item)
new_items.append(item)
new_items.append(prepared_item)

if new_items:
if isinstance(section, frappe._dict):
if isinstance(section, _dict):
new_section = section.copy()
else:
new_section = section.as_dict().copy()
new_section["links"] = new_items
new_section["label"] = section.title
new_section["label"] = _(new_section["label"])
new_data.append(new_section)

return new_data
@@ -147,7 +171,8 @@ class Workspace:

for chart in charts:
if frappe.has_permission('Dashboard Chart', doc=chart.chart_name):
chart.label = chart.label if chart.label else chart.chart_name
# Translate label
chart.label = _(chart.label) if chart.label else _(chart.chart_name)
all_charts.append(chart)

return all_charts
@@ -167,21 +192,23 @@ class Workspace:

for item in shortcuts:
new_item = item.as_dict().copy()
new_item['name'] = _(item.link_to)
if self.is_item_allowed(item.link_to, item.type) and _in_active_domains(item):
if item.type == "Page":
page = self.allowed_pages[item.link_to]
new_item['label'] = _(page.get("title", frappe.unscrub(item.link_to)))
if item.type == "Report":
report = self.allowed_reports.get(item.link_to, {})
if report.get("report_type") in ["Query Report", "Script Report"]:
new_item['is_query_report'] = 1
else:
new_item['ref_doctype'] = report.get('ref_doctype')

# Translate label
new_item["label"] = _(item.label) if item.label else _(item.link_to)

items.append(new_item)

return items

@frappe.whitelist()
@frappe.read_only()
def get_desktop_page(page):
"""Applies permissions, customizations and returns the configruration for a page
on desk.
@@ -192,9 +219,8 @@ def get_desktop_page(page):
Returns:
dict: dictionary of cards, charts and shortcuts to be displayed on website
"""
wspace = Workspace(page)
try:
wspace.build_cache()
wspace = Workspace(page)
wspace.build_workspace()
return {
'charts': wspace.charts,
@@ -213,9 +239,14 @@ def get_desk_sidebar_items():
"""Get list of sidebar items for desk
"""
# don't get domain restricted pages
blocked_modules = frappe.get_doc('User', frappe.session.user).get_blocked_modules()

filters = {
'restrict_to_domain': ['in', frappe.get_active_domains()],
'extends_another_page': False
'extends_another_page': 0,
'is_standard': 1,
'for_user': '',
'module': ['not in', blocked_modules]
}

if not frappe.local.conf.developer_mode:
@@ -228,8 +259,10 @@ def get_desk_sidebar_items():
from collections import defaultdict
sidebar_items = defaultdict(list)

# The order will be maintained while categorizing
for page in pages:
# The order will be maintained while categorizing
# Translate label
page['label'] = _(page.get('name'))
sidebar_items[page["category"]].append(page)
return sidebar_items

@@ -242,8 +275,8 @@ def get_table_with_counts():

def get_custom_reports_and_doctypes(module):
return [
frappe._dict({
"title": "Custom",
_dict({
"label": "Custom",
"links": get_custom_doctype_list(module) + get_custom_report_list(module)
})
]
@@ -280,104 +313,111 @@ def get_custom_report_list(module):

return out

def make_them_pages():
"""Helper function to make pages
"""
pages = [
('Desk', 'frappe', 'octicon octicon-calendar'),
('Settings', 'frappe', 'octicon octicon-settings'),
('Users and Permissions', 'frappe', 'octicon octicon-settings'),
('Customization', 'frappe', 'octicon octicon-settings'),
('Integrations', 'frappe', 'octicon octicon-globe'),
('Core', 'frappe', 'octicon octicon-circuit-board'),
('Website', 'frappe', 'octicon octicon-globe'),
('Getting Started', 'erpnext', 'fa fa-check-square-o'),
('Accounts', 'erpnext', 'octicon octicon-repo'),
('Selling', 'erpnext', 'octicon octicon-tag'),
('Buying', 'erpnext', 'octicon octicon-briefcase'),
('Stock', 'erpnext', 'octicon octicon-package'),
('Assets', 'erpnext', 'octicon octicon-database'),
('Projects', 'erpnext', 'octicon octicon-rocket'),
('CRM', 'erpnext', 'octicon octicon-broadcast'),
('Support', 'erpnext', 'fa fa-check-square-o'),
('HR', 'erpnext', 'octicon octicon-organization'),
('Quality Management', 'erpnext', 'fa fa-check-square-o'),
('Manufacturing', 'erpnext', 'octicon octicon-tools'),
('Retail', 'erpnext', 'octicon octicon-credit-card'),
('Education', 'erpnext', 'octicon octicon-mortar-board'),
('Healthcare', 'erpnext', 'fa fa-heartbeat'),
('Agriculture', 'erpnext', 'octicon octicon-globe'),
('Non Profit', 'erpnext', 'octicon octicon-heart'),
('Help', 'erpnext', 'octicon octicon-device-camera-video')
]
def get_custom_workspace_for_user(page):
"""Get custom page from desk_page if exists or create one

for page in pages:
print("Processing Page: {0}".format(page[0]))
make_them_cards(page[0], page[2])
Args:
page (stirng): Page name

Returns:
Object: Document object
"""
filters = {
'extends': page,
'for_user': frappe.session.user
}
pages = frappe.get_list("Desk Page", filters=filters)
if pages:
return frappe.get_doc("Desk Page", pages[0])
doc = frappe.new_doc("Desk Page")
doc.extends = page
doc.for_user = frappe.session.user
return doc


def make_them_cards(page_name, from_module=None, to_module=None, icon=None):
from frappe.desk.moduleview import get
@frappe.whitelist()
def save_customization(page, config):
"""Save customizations as a separate doctype in Desk page per user

if not from_module:
from_module = page_name
Args:
page (string): Name of the page to be edited
config (dict): Dictionary config of al widgets

if not to_module:
to_module = page_name
Returns:
Boolean: Customization saving status
"""
original_page = frappe.get_doc("Desk Page", page)
page_doc = get_custom_workspace_for_user(page)

# Update field values
page_doc.update({
"charts_label": original_page.charts_label,
"cards_label": original_page.cards_label,
"shortcuts_label": original_page.shortcuts_label,
"icon": original_page.icon,
"module": original_page.module,
"developer_mode_only": original_page.developer_mode_only,
"category": original_page.category
})

config = _dict(loads(config))
page_doc.charts = prepare_widget(config.charts, "Desk Chart", "charts")
page_doc.shortcuts = prepare_widget(config.shortcuts, "Desk Shortcut", "shortcuts")
page_doc.cards = prepare_widget(config.cards, "Desk Card", "cards")

# Set label
page_doc.label = page + '-' + frappe.session.user

try:
modules = get(from_module)['data']
except:
return

# Find or make page doc
if frappe.db.exists("Desk Page", page_name):
page = frappe.get_doc("Desk Page", page_name)
print("--- Got Page: {0}".format(page.name))
else:
page = frappe.new_doc("Desk Page")
page.label = page_name
page.cards = []
page.icon = icon
print("--- New Page: {0}".format(page.name))

# Guess Which Module
if not to_module and frappe.db.exists("Module Def", page_name):
page.module = page_name

if to_module:
page.module = to_module
elif frappe.db.exists("Module Def", page_name):
page.module = page_name

for data in modules:
# Create a New Card Child Doc
card = frappe.new_doc("Desk Card")

# Data clean up
for item in data['items']:
try:
del item['count']
del item['incomplete_dependencies']
except KeyError:
pass

# Set Child doc values
card.title = data['label']
card.icon = data.get('icon')
# Pretty dump JSON
card.links = json.dumps(data['items'], indent=4, sort_keys=True)

# Set Parent attributes
card.parent = page.name
card.parenttype = page.doctype
card.parentfield = "cards"

# Add cards to page doc
print("------- Adding Card: {0}".format(card.title))
page.cards.append(card)

# End it all
page.save()
frappe.db.commit()
return
if page_doc.is_new():
page_doc.insert(ignore_permissions=True)
else:
page_doc.save(ignore_permissions=True)
except (ValidationError, TypeError) as e:
# Create a json string to log
json_config = dumps(config, sort_keys=True, indent=4)

# Error log body
log = \
"""
page: {0}
config: {1}
exception: {2}
""".format(page, json_config, e)
frappe.log_error(log, _("Could not save customization"))
return False

return True


def prepare_widget(config, doctype, parentfield):
"""Create widget child table entries with parent details

Args:
config (dict): Dictionary containing widget config
doctype (string): Doctype name of the child table
parentfield (string): Parent field for the child table

Returns:
TYPE: List of Document objects
"""
order = config.get('order')
widgets = config.get('widgets')
prepare_widget_list = []
for idx, name in enumerate(order):
wid_config = widgets[name].copy()
# Some cleanup
wid_config.pop("name", None)

# New Doc
doc = frappe.new_doc(doctype)
doc.update(wid_config)

# Manually Set IDX
doc.idx = idx + 1

# Set Parent Field
doc.parentfield = parentfield

prepare_widget_list.append(doc)
return prepare_widget_list

+ 3
- 2
frappe/desk/doctype/dashboard_chart/dashboard_chart.json View File

@@ -49,7 +49,8 @@
"fieldname": "chart_type",
"fieldtype": "Select",
"label": "Chart Type",
"options": "Count\nSum\nAverage\nGroup By\nCustom\nReport"
"options": "Count\nSum\nAverage\nGroup By\nCustom\nReport",
"set_only_once": 1
},
{
"depends_on": "eval:doc.chart_type === 'Custom'",
@@ -215,7 +216,7 @@
}
],
"links": [],
"modified": "2020-03-31 16:00:01.987059",
"modified": "2020-04-08 18:54:36.739183",
"modified_by": "Administrator",
"module": "Desk",
"name": "Dashboard Chart",


+ 15
- 12
frappe/desk/doctype/desk_card/desk_card.json View File

@@ -2,11 +2,12 @@
"actions": [],
"creation": "2020-01-29 14:45:54.383089",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"title",
"label",
"column_break_2",
"icon",
"hidden",
"section_break_3",
"links"
],
@@ -18,13 +19,6 @@
"options": "JSON",
"reqd": 1
},
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1
},
{
"fieldname": "section_break_3",
"fieldtype": "Section Break"
@@ -34,14 +28,23 @@
"fieldtype": "Column Break"
},
{
"fieldname": "icon",
"default": "0",
"fieldname": "hidden",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Hidden"
},
{
"fieldname": "label",
"fieldtype": "Data",
"label": "Icon"
"in_list_view": 1,
"label": "Label",
"reqd": 1
}
],
"istable": 1,
"links": [],
"modified": "2020-02-03 12:40:42.595122",
"modified": "2020-03-31 14:38:06.303847",
"modified_by": "Administrator",
"module": "Desk",
"name": "Desk Card",


+ 1
- 1
frappe/desk/doctype/desk_chart/desk_chart.json View File

@@ -26,7 +26,7 @@
],
"istable": 1,
"links": [],
"modified": "2020-03-20 10:04:13.992228",
"modified": "2020-03-31 13:33:13.128804",
"modified_by": "Administrator",
"module": "Desk",
"name": "Desk Chart",


+ 14
- 10
frappe/desk/doctype/desk_page/desk_page.js View File

@@ -2,17 +2,21 @@
// For license information, please see license.txt

frappe.ui.form.on('Desk Page', {
refresh: function(frm) {
setup: function(frm) {
frm.get_field("is_standard").toggle(frappe.boot.developer_mode);
frm.get_field("extends_another_page").toggle(frappe.boot.developer_mode);
if (!frappe.boot.developer_mode) {
frm.set_read_only();
frm.fields
.filter(field => field.has_input)
.forEach(field => {
frm.set_df_property(field.df.fieldname, "read_only", "1");
});
frm.disable_save();
if (!frappe.boot.developer_mode || frm.doc.for_user) {
frm.trigger('disable_form');
}
},

disable_form: function(frm) {
frm.set_read_only();
frm.fields
.filter(field => field.has_input)
.forEach(field => {
frm.set_df_property(field.df.fieldname, "read_only", "1");
});
frm.disable_save();
}
});
});

+ 21
- 5
frappe/desk/doctype/desk_page/desk_page.json View File

@@ -9,6 +9,7 @@
"field_order": [
"label",
"extends",
"for_user",
"module",
"category",
"restrict_to_domain",
@@ -36,7 +37,6 @@
"fieldname": "label",
"fieldtype": "Data",
"label": "Name",
"length": 22,
"unique": 1
},
{
@@ -52,6 +52,7 @@
"options": "Desk Chart"
},
{
"depends_on": "eval:!doc.extends_another_page || !doc.is_standard",
"fieldname": "shortcuts",
"fieldtype": "Table",
"label": "Shortcuts",
@@ -136,16 +137,19 @@
"search_index": 1
},
{
"depends_on": "eval:!doc.extends_another_page || !doc.is_standard",
"fieldname": "charts_label",
"fieldtype": "Data",
"label": "Label"
},
{
"depends_on": "eval:!doc.extends_another_page || !doc.is_standard",
"fieldname": "shortcuts_label",
"fieldtype": "Data",
"label": "Label"
},
{
"depends_on": "eval:!doc.extends_another_page || !doc.is_standard",
"fieldname": "cards_label",
"fieldtype": "Data",
"label": "Label"
@@ -166,24 +170,36 @@
"default": "0",
"fieldname": "is_standard",
"fieldtype": "Check",
"label": "Is Standard"
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Is Standard",
"search_index": 1
},
{
"default": "0",
"fieldname": "extends_another_page",
"fieldtype": "Check",
"label": "Extends Another Page"
"label": "Extends Another Page",
"search_index": 1
},
{
"depends_on": "eval:doc.extends_another_page == 1",
"fieldname": "extends",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Extends",
"options": "Desk Page"
"options": "Desk Page",
"search_index": 1
},
{
"fieldname": "for_user",
"fieldtype": "Data",
"label": "For User",
"read_only": 1
}
],
"links": [],
"modified": "2020-03-12 16:38:16.206732",
"modified": "2020-03-26 12:35:41.981432",
"modified_by": "Administrator",
"module": "Desk",
"name": "Desk Page",


+ 12
- 10
frappe/desk/doctype/desk_shortcut/desk_shortcut.json View File

@@ -6,11 +6,11 @@
"engine": "InnoDB",
"field_order": [
"type",
"icon",
"label",
"column_break_4",
"link_to",
"icon",
"restrict_to_domain",
"is_query_report",
"section_break_5",
"stats_filter",
"column_break_3",
@@ -51,6 +51,7 @@
"label": "Format"
},
{
"depends_on": "eval:doc.type == \"DocType\" && frappe.boot.developer_mode",
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"label": "Count Filter"
@@ -61,13 +62,7 @@
"label": "Color"
},
{
"default": "0",
"depends_on": "eval:doc.type === \"Report\"",
"fieldname": "is_query_report",
"fieldtype": "Check",
"label": "Is Query Report"
},
{
"depends_on": "eval:frappe.boot.developer_mode",
"fieldname": "icon",
"fieldtype": "Data",
"label": "Icon"
@@ -77,15 +72,22 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:frappe.boot.developer_mode",
"fieldname": "restrict_to_domain",
"fieldtype": "Link",
"label": "Restrict to Domain",
"options": "Domain"
},
{
"fieldname": "label",
"fieldtype": "Data",
"label": "Label",
"reqd": 1
}
],
"istable": 1,
"links": [],
"modified": "2020-03-11 13:09:00.180528",
"modified": "2020-04-07 19:04:23.645198",
"modified_by": "Administrator",
"module": "Desk",
"name": "Desk Shortcut",


+ 9
- 0
frappe/desk/doctype/notification_settings/notification_settings.js View File

@@ -8,5 +8,14 @@ frappe.ui.form.on('Notification Settings', {
route: '#modules/Settings',
type: 'Custom'
});
},

refresh: (frm) => {
if (frappe.user.has_role('System Manager')) {
frm.add_custom_button('Go to Notification Settings List', () => {
frappe.set_route('List', 'Notification Settings');
});
}
}

});

+ 8
- 1
frappe/desk/doctype/notification_settings/notification_settings.py View File

@@ -62,7 +62,14 @@ def get_subscribed_documents():
def get_permission_query_conditions(user):
if not user: user = frappe.session.user

return '''(`tabNotification Settings`.user = '{user}')'''.format(user=user)
if user == 'Administrator':
return

roles = frappe.get_roles(user)
if "System Manager" in roles:
return '''(`tabNotification Settings`.name != 'Administrator')'''

return '''(`tabNotification Settings`.name = '{user}')'''.format(user=user)

@frappe.whitelist()
def set_seen_value(value, user):

+ 31
- 0
frappe/desk/doctype/todo/todo_calendar.js View File

@@ -0,0 +1,31 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt

frappe.views.calendar["ToDo"] = {
field_map: {
"start": "date",
"end": "date",
"id": "name",
"title": "description",
"allDay": "allDay",
"progress": "progress"
},
gantt: true,
filters: [
{
"fieldtype": "Link",
"fieldname": "reference_type",
"options": "Task",
"label": __("Task")
},
{
"fieldtype": "Dynamic Link",
"fieldname": "reference_name",
"options": "reference_type",
"label": __("Task")
}
],
get_events_method: "frappe.desk.calendar.get_events"
};


+ 11
- 5
frappe/desk/page/setup_wizard/install_fixtures.py View File

@@ -8,16 +8,22 @@ from frappe import _
from frappe.desk.doctype.global_search_settings.global_search_settings import update_global_search_doctypes

def install():
update_genders_and_salutations()
update_genders()
update_salutations()
update_global_search_doctypes()
setup_email_linking()

@frappe.whitelist()
def update_genders_and_salutations():
default_genders = [_("Male"), _("Female"), _("Other")]
default_salutations = [_("Mr"), _("Ms"), _('Mx'), _("Dr"), _("Mrs"), _("Madam"), _("Miss"), _("Master"), _("Prof")]
def update_genders():
default_genders = [_("Male"), _("Female"), _("Other"),_("Transgender"), _("Genderqueer"), _("Non-Conforming"),_("Prefer not to say")]
records = [{'doctype': 'Gender', 'gender': d} for d in default_genders]
records += [{'doctype': 'Salutation', 'salutation': d} for d in default_salutations]
for record in records:
frappe.get_doc(record).insert(ignore_permissions=True, ignore_if_duplicate=True)

@frappe.whitelist()
def update_salutations():
default_salutations = [_("Mr"), _("Ms"), _('Mx'), _("Dr"), _("Mrs"), _("Madam"), _("Miss"), _("Master"), _("Prof")]
records = [{'doctype': 'Salutation', 'salutation': d} for d in default_salutations]
for record in records:
doc = frappe.new_doc(record.get("doctype"))
doc.update(record)


+ 2
- 1
frappe/email/doctype/email_account/email_account.json View File

@@ -66,6 +66,7 @@
"fieldtype": "Data",
"in_global_search": 1,
"label": "Email Address",
"options": "Email",
"reqd": 1
},
{
@@ -410,7 +411,7 @@
],
"icon": "fa fa-inbox",
"links": [],
"modified": "2019-12-18 15:56:39.744520",
"modified": "2020-04-06 19:20:50.491146",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Account",


+ 2
- 3
frappe/email/doctype/email_group/email_group.py View File

@@ -68,8 +68,7 @@ def add_subscribers(name, email_list):
email_list = email_list.replace(",", "\n").split("\n")

template = frappe.db.get_value('Email Group', name, 'welcome_email_template')
if template:
welcome_email = frappe.get_doc("Email Template", template)
welcome_email = frappe.get_doc("Email Template", template) if template else None

count = 0
for email in email_list:
@@ -108,4 +107,4 @@ def send_welcome_email(welcome_email, email, email_group):
)

message = frappe.render_template(welcome_email.response, args)
frappe.sendmail(email, subject=welcome_email.subject, message=message)
frappe.sendmail(email, subject=welcome_email.subject, message=message)

+ 15
- 14
frappe/email/doctype/newsletter/newsletter.py View File

@@ -51,9 +51,6 @@ class Newsletter(WebsiteGenerator):

frappe.msgprint(_("Scheduled to send to {0} recipients").format(len(self.recipients)))

frappe.db.set(self, "email_sent", 1)
frappe.db.set(self, "schedule_send", now_datetime())
frappe.db.set(self, 'scheduled_to_send', len(self.recipients))
else:
frappe.msgprint(_("Newsletter should have atleast one recipient"))

@@ -71,8 +68,8 @@ class Newsletter(WebsiteGenerator):

attachments = []
if self.send_attachements:
files = frappe.get_all("File", fields = ["name"], filters = {"attached_to_doctype": "Newsletter",
"attached_to_name":self.name}, order_by="creation desc")
files = frappe.get_all("File", fields=["name"], filters={"attached_to_doctype": "Newsletter",
"attached_to_name": self.name}, order_by="creation desc")

for file in files:
try:
@@ -82,17 +79,21 @@ class Newsletter(WebsiteGenerator):
except IOError:
frappe.throw(_("Unable to find attachment {0}").format(file.name))

send(recipients = self.recipients, sender = sender,
subject = self.subject, message = self.message,
reference_doctype = self.doctype, reference_name = self.name,
add_unsubscribe_link = self.send_unsubscribe_link, attachments=attachments,
unsubscribe_method = "/unsubscribe",
unsubscribe_params = {"name": self.name},
send_priority = 0, queue_separately=True)
send(recipients=self.recipients, sender=sender,
subject=self.subject, message=self.message,
reference_doctype=self.doctype, reference_name=self.name,
add_unsubscribe_link=self.send_unsubscribe_link, attachments=attachments,
unsubscribe_method="/unsubscribe",
unsubscribe_params={"name": self.name},
send_priority=0, queue_separately=True)

if not frappe.flags.in_test:
frappe.db.auto_commit_on_many_writes = False

self.db_set("email_sent", 1)
self.db_set("schedule_send", now_datetime())
self.db_set("scheduled_to_send", len(self.recipients))

def get_recipients(self):
"""Get recipients from Email Group"""
recipients_list = []
@@ -268,6 +269,6 @@ def send_scheduled_email():
scheduled_newsletter = frappe.get_all('Newsletter', filters = {
'schedule_send': ('<=', now_datetime()),
'email_sent': 0
}, fields = ['name'])
}, fields = ['name'], ignore_ifnull=True)
for newsletter in scheduled_newsletter:
send_newsletter(newsletter.name)
send_newsletter(newsletter.name)

+ 16
- 12
frappe/integrations/desk_page/integrations/integrations.json View File

@@ -1,25 +1,29 @@
{
"cards": [
{
"links": "[\n {\n \"description\": \"Dropbox backup settings\",\n \"label\": \"Dropbox Settings\",\n \"name\": \"Dropbox Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"S3 Backup Settings\",\n \"label\": \"S3 Backup Settings\",\n \"name\": \"S3 Backup Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Backup.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]",
"title": "Backup"
"hidden": 0,
"label": "Backup",
"links": "[\n {\n \"description\": \"Dropbox backup settings\",\n \"label\": \"Dropbox Settings\",\n \"name\": \"Dropbox Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"S3 Backup Settings\",\n \"label\": \"S3 Backup Settings\",\n \"name\": \"S3 Backup Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Backup.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]"
},
{
"links": "[\n {\n \"description\": \"Google API Settings.\",\n \"label\": \"Google Settings\",\n \"name\": \"Google Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Contacts Integration.\",\n \"label\": \"Google Contacts\",\n \"name\": \"Google Contacts\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Calendar Integration.\",\n \"label\": \"Google Calendar\",\n \"name\": \"Google Calendar\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Integration.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]",
"title": "Google Services"
"hidden": 0,
"label": "Google Services",
"links": "[\n {\n \"description\": \"Google API Settings.\",\n \"label\": \"Google Settings\",\n \"name\": \"Google Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Contacts Integration.\",\n \"label\": \"Google Contacts\",\n \"name\": \"Google Contacts\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Calendar Integration.\",\n \"label\": \"Google Calendar\",\n \"name\": \"Google Calendar\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Integration.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]"
},
{
"links": "[\n {\n \"description\": \"Webhooks calling API requests into web apps\",\n \"label\": \"Webhook\",\n \"name\": \"Webhook\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Slack Webhooks for internal integration\",\n \"label\": \"Slack Webhook URL\",\n \"name\": \"Slack Webhook URL\",\n \"type\": \"doctype\"\n }\n]",
"title": "Webhook"
"hidden": 0,
"label": "Webhook",
"links": "[\n {\n \"description\": \"Webhooks calling API requests into web apps\",\n \"label\": \"Webhook\",\n \"name\": \"Webhook\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Slack Webhooks for internal integration\",\n \"label\": \"Slack Webhook URL\",\n \"name\": \"Slack Webhook URL\",\n \"type\": \"doctype\"\n }\n]"
},
{
"links": "[\n {\n \"description\": \"Enter keys to enable login via Facebook, Google, GitHub.\",\n \"label\": \"Social Login Key\",\n \"name\": \"Social Login Key\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Ldap settings\",\n \"label\": \"LDAP Settings\",\n \"name\": \"LDAP Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Register OAuth Client App\",\n \"label\": \"OAuth Client\",\n \"name\": \"OAuth Client\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for OAuth Provider\",\n \"label\": \"OAuth Provider Settings\",\n \"name\": \"OAuth Provider Settings\",\n \"type\": \"doctype\"\n }\n]",
"title": "Authentication"
"hidden": 0,
"label": "Authentication",
"links": "[\n {\n \"description\": \"Enter keys to enable login via Facebook, Google, GitHub.\",\n \"label\": \"Social Login Key\",\n \"name\": \"Social Login Key\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Ldap settings\",\n \"label\": \"LDAP Settings\",\n \"name\": \"LDAP Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Register OAuth Client App\",\n \"label\": \"OAuth Client\",\n \"name\": \"OAuth Client\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for OAuth Provider\",\n \"label\": \"OAuth Provider Settings\",\n \"name\": \"OAuth Provider Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"icon": "fa fa-star",
"links": "[\n {\n \"description\": \"Braintree payment gateway settings\",\n \"label\": \"Braintree Settings\",\n \"name\": \"Braintree Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"PayPal payment gateway settings\",\n \"label\": \"PayPal Settings\",\n \"name\": \"PayPal Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Razorpay Payment gateway settings\",\n \"label\": \"Razorpay Settings\",\n \"name\": \"Razorpay Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stripe payment gateway settings\",\n \"label\": \"Stripe Settings\",\n \"name\": \"Stripe Settings\",\n \"type\": \"doctype\"\n }\n]",
"title": "Payments"
"hidden": 0,
"label": "Payments",
"links": "[\n {\n \"description\": \"Braintree payment gateway settings\",\n \"label\": \"Braintree Settings\",\n \"name\": \"Braintree Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"PayPal payment gateway settings\",\n \"label\": \"PayPal Settings\",\n \"name\": \"PayPal Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Razorpay Payment gateway settings\",\n \"label\": \"Razorpay Settings\",\n \"name\": \"Razorpay Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stripe payment gateway settings\",\n \"label\": \"Stripe Settings\",\n \"name\": \"Stripe Settings\",\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Administration",
@@ -34,7 +38,7 @@
"idx": 0,
"is_standard": 1,
"label": "Integrations",
"modified": "2020-03-12 16:30:42.823316",
"modified": "2020-04-01 11:24:40.751651",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Integrations",


+ 109
- 467
frappe/integrations/doctype/dropbox_settings/dropbox_settings.json View File

@@ -1,487 +1,129 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-09-21 10:12:57.399174",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"editable_grid": 1,
"creation": "2016-09-21 10:12:57.399174",
"doctype": "DocType",
"document_type": "System",
"editable_grid": 1,
"field_order": [
"enabled",
"send_notifications_to",
"send_email_for_successful_backup",
"backup_frequency",
"limit_no_of_backups",
"no_of_backups",
"file_backup",
"app_access_key",
"app_secret_key",
"allow_dropbox_access",
"dropbox_access_key",
"dropbox_access_secret",
"dropbox_access_token"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "enabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enabled",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "0",
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "send_notifications_to",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Send Notifications To",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "send_notifications_to",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Send Notifications To",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"description": "Note: By default emails for failed backups are sent.",
"fieldname": "send_email_for_successful_backup",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Send Email for Successful Backup",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "1",
"description": "Note: By default emails for failed backups are sent.",
"fieldname": "send_email_for_successful_backup",
"fieldtype": "Check",
"label": "Send Email for Successful Backup"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "backup_frequency",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Backup Frequency",
"length": 0,
"no_copy": 0,
"options": "\nDaily\nWeekly",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "backup_frequency",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Backup Frequency",
"options": "\nDaily\nWeekly",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "limit_no_of_backups",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Limit Number of DB Backups",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "0",
"fieldname": "limit_no_of_backups",
"fieldtype": "Check",
"label": "Limit Number of DB Backups"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "5",
"depends_on": "eval:doc.limit_no_of_backups",
"fieldname": "no_of_backups",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Number of DB Backups",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "5",
"depends_on": "eval:doc.limit_no_of_backups",
"fieldname": "no_of_backups",
"fieldtype": "Int",
"label": "Number of DB Backups"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "file_backup",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "File Backup",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "1",
"fieldname": "file_backup",
"fieldtype": "Check",
"label": "File Backup"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "app_access_key",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "App Access Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "app_access_key",
"fieldtype": "Data",
"label": "App Access Key"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "app_secret_key",
"fieldtype": "Password",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "App Secret Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "app_secret_key",
"fieldtype": "Password",
"label": "App Secret Key"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow_dropbox_access",
"fieldtype": "Button",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow Dropbox Access",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "allow_dropbox_access",
"fieldtype": "Button",
"label": "Allow Dropbox Access"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "dropbox_access_key",
"fieldtype": "Password",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Dropbox Access Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "dropbox_access_key",
"fieldtype": "Password",
"hidden": 1,
"label": "Dropbox Access Key",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "dropbox_access_secret",
"fieldtype": "Password",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Dropbox Access Secret",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "dropbox_access_secret",
"fieldtype": "Password",
"hidden": 1,
"label": "Dropbox Access Secret",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "dropbox_access_token",
"fieldtype": "Password",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Dropbox Access Token",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldname": "dropbox_access_token",
"fieldtype": "Password",
"hidden": 1,
"label": "Dropbox Access Token"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 1,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2019-01-03 05:44:40.520943",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Dropbox Settings",
"name_case": "",
"owner": "Administrator",
],
"in_create": 1,
"issingle": 1,
"modified": "2019-08-22 16:26:44.468391",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Dropbox Settings",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 1,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}
],
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

+ 17
- 34
frappe/integrations/doctype/dropbox_settings/dropbox_settings.py View File

@@ -3,22 +3,25 @@
# For license information, please see license.txt

from __future__ import unicode_literals
import dropbox
import json
import frappe
import os
from frappe import _
from frappe.model.document import Document
import dropbox, json
from frappe.integrations.offsite_backup_utils import get_latest_backup_file, send_email, validate_file_size
from frappe.integrations.utils import make_post_request
from frappe.utils import (cint, get_request_site_address,
get_files_path, get_backups_path, get_url, encode)
from frappe.utils.backups import new_backup
from frappe.utils.background_jobs import enqueue
from six.moves.urllib.parse import urlparse, parse_qs
from frappe.integrations.utils import make_post_request
from rq.timeouts import JobTimeoutException
from frappe.utils import (cint, split_emails, get_request_site_address,
get_files_path, get_backups_path, get_url, encode)
from six import text_type

ignore_list = [".DS_Store"]


class DropboxSettings(Document):
def onload(self):
if not self.app_access_key and frappe.conf.dropbox_access_key:
@@ -48,10 +51,12 @@ def take_backup_to_dropbox(retry_count=0, upload_db_backup=True):
did_not_upload, error_log = [], []
try:
if cint(frappe.db.get_value("Dropbox Settings", None, "enabled")):
validate_file_size()

did_not_upload, error_log = backup_to_dropbox(upload_db_backup)
if did_not_upload: raise Exception

send_email(True, "Dropbox")
send_email(True, "Dropbox", "Dropbox Settings", "send_notifications_to")
except JobTimeoutException:
if retry_count < 2:
args = {
@@ -66,34 +71,8 @@ def take_backup_to_dropbox(retry_count=0, upload_db_backup=True):
else:
file_and_error = [" - ".join(f) for f in zip(did_not_upload, error_log)]
error_message = ("\n".join(file_and_error) + "\n" + frappe.get_traceback())
frappe.errprint(error_message)
send_email(False, "Dropbox", error_message)

def send_email(success, service_name, error_status=None):
if success:
if frappe.db.get_value("Dropbox Settings", None, "send_email_for_successful_backup") == '0':
return

subject = "Backup Upload Successful"
message ="""<h3>Backup Uploaded Successfully</h3><p>Hi there, this is just to inform you
that your backup was successfully uploaded to your %s account. So relax!</p>
""" % service_name

else:
subject = "[Warning] Backup Upload Failed"
message ="""<h3>Backup Upload Failed</h3><p>Oops, your automated backup to %s
failed.</p>
<p>Error message: <br>
<pre><code>%s</code></pre>
</p>
<p>Please contact your system manager for more information.</p>
""" % (service_name, error_status)

if not frappe.db:
frappe.connect()

recipients = split_emails(frappe.db.get_value("Dropbox Settings", None, "send_notifications_to"))
frappe.sendmail(recipients=recipients, subject=subject, message=message)
send_email(False, "Dropbox", "Dropbox Settings", "send_notifications_to", error_message)

def backup_to_dropbox(upload_db_backup=True):
if not frappe.db:
@@ -114,8 +93,12 @@ def backup_to_dropbox(upload_db_backup=True):
dropbox_client = dropbox.Dropbox(dropbox_settings['access_token'])

if upload_db_backup:
backup = new_backup(ignore_files=True)
filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db))
if frappe.flags.create_new_backup:
backup = new_backup(ignore_files=True)
filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db))
else:
filename = get_latest_backup_file()

upload_file_to_dropbox(filename, "/database", dropbox_client)

# delete older databases


+ 10
- 0
frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals

# import frappe
import unittest

class TestDropboxSettings(unittest.TestCase):
pass

+ 15
- 37
frappe/integrations/doctype/google_drive/google_drive.py View File

@@ -19,6 +19,7 @@ from apiclient.http import MediaFileUpload
from frappe.utils import get_backups_path, get_bench_path
from frappe.utils.backups import new_backup
from frappe.integrations.doctype.google_settings.google_settings import get_auth_url
from frappe.integrations.offsite_backup_utils import get_latest_backup_file, send_email, validate_file_size

SCOPES = "https://www.googleapis.com/auth/drive"

@@ -183,13 +184,16 @@ def upload_system_backup_to_google_drive():
check_for_folder_in_google_drive()
account.load_from_db()

progress(1, "Backing up Data.")
backup = new_backup()

fileurl_backup = os.path.basename(backup.backup_path_db)
fileurl_public_files = os.path.basename(backup.backup_path_files)
fileurl_private_files = os.path.basename(backup.backup_path_private_files)
validate_file_size()

if frappe.flags.create_new_backup:
set_progress(1, "Backing up Data.")
backup = new_backup()
fileurl_backup = os.path.basename(backup.backup_path_db)
fileurl_public_files = os.path.basename(backup.backup_path_files)
fileurl_private_files = os.path.basename(backup.backup_path_private_files)
else:
fileurl_backup, fileurl_public_files, fileurl_private_files = get_latest_backup_file(with_files=True)

for fileurl in [fileurl_backup, fileurl_public_files, fileurl_private_files]:
file_metadata = {
@@ -203,15 +207,14 @@ def upload_system_backup_to_google_drive():
frappe.throw(_("Google Drive - Could not locate locate - {0}").format(e))

try:
progress(2, "Uploading backup to Google Drive.")
set_progress(2, "Uploading backup to Google Drive.")
google_drive.files().create(body=file_metadata, media_body=media, fields="id").execute()
except HttpError as e:
send_email(success=False, error=e)
frappe.msgprint(_("Google Drive - Could not upload backup - Error {0}").format(e))
send_email(False, "Google Drive", "Google Drive", "email", error_status=e)

progress(3, "Uploading successful.")
set_progress(3, "Uploading successful.")
frappe.db.set_value("Google Drive", None, "last_backup_on", frappe.utils.now_datetime())
send_email(success=True)
send_email(True, "Google Drive", "Google Drive", "email")
return _("Google Drive Backup Successful.")

def daily_backup():
@@ -226,30 +229,5 @@ def get_absolute_path(filename):
file_path = os.path.join(get_backups_path()[2:], filename)
return "{0}/sites/{1}".format(get_bench_path(), file_path)

def progress(progress, message):
def set_progress(progress, message):
frappe.publish_realtime("upload_to_google_drive", dict(progress=progress, total=3, message=message), user=frappe.session.user)

def send_email(success, error=None):
if success:
if not frappe.db.get_single_value("Google Drive", "send_email_for_successful_backup"):
return

subject = "Backup Upload Successful"
message = """<h3>Backup Uploaded Successfully</h3><p>Hi there, this is just to inform you
that your backup was successfully uploaded to Google Drive.</p>
"""
else:
subject = "[Warning] Backup Upload Failed"
message = """<h3>Backup Upload Failed</h3><p>Oops, your automated backup to Google Drive
failed.</p>
<p>Error message: <br>
<pre><code>{0}</code></pre>
</p>
<p>Please contact your system manager for more information.</p>
""".format(error)

frappe.sendmail(
recipients=frappe.db.get_single_value("Google Drive", "email"),
subject=subject,
message=message
)

+ 478
- 478
frappe/integrations/doctype/oauth_client/oauth_client.json View File

@@ -1,517 +1,517 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2016-08-24 14:07:21.955052",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2016-08-24 14:07:21.955052",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "client_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "App Client ID",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "client_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "App Client ID",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "app_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "App Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "app_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "App Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_1",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_1",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "client_secret",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "App Client Secret",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "client_secret",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "App Client Secret",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "If checked, users will not see the Confirm Access dialog.",
"fieldname": "skip_authorization",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Skip Authorization",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "If checked, users will not see the Confirm Access dialog.",
"fieldname": "skip_authorization",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Skip Authorization",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "",
"fieldname": "sb_1",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "",
"fieldname": "sb_1",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "all openid",
"description": "A list of resources which the Client App will have access to after the user allows it.<br> e.g. project",
"fieldname": "scopes",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Scopes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "all openid",
"description": "A list of resources which the Client App will have access to after the user allows it.<br> e.g. project",
"fieldname": "scopes",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Scopes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "URIs for receiving authorization code once the user allows access, as well as failure responses. Typically a REST endpoint exposed by the Client App.\n<br>e.g. http://hostname//api/method/frappe.www.login.login_via_facebook",
"fieldname": "redirect_uris",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Redirect URIs",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "URIs for receiving authorization code once the user allows access, as well as failure responses. Typically a REST endpoint exposed by the Client App.\n<br>e.g. http://hostname//api/method/frappe.www.login.login_via_facebook",
"fieldname": "redirect_uris",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Redirect URIs",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_redirect_uri",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Redirect URI",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_redirect_uri",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Redirect URI",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "1",
"columns": 0,
"fieldname": "sb_advanced",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": " Advanced Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "1",
"columns": 0,
"fieldname": "sb_advanced",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Advanced Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "grant_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Grant Type",
"length": 0,
"no_copy": 0,
"options": "Authorization Code\nImplicit",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "grant_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Grant Type",
"length": 0,
"no_copy": 0,
"options": "Authorization Code\nImplicit",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Code",
"fieldname": "response_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Response Type",
"length": 0,
"no_copy": 0,
"options": "Code\nToken",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Code",
"fieldname": "response_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Response Type",
"length": 0,
"no_copy": 0,
"options": "Code\nToken",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-10-05 21:07:39.476360",
"modified_by": "Administrator",
"module": "Integrations",
"name": "OAuth Client",
"name_case": "",
"owner": "Administrator",
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2020-04-07 21:07:39.476360",
"modified_by": "Administrator",
"module": "Integrations",
"name": "OAuth Client",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "app_name",
"track_changes": 1,
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "app_name",
"track_changes": 1,
"track_seen": 0
}

+ 92
- 379
frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json View File

@@ -1,397 +1,110 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-09-04 20:57:20.129205",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"creation": "2017-09-04 20:57:20.129205",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"enabled",
"notify_email",
"send_email_for_successful_backup",
"frequency",
"access_key_id",
"secret_access_key",
"region",
"endpoint_url",
"bucket",
"backup_limit"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "enabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enable Automatic Backup",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "0",
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enable Automatic Backup"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "notify_email",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Send Notifications To",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "notify_email",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Send Notifications To",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"description": "Note: By default emails for failed backups are sent.",
"fetch_if_empty": 0,
"fieldname": "send_email_for_successful_backup",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Send Email for Successful Backup",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "1",
"description": "Note: By default emails for failed backups are sent.",
"fieldname": "send_email_for_successful_backup",
"fieldtype": "Check",
"label": "Send Email for Successful Backup"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "frequency",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Backup Frequency",
"length": 0,
"no_copy": 0,
"options": "Daily\nWeekly\nMonthly\nNone",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "frequency",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Backup Frequency",
"options": "Daily\nWeekly\nMonthly\nNone",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "access_key_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Access Key ID",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "access_key_id",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Access Key ID",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "secret_access_key",
"fieldtype": "Password",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Secret Access Key",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "secret_access_key",
"fieldtype": "Password",
"in_list_view": 1,
"label": "Secret Access Key",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "us-east-1",
"description": "See https://docs.aws.amazon.com/de_de/general/latest/gr/rande.html#s3_region for details.",
"fetch_if_empty": 0,
"fieldname": "region",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Region",
"length": 0,
"no_copy": 0,
"options": "us-east-1\nus-east-2\nus-west-1\nus-west-2\nap-south-1\nap-southeast-1\nap-southeast-2\nap-northeast-1\nap-northeast-2\nap-northeast-3\nca-central-1\ncn-north-1\ncn-northwest-1\neu-central-1\neu-west-1\neu-west-2\neu-west-3\neu-north-1\nsa-east-1",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "us-east-1",
"description": "See https://docs.aws.amazon.com/de_de/general/latest/gr/rande.html#s3_region for details.",
"fieldname": "region",
"fieldtype": "Select",
"label": "Region",
"options": "us-east-1\nus-east-2\nus-west-1\nus-west-2\nap-south-1\nap-southeast-1\nap-southeast-2\nap-northeast-1\nap-northeast-2\nap-northeast-3\nca-central-1\ncn-north-1\ncn-northwest-1\neu-central-1\neu-west-1\neu-west-2\neu-west-3\neu-north-1\nsa-east-1"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "endpoint_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Endpoint URL",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "endpoint_url",
"fieldtype": "Data",
"label": "Endpoint URL"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "bucket",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Bucket",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "bucket",
"fieldtype": "Data",
"label": "Bucket",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "backup_limit",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Backup Limit",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fieldname": "backup_limit",
"fieldtype": "Int",
"label": "Backup Limit",
"reqd": 1
}
],
"has_web_view": 0,
"hide_toolbar": 1,
"idx": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2019-04-10 03:56:55.632017",
"modified_by": "Administrator",
"module": "Integrations",
"name": "S3 Backup Settings",
"name_case": "",
"owner": "Administrator",
],
"hide_toolbar": 1,
"issingle": 1,
"modified": "2019-08-22 16:26:04.774571",
"modified_by": "Administrator",
"module": "Integrations",
"name": "S3 Backup Settings",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

+ 19
- 35
frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py View File

@@ -8,12 +8,14 @@ import os.path
import frappe
import boto3
from frappe import _
from frappe.integrations.offsite_backup_utils import get_latest_backup_file, send_email, validate_file_size
from frappe.model.document import Document
from frappe.utils import cint, split_emails
from frappe.utils import cint
from frappe.utils.background_jobs import enqueue
from rq.timeouts import JobTimeoutException
from botocore.exceptions import ClientError


class S3BackupSettings(Document):

def validate(self):
@@ -49,7 +51,7 @@ class S3BackupSettings(Document):

@frappe.whitelist()
def take_backup():
"Enqueue longjob for taking backup to s3"
"""Enqueue longjob for taking backup to s3"""
enqueue("frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_s3", queue='long', timeout=1500)
frappe.msgprint(_("Queued for backup. It may take a few minutes to an hour."))

@@ -65,22 +67,21 @@ def take_backups_weekly():
def take_backups_monthly():
take_backups_if("Monthly")


def take_backups_if(freq):
if cint(frappe.db.get_value("S3 Backup Settings", None, "enabled")):
if frappe.db.get_value("S3 Backup Settings", None, "frequency") == freq:
take_backups_s3()


@frappe.whitelist()
def take_backups_s3(retry_count=0):
try:
validate_file_size()
backup_to_s3()
send_email(True, "S3 Backup Settings")
send_email(True, "Amazon S3", "S3 Backup Settings", "notify_email")
except JobTimeoutException:
if retry_count < 2:
args = {
"retry_count" :retry_count + 1
"retry_count": retry_count + 1
}
enqueue("frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_s3",
queue='long', timeout=1500, **args)
@@ -89,31 +90,10 @@ def take_backups_s3(retry_count=0):
except Exception:
notify()


def notify():
error_message = frappe.get_traceback()
frappe.errprint(error_message)
send_email(False, "S3 Backup Settings", error_message)

def send_email(success, service_name, error_status=None):
if success:
if frappe.db.get_value("S3 Backup Settings", None, "send_email_for_successful_backup") == '0':
return

subject = "Backup Upload Successful"
message = """<h3>Backup Uploaded Successfully! </h3><p>Hi there, this is just to inform you
that your backup was successfully uploaded to your Amazon S3 bucket. So relax!</p> """

else:
subject = "[Warning] Backup Upload Failed"
message = """<h3>Backup Upload Failed! </h3><p>Oops, your automated backup to Amazon S3 failed.
</p> <p>Error message: %s</p> <p>Please contact your system manager
for more information.</p>""" % error_status

if not frappe.db:
frappe.connect()

recipients = split_emails(frappe.db.get_value("S3 Backup Settings", None, "notify_email"))
frappe.sendmail(recipients=recipients, subject=subject, message=message)
send_email(False, 'Amazon S3', "S3 Backup Settings", "notify_email", error_message)


def backup_to_s3():
@@ -130,11 +110,15 @@ def backup_to_s3():
endpoint_url=doc.endpoint_url or 'https://s3.amazonaws.com'
)

backup = new_backup(ignore_files=False, backup_path_db=None,
if frappe.flags.create_new_backup:
backup = new_backup(ignore_files=False, backup_path_db=None,
backup_path_files=None, backup_path_private_files=None, force=True)
db_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db))
files_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_files))
private_files = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_private_files))
db_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db))
files_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_files))
private_files = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_private_files))
else:
db_filename, files_filename, private_files = get_latest_backup_file(with_files=True)

folder = os.path.basename(db_filename)[:15] + '/'
# for adding datetime to folder name

@@ -143,8 +127,8 @@ def backup_to_s3():
upload_file_to_s3(files_filename, folder, conn, bucket)
delete_old_backups(doc.backup_limit, bucket)

def upload_file_to_s3(filename, folder, conn, bucket):

def upload_file_to_s3(filename, folder, conn, bucket):
destpath = os.path.join(folder, os.path.basename(filename))
try:
print("Uploading file:", filename)
@@ -156,7 +140,7 @@ def upload_file_to_s3(filename, folder, conn, bucket):


def delete_old_backups(limit, bucket):
all_backups = list()
all_backups = []
doc = frappe.get_single("S3 Backup Settings")
backup_limit = int(limit)



+ 83
- 0
frappe/integrations/offsite_backup_utils.py View File

@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and contributors
# For license information, please see license.txt

from __future__ import unicode_literals
import frappe
import glob
import os
from frappe.utils import split_emails, get_backups_path


def send_email(success, service_name, doctype, email_field, error_status=None):
recipients = get_recipients(service_name, email_field)
if not recipients:
frappe.log_error("No Email Recipient found for {0}".format(service_name),
"{0}: Failed to send backup status email".format(service_name))
return

if success:
if not frappe.db.get_value(doctype, None, "send_email_for_successful_backup"):
return

subject = "Backup Upload Successful"
message = """
<h3>Backup Uploaded Successfully!</h3>
<p>Hi there, this is just to inform you that your backup was successfully uploaded to your {0} bucket. So relax!</p>""".format(service_name)

else:
subject = "[Warning] Backup Upload Failed"
message = """
<h3>Backup Upload Failed!</h3>
<p>Oops, your automated backup to {0} failed.</p>
<p>Error message: {1}</p>
<p>Please contact your system manager for more information.</p>""".format(service_name, error_status)

frappe.sendmail(recipients=recipients, subject=subject, message=message)


def get_recipients(service_name, email_field):
if not frappe.db:
frappe.connect()

return split_emails(frappe.db.get_value(service_name, None, email_field))


def get_latest_backup_file(with_files=False):

def get_latest(file_ext):
file_list = glob.glob(os.path.join(get_backups_path(), file_ext))
return max(file_list, key=os.path.getctime)

latest_file = get_latest('*.sql.gz')

if with_files:
latest_public_file_bak = get_latest('*-files.tar')
latest_private_file_bak = get_latest('*-private-files.tar')
return latest_file, latest_public_file_bak, latest_private_file_bak

return latest_file


def get_file_size(file_path, unit):
if not unit:
unit = 'MB'

file_size = os.path.getsize(file_path)

memory_size_unit_mapper = {'KB': 1, 'MB': 2, 'GB': 3, 'TB': 4}
i = 0
while i < memory_size_unit_mapper[unit]:
file_size = file_size / 1000.0
i += 1

return file_size


def validate_file_size():
frappe.flags.create_new_backup = True
latest_file = get_latest_backup_file()
file_size = get_file_size(latest_file, unit='GB')

if file_size > 1:
frappe.flags.create_new_backup = False

+ 1
- 1
frappe/integrations/utils.py View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Frappe Technologies and contributors
# Copyright (c) 2019, Frappe Technologies and contributors
# For license information, please see license.txt

from __future__ import unicode_literals


+ 5
- 1
frappe/model/naming.py View File

@@ -110,7 +110,11 @@ def make_autoname(key="", doctype="", doc=""):
if "#" not in key:
key = key + ".#####"
elif "." not in key:
frappe.throw(_("Invalid naming series (. missing)") + (_(" for {0}").format(doctype) if doctype else ""))
error_message = _("Invalid naming series (. missing)")
if doctype:
error_message = _("Invalid naming series (. missing) for {0}").format(doctype)

frappe.throw(error_message)

parts = key.split('.')
n = parse_naming_series(parts, doctype, doc)


+ 1
- 0
frappe/patches.txt View File

@@ -271,3 +271,4 @@ execute:frappe.delete_doc_if_exists('DocType', 'GSuite Templates')
execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Account')
execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings')
frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats
execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders()

+ 3
- 2
frappe/patches/v8_0/update_gender_and_salutation.py View File

@@ -2,7 +2,7 @@

from __future__ import unicode_literals
import frappe
from frappe.desk.page.setup_wizard.install_fixtures import update_genders_and_salutations
from frappe.desk.page.setup_wizard.install_fixtures import update_genders, update_salutations

def execute():
frappe.db.set_value("DocType", "Contact", "module", "Contacts")
@@ -11,4 +11,5 @@ def execute():
frappe.reload_doc('contacts', 'doctype', 'gender')
frappe.reload_doc('contacts', 'doctype', 'salutation')

update_genders_and_salutations()
update_genders()
update_salutations()

+ 1
- 0
frappe/public/js/frappe/form/grid_row.js View File

@@ -550,6 +550,7 @@ export default class GridRow {
hide_form() {
frappe.dom.unfreeze();
this.row.toggle(true);
frappe.utils.scroll_to(this.row, true, 15);
this.refresh();
if(cur_frm) cur_frm.cur_grid = null;
this.wrapper.removeClass("grid-row-open");


+ 1
- 0
frappe/public/js/frappe/form/grid_row_form.js View File

@@ -9,6 +9,7 @@ export default class GridRowForm {
var me = this;
this.make_form();
this.form_area.empty();
frappe.utils.scroll_to(0, false, 0, this.wrapper.find('.grid-form-body'));

this.layout = new frappe.ui.form.Layout({
fields: this.row.docfields,


+ 13
- 15
frappe/public/js/frappe/form/workflow.js View File

@@ -29,20 +29,18 @@ frappe.ui.form.States = Class.extend({
});

frappe.workflow.get_transitions(me.frm.doc).then((transitions) => {
var next_html = $.map(transitions,
function(d) {
return d.action.bold() + __(" by Role ") + d.allowed;
}).join(", ") || __("None: End of Workflow").bold();

$(d.body).html("<p>"+__("Current status")+": " + state.bold() + "</p>"
+ "<p>"+__("Document is only editable by users of role")+": "
+ frappe.workflow.get_document_state(me.frm.doctype,
state).allow_edit.bold() + "</p>"
+ "<p>"+__("Next actions")+": "+ next_html +"</p>"
+ (me.frm.doc.__islocal ? ("<div class='alert alert-info'>"
+__("Workflow will start after saving.")+"</div>") : "")
+ "<p class='help'>"+__("Note: Other permission rules may also apply")+"</p>"
).css({padding: '15px'});
const next_actions = $.map(transitions, d => `${d.action.bold()} ${__("by Role")} ${d.allowed}`)
.join(", ") || __("None: End of Workflow").bold();

const document_editable_by = frappe.workflow.get_document_state(me.frm.doctype, state).allow_edit.bold();

$(d.body).html(`
<p>${__("Current status")}: ${state.bold()}</p>
<p>${__("Document is only editable by users with role")}: ${document_editable_by}</p>
<p>${__("Next actions")}: ${next_actions}</p>
<p>${__("{0}: Other permission rules may also apply", [__('Note').bold()])}</p>
`).css({padding: '15px'});

d.show();
});
}, true);
@@ -115,7 +113,7 @@ frappe.ui.form.States = Class.extend({
} else {
this.setup_btn(added);
}
});

},


+ 6
- 3
frappe/public/js/frappe/list/list_sidebar_group_by.js View File

@@ -53,15 +53,18 @@ frappe.views.ListGroupBy = class ListGroupBy {

render_group_by_items() {
let get_item_html = (fieldname) => {
let label;
let fieldtype;
let label, fieldtype;
if (fieldname === 'assigned_to') {
label = __('Assigned To');
} else if (fieldname === 'owner') {
label = __('Created By');
} else {
label = frappe.meta.get_label(this.doctype, fieldname);
fieldtype = frappe.meta.get_docfield(this.doctype, fieldname).fieldtype;
let docfield = frappe.meta.get_docfield(this.doctype, fieldname);
if (!docfield) {
return;
}
fieldtype = docfield.fieldtype;
}

return `<li class="group-by-field list-link">


+ 4
- 1
frappe/public/js/frappe/ui/field_group.js View File

@@ -125,11 +125,14 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({
return this.set_value(key, val);
},
set_values: function(dict) {
let promises = [];
for(var key in dict) {
if(this.fields_dict[key]) {
this.set_value(key, dict[key]);
promises.push(this.set_value(key, dict[key]));
}
}

return Promise.all(promises);
},
clear: function() {
for(var key in this.fields_dict) {


+ 1
- 0
frappe/public/js/frappe/ui/toolbar/toolbar.js View File

@@ -203,6 +203,7 @@ $.extend(frappe.ui.toolbar, {
fullwidth = !fullwidth;
localStorage.container_fullwidth = fullwidth;
frappe.ui.toolbar.set_fullwidth_if_enabled();
$(document.body).trigger('toggleFullWidth');
},
set_fullwidth_if_enabled() {
let fullwidth = JSON.parse(localStorage.container_fullwidth || 'false');


+ 8
- 6
frappe/public/js/frappe/utils/utils.js View File

@@ -122,9 +122,11 @@ Object.assign(frappe.utils, {
</a></p>');
return content.html();
},
scroll_to: function(element, animate, additional_offset) {
scroll_to: function(element, animate, additional_offset, element_to_be_scrolled) {
element_to_be_scrolled = element_to_be_scrolled || $("html, body");

var y = 0;
if(element && typeof element==='number') {
if (element && typeof element==="number") {
y = element;
} else if(element) {
var header_offset = $(".navbar").height() + $(".page-head").height();
@@ -136,14 +138,14 @@ Object.assign(frappe.utils, {
}

// already there
if(y==$('html, body').scrollTop()) {
if (y == element_to_be_scrolled.scrollTop()) {
return;
}

if (animate!==false) {
$("html, body").animate({ scrollTop: y });
if (animate !== false) {
element_to_be_scrolled.animate({ scrollTop: y });
} else {
$(window).scrollTop(y);
element_to_be_scrolled.scrollTop(y);
}

},


+ 129
- 71
frappe/public/js/frappe/views/desktop/desktop.js View File

@@ -1,7 +1,6 @@
export default class Desktop {
constructor({ wrapper }) {
this.wrapper = wrapper;
window.desk = this;
this.pages = {};
this.sidebar_items = {};
this.sidebar_categories = [
@@ -45,12 +44,12 @@ export default class Desktop {
this.desktop_settings = response.message;
} else {
frappe.throw({
title: "Couldn't Load Desk",
title: __("Couldn't Load Desk"),
message:
"Something went wrong while loading Desk. <b>Please relaod the page</b>. If the problem persists, contact the Administrator",
__("Something went wrong while loading Desk. <b>Please relaod the page</b>. If the problem persists, contact the Administrator"),
indicator: "red",
primary_action: {
label: "Reload",
label: __("Reload"),
action: () => location.reload()
}
});
@@ -64,7 +63,7 @@ export default class Desktop {
item.name}" class="sidebar-item ${
item.selected ? "selected" : ""
}">
<span>${item.name}</span>
<span>${item.label || item.name}</span>
</div>`);
};

@@ -79,8 +78,10 @@ export default class Desktop {
};

const make_category_title = name => {
// DO NOT REMOVE: Comment to load translation
// __("Modules") __("Domains") __("Places") __("Administration")
let $title = $(
`<div class="sidebar-group-title h6 uppercase">${name}</div>`
`<div class="sidebar-group-title h6 uppercase">${__(name)}</div>`
);
$title.appendTo(this.sidebar);
};
@@ -106,8 +107,6 @@ export default class Desktop {
}
this.current_page = page;
localStorage.current_desk_page = page;
frappe.set_route("workspace", page);

this.pages[page] ? this.pages[page].show() : this.make_page(page);
}

@@ -131,20 +130,20 @@ export default class Desktop {
this.pages[page] = $page;
return $page;
}

setup_events() {}
}

class DesktopPage {
constructor({ container, page_name }) {
frappe.desk_page = this;
this.container = container;
this.page_name = page_name;
this.sections = {};
this.allow_customization = false;
this.make();
this.reload();
}

show() {
frappe.desk_page = this;
this.page.show();
}

@@ -152,8 +151,34 @@ class DesktopPage {
this.page.hide();
}

reload() {
this.in_customize_mode = false;
this.page && this.page.remove();
this.make();
this.setup_events();
}

make_customization_link() {
this.customize_link = $(`<div class="small customize-options" style="cursor: pointer;">${__('Customize Workspace')}</div>`);
this.customize_link.appendTo(this.page);
this.customize_link.on('click', () => {
this.customize();
});

this.save_or_discard_link = $(`<div class="small customize-options small-bounce">
<span class="save-customization">${__('Save')}</span> / <span class="discard-customization">${__('Discard')}</span>
</div>`).hide();

this.save_or_discard_link.appendTo(this.page);
this.save_or_discard_link.find(".save-customization").on("click", () => this.save_customization());
this.save_or_discard_link.find(".discard-customization").on("click", () => this.reload());
this.page.addClass('allow-customization');
}

make() {
this.make_page();
this.page = $(`<div class="desk-page" data-page-name=${this.page_name}></div>`);
this.page.appendTo(this.container);

this.get_data().then(res => {
this.data = res.message;
// this.make_onboarding();
@@ -163,28 +188,32 @@ class DesktopPage {
return;
}

this.allow_customization = this.data.allow_customization || false;
this.refresh();
});
}

let create_shortcuts_and_cards = () => {
this.data.shortcuts.items.length && this.make_shortcuts();
this.data.cards.items.length && this.make_cards();
};
refresh() {
this.page.empty();
this.allow_customization = this.data.allow_customization || false;
this.allow_customization && this.make_customization_link();

if (!this.sections["onboarding"] && this.data.charts.items.length) {
this.make_charts().then(() => {
create_shortcuts_and_cards();
});
} else {
create_shortcuts_and_cards();
let create_shortcuts_and_cards = () => {
this.data.shortcuts.items.length && this.make_shortcuts();
this.data.cards.items.length && this.make_cards();

if (this.allow_customization) {
// Move the widget group up to align with labels if customization is allowed
$('.desk-page .widget-group:visible:first').css('margin-top', '-25px');
}
});
}
};

make_page() {
this.page = $(
`<div class="desk-page" data-page-name=${this.page_name}></div>`
);
this.page.appendTo(this.container);
if (!this.sections["onboarding"] && this.data.charts.items.length) {
this.make_charts().then(() => {
create_shortcuts_and_cards();
});
} else {
create_shortcuts_and_cards();
}
}

get_data() {
@@ -193,40 +222,50 @@ class DesktopPage {
});
}

make_onboarding() {
this.sections["onboarding"] = new frappe.widget.WidgetGroup({
title: `Getting Started`,
container: this.page,
type: "onboarding",
columns: 1,
widgets: [
{
label: "Unlock Great Customer Experience",
subtitle: "Just a few steps, and you’re good to go.",
steps: [
{
label: "Configure Lead Sources",
completed: true
},
{
label: "Add Your Leads",
completed: false
},
{
label: "Create Your First Opportunity",
completed: false
},
{
label: "Onboard your Sales Team",
completed: false
},
{
label: "Assign Territories",
completed: false
}
]
}
]
setup_events() {
$(document.body).on('toggleFullWidth', () => this.refresh());
}

customize() {
if (this.in_customize_mode) {
return;
}

// It may be possible the chart area is hidden since it has no widgets
// So the margin-top: -25px would be applied to the shortcut group
// We need to remove this as the chart group will be visible during customization
$('.desk-page .widget-group:visible:first').css('margin-top', '0px');

this.customize_link.hide();
this.save_or_discard_link.show();

Object.keys(this.sections).forEach(section => {
this.sections[section].customize();
});
this.in_customize_mode = true;

// Move the widget group up to align with labels if customization is allowed
$('.desk-page .widget-group:visible:first').css('margin-top', '-25px');
}

save_customization() {
const config = {};

if (this.sections.charts) config.charts = this.sections.charts.get_widget_config();
if (this.sections.shortcuts) config.shortcuts = this.sections.shortcuts.get_widget_config();
if (this.sections.cards) config.cards = this.sections.cards.get_widget_config();

frappe.call('frappe.desk.desktop.save_customization', {
page: this.page_name,
config: config
}).then(res => {
if (res.message) {
frappe.msgprint({ message: __("Customizations Saved Successfully"), title: __("Success")});
this.reload();
} else {
frappe.throw({message: __("Something went wrong while saving customizations"), title: __("Failed")});
this.reload();
}
});
}

@@ -240,11 +279,18 @@ class DesktopPage {
}

this.sections["charts"] = new frappe.widget.WidgetGroup({
title: this.data.charts.label || `${this.page_name} Dashboard`,
title: this.data.charts.label || __('{} Dashboard', [__(this.page_name)]),
container: this.page,
type: "chart",
columns: 1,
allow_sorting: false,
options: {
allow_sorting: this.allow_customization && !frappe.is_mobile(),
allow_create: this.allow_customization,
allow_delete: this.allow_customization,
allow_hiding: false,
allow_edit: true,
max_widget_count: 2,
},
widgets: this.data.charts.items
});
});
@@ -252,22 +298,34 @@ class DesktopPage {

make_shortcuts() {
this.sections["shortcuts"] = new frappe.widget.WidgetGroup({
title: this.data.shortcuts.label || `Your Shortcuts`,
title: this.data.shortcuts.label || __(`Your Shortcuts`),
container: this.page,
type: "bookmark",
type: "shortcut",
columns: 3,
allow_sorting: this.allow_customization && frappe.is_mobile(),
options: {
allow_sorting: this.allow_customization && !frappe.is_mobile(),
allow_create: this.allow_customization,
allow_delete: this.allow_customization,
allow_hiding: false,
allow_edit: true,
},
widgets: this.data.shortcuts.items
});
}

make_cards() {
let cards = new frappe.widget.WidgetGroup({
title: this.data.cards.label || `Reports & Masters`,
title: this.data.cards.label || __(`Reports & Masters`),
container: this.page,
type: "links",
columns: 3,
allow_sorting: this.allow_customization && frappe.is_mobile(),
options: {
allow_sorting: this.allow_customization && !frappe.is_mobile(),
allow_create: false,
allow_delete: false,
allow_hiding: this.allow_customization,
allow_edit: false,
},
widgets: this.data.cards.items
});



+ 120
- 12
frappe/public/js/frappe/widgets/base_widget.js View File

@@ -1,3 +1,5 @@
import get_dialog_constructor from './widget_dialog.js';

export default class Widget {
constructor(opts) {
Object.assign(this, opts);
@@ -8,22 +10,71 @@ export default class Widget {
this.set_title();
this.set_actions();
this.set_body();
this.setup_events();
}

get_config() {
return {
name: this.name,
label: this.label
};
}

customize() {
customize(options) {
this.in_customize_mode = true;
this.action_area.empty();

options.allow_delete &&
this.add_custom_button(
'<i class="fa fa-trash" aria-hidden="true"></i>',
() => this.delete()
);

options.allow_sorting &&
this.add_custom_button(
'<i class="fa fa-arrows" aria-hidden="true"></i>',
null,
"drag-handle"
);

if (options.allow_hiding) {
if (this.hidden) {
this.widget.removeClass("hidden");
this.body.css("opacity", 0.5);
this.title_field.css("opacity", 0.5);
this.footer.css("opacity", 0.5);
}
const classname = this.hidden ? 'fa fa-eye' : 'fa fa-eye-slash';
this.add_custom_button(
`<i class="${classname}" aria-hidden="true"></i>`,
() => this.hide_or_show(),
"show-or-hide-button"
);

this.show_or_hide_button = this.action_area.find(
".show-or-hide-button"
);
}

options.allow_edit &&
this.add_custom_button(
'<i class="fa fa-pencil" aria-hidden="true"></i>',
() => this.edit()
);

}

make() {
this.make_widget();
this.widget.appendTo(this.container);
this.setup_events();
}

make_widget() {
this.widget = $(`<div class="widget">
this.widget = $(`<div class="widget ${
this.hidden ? "hidden" : ""
}" data-widget-name=${this.name ? this.name : ''}>
<div class="widget-head">
<div class="widget-title"></div>
<div class="widget-title ellipsis"></div>
<div class="widget-control"></div>
</div>
<div class="widget-body">
@@ -37,24 +88,81 @@ export default class Widget {
this.action_area = this.widget.find(".widget-control");
this.head = this.widget.find(".widget-head");
this.footer = this.widget.find(".widget-footer");
this.set_title();
this.set_actions();
this.set_body();
this.refresh();
}

set_title() {
this.title_field[0].innerHTML = this.label || this.name;
this.title_field[0].innerHTML = this.label;
}

set_actions() {
add_custom_button(html, action, class_name = "") {
let button = $(
`<button class="btn btn-default btn-xs ${class_name}">${html}</button>`
);
button.click(event => {
event.stopPropagation();
action && action();
});
button.appendTo(this.action_area);
}

delete() {
this.widget.addClass("zoomOutDelete");
// wait for animation
setTimeout(() => {
this.widget.remove();
this.options.on_delete && this.options.on_delete(this.name);
}, 300);
}

edit() {
const dialog_class = get_dialog_constructor(this.widget_type);

this.edit_dialog = new dialog_class({
label: this.label,
type: this.widget_type,
values: this.get_config(),
primary_action: (data) => {
Object.assign(this, data);
data.name = this.name;

this.refresh();
},
primary_action_label: __("Save")
});

this.edit_dialog.make();
}

hide_or_show() {
if (!this.hidden) {
this.body.css("opacity", 0.5);
this.title_field.css("opacity", 0.5);
this.footer.css("opacity", 0.5);
this.hidden = true;
} else {
this.body.css("opacity", 1);
this.title_field.css("opacity", 1);
this.footer.css("opacity", 1);
this.hidden = false;
}
this.show_or_hide_button.empty();

const classname = this.hidden ? 'fa fa-eye' : 'fa fa-eye-slash';
$(`<i class="${classname}" aria-hidden="true"></i>`).appendTo(
this.show_or_hide_button
);
}

setup_events() {
//
}

set_body() {
set_actions() {
//
}

setup_events() {
set_body() {
//
}
}
}

+ 26
- 24
frappe/public/js/frappe/widgets/chart_widget.js View File

@@ -9,12 +9,19 @@ export default class ChartWidget extends Widget {
this.height = 240;
}

refresh() {
this.make_chart();
get_config() {
return {
name: this.name,
chart_name: this.chart_name,
label: this.label,
};
}

customize() {
this.setup_customize_actions();
refresh() {
delete this.dashboard_chart;
this.set_title();
this.set_body();
this.make_chart();
}

set_body() {
@@ -67,28 +74,22 @@ export default class ChartWidget extends Widget {
}
this.setup_container();
this.prepare_chart_object();
this.action_area.empty();
this.prepare_chart_actions();
this.setup_filter_button();

if (
this.chart_doc.timeseries &&
this.chart_doc.chart_type !== "Custom"
) {
this.render_time_series_filters();
if (!this.in_customize_mode) {
this.action_area.empty();
this.prepare_chart_actions();
this.setup_filter_button();

if (
this.chart_doc.timeseries &&
this.chart_doc.chart_type !== "Custom"
) {
this.render_time_series_filters();
}
}

this.fetch_and_update_chart();
});
}

setup_customize_actions() {
this.action_area.empty();
const buttons = $(`<button type="button" class="btn btn-xs btn-secondary btn-default selected">Resize</button>
<button class="btn btn-secondary btn-light btn-danger btn-xs"><i class="fa fa-trash" aria-hidden="true"></i></button>`);
buttons.appendTo(this.action_area);
}

render_time_series_filters() {
let filters = [
{
@@ -428,9 +429,7 @@ export default class ChartWidget extends Widget {
}

fetch(filters, refresh = false, args) {
let method = this.settings
? this.settings.method
: "frappe.desk.doctype.dashboard_chart.dashboard_chart.get";
let method = this.settings.method;

if (this.chart_doc.chart_type == "Report") {
args = {
@@ -558,6 +557,9 @@ export default class ChartWidget extends Widget {
};
return Promise.resolve();
} else {
this.settings = {
method: "frappe.desk.doctype.dashboard_chart.dashboard_chart.get"
};
return Promise.resolve();
}
});


+ 15
- 9
frappe/public/js/frappe/widgets/links_widget.js View File

@@ -6,8 +6,13 @@ export default class LinksWidget extends Widget {
super(opts);
}

refresh() {
//
get_config() {
return {
name: this.name,
links: JSON.stringify(this.links),
label: this.label,
hidden: this.hidden,
};
}

set_body() {
@@ -75,21 +80,22 @@ export default class LinksWidget extends Widget {
const popover = link.find(".module-link-popover");

link_label.mouseover(() => {
if (this.in_customize_mode) return;
popover.show();
});
link_label.mouseout(() => popover.hide());
} else {
if (link_label.hasClass("help-video-link")) {
link_label.click(event => {
link_label.click(event => {
if (this.in_customize_mode) return;

if (link_label.hasClass("help-video-link")) {
let yt_id = event.target.dataset.youtubeid;
frappe.help.show_video(yt_id);
});
} else {
link_label.click(event => {
} else {
let route = event.target.dataset.route;
frappe.set_route(route);
});
}
}
});
}
});
}

+ 52
- 0
frappe/public/js/frappe/widgets/new_widget.js View File

@@ -0,0 +1,52 @@
import get_dialog_constructor from "./widget_dialog.js";

export default class NewWidget {
constructor(opts) {
Object.assign(this, opts);
this.make();
}

customize() {
return;
}

make() {
this.make_widget();
this.widget.appendTo(this.container);
this.setup_events();
}

get_title() {
// DO NOT REMOVE: Comment to load translation
// __("New Chart") __("New Shortcut")
return __(`New ${frappe.utils.to_title_case(this.type)}`);
}

make_widget() {
this.widget = $(`<div class="widget new-widget">
+ ${this.get_title()}
</div>`);
this.body = this.widget;
}

setup_events() {
this.widget.on("click", () => this.open_dialog());
}

open_dialog() {
const dialog_class = get_dialog_constructor(this.type);

this.dialog = new dialog_class({
label: this.label,
type: this.type,
values: false,
primary_action: this.on_create,
});

this.dialog.make();
}

delete() {
this.widget.remove();
}
}

+ 0
- 1
frappe/public/js/frappe/widgets/onboarding_widget.js View File

@@ -3,7 +3,6 @@ import Widget from "./base_widget.js";
export default class OnboardingWidget extends Widget {
constructor(opts) {
super(opts);
window.onb = this;
}

refresh() { }


+ 46
- 29
frappe/public/js/frappe/widgets/shortcut_widget.js View File

@@ -1,48 +1,62 @@
import Widget from "./base_widget.js";
import { generate_route } from "./utils";
// import { get_luminosity, shadeColor } from "./utils";

String.prototype.format = function () {
var i = 0, args = arguments;
return this.replace(/{}/g, function () {
return typeof args[i] != 'undefined' ? args[i++] : '';
});
};

export default class ShortcutWidget extends Widget {
constructor(opts) {
super(opts);
}

refresh() {
//
get_config() {
return {
name: this.name,
icon: this.icon,
label: this.label,
format: this.format,
link_to: this.link_to,
color: this.color,
restrict_to_domain: this.restrict_to_domain,
stats_filter: this.stats_filter,
type: this.type,
};
}

setup_events() {
this.widget.click(() => {
let route = generate_route(this)
frappe.set_route(route)
})
if (this.in_customize_mode) return;

let route = generate_route({
route: this.route,
name: this.link_to,
type: this.type,
is_query_report: this.is_query_report,
doctype: this.ref_doctype
});

frappe.set_route(route);
});
}

set_actions() {
this.widget.addClass('shortcut-widget-box');
const get_filter = new Function(`return ${this.stats_filter}`)
if (this.in_customize_mode) return;

this.widget.addClass("shortcut-widget-box");
const get_filter = new Function(`return ${this.stats_filter}`);
if (this.type == "DocType" && this.stats_filter) {
frappe.db.count(this.link_to, {
filters: get_filter()
}).then(count => this.set_count(count))
frappe.db
.count(this.link_to, {
filters: get_filter(),
})
.then((count) => this.set_count(count));
}
}

set_title() {
if (this.icon) {
this.title_field[0].innerHTML = `<div>
<i class="${this.icon}" style="color: rgb(141, 153, 166); font-size: 18px; margin-right: 6px;"></i>
<i class="${this.icon}" style=""></i>
${this.label || this.name}
</div>`
}
else {
</div>`;
} else {
super.set_title();
}
}
@@ -50,19 +64,22 @@ export default class ShortcutWidget extends Widget {
set_count(count) {
const get_label = () => {
if (this.format) {
return this.format.format(count);
return this.format.replace(/{}/g, count);
}
return count
}
return count;
};

this.action_area.empty();
const label = get_label();
const buttons = $(`<div class="small pill">${label}</div>`);
if(this.color) {
buttons.css('background-color', this.color);
buttons.css('color', frappe.ui.color.get_contrast_color(this.color))
if (this.color) {
buttons.css("background-color", this.color);
buttons.css(
"color",
frappe.ui.color.get_contrast_color(this.color)
);
}

buttons.appendTo(this.action_area);
}
}
}

+ 266
- 0
frappe/public/js/frappe/widgets/widget_dialog.js View File

@@ -0,0 +1,266 @@
class WidgetDialog {
constructor(opts) {
Object.assign(this, opts);
this.editing = Boolean(this.values && Object.keys(this.values).length);
}

make() {
this.make_dialog();
this.setup_dialog_events();
this.dialog.show();

this.editing && this.set_default_values();
}

make_dialog() {
this.dialog = new frappe.ui.Dialog({
title: this.get_title(),
fields: this.get_fields(),
primary_action: (data) => {
data = this.process_data(data);

if (!this.editing) {
data.name = `${this.type}-${this.label}-${frappe.utils.get_random(20)}`;
}

this.dialog.hide();
this.primary_action(data);
},
primary_action_label: this.primary_action_label || __("Add"),
});
}

get_title() {
// DO NOT REMOVE: Comment to load translation
// __("New Chart") __("New Shortcut") __("Edit Chart") __("Edit Shortcut")

let action = this.editing ? "Edit" : "Add";
return __(`${action} ${frappe.utils.to_title_case(this.type)}`);
}

get_fields() {
//
}

set_default_values() {
return this.dialog.set_values(this.values);
}

process_data(data) {
return data;
}

setup_dialog_events() {
//
}

hide_field(fieldname) {
this.dialog.set_df_property(fieldname, "hidden", true);
}

show_field(fieldname) {
this.dialog.set_df_property(fieldname, "hidden", false);
}
}

class ChartDialog extends WidgetDialog {
constructor(opts) {
super(opts);
}

get_fields() {
return [
{
fieldtype: "Link",
fieldname: "chart_name",
label: "Chart Name",
options: "Dashboard Chart",
reqd: 1,
},
{
fieldtype: "Data",
fieldname: "label",
label: "Label",
},
];
}

process_data(data) {
data.label = data.label ? data.label : data.chart_name;
return data;
}
}

class ShortcutDialog extends WidgetDialog {
constructor(opts) {
super(opts);
}

hide_filters() {
this.hide_field("count_section_break");
this.hide_field("filters_section_break");
}

show_filters() {
this.show_field("count_section_break");
this.show_field("filters_section_break");
}

get_fields() {
return [
{
fieldtype: "Select",
fieldname: "type",
label: "Type",
reqd: 1,
options: "DocType\nReport\nPage",
onchange: () => {
if (this.dialog.get_value("type") == "DocType") {
this.dialog.fields_dict.link_to.get_query = () => {
return { filters: { istable: false } };
};
} else {
this.dialog.fields_dict.link_to.get_query = null;
}
},
},
{
fieldtype: "Data",
fieldname: "label",
label: "Label",
},
{
fieldtype: "Column Break",
fieldname: "column_break_4",
},
{
fieldtype: "Dynamic Link",
fieldname: "link_to",
label: "Link To",
reqd: 1,
options: "type",
onchange: () => {
if (this.dialog.get_value("type") == "DocType") {
let doctype = this.dialog.get_value("link_to");

doctype &&
frappe.db
.get_value("DocType", doctype, "issingle")
.then((res) => {
if (res.message && res.message.issingle) {
this.hide_filters();
} else {
this.setup_filter(doctype);
this.show_filters();
}
});
} else {
this.hide_filters();
}
},
},
{
fieldtype: "Section Break",
fieldname: "filters_section_break",
label: "Count Filter",
hidden: 1,
},
{
fieldtype: "HTML",
fieldname: "filter_area_loading",
},
{
fieldtype: "HTML",
fieldname: "filter_area",
hidden: 1,
},
{
fieldtype: "Section Break",
fieldname: "count_section_break",
label: "Count Customizations",
hidden: 1,
},
{
fieldtype: "Color",
fieldname: "color",
label: "Color",
},
{
fieldtype: "Column Break",
fieldname: "column_break_3",
},
{
fieldtype: "Data",
fieldname: "format",
label: "Format",
description: "For Example: {} Open",
},
];
}

set_default_values() {
super.set_default_values().then(() => {
this.dialog.fields_dict.link_to.df.onchange();
});
}

process_data(data) {
let stats_filter = {};

if (this.dialog.get_value("type") == "DocType" && this.filter_group) {
let filters = this.filter_group.get_filters();
filters.forEach((arr) => {
stats_filter[arr[1]] = [arr[2], arr[3]];
});

data.stats_filter = JSON.stringify(stats_filter);
}

data.label = data.label
? data.label
: frappe.model.unscrub(data.link_to);

return data;
}

setup_filter(doctype) {
if (this.filter_group) {
this.filter_group.wrapper.empty();
delete this.filter_group;
}

let $loading = this.dialog.get_field("filter_area_loading").$wrapper;
$(`<span class="text-muted">Loading Filters...</span>`).appendTo($loading);

this.filters = [];

if (this.values && this.values.stats_filter) {
const filters_json = JSON.parse(this.values.stats_filter);
this.filters = Object.keys(filters_json).map((filter) => {
let val = filters_json[filter];
return [this.values.link_to, filter, val[0], val[1], false];
});
}

this.filter_group = new frappe.ui.FilterGroup({
parent: this.dialog.get_field("filter_area").$wrapper,
doctype: doctype,
on_change: () => {},
});

frappe.model.with_doctype(doctype, () => {
this.filter_group.add_filters_to_filter_group(this.filters);
this.hide_field("filter_area_loading");
this.show_field("filter_area");
});
}
}

export default function get_dialog_constructor(type) {
const widget_map = {
chart: ChartDialog,
shortcut: ShortcutDialog,
};

return widget_map[type] || WidgetDialog;
}

+ 106
- 36
frappe/public/js/frappe/widgets/widget_group.js View File

@@ -3,46 +3,31 @@ import BaseWidget from "../widgets/base_widget";
import ShortcutWidget from "../widgets/shortcut_widget";
import LinksWidget from "../widgets/links_widget";
import OnboardingWidget from "../widgets/onboarding_widget";
import NewWidget from "../widgets/new_widget";

frappe.provide('frappe.widget')
frappe.provide("frappe.widget");

const widget_factory = {
chart: ChartWidget,
base: BaseWidget,
bookmark: ShortcutWidget,
shortcut: ShortcutWidget,
links: LinksWidget,
onboarding: OnboardingWidget
onboarding: OnboardingWidget,
};

export default class WidgetGroup {
constructor(opts) {
Object.assign(this, opts);
// opts = {
// title: "CRM Dashboard",
// container: $(''),
// widgets: [
// {type: "dashboard", width: "Full", options: {}}.
// {type: "dashboard", width: "Full", options: {}}
// ],
// allow_delete: true,
// allow_create: true,
// allow_rearrange: true,
// hide_edit_option: false,
// collapsible: false
// }
window.wid_area = this;
this.widgets_list = [];
this.widgets_dict = {};
this.widget_order = [];
this.make();
}

make() {
this.make_container();
this.refresh();
}

refresh() {
this.title && this.set_title(this.title);
this.title && this.set_title();
this.widgets && this.make_widgets();
this.allow_sorting && this.setup_sortable();
}

make_container() {
@@ -58,22 +43,94 @@ export default class WidgetGroup {
this.title_area = widget_area.find(".widget-group-title");
this.control_area = widget_area.find(".widget-group-control");
this.body = widget_area.find(".widget-group-body");
!this.widgets.length && this.widget_area.hide();
widget_area.appendTo(this.container);
}

set_title(title) {
set_title() {
this.title_area[0].innerText = this.title;
}

make_widgets() {
this.body.empty()
this.body.empty();
this.widgets.forEach((widget) => {
this.add_widget(widget);
});
}

add_widget(widget) {
const widget_class = widget_factory[this.type];

this.widgets.forEach(widget => {
new widget_class({
...widget,
container: this.body
})
let widget_object = new widget_class({
...widget,
widget_type: this.type,
container: this.body,
options: {
...this.options,
on_delete: (name) => this.on_delete(name),
},
});

this.widgets_list.push(widget_object);
this.widgets_dict[widget.name] = widget_object;

return widget_object;
}

customize() {
this.widget_area.show();
this.widgets_list.forEach((wid) => {
wid.customize(this.options);
});

this.options.allow_create && this.setup_new_widget();
this.options.allow_sorting && this.setup_sortable();
}

setup_new_widget() {
const max = this.options
? this.options.max_widget_count || Number.POSITIVE_INFINITY
: Number.POSITIVE_INFINITY;

if (this.widgets_list.length < max) {
this.new_widget = new NewWidget({
container: this.body,
type: this.type,
on_create: (config) => {
// Remove new widget
this.new_widget.delete();
delete this.new_widget;

config.in_customize_mode = 1;

// Add new widget and customize it
let wid = this.add_widget(config);
wid.customize(this.options);

// Put back the new widget if required
if (this.widgets_list.length < max) {
this.setup_new_widget();
}
},
});
}
}

on_delete(name) {
this.widgets_list = this.widgets_list.filter((wid) => name != wid.name);
delete this.widgets_dict[name];
this.update_widget_order();

if (!this.new_widget) this.setup_new_widget();
}

update_widget_order() {
this.widget_order = [];
this.body.children().each((index, element) => {
let name = element.dataset.widgetName;
if (name) {
this.widget_order.push(name);
}
});
}

@@ -81,13 +138,26 @@ export default class WidgetGroup {
const container = this.body[0];
this.sortable = new Sortable(container, {
animation: 150,
onEnd: () => {
console.log("Sorting")
},
// onChoose: (evt) => this.sortable_config.on_choose(evt, container),
// onStart: (evt) => this.sortable_config.on_start(evt, container)
handle: ".drag-handle",
onEnd: () => this.update_widget_order(),
});
}

get_widget_config() {
this.update_widget_order();
let prepared_dict = {};

this.widgets_list.forEach((wid) => {
let config = wid.get_config();
let name = config.docname ? config.docname : config.name;
prepared_dict[name] = config;
});

return {
order: this.widget_order,
widgets: prepared_dict,
};
}
}

frappe.widget.WidgetGroup = WidgetGroup;
frappe.widget.WidgetGroup = WidgetGroup;

+ 191
- 11
frappe/public/less/desktop.less View File

@@ -76,6 +76,24 @@
position: relative;
min-height: 1px;
padding-right: 15px;

.desk-page.allow-customization {
.customize-options {
text-align: right;
margin-top: 7px;
color: @text-muted;
z-index: 99;

.save-customization {
cursor: pointer;
color: @text-color;
}

.discard-customization {
cursor: pointer;
}
}
}
}

@media (max-width: 768px) {
@@ -90,6 +108,9 @@

.widget-group {
margin-bottom: 25px;
// -webkit-animation-name: slideInUp;
// animation-name: slideInUp;
// animation-duration: 0.4s;

.widget-group-head {
display: flex;
@@ -201,6 +222,17 @@
margin-left: 5px;
}

.drag-handle {
cursor: all-scroll;
cursor: -webkit-grabbing;

&:active {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
}
}

.dashboard-date-field {
.clearfix,
.help-box {
@@ -220,6 +252,16 @@
border-color: @disabled-background
}

&.new-widget {
min-height: 65px;
background-color: @disabled-background;
color: @text-muted;
display: flex;
align-content: center;
justify-content: center;
cursor: pointer;
}

// Overrides for each widgets
&.dashboard-widget-box {
padding: 10px 15px !important;
@@ -300,6 +342,14 @@
.widget-head {
margin-top: 5px;
margin-bottom: 5px;

.widget-title {
i {
color: @text-muted;
font-size: 18px;
margin-right: 6px;
}
}
}
}

@@ -357,23 +407,153 @@
border-radius: 10px;
}

.pill-green {
background: #71b92c;
// color: #000;
@-webkit-keyframes smallBounce {
from,
20%,
53%,
80%,
to {
-webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}

40%,
43% {
-webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
-webkit-transform: translate3d(0, -12px, 0);
transform: translate3d(0, -12px, 0);
}

70% {
-webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
-webkit-transform: translate3d(0, -6px, 0);
transform: translate3d(0, -6px, 0);
}

90% {
-webkit-transform: translate3d(0, -4px, 0);
transform: translate3d(0, -4px, 0);
}
}

@keyframes smallBounce {
from,
20%,
53%,
80%,
to {
-webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}

40%,
43% {
-webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
-webkit-transform: translate3d(0, -12px, 0);
transform: translate3d(0, -12px, 0);
}

70% {
-webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
-webkit-transform: translate3d(0, -6px, 0);
transform: translate3d(0, -6px, 0);
}

90% {
-webkit-transform: translate3d(0, -4px, 0);
transform: translate3d(0, -4px, 0);
}
}

.small-bounce {
-webkit-animation-name: smallBounce;
animation-name: smallBounce;
-webkit-transform-origin: center bottom;
transform-origin: center bottom;
animation-duration: 1s;
}

@-webkit-keyframes slideInUp {
from {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
visibility: visible;
}

to {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}

@keyframes slideInUp {
from {
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
visibility: visible;
}

to {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}

.pill-red {
background: @red;
.slide-in-up {
-webkit-animation-name: slideInUp;
animation-name: slideInUp;
animation-duration: 1s;
}

.pill-blue {
background: @blue;

@-webkit-keyframes pulse {
from {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}

50% {
-webkit-transform: scale3d(1.05, 1.05, 1.05);
transform: scale3d(1.05, 1.05, 1.05);
}

to {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}

.pill-yellow {
background: @yellow;
@keyframes pulse {
from {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}

50% {
-webkit-transform: scale3d(1.05, 1.05, 1.05);
transform: scale3d(1.05, 1.05, 1.05);
}

to {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}

.pill-orange {
background: @orange;
.zoomOutDelete {
// -webkit-animation-name: zoomOut;
// animation-name: zoomOut;
// animation-duration: 1s;
transition: opacity 0.2s, visibility 0.2s, transform 0.2s;
transform: scale3d(0.5, 0.5, 0.5);
opacity: 0;
visibility: hidden;
}

+ 5
- 0
frappe/public/less/form_grid.less View File

@@ -262,6 +262,11 @@
border-bottom: 1px solid @border-color;
}

.grid-form-body {
max-height: 75vh;
overflow-y: auto;
}

.grid-header-toolbar {
display: flow-root;
}


+ 1
- 0
frappe/public/less/link_preview.less View File

@@ -37,6 +37,7 @@
padding-bottom: 5px;
max-width: 330px;
min-width: 200px;
overflow-wrap: break-word;

.preview-field {
padding-bottom: 10px;


+ 2
- 1
frappe/templates/emails/delete_data_confirmation.html View File

@@ -7,5 +7,6 @@
<a href="{{ link }}" rel="nofollow" class="btn btn-primary btn-sm primary-action" style="padding: 8px 20px;">{{ _("Confirm Request") }}</a>
</p>
<p style="font-size: 85%;">
{{_("You can also copy-paste this ")}} <a href="{{ link }}">{{_("Verification Link")}}</a>{{_(" to your browser")}}
{% set verification_link = '<a href="{{ link }}">{{ _("Verification Link") }}</a>' %}
{{_("You can also copy-paste this {0} to your browser").format(verification_link) }}
</p>

+ 4
- 3
frappe/templates/includes/breadcrumbs.html View File

@@ -4,9 +4,10 @@
<ol class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList">
{%- set parents = parents[-3:] %}
{% for parent in parents %}
<li class="breadcrumb-item">
<a href="{{ url_prefix }}{{ parent.route | abs_url }}" itemprop="url">
{{ parent.title or parent.label or parent.name or "" }}
<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem" class="breadcrumb-item">
<a itemprop="item" href="{{ url_prefix }}{{ parent.route | abs_url }}" itemprop="url">
<span itemprop="name">{{ parent.title or parent.label or parent.name or "" }}</span>
<meta itemprop="position" content="{{ loop.index }}" />
</a>
</li>
{% endfor %}


+ 26
- 0
frappe/templates/includes/login/login.css View File

@@ -137,3 +137,29 @@ p {
position: absolute;
z-index: 2;
}

.invalid-login {
-webkit-animation: wiggle 0.5s linear;
}

@-webkit-keyframes wiggle {
8%,
41% {
-webkit-transform: translateX(-10px);
}
25%,
58% {
-webkit-transform: translateX(10px);
}
75% {
-webkit-transform: translateX(-5px);
}
92% {
-webkit-transform: translateX(5px);
}
0%,
100% {
-webkit-transform: translateX(0);
}
}


+ 9
- 1
frappe/templates/includes/login/login.js View File

@@ -141,6 +141,14 @@ login.set_indicator = function(message, color) {
.removeClass().addClass('indicator').addClass(color).text(message)
}

login.set_invalid = function(message) {
$(".login-content.page-card").addClass('invalid-login');
setTimeout(() => {
$(".login-content.page-card").removeClass('invalid-login');
}, 500)
login.set_indicator(message, 'red');
}

login.login_handlers = (function() {
var get_error_handler = function(default_message) {
return function(xhr, data) {
@@ -161,7 +169,7 @@ login.login_handlers = (function() {
}

if(message===default_message) {
login.set_indicator(message, 'red');
login.set_invalid(message);
} else {
login.reset_sections(false);
}


+ 1
- 1
frappe/tests/test_document.py View File

@@ -181,7 +181,7 @@ class TestDocument(unittest.TestCase):

# css attributes
xss = '<div style="something: doesn\'t work; color: red;">Test</div>'
escaped_xss = '<div style="color: red;">Test</div>'
escaped_xss = '<div style="">Test</div>'
d.subject += xss
d.save()
d.reload()


+ 2
- 2
frappe/utils/data.py View File

@@ -54,8 +54,8 @@ def get_datetime(datetime_str=None):
elif isinstance(datetime_str, datetime.date):
return datetime.datetime.combine(datetime_str, datetime.time())

# dateutil parser does not agree with dates like 0001-01-01
if not datetime_str or (datetime_str or "").startswith("0001-01-01"):
# dateutil parser does not agree with dates like "0001-01-01" or "0000-00-00"
if not datetime_str or (datetime_str or "").startswith(("0001-01-01", "0000-00-00")):
return None

try:


+ 21
- 18
frappe/website/desk_page/website/website.json View File

@@ -1,26 +1,29 @@
{
"cards": [
{
"icon": "fa fa-cog",
"links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]",
"title": "Setup"
"hidden": 0,
"label": "Setup",
"links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"links": "[\n {\n \"description\": \"Single Post (article).\",\n \"label\": \"Blog Post\",\n \"name\": \"Blog Post\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"A user who posts blogs.\",\n \"label\": \"Blogger\",\n \"name\": \"Blogger\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Categorize blog posts.\",\n \"label\": \"Blog Category\",\n \"name\": \"Blog Category\",\n \"type\": \"doctype\"\n }\n]",
"title": "Blog"
"hidden": 0,
"label": "Blog",
"links": "[\n {\n \"description\": \"Single Post (article).\",\n \"label\": \"Blog Post\",\n \"name\": \"Blog Post\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"A user who posts blogs.\",\n \"label\": \"Blogger\",\n \"name\": \"Blogger\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Categorize blog posts.\",\n \"label\": \"Blog Category\",\n \"name\": \"Blog Category\",\n \"type\": \"doctype\"\n }\n]"
},
{
"icon": "fa fa-star",
"links": "[\n {\n \"description\": \"Content web page.\",\n \"label\": \"Web Page\",\n \"name\": \"Web Page\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"User editable form on Website.\",\n \"label\": \"Web Form\",\n \"name\": \"Web Form\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Website Sidebar\",\n \"name\": \"Website Sidebar\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Embed image slideshows in website pages.\",\n \"label\": \"Website Slideshow\",\n \"name\": \"Website Slideshow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add meta tags to your web pages\",\n \"label\": \"Website Route Meta\",\n \"name\": \"Website Route Meta\",\n \"type\": \"doctype\"\n }\n]",
"title": "Web Site"
"hidden": 0,
"label": "Web Site",
"links": "[\n {\n \"description\": \"Content web page.\",\n \"label\": \"Web Page\",\n \"name\": \"Web Page\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"User editable form on Website.\",\n \"label\": \"Web Form\",\n \"name\": \"Web Form\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Website Sidebar\",\n \"name\": \"Website Sidebar\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Embed image slideshows in website pages.\",\n \"label\": \"Website Slideshow\",\n \"name\": \"Website Slideshow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add meta tags to your web pages\",\n \"label\": \"Website Route Meta\",\n \"name\": \"Website Route Meta\",\n \"type\": \"doctype\"\n }\n]"
},
{
"links": "[\n {\n \"label\": \"Portal Settings\",\n \"name\": \"Portal Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]",
"title": "Portal"
"hidden": 0,
"label": "Portal",
"links": "[\n {\n \"label\": \"Portal Settings\",\n \"name\": \"Portal Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]"
},
{
"links": "[\n {\n \"label\": \"Help Category\",\n \"name\": \"Help Category\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Help Article\",\n \"name\": \"Help Article\",\n \"type\": \"doctype\"\n }\n]",
"title": "Knowledge Base"
"hidden": 0,
"label": "Knowledge Base",
"links": "[\n {\n \"label\": \"Help Category\",\n \"name\": \"Help Category\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Help Article\",\n \"name\": \"Help Article\",\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Modules",
@@ -34,7 +37,7 @@
"idx": 0,
"is_standard": 1,
"label": "Website",
"modified": "2020-03-12 16:30:43.092622",
"modified": "2020-04-01 11:24:40.726934",
"modified_by": "Administrator",
"module": "Website",
"name": "Website",
@@ -45,30 +48,30 @@
{
"color": "",
"format": "{} Published",
"is_query_report": 0,
"label": "Blog Post",
"link_to": "Blog Post",
"stats_filter": "{\"published\":\"1\"}",
"type": "DocType"
},
{
"format": "{} Active",
"is_query_report": 0,
"label": "Blogger",
"link_to": "Blogger",
"stats_filter": "{\"disabled\": 0}",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "Web Page",
"link_to": "Web Page",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "Web Form",
"link_to": "Web Form",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "Website Settings",
"link_to": "Website Settings",
"type": "DocType"
}


+ 133
- 550
frappe/website/doctype/contact_us_settings/contact_us_settings.json View File

@@ -1,559 +1,142 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-02-21 20:12:42",
"custom": 0,
"description": "Settings for Contact Us Page",
"docstatus": 0,
"doctype": "DocType",
"editable_grid": 0,
"actions": [],
"creation": "2013-02-21 20:12:42",
"description": "Settings for Contact Us Page",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"introduction_section",
"forward_to_email",
"heading",
"introduction",
"query_options",
"address",
"address_title",
"address_line1",
"address_line2",
"city",
"state",
"pincode",
"country",
"column_break_14",
"phone",
"email_id",
"skype"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "introduction_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Introduction",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Send enquiries to this email address",
"fieldname": "forward_to_email",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Forward To Email Address",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Default: \"Contact Us\"",
"fieldname": "heading",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Heading",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Introductory information for the Contact Us Page",
"fieldname": "introduction",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Introduction",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Contact options, like \"Sales Query, Support Query\" etc each on a new line or separated by commas.",
"fieldname": "query_options",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Query Options",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"fieldname": "address_title",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address Title",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address_line1",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address Line 1",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address_line2",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address Line 2",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "city",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "City",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "state",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "State",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "pincode",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Pincode",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "country",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Country",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_14",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "phone",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Phone",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email Id",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "skype",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Skype",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
"fieldname": "introduction_section",
"fieldtype": "Section Break",
"label": "Introduction"
},
{
"description": "Send enquiries to this email address",
"fieldname": "forward_to_email",
"fieldtype": "Data",
"label": "Forward To Email Address",
"options": "Email"
},
{
"description": "Default: \"Contact Us\"",
"fieldname": "heading",
"fieldtype": "Data",
"label": "Heading"
},
{
"description": "Introductory information for the Contact Us Page",
"fieldname": "introduction",
"fieldtype": "Text Editor",
"label": "Introduction"
},
{
"description": "Contact options, like \"Sales Query, Support Query\" etc each on a new line or separated by commas.",
"fieldname": "query_options",
"fieldtype": "Small Text",
"label": "Query Options"
},
{
"fieldname": "address",
"fieldtype": "Section Break",
"label": "Address"
},
{
"bold": 1,
"fieldname": "address_title",
"fieldtype": "Data",
"label": "Address Title"
},
{
"fieldname": "address_line1",
"fieldtype": "Data",
"label": "Address Line 1"
},
{
"fieldname": "address_line2",
"fieldtype": "Data",
"label": "Address Line 2"
},
{
"fieldname": "city",
"fieldtype": "Data",
"label": "City"
},
{
"fieldname": "state",
"fieldtype": "Data",
"label": "State"
},
{
"fieldname": "pincode",
"fieldtype": "Data",
"label": "Pincode"
},
{
"fieldname": "country",
"fieldtype": "Data",
"label": "Country"
},
{
"fieldname": "column_break_14",
"fieldtype": "Column Break"
},
{
"fieldname": "phone",
"fieldtype": "Data",
"label": "Phone",
"options": "Phone"
},
{
"fieldname": "email_id",
"fieldtype": "Data",
"label": "Email Id",
"options": "Email"
},
{
"fieldname": "skype",
"fieldtype": "Data",
"label": "Skype"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-cog",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-09-04 21:41:55.580325",
"modified_by": "Administrator",
"module": "Website",
"name": "Contact Us Settings",
"owner": "Administrator",
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"links": [],
"modified": "2020-04-06 19:17:46.083764",
"modified_by": "Administrator",
"module": "Website",
"name": "Contact Us Settings",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "Website Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Website Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 1,
"track_seen": 0
],
"sort_field": "modified",
"sort_order": "DESC"
}

+ 1
- 1
requirements.txt View File

@@ -1,7 +1,7 @@
Babel==2.6.0
beautifulsoup4==4.8.2
bleach-whitelist==0.0.10
bleach==3.1.2
bleach==3.1.4
boto3==1.10.18
braintree==3.57.1
chardet==3.0.4


Loading…
Cancel
Save