diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
index 263005ef6c..7aa0b77759 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(){
@@ -14,8 +14,9 @@ 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(
@@ -53,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({
@@ -67,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.json b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json
index 9eacb0011c..f882e36c32 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 d8c6fb4f90..9b88d78c1f 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -3,11 +3,12 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-import frappe, requests, json
+import frappe
+import requests
from frappe import _
-from frappe.utils import get_site_url, get_url_to_form, get_link_to_form
+from frappe.utils import 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):
@@ -42,11 +43,7 @@ class LinkedInSettings(Document):
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, {
@@ -55,16 +52,16 @@ class LinkedInSettings(Document):
"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")
@@ -82,9 +79,7 @@ class LinkedInSettings(Document):
}]
}
}
- 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:
@@ -100,24 +95,33 @@ class LinkedInSettings(Document):
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 = {
- "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": {}
},
"owner":"urn:li:organization:{0}".format(self.company_id),
- "subject": "Test Share Subject",
+ "subject": title,
"text": {
"text": text
}
}
+ reference_url = self.get_reference_url(text)
+ if reference_url:
+ body["content"] = {
+ "contentEntities": [
+ {
+ "entityLocation": reference_url
+ }
+ ]
+ }
+
if media_id:
body["content"]= {
"contentEntities": [{
@@ -141,20 +145,60 @@ class LinkedInSettings(Document):
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):
+ 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(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:
+ 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:
+ 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)
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 6fb0f975f4..a8f5deea53 100644
--- a/erpnext/crm/doctype/social_media_post/social_media_post.js
+++ b/erpnext/crm/doctype/social_media_post/social_media_post.js
@@ -1,67 +1,139 @@
// 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(__("Invalid Scheduled Time"));
- }
- }
- if (frm.doc.text?.length > 280){
- frappe.throw(__("Length Must be less than 280."))
- }
- },
- refresh: function(frm){
- if (frm.doc.docstatus === 1){
- 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);
- }
+ validate: function(frm) {
+ if (frm.doc.twitter === 0 && frm.doc.linkedin === 0) {
+ 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);
+ 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');
+ },
- 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);
- }
- }
+ text: function(frm) {
+ 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) {
+ 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');
+ },
+
+ 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) {
+ 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
+ });
+ }
+ }
+ });
+ }
+ },
+
+ 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();
+ }
+ });
+ }
+ );
+ });
+ }
+
+ 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();
+ }
+ });
+ });
+ }
});
-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();
- }
- })
-
-}
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 0a00dca280..98e78f949e 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\nDeleted\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-04-14 14:24:59.821223",
"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 ed1b583944..95320bff53 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 @@ import datetime
class SocialMediaPost(Document):
def validate(self):
+ if (not self.twitter and not self.linkedin):
+ frappe.throw(_("Select atleast one Social Media Platform to 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)
+
+ self.db_set('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,28 +63,22 @@ class SocialMediaPost(Document):
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)
- self.db_set("linkedin_post_id", linkedin_post.headers['X-RestLi-Id'].split(":")[-1])
+ 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")
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)
-
-@frappe.whitelist()
-def publish(doctype, name):
- sm_post = frappe.get_doc(doctype, name)
- sm_post.post()
- frappe.db.commit()
+ 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 c60b91a9a0..a8c8272ad0 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,10 +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"
- }[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 f6f431ca5c..112f3d4d1c 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?'),
@@ -14,10 +14,11 @@ 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;
@@ -28,7 +29,7 @@ frappe.ui.form.on('Twitter Settings', {
flag = true;
}
- if (flag){
+ if (flag) {
frm.dashboard.set_headline_alert(
`
@@ -38,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({
@@ -52,7 +53,7 @@ frappe.ui.form.on('Twitter Settings', {
});
}
},
- after_save: function(frm){
+ after_save: function(frm) {
frm.trigger("login");
}
});
diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.json b/erpnext/crm/doctype/twitter_settings/twitter_settings.json
index 36776e5c20..8d05877f06 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 1e1beab2d2..47756560ec 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
@@ -32,7 +32,9 @@ class TwitterSettings(Document):
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","")
@@ -50,11 +52,11 @@ class TwitterSettings(Document):
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):
- # 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)
+ 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(self.access_token, self.access_token_secret)
return tweepy.API(auth)
@@ -68,13 +70,13 @@ class TwitterSettings(Document):
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])
@@ -84,12 +86,32 @@ class TwitterSettings(Document):
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:
+ self.api_error(e)
+
+ def get_tweet(self, tweet_id):
+ api = self.get_api()
+ 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)
def callback(oauth_token = None, oauth_verifier = None):