diff --git a/frappe/email/doctype/newsletter/__init__.py b/frappe/email/doctype/newsletter/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/email/doctype/newsletter/exceptions.py b/frappe/email/doctype/newsletter/exceptions.py deleted file mode 100644 index ccce54c957..0000000000 --- a/frappe/email/doctype/newsletter/exceptions.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See LICENSE - -from frappe.exceptions import ValidationError - - -class NewsletterAlreadySentError(ValidationError): - pass - - -class NoRecipientFoundError(ValidationError): - pass - - -class NewsletterNotSavedError(ValidationError): - pass diff --git a/frappe/email/doctype/newsletter/newsletter.js b/frappe/email/doctype/newsletter/newsletter.js deleted file mode 100644 index 385b31dfab..0000000000 --- a/frappe/email/doctype/newsletter/newsletter.js +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -frappe.ui.form.on("Newsletter", { - refresh(frm) { - let doc = frm.doc; - let can_write = frappe.boot.user.can_write.includes(doc.doctype); - if (!frm.is_new() && !frm.is_dirty() && !doc.email_sent && can_write) { - frm.add_custom_button( - __("Send a test email"), - () => { - frm.events.send_test_email(frm); - }, - __("Preview") - ); - - frm.add_custom_button( - __("Check broken links"), - () => { - frm.dashboard.set_headline(__("Checking broken links...")); - frm.call("find_broken_links").then((r) => { - frm.dashboard.set_headline(""); - let links = r.message; - if (links && links.length) { - let html = - ""; - frm.dashboard.set_headline( - __("Following links are broken in the email content: {0}", [html]) - ); - } else { - frm.dashboard.set_headline( - __("No broken links found in the email content") - ); - setTimeout(() => { - frm.dashboard.set_headline(""); - }, 3000); - } - }); - }, - __("Preview") - ); - - frm.add_custom_button( - __("Send now"), - () => { - if (frm.doc.schedule_send) { - frappe.confirm( - __( - "This newsletter was scheduled to send on a later date. Are you sure you want to send it now?" - ), - function () { - frm.events.send_emails(frm); - } - ); - return; - } - frappe.confirm( - __("Are you sure you want to send this newsletter now?"), - () => { - frm.events.send_emails(frm); - } - ); - }, - __("Send") - ); - - frm.add_custom_button( - __("Schedule sending"), - () => { - frm.events.schedule_send_dialog(frm); - }, - __("Send") - ); - } - - frm.events.update_sending_status(frm); - - if (frm.is_new() && !doc.sender_email) { - let { fullname, email } = frappe.user_info(doc.owner); - frm.set_value("sender_email", email); - frm.set_value("sender_name", fullname); - } - - frm.trigger("update_schedule_message"); - }, - - send_emails(frm) { - frappe.dom.freeze(__("Queuing emails...")); - frm.call("send_emails").then(() => { - frm.refresh(); - frappe.dom.unfreeze(); - frappe.show_alert( - __("Queued {0} emails", [frappe.utils.shorten_number(frm.doc.total_recipients)]) - ); - }); - }, - - schedule_send_dialog(frm) { - let hours = frappe.utils.range(24); - let time_slots = hours.map((hour) => { - return `${(hour + "").padStart(2, "0")}:00`; - }); - let d = new frappe.ui.Dialog({ - title: __("Schedule Newsletter"), - fields: [ - { - label: __("Date"), - fieldname: "date", - fieldtype: "Date", - options: { - minDate: new Date(), - }, - reqd: true, - }, - { - label: __("Time"), - fieldname: "time", - fieldtype: "Select", - options: time_slots, - reqd: true, - }, - ], - primary_action_label: __("Schedule"), - primary_action({ date, time }) { - frm.set_value("schedule_sending", 1); - frm.set_value("schedule_send", `${date} ${time}:00`); - d.hide(); - frm.save(); - }, - secondary_action_label: __("Cancel Scheduling"), - secondary_action() { - frm.set_value("schedule_sending", 0); - frm.set_value("schedule_send", ""); - d.hide(); - frm.save(); - }, - }); - if (frm.doc.schedule_sending) { - let parts = frm.doc.schedule_send.split(" "); - if (parts.length === 2) { - let [date, time] = parts; - d.set_value("date", date); - d.set_value("time", time.slice(0, 5)); - } - } - d.show(); - }, - - send_test_email(frm) { - let d = new frappe.ui.Dialog({ - title: __("Send Test Email"), - fields: [ - { - label: __("Email"), - fieldname: "email", - fieldtype: "Data", - options: "Email", - }, - ], - primary_action_label: __("Send"), - primary_action({ email }) { - d.get_primary_btn().text(__("Sending...")).prop("disabled", true); - frm.call("send_test_email", { email }).then(() => { - d.get_primary_btn().text(__("Send again")).prop("disabled", false); - }); - }, - }); - d.show(); - }, - - async update_sending_status(frm) { - if (frm.doc.email_sent && frm.$wrapper.is(":visible") && !frm.waiting_for_request) { - frm.waiting_for_request = true; - let res = await frm.call("get_sending_status"); - frm.waiting_for_request = false; - let stats = res.message; - stats && frm.events.update_sending_progress(frm, stats); - if ( - stats.sent + stats.error >= frm.doc.total_recipients || - (!stats.total && !stats.emails_queued) - ) { - frm.sending_status && clearInterval(frm.sending_status); - frm.sending_status = null; - return; - } - } - - if (frm.sending_status) return; - frm.sending_status = setInterval(() => frm.events.update_sending_status(frm), 5000); - }, - - update_sending_progress(frm, stats) { - if (stats.sent + stats.error >= frm.doc.total_recipients || !frm.doc.email_sent) { - frm.doc.email_sent && frm.page.set_indicator(__("Sent"), "green"); - frm.dashboard.hide_progress(); - return; - } - if (stats.total) { - frm.page.set_indicator(__("Sending"), "blue"); - frm.dashboard.show_progress( - __("Sending emails"), - (stats.sent * 100) / frm.doc.total_recipients, - __("{0} of {1} sent", [stats.sent, frm.doc.total_recipients]) - ); - } else if (stats.emails_queued) { - frm.page.set_indicator(__("Queued"), "blue"); - } - }, - - on_hide(frm) { - if (frm.sending_status) { - clearInterval(frm.sending_status); - frm.sending_status = null; - } - }, - - update_schedule_message(frm) { - if (!frm.doc.email_sent && frm.doc.schedule_send) { - let datetime = frappe.datetime.global_date_format(frm.doc.schedule_send); - frm.dashboard.set_headline_alert( - __("This newsletter is scheduled to be sent on {0}", [datetime.bold()]) - ); - } else { - frm.dashboard.clear_headline(); - } - }, -}); diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json deleted file mode 100644 index 8def40c232..0000000000 --- a/frappe/email/doctype/newsletter/newsletter.json +++ /dev/null @@ -1,282 +0,0 @@ -{ - "actions": [], - "allow_guest_to_view": 1, - "allow_rename": 1, - "creation": "2013-01-10 16:34:31", - "description": "Create and send emails to a specific group of subscribers periodically.", - "doctype": "DocType", - "document_type": "Other", - "engine": "InnoDB", - "field_order": [ - "status_section", - "email_sent_at", - "column_break_3", - "total_recipients", - "column_break_12", - "total_views", - "email_sent", - "from_section", - "sender_name", - "column_break_5", - "sender_email", - "column_break_7", - "send_from", - "recipients", - "email_group", - "subject_section", - "subject", - "newsletter_content", - "content_type", - "message", - "message_md", - "message_html", - "campaign", - "attachments", - "send_unsubscribe_link", - "send_webview_link", - "schedule_settings_section", - "scheduled_to_send", - "schedule_sending", - "schedule_send", - "publish_as_a_web_page_section", - "published", - "route" - ], - "fields": [ - { - "fieldname": "email_group", - "fieldtype": "Table", - "in_standard_filter": 1, - "label": "Audience", - "options": "Newsletter Email Group", - "reqd": 1 - }, - { - "fieldname": "send_from", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "label": "Sender", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "email_sent", - "fieldtype": "Check", - "hidden": 1, - "label": "Email Sent", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "newsletter_content", - "fieldtype": "Section Break", - "label": "Content" - }, - { - "fieldname": "subject", - "fieldtype": "Small Text", - "in_global_search": 1, - "in_list_view": 1, - "label": "Subject", - "reqd": 1 - }, - { - "depends_on": "eval: doc.content_type === 'Rich Text'", - "fieldname": "message", - "fieldtype": "Text Editor", - "in_list_view": 1, - "label": "Message", - "mandatory_depends_on": "eval: doc.content_type === 'Rich Text'" - }, - { - "default": "1", - "fieldname": "send_unsubscribe_link", - "fieldtype": "Check", - "label": "Send Unsubscribe Link" - }, - { - "default": "0", - "fieldname": "published", - "fieldtype": "Check", - "label": "Published" - }, - { - "depends_on": "published", - "fieldname": "route", - "fieldtype": "Data", - "label": "Route", - "read_only": 1 - }, - { - "fieldname": "scheduled_to_send", - "fieldtype": "Int", - "hidden": 1, - "label": "Scheduled To Send" - }, - { - "fieldname": "recipients", - "fieldtype": "Section Break", - "label": "To" - }, - { - "depends_on": "eval: doc.schedule_sending", - "fieldname": "schedule_send", - "fieldtype": "Datetime", - "label": "Send Email At", - "read_only": 1, - "read_only_depends_on": "eval: doc.email_sent" - }, - { - "fieldname": "content_type", - "fieldtype": "Select", - "label": "Content Type", - "options": "Rich Text\nMarkdown\nHTML" - }, - { - "depends_on": "eval:doc.content_type === 'Markdown'", - "fieldname": "message_md", - "fieldtype": "Markdown Editor", - "label": "Message (Markdown)", - "mandatory_depends_on": "eval:doc.content_type === 'Markdown'" - }, - { - "depends_on": "eval:doc.content_type === 'HTML'", - "fieldname": "message_html", - "fieldtype": "HTML Editor", - "label": "Message (HTML)", - "mandatory_depends_on": "eval:doc.content_type === 'HTML'" - }, - { - "default": "0", - "fieldname": "schedule_sending", - "fieldtype": "Check", - "label": "Schedule sending at a later time", - "read_only_depends_on": "eval: doc.email_sent" - }, - { - "default": "0", - "fieldname": "send_webview_link", - "fieldtype": "Check", - "label": "Send Web View Link" - }, - { - "fieldname": "from_section", - "fieldtype": "Section Break", - "label": "From" - }, - { - "fieldname": "sender_name", - "fieldtype": "Data", - "label": "Sender Name" - }, - { - "fieldname": "sender_email", - "fieldtype": "Data", - "label": "Sender Email", - "options": "Email", - "reqd": 1 - }, - { - "fieldname": "column_break_5", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_7", - "fieldtype": "Column Break" - }, - { - "fieldname": "subject_section", - "fieldtype": "Section Break", - "label": "Subject" - }, - { - "fieldname": "publish_as_a_web_page_section", - "fieldtype": "Section Break", - "label": "Publish as a web page" - }, - { - "depends_on": "schedule_sending", - "fieldname": "schedule_settings_section", - "fieldtype": "Section Break", - "label": "Scheduled Sending" - }, - { - "fieldname": "attachments", - "fieldtype": "Table", - "label": "Attachments", - "options": "Newsletter Attachment" - }, - { - "fieldname": "email_sent_at", - "fieldtype": "Datetime", - "label": "Email Sent At", - "read_only": 1 - }, - { - "fieldname": "total_recipients", - "fieldtype": "Int", - "label": "Total Recipients", - "read_only": 1 - }, - { - "depends_on": "email_sent", - "fieldname": "status_section", - "fieldtype": "Section Break", - "label": "Status" - }, - { - "fieldname": "column_break_12", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "total_views", - "fieldtype": "Int", - "label": "Total Views", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "campaign", - "fieldtype": "Link", - "label": "Campaign", - "options": "UTM Campaign" - } - ], - "has_web_view": 1, - "icon": "fa fa-envelope", - "idx": 1, - "index_web_pages_for_search": 1, - "is_published_field": "published", - "links": [], - "make_attachments_public": 1, - "modified": "2024-11-12 12:41:02.569631", - "modified_by": "Administrator", - "module": "Email", - "name": "Newsletter", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Newsletter Manager", - "share": 1, - "write": 1 - } - ], - "route": "newsletters", - "sort_field": "creation", - "sort_order": "ASC", - "states": [], - "title_field": "subject", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py deleted file mode 100644 index 8f4702ff28..0000000000 --- a/frappe/email/doctype/newsletter/newsletter.py +++ /dev/null @@ -1,457 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See LICENSE - - -import frappe -import frappe.utils -from frappe import _ -from frappe.email.doctype.email_group.email_group import add_subscribers -from frappe.rate_limiter import rate_limit -from frappe.utils.safe_exec import is_job_queued -from frappe.utils.verified_command import get_signed_params, verify_request -from frappe.website.website_generator import WebsiteGenerator - -from .exceptions import NewsletterAlreadySentError, NewsletterNotSavedError, NoRecipientFoundError - - -class Newsletter(WebsiteGenerator): - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.email.doctype.newsletter_attachment.newsletter_attachment import NewsletterAttachment - from frappe.email.doctype.newsletter_email_group.newsletter_email_group import NewsletterEmailGroup - from frappe.types import DF - - attachments: DF.Table[NewsletterAttachment] - campaign: DF.Link | None - content_type: DF.Literal["Rich Text", "Markdown", "HTML"] - email_group: DF.Table[NewsletterEmailGroup] - email_sent: DF.Check - email_sent_at: DF.Datetime | None - message: DF.TextEditor | None - message_html: DF.HTMLEditor | None - message_md: DF.MarkdownEditor | None - published: DF.Check - route: DF.Data | None - schedule_send: DF.Datetime | None - schedule_sending: DF.Check - scheduled_to_send: DF.Int - send_from: DF.Data | None - send_unsubscribe_link: DF.Check - send_webview_link: DF.Check - sender_email: DF.Data - sender_name: DF.Data | None - subject: DF.SmallText - total_recipients: DF.Int - total_views: DF.Int - # end: auto-generated types - - def validate(self): - self.route = f"newsletters/{self.name}" - self.validate_sender_address() - self.validate_publishing() - self.validate_scheduling_date() - - @property - def newsletter_recipients(self) -> list[str]: - if getattr(self, "_recipients", None) is None: - self._recipients = self.get_recipients() - return self._recipients - - @frappe.whitelist() - def get_sending_status(self): - count_by_status = frappe.get_all( - "Email Queue", - filters={"reference_doctype": self.doctype, "reference_name": self.name}, - fields=["status", "count(name) as count"], - group_by="status", - order_by="status", - ) - sent = 0 - error = 0 - total = 0 - for row in count_by_status: - if row.status == "Sent": - sent = row.count - elif row.status == "Error": - error = row.count - total += row.count - emails_queued = is_job_queued( - job_name=frappe.utils.get_job_name("send_bulk_emails_for", self.doctype, self.name), - queue="long", - ) - return {"sent": sent, "error": error, "total": total, "emails_queued": emails_queued} - - @frappe.whitelist() - def send_test_email(self, email): - test_emails = frappe.utils.validate_email_address(email, throw=True) - self.send_newsletter(emails=test_emails, test_email=True) - frappe.msgprint(_("Test email sent to {0}").format(email), alert=True) - - @frappe.whitelist() - def find_broken_links(self): - import requests - from bs4 import BeautifulSoup - - html = self.get_message() - soup = BeautifulSoup(html, "html.parser") - links = soup.find_all("a") - images = soup.find_all("img") - broken_links = [] - for el in links + images: - url = el.attrs.get("href") or el.attrs.get("src") - try: - response = requests.head(url, verify=False, timeout=5) - if response.status_code >= 400: - broken_links.append(url) - except Exception: - broken_links.append(url) - return broken_links - - @frappe.whitelist() - def send_emails(self): - """queue sending emails to recipients""" - self.schedule_sending = False - self.schedule_send = None - self.queue_all() - - def validate_send(self): - """Validate if Newsletter can be sent.""" - self.validate_newsletter_status() - self.validate_newsletter_recipients() - - def validate_newsletter_status(self): - if self.email_sent: - frappe.throw(_("Newsletter has already been sent"), exc=NewsletterAlreadySentError) - - if self.get("__islocal"): - frappe.throw(_("Please save the Newsletter before sending"), exc=NewsletterNotSavedError) - - def validate_newsletter_recipients(self): - if not self.newsletter_recipients: - frappe.throw(_("Newsletter should have atleast one recipient"), exc=NoRecipientFoundError) - - def validate_sender_address(self): - """Validate self.send_from is a valid email address or not.""" - if self.sender_email: - frappe.utils.validate_email_address(self.sender_email, throw=True) - self.send_from = ( - f"{self.sender_name} <{self.sender_email}>" if self.sender_name else self.sender_email - ) - - def validate_publishing(self): - if self.send_webview_link and not self.published: - frappe.throw(_("Newsletter must be published to send webview link in email")) - - def validate_scheduling_date(self): - if getattr(frappe.flags, "is_scheduler_running", False): - return - - if ( - self.schedule_sending - and frappe.utils.get_datetime(self.schedule_send) < frappe.utils.now_datetime() - ): - frappe.throw(_("Past dates are not allowed for Scheduling.")) - - def get_linked_email_queue(self) -> list[str]: - """Get list of email queue linked to this newsletter.""" - return frappe.get_all( - "Email Queue", - filters={ - "reference_doctype": self.doctype, - "reference_name": self.name, - }, - pluck="name", - ) - - def get_queued_recipients(self) -> list[str]: - """Recipients who have already been queued for receiving the newsletter.""" - return frappe.get_all( - "Email Queue Recipient", - filters={ - "parent": ("in", self.get_linked_email_queue()), - }, - pluck="recipient", - ) - - def get_pending_recipients(self) -> list[str]: - """Get list of pending recipients of the newsletter. These - recipients may not have receive the newsletter in the previous iteration. - """ - - queued_recipients = set(self.get_queued_recipients()) - return [x for x in self.newsletter_recipients if x not in queued_recipients] - - def queue_all(self): - """Queue Newsletter to all the recipients generated from the `Email Group` table""" - self.validate() - self.validate_send() - - recipients = self.get_pending_recipients() - self.send_newsletter(emails=recipients) - - self.email_sent = True - self.email_sent_at = frappe.utils.now() - self.total_recipients = len(recipients) - self.save() - - def get_newsletter_attachments(self) -> list[dict[str, str]]: - """Get list of attachments on current Newsletter""" - return [{"file_url": row.attachment} for row in self.attachments] - - def send_newsletter(self, emails: list[str], test_email: bool = False): - """Trigger email generation for `emails` and add it in Email Queue.""" - attachments = self.get_newsletter_attachments() - sender = self.send_from or frappe.utils.get_formatted_email(self.owner) - args = self.as_dict() - args["message"] = self.get_message(medium="email") - - is_auto_commit_set = bool(frappe.db.auto_commit_on_many_writes) - frappe.db.auto_commit_on_many_writes = not frappe.in_test - - frappe.sendmail( - subject=self.subject, - sender=sender, - recipients=emails, - attachments=attachments, - template="newsletter", - add_unsubscribe_link=self.send_unsubscribe_link, - unsubscribe_method="/unsubscribe", - reference_doctype=self.doctype, - reference_name=self.name, - queue_separately=True, - send_priority=0, - args=args, - email_read_tracker_url=None - if test_email - else "/api/method/frappe.email.doctype.newsletter.newsletter.newsletter_email_read", - ) - - frappe.db.auto_commit_on_many_writes = is_auto_commit_set - - def get_message(self, medium=None) -> str: - message = self.message - if self.content_type == "Markdown": - message = frappe.utils.md_to_html(self.message_md) - if self.content_type == "HTML": - message = self.message_html - - html = frappe.render_template(message, {"doc": self.as_dict()}) - - return self.add_source(html, medium=medium) - - def add_source(self, html: str, medium="None") -> str: - """Add source to the site links in the newsletter content.""" - from bs4 import BeautifulSoup - - soup = BeautifulSoup(html, "html.parser") - - links = soup.find_all("a") - for link in links: - href = link.get("href") - if href and not href.startswith("#"): - if not frappe.utils.is_site_link(href): - continue - new_href = frappe.utils.add_trackers_to_url( - href, source="Newsletter", campaign=self.campaign, medium=medium - ) - link["href"] = new_href - - return str(soup) - - def get_recipients(self) -> list[str]: - """Get recipients from Email Group""" - emails = frappe.get_all( - "Email Group Member", - filters={"unsubscribed": 0, "email_group": ("in", self.get_email_groups())}, - pluck="email", - ) - return list(set(emails)) - - def get_email_groups(self) -> list[str]: - # wondering why the 'or'? i can't figure out why both aren't equivalent - @gavin - return [x.email_group for x in self.email_group] or frappe.get_all( - "Newsletter Email Group", - filters={"parent": self.name, "parenttype": "Newsletter"}, - pluck="email_group", - ) - - def get_attachments(self) -> list[dict[str, str]]: - return frappe.get_all( - "File", - fields=["name", "file_name", "file_url", "is_private"], - filters={ - "attached_to_name": self.name, - "attached_to_doctype": "Newsletter", - "is_private": 0, - }, - ) - - -def confirmed_unsubscribe(email, group): - """unsubscribe the email(user) from the mailing list(email_group)""" - frappe.flags.ignore_permissions = True - doc = frappe.get_doc("Email Group Member", {"email": email, "email_group": group}) - if not doc.unsubscribed: - doc.unsubscribed = 1 - doc.save(ignore_permissions=True) - - -@frappe.whitelist(allow_guest=True) -@rate_limit(limit=10, seconds=60 * 60) -def subscribe(email, email_group=None): - """API endpoint to subscribe an email to a particular email group. Triggers a confirmation email.""" - - if email_group is None: - email_group = get_default_email_group() - - # build subscription confirmation URL - api_endpoint = frappe.utils.get_url( - "/api/method/frappe.email.doctype.newsletter.newsletter.confirm_subscription" - ) - signed_params = get_signed_params({"email": email, "email_group": email_group}) - confirm_subscription_url = f"{api_endpoint}?{signed_params}" - - # fetch custom template if available - email_confirmation_template = frappe.db.get_value( - "Email Group", email_group, "confirmation_email_template" - ) - - # build email and send - if email_confirmation_template: - args = {"email": email, "confirmation_url": confirm_subscription_url, "email_group": email_group} - email_template = frappe.get_doc("Email Template", email_confirmation_template) - email_subject = email_template.subject - content = frappe.render_template(email_template.response, args) - else: - email_subject = _("Confirm Your Email") - translatable_content = ( - _("Thank you for your interest in subscribing to our updates"), - _("Please verify your Email Address"), - confirm_subscription_url, - _("Click here to verify"), - ) - content = """ -

{}. {}.

-

{}

- """.format(*translatable_content) - - frappe.sendmail( - email, - subject=email_subject, - content=content, - ) - - -@frappe.whitelist(allow_guest=True) -def confirm_subscription(email, email_group=None): - """API endpoint to confirm email subscription. - This endpoint is called when user clicks on the link sent to their mail. - """ - if not verify_request(): - return - - if email_group is None: - email_group = get_default_email_group() - - try: - group = frappe.get_doc("Email Group", email_group) - except frappe.DoesNotExistError: - group = frappe.get_doc({"doctype": "Email Group", "title": email_group}).insert( - ignore_permissions=True - ) - - frappe.flags.ignore_permissions = True - - add_subscribers(email_group, email) - frappe.db.commit() - - welcome_url = group.get_welcome_url(email) - - if welcome_url: - frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = welcome_url - else: - frappe.respond_as_web_page( - _("Confirmed"), - _("{0} has been successfully added to the Email Group.").format(email), - indicator_color="green", - ) - - -def get_list_context(context=None): - context.update( - { - "show_search": True, - "no_breadcrumbs": True, - "title": _("Newsletters"), - "filters": {"published": 1}, - "row_template": "email/doctype/newsletter/templates/newsletter_row.html", - } - ) - - -def send_scheduled_email(): - """Send scheduled newsletter to the recipients.""" - frappe.flags.is_scheduler_running = True - - scheduled_newsletter = frappe.get_all( - "Newsletter", - filters={ - "schedule_send": ("<=", frappe.utils.now_datetime()), - "email_sent": False, - "schedule_sending": True, - }, - ignore_ifnull=True, - pluck="name", - ) - - for newsletter_name in scheduled_newsletter: - try: - newsletter = frappe.get_doc("Newsletter", newsletter_name) - newsletter.queue_all() - - except Exception: - frappe.db.rollback() - - # wasn't able to send emails :( - frappe.db.set_value("Newsletter", newsletter_name, "email_sent", 0) - newsletter.log_error("Failed to send newsletter") - - if not frappe.in_test: - frappe.db.commit() - - frappe.flags.is_scheduler_running = False - - -@frappe.whitelist(allow_guest=True) -def newsletter_email_read(recipient_email=None, reference_doctype=None, reference_name=None): - if not (recipient_email and reference_name): - return - verify_request() - try: - doc = frappe.get_cached_doc("Newsletter", reference_name) - if doc.add_viewed(recipient_email, force=True, unique_views=True): - newsletter = frappe.qb.DocType("Newsletter") - ( - frappe.qb.update(newsletter) - .set(newsletter.total_views, newsletter.total_views + 1) - .where(newsletter.name == doc.name) - ).run() - - except Exception: - frappe.log_error( - title=f"Unable to mark as viewed for {recipient_email}", - reference_doctype="Newsletter", - reference_name=reference_name, - ) - - finally: - frappe.response.update(frappe.utils.get_imaginary_pixel_response()) - - -def get_default_email_group(): - return _("Website", lang=frappe.db.get_default("language")) diff --git a/frappe/email/doctype/newsletter/newsletter_list.js b/frappe/email/doctype/newsletter/newsletter_list.js deleted file mode 100644 index 71e9423b7e..0000000000 --- a/frappe/email/doctype/newsletter/newsletter_list.js +++ /dev/null @@ -1,12 +0,0 @@ -frappe.listview_settings["Newsletter"] = { - add_fields: ["subject", "email_sent", "schedule_sending"], - get_indicator: function (doc) { - if (doc.email_sent) { - return [__("Sent"), "green", "email_sent,=,1"]; - } else if (doc.schedule_sending) { - return [__("Scheduled"), "purple", "email_sent,=,0|schedule_sending,=,1"]; - } else { - return [__("Not Sent"), "gray", "email_sent,=,0"]; - } - }, -}; diff --git a/frappe/email/doctype/newsletter/templates/newsletter.html b/frappe/email/doctype/newsletter/templates/newsletter.html deleted file mode 100644 index 05f3560648..0000000000 --- a/frappe/email/doctype/newsletter/templates/newsletter.html +++ /dev/null @@ -1,65 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} {{ doc.subject }} {% endblock %} - -{% block page_content %} - - -
-
-
-

{{ doc.subject }}

-

- {{ frappe.format_date(doc.modified) }} -

-
-
- {{ doc.get_message(medium="web_page") }} -
-
- - {% if doc.attachments %} -
-
-
- {{ _("Attachments") }} -
-
-
-
- {% for attachment in doc.attachments %} -

- - {{ attachment.attachment }} - -

- {% endfor %} -
-
-
- {% endif %} - -
-{% endblock %} \ No newline at end of file diff --git a/frappe/email/doctype/newsletter/templates/newsletter_row.html b/frappe/email/doctype/newsletter/templates/newsletter_row.html deleted file mode 100644 index 503fadb1ea..0000000000 --- a/frappe/email/doctype/newsletter/templates/newsletter_row.html +++ /dev/null @@ -1,15 +0,0 @@ -
- -
-
- {{ doc.subject }} -
-
-
- {{ frappe.utils.pretty_date(doc.modified) }} -
-
-
-
-
\ No newline at end of file diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py deleted file mode 100644 index c7ee147410..0000000000 --- a/frappe/email/doctype/newsletter/test_newsletter.py +++ /dev/null @@ -1,252 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See LICENSE - -from random import choice -from unittest.mock import MagicMock, PropertyMock, patch - -import frappe -from frappe.email.doctype.newsletter.exceptions import ( - NewsletterAlreadySentError, - NoRecipientFoundError, -) -from frappe.email.doctype.newsletter.newsletter import ( - Newsletter, - confirmed_unsubscribe, - send_scheduled_email, -) -from frappe.email.queue import flush -from frappe.tests import IntegrationTestCase -from frappe.utils import add_days, getdate - -emails = [ - "test_subscriber1@example.com", - "test_subscriber2@example.com", - "test_subscriber3@example.com", - "test1@example.com", -] -newsletters = [] - - -def get_dotted_path(obj: type) -> str: - klass = obj.__class__ - module = klass.__module__ - if module == "builtins": - return klass.__qualname__ # avoid outputs like 'builtins.str' - return f"{module}.{klass.__qualname__}" - - -class TestNewsletterMixin: - def setUp(self): - frappe.set_user("Administrator") - self.setup_email_group() - - def tearDown(self): - frappe.set_user("Administrator") - for newsletter in newsletters: - frappe.db.delete( - "Email Queue", - { - "reference_doctype": "Newsletter", - "reference_name": newsletter, - }, - ) - frappe.delete_doc("Newsletter", newsletter) - frappe.db.delete("Newsletter Email Group", {"parent": newsletter}) - newsletters.remove(newsletter) - - def setup_email_group(self): - if not frappe.db.exists("Email Group", "_Test Email Group"): - frappe.get_doc({"doctype": "Email Group", "title": "_Test Email Group"}).insert() - - for email in emails: - doctype = "Email Group Member" - email_filters = {"email": email, "email_group": "_Test Email Group"} - - savepoint = "setup_email_group" - frappe.db.savepoint(savepoint) - - try: - frappe.get_doc( - { - "doctype": doctype, - **email_filters, - } - ).insert(ignore_if_duplicate=True) - except Exception: - frappe.db.rollback(save_point=savepoint) - frappe.db.set_value(doctype, email_filters, "unsubscribed", 0) - - frappe.db.release_savepoint(savepoint) - - def send_newsletter(self, published=0, schedule_send=None) -> str | None: - frappe.db.delete("Email Queue") - frappe.db.delete("Email Queue Recipient") - frappe.db.delete("Newsletter") - - newsletter_options = { - "published": published, - "schedule_sending": bool(schedule_send), - "schedule_send": schedule_send, - } - newsletter = self.get_newsletter(**newsletter_options) - - if schedule_send: - send_scheduled_email() - else: - newsletter.send_emails() - return newsletter.name - - return newsletter - - @staticmethod - def get_newsletter(**kwargs) -> "Newsletter": - """Generate and return Newsletter object""" - doctype = "Newsletter" - newsletter_content = { - "subject": "_Test Newsletter", - "sender_name": "Test Sender", - "sender_email": "test_sender@example.com", - "content_type": "Rich Text", - "message": "Testing my news.", - } - similar_newsletters = frappe.get_all(doctype, newsletter_content, pluck="name") - - for similar_newsletter in similar_newsletters: - frappe.delete_doc(doctype, similar_newsletter) - - newsletter = frappe.get_doc({"doctype": doctype, **newsletter_content, **kwargs}) - newsletter.append("email_group", {"email_group": "_Test Email Group"}) - newsletter.save(ignore_permissions=True) - newsletter.reload() - newsletters.append(newsletter.name) - - attached_files = frappe.get_all( - "File", - { - "attached_to_doctype": newsletter.doctype, - "attached_to_name": newsletter.name, - }, - pluck="name", - ) - for file in attached_files: - frappe.delete_doc("File", file) - - return newsletter - - -class TestNewsletter(TestNewsletterMixin, IntegrationTestCase): - def test_send(self): - self.send_newsletter() - - email_queue_list = [frappe.get_doc("Email Queue", e.name) for e in frappe.get_all("Email Queue")] - self.assertEqual(len(email_queue_list), 4) - - recipients = {e.recipients[0].recipient for e in email_queue_list} - self.assertTrue(set(emails).issubset(recipients)) - - def test_unsubscribe(self): - name = self.send_newsletter() - to_unsubscribe = choice(emails) - group = frappe.get_all("Newsletter Email Group", filters={"parent": name}, fields=["email_group"]) - - flush() - confirmed_unsubscribe(to_unsubscribe, group[0].email_group) - - name = self.send_newsletter() - email_queue_list = [frappe.get_doc("Email Queue", e.name) for e in frappe.get_all("Email Queue")] - self.assertEqual(len(email_queue_list), 3) - recipients = [e.recipients[0].recipient for e in email_queue_list] - - for email in emails: - if email != to_unsubscribe: - self.assertTrue(email in recipients) - - def test_schedule_send(self): - newsletter = self.send_newsletter(schedule_send=add_days(getdate(), 1)) - newsletter.db_set("schedule_send", add_days(getdate(), -1)) # Set date in past - send_scheduled_email() - - email_queue_list = [frappe.get_doc("Email Queue", e.name) for e in frappe.get_all("Email Queue")] - self.assertEqual(len(email_queue_list), 4) - recipients = [e.recipients[0].recipient for e in email_queue_list] - for email in emails: - self.assertTrue(email in recipients) - - def test_newsletter_send_test_email(self): - """Test "Send Test Email" functionality of Newsletter""" - newsletter = self.get_newsletter() - test_email = choice(emails) - newsletter.send_test_email(test_email) - - self.assertFalse(newsletter.email_sent) - newsletter.save = MagicMock() - self.assertFalse(newsletter.save.called) - # check if the test email is in the queue - email_queue = frappe.get_all( - "Email Queue", - filters=[ - ["reference_doctype", "=", "Newsletter"], - ["reference_name", "=", newsletter.name], - ["Email Queue Recipient", "recipient", "=", test_email], - ], - ) - self.assertTrue(email_queue) - - def test_newsletter_status(self): - """Test for Newsletter's stats on onload event""" - newsletter = self.get_newsletter() - newsletter.email_sent = True - result = newsletter.get_sending_status() - self.assertTrue("total" in result) - self.assertTrue("sent" in result) - - def test_already_sent_newsletter(self): - newsletter = self.get_newsletter() - newsletter.send_emails() - - with self.assertRaises(NewsletterAlreadySentError): - newsletter.send_emails() - - def test_newsletter_with_no_recipient(self): - newsletter = self.get_newsletter() - property_path = f"{get_dotted_path(newsletter)}.newsletter_recipients" - - with patch(property_path, new_callable=PropertyMock) as mock_newsletter_recipients: - mock_newsletter_recipients.return_value = [] - with self.assertRaises(NoRecipientFoundError): - newsletter.send_emails() - - def test_send_scheduled_email_error_handling(self): - newsletter = self.get_newsletter(schedule_send=add_days(getdate(), -1)) - job_path = "frappe.email.doctype.newsletter.newsletter.Newsletter.queue_all" - m = MagicMock(side_effect=frappe.OutgoingEmailError) - - with self.assertRaises(frappe.OutgoingEmailError): - with patch(job_path, new_callable=m): - send_scheduled_email() - - newsletter.reload() - self.assertEqual(newsletter.email_sent, 0) - - def test_retry_partially_sent_newsletter(self): - frappe.db.delete("Email Queue") - frappe.db.delete("Email Queue Recipient") - frappe.db.delete("Newsletter") - - newsletter = self.get_newsletter() - newsletter.send_emails() - email_queue_list = [frappe.get_doc("Email Queue", e.name) for e in frappe.get_all("Email Queue")] - self.assertEqual(len(email_queue_list), 4) - - # delete a queue document to emulate partial send - queue_recipient_name = email_queue_list[0].recipients[0].recipient - email_queue_list[0].delete() - newsletter.email_sent = False - - # make sure the pending recipient is only the one which has been deleted - self.assertEqual(newsletter.get_pending_recipients(), [queue_recipient_name]) - - # retry - newsletter.send_emails() - self.assertEqual(frappe.db.count("Email Queue"), 4) - self.assertTrue(newsletter.email_sent) diff --git a/frappe/email/doctype/newsletter_attachment/__init__.py b/frappe/email/doctype/newsletter_attachment/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/email/doctype/newsletter_attachment/newsletter_attachment.json b/frappe/email/doctype/newsletter_attachment/newsletter_attachment.json deleted file mode 100644 index 47ff57b235..0000000000 --- a/frappe/email/doctype/newsletter_attachment/newsletter_attachment.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "actions": [], - "allow_rename": 1, - "creation": "2021-12-06 16:37:40.652468", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "attachment" - ], - "fields": [ - { - "fieldname": "attachment", - "fieldtype": "Attach", - "in_list_view": 1, - "label": "Attachment", - "reqd": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2024-03-23 16:03:31.101104", - "modified_by": "Administrator", - "module": "Email", - "name": "Newsletter Attachment", - "owner": "Administrator", - "permissions": [], - "sort_field": "creation", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file diff --git a/frappe/email/doctype/newsletter_attachment/newsletter_attachment.py b/frappe/email/doctype/newsletter_attachment/newsletter_attachment.py deleted file mode 100644 index e0a3f3aa56..0000000000 --- a/frappe/email/doctype/newsletter_attachment/newsletter_attachment.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class NewsletterAttachment(Document): - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - attachment: DF.Attach - parent: DF.Data - parentfield: DF.Data - parenttype: DF.Data - # end: auto-generated types - - pass diff --git a/frappe/email/doctype/newsletter_email_group/__init__.py b/frappe/email/doctype/newsletter_email_group/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/email/doctype/newsletter_email_group/newsletter_email_group.json b/frappe/email/doctype/newsletter_email_group/newsletter_email_group.json deleted file mode 100644 index b8f1dea630..0000000000 --- a/frappe/email/doctype/newsletter_email_group/newsletter_email_group.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "actions": [], - "creation": "2017-02-26 16:20:52.654136", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "email_group", - "total_subscribers" - ], - "fields": [ - { - "columns": 7, - "fieldname": "email_group", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Email Group", - "options": "Email Group", - "reqd": 1 - }, - { - "columns": 3, - "fetch_from": "email_group.total_subscribers", - "fieldname": "total_subscribers", - "fieldtype": "Read Only", - "in_list_view": 1, - "label": "Total Subscribers" - } - ], - "istable": 1, - "links": [], - "modified": "2024-03-23 16:03:31.190219", - "modified_by": "Administrator", - "module": "Email", - "name": "Newsletter Email Group", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "creation", - "sort_order": "DESC", - "states": [], - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/email/doctype/newsletter_email_group/newsletter_email_group.py b/frappe/email/doctype/newsletter_email_group/newsletter_email_group.py deleted file mode 100644 index 59b06e446d..0000000000 --- a/frappe/email/doctype/newsletter_email_group/newsletter_email_group.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies and contributors -# License: MIT. See LICENSE - -from frappe.model.document import Document - - -class NewsletterEmailGroup(Document): - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - email_group: DF.Link - parent: DF.Data - parentfield: DF.Data - parenttype: DF.Data - total_subscribers: DF.ReadOnly | None - # end: auto-generated types - - pass diff --git a/frappe/templates/emails/newsletter.html b/frappe/templates/emails/newsletter.html deleted file mode 100644 index 27b22023ac..0000000000 --- a/frappe/templates/emails/newsletter.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
- {{ message }} -
-
- -{% if published and send_webview_link %} -
-
- View this email on the web -
-
-{% endif %} \ No newline at end of file diff --git a/frappe/website/doctype/utm_campaign/__init__.py b/frappe/website/doctype/utm_campaign/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/website/doctype/utm_campaign/test_utm_campaign.py b/frappe/website/doctype/utm_campaign/test_utm_campaign.py deleted file mode 100644 index c5be69c032..0000000000 --- a/frappe/website/doctype/utm_campaign/test_utm_campaign.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2024, Frappe Technologies and Contributors -# See license.txt - -# import frappe -from frappe.tests import IntegrationTestCase - - -class TestUTMCampaign(IntegrationTestCase): - pass diff --git a/frappe/website/doctype/utm_campaign/utm_campaign.js b/frappe/website/doctype/utm_campaign/utm_campaign.js deleted file mode 100644 index a11b27e381..0000000000 --- a/frappe/website/doctype/utm_campaign/utm_campaign.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2024, Frappe Technologies and contributors -// For license information, please see license.txt - -// frappe.ui.form.on("UTM Campaign", { -// refresh(frm) { - -// }, -// }); diff --git a/frappe/website/doctype/utm_campaign/utm_campaign.json b/frappe/website/doctype/utm_campaign/utm_campaign.json deleted file mode 100644 index 85de90ec5d..0000000000 --- a/frappe/website/doctype/utm_campaign/utm_campaign.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "actions": [], - "autoname": "prompt", - "creation": "2023-03-20 22:36:45.058045", - "default_view": "List", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "slug", - "campaign_description" - ], - "fields": [ - { - "allow_in_quick_entry": 1, - "fieldname": "campaign_description", - "fieldtype": "Small Text", - "in_filter": 1, - "in_list_view": 1, - "label": "Campaign Description (Optional)" - }, - { - "fieldname": "slug", - "fieldtype": "Data", - "label": "Slug", - "unique": 1 - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2024-06-28 15:05:14.714600", - "modified_by": "Administrator", - "module": "Website", - "name": "UTM Campaign", - "naming_rule": "Set by user", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Newsletter Manager", - "share": 1, - "write": 1 - }, - { - "role": "Desk User", - "select": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Marketing Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "creation", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file diff --git a/frappe/website/doctype/utm_campaign/utm_campaign.py b/frappe/website/doctype/utm_campaign/utm_campaign.py deleted file mode 100644 index feedfc5434..0000000000 --- a/frappe/website/doctype/utm_campaign/utm_campaign.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2023, Frappe Technologies and contributors -# For license information, please see license.txt - -import frappe -from frappe.model.document import Document - - -class UTMCampaign(Document): - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - campaign_description: DF.SmallText | None - slug: DF.Data | None - # end: auto-generated types - - def before_save(self): - if self.slug: - self.slug = frappe.utils.slug(self.slug) diff --git a/frappe/website/doctype/utm_medium/__init__.py b/frappe/website/doctype/utm_medium/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/website/doctype/utm_medium/test_utm_medium.py b/frappe/website/doctype/utm_medium/test_utm_medium.py deleted file mode 100644 index 5d9e38e843..0000000000 --- a/frappe/website/doctype/utm_medium/test_utm_medium.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2024, Frappe Technologies and Contributors -# See license.txt - -# import frappe -from frappe.tests import IntegrationTestCase - - -class TestUTMMedium(IntegrationTestCase): - pass diff --git a/frappe/website/doctype/utm_medium/utm_medium.js b/frappe/website/doctype/utm_medium/utm_medium.js deleted file mode 100644 index bbea0c0ec9..0000000000 --- a/frappe/website/doctype/utm_medium/utm_medium.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2024, Frappe Technologies and contributors -// For license information, please see license.txt - -// frappe.ui.form.on("UTM Medium", { -// refresh(frm) { - -// }, -// }); diff --git a/frappe/website/doctype/utm_medium/utm_medium.json b/frappe/website/doctype/utm_medium/utm_medium.json deleted file mode 100644 index 1343769434..0000000000 --- a/frappe/website/doctype/utm_medium/utm_medium.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "actions": [], - "allow_rename": 1, - "autoname": "prompt", - "creation": "2024-06-28 09:46:10.102141", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "slug", - "description" - ], - "fields": [ - { - "fieldname": "description", - "fieldtype": "Small Text", - "label": "Description" - }, - { - "fieldname": "slug", - "fieldtype": "Data", - "label": "Slug", - "unique": 1 - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2024-06-28 15:04:51.679189", - "modified_by": "Administrator", - "module": "Website", - "name": "UTM Medium", - "naming_rule": "Set by user", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Marketing Manager", - "share": 1, - "write": 1 - }, - { - "email": 1, - "export": 1, - "print": 1, - "report": 1, - "role": "Desk User", - "select": 1, - "share": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file diff --git a/frappe/website/doctype/utm_medium/utm_medium.py b/frappe/website/doctype/utm_medium/utm_medium.py deleted file mode 100644 index ae22177879..0000000000 --- a/frappe/website/doctype/utm_medium/utm_medium.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2024, Frappe Technologies and contributors -# For license information, please see license.txt - -import frappe -from frappe.model.document import Document - - -class UTMMedium(Document): - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - description: DF.SmallText | None - slug: DF.Data | None - # end: auto-generated types - - def before_save(self): - if self.slug: - self.slug = frappe.utils.slug(self.slug) diff --git a/frappe/website/doctype/utm_source/__init__.py b/frappe/website/doctype/utm_source/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/website/doctype/utm_source/test_utm_source.py b/frappe/website/doctype/utm_source/test_utm_source.py deleted file mode 100644 index f08e2d1185..0000000000 --- a/frappe/website/doctype/utm_source/test_utm_source.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2024, Frappe Technologies and Contributors -# See license.txt - -# import frappe -from frappe.tests import IntegrationTestCase - - -class TestUTMSource(IntegrationTestCase): - pass diff --git a/frappe/website/doctype/utm_source/utm_source.js b/frappe/website/doctype/utm_source/utm_source.js deleted file mode 100644 index cb04f46f45..0000000000 --- a/frappe/website/doctype/utm_source/utm_source.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2024, Frappe Technologies and contributors -// For license information, please see license.txt - -// frappe.ui.form.on("UTM Source", { -// refresh(frm) { - -// }, -// }); diff --git a/frappe/website/doctype/utm_source/utm_source.json b/frappe/website/doctype/utm_source/utm_source.json deleted file mode 100644 index 270d09aaec..0000000000 --- a/frappe/website/doctype/utm_source/utm_source.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "actions": [], - "allow_rename": 1, - "autoname": "prompt", - "creation": "2024-06-28 09:42:04.478212", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "slug", - "description" - ], - "fields": [ - { - "fieldname": "description", - "fieldtype": "Small Text", - "label": "Description" - }, - { - "fieldname": "slug", - "fieldtype": "Data", - "label": "Slug", - "unique": 1 - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2024-06-28 15:04:59.643513", - "modified_by": "Administrator", - "module": "Website", - "name": "UTM Source", - "naming_rule": "Set by user", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Marketing Manager", - "share": 1, - "write": 1 - }, - { - "email": 1, - "export": 1, - "print": 1, - "report": 1, - "role": "Desk User", - "select": 1, - "share": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file diff --git a/frappe/website/doctype/utm_source/utm_source.py b/frappe/website/doctype/utm_source/utm_source.py deleted file mode 100644 index 715997c503..0000000000 --- a/frappe/website/doctype/utm_source/utm_source.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2024, Frappe Technologies and contributors -# For license information, please see license.txt - -import frappe -from frappe.model.document import Document - - -class UTMSource(Document): - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - description: DF.SmallText | None - slug: DF.Data | None - # end: auto-generated types - - def before_save(self): - if self.slug: - self.slug = frappe.utils.slug(self.slug) diff --git a/frappe/www/unsubscribe.html b/frappe/www/unsubscribe.html deleted file mode 100644 index e3c59c8016..0000000000 --- a/frappe/www/unsubscribe.html +++ /dev/null @@ -1,121 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} Unsubscribe from Newsletter {% endblock %} - -{% block navbar %}{% endblock %} -{% block footer %}{% endblock %} - -{% block page_content %} - - - - -{% if status == "waiting_for_confirmation" %} - -
-
-
{{_("Unsubscribe")}}
-
Select groups you wish to unsubscribe from ({{ email }})
- - {% if email_groups|length > 5 %} - - - {% endif %} -
- {% if email_groups %} -
- - - -
- {% for group in email_groups %} -
- -
- {% endfor %} -
-
- -
-
-
- {% else %} -
- You are not registered to any mailing list. - {{ email }} -
- {% endif %} - - - -{% elif status == "unsubscribed" %} - -
-
-
Unsubscribed
-
-
- You have been unsubscribed from selected mailing list. -
-
- -{% else %} - -
-
-
Unsubscribe
-
-
- Invalid request -
-
-{% endif %} - -{% endblock %} - -{% block style %} - -{% endblock %} - diff --git a/frappe/www/unsubscribe.py b/frappe/www/unsubscribe.py deleted file mode 100644 index 942ac9afd4..0000000000 --- a/frappe/www/unsubscribe.py +++ /dev/null @@ -1,48 +0,0 @@ -import frappe -from frappe.email.doctype.newsletter.newsletter import confirmed_unsubscribe -from frappe.utils.verified_command import verify_request - -no_cache = True - - -def get_context(context): - frappe.flags.ignore_permissions = True - # Called for confirmation. - if "email" in frappe.form_dict and frappe.request.method == "GET": - if verify_request(): - user_email = frappe.form_dict["email"] - context.email = user_email - title = frappe.form_dict.get("name") - context.email_groups = get_email_groups(user_email) - context.current_group = get_current_groups(title) - context.status = "waiting_for_confirmation" - print(context) - - # Called when form is submitted. - elif "user_email" in frappe.form_dict and frappe.request.method == "POST": - context.status = "unsubscribed" - email = frappe.form_dict["user_email"] - email_group = get_email_groups(email) - for group in email_group: - if group.email_group in frappe.form_dict: - confirmed_unsubscribe(email, group.email_group) - - # Called on Invalid or unsigned request. - else: - context.status = "invalid" - - -def get_email_groups(user_email): - # Return the all email_groups in which the email has been registered. - return frappe.get_all( - "Email Group Member", fields=["email_group"], filters={"email": user_email, "unsubscribed": 0} - ) - - -def get_current_groups(name): - # Return current group by which the mail has been sent. - return frappe.get_all( - "Newsletter Email Group", - fields=["email_group"], - filters={"parent": name, "parenttype": "Newsletter"}, - )