fix: Merge branch 'develop' of https://github.com/frappe/frappe into offline-erpnext
This commit is contained in:
commit
44b0049aff
44 changed files with 829 additions and 2835 deletions
63
cypress/integration/depends_on.js
Normal file
63
cypress/integration/depends_on.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
context('Depends On', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
});
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call('frappe.tests.ui_test_helpers.create_doctype', {
|
||||
name: 'Test Depends On',
|
||||
fields: [
|
||||
{
|
||||
"label": "Test Field",
|
||||
"fieldname": "test_field",
|
||||
"fieldtype": "Data",
|
||||
},
|
||||
{
|
||||
"label": "Dependant Field",
|
||||
"fieldname": "dependant_field",
|
||||
"fieldtype": "Data",
|
||||
"mandatory_depends_on": "eval:doc.test_field=='Some Value'",
|
||||
"read_only_depends_on": "eval:doc.test_field=='Some Other Value'",
|
||||
},
|
||||
{
|
||||
"label": "Display Dependant Field",
|
||||
"fieldname": "display_dependant_field",
|
||||
"fieldtype": "Data",
|
||||
'depends_on': "eval:doc.test_field=='Value'"
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should set the field as mandatory depending on other fields value', () => {
|
||||
cy.new_form('Test Depends On');
|
||||
cy.fill_field('test_field', 'Some Value');
|
||||
cy.get('button.primary-action').contains('Save').click();
|
||||
cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('be.visible');
|
||||
cy.get('body').click();
|
||||
cy.fill_field('test_field', 'Random value');
|
||||
cy.get('button.primary-action').contains('Save').click();
|
||||
cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('not.be.visible');
|
||||
});
|
||||
it('should set the field as read only depending on other fields value', () => {
|
||||
cy.new_form('Test Depends On');
|
||||
cy.fill_field('dependant_field', 'Some Value');
|
||||
cy.fill_field('test_field', 'Some Other Value');
|
||||
cy.get('body').click();
|
||||
cy.get('.control-input [data-fieldname="dependant_field"]').should('be.disabled');
|
||||
cy.fill_field('test_field', 'Random Value');
|
||||
cy.get('body').click();
|
||||
cy.get('.control-input [data-fieldname="dependant_field"]').should('not.be.disabled');
|
||||
});
|
||||
it('should display the field depending on other fields value', () => {
|
||||
cy.new_form('Test Depends On');
|
||||
cy.get('.control-input [data-fieldname="display_dependant_field"]').should('not.be.visible');
|
||||
cy.get('.control-input [data-fieldname="test_field"]').clear();
|
||||
cy.fill_field('test_field', 'Value');
|
||||
cy.get('body').click();
|
||||
cy.get('.control-input [data-fieldname="display_dependant_field"]').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
|
@ -9,7 +9,7 @@ from frappe.desk.form import assign_to
|
|||
from frappe.utils.jinja import validate_template
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe.utils.user import get_system_managers
|
||||
from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day
|
||||
from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day, month_diff
|
||||
from frappe.model.document import Document
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.utils.background_jobs import get_jobs
|
||||
|
|
@ -48,7 +48,7 @@ class AutoRepeat(Document):
|
|||
if self.disabled:
|
||||
self.next_schedule_date = None
|
||||
else:
|
||||
self.next_schedule_date = get_next_schedule_date(self.start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day, self.end_date)
|
||||
self.next_schedule_date = get_next_schedule_date(self.start_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day, self.end_date)
|
||||
|
||||
def unlink_if_applicable(self):
|
||||
if self.status == 'Completed' or self.disabled:
|
||||
|
|
@ -107,27 +107,27 @@ class AutoRepeat(Document):
|
|||
end_date = getdate(self.end_date)
|
||||
|
||||
if not self.end_date:
|
||||
start_date = get_next_schedule_date(start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day)
|
||||
next_date = get_next_schedule_date(start_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day)
|
||||
row = {
|
||||
"reference_document": self.reference_document,
|
||||
"frequency": self.frequency,
|
||||
"next_scheduled_date": start_date
|
||||
"next_scheduled_date": next_date
|
||||
}
|
||||
schedule_details.append(row)
|
||||
start_date = get_next_schedule_date(start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day)
|
||||
|
||||
if self.end_date:
|
||||
start_date = get_next_schedule_date(
|
||||
start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day, for_full_schedule=True)
|
||||
while (getdate(start_date) < getdate(end_date)):
|
||||
next_date = get_next_schedule_date(
|
||||
start_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day, for_full_schedule=True)
|
||||
|
||||
while (getdate(next_date) < getdate(end_date)):
|
||||
row = {
|
||||
"reference_document" : self.reference_document,
|
||||
"frequency" : self.frequency,
|
||||
"next_scheduled_date" : start_date
|
||||
"next_scheduled_date" : next_date
|
||||
}
|
||||
schedule_details.append(row)
|
||||
start_date = get_next_schedule_date(
|
||||
start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day, end_date, for_full_schedule=True)
|
||||
next_date = get_next_schedule_date(
|
||||
next_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day, end_date, for_full_schedule=True)
|
||||
|
||||
return schedule_details
|
||||
|
||||
|
|
@ -268,8 +268,12 @@ class AutoRepeat(Document):
|
|||
)
|
||||
|
||||
|
||||
def get_next_schedule_date(start_date, frequency, repeat_on_day, repeat_on_last_day=False, end_date=None, for_full_schedule=False):
|
||||
month_count = month_map.get(frequency)
|
||||
def get_next_schedule_date(schedule_date, frequency, start_date, repeat_on_day=None, repeat_on_last_day=False, end_date=None, for_full_schedule=False):
|
||||
if month_map.get(frequency):
|
||||
month_count = month_map.get(frequency) + month_diff(schedule_date, start_date) - 1
|
||||
else:
|
||||
month_count = 0
|
||||
|
||||
day_count = 0
|
||||
if month_count and repeat_on_last_day:
|
||||
next_date = get_next_date(start_date, month_count, 31)
|
||||
|
|
@ -288,7 +292,9 @@ def get_next_schedule_date(start_date, frequency, repeat_on_day, repeat_on_last_
|
|||
# next schedule date should be after or on current date
|
||||
if not for_full_schedule:
|
||||
while getdate(next_date) < getdate(today()):
|
||||
next_date = get_next_date(next_date, month_count, day_count)
|
||||
if month_count:
|
||||
month_count += month_map.get(frequency)
|
||||
next_date = get_next_date(start_date, month_count, day_count)
|
||||
|
||||
return next_date
|
||||
|
||||
|
|
@ -316,8 +322,7 @@ def create_repeated_entries(data):
|
|||
|
||||
if schedule_date == current_date and not doc.disabled:
|
||||
doc.create_documents()
|
||||
schedule_date = get_next_schedule_date(schedule_date, doc.frequency, doc.repeat_on_day, doc.repeat_on_last_day, doc.end_date)
|
||||
|
||||
schedule_date = get_next_schedule_date(schedule_date, doc.frequency, doc.start_date, doc.repeat_on_day, doc.repeat_on_last_day, doc.end_date)
|
||||
if schedule_date and not doc.disabled:
|
||||
frappe.db.set_value('Auto Repeat', doc.name, 'next_schedule_date', schedule_date)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals, print_function
|
||||
import os, frappe, json, shutil, re, warnings
|
||||
import os, frappe, json, shutil, re, warnings, tempfile
|
||||
from os.path import exists as path_exists, join as join_path, abspath, isdir
|
||||
from distutils.spawn import find_executable
|
||||
from six import iteritems, text_type
|
||||
|
|
@ -12,6 +12,51 @@ from frappe.utils.minify import JavascriptMinify
|
|||
Build the `public` folders and setup languages
|
||||
"""
|
||||
|
||||
|
||||
def symlink(target, link_name, overwrite=False):
|
||||
'''
|
||||
Create a symbolic link named link_name pointing to target.
|
||||
If link_name exists then FileExistsError is raised, unless overwrite=True.
|
||||
When trying to overwrite a directory, IsADirectoryError is raised.
|
||||
|
||||
Source: https://stackoverflow.com/a/55742015/10309266
|
||||
'''
|
||||
|
||||
if not overwrite:
|
||||
os.symlink(target, linkname)
|
||||
return
|
||||
|
||||
# os.replace() may fail if files are on different filesystems
|
||||
link_dir = os.path.dirname(link_name)
|
||||
|
||||
# Create link to target with temporary filename
|
||||
while True:
|
||||
temp_link_name = tempfile.mktemp(dir=link_dir)
|
||||
|
||||
# os.* functions mimic as closely as possible system functions
|
||||
# The POSIX symlink() returns EEXIST if link_name already exists
|
||||
# https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html
|
||||
try:
|
||||
os.symlink(target, temp_link_name)
|
||||
break
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
# Replace link_name with temp_link_name
|
||||
try:
|
||||
# Pre-empt os.replace on a directory with a nicer message
|
||||
if os.path.isdir(link_name):
|
||||
raise IsADirectoryError("Cannot symlink over existing directory: '{}'".format(link_name))
|
||||
try:
|
||||
os.replace(temp_link_name, link_name)
|
||||
except AttributeError:
|
||||
os.renames(temp_link_name, link_name)
|
||||
except:
|
||||
if os.path.islink(temp_link_name):
|
||||
os.remove(temp_link_name)
|
||||
raise
|
||||
|
||||
|
||||
app_paths = None
|
||||
def setup():
|
||||
global app_paths
|
||||
|
|
@ -118,7 +163,7 @@ def make_asset_dirs(make_copy=False, restore=False):
|
|||
else:
|
||||
shutil.rmtree(target)
|
||||
try:
|
||||
os.symlink(source, target)
|
||||
symlink(source, target, overwrite=True)
|
||||
except OSError:
|
||||
print('Cannot link {} to {}'.format(source, target))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
# imports - standard imports
|
||||
import json
|
||||
|
||||
# imports - module imports
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
import frappe
|
||||
|
||||
# imports - frappe module imports
|
||||
from frappe.chat import authenticate
|
||||
from frappe.chat import authenticate
|
||||
from frappe.core.doctype.version.version import get_diff
|
||||
from frappe.chat.doctype.chat_message import chat_message
|
||||
from frappe.chat.doctype.chat_message import chat_message
|
||||
from frappe.chat.util import (
|
||||
safe_json_loads,
|
||||
dictify,
|
||||
|
|
@ -22,13 +19,14 @@ from frappe.chat.util import (
|
|||
|
||||
session = frappe.session
|
||||
|
||||
def is_direct(owner, other, bidirectional = False):
|
||||
|
||||
def is_direct(owner, other, bidirectional=False):
|
||||
def get_room(owner, other):
|
||||
room = frappe.get_all('Chat Room', filters = [
|
||||
['Chat Room', 'type' , 'in', ('Direct', 'Visitor')],
|
||||
['Chat Room', 'owner', '=' , owner],
|
||||
['Chat Room User', 'user' , '=' , other]
|
||||
], distinct = True)
|
||||
room = frappe.get_all('Chat Room', filters=[
|
||||
['Chat Room', 'type', 'in', ('Direct', 'Visitor')],
|
||||
['Chat Room', 'owner', '=', owner],
|
||||
['Chat Room User', 'user', '=', other]
|
||||
], distinct=True)
|
||||
|
||||
return room
|
||||
|
||||
|
|
@ -38,7 +36,8 @@ def is_direct(owner, other, bidirectional = False):
|
|||
|
||||
return exists
|
||||
|
||||
def get_chat_room_user_set(users, filter_ = None):
|
||||
|
||||
def get_chat_room_user_set(users, filter_=None):
|
||||
seen, uset = set(), list()
|
||||
|
||||
for u in users:
|
||||
|
|
@ -48,12 +47,13 @@ def get_chat_room_user_set(users, filter_ = None):
|
|||
|
||||
return uset
|
||||
|
||||
|
||||
class ChatRoom(Document):
|
||||
def validate(self):
|
||||
if self.is_new():
|
||||
users = get_chat_room_user_set(self.users, filter_ = lambda u: u.user != session.user)
|
||||
users = get_chat_room_user_set(self.users, filter_=lambda u: u.user != session.user)
|
||||
self.update(dict(
|
||||
users = users
|
||||
users=users
|
||||
))
|
||||
|
||||
if self.type == "Direct":
|
||||
|
|
@ -63,7 +63,7 @@ class ChatRoom(Document):
|
|||
other = squashify(self.users)
|
||||
|
||||
if self.is_new():
|
||||
if is_direct(self.owner, other.user, bidirectional = True):
|
||||
if is_direct(self.owner, other.user, bidirectional=True):
|
||||
frappe.throw(_('Direct room with {0} already exists.').format(other.user))
|
||||
|
||||
if self.type == "Group" and not self.room_name:
|
||||
|
|
@ -74,40 +74,44 @@ class ChatRoom(Document):
|
|||
before = self.get_doc_before_save()
|
||||
if not before: return
|
||||
|
||||
after = self
|
||||
diff = dictify(get_diff(before, after))
|
||||
after = self
|
||||
diff = dictify(get_diff(before, after))
|
||||
if diff:
|
||||
update = { }
|
||||
update = {}
|
||||
for changed in diff.changed:
|
||||
field, old, new = changed
|
||||
|
||||
if field == 'last_message':
|
||||
new = chat_message.get(new)
|
||||
|
||||
update.update({ field: new })
|
||||
update.update({field: new})
|
||||
|
||||
if diff.added or diff.removed:
|
||||
update.update(dict(users = [u.user for u in self.users]))
|
||||
update.update(dict(users=[u.user for u in self.users]))
|
||||
|
||||
update = dict(room = self.name, data = update)
|
||||
update = dict(room=self.name, data=update)
|
||||
|
||||
frappe.publish_realtime('frappe.chat.room:update', update, room = self.name, after_commit = True)
|
||||
frappe.publish_realtime('frappe.chat.room:update', update, room=self.name,
|
||||
after_commit=True)
|
||||
|
||||
@frappe.whitelist(allow_guest = True)
|
||||
def get(user, rooms = None, fields = None, filters = None):
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get(user=None, token=None, rooms=None, fields=None, filters=None):
|
||||
# There is this horrible bug out here.
|
||||
# Looks like if frappe.call sends optional arguments (not in right order), the argument turns to an empty string.
|
||||
# Looks like if frappe.call sends optional arguments (not in right order),
|
||||
# the argument turns to an empty string.
|
||||
# I'm not even going to think searching for it.
|
||||
# Hence, the hack was get_if_empty (previous assign_if_none)
|
||||
# - Achilles Rasquinha achilles@frappe.io
|
||||
authenticate(user)
|
||||
data = user or token
|
||||
authenticate(data)
|
||||
|
||||
rooms, fields, filters = safe_json_loads(rooms, fields, filters)
|
||||
|
||||
rooms = listify(get_if_empty(rooms, [ ]))
|
||||
fields = listify(get_if_empty(fields, [ ]))
|
||||
rooms = listify(get_if_empty(rooms, []))
|
||||
fields = listify(get_if_empty(fields, []))
|
||||
|
||||
const = [ ] # constraints
|
||||
const = [] # constraints
|
||||
if rooms:
|
||||
const.append(['Chat Room', 'name', 'in', rooms])
|
||||
if filters:
|
||||
|
|
@ -117,24 +121,24 @@ def get(user, rooms = None, fields = None, filters = None):
|
|||
const.append(filters)
|
||||
|
||||
default = ['name', 'type', 'room_name', 'creation', 'owner', 'avatar']
|
||||
handle = ['users', 'last_message']
|
||||
handle = ['users', 'last_message']
|
||||
|
||||
param = [f for f in fields if f not in handle]
|
||||
param = [f for f in fields if f not in handle]
|
||||
|
||||
rooms = frappe.get_all('Chat Room',
|
||||
or_filters = [
|
||||
['Chat Room', 'owner', '=', user],
|
||||
['Chat Room User', 'user', '=', user]
|
||||
],
|
||||
filters = const,
|
||||
fields = param + ['name'] if param else default,
|
||||
distinct = True
|
||||
)
|
||||
rooms = frappe.get_all('Chat Room',
|
||||
or_filters=[
|
||||
['Chat Room', 'owner', '=', frappe.session.user],
|
||||
['Chat Room User', 'user', '=', frappe.session.user]
|
||||
],
|
||||
filters=const,
|
||||
fields=param + ['name'] if param else default,
|
||||
distinct=True
|
||||
)
|
||||
|
||||
if not fields or 'users' in fields:
|
||||
for i, r in enumerate(rooms):
|
||||
droom = frappe.get_doc('Chat Room', r.name)
|
||||
rooms[i]['users'] = [ ]
|
||||
rooms[i]['users'] = []
|
||||
|
||||
for duser in droom.users:
|
||||
rooms[i]['users'].append(duser.user)
|
||||
|
|
@ -151,46 +155,47 @@ def get(user, rooms = None, fields = None, filters = None):
|
|||
|
||||
return rooms
|
||||
|
||||
@frappe.whitelist(allow_guest = True)
|
||||
def create(kind, owner, users = None, name = None):
|
||||
authenticate(owner)
|
||||
|
||||
users = safe_json_loads(users)
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def create(kind, token, users=None, name=None):
|
||||
authenticate(token)
|
||||
|
||||
users = safe_json_loads(users)
|
||||
create = True
|
||||
|
||||
if kind == 'Visitor':
|
||||
room = squashify(frappe.db.sql("""
|
||||
SELECT name
|
||||
FROM `tabChat Room`
|
||||
WHERE owner = "{owner}"
|
||||
""".format(owner = owner), as_dict = True))
|
||||
WHERE owner=%s
|
||||
""", (frappe.session.user), as_dict=True))
|
||||
|
||||
if room:
|
||||
room = frappe.get_doc('Chat Room', room.name)
|
||||
room = frappe.get_doc('Chat Room', room.name)
|
||||
create = False
|
||||
|
||||
if create:
|
||||
room = frappe.new_doc('Chat Room')
|
||||
room.type = kind
|
||||
room.owner = owner
|
||||
room = frappe.new_doc('Chat Room')
|
||||
room.type = kind
|
||||
room.owner = frappe.session.user
|
||||
room.room_name = name
|
||||
|
||||
dusers = [ ]
|
||||
dusers = []
|
||||
|
||||
if kind != 'Visitor':
|
||||
if users:
|
||||
users = listify(users)
|
||||
users = listify(users)
|
||||
for user in users:
|
||||
duser = frappe.new_doc('Chat Room User')
|
||||
duser = frappe.new_doc('Chat Room User')
|
||||
duser.user = user
|
||||
dusers.append(duser)
|
||||
|
||||
room.users = dusers
|
||||
else:
|
||||
dsettings = frappe.get_single('Website Settings')
|
||||
dsettings = frappe.get_single('Website Settings')
|
||||
room.room_name = dsettings.chat_room_name
|
||||
|
||||
users = [user for user in room.users] if hasattr(room, 'users') else [ ]
|
||||
users = [user for user in room.users] if hasattr(room, 'users') else []
|
||||
|
||||
for user in dsettings.chat_operators:
|
||||
if user.user not in users:
|
||||
|
|
@ -199,24 +204,26 @@ def create(kind, owner, users = None, name = None):
|
|||
chat_room_user = {"doctype": "Chat Room User", "user": user.user}
|
||||
room.append('users', chat_room_user)
|
||||
|
||||
room.save(ignore_permissions = True)
|
||||
room.save(ignore_permissions=True)
|
||||
|
||||
room = get(owner, rooms = room.name)
|
||||
users = [room.owner] + [u for u in room.users]
|
||||
room = get(token=token, rooms=room.name)
|
||||
if room:
|
||||
users = [room.owner] + [u for u in room.users]
|
||||
|
||||
for u in users:
|
||||
frappe.publish_realtime('frappe.chat.room:create', room, user = u, after_commit = True)
|
||||
for user in users:
|
||||
frappe.publish_realtime('frappe.chat.room:create', room, user=user, after_commit=True)
|
||||
|
||||
return room
|
||||
|
||||
@frappe.whitelist(allow_guest = True)
|
||||
def history(room, user, fields = None, limit = 10, start = None, end = None):
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def history(room, user, fields=None, limit=10, start=None, end=None):
|
||||
if frappe.get_doc('Chat Room', room).type != 'Visitor':
|
||||
authenticate(user)
|
||||
|
||||
fields = safe_json_loads(fields)
|
||||
|
||||
mess = chat_message.history(room, limit = limit, start = start, end = end)
|
||||
mess = squashify(mess)
|
||||
mess = chat_message.history(room, limit=limit, start=start, end=end)
|
||||
mess = squashify(mess)
|
||||
|
||||
return dictify(mess)
|
||||
return dictify(mess)
|
||||
|
|
|
|||
|
|
@ -351,16 +351,26 @@ def get_contacts(email_strings):
|
|||
email = get_email_without_link(email)
|
||||
contact_name = get_contact_name(email)
|
||||
|
||||
if not contact_name:
|
||||
contact = frappe.get_doc({
|
||||
"doctype": "Contact",
|
||||
"first_name": frappe.unscrub(email.split("@")[0]),
|
||||
})
|
||||
contact.add_email(email_id=email, is_primary=True)
|
||||
contact.insert(ignore_permissions=True)
|
||||
contact_name = contact.name
|
||||
if not contact_name and email:
|
||||
email_parts = email.split("@")
|
||||
first_name = frappe.unscrub(email_parts[0])
|
||||
|
||||
contacts.append(contact_name)
|
||||
try:
|
||||
contact_name = '{0}-{1}'.format(first_name, email_parts[1]) if first_name == 'Contact' else first_name
|
||||
contact = frappe.get_doc({
|
||||
"doctype": "Contact",
|
||||
"first_name": contact_name,
|
||||
"name": contact_name
|
||||
})
|
||||
contact.add_email(email_id=email, is_primary=True)
|
||||
contact.insert(ignore_permissions=True)
|
||||
contact_name = contact.name
|
||||
except Exception:
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(traceback)
|
||||
|
||||
if contact_name:
|
||||
contacts.append(contact_name)
|
||||
|
||||
return contacts
|
||||
|
||||
|
|
|
|||
|
|
@ -238,8 +238,9 @@ def get_recipients_cc_and_bcc(doc, recipients, cc, bcc, fetched_from_email_accou
|
|||
return recipients, cc, bcc
|
||||
|
||||
def remove_administrator_from_email_list(email_list):
|
||||
if 'Administrator' in email_list:
|
||||
email_list.remove('Administrator')
|
||||
administrator_email = list(filter(lambda emails: "Administrator" in emails, email_list))
|
||||
if administrator_email:
|
||||
email_list.remove(administrator_email[0])
|
||||
|
||||
def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None):
|
||||
"""Prepare to make multipart MIME Email
|
||||
|
|
@ -304,27 +305,12 @@ def set_incoming_outgoing_accounts(doc):
|
|||
doc.incoming_email_account = frappe.db.get_value("Email Account",
|
||||
{"append_to": doc.reference_doctype, }, "email_id")
|
||||
|
||||
doc.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"append_to": doc.reference_doctype, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender", "name",
|
||||
"always_use_account_name_as_sender_name"], as_dict=True)
|
||||
|
||||
if not doc.incoming_email_account:
|
||||
doc.incoming_email_account = frappe.db.get_value("Email Account",
|
||||
{"default_incoming": 1, "enable_incoming": 1}, "email_id")
|
||||
|
||||
if not doc.outgoing_email_account:
|
||||
# if from address is not the default email account
|
||||
doc.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"email_id": doc.sender, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender", "name",
|
||||
"send_unsubscribe_message", "always_use_account_name_as_sender_name"], as_dict=True) or frappe._dict()
|
||||
|
||||
if not doc.outgoing_email_account:
|
||||
doc.outgoing_email_account = frappe.db.get_value("Email Account",
|
||||
{"default_outgoing": 1, "enable_outgoing": 1},
|
||||
["email_id", "always_use_account_email_id_as_sender", "name",
|
||||
"send_unsubscribe_message", "always_use_account_name_as_sender_name"],as_dict=True) or frappe._dict()
|
||||
doc.outgoing_email_account = frappe.email.smtp.get_outgoing_email_account(raise_exception_not_set=False,
|
||||
append_to=doc.doctype, sender=doc.sender)
|
||||
|
||||
if doc.sent_or_received == "Sent":
|
||||
doc.db_set("email_account", doc.outgoing_email_account.name)
|
||||
|
|
@ -543,4 +529,4 @@ def mark_email_as_seen(name=None):
|
|||
|
||||
frappe.response["type"] = 'binary'
|
||||
frappe.response["filename"] = "imaginary_pixel.png"
|
||||
frappe.response["filecontent"] = buffered_obj.getvalue()
|
||||
frappe.response["filecontent"] = buffered_obj.getvalue()
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -905,7 +905,7 @@ def validate_fields(meta):
|
|||
|
||||
def check_illegal_depends_on_conditions(docfield):
|
||||
''' assignment operation should not be allowed in the depends on condition.'''
|
||||
depends_on_fields = ["depends_on", "collapsible_depends_on"]
|
||||
depends_on_fields = ["depends_on", "collapsible_depends_on", "mandatory_depends_on", "read_only_depends_on"]
|
||||
for field in depends_on_fields:
|
||||
depends_on = docfield.get(field, None)
|
||||
if depends_on and ("=" in depends_on) and \
|
||||
|
|
|
|||
|
|
@ -96,14 +96,19 @@ class TestDocType(unittest.TestCase):
|
|||
def test_all_depends_on_fields_conditions(self):
|
||||
import re
|
||||
|
||||
docfields = frappe.get_all("DocField", or_filters={
|
||||
docfields = frappe.get_all("DocField",
|
||||
or_filters={
|
||||
"ifnull(depends_on, '')": ("!=", ''),
|
||||
"ifnull(collapsible_depends_on, '')": ("!=", '')
|
||||
}, fields=["parent", "depends_on", "collapsible_depends_on", "fieldname", "fieldtype"])
|
||||
"ifnull(collapsible_depends_on, '')": ("!=", ''),
|
||||
"ifnull(mandatory_depends_on, '')": ("!=", ''),
|
||||
"ifnull(read_only_depends_on, '')": ("!=", '')
|
||||
},
|
||||
fields=["parent", "depends_on", "collapsible_depends_on", "mandatory_depends_on",\
|
||||
"read_only_depends_on", "fieldname", "fieldtype"])
|
||||
|
||||
pattern = """[\w\.:_]+\s*={1}\s*[\w\.@'"]+"""
|
||||
for field in docfields:
|
||||
for depends_on in ["depends_on", "collapsible_depends_on"]:
|
||||
for depends_on in ["depends_on", "collapsible_depends_on", "mandatory_depends_on", "read_only_depends_on"]:
|
||||
condition = field.get(depends_on)
|
||||
if condition:
|
||||
self.assertFalse(re.match(pattern, condition))
|
||||
|
|
|
|||
|
|
@ -197,9 +197,9 @@ class File(Document):
|
|||
def generate_content_hash(self):
|
||||
if self.content_hash or not self.file_url or self.file_url.startswith('http'):
|
||||
return
|
||||
|
||||
file_name = self.file_url.split('/')[-1]
|
||||
try:
|
||||
with open(get_files_path(self.file_name.lstrip("/"), is_private=self.is_private), "rb") as f:
|
||||
with open(get_files_path(file_name, is_private=self.is_private), "rb") as f:
|
||||
self.content_hash = get_content_hash(f.read())
|
||||
except IOError:
|
||||
frappe.msgprint(_("File {0} does not exist").format(self.file_url))
|
||||
|
|
@ -311,39 +311,6 @@ class File(Document):
|
|||
exists = os.path.exists(self.get_full_path())
|
||||
return exists
|
||||
|
||||
def upload(self):
|
||||
# get record details
|
||||
self.attached_to_doctype = frappe.form_dict.doctype
|
||||
self.attached_to_name = frappe.form_dict.docname
|
||||
self.attached_to_field = frappe.form_dict.docfield
|
||||
self.file_url = frappe.form_dict.file_url
|
||||
self.file_name = frappe.form_dict.filename
|
||||
frappe.form_dict.is_private = cint(frappe.form_dict.is_private)
|
||||
|
||||
if not self.file_name and not self.file_url:
|
||||
frappe.msgprint(_("Please select a file or url"),
|
||||
raise_exception=True)
|
||||
|
||||
file_doc = self.get_file_doc()
|
||||
|
||||
comment = {}
|
||||
if self.attached_to_doctype and self.attached_to_name:
|
||||
comment = frappe.get_doc(self.attached_to_doctype, self.attached_to_name).add_comment("Attachment",
|
||||
_ ("added {0}").format("<a href='{file_url}' target='_blank'>{file_name}</a>{icon}".format(**{
|
||||
"icon": ' <i class="fa fa-lock text-warning"></i>' \
|
||||
if file_doc.is_private else "",
|
||||
"file_url": file_doc.file_url.replace("#", "%23") \
|
||||
if file_doc.file_name else file_doc.file_url,
|
||||
"file_name": file_doc.file_name or file_doc.file_url
|
||||
})))
|
||||
|
||||
return {
|
||||
"name": file_doc.name,
|
||||
"file_name": file_doc.file_name,
|
||||
"file_url": file_doc.file_url,
|
||||
"is_private": file_doc.is_private,
|
||||
"comment": comment.as_dict() if comment else {}
|
||||
}
|
||||
|
||||
def get_content(self):
|
||||
"""Returns [`file_name`, `content`] for given file name `fname`"""
|
||||
|
|
|
|||
|
|
@ -368,10 +368,10 @@ class User(Document):
|
|||
(tab, field, '%s', field, '%s'), (new_name, old_name))
|
||||
|
||||
if frappe.db.exists("Chat Profile", old_name):
|
||||
frappe.rename_doc("Chat Profile", old_name, new_name, force=True)
|
||||
frappe.rename_doc("Chat Profile", old_name, new_name, force=True, show_alert=False)
|
||||
|
||||
if frappe.db.exists("Notification Settings", old_name):
|
||||
frappe.rename_doc("Notification Settings", old_name, new_name, force=True)
|
||||
frappe.rename_doc("Notification Settings", old_name, new_name, force=True, show_alert=False)
|
||||
|
||||
# set email
|
||||
frappe.db.sql("""UPDATE `tabUser`
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"creation": "2013-01-10 16:34:01",
|
||||
"description": "Adds a custom field to a DocType",
|
||||
|
|
@ -24,10 +25,8 @@
|
|||
"collapsible_depends_on",
|
||||
"default",
|
||||
"depends_on",
|
||||
"description",
|
||||
"permlevel",
|
||||
"width",
|
||||
"columns",
|
||||
"mandatory_depends_on",
|
||||
"read_only_depends_on",
|
||||
"properties",
|
||||
"reqd",
|
||||
"unique",
|
||||
|
|
@ -46,7 +45,11 @@
|
|||
"report_hide",
|
||||
"search_index",
|
||||
"ignore_xss_filter",
|
||||
"translatable"
|
||||
"translatable",
|
||||
"description",
|
||||
"permlevel",
|
||||
"width",
|
||||
"columns"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -349,11 +352,24 @@
|
|||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Length"
|
||||
},
|
||||
{
|
||||
"fieldname": "mandatory_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Mandatory Depends On",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"fieldname": "read_only_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Read Only Depends On",
|
||||
"length": 255
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"modified": "2019-09-11 12:57:19.268934",
|
||||
"links": [],
|
||||
"modified": "2019-12-12 21:31:08.209996",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Field",
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ docfield_properties = {
|
|||
'report_hide': 'Check',
|
||||
'allow_on_submit': 'Check',
|
||||
'translatable': 'Check',
|
||||
'mandatory_depends_on': 'Data',
|
||||
'read_only_depends_on': 'Data',
|
||||
'depends_on': 'Data',
|
||||
'description': 'Text',
|
||||
'default': 'Text',
|
||||
|
|
@ -68,7 +70,8 @@ docfield_properties = {
|
|||
'columns': 'Int',
|
||||
'remember_last_selected_value': 'Check',
|
||||
'allow_bulk_edit': 'Check',
|
||||
'auto_repeat': 'Link'
|
||||
'auto_repeat': 'Link',
|
||||
'allow_in_quick_entry': 'Check'
|
||||
}
|
||||
|
||||
allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -40,6 +40,8 @@ CREATE TABLE `tabDocField` (
|
|||
`show_preview_popup` int(1) NOT NULL DEFAULT 0,
|
||||
`trigger` varchar(255) DEFAULT NULL,
|
||||
`collapsible_depends_on` text,
|
||||
`mandatory_depends_on` text,
|
||||
`read_only_depends_on` text,
|
||||
`depends_on` text,
|
||||
`permlevel` int(11) NOT NULL DEFAULT 0,
|
||||
`ignore_user_permissions` int(1) NOT NULL DEFAULT 0,
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ class PostgresDatabase(Database):
|
|||
from information_schema.tables
|
||||
where table_catalog='{0}'
|
||||
and table_type = 'BASE TABLE'
|
||||
and table_schema='public'""".format(frappe.conf.db_name))]
|
||||
and table_schema='{1}'""".format(frappe.conf.db_name, frappe.conf.get("db_schema", "public")))]
|
||||
|
||||
def format_date(self, date):
|
||||
if not date:
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ CREATE TABLE "tabDocField" (
|
|||
"show_preview_popup" smallint NOT NULL DEFAULT 0,
|
||||
"trigger" varchar(255) DEFAULT NULL,
|
||||
"collapsible_depends_on" text,
|
||||
"mandatory_depends_on" text,
|
||||
"read_only_depends_on" text,
|
||||
"depends_on" text,
|
||||
"permlevel" bigint NOT NULL DEFAULT 0,
|
||||
"ignore_user_permissions" smallint NOT NULL DEFAULT 0,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:email_account_name",
|
||||
"creation": "2014-09-11 12:04:34.163728",
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
"use_imap",
|
||||
"email_server",
|
||||
"use_ssl",
|
||||
"append_emails_to_sent_folder",
|
||||
"incoming_port",
|
||||
"attachment_limit",
|
||||
"append_to",
|
||||
|
|
@ -37,6 +39,7 @@
|
|||
"enable_outgoing",
|
||||
"smtp_server",
|
||||
"use_tls",
|
||||
"use_ssl_for_outgoing",
|
||||
"smtp_port",
|
||||
"default_outgoing",
|
||||
"always_use_account_email_id_as_sender",
|
||||
|
|
@ -389,10 +392,25 @@
|
|||
"fieldname": "incoming_port",
|
||||
"fieldtype": "Data",
|
||||
"label": "Port"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.domain && doc.enable_outgoing",
|
||||
"fieldname": "append_emails_to_sent_folder",
|
||||
"fieldtype": "Check",
|
||||
"label": "Append Emails to Sent Folder"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.domain && doc.enable_outgoing",
|
||||
"fieldname": "use_ssl_for_outgoing",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use SSL for Outgoing"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-inbox",
|
||||
"modified": "2019-08-31 18:01:15.568831",
|
||||
"links": [],
|
||||
"modified": "2019-12-18 15:56:39.744520",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Account",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import imaplib
|
|||
import re
|
||||
import json
|
||||
import socket
|
||||
import time
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import validate_email_address, cint, get_datetime, DATE_FORMAT, strip, comma_or, sanitize_html
|
||||
|
|
@ -116,7 +117,8 @@ class EmailAccount(Document):
|
|||
fields = [
|
||||
"name as domain", "use_imap", "email_server",
|
||||
"use_ssl", "smtp_server", "use_tls",
|
||||
"smtp_port", "incoming_port"
|
||||
"smtp_port", "incoming_port", "append_emails_to_sent_folder",
|
||||
"use_ssl_for_outgoing"
|
||||
]
|
||||
return frappe.db.get_value("Email Domain", domain[1], fields, as_dict=True)
|
||||
except Exception:
|
||||
|
|
@ -128,11 +130,12 @@ class EmailAccount(Document):
|
|||
if not self.smtp_server:
|
||||
frappe.throw(_("{0} is required").format("SMTP Server"))
|
||||
|
||||
server = SMTPServer(login = getattr(self, "login_id", None) \
|
||||
or self.email_id,
|
||||
server = self.smtp_server,
|
||||
port = cint(self.smtp_port),
|
||||
use_tls = cint(self.use_tls)
|
||||
server = SMTPServer(
|
||||
login = getattr(self, "login_id", None) or self.email_id,
|
||||
server=self.smtp_server,
|
||||
port=cint(self.smtp_port),
|
||||
use_tls=cint(self.use_tls),
|
||||
use_ssl=cint(self.use_ssl_for_outgoing)
|
||||
)
|
||||
if self.password and not self.no_smtp_authentication:
|
||||
server.password = self.get_password()
|
||||
|
|
@ -648,6 +651,24 @@ class EmailAccount(Document):
|
|||
if frappe.db.exists("Email Account", {"enable_automatic_linking": 1, "name": ('!=', self.name)}):
|
||||
frappe.throw(_("Automatic Linking can be activated only for one Email Account."))
|
||||
|
||||
|
||||
def append_email_to_sent_folder(self, message):
|
||||
|
||||
email_server = None
|
||||
try:
|
||||
email_server = self.get_incoming_server(in_receive=True)
|
||||
except Exception:
|
||||
frappe.log_error(title=_("Error while connecting to email account {0}").format(self.name))
|
||||
|
||||
if not email_server:
|
||||
return
|
||||
|
||||
email_server.connect()
|
||||
|
||||
if email_server.imap:
|
||||
email_server.imap.append("Sent", "\\Seen", imaplib.Time2Internaldate(time.time()), message)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None):
|
||||
if not txt: txt = ""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "field:domain_name",
|
||||
"creation": "2016-03-29 10:50:48.848239",
|
||||
"doctype": "DocType",
|
||||
|
|
@ -18,6 +19,8 @@
|
|||
"outgoing_mail_settings",
|
||||
"smtp_server",
|
||||
"use_tls",
|
||||
"use_ssl_for_outgoing",
|
||||
"append_emails_to_sent_folder",
|
||||
"smtp_port"
|
||||
],
|
||||
"fields": [
|
||||
|
|
@ -30,7 +33,7 @@
|
|||
"fieldtype": "Data",
|
||||
"label": "domain name",
|
||||
"read_only": 1,
|
||||
"unique": 0
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "email_id",
|
||||
|
|
@ -106,10 +109,23 @@
|
|||
"fieldname": "incoming_port",
|
||||
"fieldtype": "Data",
|
||||
"label": "Port"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "append_emails_to_sent_folder",
|
||||
"fieldtype": "Check",
|
||||
"label": "Append Emails to Sent Folder"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_ssl_for_outgoing",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use SSL for Outgoing"
|
||||
}
|
||||
],
|
||||
"icon": "icon-inbox",
|
||||
"modified": "2019-10-09 17:56:48.834704",
|
||||
"links": [],
|
||||
"modified": "2019-12-18 15:57:34.445308",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Domain",
|
||||
|
|
@ -127,4 +143,4 @@
|
|||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import validate_email_address ,cint
|
||||
from frappe.utils import validate_email_address ,cint, cstr
|
||||
import imaplib,poplib,smtplib
|
||||
from frappe.email.utils import get_port
|
||||
|
||||
|
|
@ -49,9 +49,16 @@ class EmailDomain(Document):
|
|||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if self.use_tls and not self.smtp_port:
|
||||
self.smtp_port = 587
|
||||
sess = smtplib.SMTP((self.smtp_server or "").encode('utf-8'), cint(self.smtp_port) or None)
|
||||
if self.use_ssl_for_outgoing:
|
||||
if not self.smtp_port:
|
||||
self.smtp_port = 465
|
||||
|
||||
sess = smtplib.SMTP_SSL((self.smtp_server or "").encode('utf-8'),
|
||||
cint(self.smtp_port) or None)
|
||||
else:
|
||||
if self.use_tls and not self.smtp_port:
|
||||
self.smtp_port = 587
|
||||
sess = smtplib.SMTP(cstr(self.smtp_server or ""), cint(self.smtp_port) or None)
|
||||
sess.quit()
|
||||
except Exception:
|
||||
frappe.throw(_("Outgoing email account not correct"))
|
||||
|
|
@ -73,6 +80,8 @@ class EmailDomain(Document):
|
|||
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.save()
|
||||
except Exception as e:
|
||||
frappe.msgprint(email_account.name)
|
||||
|
|
|
|||
|
|
@ -380,7 +380,7 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
|
|||
for update''', email, as_dict=True)[0]
|
||||
|
||||
recipients_list = frappe.db.sql('''select name, recipient, status from
|
||||
`tabEmail Queue Recipient` where parent=%s''',email.name,as_dict=1)
|
||||
`tabEmail Queue Recipient` where parent=%s''', email.name, as_dict=1)
|
||||
|
||||
if frappe.are_emails_muted():
|
||||
frappe.msgprint(_("Emails are muted"))
|
||||
|
|
@ -401,8 +401,16 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
|
|||
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)
|
||||
|
||||
try:
|
||||
message = None
|
||||
|
||||
if not frappe.flags.in_test:
|
||||
if not smtpserver: smtpserver = SMTPServer()
|
||||
if not smtpserver:
|
||||
smtpserver = SMTPServer()
|
||||
|
||||
# to avoid always using default email account for outgoing
|
||||
if getattr(frappe.local, "outgoing_email_account", None):
|
||||
frappe.local.outgoing_email_account = {}
|
||||
|
||||
smtpserver.setup_email_account(email.reference_doctype, sender=email.sender)
|
||||
|
||||
for recipient in recipients_list:
|
||||
|
|
@ -417,8 +425,10 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
|
|||
frappe.db.sql("""update `tabEmail Queue Recipient` set status='Sent', modified=%s where name=%s""",
|
||||
(now_datetime(), recipient.name), auto_commit=auto_commit)
|
||||
|
||||
email_sent_to_any_recipient = any("Sent" == s.status for s in recipients_list)
|
||||
|
||||
#if all are sent set status
|
||||
if any("Sent" == s.status for s in recipients_list):
|
||||
if email_sent_to_any_recipient:
|
||||
frappe.db.sql("""update `tabEmail Queue` set status='Sent', modified=%s where name=%s""",
|
||||
(now_datetime(), email.name), auto_commit=auto_commit)
|
||||
else:
|
||||
|
|
@ -430,6 +440,9 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
|
|||
if email.communication:
|
||||
frappe.get_doc('Communication', email.communication).set_delivery_status(commit=auto_commit)
|
||||
|
||||
if smtpserver.append_emails_to_sent_folder and email_sent_to_any_recipient:
|
||||
smtpserver.email_account.append_email_to_sent_folder(encode(message))
|
||||
|
||||
except (smtplib.SMTPServerDisconnected,
|
||||
smtplib.SMTPConnectError,
|
||||
smtplib.SMTPHeloError,
|
||||
|
|
@ -439,7 +452,7 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
|
|||
|
||||
# bad connection/timeout, retry later
|
||||
|
||||
if any("Sent" == s.status for s in recipients_list):
|
||||
if email_sent_to_any_recipient:
|
||||
frappe.db.sql("""update `tabEmail Queue` set status='Partially Sent', modified=%s where name=%s""",
|
||||
(now_datetime(), email.name), auto_commit=auto_commit)
|
||||
else:
|
||||
|
|
@ -459,7 +472,7 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
|
|||
frappe.db.sql("""update `tabEmail Queue` set status='Not Sent', modified=%s, retry=retry+1 where name=%s""",
|
||||
(now_datetime(), email.name), auto_commit=auto_commit)
|
||||
else:
|
||||
if any("Sent" == s.status for s in recipients_list):
|
||||
if email_sent_to_any_recipient:
|
||||
frappe.db.sql("""update `tabEmail Queue` set status='Partially Errored', error=%s where name=%s""",
|
||||
(text_type(e), email.name), auto_commit=auto_commit)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -480,7 +480,7 @@ class Email:
|
|||
"""Detect chartset."""
|
||||
charset = part.get_content_charset()
|
||||
if not charset:
|
||||
charset = chardet.detect(cstr(part))['encoding']
|
||||
charset = chardet.detect(safe_encode(cstr(part)))['encoding']
|
||||
|
||||
return charset
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import smtplib
|
|||
import email.utils
|
||||
import _socket, sys
|
||||
from frappe import _
|
||||
from frappe.utils import cint, parse_addr
|
||||
from frappe.utils import cint, cstr, parse_addr
|
||||
|
||||
def send(email, append_to=None, retry=1):
|
||||
"""Deprecated: Send the message or add it to Outbox Email"""
|
||||
|
|
@ -52,35 +52,38 @@ def get_outgoing_email_account(raise_exception_not_set=True, append_to=None, sen
|
|||
or frappe.local.outgoing_email_account.get("default")):
|
||||
email_account = None
|
||||
|
||||
if append_to:
|
||||
# append_to is only valid when enable_incoming is checked
|
||||
if sender_email_id:
|
||||
# check if the sender has an email account with enable_outgoing
|
||||
email_account = _get_email_account({"enable_outgoing": 1,
|
||||
"email_id": sender_email_id})
|
||||
|
||||
# in case of multiple Email Accounts with same append_to
|
||||
# narrow it down based on email_id
|
||||
email_account = _get_email_account({
|
||||
if not email_account and append_to:
|
||||
# append_to is only valid when enable_incoming is checked
|
||||
email_accounts = frappe.db.get_values("Email Account", {
|
||||
"enable_outgoing": 1,
|
||||
"enable_incoming": 1,
|
||||
"append_to": append_to,
|
||||
"email_id": sender_email_id
|
||||
})
|
||||
}, cache=True)
|
||||
|
||||
# else find the first Email Account with append_to
|
||||
if not email_account:
|
||||
if email_accounts:
|
||||
_email_account = email_accounts[0]
|
||||
|
||||
else:
|
||||
email_account = _get_email_account({
|
||||
"enable_outgoing": 1,
|
||||
"enable_incoming": 1,
|
||||
"append_to": append_to
|
||||
})
|
||||
|
||||
if not email_account and sender_email_id:
|
||||
# check if the sender has email account with enable_outgoing
|
||||
email_account = _get_email_account({"enable_outgoing": 1, "email_id": sender_email_id})
|
||||
|
||||
if not email_account:
|
||||
# sender don't have the outging email account
|
||||
sender_email_id = None
|
||||
email_account = get_default_outgoing_email_account(raise_exception_not_set=raise_exception_not_set)
|
||||
|
||||
if not email_account and _email_account:
|
||||
# if default email account is not configured then setup first email account based on append to
|
||||
email_account = _email_account
|
||||
|
||||
if not email_account and raise_exception_not_set and cint(frappe.db.get_single_value('System Settings', 'setup_complete')):
|
||||
frappe.throw(_("Please setup default Email Account from Setup > Email > Email Account"),
|
||||
frappe.OutgoingEmailError)
|
||||
|
|
@ -152,16 +155,19 @@ def _get_email_account(filters):
|
|||
return frappe.get_doc("Email Account", name) if name else None
|
||||
|
||||
class SMTPServer:
|
||||
def __init__(self, login=None, password=None, server=None, port=None, use_tls=None, append_to=None):
|
||||
def __init__(self, login=None, password=None, server=None, port=None, use_tls=None, use_ssl=None, append_to=None):
|
||||
# get defaults from mail settings
|
||||
|
||||
self._sess = None
|
||||
self.email_account = None
|
||||
self.server = None
|
||||
self.append_emails_to_sent_folder = None
|
||||
|
||||
if server:
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.use_tls = cint(use_tls)
|
||||
self.use_ssl = cint(use_ssl)
|
||||
self.login = login
|
||||
self.password = password
|
||||
|
||||
|
|
@ -183,6 +189,8 @@ class SMTPServer:
|
|||
self.port = self.email_account.smtp_port
|
||||
self.use_tls = self.email_account.use_tls
|
||||
self.sender = self.email_account.email_id
|
||||
self.use_ssl = self.email_account.use_ssl_for_outgoing
|
||||
self.append_emails_to_sent_folder = self.email_account.append_emails_to_sent_folder
|
||||
self.always_use_account_email_id_as_sender = cint(self.email_account.get("always_use_account_email_id_as_sender"))
|
||||
self.always_use_account_name_as_sender_name = cint(self.email_account.get("always_use_account_name_as_sender_name"))
|
||||
|
||||
|
|
@ -199,11 +207,18 @@ class SMTPServer:
|
|||
raise frappe.OutgoingEmailError(err_msg)
|
||||
|
||||
try:
|
||||
if self.use_tls and not self.port:
|
||||
self.port = 587
|
||||
if self.use_ssl:
|
||||
if not self.port:
|
||||
self.smtp_port = 465
|
||||
|
||||
self._sess = smtplib.SMTP((self.server or "").encode('utf-8'),
|
||||
cint(self.port) or None)
|
||||
self._sess = smtplib.SMTP_SSL((self.server or "").encode('utf-8'),
|
||||
cint(self.port) or None)
|
||||
else:
|
||||
if self.use_tls and not self.port:
|
||||
self.port = 587
|
||||
|
||||
self._sess = smtplib.SMTP(cstr(self.server or ""),
|
||||
cint(self.port) or None)
|
||||
|
||||
if not self._sess:
|
||||
err_msg = _('Could not connect to outgoing email server')
|
||||
|
|
|
|||
|
|
@ -106,13 +106,12 @@ def get_webhook_headers(doc, webhook):
|
|||
|
||||
def get_webhook_data(doc, webhook):
|
||||
data = {}
|
||||
doc = doc.as_dict(convert_dates_to_str=True)
|
||||
|
||||
if webhook.webhook_data:
|
||||
for w in webhook.webhook_data:
|
||||
value = doc.get(w.fieldname)
|
||||
if isinstance(value, datetime.datetime):
|
||||
value = frappe.utils.get_datetime_str(value)
|
||||
data[w.key] = value
|
||||
data = {w.key: doc.get(w.fieldname) for w in webhook.webhook_data}
|
||||
elif webhook.webhook_json:
|
||||
data = frappe.render_template(webhook.webhook_json, get_context(doc))
|
||||
data = json.loads(data)
|
||||
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ class BaseDocument(object):
|
|||
doc["doctype"] = self.doctype
|
||||
for df in self.meta.get_table_fields():
|
||||
children = self.get(df.fieldname) or []
|
||||
doc[df.fieldname] = [d.as_dict(no_nulls=no_nulls) for d in children]
|
||||
doc[df.fieldname] = [d.as_dict(convert_dates_to_str=convert_dates_to_str, no_nulls=no_nulls) for d in children]
|
||||
|
||||
if no_nulls:
|
||||
for k in list(doc):
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ def update_document_title(doctype, docname, title_field=None, old_title=None, ne
|
|||
|
||||
|
||||
@frappe.whitelist()
|
||||
def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=False, ignore_if_exists=False):
|
||||
def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=False, ignore_if_exists=False, show_alert=True):
|
||||
"""
|
||||
Renames a doc(dt, old) to doc(dt, new) and
|
||||
updates all linked fields of type "Link"
|
||||
|
|
@ -99,7 +99,9 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F
|
|||
|
||||
frappe.clear_cache()
|
||||
frappe.enqueue('frappe.utils.global_search.rebuild_for_doctype', doctype=doctype)
|
||||
frappe.msgprint(_('Document renamed from {0} to {1}').format(bold(old), bold(new)), alert=True, indicator='green')
|
||||
|
||||
if show_alert:
|
||||
frappe.msgprint(_('Document renamed from {0} to {1}').format(bold(old), bold(new)), alert=True, indicator='green')
|
||||
|
||||
return new
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,18 @@ def apply_workflow(doc, action):
|
|||
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
def can_cancel_document(doc):
|
||||
doc = frappe.get_doc(frappe.parse_json(doc))
|
||||
workflow = get_workflow(doc.doctype)
|
||||
for state_doc in workflow.states:
|
||||
if state_doc.doc_status == '2':
|
||||
for transition in workflow.transitions:
|
||||
if transition.next_state == state_doc.state:
|
||||
return False
|
||||
return True
|
||||
return True
|
||||
|
||||
def validate_workflow(doc):
|
||||
'''Validate Workflow State and Transition for the current user.
|
||||
|
||||
|
|
|
|||
|
|
@ -718,7 +718,7 @@ frappe.chat.room.create = function (kind, owner, users, name, fn) {
|
|||
|
||||
return new Promise(resolve => {
|
||||
frappe.call("frappe.chat.doctype.chat_room.chat_room.create",
|
||||
{ kind: kind, owner: owner || frappe.session.user, users: users, name: name },
|
||||
{ kind: kind, token: owner || frappe.session.user, users: users, name: name },
|
||||
r => {
|
||||
let room = r.message
|
||||
room = { ...room, creation: new frappe.datetime.datetime(room.creation) }
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ frappe.ui.form.ControlTime = frappe.ui.form.ControlDate.extend({
|
|||
&& ((this.last_value && this.last_value !== this.value)
|
||||
|| (!this.datepicker.selectedDates.length))) {
|
||||
|
||||
var date_obj = frappe.datetime.moment_to_date_obj(moment(value, frappe.sys_defaults['time_format']));
|
||||
let time_format = frappe.sys_defaults.time_format || 'HH:mm:ss';
|
||||
var date_obj = frappe.datetime.moment_to_date_obj(moment(value, time_format));
|
||||
this.datepicker.selectDate(date_obj);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -377,11 +377,11 @@ frappe.ui.form.Timeline = class Timeline {
|
|||
c["edit"] = "";
|
||||
if(c.communication_type=="Comment" && (c.comment_type || "Comment") === "Comment") {
|
||||
if(frappe.model.can_delete("Comment")) {
|
||||
c["delete"] = '<a class="close delete-comment" title="Delete" href="#"><i class="octicon octicon-x"></i></a>';
|
||||
c["delete"] = `<a class="close delete-comment" title="${__('Delete')}" href="#"><i class="octicon octicon-x"></i></a>`;
|
||||
}
|
||||
|
||||
if(frappe.user.name == c.sender || (frappe.user.name == 'Administrator')) {
|
||||
c["edit"] = '<a class="edit-comment text-muted" title="Edit" href="#">Edit</a>';
|
||||
c["edit"] = `<a class="edit-comment text-muted" title="${__('Edit')}" href="#">${__('Edit')}</a>`;
|
||||
}
|
||||
}
|
||||
let communication_date = c.communication_date || c.creation;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ frappe.form.formatters = {
|
|||
},
|
||||
Check: function(value) {
|
||||
if(value) {
|
||||
return '<i class="octicon octicon-check" style="margin-right: 3px;"></i>';
|
||||
return '<i class="fa fa-check" style="margin-right: 3px;"></i>';
|
||||
} else {
|
||||
return '<i class="fa fa-square disabled-check"></i>';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -451,27 +451,27 @@ frappe.ui.form.Layout = Class.extend({
|
|||
// build dependants' dictionary
|
||||
var has_dep = false;
|
||||
|
||||
for(var fkey in this.fields_list) {
|
||||
for (var fkey in this.fields_list) {
|
||||
var f = this.fields_list[fkey];
|
||||
f.dependencies_clear = true;
|
||||
if(f.df.depends_on) {
|
||||
if (f.df.depends_on || f.df.mandatory_depends_on || f.df.read_only_depends_on) {
|
||||
has_dep = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!has_dep)return;
|
||||
if (!has_dep) return;
|
||||
|
||||
// show / hide based on values
|
||||
for(var i=me.fields_list.length-1;i>=0;i--) {
|
||||
for (var i=me.fields_list.length-1;i>=0;i--) {
|
||||
var f = me.fields_list[i];
|
||||
f.guardian_has_value = true;
|
||||
if(f.df.depends_on) {
|
||||
if (f.df.depends_on) {
|
||||
// evaluate guardian
|
||||
|
||||
f.guardian_has_value = this.evaluate_depends_on_value(f.df.depends_on);
|
||||
|
||||
// show / hide
|
||||
if(f.guardian_has_value) {
|
||||
if (f.guardian_has_value) {
|
||||
if(f.df.hidden_due_to_dependency) {
|
||||
f.df.hidden_due_to_dependency = false;
|
||||
f.refresh();
|
||||
|
|
@ -483,10 +483,28 @@ frappe.ui.form.Layout = Class.extend({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (f.df.mandatory_depends_on) {
|
||||
this.set_dependant_property(f.df.mandatory_depends_on, f.df.fieldname, 'reqd');
|
||||
}
|
||||
|
||||
if (f.df.read_only_depends_on) {
|
||||
this.set_dependant_property(f.df.read_only_depends_on, f.df.fieldname, 'read_only');
|
||||
}
|
||||
}
|
||||
|
||||
this.refresh_section_count();
|
||||
},
|
||||
set_dependant_property: function(condition, fieldname, property) {
|
||||
let set_property = this.evaluate_depends_on_value(condition);
|
||||
if (this.frm) {
|
||||
if (set_property) {
|
||||
this.frm.set_df_property(fieldname, property, 1);
|
||||
} else {
|
||||
this.frm.set_df_property(fieldname, property, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
evaluate_depends_on_value: function(expression) {
|
||||
var out = null;
|
||||
var doc = this.doc;
|
||||
|
|
|
|||
|
|
@ -105,7 +105,17 @@ frappe.ui.form.States = Class.extend({
|
|||
});
|
||||
}
|
||||
});
|
||||
this.setup_btn(added);
|
||||
if (!added) {
|
||||
//call function and clear cancel button if Cancel doc state is defined in the workfloe
|
||||
frappe.xcall('frappe.model.workflow.can_cancel_document', {doc: this.frm.doc}).then((can_cancel) => {
|
||||
if (!can_cancel) {
|
||||
this.frm.page.clear_secondary_action();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setup_btn(added);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ frappe.views.ListGroupBy = class ListGroupBy {
|
|||
this.render_dropdown_items(field_count_list, dropdown);
|
||||
this.sidebar.setup_dropdown_search(dropdown, '.group-by-value');
|
||||
} else {
|
||||
dropdown.find('.group-by-loading').hide();
|
||||
dropdown.find('.group-by-loading').html(`${__("No filters found")}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ export default {
|
|||
|
||||
user_section = [
|
||||
{
|
||||
fieldname: 'user_section',
|
||||
fieldtype: 'Section Break',
|
||||
depends_on: doc => doc.setup_for === user_value
|
||||
}
|
||||
|
|
@ -134,6 +135,7 @@ export default {
|
|||
|
||||
global_section = [
|
||||
{
|
||||
fieldname: 'global_section',
|
||||
fieldtype: 'Section Break',
|
||||
depends_on: doc => doc.setup_for === 'Everyone'
|
||||
}
|
||||
|
|
@ -188,8 +190,11 @@ export default {
|
|||
update_global_modules(d) {
|
||||
let blocked_modules = [];
|
||||
for (let category of this.module_categories) {
|
||||
let unchecked_options = d.get_field(`global:${category}`).get_unchecked_options();
|
||||
blocked_modules = blocked_modules.concat(unchecked_options);
|
||||
let field = d.get_field(`global:${category}`);
|
||||
if (field) {
|
||||
let unchecked_options = field.get_unchecked_options();
|
||||
blocked_modules = blocked_modules.concat(unchecked_options);
|
||||
}
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@
|
|||
|
||||
.navbar-form .awesomplete {
|
||||
margin-left: -15px;
|
||||
width: 300px;
|
||||
width: 370px;
|
||||
|
||||
@media (max-width: @screen-md) {
|
||||
width: 280px;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<h3>{{ _('Top Performer') }} 🏆 </h3>
|
||||
<p> {{ frappe.get_fullname(top_performer.user) }}
|
||||
<span class="text-muted">
|
||||
{{ frappe._('gained {0} points').format(frappe.utils.cint(top_performer.energy_points)) }}
|
||||
{{ _('gained {0} points').format(frappe.utils.cint(top_performer.energy_points)) }}
|
||||
</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<h3>{{ _('Top Reviewer') }} ❤️ </h3>
|
||||
<p> {{ frappe.get_fullname(top_reviewer.user) }}
|
||||
<span class="text-muted">
|
||||
{{ frappe._('gave {0} points').format(frappe.utils.cint(top_reviewer.given_points)) }}
|
||||
{{ _('gave {0} points').format(frappe.utils.cint(top_reviewer.given_points)) }}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
|
|
@ -24,9 +24,9 @@
|
|||
<table class='table table-bordered'>
|
||||
<tr>
|
||||
<th> # </th>
|
||||
<th style='width: 70%'>{{ frappe._('User') }}</th>
|
||||
<th style='width: 15%'>{{ frappe._('Energy Points') }}</th>
|
||||
<th style='width: 15%'>{{ frappe._('Points Given') }}</th>
|
||||
<th style='width: 70%'>{{ _('User') }}</th>
|
||||
<th style='width: 15%'>{{ _('Energy Points') }}</th>
|
||||
<th style='width: 15%'>{{ _('Points Given') }}</th>
|
||||
</tr>
|
||||
{% for user in standings %}
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.disabled-check {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.data-field {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
|
|
|
|||
|
|
@ -75,6 +75,23 @@ def create_contact_phone_nos_records():
|
|||
doc.append('phone_nos', {'phone': '123456{}'.format(index)})
|
||||
doc.insert()
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_doctype(name, fields):
|
||||
fields = frappe.parse_json(fields)
|
||||
if frappe.db.exists('DocType', name):
|
||||
return
|
||||
frappe.get_doc({
|
||||
"doctype": "DocType",
|
||||
"module": "Core",
|
||||
"custom": 1,
|
||||
"fields": fields,
|
||||
"permissions": [{
|
||||
"role": "System Manager",
|
||||
"read": 1
|
||||
}],
|
||||
"name": name
|
||||
}).insert()
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_contact_records():
|
||||
if frappe.db.get_all('Contact', {'first_name': 'Test Form Contact 1'}):
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ def get_jenv():
|
|||
set_filters(jenv)
|
||||
|
||||
jenv.globals.update(get_safe_globals())
|
||||
jenv.globals.update(get_jenv_customization('methods'))
|
||||
|
||||
frappe.local.jenv = jenv
|
||||
|
||||
|
|
@ -124,4 +125,27 @@ def set_filters(jenv):
|
|||
jenv.filters["flt"] = flt
|
||||
jenv.filters["abs_url"] = abs_url
|
||||
|
||||
if frappe.flags.in_setup_help: return
|
||||
if frappe.flags.in_setup_help:
|
||||
return
|
||||
|
||||
jenv.filters.update(get_jenv_customization('filters'))
|
||||
|
||||
|
||||
def get_jenv_customization(customization_type):
|
||||
'''Returns a dict with filter/method name as key and definition as value'''
|
||||
|
||||
import frappe
|
||||
|
||||
out = {}
|
||||
if not getattr(frappe.local, "site", None):
|
||||
return out
|
||||
|
||||
values = frappe.get_hooks("jenv", {}).get(customization_type)
|
||||
if not values:
|
||||
return out
|
||||
|
||||
for value in values:
|
||||
fn_name, fn_string = value.split(":")
|
||||
out[fn_name] = frappe.get_attr(fn_string)
|
||||
|
||||
return out
|
||||
|
|
|
|||
|
|
@ -2,14 +2,24 @@
|
|||
# MIT License. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pdfkit, os, frappe
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
from distutils.version import LooseVersion
|
||||
from frappe.utils import scrub_urls, get_wkhtmltopdf_version
|
||||
from frappe import _
|
||||
import six, re, io
|
||||
|
||||
import pdfkit
|
||||
import six
|
||||
from bs4 import BeautifulSoup
|
||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import get_wkhtmltopdf_version, scrub_urls
|
||||
|
||||
PDF_CONTENT_ERRORS = ["ContentNotFoundError", "ContentOperationNotPermittedError",
|
||||
"UnknownContentError", "RemoteHostClosedError"]
|
||||
|
||||
|
||||
def get_pdf(html, options=None, output=None):
|
||||
html = scrub_urls(html)
|
||||
html, options = prepare_options(html, options)
|
||||
|
|
@ -30,20 +40,14 @@ def get_pdf(html, options=None, output=None):
|
|||
# https://pythonhosted.org/PyPDF2/PdfFileReader.html
|
||||
# create in-memory binary streams from filedata and create a PdfFileReader object
|
||||
reader = PdfFileReader(io.BytesIO(filedata))
|
||||
|
||||
except IOError as e:
|
||||
if ("ContentNotFoundError" in e.message
|
||||
or "ContentOperationNotPermittedError" in e.message
|
||||
or "UnknownContentError" in e.message
|
||||
or "RemoteHostClosedError" in e.message):
|
||||
except OSError as e:
|
||||
if any([error in str(e) for error in PDF_CONTENT_ERRORS]):
|
||||
if not filedata:
|
||||
frappe.throw(_("PDF generation failed because of broken image links"))
|
||||
|
||||
# allow pdfs with missing images if file got created
|
||||
if filedata:
|
||||
if output: # output is a PdfFileWriter object
|
||||
output.appendPagesFromReader(reader)
|
||||
|
||||
else:
|
||||
frappe.throw(_("PDF generation failed because of broken image links"))
|
||||
if output: # output is a PdfFileWriter object
|
||||
output.appendPagesFromReader(reader)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
|
@ -66,6 +70,7 @@ def get_pdf(html, options=None, output=None):
|
|||
|
||||
return filedata
|
||||
|
||||
|
||||
def get_file_data_from_writer(writer_obj):
|
||||
|
||||
# https://docs.python.org/3/library/io.html
|
||||
|
|
@ -112,6 +117,7 @@ def prepare_options(html, options):
|
|||
|
||||
return html, options
|
||||
|
||||
|
||||
def read_options_from_html(html):
|
||||
options = {}
|
||||
soup = BeautifulSoup(html, "html5lib")
|
||||
|
|
@ -132,6 +138,7 @@ def read_options_from_html(html):
|
|||
|
||||
return soup.prettify(), options
|
||||
|
||||
|
||||
def prepare_header_footer(soup):
|
||||
options = {}
|
||||
|
||||
|
|
@ -174,6 +181,7 @@ def prepare_header_footer(soup):
|
|||
|
||||
return options
|
||||
|
||||
|
||||
def cleanup(fname, options):
|
||||
if os.path.exists(fname):
|
||||
os.remove(fname)
|
||||
|
|
@ -182,6 +190,7 @@ def cleanup(fname, options):
|
|||
if options.get(key) and os.path.exists(options[key]):
|
||||
os.remove(options[key])
|
||||
|
||||
|
||||
def toggle_visible_pdf(soup):
|
||||
for tag in soup.find_all(attrs={"class": "visible-pdf"}):
|
||||
# remove visible-pdf class to unhide
|
||||
|
|
|
|||
|
|
@ -48,11 +48,9 @@ def get_safe_globals():
|
|||
# make available limited methods of frappe
|
||||
json=json,
|
||||
dict=dict,
|
||||
_dict=frappe._dict,
|
||||
frappe=frappe._dict(
|
||||
_=frappe._,
|
||||
_dict=frappe._dict,
|
||||
flags=frappe.flags,
|
||||
|
||||
format=frappe.format_value,
|
||||
format_value=frappe.format_value,
|
||||
date_format=date_format,
|
||||
|
|
|
|||
|
|
@ -34,8 +34,7 @@ passlib==1.7.1
|
|||
pdfkit==0.6.1
|
||||
Pillow==6.2.1
|
||||
premailer==3.6.1
|
||||
psycopg2-binary==2.7.5
|
||||
psycopg2==2.7.5
|
||||
psycopg2-binary==2.8.4
|
||||
pyasn1==0.4.7
|
||||
Pygments==2.2.0
|
||||
PyJWT==1.7.1
|
||||
|
|
@ -64,4 +63,4 @@ urllib3==1.25.7
|
|||
watchdog==0.8.0
|
||||
Werkzeug==0.16.0
|
||||
xlrd==1.2.0
|
||||
zxcvbn-python==4.4.24
|
||||
zxcvbn-python==4.4.24
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue