Merge branch 'develop' into paytm-integration
This commit is contained in:
commit
c09da72a7d
52 changed files with 755 additions and 157 deletions
|
|
@ -78,6 +78,7 @@
|
|||
"has_common": true,
|
||||
"has_words": true,
|
||||
"validate_email": true,
|
||||
"validate_name": true,
|
||||
"validate_phone": true,
|
||||
"get_number_format": true,
|
||||
"format_number": true,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"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\": \"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 \"label\": \"Video\",\n \"name\": \"Video\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Tools",
|
||||
"modified": "2020-04-01 11:24:40.804346",
|
||||
"modified": "2020-04-20 18:21:14.152537",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Tools",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from frappe.utils.change_log import get_versions
|
|||
from frappe.translate import get_lang_dict
|
||||
from frappe.email.inbox import get_email_accounts
|
||||
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
|
||||
from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabled
|
||||
from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_points
|
||||
from frappe.social.doctype.post.post import frequently_visited_links
|
||||
|
||||
|
|
@ -79,6 +80,7 @@ def get_bootinfo():
|
|||
bootinfo.success_action = get_success_action()
|
||||
bootinfo.update(get_email_accounts(user=frappe.session.user))
|
||||
bootinfo.energy_points_enabled = is_energy_point_enabled()
|
||||
bootinfo.website_tracking_enabled = is_tracking_enabled()
|
||||
bootinfo.points = get_energy_points(frappe.session.user)
|
||||
bootinfo.frequently_visited_links = frequently_visited_links()
|
||||
bootinfo.link_preview_doctypes = get_link_preview_doctypes()
|
||||
|
|
|
|||
|
|
@ -477,7 +477,8 @@ class DocType(Document):
|
|||
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict['fields']))
|
||||
if field_dict:
|
||||
new_field_dicts.append(field_dict[0])
|
||||
remaining_field_names.remove(fieldname)
|
||||
if fieldname in remaining_field_names:
|
||||
remaining_field_names.remove(fieldname)
|
||||
|
||||
for fieldname in remaining_field_names:
|
||||
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict['fields']))
|
||||
|
|
@ -498,7 +499,8 @@ class DocType(Document):
|
|||
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', [])))
|
||||
if field_dict:
|
||||
new_field_dicts.append(field_dict[0])
|
||||
remaining_field_names.remove(fieldname)
|
||||
if fieldname in remaining_field_names:
|
||||
remaining_field_names.remove(fieldname)
|
||||
|
||||
for fieldname in remaining_field_names:
|
||||
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', [])))
|
||||
|
|
@ -893,7 +895,7 @@ def validate_fields(meta):
|
|||
field.fetch_from = field.fetch_from.strip('\n').strip()
|
||||
|
||||
def validate_data_field_type(docfield):
|
||||
if docfield.fieldtype == "Data":
|
||||
if docfield.fieldtype == "Data" and not (docfield.oldfieldtype and docfield.oldfieldtype != "Data"):
|
||||
if docfield.options and (docfield.options not in data_field_options):
|
||||
df_str = frappe.bold(_(docfield.label))
|
||||
text_str = _("{0} is an invalid Data field.").format(df_str) + "<br>" * 2 + _("Only Options allowed for Data field are:") + "<br>"
|
||||
|
|
|
|||
|
|
@ -97,47 +97,49 @@ frappe.ui.form.on('User', {
|
|||
});
|
||||
}, __("Password"));
|
||||
|
||||
frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => {
|
||||
if (value === 1 && frm.doc.name != "Administrator") {
|
||||
frm.add_custom_button(__("Reset LDAP Password"), function() {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __("Reset LDAP Password"),
|
||||
fields: [
|
||||
{
|
||||
label: __("New Password"),
|
||||
fieldtype: "Password",
|
||||
fieldname: "new_password",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("Confirm New Password"),
|
||||
fieldtype: "Password",
|
||||
fieldname: "confirm_password",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("Logout All Sessions"),
|
||||
fieldtype: "Check",
|
||||
fieldname: "logout_sessions"
|
||||
if (frappe.user.has_role("System Manager")) {
|
||||
frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => {
|
||||
if (value === 1 && frm.doc.name != "Administrator") {
|
||||
frm.add_custom_button(__("Reset LDAP Password"), function() {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __("Reset LDAP Password"),
|
||||
fields: [
|
||||
{
|
||||
label: __("New Password"),
|
||||
fieldtype: "Password",
|
||||
fieldname: "new_password",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("Confirm New Password"),
|
||||
fieldtype: "Password",
|
||||
fieldname: "confirm_password",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("Logout All Sessions"),
|
||||
fieldtype: "Check",
|
||||
fieldname: "logout_sessions"
|
||||
}
|
||||
],
|
||||
primary_action: (values) => {
|
||||
d.hide();
|
||||
if (values.new_password !== values.confirm_password) {
|
||||
frappe.throw(__("Passwords do not match!"));
|
||||
}
|
||||
frappe.call(
|
||||
"frappe.integrations.doctype.ldap_settings.ldap_settings.reset_password", {
|
||||
user: frm.doc.email,
|
||||
password: values.new_password,
|
||||
logout: values.logout_sessions
|
||||
});
|
||||
}
|
||||
],
|
||||
primary_action: (values) => {
|
||||
d.hide();
|
||||
if (values.new_password !== values.confirm_password) {
|
||||
frappe.throw(__("Passwords do not match!"));
|
||||
}
|
||||
frappe.call(
|
||||
"frappe.integrations.doctype.ldap_settings.ldap_settings.reset_password", {
|
||||
user: frm.doc.email,
|
||||
password: values.new_password,
|
||||
logout: values.logout_sessions
|
||||
});
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
}, __("Password"));
|
||||
}
|
||||
});
|
||||
});
|
||||
d.show();
|
||||
}, __("Password"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frm.add_custom_button(__("Reset OTP Secret"), function() {
|
||||
frappe.call({
|
||||
|
|
|
|||
|
|
@ -551,6 +551,7 @@ def update_password(new_password, logout_all_sessions=0, key=None, old_password=
|
|||
|
||||
res = _get_user_for_update_password(key, old_password)
|
||||
if res.get('message'):
|
||||
frappe.local.response.http_status_code = 410
|
||||
return res['message']
|
||||
else:
|
||||
user = res['user']
|
||||
|
|
@ -718,7 +719,7 @@ def _get_user_for_update_password(key, old_password):
|
|||
user = frappe.db.get_value("User", {"reset_password_key": key})
|
||||
if not user:
|
||||
return {
|
||||
'message': _("Cannot Update: Incorrect / Expired Link.")
|
||||
'message': _("The Link specified has either been used before or Invalid")
|
||||
}
|
||||
|
||||
elif old_password:
|
||||
|
|
|
|||
0
frappe/core/doctype/video/__init__.py
Normal file
0
frappe/core/doctype/video/__init__.py
Normal file
10
frappe/core/doctype/video/test_video.py
Normal file
10
frappe/core/doctype/video/test_video.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestVideo(unittest.TestCase):
|
||||
pass
|
||||
8
frappe/core/doctype/video/video.js
Normal file
8
frappe/core/doctype/video/video.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Video', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
106
frappe/core/doctype/video/video.json
Normal file
106
frappe/core/doctype/video/video.json
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"creation": "2018-10-17 05:47:13.087395",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"provider",
|
||||
"url",
|
||||
"column_break_4",
|
||||
"publish_date",
|
||||
"duration",
|
||||
"section_break_7",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "provider",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Provider",
|
||||
"options": "YouTube\nVimeo",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "url",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "URL",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "publish_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Publish Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "duration",
|
||||
"fieldtype": "Data",
|
||||
"label": "Duration"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-04-22 12:09:49.057403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Video",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/core/doctype/video/video.py
Normal file
10
frappe/core/doctype/video/video.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Video(Document):
|
||||
pass
|
||||
|
|
@ -268,8 +268,9 @@ def get_open_count(doctype, name, items=[]):
|
|||
"count": out,
|
||||
}
|
||||
|
||||
module = frappe.get_meta_module(doctype)
|
||||
if hasattr(module, "get_timeline_data"):
|
||||
out["timeline_data"] = module.get_timeline_data(doctype, name)
|
||||
if not meta.custom:
|
||||
module = frappe.get_meta_module(doctype)
|
||||
if hasattr(module, "get_timeline_data"):
|
||||
out["timeline_data"] = module.get_timeline_data(doctype, name)
|
||||
|
||||
return out
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ def get_prepared_report_result(report, filters, dn="", user=None):
|
|||
columns = json.loads(doc.columns) if doc.columns else data[0]
|
||||
|
||||
for column in columns:
|
||||
if isinstance(column, dict):
|
||||
if isinstance(column, dict) and column.get("label"):
|
||||
column["label"] = _(column["label"])
|
||||
|
||||
latest_report_data = {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class EmailDomain(Document):
|
|||
|
||||
except Exception:
|
||||
frappe.throw(_("Incoming email account not correct"))
|
||||
return None
|
||||
|
||||
finally:
|
||||
try:
|
||||
if self.use_imap:
|
||||
|
|
@ -48,9 +48,10 @@ class EmailDomain(Document):
|
|||
test.quit()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if self.use_ssl_for_outgoing:
|
||||
if not self.smtp_port:
|
||||
if self.get('use_ssl_for_outgoing'):
|
||||
if not self.get('smtp_port'):
|
||||
self.smtp_port = 465
|
||||
|
||||
sess = smtplib.SMTP_SSL((self.smtp_server or "").encode('utf-8'),
|
||||
|
|
@ -62,28 +63,15 @@ class EmailDomain(Document):
|
|||
sess.quit()
|
||||
except Exception:
|
||||
frappe.throw(_("Outgoing email account not correct"))
|
||||
return None
|
||||
return
|
||||
|
||||
def on_update(self):
|
||||
"""update all email accounts using this domain"""
|
||||
for email_account in frappe.get_all("Email Account",
|
||||
filters={"domain": self.name}):
|
||||
|
||||
for email_account in frappe.get_all("Email Account", filters={"domain": self.name}):
|
||||
try:
|
||||
email_account = frappe.get_doc("Email Account",
|
||||
email_account.name)
|
||||
email_account.set("email_server",self.email_server)
|
||||
email_account.set("use_imap",self.use_imap)
|
||||
email_account.set("use_ssl",self.use_ssl)
|
||||
email_account.set("use_tls",self.use_tls)
|
||||
email_account.set("attachment_limit",self.attachment_limit)
|
||||
email_account.set("smtp_server",self.smtp_server)
|
||||
email_account.set("smtp_port",self.smtp_port)
|
||||
email_account.set("use_ssl_for_outgoing", self.use_ssl_for_outgoing)
|
||||
email_account.set("append_emails_to_sent_folder", self.append_emails_to_sent_folder)
|
||||
email_account = frappe.get_doc("Email Account", email_account.name)
|
||||
for attr in ["email_server", "use_imap", "use_ssl", "use_tls", "attachment_limit", "smtp_server", "smtp_port", "use_ssl_for_outgoing", "append_emails_to_sent_folder"]:
|
||||
email_account.set(attr, self.get(attr, default=0))
|
||||
email_account.save()
|
||||
|
||||
except Exception as e:
|
||||
frappe.msgprint(email_account.name)
|
||||
frappe.throw(e)
|
||||
return None
|
||||
frappe.msgprint(_("Error has occurred in {0}").format(email_account.name), raise_exception=e.__class__)
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ class TimestampMismatchError(ValidationError): pass
|
|||
class EmptyTableError(ValidationError): pass
|
||||
class LinkExistsError(ValidationError): pass
|
||||
class InvalidEmailAddressError(ValidationError): pass
|
||||
class InvalidNameError(ValidationError): pass
|
||||
class InvalidPhoneNumberError(ValidationError): pass
|
||||
class TemplateNotFoundError(ValidationError): pass
|
||||
class UniqueValidationError(ValidationError): pass
|
||||
|
|
@ -95,4 +96,4 @@ class DataTooLongException(ValidationError): pass
|
|||
# OAuth exceptions
|
||||
class InvalidAuthorizationHeader(CSRFTokenError): pass
|
||||
class InvalidAuthorizationPrefix(CSRFTokenError): pass
|
||||
class InvalidAuthorizationToken(CSRFTokenError): pass
|
||||
class InvalidAuthorizationToken(CSRFTokenError): pass
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ table_fields = ('Table', 'Table MultiSelect')
|
|||
core_doctypes_list = ('DocType', 'DocField', 'DocPerm', 'DocType Action', 'DocType Link', 'User', 'Role', 'Has Role',
|
||||
'Page', 'Module Def', 'Print Format', 'Report', 'Customize Form',
|
||||
'Customize Form Field', 'Property Setter', 'Custom Field', 'Custom Script')
|
||||
data_field_options = ('Email', 'Phone')
|
||||
data_field_options = ('Email', 'Name', 'Phone')
|
||||
|
||||
def copytables(srctype, src, srcfield, tartype, tar, tarfield, srcfields, tarfields=[]):
|
||||
if not tarfields:
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ from frappe.model import default_fields, table_fields
|
|||
from frappe.model.naming import set_new_name
|
||||
from frappe.model.utils.link_count import notify_link_count
|
||||
from frappe.modules import load_doctype_module
|
||||
from frappe.model import display_fieldtypes, data_fieldtypes
|
||||
from frappe.model import display_fieldtypes
|
||||
from frappe.utils.password import get_decrypted_password, set_encrypted_password
|
||||
from frappe.utils import (cint, flt, now, cstr, strip_html, getdate, get_datetime, to_timedelta,
|
||||
from frappe.utils import (cint, flt, now, cstr, strip_html,
|
||||
sanitize_html, sanitize_email, cast_fieldtype)
|
||||
from frappe.utils.html_utils import unescape_html
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
max_positive_value = {
|
||||
'smallint': 2 ** 15,
|
||||
|
|
@ -288,7 +289,7 @@ class BaseDocument(object):
|
|||
if k in default_fields:
|
||||
del doc[k]
|
||||
|
||||
for key in ("_user_tags", "__islocal", "__onload", "_liked_by", "__run_link_triggers"):
|
||||
for key in ("_user_tags", "__islocal", "__onload", "_liked_by", "__run_link_triggers", "__unsaved"):
|
||||
if self.get(key):
|
||||
doc[key] = self.get(key)
|
||||
|
||||
|
|
@ -564,13 +565,20 @@ class BaseDocument(object):
|
|||
for data_field in self.meta.get_data_fields():
|
||||
data = self.get(data_field.fieldname)
|
||||
data_field_options = data_field.get("options")
|
||||
old_fieldtype = data_field.get("oldfieldtype")
|
||||
|
||||
if old_fieldtype and old_fieldtype != "Data":
|
||||
continue
|
||||
|
||||
if data_field_options == "Email":
|
||||
if (self.owner in STANDARD_USERS) and (data in STANDARD_USERS):
|
||||
return
|
||||
continue
|
||||
for email_address in frappe.utils.split_emails(data):
|
||||
frappe.utils.validate_email_address(email_address, throw=True)
|
||||
|
||||
if data_field_options == "Name":
|
||||
frappe.utils.validate_name(data, throw=True)
|
||||
|
||||
if data_field_options == "Phone":
|
||||
frappe.utils.validate_phone_number(data, throw=True)
|
||||
|
||||
|
|
@ -678,7 +686,7 @@ class BaseDocument(object):
|
|||
# doesn't look like html so no need
|
||||
continue
|
||||
|
||||
elif "<!-- markdown -->" in value and not ("<script" in value or "javascript:" in value):
|
||||
elif "<!-- markdown -->" in value and not bool(BeautifulSoup(value, "html.parser").find()):
|
||||
# should be handled separately via the markdown converter function
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -74,11 +74,9 @@ def set_user_and_static_default_values(doc):
|
|||
def get_user_default_value(df, defaults, doctype_user_permissions, allowed_records, default_doc):
|
||||
# don't set defaults for "User" link field using User Permissions!
|
||||
if df.fieldtype == "Link" and df.options != "User":
|
||||
# 1 - look in user permissions only for document_type==Setup
|
||||
# We don't want to include permissions of transactions to be used for defaults.
|
||||
if (frappe.get_meta(df.options).document_type=="Setup"
|
||||
and not df.ignore_user_permissions and default_doc):
|
||||
return default_doc
|
||||
# If user permission has Is Default enabled or single-user permission has found against respective doctype.
|
||||
if (not df.ignore_user_permissions and default_doc):
|
||||
return default_doc
|
||||
|
||||
# 2 - Look in user defaults
|
||||
user_default = defaults.get(df.fieldname)
|
||||
|
|
|
|||
|
|
@ -329,6 +329,10 @@ class Document(BaseDocument):
|
|||
self.update_children()
|
||||
self.run_post_save_methods()
|
||||
|
||||
# clear unsaved flag
|
||||
if hasattr(self, "__unsaved"):
|
||||
delattr(self, "__unsaved")
|
||||
|
||||
return self
|
||||
|
||||
def copy_attachments_from_amended_from(self):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("contacts", "doctype", "contact_email")
|
||||
frappe.reload_doc("contacts", "doctype", "contact_phone")
|
||||
frappe.reload_doc("contacts", "doctype", "contact")
|
||||
|
||||
contact_details = frappe.db.sql("""
|
||||
SELECT
|
||||
`name`, `email_id`, `phone`, `mobile_no`, `modified_by`, `creation`, `modified`
|
||||
|
|
@ -10,10 +14,6 @@ def execute():
|
|||
and `tabContact Email`.email_id=`tabContact`.email_id)
|
||||
""", as_dict=True)
|
||||
|
||||
frappe.reload_doc("contacts", "doctype", "contact_email")
|
||||
frappe.reload_doc("contacts", "doctype", "contact_phone")
|
||||
frappe.reload_doc("contacts", "doctype", "contact")
|
||||
|
||||
email_values = []
|
||||
phone_values = []
|
||||
for count, contact_detail in enumerate(contact_details):
|
||||
|
|
|
|||
|
|
@ -441,18 +441,16 @@ frappe.PrintFormatBuilder = Class.extend({
|
|||
});
|
||||
},
|
||||
setup_field_settings: function() {
|
||||
|
||||
this.page.main.find(".field-settings").on("click", () => {
|
||||
var field = $(this).parent();
|
||||
|
||||
this.page.main.find(".field-settings").on("click", e => {
|
||||
const field = $(e.currentTarget).parent();
|
||||
// new dialog
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: "Set Properties",
|
||||
fields: [
|
||||
{
|
||||
label:__("Label"),
|
||||
fieldname:"label",
|
||||
fieldtype:"Data"
|
||||
label: __("Label"),
|
||||
fieldname: "label",
|
||||
fieldtype: "Data"
|
||||
},
|
||||
{
|
||||
label: __("Align Value"),
|
||||
|
|
@ -485,7 +483,7 @@ frappe.PrintFormatBuilder = Class.extend({
|
|||
});
|
||||
|
||||
// set current value
|
||||
if(field.attr('data-align')) {
|
||||
if (field.attr('data-align')) {
|
||||
d.set_value('align', field.attr('data-align'));
|
||||
} else {
|
||||
d.set_value('align', 'left');
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@
|
|||
"public/css/font-awesome.css",
|
||||
"public/css/octicons/octicons.css",
|
||||
"public/less/desk.less",
|
||||
"public/less/module.less",
|
||||
"public/less/flex.less",
|
||||
"public/less/indicator.less",
|
||||
"public/less/avatar.less",
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({
|
|||
.appendTo(this.input_area);
|
||||
|
||||
this.expanded = false;
|
||||
this.$expand_button = $(`<button class="margin-top btn btn-xs btn-default">${__('Expand / Collapse')}</button>`).click(() => {
|
||||
this.$expand_button = $(`<button class="btn btn-xs btn-default">${__('Expand')}</button>`).click(() => {
|
||||
this.expanded = !this.expanded;
|
||||
this.refresh_height();
|
||||
}).insertAfter(this.ace_editor_target);
|
||||
|
||||
this.toggle_label();
|
||||
}).appendTo(this.$input_wrapper);
|
||||
// styling
|
||||
this.ace_editor_target.addClass('border rounded');
|
||||
this.ace_editor_target.css('height', 300);
|
||||
|
|
@ -37,6 +37,11 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({
|
|||
this.editor.resize();
|
||||
},
|
||||
|
||||
toggle_label() {
|
||||
const button_label = this.expanded ? __('Collapse') : __('Expand');
|
||||
this.$expand_button.text(button_label);
|
||||
},
|
||||
|
||||
set_language() {
|
||||
const language_map = {
|
||||
'Javascript': 'ace/mode/javascript',
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({
|
|||
if(this.df.options == 'Phone') {
|
||||
this.df.invalid = !validate_phone(v);
|
||||
return v;
|
||||
} else if (this.df.options == 'Name') {
|
||||
this.df.invalid = !validate_name(v);
|
||||
return v;
|
||||
} else if(this.df.options == 'Email') {
|
||||
var email_list = frappe.utils.split_emails(v);
|
||||
if (!email_list) {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
},
|
||||
|
||||
refresh: function() {
|
||||
if(this.frm.doc.__islocal) {
|
||||
if (this.frm.doc.__islocal) {
|
||||
this.sidebar.toggle(false);
|
||||
} else {
|
||||
this.sidebar.toggle(true);
|
||||
|
|
@ -81,12 +81,34 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
}
|
||||
this.frm.viewers.refresh();
|
||||
this.frm.tags && this.frm.tags.refresh(this.frm.get_docinfo().tags);
|
||||
this.sidebar.find(".modified-by").html(__("{0} edited this {1}",
|
||||
["<strong>" + frappe.user.full_name(this.frm.doc.modified_by) + "</strong>",
|
||||
"<br>" + comment_when(this.frm.doc.modified)]));
|
||||
this.sidebar.find(".created-by").html(__("{0} created this {1}",
|
||||
["<strong>" + frappe.user.full_name(this.frm.doc.owner) + "</strong>",
|
||||
"<br>" + comment_when(this.frm.doc.creation)]));
|
||||
|
||||
if (this.frm.doc.route && cint(frappe.boot.website_tracking_enabled)) {
|
||||
let route = this.frm.doc.route;
|
||||
frappe.utils.get_page_view_count(route).then((res) => {
|
||||
this.sidebar
|
||||
.find(".pageview-count")
|
||||
.html(
|
||||
__("{0} Page Views", [String(res.message).bold()])
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this.sidebar
|
||||
.find(".modified-by")
|
||||
.html(
|
||||
__("{0} edited this {1}", [
|
||||
frappe.user.full_name(this.frm.doc.modified_by).bold(),
|
||||
"<br>" + comment_when(this.frm.doc.modified),
|
||||
])
|
||||
);
|
||||
this.sidebar
|
||||
.find(".created-by")
|
||||
.html(
|
||||
__("{0} created this {1}", [
|
||||
frappe.user.full_name(this.frm.doc.owner).bold(),
|
||||
"<br>" + comment_when(this.frm.doc.creation),
|
||||
])
|
||||
);
|
||||
|
||||
this.refresh_like();
|
||||
frappe.ui.form.set_user_image(this.frm);
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@
|
|||
</li>
|
||||
</ul>
|
||||
<ul class="list-unstyled sidebar-menu text-muted">
|
||||
<li class="pageview-count"></li>
|
||||
<li class="modified-by"></li>
|
||||
<li class="created-by"></li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -137,10 +137,8 @@ $.extend(frappe.model, {
|
|||
|
||||
// don't set defaults for "User" link field using User Permissions!
|
||||
if (df.fieldtype==="Link" && df.options!=="User") {
|
||||
// 1 - look in user permissions for document_type=="Setup".
|
||||
// We don't want to include permissions of transactions to be used for defaults.
|
||||
if (df.linked_document_type==="Setup"
|
||||
&& has_user_permissions && default_doc) {
|
||||
// If user permission has Is Default enabled or single-user permission has found against respective doctype.
|
||||
if (has_user_permissions && default_doc) {
|
||||
return default_doc;
|
||||
}
|
||||
|
||||
|
|
@ -161,10 +159,6 @@ $.extend(frappe.model, {
|
|||
user_default = frappe.boot.user.last_selected_values[df.options];
|
||||
}
|
||||
|
||||
if (!user_default && default_doc) {
|
||||
user_default = default_doc;
|
||||
}
|
||||
|
||||
var is_allowed_user_default = user_default &&
|
||||
(!has_user_permissions || allowed_records.includes(user_default));
|
||||
|
||||
|
|
|
|||
|
|
@ -352,3 +352,9 @@ frappe.utils.new_auto_repeat_prompt = function(frm) {
|
|||
__('Save')
|
||||
);
|
||||
}
|
||||
|
||||
frappe.utils.get_page_view_count = function(route) {
|
||||
return frappe.call("frappe.website.doctype.web_page_view.web_page_view.get_page_view_count", {
|
||||
path: route
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ window.validate_phone = function(txt) {
|
|||
return frappe.utils.validate_type(txt, "phone");
|
||||
};
|
||||
|
||||
window.validate_name = function(txt) {
|
||||
return frappe.utils.validate_type(txt, "name");
|
||||
};
|
||||
|
||||
window.nth = function(number) {
|
||||
number = cint(number);
|
||||
var s = 'th';
|
||||
|
|
@ -73,4 +77,4 @@ window.has_common = function(list1, list2) {
|
|||
if(in_list(list2, list1[i]))return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ function prettyDate(date, mini) {
|
|||
// Return short format of time difference
|
||||
if (day_diff == 0) {
|
||||
if (diff < 60) {
|
||||
return __("Now");
|
||||
return __("now");
|
||||
} else if (diff < 3600) {
|
||||
return __("{0} m", [Math.floor(diff / 60)]);
|
||||
} else if (diff < 86400) {
|
||||
|
|
@ -21,20 +21,20 @@ function prettyDate(date, mini) {
|
|||
}
|
||||
} else {
|
||||
if (day_diff < 7) {
|
||||
return __("{0} D", [day_diff]);
|
||||
return __("{0} d", [day_diff]);
|
||||
} else if (day_diff < 31) {
|
||||
return __("{0} W", [Math.ceil(day_diff / 7)]);
|
||||
return __("{0} w", [Math.ceil(day_diff / 7)]);
|
||||
} else if (day_diff < 365) {
|
||||
return __("{0} M", [Math.ceil(day_diff / 30)]);
|
||||
} else {
|
||||
return __("{0} Y", [Math.ceil(day_diff / 365)]);
|
||||
return __("{0} y", [Math.ceil(day_diff / 365)]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Return long format of time difference
|
||||
if (day_diff == 0) {
|
||||
if (diff < 60) {
|
||||
return __("Just now");
|
||||
return __("just now");
|
||||
} else if (diff < 120) {
|
||||
return __("1 minute ago");
|
||||
} else if (diff < 3600) {
|
||||
|
|
@ -46,7 +46,7 @@ function prettyDate(date, mini) {
|
|||
}
|
||||
} else {
|
||||
if (day_diff == 1) {
|
||||
return __("Yesterday");
|
||||
return __("yesterday");
|
||||
} else if (day_diff < 7) {
|
||||
return __("{0} days ago", [day_diff]);
|
||||
} else if (day_diff < 14) {
|
||||
|
|
|
|||
|
|
@ -237,6 +237,9 @@ Object.assign(frappe.utils, {
|
|||
case "phone":
|
||||
regExp = /^([0-9\ \+\_\-\,\.\*\#\(\)]){1,20}$/;
|
||||
break;
|
||||
case "name":
|
||||
regExp = /^[\w][\w'-]*([ \w][\w'-]+)*$/;
|
||||
break;
|
||||
case "number":
|
||||
regExp = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/;
|
||||
break;
|
||||
|
|
@ -745,7 +748,36 @@ Object.assign(frappe.utils, {
|
|||
});
|
||||
|
||||
return $el;
|
||||
}
|
||||
},
|
||||
|
||||
get_browser() {
|
||||
var ua = navigator.userAgent,
|
||||
tem,
|
||||
M =
|
||||
ua.match(
|
||||
/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i
|
||||
) || [];
|
||||
if (/trident/i.test(M[1])) {
|
||||
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
|
||||
return { name: "IE", version: tem[1] || "" };
|
||||
}
|
||||
if (M[1] === "Chrome") {
|
||||
tem = ua.match(/\bOPR|Edge\/(\d+)/);
|
||||
if (tem != null) {
|
||||
return { name: "Opera", version: tem[1] };
|
||||
}
|
||||
}
|
||||
M = M[2]
|
||||
? [M[1], M[2]]
|
||||
: [navigator.appName, navigator.appVersion, "-?"];
|
||||
if ((tem = ua.match(/version\/(\d+)/i)) != null) {
|
||||
M.splice(1, 1, tem[1]);
|
||||
}
|
||||
return {
|
||||
name: M[0],
|
||||
version: M[1],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Array de duplicate
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ frappe.breadcrumbs = {
|
|||
|
||||
preferred: {
|
||||
"File": "",
|
||||
"Video": "",
|
||||
"Dashboard": "Customization",
|
||||
"Dashboard Chart": "Customization",
|
||||
"Dashboard Chart Source": "Customization",
|
||||
"Dashboard Chart Source": "Customization"
|
||||
},
|
||||
|
||||
module_map: {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
primary_action: function() {
|
||||
me.delete_saved_draft();
|
||||
me.send_action();
|
||||
}
|
||||
},
|
||||
minimizable: true
|
||||
});
|
||||
|
||||
['recipients', 'cc', 'bcc'].forEach(field => {
|
||||
|
|
|
|||
|
|
@ -770,6 +770,7 @@ h6.uppercase, .h6.uppercase {
|
|||
|
||||
.help-box {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
pre {
|
||||
|
|
|
|||
147
frappe/public/less/module.less
Normal file
147
frappe/public/less/module.less
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
@import "variables.less";
|
||||
|
||||
.module-head {
|
||||
padding: 15px 30px;
|
||||
border-bottom: 1px solid @light-border-color;
|
||||
}
|
||||
|
||||
.module-head h1 {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.module-body {
|
||||
padding: 0px 15px;
|
||||
|
||||
.section-head {
|
||||
margin-bottom: 15px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.module-section {
|
||||
border-bottom: 1px solid @light-border-color;
|
||||
|
||||
.module-section-link {
|
||||
line-height: 1.5em;
|
||||
// font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.module-section-column {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
@media(min-width: @screen-xs) {
|
||||
.module-section:nth-child(even) {
|
||||
background-color: @light-bg;
|
||||
}
|
||||
|
||||
.module-section:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: @screen-sm) {
|
||||
.module-body {
|
||||
margin-top: 15px;
|
||||
border-top: 1px solid @border-color;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: @screen-xs) {
|
||||
.module-body {
|
||||
margin-top: 0;
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: @screen-xs) {
|
||||
.module-section {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.module-section-column {
|
||||
border-bottom: 1px solid @light-border-color;
|
||||
}
|
||||
|
||||
.module-section-column:nth-child(even) {
|
||||
background-color: @light-bg;
|
||||
}
|
||||
|
||||
.module-section:last-child .module-section-column:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.module-item {
|
||||
margin: 0px;
|
||||
padding: 7px;
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid @border-color;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
-webkit-transition: 0.2s;
|
||||
}
|
||||
|
||||
.module-item h4 {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.module-item .module-item-description {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.module-item .badge {
|
||||
margin-top: -2px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.module-item:hover, .module-item:focus {
|
||||
background-color: @panel-bg;
|
||||
}
|
||||
|
||||
.module-item:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.module-link.active .icon-chevron-right {
|
||||
margin-top: 4px;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.module-item-progress {
|
||||
margin-bottom: 10px;
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
.module-item-progress-total {
|
||||
height: 7px;
|
||||
background-color: #999999;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.module-item-progress-open {
|
||||
height: 7px;
|
||||
background-color: red;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
@media(max-width: @screen-xs) {
|
||||
|
||||
body[data-route^="Module"] {
|
||||
.page-title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.page-actions {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.layout-main-section {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -273,7 +273,8 @@ body[data-route^="Module"] .main-menu {
|
|||
}
|
||||
|
||||
.layout-side-section .form-sidebar {
|
||||
.modified-by {
|
||||
.modified-by,
|
||||
.pageview-count {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,13 +81,29 @@ def validate_phone_number(phone_number, throw=False):
|
|||
return False
|
||||
|
||||
phone_number = phone_number.strip()
|
||||
match = re.match("([0-9\ \+\_\-\,\.\*\#\(\)]){1,20}$", phone_number)
|
||||
match = re.match(r"([0-9\ \+\_\-\,\.\*\#\(\)]){1,20}$", phone_number)
|
||||
|
||||
if not match and throw:
|
||||
frappe.throw(frappe._("{0} is not a valid Phone Number").format(phone_number), frappe.InvalidPhoneNumberError)
|
||||
|
||||
return bool(match)
|
||||
|
||||
def validate_name(name, throw=False):
|
||||
"""Returns True if the name is valid
|
||||
valid names may have unicode and ascii characters, dash, quotes, numbers
|
||||
anything else is considered invalid
|
||||
"""
|
||||
if not name:
|
||||
return False
|
||||
|
||||
name = name.strip()
|
||||
match = re.match(r"^[\w][\w\'\-]*([ \w][\w\'\-]+)*$", name)
|
||||
|
||||
if not match and throw:
|
||||
frappe.throw(frappe._("{0} is not a valid Name").format(name), frappe.InvalidNameError)
|
||||
|
||||
return bool(match)
|
||||
|
||||
def validate_email_address(email_str, throw=False):
|
||||
"""Validates the email string"""
|
||||
email = email_str = (email_str or "").strip()
|
||||
|
|
|
|||
|
|
@ -174,9 +174,12 @@ def parse_latest_non_beta_release(response):
|
|||
Returns
|
||||
json : json object pertaining to the latest non-beta release
|
||||
"""
|
||||
for release in response:
|
||||
if release['prerelease'] == True: continue
|
||||
return release
|
||||
version_list = [release.get('tag_name').strip('v') for release in response if not release.get('prerelease')]
|
||||
|
||||
if version_list:
|
||||
return sorted(version_list, key=Version, reverse=True)[0]
|
||||
|
||||
return None
|
||||
|
||||
def check_release_on_github(app):
|
||||
# Check if repo remote is on github
|
||||
|
|
@ -199,12 +202,11 @@ def check_release_on_github(app):
|
|||
|
||||
org_name = remote_url.split('/')[3]
|
||||
r = requests.get('https://api.github.com/repos/{}/{}/releases'.format(org_name, app))
|
||||
if r.status_code == 200 and r.json():
|
||||
if r.ok:
|
||||
lastest_non_beta_release = parse_latest_non_beta_release(r.json())
|
||||
return Version(lastest_non_beta_release['tag_name'].strip('v')), org_name
|
||||
else:
|
||||
# In case of an improper response or if there are no releases
|
||||
return None
|
||||
return Version(lastest_non_beta_release), org_name
|
||||
# In case of an improper response or if there are no releases
|
||||
return None
|
||||
|
||||
def add_message_to_redis(update_json):
|
||||
# "update-message" will store the update message string
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from logging.handlers import RotatingFileHandler
|
|||
from six import text_type
|
||||
|
||||
default_log_level = logging.DEBUG
|
||||
LOG_FILENAME = '../logs/frappe.log'
|
||||
LOG_FILENAME = '../logs/{}-frappe.log'.format(frappe.local.site)
|
||||
|
||||
def get_logger(module, with_more_info=True):
|
||||
if module in frappe.loggers:
|
||||
|
|
@ -57,4 +57,3 @@ def set_log_level(level):
|
|||
'''Use this method to set log level to something other than the default DEBUG'''
|
||||
frappe.log_level = getattr(logging, (level or '').upper(), None) or default_log_level
|
||||
frappe.loggers = {}
|
||||
|
||||
|
|
|
|||
0
frappe/website/doctype/web_page_view/__init__.py
Normal file
0
frappe/website/doctype/web_page_view/__init__.py
Normal file
10
frappe/website/doctype/web_page_view/test_web_page_view.py
Normal file
10
frappe/website/doctype/web_page_view/test_web_page_view.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestWebPageView(unittest.TestCase):
|
||||
pass
|
||||
8
frappe/website/doctype/web_page_view/web_page_view.js
Normal file
8
frappe/website/doctype/web_page_view/web_page_view.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Web Page View', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
75
frappe/website/doctype/web_page_view/web_page_view.json
Normal file
75
frappe/website/doctype/web_page_view/web_page_view.json
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-04-15 22:54:46.009703",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"path",
|
||||
"referrer",
|
||||
"browser",
|
||||
"browser_version",
|
||||
"date"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "path",
|
||||
"fieldtype": "Data",
|
||||
"label": "Path",
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "referrer",
|
||||
"fieldtype": "Data",
|
||||
"label": "Referrer",
|
||||
"search_index": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "browser",
|
||||
"fieldtype": "Data",
|
||||
"label": "Browser",
|
||||
"search_index": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "browser_version",
|
||||
"fieldtype": "Data",
|
||||
"label": "Browser Version",
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Date",
|
||||
"set_only_once": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-15 23:31:27.517793",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Page View",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "path",
|
||||
"track_changes": 1
|
||||
}
|
||||
43
frappe/website/doctype/web_page_view/web_page_view.py
Normal file
43
frappe/website/doctype/web_page_view/web_page_view.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class WebPageView(Document):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def make_view_log(path, referrer=None, browser=None, version=None, url=None, user_tz=None):
|
||||
request_dict = frappe.request.__dict__
|
||||
user_agent = request_dict.get('environ', {}).get('HTTP_USER_AGENT')
|
||||
|
||||
is_unique = True
|
||||
if referrer.startswith(url):
|
||||
is_unique = False
|
||||
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
|
||||
if is_tracking_enabled():
|
||||
view = frappe.new_doc("Web Page View")
|
||||
view.path = path
|
||||
view.referrer = referrer
|
||||
view.browser = browser
|
||||
view.browser_version = version
|
||||
view.time_zone = user_tz
|
||||
view.user_agent = user_agent
|
||||
view.is_unique = is_unique
|
||||
view.insert(ignore_permissions=True)
|
||||
|
||||
return
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_page_view_count(path):
|
||||
return frappe.db.count("Web Page View", filters={'path': path})
|
||||
|
||||
def is_tracking_enabled():
|
||||
return frappe.db.get_value("Website Settings", "Website Settings", "enable_view_tracking")
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
"allow_guest_to_view": 1,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"beta": 1,
|
||||
"creation": "2020-03-16 15:28:03.828741",
|
||||
"doctype": "DocType",
|
||||
|
|
@ -117,7 +116,7 @@
|
|||
"has_web_view": 1,
|
||||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"modified": "2020-04-19 12:25:48.014935",
|
||||
"modified": "2020-04-22 00:54:23.413077",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web View",
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ frappe.ui.form.on('Website Settings', {
|
|||
});
|
||||
},
|
||||
|
||||
enable_view_tracking: function(frm) {
|
||||
frappe.boot.website_tracking_enabled = frm.doc.enable_view_tracking;
|
||||
},
|
||||
|
||||
set_parent_options: function(frm, doctype, name) {
|
||||
var item = frappe.get_doc(doctype, name);
|
||||
if(item.parentfield === "top_bar_items") {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
"footer_items",
|
||||
"hide_footer_signup",
|
||||
"integrations",
|
||||
"enable_view_tracking",
|
||||
"enable_google_indexing",
|
||||
"authorize_api_indexing_access",
|
||||
"indexing_refresh_token",
|
||||
|
|
@ -196,7 +197,7 @@
|
|||
"collapsible": 1,
|
||||
"fieldname": "integrations",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Google Integrations"
|
||||
"label": "Integrations"
|
||||
},
|
||||
{
|
||||
"description": "Add Google Analytics ID: eg. UA-89XXX57-1. Please search help on Google Analytics for more information.",
|
||||
|
|
@ -330,6 +331,12 @@
|
|||
"fieldtype": "Button",
|
||||
"label": "Authorize API Indexing Access"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_view_tracking",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable In App Website Tracking"
|
||||
},
|
||||
{
|
||||
"default": "Standard",
|
||||
"fieldname": "footer_type",
|
||||
|
|
@ -364,7 +371,7 @@
|
|||
"issingle": 1,
|
||||
"links": [],
|
||||
"max_attachments": 10,
|
||||
"modified": "2020-04-21 16:46:59.947403",
|
||||
"modified": "2020-04-21 12:37:44.070662",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Website Settings",
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ def get_website_settings():
|
|||
for k in ["banner_html", "brand_html", "copyright", "twitter_share_via",
|
||||
"facebook_share", "google_plus_one", "twitter_share", "linked_in_share",
|
||||
"disable_signup", "hide_footer_signup", "head_html", "title_prefix",
|
||||
"navbar_search"]:
|
||||
"navbar_search", "enable_view_tracking"]:
|
||||
if hasattr(settings, k):
|
||||
context[k] = settings.get(k)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<span class='indicator blue password-box'>{{ _("Reset Password") if frappe.db.get_default('company') else _("Set Password")}}</span>
|
||||
</div>
|
||||
<form id="reset-password">
|
||||
<div class="form-group">
|
||||
<div class="form-group" style="display: none;">
|
||||
<input id="old_password" type="password"
|
||||
class="form-control" placeholder="{{ _("Old Password") }}">
|
||||
</div>
|
||||
|
|
@ -32,8 +32,8 @@
|
|||
<script>
|
||||
|
||||
frappe.ready(function() {
|
||||
if(frappe.utils.get_url_arg("key")) {
|
||||
$("#old_password").parent().toggle(false);
|
||||
if(!frappe.utils.get_url_arg("key")) {
|
||||
$("#old_password").parent().toggle();
|
||||
}
|
||||
|
||||
if(frappe.utils.get_url_arg("password_expired")) {
|
||||
|
|
@ -57,12 +57,10 @@ frappe.ready(function() {
|
|||
}
|
||||
|
||||
if(!args.old_password && !args.key) {
|
||||
frappe.msgprint("{{ _("Old Password Required.") }}");
|
||||
return;
|
||||
frappe.msgprint(__("Old Password Required."));
|
||||
}
|
||||
if(!args.new_password) {
|
||||
frappe.msgprint("{{ _("New Password Required.") }}");
|
||||
return;
|
||||
frappe.msgprint(__("New Password Required."));
|
||||
}
|
||||
frappe.call({
|
||||
type: "POST",
|
||||
|
|
@ -71,19 +69,24 @@ frappe.ready(function() {
|
|||
args: args,
|
||||
statusCode: {
|
||||
401: function() {
|
||||
$('.page-card-head .indicator').removeClass().addClass('indicator red')
|
||||
.text("{{ _('Invalid Password') }}");
|
||||
$(".page-card-head .indicator").removeClass().addClass("indicator red").text(__("Invalid Password"));
|
||||
},
|
||||
410: function({ responseJSON }) {
|
||||
const title = __("Invalid Link");
|
||||
const message = responseJSON.message;
|
||||
$(".page-card-head .indicator").removeClass().addClass("indicator grey").text(title);
|
||||
frappe.msgprint({ title: title, message: message, clear: true });
|
||||
},
|
||||
200: function(r) {
|
||||
$("input").val("");
|
||||
strength_indicator.addClass('hidden');
|
||||
strength_message.addClass('hidden');
|
||||
$('.page-card-head .indicator')
|
||||
.removeClass().addClass('indicator green')
|
||||
.html("{{ _('Password Updated') }}");
|
||||
strength_indicator.addClass("hidden");
|
||||
strength_message.addClass("hidden");
|
||||
$(".page-card-head .indicator")
|
||||
.removeClass().addClass("indicator blue")
|
||||
.html(__("Status Updated"));
|
||||
if(r.message) {
|
||||
frappe.msgprint({
|
||||
message: "{{ _("Password Updated") }}",
|
||||
message: __("Password Updated"),
|
||||
// password is updated successfully
|
||||
// clear any server message
|
||||
clear: true
|
||||
|
|
|
|||
|
|
@ -12,3 +12,19 @@ ga('create', '{{ google_analytics_id }}', 'auto');
|
|||
ga('send', 'pageview');
|
||||
// End Google Analytics
|
||||
{%- endif %}
|
||||
|
||||
{% if enable_view_tracking %}
|
||||
if (navigator.doNotTrack != 1) {
|
||||
frappe.ready(() => {
|
||||
let browser = frappe.utils.get_browser();
|
||||
frappe.call("frappe.website.doctype.web_page_view.web_page_view.make_view_log", {
|
||||
path: location.pathname,
|
||||
referrer: document.referrer,
|
||||
browser: browser.name,
|
||||
version: browser.version,
|
||||
url: location.origin,
|
||||
user_tz: Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
})
|
||||
})
|
||||
}
|
||||
{% endif %}
|
||||
|
|
@ -11,6 +11,7 @@ const buble = require('rollup-plugin-buble');
|
|||
const { terser } = require('rollup-plugin-terser');
|
||||
const vue = require('rollup-plugin-vue');
|
||||
const frappe_html = require('./frappe-html-plugin');
|
||||
const less_loader = require('./less-loader');
|
||||
|
||||
const production = process.env.FRAPPE_ENV === 'production';
|
||||
|
||||
|
|
@ -116,6 +117,7 @@ function get_rollup_options_for_css(output_file, input_files) {
|
|||
// less -> css
|
||||
postcss({
|
||||
extract: output_path,
|
||||
loaders: [less_loader],
|
||||
use: [
|
||||
['less', {
|
||||
// import other less/css files starting from these folders
|
||||
|
|
@ -130,7 +132,8 @@ function get_rollup_options_for_css(output_file, input_files) {
|
|||
path.resolve(bench_path, '**/*.scss'),
|
||||
path.resolve(bench_path, '**/*.css')
|
||||
],
|
||||
minimize: minimize_css
|
||||
minimize: minimize_css,
|
||||
sourceMap: output_file.startsWith('css/') && !production
|
||||
})
|
||||
];
|
||||
|
||||
|
|
|
|||
54
rollup/less-loader.js
Normal file
54
rollup/less-loader.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
const pify = require('pify');
|
||||
const importCwd = require('import-cwd');
|
||||
const path = require('path');
|
||||
|
||||
const getFileName = filepath => path.basename(filepath);
|
||||
|
||||
function loadModule(moduleId) {
|
||||
// Trying to load module normally (relative to plugin directory)
|
||||
try {
|
||||
return require(moduleId);
|
||||
} catch (_) {
|
||||
// Ignore error
|
||||
}
|
||||
|
||||
// Then, trying to load it relative to CWD
|
||||
return importCwd.silent(moduleId);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'less',
|
||||
test: /\.less$/,
|
||||
async process({
|
||||
code
|
||||
}) {
|
||||
const less = loadModule('less');
|
||||
if (!less) {
|
||||
throw new Error('You need to install "less" packages in order to process Less files');
|
||||
}
|
||||
|
||||
let {
|
||||
css,
|
||||
map,
|
||||
imports
|
||||
} = await pify(less.render.bind(less))(code, {
|
||||
...this.options,
|
||||
sourceMap: this.sourceMap && { outputSourceFiles: true },
|
||||
filename: this.id
|
||||
});
|
||||
|
||||
for (const dep of imports) {
|
||||
this.dependencies.add(dep);
|
||||
}
|
||||
|
||||
if (map) {
|
||||
map = JSON.parse(map);
|
||||
map.sources = map.sources.map(source => getFileName(source));
|
||||
}
|
||||
|
||||
return {
|
||||
code: css,
|
||||
map
|
||||
};
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue