From 8498e4c0a6db6b66ee4b5b3800efdc2b0f20cd3a Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 17 Feb 2021 23:35:46 +0530 Subject: [PATCH 1/9] fix: social media post fixes --- .../linkedin_settings/linkedin_settings.js | 1 + .../linkedin_settings/linkedin_settings.json | 3 +- .../linkedin_settings/linkedin_settings.py | 50 +++++++--- .../social_media_post/social_media_post.js | 4 +- .../social_media_post/social_media_post.json | 92 +++++++------------ .../social_media_post/social_media_post.py | 12 ++- .../twitter_settings/twitter_settings.js | 1 + .../twitter_settings/twitter_settings.json | 3 +- .../twitter_settings/twitter_settings.py | 26 ++++-- 9 files changed, 110 insertions(+), 82 deletions(-) diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js index 263005ef6c50..b9145e730cb5 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js @@ -14,6 +14,7 @@ frappe.ui.form.on('LinkedIn Settings', { } ); } + frm.dashboard.set_headline(__("For more information, {0}.", [`${__('Click here')}`])); }, refresh: function(frm){ if (frm.doc.session_status=="Expired"){ diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json index 9eacb0011c5c..f882e36c32a2 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json @@ -2,6 +2,7 @@ "actions": [], "creation": "2020-01-30 13:36:39.492931", "doctype": "DocType", + "documentation": "https://docs.erpnext.com/docs/user/manual/en/CRM/linkedin-settings", "editable_grid": 1, "engine": "InnoDB", "field_order": [ @@ -87,7 +88,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-04-16 23:22:51.966397", + "modified": "2021-02-18 15:19:21.920725", "modified_by": "Administrator", "module": "CRM", "name": "LinkedIn Settings", diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py index 377e061fdf47..0ef33eb620c5 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py @@ -41,11 +41,7 @@ def get_access_token(self, code): self.db_set("access_token", response["access_token"]) def get_member_profile(self): - headers = { - "Authorization": "Bearer {}".format(self.access_token) - } - url = "https://api.linkedin.com/v2/me" - response = requests.get(url=url, headers=headers) + response = requests.get(url="https://api.linkedin.com/v2/me", headers=self.get_headers()) response = frappe.parse_json(response.content.decode()) frappe.db.set_value(self.doctype, self.name, { @@ -81,9 +77,7 @@ def upload_image(self, media): }] } } - headers = { - "Authorization": "Bearer {}".format(self.access_token) - } + headers = self.get_headers() response = self.http_post(url=register_url, body=body, headers=headers) if response.status_code == 200: @@ -101,11 +95,10 @@ def upload_image(self, media): def post_text(self, text, media_id=None): url = "https://api.linkedin.com/v2/shares" - headers = { - "X-Restli-Protocol-Version": "2.0.0", - "Authorization": "Bearer {}".format(self.access_token), - "Content-Type": "application/json; charset=UTF-8" - } + headers = self.get_headers() + headers["X-Restli-Protocol-Version"] = "2.0.0" + headers["Content-Type"] = "application/json; charset=UTF-8" + body = { "distribution": { "linkedInDistributionTarget": {} @@ -117,6 +110,16 @@ def post_text(self, text, media_id=None): } } + reference_url = self.get_reference_url(text) + if reference_url: + body["content"] = { + "contentEntities": [ + { + "entityLocation": reference_url + } + ] + } + if media_id: body["content"]= { "contentEntities": [{ @@ -154,6 +157,27 @@ def http_post(self, url, headers=None, body=None, data=None): return response + def get_headers(self): + return { + "Authorization": "Bearer {}".format(self.access_token) + } + + def get_reference_url(self, text): + import re + regex_url = r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+" + urls = re.findall(regex_url, text) + if urls: + return urls[0] + + def delete_post(self, post_id): + try: + response = requests.delete("https://api.linkedin.com/v2/shares/{0}".format(post_id), self.get_headers()) + if response.status_code !=200: + raise + except Exception as e: + content = json.loads(response.content) + frappe.throw(response.reason, title=response.status_code) + @frappe.whitelist(allow_guest=True) def callback(code=None, error=None, error_description=None): if not error: diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js index 0ce8b44e19bc..45b9388989b6 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post.js @@ -21,8 +21,8 @@ frappe.ui.form.on('Social Media Post', { if (frm.doc.post_status != "Posted"){ add_post_btn(frm); } - else if (frm.doc.post_status == "Posted"){ - frm.set_df_property('sheduled_time', 'read_only', 1); + else { + frm.disable_form(); } let html=''; diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.json b/erpnext/crm/doctype/social_media_post/social_media_post.json index 0a00dca2808b..947847cc0274 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.json +++ b/erpnext/crm/doctype/social_media_post/social_media_post.json @@ -3,9 +3,11 @@ "autoname": "format: CRM-SMP-{YYYY}-{MM}-{DD}-{###}", "creation": "2020-01-30 11:53:13.872864", "doctype": "DocType", + "documentation": "https://docs.erpnext.com/docs/user/manual/en/CRM/social-media-post", "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "title", "campaign_name", "scheduled_time", "post_status", @@ -30,32 +32,24 @@ "fieldname": "text", "fieldtype": "Small Text", "label": "Tweet", - "mandatory_depends_on": "eval:doc.twitter ==1", - "show_days": 1, - "show_seconds": 1 + "mandatory_depends_on": "eval:doc.twitter ==1" }, { "fieldname": "image", "fieldtype": "Attach Image", - "label": "Image", - "show_days": 1, - "show_seconds": 1 + "label": "Image" }, { - "default": "0", + "default": "1", "fieldname": "twitter", "fieldtype": "Check", - "label": "Twitter", - "show_days": 1, - "show_seconds": 1 + "label": "Twitter" }, { - "default": "0", + "default": "1", "fieldname": "linkedin", "fieldtype": "Check", - "label": "LinkedIn", - "show_days": 1, - "show_seconds": 1 + "label": "LinkedIn" }, { "fieldname": "amended_from", @@ -64,27 +58,22 @@ "no_copy": 1, "options": "Social Media Post", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "depends_on": "eval:doc.twitter ==1", "fieldname": "content", "fieldtype": "Section Break", - "label": "Twitter", - "show_days": 1, - "show_seconds": 1 + "label": "Twitter" }, { "allow_on_submit": 1, "fieldname": "post_status", "fieldtype": "Select", "label": "Post Status", - "options": "\nScheduled\nPosted\nError", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "no_copy": 1, + "options": "\nScheduled\nPosted\nCancelled\nError", + "read_only": 1 }, { "allow_on_submit": 1, @@ -92,9 +81,8 @@ "fieldtype": "Data", "hidden": 1, "label": "Twitter Post Id", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "no_copy": 1, + "read_only": 1 }, { "allow_on_submit": 1, @@ -102,82 +90,69 @@ "fieldtype": "Data", "hidden": 1, "label": "LinkedIn Post Id", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "no_copy": 1, + "read_only": 1 }, { "fieldname": "campaign_name", "fieldtype": "Link", "in_list_view": 1, "label": "Campaign", - "options": "Campaign", - "show_days": 1, - "show_seconds": 1 + "options": "Campaign" }, { "fieldname": "column_break_6", "fieldtype": "Column Break", - "label": "Share On", - "show_days": 1, - "show_seconds": 1 + "label": "Share On" }, { "fieldname": "column_break_14", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "tweet_preview", - "fieldtype": "HTML", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "HTML" }, { "collapsible": 1, "depends_on": "eval:doc.linkedin==1", "fieldname": "linkedin_section", "fieldtype": "Section Break", - "label": "LinkedIn", - "show_days": 1, - "show_seconds": 1 + "label": "LinkedIn" }, { "collapsible": 1, "fieldname": "attachments_section", "fieldtype": "Section Break", - "label": "Attachments", - "show_days": 1, - "show_seconds": 1 + "label": "Attachments" }, { "fieldname": "linkedin_post", "fieldtype": "Text", "label": "Post", - "mandatory_depends_on": "eval:doc.linkedin ==1", - "show_days": 1, - "show_seconds": 1 + "mandatory_depends_on": "eval:doc.linkedin ==1" }, { "fieldname": "column_break_15", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "allow_on_submit": 1, "fieldname": "scheduled_time", "fieldtype": "Datetime", "label": "Scheduled Time", - "read_only_depends_on": "eval:doc.post_status == \"Posted\"", - "show_days": 1, - "show_seconds": 1 + "read_only_depends_on": "eval:doc.post_status == \"Posted\"" + }, + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-06-14 10:31:33.961381", + "modified": "2021-02-18 14:06:44.633463", "modified_by": "Administrator", "module": "CRM", "name": "Social Media Post", @@ -228,5 +203,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "title_field": "title", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.py b/erpnext/crm/doctype/social_media_post/social_media_post.py index ed1b58394460..1659ddbbb02c 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.py +++ b/erpnext/crm/doctype/social_media_post/social_media_post.py @@ -30,7 +30,7 @@ def post(self): if self.linkedin and not self.linkedin_post_id: linkedin = frappe.get_doc("LinkedIn Settings") linkedin_post = linkedin.post(self.linkedin_post, self.image) - self.db_set("linkedin_post_id", linkedin_post.headers['X-RestLi-Id'].split(":")[-1]) + self.db_set("linkedin_post_id", linkedin_post.headers['X-RestLi-Id']) self.db_set("post_status", "Posted") except: @@ -39,6 +39,16 @@ def post(self): traceback = frappe.get_traceback() frappe.log_error(message=traceback , title=title) + def on_cancel(self): + if self.twitter and self.twitter_post_id: + twitter = frappe.get_doc("Twitter Settings") + twitter.delete_tweet(self.twitter_post_id) + + if self.linkedin and self.linkedin_post_id: + linkedin = frappe.get_doc("LinkedIn Settings") + linkedin.delete_post(self.linkedin_post_id) + self.post_status = 'Cancelled' + def process_scheduled_social_media_posts(): posts = frappe.get_list("Social Media Post", filters={"post_status": "Scheduled", "docstatus":1}, fields= ["name", "scheduled_time","post_status"]) start = frappe.utils.now_datetime() diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.js b/erpnext/crm/doctype/twitter_settings/twitter_settings.js index f6f431ca5c91..6c8b63309523 100644 --- a/erpnext/crm/doctype/twitter_settings/twitter_settings.js +++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.js @@ -14,6 +14,7 @@ frappe.ui.form.on('Twitter Settings', { } ); } + frm.dashboard.set_headline(__("For more information, {0}.", [`${__('Click here')}`])); }, refresh: function(frm){ let msg, color, flag=false; diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.json b/erpnext/crm/doctype/twitter_settings/twitter_settings.json index 36776e5c2024..8d05877f0602 100644 --- a/erpnext/crm/doctype/twitter_settings/twitter_settings.json +++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.json @@ -2,6 +2,7 @@ "actions": [], "creation": "2020-01-30 10:29:08.562108", "doctype": "DocType", + "documentation": "https://docs.erpnext.com/docs/user/manual/en/CRM/twitter-settings", "editable_grid": 1, "engine": "InnoDB", "field_order": [ @@ -77,7 +78,7 @@ "image_field": "profile_pic", "issingle": 1, "links": [], - "modified": "2020-05-13 17:50:47.934776", + "modified": "2021-02-18 15:18:07.900031", "modified_by": "Administrator", "module": "CRM", "name": "Twitter Settings", diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.py b/erpnext/crm/doctype/twitter_settings/twitter_settings.py index 976a23dfc7d3..f891f5da3611 100644 --- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py +++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py @@ -31,7 +31,9 @@ def get_access_token(self, oauth_token, oauth_verifier): try: auth.get_access_token(oauth_verifier) - api = self.get_api(auth.access_token, auth.access_token_secret) + self.access_token = auth.access_token + self.access_token_secret = auth.access_token_secret + api = self.get_api() user = api.me() profile_pic = (user._json["profile_image_url"]).replace("_normal","") @@ -49,11 +51,11 @@ def get_access_token(self, oauth_token, oauth_verifier): frappe.msgprint(_("Error! Failed to get access token.")) frappe.throw(_('Invalid Consumer Key or Consumer Secret Key')) - def get_api(self, access_token, access_token_secret): + def get_api(self): # authentication of consumer key and secret auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret")) # authentication of access token and secret - auth.set_access_token(access_token, access_token_secret) + auth.set_access_token(self.access_token, self.access_token_secret) return tweepy.API(auth) @@ -67,13 +69,13 @@ def post(self, text, media=None): def upload_image(self, media): media = get_file_path(media) - api = self.get_api(self.access_token, self.access_token_secret) + api = self.get_api() media = api.media_upload(media) return media.media_id def send_tweet(self, text, media_id=None): - api = self.get_api(self.access_token, self.access_token_secret) + api = self.get_api() try: if media_id: response = api.update_status(status = text, media_ids = [media_id]) @@ -88,7 +90,19 @@ def send_tweet(self, text, media_id=None): if e.response.status_code == 401: self.db_set("session_status", "Expired") frappe.db.commit() - frappe.throw(content["message"],title="Twitter Error {0} {1}".format(e.response.status_code, e.response.reason)) + frappe.throw(content["message"],title="Twitter Error {0} : {1}".format(e.response.status_code, e.response.reason)) + + def delete_tweet(self, tweet_id): + api = self.get_api() + try: + api.destroy_status(tweet_id) + except TweepError as e: + content = json.loads(e.response.content) + content = content["errors"][0] + if e.response.status_code == 401: + self.db_set("session_status", "Expired") + frappe.db.commit() + frappe.throw(content["message"],title="Twitter Error {0} : {1}".format(e.response.status_code, e.response.reason)) @frappe.whitelist(allow_guest=True) def callback(oauth_token = None, oauth_verifier = None): From 8fef290bb71d3aa9c8c66507e4b99c0bbbc89929 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 14 Apr 2021 04:04:41 +0530 Subject: [PATCH 2/9] feat: post metrics and some fixes --- .../linkedin_settings/linkedin_settings.py | 65 ++++--- .../test_opportunity_lost_reason.py | 10 -- .../social_media_post/social_media_post.js | 165 ++++++++++++------ .../social_media_post/social_media_post.json | 4 +- .../social_media_post/social_media_post.py | 59 ++++--- .../social_media_post_list.js | 3 +- .../twitter_settings/twitter_settings.py | 33 ++-- 7 files changed, 215 insertions(+), 124 deletions(-) delete mode 100644 erpnext/crm/doctype/opportunity_lost_reason/test_opportunity_lost_reason.py diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py index eca5f048ff11..5d78c044802e 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py @@ -3,11 +3,11 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, requests, json +import frappe, requests from frappe import _ -from frappe.utils import get_site_url, get_url_to_form, get_link_to_form +from frappe.utils import get_site_url, get_url_to_form from frappe.model.document import Document -from frappe.utils.file_manager import get_file, get_file_path +from frappe.utils.file_manager import get_file_path from six.moves.urllib.parse import urlencode class LinkedInSettings(Document): @@ -51,16 +51,16 @@ def get_member_profile(self): "session_status": "Active" }) frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = get_url_to_form("LinkedIn Settings","LinkedIn Settings") + frappe.local.response["location"] = get_url_to_form("LinkedIn Settings", "LinkedIn Settings") - def post(self, text, media=None): + def post(self, text, title, media=None): if not media: - return self.post_text(text) + return self.post_text(text, title) else: media_id = self.upload_image(media) if media_id: - return self.post_text(text, media_id=media_id) + return self.post_text(text, title, media_id=media_id) else: frappe.log_error("Failed to upload media.","LinkedIn Upload Error") @@ -94,7 +94,7 @@ def upload_image(self, media): return None - def post_text(self, text, media_id=None): + def post_text(self, text, title, media_id=None): url = "https://api.linkedin.com/v2/shares" headers = self.get_headers() headers["X-Restli-Protocol-Version"] = "2.0.0" @@ -105,7 +105,7 @@ def post_text(self, text, media_id=None): "linkedInDistributionTarget": {} }, "owner":"urn:li:organization:{0}".format(self.company_id), - "subject": "Test Share Subject", + "subject": title, "text": { "text": text } @@ -144,18 +144,8 @@ def http_post(self, url, headers=None, body=None, data=None): raise except Exception as e: - content = json.loads(response.content) - - if response.status_code == 401: - self.db_set("session_status", "Expired") - frappe.db.commit() - frappe.throw(content["message"], title="LinkedIn Error - Unauthorized") - elif response.status_code == 403: - frappe.msgprint(_("You Didn't have permission to access this API")) - frappe.throw(content["message"], title="LinkedIn Error - Access Denied") - else: - frappe.throw(response.reason, title=response.status_code) - + self.api_error(response) + return response def get_headers(self): @@ -172,11 +162,40 @@ def get_reference_url(self, text): def delete_post(self, post_id): try: - response = requests.delete("https://api.linkedin.com/v2/shares/{0}".format(post_id), self.get_headers()) + response = requests.delete(url="https://api.linkedin.com/v2/shares/urn:li:share:{0}".format(post_id), headers=self.get_headers()) + if response.status_code !=200: + raise + except Exception as e: + self.api_error(response) + + def get_post(self, post_id): + url = "https://api.linkedin.com/v2/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:{0}&shares[0]=urn:li:share:{1}".format(self.company_id, post_id) + + try: + response = requests.get(url=url, headers=self.get_headers()) if response.status_code !=200: raise + except Exception as e: - content = json.loads(response.content) + self.api_error(response) + + response = frappe.parse_json(response.content.decode()) + if len(response.elements): + return response.elements[0] + + return None + + def api_error(self, response): + content = frappe.parse_json(response.content.decode()) + + if response.status_code == 401: + self.db_set("session_status", "Expired") + frappe.db.commit() + frappe.throw(content["message"], title="LinkedIn Error - Unauthorized") + elif response.status_code == 403: + frappe.msgprint(_("You Didn't have permission to access this API")) + frappe.throw(content["message"], title="LinkedIn Error - Access Denied") + else: frappe.throw(response.reason, title=response.status_code) @frappe.whitelist(allow_guest=True) diff --git a/erpnext/crm/doctype/opportunity_lost_reason/test_opportunity_lost_reason.py b/erpnext/crm/doctype/opportunity_lost_reason/test_opportunity_lost_reason.py deleted file mode 100644 index d9d00bd29e4e..000000000000 --- a/erpnext/crm/doctype/opportunity_lost_reason/test_opportunity_lost_reason.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -# import frappe -import unittest - -class TestOpportunityLostReason(unittest.TestCase): - pass diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js index f65568b3f35d..13351f224f0b 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post.js @@ -9,66 +9,131 @@ frappe.ui.form.on('Social Media Post', { let scheduled_time = new Date(frm.doc.scheduled_time); let date_time = new Date(); if (scheduled_time.getTime() < date_time.getTime()){ - frappe.throw(__("Invalid Scheduled Time")); + frappe.throw(__("Scheduled Time must be a future time.")); } } - if (frm.doc.text?.length > 280){ - frappe.throw(__("Length Must be less than 280.")) + frm.trigger('validate_tweet_length'); + }, + + text: function(frm) { + frm.set_df_property('text', 'description', `${frm.doc.text.length}/280`); + frm.refresh_field('text'); + frm.trigger('validate_tweet_length'); + }, + + validate_tweet_length: function(frm) { + if (frm.doc?.text?.length > 280){ + frappe.throw(__("Tweet length Must be less than 280.")) + } + }, + + onload: function(frm) { + frm.trigger('make_dashboard'); + }, + + make_dashboard: function(frm) { + if (frm.doc.post_status == "Posted") { + frappe.call({ + doc: frm.doc, + method: 'get_post', + freeze: true, + callback: (r) => { + if (!r.message) { + return + } + + let datasets = [],colors = [] + if (r.message?.twitter) { + window.post = r.message + colors.push('#1DA1F2') + datasets.push({ + name: 'Twitter', + values: [r.message.twitter.favorite_count, r.message.twitter.retweet_count] + }) + } + if (r.message?.linkedin) { + colors.push('#0077b5') + datasets.push({ + name: 'LinkedIn', + values: [r.message.linkedin.totalShareStatistics.likeCount, r.message.linkedin.totalShareStatistics.shareCount] + }) + } + + if(datasets.length) { + frm.dashboard.render_graph({ + data: { + labels: ['Likes', 'Retweets/Shares'], + datasets: datasets + }, + + title: "Post Metrics", + type: 'bar', + height: 300, + colors: colors + }); + } + } + }); } }, + refresh: function(frm){ + frm.trigger('text'); + if (frm.doc.docstatus === 1){ - if (frm.doc.post_status != "Posted"){ - add_post_btn(frm); + if (!['Posted', 'Deleted'].includes(frm.doc.post_status)){ + frm.trigger('add_post_btn'); } - else { - frappe.call({ - doc: frm.doc, - method: 'get_status', - callback: function() { - - } + if(frm.doc.post_status !='Deleted') { + frm.add_custom_button(('Delete Post'), function(){ + frappe.confirm( + __('Are you sure want to delete the Post from Social Media platforms?'), + function(){ + frappe.call({ + doc: frm.doc, + method: 'delete_post', + freeze: true, + callback: (r) => { + frm.reload_doc(); + } + }); + } + ) }); - frm.disable_form(); } - let html=''; - if (frm.doc.twitter){ - let color = frm.doc.twitter_post_id ? "green" : "red"; - let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted"; - html += `
- Twitter : ${status} -
` ; - } - if (frm.doc.linkedin){ - let color = frm.doc.linkedin_post_id ? "green" : "red"; - let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted"; - html += `
- LinkedIn : ${status} -
` ; + if (frm.doc.post_status !='Deleted') { + let html=''; + if (frm.doc.twitter){ + let color = frm.doc.twitter_post_id ? "green" : "red"; + let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted"; + html += `
+ Twitter : ${status} +
` ; + } + if (frm.doc.linkedin){ + let color = frm.doc.linkedin_post_id ? "green" : "red"; + let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted"; + html += `
+ LinkedIn : ${status} +
` ; + } + html = `
${html}
`; + frm.dashboard.set_headline_alert(html); } - html = `
${html}
`; - frm.dashboard.set_headline_alert(html); } + }, + + add_post_btn: function(frm) { + frm.add_custom_button(__('Post Now'), function(){ + frappe.call({ + doc: frm.doc, + method: 'post', + freeze: true, + callback: function(r) { + frm.reload_doc(); + } + }); + }); } -}); -var add_post_btn = function(frm){ - frm.add_custom_button(('Post Now'), function(){ - post(frm); - }); -} -var post = function(frm){ - frappe.dom.freeze(); - frappe.call({ - method: "erpnext.crm.doctype.social_media_post.social_media_post.publish", - args: { - doctype: frm.doc.doctype, - name: frm.doc.name - }, - callback: function(r) { - frm.reload_doc(); - frappe.dom.unfreeze(); - } - }) - -} \ No newline at end of file +}); \ No newline at end of file diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.json b/erpnext/crm/doctype/social_media_post/social_media_post.json index 947847cc0274..98e78f949e83 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.json +++ b/erpnext/crm/doctype/social_media_post/social_media_post.json @@ -72,7 +72,7 @@ "fieldtype": "Select", "label": "Post Status", "no_copy": 1, - "options": "\nScheduled\nPosted\nCancelled\nError", + "options": "\nScheduled\nPosted\nCancelled\nDeleted\nError", "read_only": 1 }, { @@ -152,7 +152,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-02-18 14:06:44.633463", + "modified": "2021-04-14 14:24:59.821223", "modified_by": "Administrator", "module": "CRM", "name": "Social Media Post", diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.py b/erpnext/crm/doctype/social_media_post/social_media_post.py index 64868bcaa54b..33a188de2e87 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.py +++ b/erpnext/crm/doctype/social_media_post/social_media_post.py @@ -10,17 +10,51 @@ class SocialMediaPost(Document): def validate(self): + if (not self.twitter and not self.linkedin): + frappe.throw(_("Select atleast one Social Media from Share on.")) + if self.scheduled_time: current_time = frappe.utils.now_datetime() scheduled_time = frappe.utils.get_datetime(self.scheduled_time) if scheduled_time < current_time: - frappe.throw(_("Invalid Scheduled Time")) + frappe.throw(_("Scheduled Time must be a future time.")) + + if self.text and len(self.text)>280: + frappe.throw(_("Tweet length Must be less than 280.")) def submit(self): if self.scheduled_time: self.post_status = "Scheduled" super(SocialMediaPost, self).submit() + + def on_cancel(self): + self.db_set('post_status', 'Cancelled') + @frappe.whitelist() + def delete_post(self): + if self.twitter and self.twitter_post_id: + twitter = frappe.get_doc("Twitter Settings") + twitter.delete_tweet(self.twitter_post_id) + + if self.linkedin and self.linkedin_post_id: + linkedin = frappe.get_doc("LinkedIn Settings") + linkedin.delete_post(self.linkedin_post_id) + + frappe.db.set_value('Social Media Post', self.name, 'post_status', 'Deleted') + + @frappe.whitelist() + def get_post(self): + response = {} + if self.linkedin and self.linkedin_post_id: + linkedin = frappe.get_doc("LinkedIn Settings") + response['linkedin'] = linkedin.get_post(self.linkedin_post_id) + if self.twitter and self.twitter_post_id: + twitter = frappe.get_doc("Twitter Settings") + response['twitter'] = twitter.get_tweet(self.twitter_post_id) + + return response + + @frappe.whitelist() def post(self): try: if self.twitter and not self.twitter_post_id: @@ -29,7 +63,7 @@ def post(self): self.db_set("twitter_post_id", twitter_post.id) if self.linkedin and not self.linkedin_post_id: linkedin = frappe.get_doc("LinkedIn Settings") - linkedin_post = linkedin.post(self.linkedin_post, self.image) + linkedin_post = linkedin.post(self.linkedin_post, self.title, self.image) self.db_set("linkedin_post_id", linkedin_post.headers['X-RestLi-Id']) self.db_set("post_status", "Posted") @@ -39,21 +73,6 @@ def post(self): traceback = frappe.get_traceback() frappe.log_error(message=traceback , title=title) - def on_cancel(self): - if self.twitter and self.twitter_post_id: - twitter = frappe.get_doc("Twitter Settings") - twitter.delete_tweet(self.twitter_post_id) - - if self.linkedin and self.linkedin_post_id: - linkedin = frappe.get_doc("LinkedIn Settings") - linkedin.delete_post(self.linkedin_post_id) - self.post_status = 'Cancelled' - - def get_status(self): - twitter = frappe.get_doc("Twitter Settings") - return twitter.get_status(self.twitter_post_id) - - def process_scheduled_social_media_posts(): posts = frappe.get_list("Social Media Post", filters={"post_status": "Scheduled", "docstatus":1}, fields= ["name", "scheduled_time","post_status"]) start = frappe.utils.now_datetime() @@ -63,9 +82,3 @@ def process_scheduled_social_media_posts(): post_time = frappe.utils.get_datetime(post.scheduled_time) if post_time > start and post_time <= end: publish('Social Media Post', post.name) - -@frappe.whitelist() -def publish(doctype, name): - sm_post = frappe.get_doc(doctype, name) - sm_post.post() - frappe.db.commit() diff --git a/erpnext/crm/doctype/social_media_post/social_media_post_list.js b/erpnext/crm/doctype/social_media_post/social_media_post_list.js index c60b91a9a025..999bb3a1a94b 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post_list.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post_list.js @@ -4,7 +4,8 @@ frappe.listview_settings['Social Media Post'] = { return [__(doc.post_status), { "Scheduled": "orange", "Posted": "green", - "Error": "red" + "Error": "red", + "Deleted": "red" }[doc.post_status]]; } } diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.py b/erpnext/crm/doctype/twitter_settings/twitter_settings.py index 4c0dfba616e9..b8a2c45e522d 100644 --- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py +++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py @@ -86,28 +86,31 @@ def send_tweet(self, text, media_id=None): return response except TweepError as e: - content = json.loads(e.response.content) - content = content["errors"][0] - if e.response.status_code == 401: - self.db_set("session_status", "Expired") - frappe.db.commit() - frappe.throw(content["message"],title="Twitter Error {0} : {1}".format(e.response.status_code, e.response.reason)) + self.api_error(e) def delete_tweet(self, tweet_id): api = self.get_api() try: api.destroy_status(tweet_id) except TweepError as e: - content = json.loads(e.response.content) - content = content["errors"][0] - if e.response.status_code == 401: - self.db_set("session_status", "Expired") - frappe.db.commit() - frappe.throw(content["message"],title="Twitter Error {0} : {1}".format(e.response.status_code, e.response.reason)) - - def get_status(self, tweet_id): + self.api_error(e) + + def get_tweet(self, tweet_id): api = self.get_api() - return api.get_status(tweet_id) + try: + response = api.get_status(tweet_id, trim_user=True, include_entities=True) + except TweepError as e: + self.api_error(e) + + return response._json + + def api_error(self, e): + content = json.loads(e.response.content) + content = content["errors"][0] + if e.response.status_code == 401: + self.db_set("session_status", "Expired") + frappe.db.commit() + frappe.throw(content["message"],title="Twitter Error {0} : {1}".format(e.response.status_code, e.response.reason)) @frappe.whitelist(allow_guest=True) From 19e706cf7724a8ebdd55baaafb516f99ef212271 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 14 Apr 2021 16:40:18 +0530 Subject: [PATCH 3/9] fix: sider issues --- .../linkedin_settings/linkedin_settings.js | 10 +++++----- .../linkedin_settings/linkedin_settings.py | 5 +++-- .../social_media_post/social_media_post.py | 8 ++++---- .../social_media_post_list.js | 18 +++++++++--------- .../twitter_settings/twitter_settings.js | 12 ++++++------ 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js index b9145e730cb5..7aa0b7775963 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js @@ -2,8 +2,8 @@ // For license information, please see license.txt frappe.ui.form.on('LinkedIn Settings', { - onload: function(frm){ - if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){ + onload: function(frm) { + if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret) { frappe.confirm( __('Session not valid, Do you want to login?'), function(){ @@ -16,7 +16,7 @@ frappe.ui.form.on('LinkedIn Settings', { } frm.dashboard.set_headline(__("For more information, {0}.", [`${__('Click here')}`])); }, - refresh: function(frm){ + refresh: function(frm) { if (frm.doc.session_status=="Expired"){ let msg = __("Session Not Active. Save doc to login."); frm.dashboard.set_headline_alert( @@ -54,7 +54,7 @@ frappe.ui.form.on('LinkedIn Settings', { ); } }, - login: function(frm){ + login: function(frm) { if (frm.doc.consumer_key && frm.doc.consumer_secret){ frappe.dom.freeze(); frappe.call({ @@ -68,7 +68,7 @@ frappe.ui.form.on('LinkedIn Settings', { }); } }, - after_save: function(frm){ + after_save: function(frm) { frm.trigger("login"); } }); diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py index 5d78c044802e..97352dd4f3eb 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py @@ -3,9 +3,10 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, requests +import frappe +import requests from frappe import _ -from frappe.utils import get_site_url, get_url_to_form +from frappe.utils import get_url_to_form from frappe.model.document import Document from frappe.utils.file_manager import get_file_path from six.moves.urllib.parse import urlencode diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.py b/erpnext/crm/doctype/social_media_post/social_media_post.py index 33a188de2e87..0c0fa2efac09 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.py +++ b/erpnext/crm/doctype/social_media_post/social_media_post.py @@ -70,15 +70,15 @@ def post(self): except: self.db_set("post_status", "Error") title = _("Error while POSTING {0}").format(self.name) - traceback = frappe.get_traceback() - frappe.log_error(message=traceback , title=title) + frappe.log_error(message=frappe.get_traceback() , title=title) def process_scheduled_social_media_posts(): - posts = frappe.get_list("Social Media Post", filters={"post_status": "Scheduled", "docstatus":1}, fields= ["name", "scheduled_time","post_status"]) + posts = frappe.get_list("Social Media Post", filters={"post_status": "Scheduled", "docstatus":1}, fields= ["name", "scheduled_time"]) start = frappe.utils.now_datetime() end = start + datetime.timedelta(minutes=10) for post in posts: if post.scheduled_time: post_time = frappe.utils.get_datetime(post.scheduled_time) if post_time > start and post_time <= end: - publish('Social Media Post', post.name) + sm_post = frappe.get_doc('Social Media Post', post.name) + sm_post.post() diff --git a/erpnext/crm/doctype/social_media_post/social_media_post_list.js b/erpnext/crm/doctype/social_media_post/social_media_post_list.js index 999bb3a1a94b..a8c8272ad08f 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post_list.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post_list.js @@ -1,11 +1,11 @@ frappe.listview_settings['Social Media Post'] = { - add_fields: ["status","post_status"], - get_indicator: function(doc) { - return [__(doc.post_status), { - "Scheduled": "orange", - "Posted": "green", - "Error": "red", - "Deleted": "red" - }[doc.post_status]]; - } + add_fields: ["status", "post_status"], + get_indicator: function(doc) { + return [__(doc.post_status), { + "Scheduled": "orange", + "Posted": "green", + "Error": "red", + "Deleted": "red" + }[doc.post_status]]; + } } diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.js b/erpnext/crm/doctype/twitter_settings/twitter_settings.js index 6c8b63309523..112f3d4d1c34 100644 --- a/erpnext/crm/doctype/twitter_settings/twitter_settings.js +++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('Twitter Settings', { - onload: function(frm){ + onload: function(frm) { if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){ frappe.confirm( __('Session not valid, Do you want to login?'), @@ -16,9 +16,9 @@ frappe.ui.form.on('Twitter Settings', { } frm.dashboard.set_headline(__("For more information, {0}.", [`${__('Click here')}`])); }, - refresh: function(frm){ + refresh: function(frm) { let msg, color, flag=false; - if (frm.doc.session_status == "Active"){ + if (frm.doc.session_status == "Active") { msg = __("Session Active"); color = 'green'; flag = true; @@ -29,7 +29,7 @@ frappe.ui.form.on('Twitter Settings', { flag = true; } - if (flag){ + if (flag) { frm.dashboard.set_headline_alert( `
@@ -39,7 +39,7 @@ frappe.ui.form.on('Twitter Settings', { ); } }, - login: function(frm){ + login: function(frm) { if (frm.doc.consumer_key && frm.doc.consumer_secret){ frappe.dom.freeze(); frappe.call({ @@ -53,7 +53,7 @@ frappe.ui.form.on('Twitter Settings', { }); } }, - after_save: function(frm){ + after_save: function(frm) { frm.trigger("login"); } }); From f29737c4515834ba84f0f1f8bf8ec79ca41b63b3 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 29 Jul 2021 11:56:18 +0530 Subject: [PATCH 4/9] fix: sider issue --- erpnext/crm/doctype/linkedin_settings/linkedin_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py index 97352dd4f3eb..440442457777 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py @@ -166,7 +166,7 @@ def delete_post(self, post_id): response = requests.delete(url="https://api.linkedin.com/v2/shares/urn:li:share:{0}".format(post_id), headers=self.get_headers()) if response.status_code !=200: raise - except Exception as e: + except Exception: self.api_error(response) def get_post(self, post_id): @@ -177,7 +177,7 @@ def get_post(self, post_id): if response.status_code !=200: raise - except Exception as e: + except Exception: self.api_error(response) response = frappe.parse_json(response.content.decode()) From 6c6505a6ff36fae61dfaba649b4304ce5990f34c Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 30 Jul 2021 11:00:47 +0530 Subject: [PATCH 5/9] fix: reverting optional chaning statements --- erpnext/crm/doctype/social_media_post/social_media_post.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js index 13351f224f0b..4f9eff0193ae 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post.js @@ -22,7 +22,7 @@ frappe.ui.form.on('Social Media Post', { }, validate_tweet_length: function(frm) { - if (frm.doc?.text?.length > 280){ + if (frm.doc.text && frm.doc.text.length > 280){ frappe.throw(__("Tweet length Must be less than 280.")) } }, @@ -43,7 +43,7 @@ frappe.ui.form.on('Social Media Post', { } let datasets = [],colors = [] - if (r.message?.twitter) { + if (r.message && r.message.twitter) { window.post = r.message colors.push('#1DA1F2') datasets.push({ @@ -51,7 +51,7 @@ frappe.ui.form.on('Social Media Post', { values: [r.message.twitter.favorite_count, r.message.twitter.retweet_count] }) } - if (r.message?.linkedin) { + if (r.message && r.message.linkedin) { colors.push('#0077b5') datasets.push({ name: 'LinkedIn', From dba329e20790aa6e554e1ea00b54c60d4eecb2d9 Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 30 Jul 2021 11:39:49 +0530 Subject: [PATCH 6/9] fix: sider issues --- .../social_media_post/social_media_post.js | 250 +++++++++--------- 1 file changed, 124 insertions(+), 126 deletions(-) diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js index 4f9eff0193ae..5b9363757f10 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post.js @@ -1,139 +1,137 @@ // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt frappe.ui.form.on('Social Media Post', { - validate: function(frm){ - if (frm.doc.twitter === 0 && frm.doc.linkedin === 0){ - frappe.throw(__("Select atleast one Social Media from Share on.")) - } - if (frm.doc.scheduled_time) { - let scheduled_time = new Date(frm.doc.scheduled_time); - let date_time = new Date(); - if (scheduled_time.getTime() < date_time.getTime()){ - frappe.throw(__("Scheduled Time must be a future time.")); - } - } - frm.trigger('validate_tweet_length'); - }, + validate: function(frm) { + if (frm.doc.twitter === 0 && frm.doc.linkedin === 0) { + frappe.throw(__("Select atleast one Social Media from Share on.")); + } + if (frm.doc.scheduled_time) { + let scheduled_time = new Date(frm.doc.scheduled_time); + let date_time = new Date(); + if (scheduled_time.getTime() < date_time.getTime()) { + frappe.throw(__("Scheduled Time must be a future time.")); + } + } + frm.trigger('validate_tweet_length'); + }, - text: function(frm) { - frm.set_df_property('text', 'description', `${frm.doc.text.length}/280`); - frm.refresh_field('text'); - frm.trigger('validate_tweet_length'); - }, + text: function(frm) { + frm.set_df_property('text', 'description', `${frm.doc.text.length}/280`); + frm.refresh_field('text'); + frm.trigger('validate_tweet_length'); + }, - validate_tweet_length: function(frm) { - if (frm.doc.text && frm.doc.text.length > 280){ - frappe.throw(__("Tweet length Must be less than 280.")) - } - }, + validate_tweet_length: function(frm) { + if (frm.doc.text && frm.doc.text.length > 280) { + frappe.throw(__("Tweet length Must be less than 280.")); + } + }, - onload: function(frm) { - frm.trigger('make_dashboard'); - }, + onload: function(frm) { + frm.trigger('make_dashboard'); + }, - make_dashboard: function(frm) { - if (frm.doc.post_status == "Posted") { - frappe.call({ - doc: frm.doc, - method: 'get_post', - freeze: true, - callback: (r) => { - if (!r.message) { - return - } + make_dashboard: function(frm) { + if (frm.doc.post_status == "Posted") { + frappe.call({ + doc: frm.doc, + method: 'get_post', + freeze: true, + callback: (r) => { + if (!r.message) { + return; + } - let datasets = [],colors = [] - if (r.message && r.message.twitter) { - window.post = r.message - colors.push('#1DA1F2') - datasets.push({ - name: 'Twitter', - values: [r.message.twitter.favorite_count, r.message.twitter.retweet_count] - }) - } - if (r.message && r.message.linkedin) { - colors.push('#0077b5') - datasets.push({ - name: 'LinkedIn', - values: [r.message.linkedin.totalShareStatistics.likeCount, r.message.linkedin.totalShareStatistics.shareCount] - }) - } + let datasets = [], colors = []; + if (r.message && r.message.twitter) { + colors.push('#1DA1F2'); + datasets.push({ + name: 'Twitter', + values: [r.message.twitter.favorite_count, r.message.twitter.retweet_count] + }); + } + if (r.message && r.message.linkedin) { + colors.push('#0077b5'); + datasets.push({ + name: 'LinkedIn', + values: [r.message.linkedin.totalShareStatistics.likeCount, r.message.linkedin.totalShareStatistics.shareCount] + }); + } - if(datasets.length) { - frm.dashboard.render_graph({ - data: { - labels: ['Likes', 'Retweets/Shares'], - datasets: datasets - }, - - title: "Post Metrics", - type: 'bar', - height: 300, - colors: colors - }); - } - } - }); - } - }, + if (datasets.length) { + frm.dashboard.render_graph({ + data: { + labels: ['Likes', 'Retweets/Shares'], + datasets: datasets + }, - refresh: function(frm){ - frm.trigger('text'); - - if (frm.doc.docstatus === 1){ - if (!['Posted', 'Deleted'].includes(frm.doc.post_status)){ - frm.trigger('add_post_btn'); - } - if(frm.doc.post_status !='Deleted') { - frm.add_custom_button(('Delete Post'), function(){ - frappe.confirm( - __('Are you sure want to delete the Post from Social Media platforms?'), - function(){ - frappe.call({ - doc: frm.doc, - method: 'delete_post', - freeze: true, - callback: (r) => { - frm.reload_doc(); - } - }); - } - ) - }); - } + title: __("Post Metrics"), + type: 'bar', + height: 300, + colors: colors + }); + } + } + }); + } + }, - if (frm.doc.post_status !='Deleted') { - let html=''; - if (frm.doc.twitter){ - let color = frm.doc.twitter_post_id ? "green" : "red"; - let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted"; - html += `
- Twitter : ${status} -
` ; - } - if (frm.doc.linkedin){ - let color = frm.doc.linkedin_post_id ? "green" : "red"; - let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted"; - html += `
- LinkedIn : ${status} -
` ; - } - html = `
${html}
`; - frm.dashboard.set_headline_alert(html); - } - } - }, + refresh: function(frm) { + frm.trigger('text'); + + if (frm.doc.docstatus === 1) { + if (!['Posted', 'Deleted'].includes(frm.doc.post_status)) { + frm.trigger('add_post_btn'); + } + if (frm.doc.post_status !='Deleted') { + frm.add_custom_button(('Delete Post'), function() { + frappe.confirm(__('Are you sure want to delete the Post from Social Media platforms?'), + function() { + frappe.call({ + doc: frm.doc, + method: 'delete_post', + freeze: true, + callback: () => { + frm.reload_doc(); + } + }); + } + ); + }); + } - add_post_btn: function(frm) { - frm.add_custom_button(__('Post Now'), function(){ - frappe.call({ - doc: frm.doc, - method: 'post', - freeze: true, - callback: function(r) { - frm.reload_doc(); - } - }); - }); - } + if (frm.doc.post_status !='Deleted') { + let html=''; + if (frm.doc.twitter) { + let color = frm.doc.twitter_post_id ? "green" : "red"; + let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted"; + html += `
+ Twitter : ${status} +
` ; + } + if (frm.doc.linkedin) { + let color = frm.doc.linkedin_post_id ? "green" : "red"; + let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted"; + html += `
+ LinkedIn : ${status} +
` ; + } + html = `
${html}
`; + frm.dashboard.set_headline_alert(html); + } + } + }, + + add_post_btn: function(frm) { + frm.add_custom_button(__('Post Now'), function() { + frappe.call({ + doc: frm.doc, + method: 'post', + freeze: true, + callback: function() { + frm.reload_doc(); + } + }); + }); + } }); \ No newline at end of file From cf548d0ccd52c015c2c7870c90bf1d635818f265 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 17 Aug 2021 18:33:43 +0530 Subject: [PATCH 7/9] fix: review chnages --- .../crm/doctype/linkedin_settings/linkedin_settings.py | 6 +++--- .../crm/doctype/social_media_post/social_media_post.js | 2 +- .../crm/doctype/social_media_post/social_media_post.py | 10 +++++----- .../crm/doctype/twitter_settings/twitter_settings.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py index 440442457777..9b88d78c1ff2 100644 --- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py +++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py @@ -192,10 +192,10 @@ def api_error(self, response): if response.status_code == 401: self.db_set("session_status", "Expired") frappe.db.commit() - frappe.throw(content["message"], title="LinkedIn Error - Unauthorized") + frappe.throw(content["message"], title=_("LinkedIn Error - Unauthorized")) elif response.status_code == 403: - frappe.msgprint(_("You Didn't have permission to access this API")) - frappe.throw(content["message"], title="LinkedIn Error - Access Denied") + frappe.msgprint(_("You didn't have permission to access this API")) + frappe.throw(content["message"], title=_("LinkedIn Error - Access Denied")) else: frappe.throw(response.reason, title=response.status_code) diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js index 5b9363757f10..aa953d74c5de 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post.js @@ -3,7 +3,7 @@ frappe.ui.form.on('Social Media Post', { validate: function(frm) { if (frm.doc.twitter === 0 && frm.doc.linkedin === 0) { - frappe.throw(__("Select atleast one Social Media from Share on.")); + frappe.throw(__("Select atleast one Social Media Platform to Share on.")); } if (frm.doc.scheduled_time) { let scheduled_time = new Date(frm.doc.scheduled_time); diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.py b/erpnext/crm/doctype/social_media_post/social_media_post.py index 0c0fa2efac09..95320bff5352 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.py +++ b/erpnext/crm/doctype/social_media_post/social_media_post.py @@ -11,7 +11,7 @@ class SocialMediaPost(Document): def validate(self): if (not self.twitter and not self.linkedin): - frappe.throw(_("Select atleast one Social Media from Share on.")) + frappe.throw(_("Select atleast one Social Media Platform to Share on.")) if self.scheduled_time: current_time = frappe.utils.now_datetime() @@ -19,8 +19,8 @@ def validate(self): if scheduled_time < current_time: frappe.throw(_("Scheduled Time must be a future time.")) - if self.text and len(self.text)>280: - frappe.throw(_("Tweet length Must be less than 280.")) + if self.text and len(self.text) > 280: + frappe.throw(_("Tweet length must be less than 280.")) def submit(self): if self.scheduled_time: @@ -40,7 +40,7 @@ def delete_post(self): linkedin = frappe.get_doc("LinkedIn Settings") linkedin.delete_post(self.linkedin_post_id) - frappe.db.set_value('Social Media Post', self.name, 'post_status', 'Deleted') + self.db_set('post_status', 'Deleted') @frappe.whitelist() def get_post(self): @@ -70,7 +70,7 @@ def post(self): except: self.db_set("post_status", "Error") title = _("Error while POSTING {0}").format(self.name) - frappe.log_error(message=frappe.get_traceback() , title=title) + frappe.log_error(message=frappe.get_traceback(), title=title) def process_scheduled_social_media_posts(): posts = frappe.get_list("Social Media Post", filters={"post_status": "Scheduled", "docstatus":1}, fields= ["name", "scheduled_time"]) diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.py b/erpnext/crm/doctype/twitter_settings/twitter_settings.py index b8a2c45e522d..47756560ec5f 100644 --- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py +++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py @@ -110,7 +110,7 @@ def api_error(self, e): if e.response.status_code == 401: self.db_set("session_status", "Expired") frappe.db.commit() - frappe.throw(content["message"],title="Twitter Error {0} : {1}".format(e.response.status_code, e.response.reason)) + frappe.throw(content["message"],title=_("Twitter Error {0} : {1}").format(e.response.status_code, e.response.reason)) @frappe.whitelist(allow_guest=True) From f271618f3ba1e78d192b4a2066ffc7de9a35f6f2 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 24 Aug 2021 17:50:09 +0530 Subject: [PATCH 8/9] fix: text trigger check --- .../crm/doctype/social_media_post/social_media_post.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js index cd6760c08806..9353eda090ac 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post.js @@ -16,9 +16,11 @@ frappe.ui.form.on('Social Media Post', { }, text: function(frm) { - frm.set_df_property('text', 'description', `${frm.doc.text.length}/280`); - frm.refresh_field('text'); - frm.trigger('validate_tweet_length'); + if (frm.doc.test) { + frm.set_df_property('text', 'description', `${frm.doc.text.length}/280`); + frm.refresh_field('text'); + frm.trigger('validate_tweet_length'); + } }, validate_tweet_length: function(frm) { From 31702c7c9291128b05546d015cd4154d762dc00e Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 24 Aug 2021 18:35:19 +0530 Subject: [PATCH 9/9] fix: sider issue --- .../crm/doctype/social_media_post/social_media_post.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js index 9353eda090ac..a8f5deea5350 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post.js @@ -16,11 +16,11 @@ frappe.ui.form.on('Social Media Post', { }, text: function(frm) { - if (frm.doc.test) { - frm.set_df_property('text', 'description', `${frm.doc.text.length}/280`); - frm.refresh_field('text'); - frm.trigger('validate_tweet_length'); - } + if (frm.doc.text) { + frm.set_df_property('text', 'description', `${frm.doc.text.length}/280`); + frm.refresh_field('text'); + frm.trigger('validate_tweet_length'); + } }, validate_tweet_length: function(frm) {