Conflicts: frappe/__version__.py frappe/core/doctype/communication/communication.json frappe/hooks.py frappe/model/document.py frappe/public/css/form.css frappe/public/less/form.less requirements.txt setup.pyversion-14
@@ -241,7 +241,7 @@ def msgprint(msg, small=0, raise_exception=0, as_table=False): | |||
msg = '<table border="1px" style="border-collapse: collapse" cellpadding="2px">' + ''.join(['<tr>'+''.join(['<td>%s</td>' % c for c in r])+'</tr>' for r in msg]) + '</table>' | |||
if flags.print_messages: | |||
print "Message: " + repr(msg) | |||
print "Message: " + repr(msg).encode("utf-8") | |||
message_log.append((small and '__small:' or '')+cstr(msg or '')) | |||
_raise_exception() | |||
@@ -430,6 +430,9 @@ def has_website_permission(doctype, ptype="read", doc=None, user=None, verbose=F | |||
hooks = (get_hooks("has_website_permission") or {}).get(doctype, []) | |||
if hooks: | |||
if isinstance(doc, basestring): | |||
doc = get_doc(doctype, doc) | |||
for method in hooks: | |||
result = call(get_attr(method), doc=doc, ptype=ptype, user=user, verbose=verbose) | |||
# if even a single permission check is Falsy | |||
@@ -1,2 +1,2 @@ | |||
from __future__ import unicode_literals | |||
__version__ = "6.0.0-wip" | |||
__version__ = "6.0.0" |
@@ -23,7 +23,7 @@ class HTTPRequest: | |||
self.domain = self.domain[4:] | |||
if frappe.get_request_header('X-Forwarded-For'): | |||
frappe.local.request_ip = frappe.get_request_header('X-Forwarded-For') | |||
frappe.local.request_ip = (frappe.get_request_header('X-Forwarded-For').split(",")[0]).strip() | |||
elif frappe.get_request_header('REMOTE_ADDR'): | |||
frappe.local.request_ip = frappe.get_request_header('REMOTE_ADDR') | |||
@@ -0,0 +1 @@ | |||
- Moved Backup Manager and Social Login Keys to the new **Integrations** module |
@@ -72,4 +72,10 @@ def get_data(): | |||
"type": "module", | |||
"system_manager": 1 | |||
}, | |||
"Integrations": { | |||
"color": "#36414C", | |||
"icon": "octicon octicon-plug", | |||
"type": "module", | |||
"system_manager": 1 | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
from __future__ import unicode_literals | |||
from frappe import _ | |||
def get_data(): | |||
return [ | |||
{ | |||
"label": _("Documents"), | |||
"icon": "icon-star", | |||
"items": [ | |||
{ | |||
"type": "doctype", | |||
"name": "Social Login Keys", | |||
"description": _("Enter keys to enable login via Facebook, Google, GitHub."), | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Backup Manager", | |||
"description": _("Manage cloud backups on Dropbox"), | |||
"hide_count": True | |||
} | |||
] | |||
} | |||
] |
@@ -208,12 +208,6 @@ def get_data(): | |||
"description": _("Install Applications."), | |||
"icon": "icon-download" | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Backup Manager", | |||
"description": _("Manage cloud backups on Dropbox"), | |||
"hide_count": True | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Scheduler Log", | |||
@@ -77,11 +77,6 @@ def get_data(): | |||
"type": "doctype", | |||
"name": "Website Theme", | |||
"description": _("List of themes for Website."), | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "Social Login Keys", | |||
"description": _("Enter keys to enable login via Facebook, Google, GitHub."), | |||
} | |||
] | |||
}, | |||
@@ -1,251 +1,267 @@ | |||
{ | |||
"allow_import": 1, | |||
"autoname": "naming_series:", | |||
"creation": "2013-01-29 10:47:14", | |||
"description": "Keep a track of all communications", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "Setup", | |||
"allow_import": 1, | |||
"autoname": "naming_series:", | |||
"creation": "2013-01-29 10:47:14", | |||
"description": "Keep a track of all communications", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "Setup", | |||
"fields": [ | |||
{ | |||
"default": "COMM-", | |||
"fieldname": "naming_series", | |||
"fieldtype": "Select", | |||
"hidden": 1, | |||
"label": "Series", | |||
"options": "COMM-", | |||
"default": "COMM-", | |||
"fieldname": "naming_series", | |||
"fieldtype": "Select", | |||
"hidden": 1, | |||
"label": "Series", | |||
"options": "COMM-", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "sent_or_received", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Sent or Received", | |||
"options": "Sent\nReceived", | |||
"permlevel": 0, | |||
}, | |||
{ | |||
"fieldname": "sent_or_received", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Sent or Received", | |||
"options": "Sent\nReceived", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "status", | |||
"fieldtype": "Select", | |||
"label": "Status", | |||
"options": "Open\nReplied\nArchived", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "status", | |||
"fieldtype": "Select", | |||
"label": "Status", | |||
"options": "Open\nReplied\nArchived", | |||
"permlevel": 0, | |||
"description": "Integrations can use this field to set email delivery status", | |||
"fieldname": "delivery_status", | |||
"fieldtype": "Select", | |||
"hidden": 1, | |||
"label": "Delivery Status", | |||
"options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "subject", | |||
"fieldtype": "Data", | |||
"in_list_view": 0, | |||
"label": "Subject", | |||
"permlevel": 0, | |||
"fieldname": "subject", | |||
"fieldtype": "Data", | |||
"in_list_view": 0, | |||
"label": "Subject", | |||
"permlevel": 0, | |||
"reqd": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "column_break_5", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0, | |||
"fieldname": "column_break_5", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "reference_doctype", | |||
"fieldtype": "Link", | |||
"label": "Reference DocType", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"fieldname": "reference_doctype", | |||
"fieldtype": "Link", | |||
"label": "Reference DocType", | |||
"options": "DocType", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "reference_name", | |||
"fieldtype": "Dynamic Link", | |||
"label": "Reference Name", | |||
"options": "reference_doctype", | |||
"permlevel": 0, | |||
"fieldname": "reference_name", | |||
"fieldtype": "Dynamic Link", | |||
"label": "Reference Name", | |||
"options": "reference_doctype", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "section_break_8", | |||
"fieldtype": "Section Break", | |||
"permlevel": 0, | |||
"fieldname": "section_break_8", | |||
"fieldtype": "Section Break", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "content", | |||
"fieldtype": "Text Editor", | |||
"label": "Content", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"fieldname": "content", | |||
"fieldtype": "Text Editor", | |||
"label": "Content", | |||
"permlevel": 0, | |||
"reqd": 0, | |||
"width": "400" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "additional_info", | |||
"fieldtype": "Section Break", | |||
"label": "Additional Info", | |||
"fieldname": "additional_info", | |||
"fieldtype": "Section Break", | |||
"label": "Additional Info", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "recipients", | |||
"fieldtype": "Data", | |||
"label": "Recipients", | |||
"fieldname": "recipients", | |||
"fieldtype": "Data", | |||
"label": "Recipients", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "sender", | |||
"fieldtype": "Data", | |||
"label": "Sender", | |||
"fieldname": "phone_no", | |||
"fieldtype": "Data", | |||
"label": "Phone No.", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "sender_full_name", | |||
"fieldtype": "Data", | |||
"label": "Sender Full Name", | |||
"permlevel": 0, | |||
"fieldname": "communication_medium", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Communication Medium", | |||
"options": "\nChat\nPhone\nEmail\nSMS\nVisit\nOther", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "column_break_14", | |||
"fieldtype": "Column Break", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
}, | |||
{ | |||
"fieldname": "communication_medium", | |||
"fieldtype": "Select", | |||
"in_list_view": 1, | |||
"label": "Communication Medium", | |||
"options": "\nChat\nPhone\nEmail\nSMS\nVisit\nOther", | |||
"fieldname": "sender", | |||
"fieldtype": "Data", | |||
"label": "Sender", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "phone_no", | |||
"fieldtype": "Data", | |||
"label": "Phone No.", | |||
"permlevel": 0 | |||
}, | |||
"fieldname": "sender_full_name", | |||
"fieldtype": "Data", | |||
"label": "Sender Full Name", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"fieldname": "section_break2", | |||
"fieldtype": "Section Break", | |||
"options": "simple", | |||
"fieldname": "section_break2", | |||
"fieldtype": "Section Break", | |||
"options": "simple", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "column_break4", | |||
"fieldtype": "Column Break", | |||
"label": "By", | |||
"fieldname": "column_break4", | |||
"fieldtype": "Column Break", | |||
"label": "By", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "email_account", | |||
"fieldtype": "Link", | |||
"label": "Email Account", | |||
"options": "Email Account", | |||
"permlevel": 0, | |||
"fieldname": "email_account", | |||
"fieldtype": "Link", | |||
"label": "Email Account", | |||
"options": "Email Account", | |||
"permlevel": 0, | |||
"precision": "" | |||
}, | |||
{ | |||
"default": "__user", | |||
"fieldname": "user", | |||
"fieldtype": "Link", | |||
"ignore_user_permissions": 1, | |||
"label": "User", | |||
"options": "User", | |||
"permlevel": 0, | |||
}, | |||
{ | |||
"default": "__user", | |||
"fieldname": "user", | |||
"fieldtype": "Link", | |||
"ignore_user_permissions": 1, | |||
"label": "User", | |||
"options": "User", | |||
"permlevel": 0, | |||
"read_only": 1 | |||
}, | |||
}, | |||
{ | |||
"fieldname": "column_break5", | |||
"fieldtype": "Column Break", | |||
"label": "On", | |||
"fieldname": "column_break5", | |||
"fieldtype": "Column Break", | |||
"label": "On", | |||
"permlevel": 0 | |||
}, | |||
}, | |||
{ | |||
"default": "Today", | |||
"fieldname": "communication_date", | |||
"fieldtype": "Datetime", | |||
"label": "Date", | |||
"default": "Today", | |||
"fieldname": "communication_date", | |||
"fieldtype": "Datetime", | |||
"label": "Date", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "_user_tags", | |||
"fieldtype": "Data", | |||
"hidden": 1, | |||
"label": "User Tags", | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
}, | |||
{ | |||
"fieldname": "_user_tags", | |||
"fieldtype": "Data", | |||
"hidden": 1, | |||
"label": "User Tags", | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 1 | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "unread_notification_sent", | |||
"fieldtype": "Check", | |||
"label": "Unread Notification Sent", | |||
"permlevel": 0, | |||
"precision": "", | |||
}, | |||
{ | |||
"default": "0", | |||
"fieldname": "unread_notification_sent", | |||
"fieldtype": "Check", | |||
"label": "Unread Notification Sent", | |||
"permlevel": 0, | |||
"precision": "", | |||
"read_only": 1 | |||
} | |||
], | |||
"icon": "icon-comment", | |||
"idx": 1, | |||
"in_dialog": 0, | |||
"issingle": 0, | |||
"modified": "2015-07-28 16:18:11.664740", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Communication", | |||
"owner": "Administrator", | |||
], | |||
"icon": "icon-comment", | |||
"idx": 1, | |||
"in_dialog": 0, | |||
"issingle": 0, | |||
"modified": "2015-07-28 17:18:11.664740", | |||
"modified_by": "Administrator", | |||
"module": "Core", | |||
"name": "Communication", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
"apply_user_permissions": 1, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "Support Team", | |||
"share": 1, | |||
"submit": 0, | |||
"amend": 0, | |||
"apply_user_permissions": 1, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "Support Team", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
{ | |||
"amend": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "Sales Manager", | |||
"share": 1, | |||
"submit": 0, | |||
}, | |||
{ | |||
"amend": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "Sales Manager", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
{ | |||
"amend": 0, | |||
"apply_user_permissions": 1, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "Sales User", | |||
"share": 1, | |||
"submit": 0, | |||
}, | |||
{ | |||
"amend": 0, | |||
"apply_user_permissions": 1, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "Sales User", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
}, | |||
{ | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"submit": 0, | |||
}, | |||
{ | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
} | |||
], | |||
"search_fields": "subject", | |||
], | |||
"search_fields": "subject", | |||
"title_field": "subject" | |||
} | |||
} |
@@ -135,6 +135,9 @@ class CustomizeForm(Document): | |||
and cint(df.get("precision")) > cint(meta_df[0].get("precision")): | |||
update_db = True | |||
elif property == "unique": | |||
update_db = True | |||
self.make_property_setter(property=property, value=df.get(property), | |||
property_type=self.docfield_properties[property], fieldname=df.fieldname) | |||
@@ -113,7 +113,7 @@ def get_comments(dt, dn, limit=100): | |||
communications = frappe.db.sql("""select name, | |||
content as comment, sender as comment_by, creation, | |||
communication_medium as comment_type, subject, | |||
communication_medium as comment_type, subject, delivery_status, | |||
"Communication" as doctype | |||
from tabCommunication | |||
where reference_doctype=%s and reference_name=%s | |||
@@ -95,8 +95,7 @@ def clear_notifications(user="*"): | |||
frappe.cache().delete_keys("notification_count:") | |||
else: | |||
# delete count for user | |||
for key in frappe.cache().get_keys("notification_count:"): | |||
frappe.cache().hdel(key, user) | |||
frappe.cache().hdel_keys("notification_count:", user) | |||
def delete_notification_count_for(doctype): | |||
frappe.cache().delete_key("notification_count:" + doctype) | |||
@@ -60,6 +60,9 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc | |||
if reference_doctype and reference_name: | |||
unsubscribed = [d.email for d in frappe.db.get_all("Email Unsubscribe", "email", | |||
{"reference_doctype": reference_doctype, "reference_name": reference_name})] | |||
unsubscribed += [d.email for d in frappe.db.get_all("Email Unsubscribe", "email", | |||
{"global_unsubscribe": 1})] | |||
else: | |||
unsubscribed = [] | |||
@@ -159,14 +162,19 @@ def unsubscribe(doctype, name, email): | |||
if not verify_request(): | |||
return | |||
frappe.get_doc({ | |||
"doctype": "Email Unsubscribe", | |||
"email": email, | |||
"reference_doctype": doctype, | |||
"reference_name": name | |||
}).insert(ignore_permissions=True) | |||
try: | |||
frappe.get_doc({ | |||
"doctype": "Email Unsubscribe", | |||
"email": email, | |||
"reference_doctype": doctype, | |||
"reference_name": name | |||
}).insert(ignore_permissions=True) | |||
except frappe.DuplicateEntryError: | |||
frappe.db.rollback() | |||
frappe.db.commit() | |||
else: | |||
frappe.db.commit() | |||
return_unsubscribed_page(email, doctype, name) | |||
@@ -25,7 +25,8 @@ | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0 | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
@@ -43,9 +44,10 @@ | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0 | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
@@ -63,9 +65,30 @@ | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "global_unsubscribe", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 1, | |||
"label": "Global Unsubscribe", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0 | |||
"set_only_once": 0, | |||
"unique": 0 | |||
} | |||
], | |||
"hide_heading": 0, | |||
@@ -75,7 +98,7 @@ | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"modified": "2015-03-18 09:41:20.216319", | |||
"modified": "2015-08-05 06:02:12.805282", | |||
"modified_by": "Administrator", | |||
"module": "Email", | |||
"name": "Email Unsubscribe", | |||
@@ -90,6 +113,7 @@ | |||
"delete": 1, | |||
"email": 1, | |||
"export": 1, | |||
"if_owner": 0, | |||
"import": 0, | |||
"permlevel": 0, | |||
"print": 1, | |||
@@ -8,6 +8,32 @@ from frappe.model.document import Document | |||
from frappe import _ | |||
class EmailUnsubscribe(Document): | |||
def validate(self): | |||
if not self.global_unsubscribe and not (self.reference_doctype and self.reference_name): | |||
frappe.throw(_("Reference DocType and Reference Name are required"), frappe.MandatoryError) | |||
if not self.global_unsubscribe and frappe.db.get_value(self.doctype, self.name, "global_unsubscribe"): | |||
frappe.throw(_("Delete this record to allow sending to this email address")) | |||
if self.global_unsubscribe: | |||
if frappe.get_all("Email Unsubscribe", | |||
filters={"email": self.email, "global_unsubscribe": 1, "name": ["!=", self.name]}): | |||
frappe.throw(_("{0} already unsubscribed").format(self.email), frappe.DuplicateEntryError) | |||
else: | |||
if frappe.get_all("Email Unsubscribe", | |||
filters={ | |||
"email": self.email, | |||
"reference_doctype": self.reference_doctype, | |||
"reference_name": self.reference_name, | |||
"name": ["!=", self.name] | |||
}): | |||
frappe.throw(_("{0} already unsubscribed for {1} {2}").format( | |||
self.email, self.reference_doctype, self.reference_name), | |||
frappe.DuplicateEntryError) | |||
def on_update(self): | |||
doc = frappe.get_doc(self.reference_doctype, self.reference_name) | |||
doc.add_comment("Label", _("Left this conversation"), comment_by=self.email) | |||
if self.reference_doctype and self.reference_name: | |||
doc = frappe.get_doc(self.reference_doctype, self.reference_name) | |||
doc.add_comment("Label", _("Left this conversation"), comment_by=self.email) | |||
@@ -192,7 +192,7 @@ class EMail: | |||
"Date": email.utils.formatdate(), | |||
"Reply-To": self.reply_to.encode("utf-8") if self.reply_to else None, | |||
"CC": ', '.join(self.cc).encode("utf-8") if self.cc else None, | |||
b'X-Frappe-Site': get_url().encode('utf-8') | |||
b'X-Frappe-Site': get_url().encode('utf-8'), | |||
} | |||
# reset headers as values may be changed. | |||
@@ -201,6 +201,10 @@ class EMail: | |||
del self.msg_root[key] | |||
self.msg_root[key] = val | |||
# call hook to enable apps to modify msg_root before sending | |||
for hook in frappe.get_hooks("make_email_body_message"): | |||
frappe.get_attr(hook)(self) | |||
def as_string(self): | |||
"""validate, build message and convert to string""" | |||
self.validate() | |||
@@ -90,11 +90,11 @@ def execute_cmd(cmd, async=False): | |||
if frappe.session['user'] == 'Guest': | |||
if (method not in frappe.guest_methods): | |||
frappe.msgprint(_("Not permitted")) | |||
raise frappe.PermissionError('Not Allowed, %s' % str(method)) | |||
raise frappe.PermissionError('Not Allowed, {0}'.format(method)) | |||
else: | |||
if not method in frappe.whitelisted: | |||
frappe.msgprint(_("Not permitted")) | |||
raise frappe.PermissionError('Not Allowed, %s' % str(method)) | |||
raise frappe.PermissionError('Not Allowed, {0}'.format(method)) | |||
ret = frappe.call(method, **frappe.form_dict) | |||
@@ -26,7 +26,7 @@ to ERPNext. | |||
""" | |||
app_icon = "octicon octicon-circuit-board" | |||
app_version = "6.0.0-wip" | |||
app_version = "6.0.0" | |||
app_color = "orange" | |||
github_link = "https://github.com/frappe/frappe" | |||
@@ -0,0 +1,221 @@ | |||
{ | |||
"allow_copy": 0, | |||
"allow_import": 0, | |||
"allow_rename": 0, | |||
"creation": "2014-03-04 08:29:52", | |||
"custom": 0, | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "System", | |||
"fields": [ | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "facebook", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Facebook", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "facebook_client_id", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Facebook Client ID", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "facebook_client_secret", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Facebook Client Secret", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "google", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Google", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "google_client_id", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Google Client ID", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "google_client_secret", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "Google Client Secret", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "github", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "GitHub", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "github_client_id", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "GitHub Client ID", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_on_submit": 0, | |||
"fieldname": "github_client_secret", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"in_filter": 0, | |||
"in_list_view": 0, | |||
"label": "GitHub Client Secret", | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"read_only": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"unique": 0 | |||
} | |||
], | |||
"hide_heading": 0, | |||
"hide_toolbar": 0, | |||
"icon": "icon-signin", | |||
"idx": 1, | |||
"in_create": 0, | |||
"in_dialog": 0, | |||
"is_submittable": 0, | |||
"issingle": 1, | |||
"istable": 0, | |||
"modified": "2015-08-05 08:14:52.667728", | |||
"modified_by": "Administrator", | |||
"module": "Integrations", | |||
"name": "Social Login Keys", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
"apply_user_permissions": 0, | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 0, | |||
"email": 0, | |||
"export": 0, | |||
"if_owner": 0, | |||
"import": 0, | |||
"permlevel": 0, | |||
"print": 0, | |||
"read": 1, | |||
"report": 0, | |||
"role": "System Manager", | |||
"set_user_permissions": 0, | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
} | |||
], | |||
"read_only": 0, | |||
"read_only_onload": 0 | |||
} |
@@ -140,6 +140,7 @@ class DbTable: | |||
""" | |||
fl = frappe.db.sql("SELECT * FROM tabDocField WHERE parent = %s", self.doctype, as_dict = 1) | |||
precisions = {} | |||
uniques = {} | |||
if not frappe.flags.in_install: | |||
custom_fl = frappe.db.sql("""\ | |||
@@ -152,10 +153,15 @@ class DbTable: | |||
filters={"doc_type": self.doctype, "doctype_or_field": "DocField", "property": "precision"}): | |||
precisions[ps.field_name] = ps.value | |||
# apply unique from property setters | |||
for ps in frappe.get_all("Property Setter", fields=["field_name", "value"], | |||
filters={"doc_type": self.doctype, "doctype_or_field": "DocField", "property": "unique"}): | |||
uniques[ps.field_name] = cint(ps.value) | |||
for f in fl: | |||
self.columns[f['fieldname']] = DbColumn(self, f['fieldname'], | |||
f['fieldtype'], f.get('length'), f.get('default'), f.get('search_index'), | |||
f.get('options'), f.get('unique'), precisions.get(f['fieldname']) or f.get('precision')) | |||
f.get('options'), uniques.get(f["fieldname"], f.get('unique')), precisions.get(f['fieldname']) or f.get('precision')) | |||
def get_columns_from_db(self): | |||
self.show_columns = frappe.db.sql("desc `%s`" % self.name) | |||
@@ -294,18 +300,11 @@ class DbColumn: | |||
return | |||
# type | |||
if (current_def['type'] != column_def) or (self.unique and not current_def['unique'] \ | |||
and column_def not in ('text', 'longtext')): | |||
if (current_def['type'] != column_def) or \ | |||
((self.unique and not current_def['unique']) and column_def not in ('text', 'longtext')): | |||
self.table.change_type.append(self) | |||
else: | |||
# index | |||
if current_def['index'] and not self.set_index and not self.unique: | |||
self.table.drop_index.append(self) | |||
if (not current_def['index'] and self.set_index) and not (column_def in ('text', 'longtext')): | |||
self.table.add_index.append(self) | |||
# default | |||
if (self.default_changed(current_def) \ | |||
and (self.default not in default_shortcuts) \ | |||
@@ -313,6 +312,15 @@ class DbColumn: | |||
and not (column_def in ['text','longtext'])): | |||
self.table.set_default.append(self) | |||
# index should be applied or dropped irrespective of type change | |||
if ( (current_def['index'] and not self.set_index and not self.unique) | |||
or (current_def['unique'] and not self.unique) ): | |||
# to drop unique you have to drop index | |||
self.table.drop_index.append(self) | |||
elif (not current_def['index'] and self.set_index) and not (column_def in ('text', 'longtext')): | |||
self.table.add_index.append(self) | |||
def default_changed(self, current_def): | |||
if "decimal" in current_def['type']: | |||
return self.default_changed_for_decimal(current_def) | |||
@@ -150,27 +150,41 @@ def check_if_doc_is_linked(doc, method="Delete"): | |||
if item and item.parent != doc.name and ((method=="Delete" and item.docstatus<2) or | |||
(method=="Cancel" and item.docstatus==1)): | |||
# raise exception only if | |||
# linked to an non-cancelled doc when deleting | |||
# or linked to a submitted doc when cancelling | |||
frappe.throw(_("Cannot delete or cancel because {0} {1} is linked with {2} {3}").format(doc.doctype, | |||
doc.name, item.parenttype if item.parent else link_dt, item.parent or item.name), | |||
frappe.LinkExistsError) | |||
def check_if_doc_is_dynamically_linked(doc): | |||
def check_if_doc_is_dynamically_linked(doc, method="Delete"): | |||
for query in dynamic_link_queries: | |||
for df in frappe.db.sql(query, as_dict=True): | |||
if frappe.get_meta(df.parent).issingle: | |||
# dynamic link in single doc | |||
refdoc = frappe.db.get_singles_dict(df.parent) | |||
if refdoc.get(df.options)==doc.doctype and refdoc.get(df.fieldname)==doc.name: | |||
if (refdoc.get(df.options)==doc.doctype | |||
and refdoc.get(df.fieldname)==doc.name | |||
and ((method=="Delete" and refdoc.docstatus < 2) | |||
or (method=="Cancel" and refdoc.docstatus==1)) | |||
): | |||
# raise exception only if | |||
# linked to an non-cancelled doc when deleting | |||
# or linked to a submitted doc when cancelling | |||
frappe.throw(_("Cannot delete or cancel because {0} {1} is linked with {2} {3}").format(doc.doctype, | |||
doc.name, df.parent, ""), frappe.LinkExistsError) | |||
else: | |||
# dynamic link in table | |||
for name in frappe.db.sql_list("""select name from `tab{parent}` where | |||
{options}=%s and {fieldname}=%s""".format(**df), (doc.doctype, doc.name)): | |||
frappe.throw(_("Cannot delete or cancel because {0} {1} is linked with {2} {3}").format(doc.doctype, | |||
doc.name, df.parent, name), frappe.LinkExistsError) | |||
for refdoc in frappe.db.sql("""select name, docstatus from `tab{parent}` where | |||
{options}=%s and {fieldname}=%s""".format(**df), (doc.doctype, doc.name), as_dict=True): | |||
if ((method=="Delete" and refdoc.docstatus < 2) or (method=="Cancel" and refdoc.docstatus==1)): | |||
# raise exception only if | |||
# linked to an non-cancelled doc when deleting | |||
# or linked to a submitted doc when cancelling | |||
frappe.throw(_("Cannot delete or cancel because {0} {1} is linked with {2} {3}")\ | |||
.format(doc.doctype, doc.name, df.parent, refdoc.name), frappe.LinkExistsError) | |||
def delete_linked_todos(doc): | |||
delete_doc("ToDo", frappe.db.sql_list("""select name from `tabToDo` | |||
@@ -457,7 +457,7 @@ class Document(BaseDocument): | |||
msgprint(msg) | |||
if frappe.flags.print_messages: | |||
print self.as_dict() | |||
print self.as_json().encode("utf-8") | |||
raise frappe.MandatoryError(", ".join((each[0] for each in missing))) | |||
@@ -581,7 +581,7 @@ class Document(BaseDocument): | |||
from frappe.model.delete_doc import check_if_doc_is_linked, check_if_doc_is_dynamically_linked | |||
if not self.flags.ignore_links: | |||
check_if_doc_is_linked(self, method="Cancel") | |||
check_if_doc_is_dynamically_linked(self) | |||
check_if_doc_is_dynamically_linked(self, method="Cancel") | |||
@staticmethod | |||
def whitelist(f): | |||
@@ -6,3 +6,4 @@ Custom | |||
Geo | |||
Desk | |||
Integrations |
@@ -161,3 +161,15 @@ select.form-control { | |||
font-weight: bold; | |||
background-color: #fffce7; | |||
} | |||
.form-headline .alert { | |||
font-size: 12px; | |||
border-color: #d1d8dd; | |||
margin-bottom: 0px; | |||
} | |||
.delivery-status-indicator { | |||
display: inline-block; | |||
margin-top: -3px; | |||
margin-left: 1px; | |||
font-weight: 500; | |||
color: #8d99a6; | |||
} |
@@ -119,6 +119,16 @@ frappe.ui.form.ControlHTML = frappe.ui.form.Control.extend({ | |||
}, | |||
html: function(html) { | |||
this.$wrapper.html(html || this.get_content()); | |||
}, | |||
set_value: function(html) { | |||
if(html.appendTo) { | |||
// jquery object | |||
html.appendTo(this.$wrapper.empty()); | |||
} else { | |||
// html | |||
this.df.options = html; | |||
this.html(html); | |||
} | |||
} | |||
}); | |||
@@ -24,7 +24,7 @@ frappe.ui.form.Dashboard = Class.extend({ | |||
}, | |||
set_headline_alert: function(text, alert_class, icon) { | |||
this.set_headline(repl('<div class="alert %(alert_class)s">%(icon)s%(text)s</div>', { | |||
"alert_class": alert_class || "alert-info", | |||
"alert_class": alert_class || "", | |||
"icon": icon ? '<i class="'+icon+'" /> ' : "", | |||
"text": text | |||
})); | |||
@@ -10,15 +10,28 @@ | |||
</span> | |||
</div> | |||
{% if(data.doctype=="Communication" || data.comment_type=="Comment") { %} | |||
<h6> | |||
<h6> | |||
<i class="{%= data.icon %} icon-fixed-width"></i> | |||
<span title="{%= data.comment_by %}">{%= data.fullname %}</span> | |||
<span class="text-muted" style="font-weight: normal;"> | |||
– {%= data.comment_on %}</span> | |||
{% if(data.doctype=="Communication") { %} | |||
<span class="text-muted">–</span> | |||
<a href="#Form/{%= data.doctype %}/{%= data.name %}" | |||
class="text-muted">{%= __("Details") %}</a> | |||
<span class="text-muted">–</span> | |||
<a href="#Form/{%= data.doctype %}/{%= data.name %}" | |||
class="text-muted"> | |||
{% if (data.delivery_status) { | |||
if (in_list(["Sent", "Opened", "Clicked"], data.delivery_status)) { | |||
var indicator_class = "green"; | |||
} else { | |||
var indicator_class = "red"; | |||
} | |||
%} | |||
<span class="indicator-right {%= indicator_class %} delivery-status-indicator" | |||
title="{%= data.delivery_status %}"> | |||
{%= data.delivery_status %}</span> | |||
{% } else { %} {%= __("Details") %} {% } %} | |||
</a> | |||
<a class="text-muted reply-link pull-right" | |||
data-name="{%= data.name %}">{%= __("Reply") %}</a> | |||
{% } %} | |||
@@ -53,6 +53,14 @@ frappe.ui.form.Grid = Class.extend({ | |||
var me = this, | |||
$rows = $(me.parent).find(".rows"), | |||
data = this.get_data(); | |||
if (this.frm && this.frm.docname) { | |||
// use doc specific docfield object | |||
this.df = frappe.meta.get_docfield(this.frm.doctype, this.df.fieldname, this.frm.docname); | |||
} else { | |||
// use non-doc specific docfield | |||
this.df = frappe.meta.get_docfield(this.df.options, this.df.fieldname); | |||
} | |||
this.docfields = frappe.meta.get_docfields(this.doctype, this.frm.docname); | |||
this.display_status = frappe.perm.get_field_display_status(this.df, this.frm.doc, | |||
@@ -10,7 +10,7 @@ | |||
</div> | |||
<div class="text-right col-sm-5 col-xs-6 page-actions"> | |||
<!-- ID and icon buttons --> | |||
<h6 class="text-ellipsis sub-heading rtl hide text-muted"></h6> | |||
<h6 class="text-ellipsis sub-heading hide text-muted"></h6> | |||
<span class="page-icon-group hide hidden-xs hidden-sm"></span> | |||
<!-- buttons --> | |||
@@ -209,3 +209,18 @@ select.form-control { | |||
font-weight: bold; | |||
background-color: @light-yellow; | |||
} | |||
.form-headline .alert { | |||
font-size: @text-medium; | |||
border-color: @border-color; | |||
// background-color: @light-bg; | |||
margin-bottom: 0px; | |||
} | |||
.delivery-status-indicator { | |||
display: inline-block; | |||
margin-top: -3px; | |||
margin-left: 1px; | |||
font-weight: 500; | |||
color: @text-muted; | |||
} |
@@ -10,6 +10,7 @@ | |||
@modal-backdrop-bg: #334143; | |||
@light-yellow: #fffce7; | |||
@text-extra-muted: @border-color; | |||
@text-regular: 14px; | |||
@text-medium: 12px; | |||
@text-small: 10px; | |||
@@ -32,7 +32,7 @@ def clear_cache(user=None): | |||
cache = frappe.cache() | |||
groups = ("bootinfo", "user_recent", "user_roles", "user_doc", "lang", | |||
"defaults", "user_permissions", "roles") | |||
"defaults", "user_permissions", "roles", "home_page") | |||
if user: | |||
for name in groups: | |||
@@ -82,7 +82,7 @@ def make_boilerplate(dest, app_name): | |||
with open(os.path.join(dest, hooks.app_name, hooks.app_name, "config", "desktop.py"), "w") as f: | |||
f.write(encode(desktop_template.format(**hooks))) | |||
print "'{app}' created at {path}".format(app=app_name, path=os.path.join(dest, app_name)) | |||
manifest_template = """include MANIFEST.in | |||
@@ -136,8 +136,15 @@ class RedisWrapper(redis.Redis): | |||
except redis.exceptions.ConnectionError: | |||
pass | |||
def hdel_keys(self, name_starts_with, key): | |||
"""Delete hash names with wildcard `*` and key""" | |||
for name in frappe.cache().get_keys(name_starts_with): | |||
name = name.split("|", 1)[1] | |||
self.hdel(name, key) | |||
def hkeys(self, name): | |||
try: | |||
return super(redis.Redis, self).hkeys(self.make_key(name)) | |||
except redis.exceptions.ConnectionError: | |||
return [] | |||
@@ -1,80 +0,0 @@ | |||
{ | |||
"creation": "2014-03-04 08:29:52", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "System", | |||
"fields": [ | |||
{ | |||
"fieldname": "facebook", | |||
"fieldtype": "Section Break", | |||
"label": "Facebook", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "facebook_client_id", | |||
"fieldtype": "Data", | |||
"label": "Facebook Client ID", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "facebook_client_secret", | |||
"fieldtype": "Data", | |||
"label": "Facebook Client Secret", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "google", | |||
"fieldtype": "Section Break", | |||
"label": "Google", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "google_client_id", | |||
"fieldtype": "Data", | |||
"label": "Google Client ID", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "google_client_secret", | |||
"fieldtype": "Data", | |||
"label": "Google Client Secret", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "github", | |||
"fieldtype": "Section Break", | |||
"label": "GitHub", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "github_client_id", | |||
"fieldtype": "Data", | |||
"label": "GitHub Client ID", | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"fieldname": "github_client_secret", | |||
"fieldtype": "Data", | |||
"label": "GitHub Client Secret", | |||
"permlevel": 0 | |||
} | |||
], | |||
"icon": "icon-signin", | |||
"idx": 1, | |||
"issingle": 1, | |||
"modified": "2015-02-05 05:11:46.875246", | |||
"modified_by": "Administrator", | |||
"module": "Website", | |||
"name": "Social Login Keys", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"permlevel": 0, | |||
"read": 1, | |||
"role": "System Manager", | |||
"share": 1, | |||
"write": 1 | |||
} | |||
] | |||
} |
@@ -41,6 +41,9 @@ class WebForm(WebsiteGenerator): | |||
name = frappe.db.get_value(self.doc_type, {"owner": frappe.session.user}, "name") | |||
if name: | |||
frappe.form_dict.name = name | |||
else: | |||
# only a single doc allowed and no existing doc, hence new | |||
frappe.form_dict.new = 1 | |||
# always render new form if login is not required or doesn't allow editing existing ones | |||
if not self.login_required or not self.allow_edit: | |||
@@ -217,6 +217,7 @@ def clear_cache(path=None): | |||
clear_sitemap() | |||
frappe.clear_cache("Guest") | |||
frappe.cache().delete_value("_website_pages") | |||
frappe.cache().delete_value("home_page") | |||
for method in frappe.get_hooks("website_clear_cache"): | |||
frappe.get_attr(method)(path) |
@@ -59,8 +59,10 @@ def set_breadcrumbs(out, context): | |||
"""Build breadcrumbs template (deprecated)""" | |||
out["no_breadcrumbs"] = context.get("no_breadcrumbs", 0) or ("<!-- no-breadcrumbs -->" in out.get("content", "")) | |||
# breadcrumbs | |||
if not out["no_breadcrumbs"] and "breadcrumbs" not in out: | |||
if out["no_breadcrumbs"]: | |||
out["breadcrumbs"] = "" | |||
elif "breadcrumbs" not in out: | |||
out["breadcrumbs"] = frappe.get_template("templates/includes/breadcrumbs.html").render(context) | |||
def set_title_and_header(out, context): | |||
@@ -29,3 +29,4 @@ email_reply_parser | |||
click | |||
num2words | |||
gevent-socketio | |||
watchdog==0.8.0 |
@@ -1,6 +1,6 @@ | |||
from setuptools import setup, find_packages | |||
version = "6.0.0-wip" | |||
version = "6.0.0" | |||
with open("requirements.txt", "r") as f: | |||
install_requires = f.readlines() | |||