refactor: seperate blogs into a seperate app (#32737)

* fix: remove doctypes,workspace blocks, files

* fix: minor python tests and UI tests

* fix: remove blog post from tests

* fix: remove blogger as role for tests

* fix: add check for if doctype exists

* fix: ui test

* fix: more cleanup

* fix: cleanup comments and fix test_query

* fix: resolve conflicts

* fix: add warning and handle comments
This commit is contained in:
Soham Kulkarni 2025-07-28 14:35:02 +05:30 committed by GitHub
parent 20e4815d81
commit 64db88228f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 572 additions and 2491 deletions

View file

@ -4,9 +4,9 @@ context("Awesome Bar", () => {
cy.login(); cy.login();
cy.visit("/app/todo"); // Make sure ToDo filters are cleared. cy.visit("/app/todo"); // Make sure ToDo filters are cleared.
cy.clear_filters(); cy.clear_filters();
cy.visit("/app/blog-post"); // Make sure Blog Post filters are cleared. cy.visit("/app/web-page"); // Make sure Blog Post filters are cleared.
cy.clear_filters(); cy.clear_filters();
cy.visit("/app/website"); // Go to some other page. cy.visit("/app/build"); // Go to some other page.
}); });
beforeEach(() => { beforeEach(() => {
@ -53,19 +53,19 @@ context("Awesome Bar", () => {
}); });
it("navigates to another doctype, filter not bleeding", () => { it("navigates to another doctype, filter not bleeding", () => {
cy.get("@awesome_bar").type("blog post"); cy.get("@awesome_bar").type("web page");
cy.wait(150); // Wait a bit before hitting enter. cy.wait(150); // Wait a bit before hitting enter.
cy.get("@awesome_bar").type("{enter}"); cy.get("@awesome_bar").type("{enter}");
cy.get(".title-text").should("contain", "Blog Post"); cy.get(".title-text").should("contain", "Web Page");
cy.wait(200); // Wait a bit longer before checking the filter. cy.wait(200); // Wait a bit longer before checking the filter.
cy.location("search").should("be.empty"); cy.location("search").should("be.empty");
}); });
it("navigates to new form", () => { it("navigates to new form", () => {
cy.get("@awesome_bar").type("new blog post"); cy.get("@awesome_bar").type("new web page");
cy.wait(150); // Wait a bit before hitting enter cy.wait(150); // Wait a bit before hitting enter
cy.get("@awesome_bar").type("{enter}"); cy.get("@awesome_bar").type("{enter}");
cy.get(".title-text:visible").should("have.text", "New Blog Post"); cy.get(".title-text:visible").should("have.text", "New Web Page");
}); });
it("calculates math expressions", () => { it("calculates math expressions", () => {

View file

@ -43,7 +43,7 @@ context("Sidebar", () => {
.window() .window()
.its("frappe") .its("frappe")
.then((frappe) => { .then((frappe) => {
return frappe.call("frappe.tests.ui_test_helpers.create_blog_post"); return frappe.call("frappe.tests.ui_test_helpers.create_doctype_for_attachment");
}); });
}); });
@ -53,7 +53,7 @@ context("Sidebar", () => {
}).then((todo) => { }).then((todo) => {
verify_attachment_visibility(`todo/${todo.message.name}`, true); verify_attachment_visibility(`todo/${todo.message.name}`, true);
}); });
verify_attachment_visibility("blog-post/test-blog-attachment-post", false); verify_attachment_visibility("test-blog-category/_Test Blog Category 2", false);
}); });
it("Verify attachment accessibility UX", () => { it("Verify attachment accessibility UX", () => {

View file

@ -8,7 +8,7 @@ context("Table MultiSelect", () => {
it("select value from multiselect dropdown", () => { it("select value from multiselect dropdown", () => {
cy.new_form("Assignment Rule"); cy.new_form("Assignment Rule");
cy.fill_field("__newname", name); cy.fill_field("__newname", name);
cy.fill_field("document_type", "Blog Post"); cy.fill_field("document_type", "Web Page");
cy.get(".section-head").contains("Assignment Rules").scrollIntoView(); cy.get(".section-head").contains("Assignment Rules").scrollIntoView();
cy.fill_field("assign_condition", 'status=="Open"', "Code"); cy.fill_field("assign_condition", 'status=="Open"', "Code");
cy.get('input[data-fieldname="users"]').focus().as("input"); cy.get('input[data-fieldname="users"]').focus().as("input");

View file

@ -5,11 +5,16 @@ import json
import frappe import frappe
from frappe.templates.includes.comments.comments import add_comment from frappe.templates.includes.comments.comments import add_comment
from frappe.tests import IntegrationTestCase from frappe.tests import IntegrationTestCase
from frappe.tests.test_helpers import setup_for_tests
from frappe.tests.test_model_utils import set_user from frappe.tests.test_model_utils import set_user
from frappe.website.doctype.blog_post.test_blog_post import make_test_blog
EXTRA_TEST_RECORD_DEPENDENCIES = ["Web Page"]
class TestComment(IntegrationTestCase): class TestComment(IntegrationTestCase):
def setUp(self):
setup_for_tests()
def test_comment_creation(self): def test_comment_creation(self):
test_doc = frappe.get_doc(doctype="ToDo", description="test") test_doc = frappe.get_doc(doctype="ToDo", description="test")
test_doc.insert() test_doc.insert()
@ -42,16 +47,16 @@ class TestComment(IntegrationTestCase):
# test via blog # test via blog
def test_public_comment(self): def test_public_comment(self):
test_blog = make_test_blog() test_blog = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) frappe.db.delete("Comment", {"reference_doctype": "Test Blog Post"})
add_comment_args = { add_comment_args = {
"comment": "Good comment with 10 chars", "comment": "Good comment with 10 chars",
"comment_email": "test@test.com", "comment_email": "test@test.com",
"comment_by": "Good Tester", "comment_by": "Good Tester",
"reference_doctype": test_blog.doctype, "reference_doctype": test_blog.doctype,
"reference_name": test_blog.name, "reference_name": test_blog.name,
"route": test_blog.route, "route": f"blog/{test_blog.doctype}/{test_blog.name}",
} }
add_comment(**add_comment_args) add_comment(**add_comment_args)
@ -64,7 +69,7 @@ class TestComment(IntegrationTestCase):
1, 1,
) )
frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) frappe.db.delete("Comment", {"reference_doctype": "Test Blog Post"})
add_comment_args.update(comment="pleez vizits my site http://mysite.com", comment_by="bad commentor") add_comment_args.update(comment="pleez vizits my site http://mysite.com", comment_by="bad commentor")
add_comment(**add_comment_args) add_comment(**add_comment_args)
@ -81,7 +86,7 @@ class TestComment(IntegrationTestCase):
) )
# test for filtering html and css injection elements # test for filtering html and css injection elements
frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) frappe.db.delete("Comment", {"reference_doctype": "Test Blog Post"})
add_comment_args.update(comment="<script>alert(1)</script>Comment", comment_by="hacker") add_comment_args.update(comment="<script>alert(1)</script>Comment", comment_by="hacker")
add_comment(**add_comment_args) add_comment(**add_comment_args)
@ -96,26 +101,10 @@ class TestComment(IntegrationTestCase):
test_blog.delete() test_blog.delete()
@IntegrationTestCase.change_settings("Blog Settings", {"allow_guest_to_comment": 0})
def test_guest_cannot_comment(self):
test_blog = make_test_blog()
with set_user("Guest"):
self.assertEqual(
add_comment(
comment="Good comment with 10 chars",
comment_email="mail@example.org",
comment_by="Good Tester",
reference_doctype="Blog Post",
reference_name=test_blog.name,
route=test_blog.route,
),
None,
)
def test_user_not_logged_in(self): def test_user_not_logged_in(self):
some_system_user = frappe.db.get_value("User", {"name": ("not in", frappe.STANDARD_USERS)}) some_system_user = frappe.db.get_value("User", {"name": ("not in", frappe.STANDARD_USERS)})
test_blog = make_test_blog() test_blog = frappe.get_doc("Web Page", "test-web-page-1")
with set_user("Guest"): with set_user("Guest"):
self.assertRaises( self.assertRaises(
frappe.ValidationError, frappe.ValidationError,
@ -123,7 +112,7 @@ class TestComment(IntegrationTestCase):
comment="Good comment with 10 chars", comment="Good comment with 10 chars",
comment_email=some_system_user, comment_email=some_system_user,
comment_by="Good Tester", comment_by="Good Tester",
reference_doctype="Blog Post", reference_doctype="Web Page",
reference_name=test_blog.name, reference_name=test_blog.name,
route=test_blog.route, route=test_blog.route,
) )

View file

@ -845,11 +845,6 @@
"link_doctype": "Contact", "link_doctype": "Contact",
"link_fieldname": "user" "link_fieldname": "user"
}, },
{
"group": "Profile",
"link_doctype": "Blogger",
"link_fieldname": "user"
},
{ {
"group": "Logs", "group": "Logs",
"link_doctype": "Access Log", "link_doctype": "Access Log",

View file

@ -8,7 +8,7 @@ from frappe.core.doctype.user_permission.user_permission import (
) )
from frappe.permissions import add_permission, has_user_permission from frappe.permissions import add_permission, has_user_permission
from frappe.tests import IntegrationTestCase from frappe.tests import IntegrationTestCase
from frappe.website.doctype.blog_post.test_blog_post import make_test_blog from frappe.tests.test_helpers import setup_for_tests
class TestUserPermission(IntegrationTestCase): class TestUserPermission(IntegrationTestCase):
@ -23,6 +23,7 @@ class TestUserPermission(IntegrationTestCase):
frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabPerson`") frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabPerson`")
frappe.delete_doc_if_exists("DocType", "Doc A") frappe.delete_doc_if_exists("DocType", "Doc A")
frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabDoc A`") frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabDoc A`")
setup_for_tests()
def test_default_user_permission_validation(self): def test_default_user_permission_validation(self):
user = create_user("test_default_permission@example.com") user = create_user("test_default_permission@example.com")
@ -39,27 +40,27 @@ class TestUserPermission(IntegrationTestCase):
add_user_permissions(param) add_user_permissions(param)
# create a duplicate entry with default # create a duplicate entry with default
perm_user = create_user("test_default_corectness2@example.com") perm_user = create_user("test_default_corectness2@example.com")
test_blog = make_test_blog() test_blog = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
param = get_params(perm_user, "Blog Post", test_blog.name, is_default=1, hide_descendants=1) param = get_params(perm_user, "Test Blog Post", test_blog.name, is_default=1, hide_descendants=1)
add_user_permissions(param) add_user_permissions(param)
frappe.db.delete("User Permission", filters={"for_value": test_blog.name}) frappe.db.delete("User Permission", filters={"for_value": test_blog.name})
frappe.delete_doc("Blog Post", test_blog.name) frappe.delete_doc("Test Blog Post", test_blog.name)
def test_default_user_permission(self): def test_default_user_permission(self):
frappe.set_user("Administrator") frappe.set_user("Administrator")
user = create_user("test_user_perm1@example.com", "Website Manager") user = create_user("test_user_perm1@example.com", "Website Manager")
for category in ["general", "public"]: for category in ["general", "public"]:
if not frappe.db.exists("Blog Category", category): if not frappe.db.exists("Test Blog Category", category):
frappe.get_doc({"doctype": "Blog Category", "title": category}).insert() frappe.get_doc({"doctype": "Test Blog Category", "title": category}).insert()
param = get_params(user, "Blog Category", "general", is_default=1) param = get_params(user, "Test Blog Category", "general", is_default=1)
add_user_permissions(param) add_user_permissions(param)
param = get_params(user, "Blog Category", "public") param = get_params(user, "Test Blog Category", "public")
add_user_permissions(param) add_user_permissions(param)
frappe.set_user("test_user_perm1@example.com") frappe.set_user("test_user_perm1@example.com")
doc = frappe.new_doc("Blog Post") doc = frappe.new_doc("Test Blog Post")
self.assertEqual(doc.blog_category, "general") self.assertEqual(doc.blog_category, "general")
frappe.set_user("Administrator") frappe.set_user("Administrator")

View file

@ -1,6 +1,5 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE # License: MIT. See LICENSE
"""Use blog post test to test user permissions logic"""
import json import json
from datetime import date from datetime import date

View file

@ -6,7 +6,7 @@ from frappe.model.db_query import DatabaseQuery
from frappe.permissions import add_permission, reset_perms from frappe.permissions import add_permission, reset_perms
from frappe.tests import IntegrationTestCase from frappe.tests import IntegrationTestCase
EXTRA_TEST_RECORD_DEPENDENCIES = ["User"] EXTRA_TEST_RECORD_DEPENDENCIES = ["User", "Web Page"]
class TestToDo(IntegrationTestCase): class TestToDo(IntegrationTestCase):
@ -93,8 +93,8 @@ class TestToDo(IntegrationTestCase):
frappe.set_user("Administrator") frappe.set_user("Administrator")
test_user.add_roles("Blogger") test_user.add_roles("Website Manager")
add_permission("ToDo", "Blogger") add_permission("ToDo", "Website Manager")
frappe.set_user("test4@example.com") frappe.set_user("test4@example.com")
@ -103,7 +103,7 @@ class TestToDo(IntegrationTestCase):
self.assertFalse(todo1.has_permission("write")) self.assertFalse(todo1.has_permission("write"))
frappe.set_user("Administrator") frappe.set_user("Administrator")
test_user.remove_roles("Blogger") test_user.remove_roles("Website Manager")
reset_perms("ToDo") reset_perms("ToDo")
clear_permissions_cache("ToDo") clear_permissions_cache("ToDo")
frappe.db.rollback() frappe.db.rollback()

View file

@ -47,11 +47,9 @@ class TestSMTP(IntegrationTestCase):
password="password", password="password",
enable_outgoing=1, enable_outgoing=1,
default_outgoing=1, default_outgoing=1,
append_to="Blog Post", append_to="Todo",
)
self.assertEqual(
EmailAccount.find_outgoing(match_by_doctype="Blog Post").email_id, "append_to@gmail.com"
) )
self.assertEqual(EmailAccount.find_outgoing(match_by_doctype="Todo").email_id, "append_to@gmail.com")
# add back the mail_server # add back the mail_server
frappe.conf["mail_server"] = mail_server frappe.conf["mail_server"] = mail_server

View file

@ -54,7 +54,6 @@ web_include_icons = [
email_css = ["email.bundle.css"] email_css = ["email.bundle.css"]
website_route_rules = [ website_route_rules = [
{"from_route": "/blog/<category>", "to_route": "Blog Post"},
{"from_route": "/kb/<category>", "to_route": "Help Article"}, {"from_route": "/kb/<category>", "to_route": "Help Article"},
{"from_route": "/profile", "to_route": "me"}, {"from_route": "/profile", "to_route": "me"},
{"from_route": "/app/<path:app_path>", "to_route": "app"}, {"from_route": "/app/<path:app_path>", "to_route": "app"},
@ -352,7 +351,6 @@ global_search_doctypes = {
{"doctype": "ToDo"}, {"doctype": "ToDo"},
{"doctype": "Note"}, {"doctype": "Note"},
{"doctype": "Event"}, {"doctype": "Event"},
{"doctype": "Blog Post"},
{"doctype": "Dashboard"}, {"doctype": "Dashboard"},
{"doctype": "Country"}, {"doctype": "Country"},
{"doctype": "Currency"}, {"doctype": "Currency"},

View file

@ -613,7 +613,7 @@ class Test_OpenLDAP(LDAP_TestCase, TestCase):
"ldap_group": "Administrators", "ldap_group": "Administrators",
"erpnext_role": "System Manager", "erpnext_role": "System Manager",
}, },
{"doctype": "LDAP Group Mapping", "ldap_group": "Users", "erpnext_role": "Blogger"}, {"doctype": "LDAP Group Mapping", "ldap_group": "Users", "erpnext_role": "Website Manager"},
{"doctype": "LDAP Group Mapping", "ldap_group": "Group3", "erpnext_role": "Accounts User"}, {"doctype": "LDAP Group Mapping", "ldap_group": "Group3", "erpnext_role": "Accounts User"},
] ]
LDAP_USERNAME_FIELD = "uid" LDAP_USERNAME_FIELD = "uid"
@ -637,7 +637,7 @@ class Test_ActiveDirectory(LDAP_TestCase, TestCase):
"ldap_group": "Domain Administrators", "ldap_group": "Domain Administrators",
"erpnext_role": "System Manager", "erpnext_role": "System Manager",
}, },
{"doctype": "LDAP Group Mapping", "ldap_group": "Domain Users", "erpnext_role": "Blogger"}, {"doctype": "LDAP Group Mapping", "ldap_group": "Domain Users", "erpnext_role": "Website Manager"},
{ {
"doctype": "LDAP Group Mapping", "doctype": "LDAP Group Mapping",
"ldap_group": "Enterprise Administrators", "ldap_group": "Enterprise Administrators",

View file

@ -6,6 +6,7 @@ def execute():
"Social Module/ Energy Points System": ("eps", "system"), "Social Module/ Energy Points System": ("eps", "system"),
"Offsite Backup Integrations (Google Drive, S3, Dropbox)": ("offsite_backups", "intergration"), "Offsite Backup Integrations (Google Drive, S3, Dropbox)": ("offsite_backups", "intergration"),
"Newsletter": ("newsletter", "functionality"), "Newsletter": ("newsletter", "functionality"),
"Blogs": ("blogs", "functionality"),
} }
for module, (app, system_type) in module_app_map.items(): for module, (app, system_type) in module_app_map.items():
click.secho( click.secho(

View file

@ -1,138 +0,0 @@
:root {
--comment-timeline-bottom: 60px;
--comment-timeline-top: 8px;
}
.blog-list {
display: flex;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px;
&.result {
border-bottom: none;
}
}
.blog-list-content {
margin-bottom: 3rem;
}
.blog-card {
margin-bottom: 2rem;
position: relative;
width: 100%;
.card {
border: 1px solid var(--border-color);
}
.card-body {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.card-img-top {
width: 100%;
overflow: hidden;
height: 12rem;
img {
min-height: 12rem;
min-width: 100%;
object-fit: cover;
}
.default-cover {
height: 100%;
width: 100%;
padding: 1rem;
display: flex;
align-items: center;
justify-content: center;
background: $gray-200;
font-size: 1.2rem;
font-weight: 500;
color: $gray-600;
}
}
.blog-card-footer {
display: flex;
align-items: top;
margin-top: 0.5rem;
.avatar {
margin-top: 0.4rem;
margin-right: 0.5rem;
}
}
}
.blog-container {
font-size: 1rem;
max-width: 800px;
margin: 0px auto;
.blog-title {
margin-top: 1rem;
@include media-breakpoint-up(xl) {
line-height: 1;
font-size: $font-size-4xl;
}
}
.blog-footer {
display: flex;
justify-content: space-between;
color: $text-muted;
margin-top: 3rem;
}
.blog-intro {
font-size: 1.125rem;
font-weight: 400;
}
.blog-content {
margin-bottom: 1rem;
.blog-header {
margin-bottom: 3rem;
margin-top: 5rem;
}
.from-markdown a {
text-decoration: underline;
}
}
.blog-comments {
margin-top: 1rem;
margin-bottom: 5rem;
}
.feedback-item svg {
vertical-align: sub;
}
.blog-feedback {
display: inline-flex;
.like-icon {
cursor: pointer;
use {
stroke: var(--gray-800);
--icon-stroke: transparent;
}
}
.like-icon.liked {
use {
stroke: var(--gray-800);
--icon-stroke: var(--red-500);
}
}
}
}

View file

@ -21,7 +21,6 @@
@import "website_avatar"; @import "website_avatar";
@import "web_form"; @import "web_form";
@import "page_builder"; @import "page_builder";
@import "blog";
@import "markdown"; @import "markdown";
@import "sidebar"; @import "sidebar";
@import "portal"; @import "portal";

View file

@ -1,14 +0,0 @@
{% from "frappe/templates/includes/avatar_macro.html" import avatar %}
<div class="media">
{{ avatar(full_name=blogger_info.full_name, image=blogger_info.avatar, size='avatar-large') }}
<div class="media-body ml-3">
<h5 class="mt-0">
<a href="/blog?blogger={{ blogger_info.name }}" class="text-dark">{{ blogger_info.full_name }}</a>
</h5>
{% if blogger_info.bio %}
<p class="text-muted">{{ blogger_info.bio }}</p>
{% endif %}
</div>
</div>

View file

@ -1,12 +0,0 @@
{% if blog_title and not (form_dict.txt or form_dict.by) %}
<div class="page-hero border-bottom">
<div class="container">
<h1 class="page-title">
{{ blog_title }}
</h1>
{% if blog_introduction -%}
<p class="blog-introduction">{{ blog_introduction }}</p>
{%- endif %}
</div>
</div>
{% endif %}

View file

@ -6,7 +6,6 @@ import frappe
from frappe import _, scrub from frappe import _, scrub
from frappe.rate_limiter import rate_limit from frappe.rate_limiter import rate_limit
from frappe.utils.html_utils import clean_html from frappe.utils.html_utils import clean_html
from frappe.website.doctype.blog_settings.blog_settings import get_comment_limit
from frappe.website.utils import clear_cache from frappe.website.utils import clear_cache
URLS_COMMENT_PATTERN = re.compile( URLS_COMMENT_PATTERN = re.compile(
@ -15,17 +14,32 @@ URLS_COMMENT_PATTERN = re.compile(
EMAIL_PATTERN = re.compile(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", re.IGNORECASE) EMAIL_PATTERN = re.compile(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", re.IGNORECASE)
def get_limit():
method = frappe.get_hooks("comment_rate_limit")
if not method:
return 5
else:
limit = frappe.call(method[0])
return limit
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
@rate_limit(key="reference_name", limit=get_comment_limit, seconds=60 * 60) # @rate_limit(key="reference_name", limit=get_limit, seconds=60 * 60)
def add_comment(comment, comment_email, comment_by, reference_doctype, reference_name, route): def add_comment(comment, comment_email, comment_by, reference_doctype, reference_name, route):
if frappe.session.user == "Guest": if frappe.session.user == "Guest":
if reference_doctype not in ("Blog Post", "Web Page"): allowed_doctypes = ["Web Page"]
comments_permission_config = frappe.get_hooks("has_comment_permission")
guest_allowed = False
if len(comments_permission_config):
if comments_permission_config["doctype"]:
allowed_doctypes.append(comments_permission_config["doctype"][0])
check_permission_method = comments_permission_config["method"]
guest_allowed = frappe.call(check_permission_method[0], ref_doctype=reference_doctype)
if reference_doctype not in allowed_doctypes:
return return
if reference_doctype == "Blog Post" and not frappe.db.get_single_value( if not guest_allowed:
"Blog Settings", "allow_guest_to_comment" frappe.throw(_("Please login to post a comment."))
):
return
if frappe.db.exists("User", comment_email): if frappe.db.exists("User", comment_email):
frappe.throw(_("Please login to post a comment.")) frappe.throw(_("Please login to post a comment."))
@ -47,28 +61,6 @@ def add_comment(comment, comment_email, comment_by, reference_doctype, reference
if route: if route:
clear_cache(route) clear_cache(route)
if doc.get("route"):
url = f"{frappe.utils.get_request_site_address()}/{doc.route}#{comment.name}"
else:
url = f"{frappe.utils.get_request_site_address()}/app/{scrub(doc.doctype)}/{doc.name}#comment-{comment.name}"
content = comment.content + "<p><a href='{}' style='font-size: 80%'>{}</a></p>".format(
url, _("View Comment")
)
if doc.doctype != "Blog Post" or doc.enable_email_notification:
# notify creator
creator_email = frappe.db.get_value("User", doc.owner, "email") or doc.owner
subject = _("New Comment on {0}: {1}").format(doc.doctype, doc.get_title())
frappe.sendmail(
recipients=creator_email,
subject=subject,
message=content,
reference_doctype=doc.doctype,
reference_name=doc.name,
)
# revert with template if all clear (no backlinks) # revert with template if all clear (no backlinks)
template = frappe.get_template("templates/includes/comments/comment.html") template = frappe.get_template("templates/includes/comments/comment.html")
return template.render({"comment": comment.as_dict()}) return template.render({"comment": comment.as_dict()})

View file

@ -1,41 +0,0 @@
<div id="likes" class="feedback-item likes mr-3">
<span class="like-icon"></span>
<span class="like-count"></span>
</div>
<script type="text/javascript">
frappe.ready(() => {
let like = parseInt("{{ like or 0 }}");
let like_count = parseInt("{{ like_count or 0 }}");
let toggle_like_icon = function(active) {
active ? $('.like-icon').addClass('liked') : $('.like-icon').removeClass('liked');
}
$('.like-icon').append(`<svg class="icon icon-sm"><use href="#icon-heart"></use></svg>`)
toggle_like_icon(like);
$('.like-count').text(like_count);
$('.like-icon').click(() => {
update_like();
})
let update_like = function() {
like = !like;
like ? like_count++ : like_count--;
toggle_like_icon(like);
$('.like-count').text(like_count);
return frappe.call({
method: "frappe.templates.includes.likes.likes.like",
args: {
reference_doctype: "{{ reference_doctype or doctype }}",
reference_name: "{{ reference_name or name }}",
like,
route: "{{ pathname }}",
}
});
}
});
</script>

View file

@ -1,78 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
from frappe import _
from frappe.rate_limiter import rate_limit
from frappe.website.doctype.blog_settings.blog_settings import get_like_limit
from frappe.website.utils import clear_cache
@frappe.whitelist(allow_guest=True)
@rate_limit(key="reference_name", limit=get_like_limit, seconds=60 * 60)
def like(reference_doctype, reference_name, like, route=""):
like = frappe.parse_json(like)
ref_doc = frappe.get_doc(reference_doctype, reference_name)
if ref_doc.disable_likes == 1:
return
if like:
liked = add_like(reference_doctype, reference_name)
else:
liked = delete_like(reference_doctype, reference_name)
# since likes are embedded in the page, clear the web cache
if route:
clear_cache(route)
if like and ref_doc.enable_email_notification:
ref_doc_title = ref_doc.get_title()
subject = _("Like on {0}: {1}").format(reference_doctype, ref_doc_title)
content = _("You have received a ❤️ like on your blog post")
message = f"<p>{content} <b>{ref_doc_title}</b></p>"
message = message + "<p><a href='{}/{}#likes' style='font-size: 80%'>{}</a></p>".format(
frappe.utils.get_request_site_address(), ref_doc.route, _("View Blog Post")
)
# notify creator
frappe.sendmail(
recipients=frappe.db.get_value("User", ref_doc.owner, "email") or ref_doc.owner,
subject=subject,
message=message,
reference_doctype=ref_doc.doctype,
reference_name=ref_doc.name,
)
return liked
def add_like(reference_doctype, reference_name):
user = frappe.session.user
like = frappe.new_doc("Comment")
like.comment_type = "Like"
like.comment_email = user
like.reference_doctype = reference_doctype
like.reference_name = reference_name
like.content = "Liked by: " + user
if user == "Guest":
like.ip_address = frappe.local.request_ip
like.save(ignore_permissions=True)
return True
def delete_like(reference_doctype, reference_name):
user = frappe.session.user
filters = {
"comment_type": "Like",
"comment_email": user,
"reference_doctype": reference_doctype,
"reference_name": reference_name,
}
if user == "Guest":
filters["ip_address"] = frappe.local.request_ip
frappe.db.delete("Comment", filters)
return False

View file

@ -188,7 +188,7 @@ class TestMethodAPIV2(FrappeAPITestCase):
def test_shorthand_controller_methods(self): def test_shorthand_controller_methods(self):
shorthand_response = self.get(self.method("User", "get_all_roles"), {"sid": self.sid}) shorthand_response = self.get(self.method("User", "get_all_roles"), {"sid": self.sid})
self.assertIn("Blogger", shorthand_response.json["data"]) self.assertIn("Website Manager", shorthand_response.json["data"])
expanded_response = self.get( expanded_response = self.get(
self.method("frappe.core.doctype.user.user.get_all_roles"), {"sid": self.sid} self.method("frappe.core.doctype.user.user.get_all_roles"), {"sid": self.sid}

View file

@ -15,10 +15,11 @@ from frappe.model.db_query import DatabaseQuery, get_between_date_filter
from frappe.permissions import add_user_permission, clear_user_permissions_for_doctype from frappe.permissions import add_user_permission, clear_user_permissions_for_doctype
from frappe.query_builder import Column from frappe.query_builder import Column
from frappe.tests import IntegrationTestCase from frappe.tests import IntegrationTestCase
from frappe.tests.test_helpers import setup_for_tests
from frappe.tests.test_query_builder import db_type_is, run_only_if from frappe.tests.test_query_builder import db_type_is, run_only_if
from frappe.utils.testutils import add_custom_field, clear_custom_fields from frappe.utils.testutils import add_custom_field, clear_custom_fields
EXTRA_TEST_RECORD_DEPENDENCIES = ["User", "Blog Post", "Blog Category", "Blogger"] EXTRA_TEST_RECORD_DEPENDENCIES = ["User"]
@contextmanager @contextmanager
@ -41,15 +42,16 @@ def setup_test_user(set_user=False):
@contextmanager @contextmanager
def setup_patched_blog_post(): def setup_patched_blog_post():
add_child_table_to_blog_post() add_child_table_to_blog_post()
make_property_setter("Blog Post", "published", "permlevel", 1, "Int") make_property_setter("Test Blog Post", "published", "permlevel", 1, "Int")
reset("Blog Post") reset("Test Blog Post")
add("Blog Post", "Website Manager", 1) add("Test Blog Post", "Website Manager", 1)
update("Blog Post", "Website Manager", 1, "write", 1) update("Test Blog Post", "Website Manager", 1, "write", 1)
yield yield
class TestDBQuery(IntegrationTestCase): class TestDBQuery(IntegrationTestCase):
def setUp(self): def setUp(self):
setup_for_tests()
frappe.set_user("Administrator") frappe.set_user("Administrator")
def test_basic(self): def test_basic(self):
@ -192,14 +194,14 @@ class TestDBQuery(IntegrationTestCase):
todo.delete() todo.delete()
def test_build_match_conditions(self): def test_build_match_conditions(self):
clear_user_permissions_for_doctype("Blog Post", "test2@example.com") clear_user_permissions_for_doctype("Test Blog Post", "test2@example.com")
test2user = frappe.get_doc("User", "test2@example.com") test2user = frappe.get_doc("User", "test2@example.com")
test2user.add_roles("Blogger") test2user.add_roles("Blogger")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
# this will get match conditions for Blog Post # this will get match conditions for Blog Post
build_match_conditions = DatabaseQuery("Blog Post").build_match_conditions build_match_conditions = DatabaseQuery("Test Blog Post").build_match_conditions
# Before any user permission is applied # Before any user permission is applied
# get as filters # get as filters
@ -207,20 +209,20 @@ class TestDBQuery(IntegrationTestCase):
# get as conditions # get as conditions
self.assertEqual(build_match_conditions(as_condition=True), "") self.assertEqual(build_match_conditions(as_condition=True), "")
add_user_permission("Blog Post", "-test-blog-post", "test2@example.com", True) add_user_permission("Test Blog Post", "_Test Blog Post", "test2@example.com", True)
add_user_permission("Blog Post", "-test-blog-post-1", "test2@example.com", True) add_user_permission("Test Blog Post", "_Test Blog Post 1", "test2@example.com", True)
# After applying user permission # After applying user permission
# get as filters # get as filters
self.assertTrue( self.assertTrue(
{"Blog Post": ["-test-blog-post-1", "-test-blog-post"]} {"Test Blog Post": ["_Test Blog Post 1", "_Test Blog Post"]}
in build_match_conditions(as_condition=False) in build_match_conditions(as_condition=False)
) )
# get as conditions # get as conditions
if frappe.db.db_type == "mariadb": if frappe.db.db_type == "mariadb":
assertion_string = """(((ifnull(`tabBlog Post`.`name`, '')='' or `tabBlog Post`.`name` in ('-test-blog-post-1', '-test-blog-post'))))""" assertion_string = """(((ifnull(`tabTest Blog Post`.`name`, '')='' or `tabTest Blog Post`.`name` in ('_Test Blog Post 1', '_Test Blog Post'))))"""
else: else:
assertion_string = """(((ifnull(cast(`tabBlog Post`.`name` as varchar), '')='' or cast(`tabBlog Post`.`name` as varchar) in ('-test-blog-post-1', '-test-blog-post'))))""" assertion_string = """(((ifnull(cast(`tabBlog Post`.`name` as varchar), '')='' or cast(`tabBlog Post`.`name` as varchar) in ('_Test Blog Post 1', '_Test Blog Post'))))"""
self.assertEqual(build_match_conditions(as_condition=True), assertion_string) self.assertEqual(build_match_conditions(as_condition=True), assertion_string)
@ -848,7 +850,7 @@ class TestDBQuery(IntegrationTestCase):
def test_permlevel_fields(self): def test_permlevel_fields(self):
with setup_patched_blog_post(), setup_test_user(set_user=True): with setup_patched_blog_post(), setup_test_user(set_user=True):
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
fields=["name", "published"], fields=["name", "published"],
limit=1, limit=1,
@ -858,7 +860,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1) self.assertEqual(len(data[0]), 1)
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
fields=["name", "`published`"], fields=["name", "`published`"],
limit=1, limit=1,
@ -868,9 +870,9 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1) self.assertEqual(len(data[0]), 1)
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
fields=["name", "`tabBlog Post`.`published`"], fields=["name", "`tabTest Blog Post`.`published`"],
limit=1, limit=1,
) )
self.assertFalse("published" in data[0]) self.assertFalse("published" in data[0])
@ -878,7 +880,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1) self.assertEqual(len(data[0]), 1)
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
fields=["name", "`tabTest Child`.`test_field`"], fields=["name", "`tabTest Child`.`test_field`"],
limit=1, limit=1,
@ -888,7 +890,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1) self.assertEqual(len(data[0]), 1)
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
fields=["name", "MAX(`published`)"], fields=["name", "MAX(`published`)"],
limit=1, limit=1,
@ -897,7 +899,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1) self.assertEqual(len(data[0]), 1)
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
fields=["name", "LAST(published)"], fields=["name", "LAST(published)"],
limit=1, limit=1,
@ -906,7 +908,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1) self.assertEqual(len(data[0]), 1)
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
fields=["name", "MAX(`modified`)"], fields=["name", "MAX(`modified`)"],
limit=1, limit=1,
@ -916,7 +918,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 2) self.assertEqual(len(data[0]), 2)
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
fields=["name", "now() abhi"], fields=["name", "now() abhi"],
limit=1, limit=1,
@ -925,7 +927,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 2) self.assertEqual(len(data[0]), 2)
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
fields=["name", "'LABEL'"], fields=["name", "'LABEL'"],
limit=1, limit=1,
@ -935,7 +937,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 2) self.assertEqual(len(data[0]), 2)
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
fields=["name", "COUNT(*) as count"], fields=["name", "COUNT(*) as count"],
limit=1, limit=1,
@ -946,7 +948,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 2) self.assertEqual(len(data[0]), 2)
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
fields=["name", "COUNT(*) count"], fields=["name", "COUNT(*) count"],
limit=1, limit=1,
@ -957,17 +959,18 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 2) self.assertEqual(len(data[0]), 2)
data = frappe.get_list( data = frappe.get_list(
"Blog Post", "Test Blog Post",
fields=[ fields=[
"name", "name",
"blogger.full_name as blogger_full_name", "blogger.full_name as blogger_full_name",
"blog_category.description", "blog_category.title",
], ],
limit=1, limit=1,
) )
print(data[0])
self.assertTrue("name" in data[0]) self.assertTrue("name" in data[0])
self.assertTrue("blogger_full_name" in data[0]) self.assertTrue("blogger_full_name" in data[0])
self.assertTrue("description" in data[0]) self.assertTrue("title" in data[0])
def test_cast_name(self): def test_cast_name(self):
from frappe.core.doctype.doctype.test_doctype import new_doctype from frappe.core.doctype.doctype.test_doctype import new_doctype
@ -1296,10 +1299,10 @@ class TestReportView(IntegrationTestCase):
user.remove_roles(*user_roles) user.remove_roles(*user_roles)
user.add_roles("Blogger") user.add_roles("Blogger")
make_property_setter("Blog Post", "published", "permlevel", 1, "Int") make_property_setter("Test Blog Post", "published", "permlevel", 1, "Int")
reset("Blog Post") reset("Test Blog Post")
add("Blog Post", "Website Manager", 1) add("Test Blog Post", "Website Manager", 1)
update("Blog Post", "Website Manager", 1, "write", 1) update("Test Blog Post", "Website Manager", 1, "write", 1)
frappe.set_user(user.name) frappe.set_user(user.name)
@ -1308,7 +1311,7 @@ class TestReportView(IntegrationTestCase):
frappe.local.form_dict = frappe._dict( frappe.local.form_dict = frappe._dict(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"], "fields": ["published", "title", "`tabTest Child`.`test_field`"],
} }
) )
@ -1318,7 +1321,7 @@ class TestReportView(IntegrationTestCase):
self.assertListEqual(response["keys"], ["title"]) self.assertListEqual(response["keys"], ["title"])
frappe.local.form_dict = frappe._dict( frappe.local.form_dict = frappe._dict(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"fields": ["*"], "fields": ["*"],
} }
) )
@ -1335,7 +1338,7 @@ class TestReportView(IntegrationTestCase):
# Admin should be able to see access all fields # Admin should be able to see access all fields
frappe.local.form_dict = frappe._dict( frappe.local.form_dict = frappe._dict(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"], "fields": ["published", "title", "`tabTest Child`.`test_field`"],
} }
) )
@ -1377,7 +1380,7 @@ class TestReportView(IntegrationTestCase):
frappe.local.request.method = "POST" frappe.local.request.method = "POST"
frappe.local.form_dict = frappe._dict( frappe.local.form_dict = frappe._dict(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"], "fields": ["published", "title", "`tabTest Child`.`test_field`"],
} }
) )
@ -1387,7 +1390,7 @@ class TestReportView(IntegrationTestCase):
self.assertListEqual(response["keys"], ["title"]) self.assertListEqual(response["keys"], ["title"])
frappe.local.form_dict = frappe._dict( frappe.local.form_dict = frappe._dict(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"fields": ["*"], "fields": ["*"],
} }
) )
@ -1396,7 +1399,7 @@ class TestReportView(IntegrationTestCase):
self.assertNotIn("published", response["keys"]) self.assertNotIn("published", response["keys"])
# If none of the fields are accessible then result should be empty # If none of the fields are accessible then result should be empty
self.assertEqual(frappe.get_list("Blog Post", "published"), []) self.assertEqual(frappe.get_list("Test Blog Post", "published"), [])
def test_reportview_get_admin(self): def test_reportview_get_admin(self):
# Admin should be able to see access all fields # Admin should be able to see access all fields
@ -1405,7 +1408,7 @@ class TestReportView(IntegrationTestCase):
frappe.local.request.method = "POST" frappe.local.request.method = "POST"
frappe.local.form_dict = frappe._dict( frappe.local.form_dict = frappe._dict(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"], "fields": ["published", "title", "`tabTest Child`.`test_field`"],
} }
) )
@ -1441,8 +1444,8 @@ def add_child_table_to_blog_post():
) )
child_table.insert(ignore_permissions=True, ignore_if_duplicate=True) child_table.insert(ignore_permissions=True, ignore_if_duplicate=True)
clear_custom_fields("Blog Post") clear_custom_fields("Test Blog Post")
add_custom_field("Blog Post", "child_table", "Table", child_table.name) add_custom_field("Test Blog Post", "child_table", "Table", child_table.name)
def create_event(subject="_Test Event", starts_on=None): def create_event(subject="_Test Event", starts_on=None):

View file

@ -73,7 +73,7 @@ class TestDefaults(IntegrationTestCase):
@run_only_if(db_type_is.MARIADB) @run_only_if(db_type_is.MARIADB)
def test_user_permission_defaults(self): def test_user_permission_defaults(self):
# Create user permission # Create user permission
create_user("user_default_test@example.com", "Blogger") create_user("user_default_test@example.com", "Website Manager")
frappe.set_user("user_default_test@example.com") frappe.set_user("user_default_test@example.com")
set_global_default("Country", "") set_global_default("Country", "")
clear_user_default("Country") clear_user_default("Country")

View file

@ -5,10 +5,9 @@ from frappe.core.page.permission_manager.permission_manager import add, reset, u
from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.desk.form.load import get_docinfo, getdoc, getdoctype from frappe.desk.form.load import get_docinfo, getdoc, getdoctype
from frappe.tests import IntegrationTestCase from frappe.tests import IntegrationTestCase
from frappe.tests.test_helpers import setup_for_tests
from frappe.utils.file_manager import save_file from frappe.utils.file_manager import save_file
EXTRA_TEST_RECORD_DEPENDENCIES = ["Blog Category", "Blogger"]
class TestFormLoad(IntegrationTestCase): class TestFormLoad(IntegrationTestCase):
def test_load(self): def test_load(self):
@ -23,10 +22,11 @@ class TestFormLoad(IntegrationTestCase):
self.assertTrue(meta.get("__calendar_js")) self.assertTrue(meta.get("__calendar_js"))
def test_fieldlevel_permissions_in_load(self): def test_fieldlevel_permissions_in_load(self):
setup_for_tests()
blog = frappe.get_doc( blog = frappe.get_doc(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"blog_category": "-test-blog-category-1", "blog_category": "_Test Blog Category 1",
"blog_intro": "Test Blog Intro", "blog_intro": "Test Blog Intro",
"blogger": "_Test Blogger 1", "blogger": "_Test Blogger 1",
"content": "Test Blog Content", "content": "Test Blog Content",
@ -43,8 +43,8 @@ class TestFormLoad(IntegrationTestCase):
user.remove_roles(*user_roles) user.remove_roles(*user_roles)
user.add_roles("Blogger") user.add_roles("Blogger")
blog_post_property_setter = make_property_setter("Blog Post", "published", "permlevel", 1, "Int") blog_post_property_setter = make_property_setter("Test Blog Post", "published", "permlevel", 1, "Int")
reset("Blog Post") reset("Test Blog Post")
# test field level permission before role level permissions are defined # test field level permission before role level permissions are defined
frappe.set_user(user.name) frappe.set_user(user.name)
@ -63,8 +63,8 @@ class TestFormLoad(IntegrationTestCase):
# test field level permission after role level permissions are defined # test field level permission after role level permissions are defined
frappe.set_user("Administrator") frappe.set_user("Administrator")
add("Blog Post", "Website Manager", 1) add("Test Blog Post", "Website Manager", 1)
update("Blog Post", "Website Manager", 1, "write", 1) update("Test Blog Post", "Website Manager", 1, "write", 1)
frappe.set_user(user.name) frappe.set_user(user.name)
blog_doc = get_blog(blog.name) blog_doc = get_blog(blog.name)
@ -86,7 +86,7 @@ class TestFormLoad(IntegrationTestCase):
user.add_roles("Website Manager") user.add_roles("Website Manager")
frappe.set_user(user.name) frappe.set_user(user.name)
doc = frappe.get_doc("Blog Post", blog.name) doc = frappe.get_doc("Test Blog Post", blog.name)
doc.published = 1 doc.published = 1
doc.save() doc.save()
@ -196,5 +196,5 @@ class TestFormLoad(IntegrationTestCase):
def get_blog(blog_name): def get_blog(blog_name):
frappe.response.docs = [] frappe.response.docs = []
getdoc("Blog Post", blog_name) getdoc("Test Blog Post", blog_name)
return frappe.response.docs[0] return frappe.response.docs[0]

View file

@ -0,0 +1,271 @@
import frappe
def create_test_blog_post():
test_blog_doc = frappe.get_doc(
{
"doctype": "DocType",
"name": "Test Blog Post",
"allow_guest_to_view": 1,
"module": "Custom",
"custom": 1,
"title_field": "title",
"autoname": "field:title",
"naming_rule": "By fieldname",
"make_attachments_public": 1,
"owner": "Administrator",
"fields": [
{
"fieldname": "blog_category",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Test Blog Category",
"options": "Test Blog Category",
"reqd": 1,
},
{
"fieldname": "blogger",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Test Blogger",
"options": "Test Blogger",
"reqd": 1,
},
{
"description": "Description for listing page, in plain text, only a couple of lines. (max 200 characters)",
"fieldname": "blog_intro",
"fieldtype": "Small Text",
"label": "Blog Intro",
},
{
"depends_on": "eval:doc.content_type === 'Rich Text'",
"fieldname": "content",
"fieldtype": "Text Editor",
"ignore_xss_filter": 1,
"in_global_search": 1,
"label": "Content",
},
{
"fieldname": "title",
"fieldtype": "Data",
"in_global_search": 1,
"label": "Title",
"no_copy": 1,
"reqd": 1,
},
{
"default": "0",
"fieldname": "published",
"fieldtype": "Check",
"hidden": 1,
"label": "Published",
},
],
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Website Manager",
"share": 1,
"write": 1,
},
{
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Blogger",
"share": 1,
"write": 1,
},
],
}
)
test_blog_doc.insert(ignore_if_duplicate=True, ignore_links=True)
create_test_blog_records()
def create_test_blog_records():
test_blog_records = [
{
"blog_category": "_Test Blog Category",
"blog_intro": "Test Blog Intro",
"blogger": "_Test Blogger",
"content": "Test Blog Content",
"doctype": "Test Blog Post",
"title": "_Test Blog Post",
"published": 1,
},
{
"blog_category": "_Test Blog Category 1",
"blog_intro": "Test Blog Intro",
"blogger": "_Test Blogger",
"content": "Test Blog Content",
"doctype": "Test Blog Post",
"title": "_Test Blog Post 1",
"published": 1,
},
{
"blog_category": "_Test Blog Category 1",
"blog_intro": "Test Blog Intro",
"blogger": "_Test Blogger 1",
"content": "Test Blog Content",
"doctype": "Test Blog Post",
"title": "_Test Blog Post 2",
"published": 0,
},
{
"blog_category": "_Test Blog Category 1",
"blog_intro": "Test Blog Intro",
"blogger": "_Test Blogger 2",
"content": "Test Blog Content",
"doctype": "Test Blog Post",
"title": "_Test Blog Post 3",
"published": 0,
},
]
for r in test_blog_records:
frappe.get_doc(r).insert(ignore_if_duplicate=True, ignore_links=True)
def create_test_blog_category():
frappe.get_doc(
{
"doctype": "DocType",
"autoname": "field:title",
"name": "Test Blog Category",
"module": "Custom",
"custom": 1,
"make_attachments_public": 1,
"naming_rule": "By fieldname",
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"no_copy": 1,
"reqd": 1,
},
{
"default": "1",
"fieldname": "published",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Published",
},
{
"depends_on": "published",
"fieldname": "route",
"fieldtype": "Data",
"label": "Route",
"read_only": 1,
"unique": 1,
},
],
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Website Manager",
"share": 1,
"write": 1,
},
{"email": 1, "print": 1, "read": 1, "role": "Blogger"},
],
}
).insert(ignore_if_duplicate=True, ignore_links=True)
create_blog_category_records()
def create_blog_category_records():
test_blog_category_records = [
{"doctype": "Test Blog Category", "parent_website_route": "blog", "title": "_Test Blog Category"},
{"doctype": "Test Blog Category", "parent_website_route": "blog", "title": "_Test Blog Category 1"},
{"doctype": "Test Blog Category", "parent_website_route": "blog", "title": "_Test Blog Category 2"},
]
for r in test_blog_category_records:
frappe.get_doc(r).insert(ignore_if_duplicate=True, ignore_links=True)
def create_test_blogger():
frappe.get_doc(
{
"doctype": "DocType",
"name": "Test Blogger",
"module": "Custom",
"custom": 1,
"autoname": "field:short_name",
"make_attachments_public": 1,
"naming_rule": "By fieldname",
"fields": [
{"default": "0", "fieldname": "disabled", "fieldtype": "Check", "label": "Disabled"},
{
"description": "Will be used in url (usually first name).",
"fieldname": "short_name",
"fieldtype": "Data",
"label": "Short Name",
"reqd": 1,
"unique": 1,
},
{
"fieldname": "full_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Full Name",
"reqd": 1,
},
{"fieldname": "user", "fieldtype": "Link", "label": "User", "options": "User"},
{"fieldname": "bio", "fieldtype": "Small Text", "label": "Bio"},
{"fieldname": "avatar", "fieldtype": "Attach Image", "label": "Avatar"},
],
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Website Manager",
"share": 1,
"write": 1,
},
{"email": 1, "print": 1, "read": 1, "role": "Blogger", "share": 1, "write": 1},
],
}
).insert(ignore_if_duplicate=True, ignore_links=True)
create_test_blogger_records()
def create_test_blogger_records():
test_blogger_records = [
{"doctype": "Test Blogger", "full_name": "_Test Blogger", "short_name": "_Test Blogger"},
{"doctype": "Test Blogger", "full_name": "_Test Blogger 1", "short_name": "_Test Blogger 1"},
{"doctype": "Test Blogger", "full_name": "_Test Blogger 2", "short_name": "_Test Blogger 2"},
]
for r in test_blogger_records:
frappe.get_doc(r).insert(ignore_if_duplicate=True, ignore_links=True)
def setup_for_tests():
frappe.set_user("Administrator")
frappe.delete_doc_if_exists("DocType", "Test Blog Post")
frappe.delete_doc_if_exists("DocType", "Test Blog Category")
frappe.delete_doc_if_exists("DocType", "Test Blogger")
create_test_blog_category()
create_test_blogger()
create_test_blog_post()

View file

@ -5,10 +5,12 @@
import frappe import frappe
import frappe.defaults import frappe.defaults
import frappe.model.meta import frappe.model.meta
import frappe.permissions
from frappe.core.doctype.doctype.test_doctype import new_doctype from frappe.core.doctype.doctype.test_doctype import new_doctype
from frappe.core.doctype.user_permission.user_permission import clear_user_permissions from frappe.core.doctype.user_permission.user_permission import clear_user_permissions
from frappe.core.page.permission_manager.permission_manager import add, remove, reset, update from frappe.core.page.permission_manager.permission_manager import add, remove, reset, update
from frappe.desk.form.load import getdoc from frappe.desk.form.load import getdoc
from frappe.installer import _delete_doctypes
from frappe.permissions import ( from frappe.permissions import (
ALL_USER_ROLE, ALL_USER_ROLE,
AUTOMATIC_ROLES, AUTOMATIC_ROLES,
@ -23,17 +25,19 @@ from frappe.permissions import (
update_permission_property, update_permission_property,
) )
from frappe.tests import IntegrationTestCase from frappe.tests import IntegrationTestCase
from frappe.tests.test_helpers import setup_for_tests
from frappe.tests.utils import make_test_records_for_doctype from frappe.tests.utils import make_test_records_for_doctype
from frappe.utils.data import now_datetime from frappe.utils.data import now_datetime
EXTRA_TEST_RECORD_DEPENDENCIES = ["Blogger", "Blog Post", "User", "Contact", "Salutation"] EXTRA_TEST_RECORD_DEPENDENCIES = ["User", "Contact", "Salutation"]
class TestPermissions(IntegrationTestCase): class TestPermissions(IntegrationTestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()
frappe.clear_cache(doctype="Blog Post") setup_for_tests()
frappe.clear_cache(doctype="Test Blog Post")
user = frappe.get_doc("User", "test1@example.com") user = frappe.get_doc("User", "test1@example.com")
user.add_roles("Website Manager") user.add_roles("Website Manager")
user.add_roles("System Manager") user.add_roles("System Manager")
@ -48,10 +52,10 @@ class TestPermissions(IntegrationTestCase):
user.add_roles("Website Manager") user.add_roles("Website Manager")
def setUp(self): def setUp(self):
frappe.clear_cache(doctype="Blog Post") frappe.clear_cache(doctype="Test Blog Post")
reset("Blogger") reset("Test Blogger")
reset("Blog Post") reset("Test Blog Post")
frappe.db.delete("User Permission") frappe.db.delete("User Permission")
@ -59,11 +63,11 @@ class TestPermissions(IntegrationTestCase):
def tearDown(self): def tearDown(self):
frappe.set_user("Administrator") frappe.set_user("Administrator")
frappe.db.set_value("Blogger", "_Test Blogger 1", "user", None) frappe.db.set_value("Test Blogger", "_Test Blogger 1", "user", None)
clear_user_permissions_for_doctype("Blog Category") clear_user_permissions_for_doctype("Test Blog Category")
clear_user_permissions_for_doctype("Blog Post") clear_user_permissions_for_doctype("Test Blog Post")
clear_user_permissions_for_doctype("Blogger") clear_user_permissions_for_doctype("Test Blogger")
@staticmethod @staticmethod
def set_strict_user_permissions(ignore): def set_strict_user_permissions(ignore):
@ -73,119 +77,124 @@ class TestPermissions(IntegrationTestCase):
ss.save() ss.save()
def test_basic_permission(self): def test_basic_permission(self):
post = frappe.get_doc("Blog Post", "-test-blog-post") post = frappe.get_doc("Test Blog Post", "_Test Blog Post")
self.assertTrue(post.has_permission("read")) self.assertTrue(post.has_permission("read"))
def test_select_permission(self): def test_select_permission(self):
# grant only select perm to blog post # grant only select perm to blog post
add_permission("Blog Post", "Sales User", 0) add_permission("Test Blog Post", "Sales User", 0)
update_permission_property("Blog Post", "Sales User", 0, "select", 1) update_permission_property("Test Blog Post", "Sales User", 0, "select", 1)
update_permission_property("Blog Post", "Sales User", 0, "read", 0) update_permission_property("Test Blog Post", "Sales User", 0, "read", 0)
update_permission_property("Blog Post", "Sales User", 0, "write", 0) update_permission_property("Test Blog Post", "Sales User", 0, "write", 0)
frappe.clear_cache(doctype="Blog Post") frappe.clear_cache(doctype="Test Blog Post")
frappe.set_user("test3@example.com") frappe.set_user("test3@example.com")
# validate select perm # validate select perm
post = frappe.get_doc("Blog Post", "-test-blog-post") post = frappe.get_doc("Test Blog Post", "_Test Blog Post")
self.assertTrue(post.has_permission("select")) self.assertTrue(post.has_permission("select"))
# validate does not have read and write perm # validate does not have read and write perm
self.assertFalse(post.has_permission("read")) self.assertFalse(post.has_permission("read"))
self.assertRaises(frappe.PermissionError, post.save) self.assertRaises(frappe.PermissionError, post.save)
permitted_record = frappe.get_list("Blog Post", fields="*", limit=1)[0] permitted_record = frappe.get_list("Test Blog Post", fields="*", limit=1)[0]
full_record = frappe.get_all("Blog Post", fields="*", limit=1)[0] full_record = frappe.get_all("Test Blog Post", fields="*", limit=1)[0]
self.assertNotEqual(permitted_record, full_record) self.assertNotEqual(permitted_record, full_record)
self.assertSequenceSubset(post.meta.get_search_fields(), permitted_record) self.assertSequenceSubset(post.meta.get_search_fields(), permitted_record)
def test_user_permissions_in_doc(self): def test_user_permissions_in_doc(self):
add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") add_user_permission("Test Blog Category", "_Test Blog Category 1", "test2@example.com")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
post = frappe.get_doc("Blog Post", "-test-blog-post") post = frappe.get_doc("Test Blog Post", "_Test Blog Post")
self.assertFalse(post.has_permission("read")) self.assertFalse(post.has_permission("read"))
self.assertFalse(get_doc_permissions(post).get("read")) self.assertFalse(get_doc_permissions(post).get("read"))
post1 = frappe.get_doc("Blog Post", "-test-blog-post-1") post1 = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
self.assertTrue(post1.has_permission("read")) self.assertTrue(post1.has_permission("read"))
self.assertTrue(get_doc_permissions(post1).get("read")) self.assertTrue(get_doc_permissions(post1).get("read"))
def test_user_permissions_in_report(self): def test_user_permissions_in_report(self):
add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") add_user_permission("Test Blog Category", "_Test Blog Category 1", "test2@example.com")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
names = [d.name for d in frappe.get_list("Blog Post", fields=["name", "blog_category"])] names = [d.name for d in frappe.get_list("Test Blog Post", fields=["name", "blog_category"])]
self.assertTrue("-test-blog-post-1" in names) self.assertTrue("_Test Blog Post 1" in names)
self.assertFalse("-test-blog-post" in names) self.assertFalse("_Test Blog Post" in names)
def test_default_values(self): def test_default_values(self):
doc = frappe.new_doc("Blog Post") doc = frappe.new_doc("Test Blog Post")
self.assertFalse(doc.get("blog_category")) self.assertFalse(doc.get("blog_category"))
# Fetch default based on single user permission # Fetch default based on single user permission
add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") add_user_permission("Test Blog Category", "_Test Blog Category 1", "test2@example.com")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
doc = frappe.new_doc("Blog Post") doc = frappe.new_doc("Test Blog Post")
self.assertEqual(doc.get("blog_category"), "-test-blog-category-1") self.assertEqual(doc.get("blog_category"), "_Test Blog Category 1")
# Don't fetch default if user permissions is more than 1 # Don't fetch default if user permissions is more than 1
add_user_permission( add_user_permission(
"Blog Category", "-test-blog-category", "test2@example.com", ignore_permissions=True "Test Blog Category", "_Test Blog Category", "test2@example.com", ignore_permissions=True
) )
frappe.clear_cache() frappe.clear_cache()
doc = frappe.new_doc("Blog Post") doc = frappe.new_doc("Test Blog Post")
self.assertFalse(doc.get("blog_category")) self.assertFalse(doc.get("blog_category"))
# Fetch user permission set as default from multiple user permission # Fetch user permission set as default from multiple user permission
add_user_permission( add_user_permission(
"Blog Category", "Test Blog Category",
"-test-blog-category-2", "_Test Blog Category 2",
"test2@example.com", "test2@example.com",
ignore_permissions=True, ignore_permissions=True,
is_default=1, is_default=1,
) )
frappe.clear_cache() frappe.clear_cache()
doc = frappe.new_doc("Blog Post") doc = frappe.new_doc("Test Blog Post")
self.assertEqual(doc.get("blog_category"), "-test-blog-category-2") self.assertEqual(doc.get("blog_category"), "_Test Blog Category 2")
def test_user_link_match_doc(self): def test_user_link_match_doc(self):
blogger = frappe.get_doc("Blogger", "_Test Blogger 1") blogger = frappe.get_doc("Test Blogger", "_Test Blogger 1")
blogger.user = "test2@example.com" blogger.user = "test2@example.com"
blogger.save() blogger.save()
frappe.permissions.add_user_permission("Test Blogger", blogger.name, blogger.user)
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
post = frappe.get_doc("Blog Post", "-test-blog-post-2") post = frappe.get_doc("Test Blog Post", "_Test Blog Post 2")
self.assertTrue(post.has_permission("read")) self.assertTrue(post.has_permission("read"))
post1 = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
post1 = frappe.get_doc("Blog Post", "-test-blog-post-1")
self.assertFalse(post1.has_permission("read")) self.assertFalse(post1.has_permission("read"))
def test_user_link_match_report(self): def test_user_link_match_report(self):
blogger = frappe.get_doc("Blogger", "_Test Blogger 1") blogger = frappe.get_doc("Test Blogger", "_Test Blogger 1")
blogger.user = "test2@example.com" blogger.user = "test2@example.com"
blogger.save() blogger.save()
frappe.permissions.add_user_permission("Test Blogger", blogger.name, blogger.user)
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
names = [d.name for d in frappe.get_list("Blog Post", fields=["name", "owner"])] names = [d.name for d in frappe.get_list("Test Blog Post", fields=["name", "owner"])]
self.assertTrue("-test-blog-post-2" in names) self.assertTrue("_Test Blog Post 2" in names)
self.assertFalse("-test-blog-post-1" in names) self.assertFalse("_Test Blog Post 1" in names)
def test_set_user_permissions(self): def test_set_user_permissions(self):
frappe.set_user("test1@example.com") frappe.set_user("test1@example.com")
add_user_permission("Blog Post", "-test-blog-post", "test2@example.com") add_user_permission("Test Blog Post", "_Test Blog Post", "test2@example.com")
def test_not_allowed_to_set_user_permissions(self): def test_not_allowed_to_set_user_permissions(self):
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
# this user can't add user permissions # this user can't add user permissions
self.assertRaises( self.assertRaises(
frappe.PermissionError, add_user_permission, "Blog Post", "-test-blog-post", "test2@example.com" frappe.PermissionError,
add_user_permission,
"Test Blog Post",
"_Test Blog Post",
"test2@example.com",
) )
def test_read_if_explicit_user_permissions_are_set(self): def test_read_if_explicit_user_permissions_are_set(self):
@ -194,11 +203,11 @@ class TestPermissions(IntegrationTestCase):
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
# user can only access permitted blog post # user can only access permitted blog post
doc = frappe.get_doc("Blog Post", "-test-blog-post") doc = frappe.get_doc("Test Blog Post", "_Test Blog Post")
self.assertTrue(doc.has_permission("read")) self.assertTrue(doc.has_permission("read"))
# and not this one # and not this one
doc = frappe.get_doc("Blog Post", "-test-blog-post-1") doc = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
self.assertFalse(doc.has_permission("read")) self.assertFalse(doc.has_permission("read"))
def test_not_allowed_to_remove_user_permissions(self): def test_not_allowed_to_remove_user_permissions(self):
@ -210,24 +219,24 @@ class TestPermissions(IntegrationTestCase):
self.assertRaises( self.assertRaises(
frappe.PermissionError, frappe.PermissionError,
remove_user_permission, remove_user_permission,
"Blog Post", "Test Blog Post",
"-test-blog-post", "_Test Blog Post",
"test2@example.com", "test2@example.com",
) )
def test_user_permissions_if_applied_on_doc_being_evaluated(self): def test_user_permissions_if_applied_on_doc_being_evaluated(self):
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
doc = frappe.get_doc("Blog Post", "-test-blog-post-1") doc = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
self.assertTrue(doc.has_permission("read")) self.assertTrue(doc.has_permission("read"))
frappe.set_user("test1@example.com") frappe.set_user("test1@example.com")
add_user_permission("Blog Post", "-test-blog-post", "test2@example.com") add_user_permission("Test Blog Post", "_Test Blog Post", "test2@example.com")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
doc = frappe.get_doc("Blog Post", "-test-blog-post-1") doc = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
self.assertFalse(doc.has_permission("read")) self.assertFalse(doc.has_permission("read"))
doc = frappe.get_doc("Blog Post", "-test-blog-post") doc = frappe.get_doc("Test Blog Post", "_Test Blog Post")
self.assertTrue(doc.has_permission("read")) self.assertTrue(doc.has_permission("read"))
def test_set_standard_fields_manually(self): def test_set_standard_fields_manually(self):
@ -257,8 +266,8 @@ class TestPermissions(IntegrationTestCase):
self.assertRaises(frappe.CannotChangeConstantError, user.save) self.assertRaises(frappe.CannotChangeConstantError, user.save)
def test_set_only_once(self): def test_set_only_once(self):
blog_post = frappe.get_meta("Blog Post") blog_post = frappe.get_meta("Test Blog Post")
doc = frappe.get_doc("Blog Post", "-test-blog-post-1") doc = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
doc.db_set("title", "Old") doc.db_set("title", "Old")
blog_post.get_field("title").set_only_once = 1 blog_post.get_field("title").set_only_once = 1
doc.title = "New" doc.title = "New"
@ -268,7 +277,7 @@ class TestPermissions(IntegrationTestCase):
def test_set_only_once_child_table_rows(self): def test_set_only_once_child_table_rows(self):
doctype_meta = frappe.get_meta("DocType") doctype_meta = frappe.get_meta("DocType")
doctype_meta.get_field("fields").set_only_once = 1 doctype_meta.get_field("fields").set_only_once = 1
doc = frappe.get_doc("DocType", "Blog Post") doc = frappe.get_doc("DocType", "Test Blog Post")
# remove last one # remove last one
doc.fields = doc.fields[:-1] doc.fields = doc.fields[:-1]
@ -278,51 +287,50 @@ class TestPermissions(IntegrationTestCase):
def test_set_only_once_child_table_row_value(self): def test_set_only_once_child_table_row_value(self):
doctype_meta = frappe.get_meta("DocType") doctype_meta = frappe.get_meta("DocType")
doctype_meta.get_field("fields").set_only_once = 1 doctype_meta.get_field("fields").set_only_once = 1
doc = frappe.get_doc("DocType", "Blog Post") doc = frappe.get_doc("DocType", "Test Blog Post")
# change one property from the child table # change one property from the child table
doc.fields[-1].fieldtype = "Check" doc.fields[-3].fieldtype = "Check"
self.assertRaises(frappe.CannotChangeConstantError, doc.save) self.assertRaises(frappe.CannotChangeConstantError, doc.save)
frappe.clear_cache(doctype="DocType") frappe.clear_cache(doctype="DocType")
def test_set_only_once_child_table_okay(self): def test_set_only_once_child_table_okay(self):
doctype_meta = frappe.get_meta("DocType") doctype_meta = frappe.get_meta("DocType")
doctype_meta.get_field("fields").set_only_once = 1 doctype_meta.get_field("fields").set_only_once = 1
doc = frappe.get_doc("DocType", "Blog Post") doc = frappe.get_doc("DocType", "Test Blog Post")
doc.load_doc_before_save() doc.load_doc_before_save()
self.assertFalse(doc.validate_set_only_once()) self.assertFalse(doc.validate_set_only_once())
frappe.clear_cache(doctype="DocType") frappe.clear_cache(doctype="DocType")
def test_user_permission_doctypes(self): def test_user_permission_doctypes(self):
add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") add_user_permission("Test Blog Category", "_Test Blog Category 1", "test2@example.com")
add_user_permission("Blogger", "_Test Blogger 1", "test2@example.com") add_user_permission("Test Blogger", "_Test Blogger 1", "test2@example.com")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
frappe.clear_cache(doctype="Blog Post") frappe.clear_cache(doctype="Test Blog Post")
doc = frappe.get_doc("Blog Post", "-test-blog-post") doc = frappe.get_doc("Test Blog Post", "_Test Blog Post")
self.assertFalse(doc.has_permission("read")) self.assertFalse(doc.has_permission("read"))
doc = frappe.get_doc("Blog Post", "-test-blog-post-2") doc = frappe.get_doc("Test Blog Post", "_Test Blog Post 2")
self.assertTrue(doc.has_permission("read")) self.assertTrue(doc.has_permission("read"))
frappe.clear_cache(doctype="Blog Post") frappe.clear_cache(doctype="Test Blog Post")
def if_owner_setup(self): def if_owner_setup(self):
update("Blog Post", "Blogger", 0, "if_owner", 1) update("Test Blog Post", "Blogger", 0, "if_owner", 1)
add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") add_user_permission("Test Blog Category", "_Test Blog Category 1", "test2@example.com")
add_user_permission("Blogger", "_Test Blogger 1", "test2@example.com") add_user_permission("Test Blogger", "_Test Blogger 1", "test2@example.com")
frappe.clear_cache(doctype="Blog Post") frappe.clear_cache(doctype="Test Blog Post")
def test_insert_if_owner_with_user_permissions(self): def test_insert_if_owner_with_user_permissions(self):
"""If `If Owner` is checked for a Role, check if that document """If `If Owner` is checked for a Role, check if that document
is allowed to be read, updated, submitted, etc. except be created, is allowed to be read, updated, submitted, etc. except be created,
even if the document is restricted based on User Permissions.""" even if the document is restricted based on User Permissions."""
frappe.delete_doc("Blog Post", "-test-blog-post-title") frappe.delete_doc("Test Blog Post", "-test-blog-post-title")
self.if_owner_setup() self.if_owner_setup()
@ -330,8 +338,8 @@ class TestPermissions(IntegrationTestCase):
doc = frappe.get_doc( doc = frappe.get_doc(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"blog_category": "-test-blog-category", "blog_category": "_Test Blog Category",
"blogger": "_Test Blogger 1", "blogger": "_Test Blogger 1",
"title": "_Test Blog Post Title", "title": "_Test Blog Post Title",
"content": "_Test Blog Post Content", "content": "_Test Blog Post Content",
@ -341,34 +349,35 @@ class TestPermissions(IntegrationTestCase):
self.assertRaises(frappe.PermissionError, doc.insert) self.assertRaises(frappe.PermissionError, doc.insert)
frappe.set_user("test1@example.com") frappe.set_user("test1@example.com")
add_user_permission("Blog Category", "-test-blog-category", "test2@example.com") add_user_permission("Test Blog Category", "_Test Blog Category", "test2@example.com")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
doc.insert() doc.insert()
frappe.set_user("Administrator") frappe.set_user("Administrator")
remove_user_permission("Blog Category", "-test-blog-category", "test2@example.com") remove_user_permission("Test Blog Category", "_Test Blog Category", "test2@example.com")
frappe.clear_cache()
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
doc = frappe.get_doc(doc.doctype, doc.name) doc = frappe.get_doc(doc.doctype, doc.name)
self.assertTrue(doc.has_permission("read")) self.assertTrue(doc.has_permission("read"))
self.assertTrue(doc.has_permission("write")) self.assertTrue(doc.has_permission("write"))
self.assertFalse(doc.has_permission("create")) self.assertFalse(doc.has_permission("create"))
# delete created record # delete created record
frappe.set_user("Administrator") frappe.set_user("Administrator")
frappe.delete_doc("Blog Post", "-test-blog-post-title") frappe.delete_doc("Test Blog Post", "_Test Blog Post Title")
def test_ignore_user_permissions_if_missing(self): def test_ignore_user_permissions_if_missing(self):
"""If there are no user permissions, then allow as per role""" """If there are no user permissions, then allow as per role"""
add_user_permission("Blog Category", "-test-blog-category", "test2@example.com") add_user_permission("Test Blog Category", "_Test Blog Category", "test2@example.com")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
doc = frappe.get_doc( doc = frappe.get_doc(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"blog_category": "-test-blog-category-2", "blog_category": "_Test Blog Category 2",
"blogger": "_Test Blogger 1", "blogger": "_Test Blogger 1",
"title": "_Test Blog Post Title", "title": "_Test Blog Post Title",
"content": "_Test Blog Post Content", "content": "_Test Blog Post Content",
@ -378,7 +387,7 @@ class TestPermissions(IntegrationTestCase):
self.assertFalse(doc.has_permission("write")) self.assertFalse(doc.has_permission("write"))
frappe.set_user("Administrator") frappe.set_user("Administrator")
remove_user_permission("Blog Category", "-test-blog-category", "test2@example.com") remove_user_permission("Test Blog Category", "_Test Blog Category", "test2@example.com")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
self.assertTrue(doc.has_permission("write")) self.assertTrue(doc.has_permission("write"))
@ -428,9 +437,9 @@ class TestPermissions(IntegrationTestCase):
clear_user_permissions_for_doctype("Contact") clear_user_permissions_for_doctype("Contact")
def test_user_permission_is_not_applied_if_user_roles_does_not_have_permission(self): def test_user_permission_is_not_applied_if_user_roles_does_not_have_permission(self):
add_user_permission("Blog Post", "-test-blog-post-1", "test3@example.com") add_user_permission("Test Blog Post", "_Test Blog Post 1", "test3@example.com")
frappe.set_user("test3@example.com") frappe.set_user("test3@example.com")
doc = frappe.get_doc("Blog Post", "-test-blog-post-1") doc = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
self.assertFalse(doc.has_permission("read")) self.assertFalse(doc.has_permission("read"))
frappe.set_user("Administrator") frappe.set_user("Administrator")
@ -444,20 +453,22 @@ class TestPermissions(IntegrationTestCase):
def test_contextual_user_permission(self): def test_contextual_user_permission(self):
# should be applicable for across all doctypes # should be applicable for across all doctypes
add_user_permission("Blogger", "_Test Blogger", "test2@example.com") add_user_permission("Test Blogger", "_Test Blogger", "test2@example.com")
# should be applicable only while accessing Blog Post # should be applicable only while accessing Blog Post
add_user_permission("Blogger", "_Test Blogger 1", "test2@example.com", applicable_for="Blog Post") add_user_permission(
"Test Blogger", "_Test Blogger 1", "test2@example.com", applicable_for="Test Blog Post"
)
# should be applicable only while accessing User # should be applicable only while accessing User
add_user_permission("Blogger", "_Test Blogger 2", "test2@example.com", applicable_for="User") add_user_permission("Test Blogger", "_Test Blogger 2", "test2@example.com", applicable_for="User")
posts = frappe.get_all("Blog Post", fields=["name", "blogger"]) posts = frappe.get_all("Test Blog Post", fields=["name", "blogger"])
# Get all posts for admin # Get all posts for admin
self.assertEqual(len(posts), 4) self.assertEqual(len(posts), 4)
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
posts = frappe.get_list("Blog Post", fields=["name", "blogger"]) posts = frappe.get_list("Test Blog Post", fields=["name", "blogger"])
# Should get only posts with allowed blogger via user permission # Should get only posts with allowed blogger via user permission
# only '_Test Blogger', '_Test Blogger 1' are allowed in Blog Post # only '_Test Blogger', '_Test Blogger 1' are allowed in Blog Post
@ -473,32 +484,32 @@ class TestPermissions(IntegrationTestCase):
def test_if_owner_permission_overrides_properly(self): def test_if_owner_permission_overrides_properly(self):
# check if user is not granted access if the user is not the owner of the doc # check if user is not granted access if the user is not the owner of the doc
# Blogger has only read access on the blog post unless he is the owner of the blog # Blogger has only read access on the blog post unless he is the owner of the blog
update("Blog Post", "Blogger", 0, "if_owner", 1) update("Test Blog Post", "Blogger", 0, "if_owner", 1)
update("Blog Post", "Blogger", 0, "read", 1, 1) update("Test Blog Post", "Blogger", 0, "read", 1, 1)
update("Blog Post", "Blogger", 0, "write", 1, 1) update("Test Blog Post", "Blogger", 0, "write", 1, 1)
update("Blog Post", "Blogger", 0, "delete", 1, 1) update("Test Blog Post", "Blogger", 0, "delete", 1, 1)
# currently test2 user has not created any document # currently test2 user has not created any document
# still he should be able to do get_list query which should # still he should be able to do get_list query which should
# not raise permission error but simply return empty list # not raise permission error but simply return empty list
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
self.assertEqual(frappe.get_list("Blog Post"), []) self.assertEqual(frappe.get_list("Test Blog Post"), [])
frappe.set_user("Administrator") frappe.set_user("Administrator")
# creates a custom docperm with just read access # creates a custom docperm with just read access
# now any user can read any blog post (but other rights are limited to the blog post owner) # now any user can read any blog post (but other rights are limited to the blog post owner)
add_permission("Blog Post", "Blogger") add_permission("Test Blog Post", "Blogger")
frappe.clear_cache(doctype="Blog Post") frappe.clear_cache(doctype="Test Blog Post")
frappe.delete_doc("Blog Post", "-test-blog-post-title") frappe.delete_doc("Test Blog Post", "_Test Blog Post Title")
frappe.set_user("test1@example.com") frappe.set_user("test1@example.com")
doc = frappe.get_doc( doc = frappe.get_doc(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"blog_category": "-test-blog-category", "blog_category": "_Test Blog Category",
"blogger": "_Test Blogger 1", "blogger": "_Test Blogger 1",
"title": "_Test Blog Post Title", "title": "_Test Blog Post Title",
"content": "_Test Blog Post Content", "content": "_Test Blog Post Content",
@ -523,21 +534,21 @@ class TestPermissions(IntegrationTestCase):
self.assertTrue(doc.has_permission("delete")) self.assertTrue(doc.has_permission("delete"))
# delete the created doc # delete the created doc
frappe.delete_doc("Blog Post", "-test-blog-post-title") frappe.delete_doc("Test Blog Post", "_Test Blog Post Title")
def test_if_owner_permission_on_getdoc(self): def test_if_owner_permission_on_getdoc(self):
update("Blog Post", "Blogger", 0, "if_owner", 1) update("Test Blog Post", "Blogger", 0, "if_owner", 1)
update("Blog Post", "Blogger", 0, "read", 1) update("Test Blog Post", "Blogger", 0, "read", 1)
update("Blog Post", "Blogger", 0, "write", 1) update("Test Blog Post", "Blogger", 0, "write", 1)
update("Blog Post", "Blogger", 0, "delete", 1) update("Test Blog Post", "Blogger", 0, "delete", 1)
frappe.clear_cache(doctype="Blog Post") frappe.clear_cache(doctype="Test Blog Post")
frappe.set_user("test1@example.com") frappe.set_user("test1@example.com")
doc = frappe.get_doc( doc = frappe.get_doc(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"blog_category": "-test-blog-category", "blog_category": "_Test Blog Category",
"blogger": "_Test Blogger 1", "blogger": "_Test Blogger 1",
"title": "_Test Blog Post Title New", "title": "_Test Blog Post Title New",
"content": "_Test Blog Post Content", "content": "_Test Blog Post Content",
@ -546,18 +557,18 @@ class TestPermissions(IntegrationTestCase):
doc.insert() doc.insert()
getdoc("Blog Post", doc.name) getdoc("Test Blog Post", doc.name)
doclist = [d.name for d in frappe.response.docs] doclist = [d.name for d in frappe.response.docs]
self.assertTrue(doc.name in doclist) self.assertTrue(doc.name in doclist)
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
self.assertRaises(frappe.PermissionError, getdoc, "Blog Post", doc.name) self.assertRaises(frappe.PermissionError, getdoc, "Test Blog Post", doc.name)
def test_if_owner_permission_on_get_list(self): def test_if_owner_permission_on_get_list(self):
doc = frappe.get_doc( doc = frappe.get_doc(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"blog_category": "-test-blog-category", "blog_category": "_Test Blog Category",
"blogger": "_Test Blogger 1", "blogger": "_Test Blogger 1",
"title": "_Test If Owner Permissions on Get List", "title": "_Test If Owner Permissions on Get List",
"content": "_Test Blog Post Content", "content": "_Test Blog Post Content",
@ -566,39 +577,39 @@ class TestPermissions(IntegrationTestCase):
doc.insert(ignore_if_duplicate=True) doc.insert(ignore_if_duplicate=True)
update("Blog Post", "Blogger", 0, "if_owner", 1) update("Test Blog Post", "Blogger", 0, "if_owner", 1)
update("Blog Post", "Blogger", 0, "read", 1) update("Test Blog Post", "Blogger", 0, "read", 1)
user = frappe.get_doc("User", "test2@example.com") user = frappe.get_doc("User", "test2@example.com")
user.add_roles("Website Manager") user.add_roles("Website Manager")
frappe.clear_cache(doctype="Blog Post") frappe.clear_cache(doctype="Test Blog Post")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
self.assertIn(doc.name, frappe.get_list("Blog Post", pluck="name")) self.assertIn(doc.name, frappe.get_list("Test Blog Post", pluck="name"))
# Become system manager to remove role # Become system manager to remove role
frappe.set_user("test1@example.com") frappe.set_user("test1@example.com")
user.remove_roles("Website Manager") user.remove_roles("Website Manager")
frappe.clear_cache(doctype="Blog Post") frappe.clear_cache(doctype="Test Blog Post")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
self.assertNotIn(doc.name, frappe.get_list("Blog Post", pluck="name")) self.assertNotIn(doc.name, frappe.get_list("Test Blog Post", pluck="name"))
def test_if_owner_permission_on_delete(self): def test_if_owner_permission_on_delete(self):
update("Blog Post", "Blogger", 0, "if_owner", 1) update("Test Blog Post", "Blogger", 0, "if_owner", 1)
update("Blog Post", "Blogger", 0, "read", 1, 1) update("Test Blog Post", "Blogger", 0, "read", 1, 1)
update("Blog Post", "Blogger", 0, "write", 1, 1) update("Test Blog Post", "Blogger", 0, "write", 1, 1)
update("Blog Post", "Blogger", 0, "delete", 1, 1) update("Test Blog Post", "Blogger", 0, "delete", 1, 1)
# Remove delete perm # Remove delete perm
update("Blog Post", "Website Manager", 0, "delete", 0) update("Test Blog Post", "Website Manager", 0, "delete", 0)
frappe.clear_cache(doctype="Blog Post") frappe.clear_cache(doctype="Test Blog Post")
with self.set_user("test2@example.com"): with self.set_user("test2@example.com"):
doc = frappe.get_doc( doc = frappe.get_doc(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"blog_category": "-test-blog-category", "blog_category": "_Test Blog Category",
"blogger": "_Test Blogger 1", "blogger": "_Test Blogger 1",
"title": "_Test Blog Post Title New 1", "title": "_Test Blog Post Title New 1",
"content": "_Test Blog Post Content", "content": "_Test Blog Post Content",
@ -607,46 +618,46 @@ class TestPermissions(IntegrationTestCase):
doc.insert() doc.insert()
getdoc("Blog Post", doc.name) getdoc("Test Blog Post", doc.name)
doclist = [d.name for d in frappe.response.docs] doclist = [d.name for d in frappe.response.docs]
self.assertTrue(doc.name in doclist) self.assertTrue(doc.name in doclist)
with self.set_user("testperm@example.com"): with self.set_user("testperm@example.com"):
# Website Manager able to read # Website Manager able to read
getdoc("Blog Post", doc.name) getdoc("Test Blog Post", doc.name)
doclist = [d.name for d in frappe.response.docs] doclist = [d.name for d in frappe.response.docs]
self.assertTrue(doc.name in doclist) self.assertTrue(doc.name in doclist)
# Website Manager should not be able to delete # Website Manager should not be able to delete
self.assertRaises(frappe.PermissionError, frappe.delete_doc, "Blog Post", doc.name) self.assertRaises(frappe.PermissionError, frappe.delete_doc, "Test Blog Post", doc.name)
with self.set_user("test2@example.com"): with self.set_user("test2@example.com"):
frappe.delete_doc("Blog Post", "-test-blog-post-title-new-1") frappe.delete_doc("Test Blog Post", "_Test Blog Post Title New 1")
update("Blog Post", "Website Manager", 0, "delete", 1, 1) update("Test Blog Post", "Website Manager", 0, "delete", 1, 1)
def test_clear_user_permissions(self): def test_clear_user_permissions(self):
current_user = frappe.session.user current_user = frappe.session.user
frappe.set_user("Administrator") frappe.set_user("Administrator")
clear_user_permissions_for_doctype("Blog Category", "test2@example.com") clear_user_permissions_for_doctype("Test Blog Category", "test2@example.com")
clear_user_permissions_for_doctype("Blog Post", "test2@example.com") clear_user_permissions_for_doctype("Test Blog Post", "test2@example.com")
add_user_permission("Blog Post", "-test-blog-post-1", "test2@example.com") add_user_permission("Test Blog Post", "_Test Blog Post 1", "test2@example.com")
add_user_permission("Blog Post", "-test-blog-post-2", "test2@example.com") add_user_permission("Test Blog Post", "_Test Blog Post 2", "test2@example.com")
add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com") add_user_permission("Test Blog Category", "_Test Blog Category 1", "test2@example.com")
deleted_user_permission_count = clear_user_permissions("test2@example.com", "Blog Post") deleted_user_permission_count = clear_user_permissions("test2@example.com", "Test Blog Post")
self.assertEqual(deleted_user_permission_count, 2) self.assertEqual(deleted_user_permission_count, 2)
blog_post_user_permission_count = frappe.db.count( blog_post_user_permission_count = frappe.db.count(
"User Permission", filters={"user": "test2@example.com", "allow": "Blog Post"} "User Permission", filters={"user": "test2@example.com", "allow": "Test Blog Post"}
) )
self.assertEqual(blog_post_user_permission_count, 0) self.assertEqual(blog_post_user_permission_count, 0)
blog_category_user_permission_count = frappe.db.count( blog_category_user_permission_count = frappe.db.count(
"User Permission", filters={"user": "test2@example.com", "allow": "Blog Category"} "User Permission", filters={"user": "test2@example.com", "allow": "Test Blog Category"}
) )
self.assertEqual(blog_category_user_permission_count, 1) self.assertEqual(blog_category_user_permission_count, 1)

View file

@ -13,10 +13,11 @@ from frappe.tests.test_db_query import (
setup_patched_blog_post, setup_patched_blog_post,
setup_test_user, setup_test_user,
) )
from frappe.tests.test_helpers import setup_for_tests
from frappe.tests.test_query_builder import db_type_is, run_only_if from frappe.tests.test_query_builder import db_type_is, run_only_if
from frappe.utils.nestedset import get_ancestors_of, get_descendants_of from frappe.utils.nestedset import get_ancestors_of, get_descendants_of
EXTRA_TEST_RECORD_DEPENDENCIES = ["User", "Blog Post", "Blog Category", "Blogger"] EXTRA_TEST_RECORD_DEPENDENCIES = ["User"]
def create_tree_docs(): def create_tree_docs():
@ -63,6 +64,9 @@ def create_tree_docs():
class TestQuery(IntegrationTestCase): class TestQuery(IntegrationTestCase):
def setUp(self):
setup_for_tests()
@run_only_if(db_type_is.MARIADB) @run_only_if(db_type_is.MARIADB)
def test_multiple_tables_in_filters(self): def test_multiple_tables_in_filters(self):
self.assertEqual( self.assertEqual(
@ -719,31 +723,30 @@ class TestQuery(IntegrationTestCase):
def test_build_match_conditions(self): def test_build_match_conditions(self):
from frappe.permissions import add_user_permission, clear_user_permissions_for_doctype from frappe.permissions import add_user_permission, clear_user_permissions_for_doctype
clear_user_permissions_for_doctype("Blog Post", "test2@example.com") clear_user_permissions_for_doctype("Test Blog Post", "test2@example.com")
test2user = frappe.get_doc("User", "test2@example.com") test2user = frappe.get_doc("User", "test2@example.com")
test2user.add_roles("Blogger") test2user.add_roles("Blogger")
frappe.set_user("test2@example.com") frappe.set_user("test2@example.com")
# Before any user permission is applied, there should be no conditions # Before any user permission is applied, there should be no conditions
query = frappe.qb.get_query("Blog Post", ignore_permissions=False) query = frappe.qb.get_query("Test Blog Post", ignore_permissions=False)
self.assertNotIn("(`tabBlog Post`.`name` in (", str(query)) self.assertNotIn("(`tabBlog Post`.`name` in (", str(query))
# Add user permissions # Add user permissions
add_user_permission("Blog Post", "-test-blog-post", "test2@example.com", True) add_user_permission("Test Blog Post", "_Test Blog Post", "test2@example.com", True)
add_user_permission("Blog Post", "-test-blog-post-1", "test2@example.com", True) add_user_permission("Test Blog Post", "_Test Blog Post 1", "test2@example.com", True)
# After applying user permission, condition should be in query # After applying user permission, condition should be in query
query = str(frappe.qb.get_query("Blog Post", ignore_permissions=False)) query = str(frappe.qb.get_query("Test Blog Post", ignore_permissions=False))
# Check for user permission condition in the query string # Check for user permission condition in the query string
if frappe.db.db_type == "mariadb": if frappe.db.db_type == "mariadb":
self.assertIn("`name` IS NULL OR `name` IN ('-test-blog-post-1','-test-blog-post')", query) self.assertIn("`name` IS NULL OR `name` IN ('_Test Blog Post 1','_Test Blog Post')", query)
elif frappe.db.db_type == "postgres": elif frappe.db.db_type == "postgres":
self.assertIn("\"name\" IS NULL OR \"name\" IN ('-test-blog-post-1','-test-blog-post')", query) self.assertIn("\"name\" IS NULL OR \"name\" IN ('_Test Blog Post 1','_Test Blog Post')", query)
frappe.set_user("Administrator") frappe.set_user("Administrator")
clear_user_permissions_for_doctype("Blog Post", "test2@example.com") clear_user_permissions_for_doctype("Test Blog Post", "test2@example.com")
test2user.remove_roles("Blogger") test2user.remove_roles("Blogger")
def test_ignore_permissions_for_query(self): def test_ignore_permissions_for_query(self):
@ -763,17 +766,17 @@ class TestQuery(IntegrationTestCase):
# Create a test blog post # Create a test blog post
test_post = frappe.get_doc( test_post = frappe.get_doc(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"title": "Test Permission Post", "title": "Test Permission Post",
"content": "Test Content", "content": "Test Content",
"blog_category": "-test-blog-category", "blog_category": "_Test Blog Category",
"published": 1, "published": 1,
} }
).insert(ignore_permissions=True, ignore_mandatory=True) ).insert(ignore_permissions=True, ignore_mandatory=True)
# Without proper permission, published field should be filtered out # Without proper permission, published field should be filtered out
data = frappe.qb.get_query( data = frappe.qb.get_query(
"Blog Post", "Test Blog Post",
filters={"name": test_post.name}, filters={"name": test_post.name},
fields=["name", "published", "title"], fields=["name", "published", "title"],
ignore_permissions=False, ignore_permissions=False,
@ -787,7 +790,7 @@ class TestQuery(IntegrationTestCase):
# With Administrator, all fields should be accessible # With Administrator, all fields should be accessible
frappe.set_user("Administrator") frappe.set_user("Administrator")
data = frappe.qb.get_query( data = frappe.qb.get_query(
"Blog Post", "Test Blog Post",
filters={"name": test_post.name}, filters={"name": test_post.name},
fields=["name", "published", "title"], fields=["name", "published", "title"],
ignore_permissions=False, ignore_permissions=False,
@ -1055,10 +1058,10 @@ class TestQuery(IntegrationTestCase):
# Create a test blog post # Create a test blog post
test_post = frappe.get_doc( test_post = frappe.get_doc(
{ {
"doctype": "Blog Post", "doctype": "Test Blog Post",
"title": "Test Filter Permission Post", "title": "Test Filter Permission Post",
"content": "Test Content", "content": "Test Content",
"blog_category": "-test-blog-category", "blog_category": "_Test Blog Category",
"published": 1, # permlevel 1 "published": 1, # permlevel 1
} }
).insert(ignore_permissions=True, ignore_mandatory=True, ignore_if_duplicate=True) ).insert(ignore_permissions=True, ignore_mandatory=True, ignore_if_duplicate=True)
@ -1067,7 +1070,7 @@ class TestQuery(IntegrationTestCase):
# Try filtering on permitted field (title - permlevel 0) # Try filtering on permitted field (title - permlevel 0)
try: try:
frappe.qb.get_query( frappe.qb.get_query(
"Blog Post", "Test Blog Post",
filters={"title": test_post.title}, filters={"title": test_post.title},
ignore_permissions=False, ignore_permissions=False,
user=user.name, user=user.name,
@ -1078,7 +1081,7 @@ class TestQuery(IntegrationTestCase):
# Try filtering on non-permitted field (published - permlevel 1) # Try filtering on non-permitted field (published - permlevel 1)
with self.assertRaises(frappe.PermissionError) as cm: with self.assertRaises(frappe.PermissionError) as cm:
frappe.qb.get_query( frappe.qb.get_query(
"Blog Post", "Test Blog Post",
filters={"published": 1}, filters={"published": 1},
ignore_permissions=False, ignore_permissions=False,
user=user.name, user=user.name,

View file

@ -5,11 +5,6 @@ from frappe.utils import get_html_for_route
class TestSitemap(IntegrationTestCase): class TestSitemap(IntegrationTestCase):
def test_sitemap(self): def test_sitemap(self):
from frappe.tests.utils import make_test_records
make_test_records("Blog Post")
blogs = frappe.get_all("Blog Post", {"published": 1}, ["route"], limit=1)
xml = get_html_for_route("sitemap.xml") xml = get_html_for_route("sitemap.xml")
self.assertTrue("/about</loc>" in xml) self.assertTrue("/about</loc>" in xml)
self.assertTrue("/contact</loc>" in xml) self.assertTrue("/contact</loc>" in xml)
self.assertTrue(blogs[0].route in xml)

View file

@ -1,6 +1,7 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.permissions import AUTOMATIC_ROLES from frappe.permissions import AUTOMATIC_ROLES
from frappe.tests.test_helpers import create_test_blog_category
from frappe.utils import add_to_date, now from frappe.utils import add_to_date, now
UI_TEST_USER = "frappe@example.com" UI_TEST_USER = "frappe@example.com"
@ -85,6 +86,13 @@ def prepare_webform_test():
frappe.delete_doc_if_exists("Web Form", "note") frappe.delete_doc_if_exists("Web Form", "note")
@whitelist_for_tests
def create_doctype_for_attachment():
create_test_blog_category()
doc = frappe.get_doc("Test Blog Category", "_Test Blog Category 2")
return doc
@whitelist_for_tests @whitelist_for_tests
def create_communication_record(): def create_communication_record():
doc = frappe.get_doc( doc = frappe.get_doc(
@ -397,33 +405,6 @@ def insert_translations():
frappe.get_doc(doc).insert(ignore_if_duplicate=True) frappe.get_doc(doc).insert(ignore_if_duplicate=True)
@whitelist_for_tests
def create_blog_post():
blog_category = frappe.get_doc(
{"name": "general", "doctype": "Blog Category", "title": "general"}
).insert(ignore_if_duplicate=True)
blogger = frappe.get_doc(
{
"name": "attachment blogger",
"doctype": "Blogger",
"full_name": "attachment blogger",
"short_name": "attachment blogger",
}
).insert(ignore_if_duplicate=True)
return frappe.get_doc(
{
"name": "test-blog-attachment-post",
"doctype": "Blog Post",
"title": "test-blog-attachment-post",
"blog_category": blog_category.name,
"blogger": blogger.name,
"content_type": "Rich Text",
},
).insert(ignore_if_duplicate=True)
@whitelist_for_tests @whitelist_for_tests
def create_test_user(username=None): def create_test_user(username=None):
name = username or UI_TEST_USER name = username or UI_TEST_USER

View file

@ -1 +0,0 @@
Blog category.

View file

@ -1,6 +0,0 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on("Blog Category", {
refresh: function (frm) {},
});

View file

@ -1,94 +0,0 @@
{
"actions": [],
"allow_guest_to_view": 1,
"allow_import": 1,
"allow_rename": 1,
"creation": "2013-03-08 09:41:11",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"published",
"title",
"route",
"preview_image",
"description"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"no_copy": 1,
"reqd": 1
},
{
"default": "1",
"fieldname": "published",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Published"
},
{
"depends_on": "published",
"fieldname": "route",
"fieldtype": "Data",
"label": "Route",
"read_only": 1,
"unique": 1
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description"
},
{
"fieldname": "preview_image",
"fieldtype": "Attach Image",
"label": "Preview Image"
}
],
"has_web_view": 1,
"icon": "fa fa-tag",
"idx": 1,
"index_web_pages_for_search": 1,
"is_published_field": "published",
"links": [
{
"link_doctype": "Blog Post",
"link_fieldname": "blog_category"
}
],
"make_attachments_public": 1,
"modified": "2024-08-15 19:03:00.345431",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Category",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Website Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Blogger"
}
],
"quick_entry": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}

View file

@ -1,33 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
from frappe.website.utils import clear_cache
from frappe.website.website_generator import WebsiteGenerator
class BlogCategory(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.types import DF
description: DF.SmallText | None
preview_image: DF.AttachImage | None
published: DF.Check
route: DF.Data | None
title: DF.Data
# end: auto-generated types
def autoname(self):
# to override autoname of WebsiteGenerator
self.name = self.scrub(self.title)
def on_update(self):
clear_cache()
def set_route(self):
# Override blog route since it has to been templated
self.route = "blog/" + self.name

View file

@ -1,9 +0,0 @@
{% extends "templates/pages/blog.html" %}
{% block title %}{{ title }}{% endblock %}
{% block script %}
<script>
window.category = "{{ docname }}";
</script>
{% endblock %}

View file

@ -1,4 +0,0 @@
<div>
<a href={{ route }}>{{ title }}</a>
</div>
<!-- this is a sample default list template -->

View file

@ -1,8 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
from frappe.tests import IntegrationTestCase
class TestBlogCategory(IntegrationTestCase):
pass

View file

@ -1,17 +0,0 @@
[
{
"doctype": "Blog Category",
"parent_website_route": "blog",
"title": "_Test Blog Category"
},
{
"doctype": "Blog Category",
"parent_website_route": "blog",
"title": "_Test Blog Category 1"
},
{
"doctype": "Blog Category",
"parent_website_route": "blog",
"title": "_Test Blog Category 2"
}
]

View file

@ -1 +0,0 @@
Blog post for "Blogs" section of website.

View file

@ -1,67 +0,0 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on("Blog Post", {
refresh: function (frm) {
frappe.db.get_single_value("Blog Settings", "show_cta_in_blog").then((value) => {
frm.set_df_property("hide_cta", "hidden", !value);
});
frm.trigger("add_publish_button");
generate_google_search_preview(frm);
},
title: function (frm) {
generate_google_search_preview(frm);
frm.trigger("set_route");
},
meta_description: function (frm) {
generate_google_search_preview(frm);
},
blog_intro: function (frm) {
generate_google_search_preview(frm);
},
blog_category(frm) {
frm.trigger("set_route");
},
set_route(frm) {
if (frm.doc.route) return;
if (frm.doc.title && frm.doc.blog_category) {
frm.call("make_route").then((r) => {
frm.set_value("route", r.message);
});
}
},
add_publish_button(frm) {
frm.add_custom_button(frm.doc.published ? __("Unpublish") : __("Publish"), () => {
frm.set_value("published", !frm.doc.published);
frm.save();
});
},
});
function generate_google_search_preview(frm) {
if (!(frm.doc.meta_title || frm.doc.title)) return;
let google_preview = frm.get_field("google_preview");
let seo_title = (frm.doc.meta_title || frm.doc.title).slice(0, 60);
let seo_description = (frm.doc.meta_description || frm.doc.blog_intro || "").slice(0, 160);
let date = frm.doc.published_on ? moment(frm.doc.published_on).format("ll") + "-" : "";
let route_array = frm.doc.route ? frm.doc.route.split("/") : [];
route_array.pop();
google_preview.html(`
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400&display=swap" rel="stylesheet">
<div style="font-family: Open Sans; padding: 15px; border: 1px solid #d1d8dd !important; border-radius: 6px;">
<cite style="font-size: 14px; padding-top: 1px; line-height: 1.3; color: #202124; font-style: normal;">
${frappe.boot.sitename}
<span style="color: #5f6368;"> ${route_array.join(" ")}</span>
</cite>
<div style="font-size: 20px; line-height: 1.3; color: #1a0dab; padding-top: 4px; margin-bottom: 3px;">
${seo_title}
</div>
<p style="color: #545454; max-width: 48em; line-height: 1.58; font-size:14px;">
<span style="color: #70757a;">${date}</span> ${seo_description}
</p>
</div>
`);
}

View file

@ -1,253 +0,0 @@
{
"actions": [],
"allow_guest_to_view": 1,
"allow_import": 1,
"creation": "2023-08-03 19:53:03.782490",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"title",
"blog_category",
"blogger",
"route",
"read_time",
"column_break_3",
"published_on",
"published",
"featured",
"hide_cta",
"enable_email_notification",
"disable_comments",
"disable_likes",
"section_break_5",
"blog_intro",
"content_type",
"content",
"content_md",
"content_html",
"email_sent",
"meta_tags",
"meta_title",
"meta_description",
"column_break_18",
"meta_image",
"section_break_20",
"google_preview"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"in_global_search": 1,
"label": "Title",
"no_copy": 1,
"reqd": 1
},
{
"fieldname": "published_on",
"fieldtype": "Date",
"label": "Published On"
},
{
"default": "0",
"fieldname": "published",
"fieldtype": "Check",
"hidden": 1,
"label": "Published"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "blog_category",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Blog Category",
"options": "Blog Category",
"reqd": 1
},
{
"fieldname": "blogger",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Blogger",
"options": "Blogger",
"reqd": 1
},
{
"fieldname": "route",
"fieldtype": "Data",
"label": "Route",
"unique": 1
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
},
{
"description": "Description for listing page, in plain text, only a couple of lines. (max 200 characters)",
"fieldname": "blog_intro",
"fieldtype": "Small Text",
"label": "Blog Intro"
},
{
"default": "Markdown",
"fieldname": "content_type",
"fieldtype": "Select",
"label": "Content Type",
"options": "Markdown\nRich Text\nHTML",
"reqd": 1
},
{
"depends_on": "eval:doc.content_type === 'Rich Text'",
"fieldname": "content",
"fieldtype": "Text Editor",
"ignore_xss_filter": 1,
"in_global_search": 1,
"label": "Content"
},
{
"depends_on": "eval:doc.content_type === 'Markdown'",
"fieldname": "content_md",
"fieldtype": "Markdown Editor",
"ignore_xss_filter": 1,
"label": "Content (Markdown)"
},
{
"depends_on": "eval:doc.content_type === 'HTML'",
"fieldname": "content_html",
"fieldtype": "HTML Editor",
"ignore_xss_filter": 1,
"label": "Content (HTML)"
},
{
"default": "0",
"fieldname": "email_sent",
"fieldtype": "Check",
"hidden": 1,
"label": "Email Sent"
},
{
"default": "0",
"fieldname": "disable_comments",
"fieldtype": "Check",
"label": "Disable Comments"
},
{
"fieldname": "meta_description",
"fieldtype": "Small Text",
"label": "Meta Description"
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
},
{
"fieldname": "meta_image",
"fieldtype": "Attach Image",
"label": "Meta Image",
"mandatory_depends_on": "eval:doc.featured"
},
{
"fieldname": "section_break_20",
"fieldtype": "Section Break"
},
{
"description": "This is an example Google SERP Preview.",
"fieldname": "google_preview",
"fieldtype": "HTML",
"label": "Google Snippet Preview",
"read_only": 1
},
{
"fieldname": "meta_tags",
"fieldtype": "Section Break",
"label": "Meta Tags"
},
{
"description": "in minutes",
"fieldname": "read_time",
"fieldtype": "Int",
"hidden": 1,
"label": "Read Time",
"read_only": 1
},
{
"default": "0",
"fieldname": "featured",
"fieldtype": "Check",
"label": "Featured"
},
{
"default": "0",
"fieldname": "hide_cta",
"fieldtype": "Check",
"hidden": 1,
"label": "Hide CTA"
},
{
"fieldname": "meta_title",
"fieldtype": "Data",
"label": "Meta Title",
"length": 60
},
{
"default": "1",
"description": "Enable email notification for any comment or likes received on your Blog Post.",
"fieldname": "enable_email_notification",
"fieldtype": "Check",
"label": "Enable Email Notification"
},
{
"default": "0",
"fieldname": "disable_likes",
"fieldtype": "Check",
"label": "Disable Likes"
}
],
"has_web_view": 1,
"icon": "fa fa-quote-left",
"idx": 1,
"index_web_pages_for_search": 1,
"is_published_field": "published",
"links": [],
"make_attachments_public": 1,
"modified": "2024-03-23 16:01:29.148911",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Post",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Website Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Blogger",
"share": 1,
"write": 1
}
],
"route": "blog",
"sort_field": "creation",
"sort_order": "ASC",
"states": [],
"title_field": "title",
"track_changes": 1
}

View file

@ -1,396 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
from math import ceil
import frappe
from frappe import _
from frappe.utils import (
cint,
get_fullname,
global_date_format,
markdown,
sanitize_html,
strip_html_tags,
today,
)
from frappe.website.utils import (
clear_cache,
find_first_image,
get_comment_list,
get_html_content_based_on_type,
)
from frappe.website.website_generator import WebsiteGenerator
class BlogPost(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.types import DF
blog_category: DF.Link
blog_intro: DF.SmallText | None
blogger: DF.Link
content: DF.TextEditor | None
content_html: DF.HTMLEditor | None
content_md: DF.MarkdownEditor | None
content_type: DF.Literal["Markdown", "Rich Text", "HTML"]
disable_comments: DF.Check
disable_likes: DF.Check
email_sent: DF.Check
enable_email_notification: DF.Check
featured: DF.Check
hide_cta: DF.Check
meta_description: DF.SmallText | None
meta_image: DF.AttachImage | None
meta_title: DF.Data | None
published: DF.Check
published_on: DF.Date | None
read_time: DF.Int
route: DF.Data | None
title: DF.Data
# end: auto-generated types
@frappe.whitelist()
def make_route(self):
if not self.route:
return (
frappe.db.get_value("Blog Category", self.blog_category, "route")
+ "/"
+ self.scrub(self.title)
)
def validate(self):
super().validate()
if not self.blog_intro:
content = get_html_content_based_on_type(self, "content", self.content_type)
self.blog_intro = strip_html_tags(content)[:200]
if self.blog_intro:
self.blog_intro = self.blog_intro[:200]
if not self.meta_title:
self.meta_title = self.title[:60]
else:
self.meta_title = self.meta_title[:60]
if not self.meta_description:
self.meta_description = self.blog_intro[:140]
else:
self.meta_description = self.meta_description[:140]
if self.published and not self.published_on:
self.published_on = today()
if self.featured:
if not self.meta_image:
frappe.throw(_("A featured post must have a cover image"))
self.reset_featured_for_other_blogs()
self.set_read_time()
if self.is_website_published():
from frappe.core.doctype.file.utils import extract_images_from_doc
# Extract images first before the standard image extraction to ensure they are public.
extract_images_from_doc(self, "content", is_private=False)
extract_images_from_doc(self, "content_md", is_private=False)
def reset_featured_for_other_blogs(self):
all_posts = frappe.get_all("Blog Post", {"featured": 1})
for post in all_posts:
frappe.db.set_value("Blog Post", post.name, "featured", 0)
def on_update(self):
super().on_update()
clear_cache("writers")
def on_trash(self):
super().on_trash()
def get_context(self, context):
# this is for double precaution. usually it wont reach this code if not published
if not cint(self.published):
raise Exception("This blog has not been published yet!")
context.no_breadcrumbs = True
# temp fields
context.full_name = get_fullname(self.owner)
context.updated = global_date_format(self.published_on)
context.social_links = self.fetch_social_links_info()
context.cta = self.fetch_cta()
context.enable_cta = not self.hide_cta and frappe.db.get_single_value(
"Blog Settings", "show_cta_in_blog", cache=True
)
if self.blogger:
context.blogger_info = frappe.get_doc("Blogger", self.blogger).as_dict()
context.author = self.blogger
context.content = get_html_content_based_on_type(self, "content", self.content_type)
# if meta description is not present, then blog intro or first 140 characters of the blog will be set as description
context.description = (
self.meta_description or self.blog_intro or strip_html_tags(context.content[:140])
)
context.metatags = {
"name": self.meta_title,
"description": context.description,
}
# if meta image is not present, then first image inside the blog will be set as the meta image
image = find_first_image(context.content)
context.metatags["image"] = self.meta_image or image or None
self.load_comments(context)
self.load_likes(context)
context.category = frappe.db.get_value(
"Blog Category", context.doc.blog_category, ["title", "route"], as_dict=1
)
context.parents = [
{"name": _("Home"), "route": "/"},
{"name": "Blog", "route": "/blog"},
{"label": context.category.title, "route": context.category.route},
]
context.guest_allowed = frappe.db.get_single_value("Blog Settings", "allow_guest_to_comment")
def fetch_cta(self):
if frappe.db.get_single_value("Blog Settings", "show_cta_in_blog", cache=True):
blog_settings = frappe.get_cached_doc("Blog Settings")
return {
"show_cta_in_blog": 1,
"title": blog_settings.title,
"subtitle": blog_settings.subtitle,
"cta_label": blog_settings.cta_label,
"cta_url": blog_settings.cta_url,
}
return {}
def fetch_social_links_info(self):
if not frappe.db.get_single_value("Blog Settings", "enable_social_sharing", cache=True):
return []
url = frappe.local.site + "/" + self.route
return [
{
"icon": "twitter",
"link": "https://twitter.com/intent/tweet?text=" + self.title + "&url=" + url,
},
{
"icon": "facebook",
"link": "https://www.facebook.com/sharer.php?u=" + url,
},
{
"icon": "linkedin",
"link": "https://www.linkedin.com/sharing/share-offsite/?url=" + url,
},
{
"icon": "envelope",
"link": "mailto:?subject=" + self.title + "&body=" + url,
},
]
def load_comments(self, context):
context.comment_list = get_comment_list(self.doctype, self.name)
if not context.comment_list:
context.comment_count = 0
else:
context.comment_count = len(context.comment_list)
def load_likes(self, context):
user = frappe.session.user
filters = {
"comment_type": "Like",
"reference_doctype": self.doctype,
"reference_name": self.name,
}
context.like_count = frappe.db.count("Comment", filters)
filters["comment_email"] = user
if user == "Guest":
filters["ip_address"] = frappe.local.request_ip
context.like = frappe.db.count("Comment", filters)
def set_read_time(self):
content = self.content or self.content_html or ""
if self.content_type == "Markdown":
content = markdown(self.content_md)
total_words = len(strip_html_tags(content).split())
self.read_time = ceil(total_words / 250)
def get_list_context(context=None):
list_context = frappe._dict(
get_list=get_blog_list,
no_breadcrumbs=True,
hide_filters=True,
# show_search = True,
title=_("Blog"),
)
blog_settings = frappe.get_doc("Blog Settings").as_dict(no_default_fields=True)
list_context.update(blog_settings)
category_name = frappe.utils.escape_html(
frappe.local.form_dict.blog_category or frappe.local.form_dict.category
)
if category_name:
category = frappe.get_doc("Blog Category", category_name)
list_context.blog_introduction = category.description or _("Posts filed under {0}").format(
category.title
)
list_context.blog_title = category.title
list_context.preview_image = category.preview_image
elif frappe.local.form_dict.blogger:
blogger = frappe.db.get_value("Blogger", {"name": frappe.local.form_dict.blogger}, "full_name")
list_context.sub_title = _("Posts by {0}").format(blogger)
list_context.title = blogger
elif frappe.local.form_dict.txt:
list_context.sub_title = _('Filtered by "{0}"').format(sanitize_html(frappe.local.form_dict.txt))
if list_context.sub_title:
list_context.parents = [{"name": _("Home"), "route": "/"}, {"name": "Blog", "route": "/blog"}]
else:
list_context.parents = [{"name": _("Home"), "route": "/"}]
if blog_settings.browse_by_category:
list_context.blog_categories = get_blog_categories()
list_context.metatags = {
"name": list_context.blog_title,
"title": list_context.blog_title,
"description": list_context.blog_introduction,
"image": list_context.preview_image,
}
return list_context
def get_blog_categories():
from pypika import Order
from pypika.terms import ExistsCriterion
post, category = frappe.qb.DocType("Blog Post"), frappe.qb.DocType("Blog Category")
return (
frappe.qb.from_(category)
.select(category.name, category.route, category.title)
.where(
(category.published == 1)
& ExistsCriterion(
frappe.qb.from_(post)
.select("name")
.where((post.published == 1) & (post.blog_category == category.name))
)
)
.orderby(category.title, order=Order.asc)
.run(as_dict=1)
)
def clear_blog_cache():
for blog in frappe.db.get_list("Blog Post", fields=["route"], pluck="route", filters={"published": True}):
clear_cache(blog)
clear_cache("writers")
def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by=None):
conditions = []
if filters and filters.get("blog_category"):
category = filters.get("blog_category")
else:
category = frappe.utils.escape_html(
frappe.local.form_dict.blog_category or frappe.local.form_dict.category
)
if filters and filters.get("blogger"):
conditions.append("t1.blogger={}".format(frappe.db.escape(filters.get("blogger"))))
if category:
conditions.append("t1.blog_category={}".format(frappe.db.escape(category)))
if txt:
conditions.append(
"(t1.content like {0} or t1.title like {0})".format(frappe.db.escape("%" + txt + "%"))
)
if conditions:
frappe.local.no_cache = 1
query = """\
select
t1.title, t1.name, t1.blog_category, t1.route, t1.published_on, t1.read_time,
t1.published_on as creation,
t1.read_time as read_time,
t1.featured as featured,
t1.meta_image as cover_image,
t1.content as content,
t1.content_type as content_type,
t1.content_html as content_html,
t1.content_md as content_md,
ifnull(t1.blog_intro, t1.content) as intro,
t2.full_name, t2.avatar, t1.blogger,
(select count(name) from `tabComment`
where
comment_type='Comment'
and reference_doctype='Blog Post'
and reference_name=t1.name) as comments
from `tabBlog Post` t1, `tabBlogger` t2
where t1.published = 1
and t1.blogger = t2.name
{condition}
order by featured desc, published_on desc, name asc
limit {page_len} OFFSET {start}""".format(
start=limit_start,
page_len=limit_page_length,
condition=(" and " + " and ".join(conditions)) if conditions else "",
)
posts = frappe.db.sql(query, as_dict=1)
for post in posts:
post.content = get_html_content_based_on_type(post, "content", post.content_type)
if not post.cover_image:
post.cover_image = find_first_image(post.content)
post.published = global_date_format(post.creation)
post.content = strip_html_tags(post.content)
if not post.comments:
post.comment_text = _("No comments yet")
elif post.comments == 1:
post.comment_text = _("1 comment")
else:
post.comment_text = _("{0} comments").format(str(post.comments))
post.avatar = post.avatar or ""
post.category = frappe.db.get_value(
"Blog Category", post.blog_category, ["name", "route", "title"], as_dict=True
)
if (
post.avatar
and ("http:" not in post.avatar and "https:" not in post.avatar)
and not post.avatar.startswith("/")
):
post.avatar = "/" + post.avatar
return posts

View file

@ -1,10 +0,0 @@
frappe.listview_settings["Blog Post"] = {
add_fields: ["title", "published", "blogger", "blog_category"],
get_indicator: function (doc) {
if (doc.published) {
return [__("Published"), "green", "published,=,1"];
} else {
return [__("Not Published"), "gray", "published,=,0"];
}
},
};

View file

@ -1,91 +0,0 @@
{% extends "templates/web.html" %}
{% block meta_block %}
{% include "templates/includes/meta_block.html" %}
{% endblock %}
{% block page_content %}
<div class="blog-container">
<article class="blog-content" itemscope itemtype="http://schema.org/BlogPosting">
<!-- begin blog content -->
<div class="blog-header">
<div>
<a class="mr-2" href="/blog">{{ _('Blog') }}</a>
<span class="text-muted">/</span>
<a class="ml-2" href="/{{ category.route }}">{{ category.title }}</a>
</div>
<h1 itemprop="headline" class="blog-title">{{ title }}</h1>
<p class="blog-intro">
{{ blog_intro }}
</p>
<div class="text-muted">
<time datetime="{{ published_on }}">{{ frappe.format_date(published_on) }}</time>
{%- if read_time -%}
&nbsp;&middot;
<span>{{ read_time }} {{ _('min read') }} </span>
{%- endif -%}
</div>
</div>
<hr class="my-5">
<div itemprop="articleBody" class="from-markdown">
{{ content }}
</div>
<!-- end blog content -->
</article>
{%- if enable_cta -%}
{{ web_block(
"Section With Small CTA",
values=cta,
add_container=0,
add_top_padding=0,
add_bottom_padding=0,
css_class="my-5"
) }}
{%- endif -%}
<div class="blog-footer">
<div class="blog-feedback">
{% if not disable_likes %}
{% include 'templates/includes/likes/likes.html' %}
{% endif %}
</div>
{% if social_links %}
<div>
{% for link in social_links %}
<a href="{{ link.link }}" class="text-muted ml-2 fa fa-{{ link.icon }}" target="_blank"></a>
{% endfor %}
</div>
{% endif %}
<div>
{{ _('Published on') }} <time datetime="{{ published_on }}">{{ frappe.format_date(published_on) }}</time>
</div>
</div>
{% if blogger_info %}
<hr class="mt-2 mb-5">
{% include "templates/includes/blog/blogger.html" %}
{% endif %}
{% if not disable_comments %}
<div class="blog-comments">
{% include 'templates/includes/comments/comments.html' %}
</div>
{% endif %}
</div>
<script>
frappe.ready(() => {
frappe.set_search_path("/blog");
// scroll to comment or like section if url contain hash
if (window.location.hash) {
var hash = window.location.hash;
if ($(hash).length) {
$('html, body').animate({
scrollTop: $(hash).offset().top - 100
}, 900, 'swing');
}
}
})
</script>
{% endblock %}

View file

@ -1,91 +0,0 @@
{% extends "templates/web.html" %}
{% block title %}{{ blog_title or _("Blog") }}{% endblock %}
{% block hero %}{% endblock %}
{% block page_content %}
<div class="row py-8">
<div class="col-md-8">
<div class="hero">
<div class="hero-content">
<h1>{{ blog_title or _('Blog') }}</h1>
<p>{{ blog_introduction or '' }}</p>
</div>
</div>
{%- if browse_by_category -%}
<div style="max-width: 20rem">
<label for="category-select" class="sr-only">{{ _("Browse by category") }}</label>
<select id="category-select" class="custom-select" onchange="window.location.pathname = this.value">
<option value="" {{ not frappe.form_dict.category and "selected" or "" }} disabled>
{{ _("Browse by category") }}
</option>
{%- if frappe.form_dict.category -%}
<option value="blog">{{ _("Show all blogs") }}</option>
{%- endif -%}
{%- for category in blog_categories -%}
<option value="{{ category.route }}" {{ frappe.form_dict.category == category.name and "selected" or "" }}>
{{ _(category.title) }}
</option>
{%- endfor -%}
</select>
</div>
{%- endif -%}
</div>
</div>
<div class="blog-list-content">
<div data-doctype="{{ doctype }}" data-txt="{{ (txt or '') | e }}">
{% if not result -%}
<div class="text-muted" style="min-height: 300px;">
{{ no_result_message or _("Nothing to show") }}
</div>
{% else %}
<div id="blog-list" class="blog-list result row">
{% for item in result %}
{{ item }}
{% endfor %}
</div>
{% endif %}
<button class="btn btn-light btn-more btn {% if not show_more -%} hidden {%- endif %}">{{ _("Load More") }}</button>
</div>
</div>
{% endblock %}
{% block script %}
<script>
frappe.ready(() => {
let result_wrapper = $(".blog-list.result");
let next_start = {{ next_start or 0 }};
$(".blog-list-content .btn-more").on("click", function() {
let $btn = $(this);
let args = $.extend(frappe.utils.get_query_params(), {
doctype: "Blog Post",
category: {{ frappe.form_dict.category|tojson or "''"}},
limit_start: next_start,
pathname: location.pathname,
});
$btn.prop("disabled", true);
frappe.call('frappe.www.list.get', args)
.then(r => {
var data = r.message;
next_start = data.next_start;
$.each(data.result, function(i, d) {
$(d).appendTo(result_wrapper);
});
toggle_more(data.show_more);
})
.always(() => {
$btn.prop("disabled", false);
});
});
function toggle_more(show) {
if (!show) {
$(".btn-more").addClass("hide");
}
}
});
</script>
{% endblock %}

View file

@ -1,43 +0,0 @@
{% from "frappe/templates/includes/avatar_macro.html" import avatar %}
{%- set post = doc -%}
<div class="blog-card col-sm-12 {{ 'col-md-8' if post.featured else 'col-md-4' }}">
<div class="card h-100">
<div class="card-img-top">
{% if post.cover_image %}
<img src="{{ post.cover_image }}" alt="{{post.title}} - Cover Image">
{% else %}
<div class="default-cover">
<span>{{ post.title }}</span>
</div>
{% endif %}
</div>
<div class="card-body">
<div>
<div class="text-muted small text-uppercase">
{%- if post.featured -%}
<span class="text-body">{{ _('Featured') }} &middot; </span>
{%- endif -%}
<span>{{ post.category.title }}</span>
</div>
{%- if post.featured -%}
<h5 class="mt-1"><span class="text-dark">{{ post.title }}</span></h5>
{%- else -%}
<h5 class="mt-1"><span class="text-dark">{{ post.title }}</span></h5>
{%- endif -%}
<p class="post-description text-muted">{{ post.intro }}</p>
</div>
<div class="blog-card-footer">
{{ avatar(full_name=post.full_name, image=post.avatar, size='avatar-medium') }}
<div class="text-muted">
<a href="/blog?blogger={{ post.blogger }}">{{ post.full_name }}</a>
<div class="small">
{{ frappe.format_date(post.published_on) }}
{% if post.read_time %} &middot; {{ post.read_time }} {{ _('min read') }} {% endif %}
</div>
</div>
</div>
</div>
<a class="stretched-link" href="/{{ post.route }}"></a>
</div>
</div>

View file

@ -1,191 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import re
from bs4 import BeautifulSoup
import frappe
from frappe.custom.doctype.customize_form.customize_form import reset_customization
from frappe.tests import IntegrationTestCase
from frappe.utils import random_string, set_request
from frappe.website.doctype.blog_post.blog_post import get_blog_list
from frappe.website.serve import get_response
from frappe.website.utils import clear_website_cache
from frappe.website.website_generator import WebsiteGenerator
EXTRA_TEST_RECORD_DEPENDENCIES = ["Blog Post"]
class TestBlogPost(IntegrationTestCase):
def setUp(self):
reset_customization("Blog Post")
def tearDown(self):
if hasattr(frappe.local, "request"):
delattr(frappe.local, "request")
def test_generator_view(self):
pages = frappe.get_all(
"Blog Post", fields=["name", "route"], filters={"published": 1, "route": ("!=", "")}, limit=1
)
set_request(path=pages[0].route)
response = get_response()
self.assertTrue(response.status_code, 200)
html = response.get_data().decode()
self.assertTrue(
'<article class="blog-content" itemscope itemtype="http://schema.org/BlogPosting">' in html
)
def test_generator_not_found(self):
pages = frappe.get_all("Blog Post", fields=["name", "route"], filters={"published": 0}, limit=1)
route = f"test-route-{frappe.generate_hash(length=5)}"
frappe.db.set_value("Blog Post", pages[0].name, "route", route)
set_request(path=route)
response = get_response()
self.assertTrue(response.status_code, 404)
def test_category_link(self):
# Make a temporary Blog Post (and a Blog Category)
blog = make_test_blog("Test Category Link")
# Visit the blog post page
set_request(path=blog.route)
blog_page_response = get_response()
blog_page_html = frappe.safe_decode(blog_page_response.get_data())
# On blog post page find link to the category page
soup = BeautifulSoup(blog_page_html, "html.parser")
category_page_link = next(iter(soup.find_all("a", href=re.compile(blog.blog_category))))
category_page_url = category_page_link["href"]
# Visit the category page (by following the link found in above stage)
set_request(path=category_page_url)
category_page_response = get_response()
category_page_html = frappe.safe_decode(category_page_response.get_data())
# Category page should contain the blog post title
self.assertIn(blog.title, category_page_html)
# Cleanup
frappe.delete_doc("Blog Post", blog.name)
frappe.delete_doc("Blog Category", blog.blog_category)
def test_blog_pagination(self):
# Create some Blog Posts for a Blog Category
category_title, blogs, BLOG_COUNT = "List Category", [], 4
for _ in range(BLOG_COUNT):
blog = make_test_blog(category_title)
blogs.append(blog)
filters = frappe._dict({"blog_category": scrub(category_title)})
# Assert that get_blog_list returns results as expected
self.assertEqual(len(get_blog_list(None, None, filters, 0, 3)), 3)
self.assertEqual(len(get_blog_list(None, None, filters, 0, BLOG_COUNT)), BLOG_COUNT)
self.assertEqual(len(get_blog_list(None, None, filters, 0, 2)), 2)
self.assertEqual(len(get_blog_list(None, None, filters, 2, BLOG_COUNT)), 2)
# Cleanup Blog Post and linked Blog Category
for blog in blogs:
frappe.delete_doc(blog.doctype, blog.name)
frappe.delete_doc("Blog Category", blogs[0].blog_category)
def test_caching(self):
# to enable caching
frappe.flags.force_website_cache = True
print(frappe.session.user)
clear_website_cache()
# first response no-cache
pages = frappe.get_all(
"Blog Post",
fields=["name", "route"],
filters={"published": 1, "title": "_Test Blog Post"},
limit=1,
)
route = pages[0].route
set_request(path=route)
# response = get_response()
response = get_response()
# TODO: enable this assert
# self.assertIn(('X-From-Cache', 'False'), list(response.headers))
set_request(path=route)
response = get_response()
self.assertIn(("X-From-Cache", "True"), list(response.headers))
frappe.flags.force_website_cache = True
def test_spam_comments(self):
# Make a temporary Blog Post (and a Blog Category)
blog = make_test_blog("Test Spam Comment")
# Create a spam comment
frappe.get_doc(
doctype="Comment",
comment_type="Comment",
reference_doctype="Blog Post",
reference_name=blog.name,
comment_email='<a href="https://example.com/spam/">spam</a>',
comment_by='<a href="https://example.com/spam/">spam</a>',
published=1,
content='More spam content. <a href="https://example.com/spam/">spam</a> with link.',
).insert()
# Visit the blog post page
set_request(path=blog.route)
blog_page_response = get_response()
blog_page_html = frappe.safe_decode(blog_page_response.get_data())
self.assertNotIn('<a href="https://example.com/spam/">spam</a>', blog_page_html)
self.assertIn("More spam content. spam with link.", blog_page_html)
# Cleanup
frappe.delete_doc("Blog Post", blog.name)
frappe.delete_doc("Blog Category", blog.blog_category)
def test_like_dislike(self):
test_blog = make_test_blog()
frappe.db.delete("Comment", {"comment_type": "Like", "reference_doctype": "Blog Post"})
from frappe.templates.includes.likes.likes import like
liked = like("Blog Post", test_blog.name, True)
self.assertEqual(liked, True)
disliked = like("Blog Post", test_blog.name, False)
self.assertEqual(disliked, False)
frappe.db.delete("Comment", {"comment_type": "Like", "reference_doctype": "Blog Post"})
test_blog.delete()
def scrub(text):
return WebsiteGenerator.scrub(None, text)
def make_test_blog(category_title="Test Blog Category"):
category_name = scrub(category_title)
if not frappe.db.exists("Blog Category", category_name):
frappe.get_doc(doctype="Blog Category", title=category_title).insert()
if not frappe.db.exists("Blogger", "test-blogger"):
frappe.get_doc(doctype="Blogger", short_name="test-blogger", full_name="Test Blogger").insert()
return frappe.get_doc(
doctype="Blog Post",
blog_category=category_name,
blogger="test-blogger",
title=random_string(20),
route=random_string(20),
content=random_string(20),
published=1,
).insert()

View file

@ -1,38 +0,0 @@
[
{
"blog_category": "-test-blog-category",
"blog_intro": "Test Blog Intro",
"blogger": "_Test Blogger",
"content": "Test Blog Content",
"doctype": "Blog Post",
"title": "_Test Blog Post",
"published": 1
},
{
"blog_category": "-test-blog-category-1",
"blog_intro": "Test Blog Intro",
"blogger": "_Test Blogger",
"content": "Test Blog Content",
"doctype": "Blog Post",
"title": "_Test Blog Post 1",
"published": 1
},
{
"blog_category": "-test-blog-category-1",
"blog_intro": "Test Blog Intro",
"blogger": "_Test Blogger 1",
"content": "Test Blog Content",
"doctype": "Blog Post",
"title": "_Test Blog Post 2",
"published": 0
},
{
"blog_category": "-test-blog-category-1",
"blog_intro": "Test Blog Intro",
"blogger": "_Test Blogger 2",
"content": "Test Blog Content",
"doctype": "Blog Post",
"title": "_Test Blog Post 3",
"published": 0
}
]

View file

@ -1 +0,0 @@
Blog titles and introduction texts.

View file

@ -1,6 +0,0 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on("Blog Settings", {
refresh: function (frm) {},
});

View file

@ -1,161 +0,0 @@
{
"actions": [],
"creation": "2013-03-11 17:48:16",
"description": "Settings to control blog categories and interactions like comments and likes",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"blog_title",
"blog_introduction",
"preview_image",
"column_break",
"enable_social_sharing",
"allow_guest_to_comment",
"browse_by_category",
"show_cta_in_blog",
"cta_section",
"title",
"subtitle",
"column_break_11",
"cta_label",
"cta_url",
"section_break_12",
"like_limit",
"column_break_14",
"comment_limit"
],
"fields": [
{
"fieldname": "blog_title",
"fieldtype": "Data",
"label": "Blog Title"
},
{
"fieldname": "blog_introduction",
"fieldtype": "Small Text",
"label": "Blog Introduction"
},
{
"default": "0",
"fieldname": "enable_social_sharing",
"fieldtype": "Check",
"label": "Enable Social Sharing"
},
{
"collapsible": 1,
"fieldname": "column_break",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "show_cta_in_blog",
"fieldtype": "Check",
"label": "Show \"Call to Action\" in Blog"
},
{
"depends_on": "eval:doc.show_cta_in_blog",
"fieldname": "cta_section",
"fieldtype": "Section Break",
"label": "Call to Action"
},
{
"fieldname": "title",
"fieldtype": "Data",
"label": "Title",
"mandatory_depends_on": "eval:doc.show_cta_in_blog"
},
{
"fieldname": "subtitle",
"fieldtype": "Data",
"label": "Subtitle",
"mandatory_depends_on": "eval:doc.show_cta_in_blog"
},
{
"fieldname": "cta_label",
"fieldtype": "Data",
"label": "CTA Label",
"mandatory_depends_on": "eval:doc.show_cta_in_blog"
},
{
"fieldname": "cta_url",
"fieldtype": "Data",
"label": "CTA URL",
"mandatory_depends_on": "eval:doc.show_cta_in_blog"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_12",
"fieldtype": "Section Break",
"label": "Rate Limits"
},
{
"default": "5",
"description": "Comment limit per hour",
"fieldname": "comment_limit",
"fieldtype": "Int",
"label": "Comment limit"
},
{
"fieldname": "column_break_14",
"fieldtype": "Column Break"
},
{
"default": "1",
"fieldname": "allow_guest_to_comment",
"fieldtype": "Check",
"label": "Allow Guest to comment"
},
{
"default": "0",
"fieldname": "browse_by_category",
"fieldtype": "Check",
"label": "Browse by category"
},
{
"default": "5",
"description": "Like limit per hour",
"fieldname": "like_limit",
"fieldtype": "Int",
"label": "Like limit"
},
{
"fieldname": "preview_image",
"fieldtype": "Attach Image",
"label": "Preview Image"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"links": [],
"modified": "2024-03-23 16:01:29.318488",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Website Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Blogger",
"share": 1
}
],
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View file

@ -1,46 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
# License: MIT. See LICENSE
import frappe
from frappe.model.document import Document
class BlogSettings(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
allow_guest_to_comment: DF.Check
blog_introduction: DF.SmallText | None
blog_title: DF.Data | None
browse_by_category: DF.Check
comment_limit: DF.Int
cta_label: DF.Data | None
cta_url: DF.Data | None
enable_social_sharing: DF.Check
like_limit: DF.Int
preview_image: DF.AttachImage | None
show_cta_in_blog: DF.Check
subtitle: DF.Data | None
title: DF.Data | None
# end: auto-generated types
def on_update(self):
from frappe.website.utils import clear_cache
clear_cache("blog")
clear_cache("writers")
def get_like_limit():
return frappe.get_single_value("Blog Settings", "like_limit") or 5
def get_comment_limit():
return frappe.get_single_value("Blog Settings", "comment_limit") or 5

View file

@ -1,8 +0,0 @@
# Copyright (c) 2020, Frappe Technologies and Contributors
# License: MIT. See LICENSE
# import frappe
from frappe.tests import IntegrationTestCase
class TestBlogSettings(IntegrationTestCase):
pass

View file

@ -1 +0,0 @@
User of blog writer in "Blog" section.

View file

@ -1,6 +0,0 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on("Blogger", {
refresh: function (frm) {},
});

View file

@ -1,102 +0,0 @@
{
"actions": [],
"allow_import": 1,
"autoname": "field:short_name",
"creation": "2013-03-25 16:00:51",
"description": "User ID of a Blogger",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"disabled",
"short_name",
"full_name",
"user",
"bio",
"avatar"
],
"fields": [
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"description": "Will be used in url (usually first name).",
"fieldname": "short_name",
"fieldtype": "Data",
"label": "Short Name",
"reqd": 1,
"unique": 1
},
{
"fieldname": "full_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Full Name",
"reqd": 1
},
{
"fieldname": "user",
"fieldtype": "Link",
"label": "User",
"options": "User"
},
{
"fieldname": "bio",
"fieldtype": "Small Text",
"label": "Bio"
},
{
"fieldname": "avatar",
"fieldtype": "Attach Image",
"label": "Avatar"
}
],
"icon": "fa fa-user",
"idx": 1,
"image_field": "avatar",
"links": [
{
"link_doctype": "Blog Post",
"link_fieldname": "blogger"
}
],
"make_attachments_public": 1,
"max_attachments": 1,
"modified": "2024-03-23 16:01:29.432477",
"modified_by": "Administrator",
"module": "Website",
"name": "Blogger",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"import": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Website Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Blogger",
"share": 1,
"write": 1
}
],
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "full_name",
"track_changes": 1
}

View file

@ -1,52 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
# License: MIT. See LICENSE
import frappe
from frappe import _
from frappe.model.document import Document
class Blogger(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
avatar: DF.AttachImage | None
bio: DF.SmallText | None
disabled: DF.Check
full_name: DF.Data
short_name: DF.Data
user: DF.Link | None
# end: auto-generated types
def validate(self):
if self.user and not frappe.db.exists("User", self.user):
# for data import
frappe.get_doc(
{"doctype": "User", "email": self.user, "first_name": self.user.split("@", 1)[0]}
).insert()
def on_update(self):
"if user is set, then update all older blogs"
from frappe.website.doctype.blog_post.blog_post import clear_blog_cache
clear_blog_cache()
if self.user:
for blog in frappe.db.sql_list(
"""select name from `tabBlog Post` where owner=%s
and ifnull(blogger,'')=''""",
self.user,
):
b = frappe.get_doc("Blog Post", blog)
b.blogger = self.name
b.save()
frappe.permissions.add_user_permission("Blogger", self.name, self.user)

View file

@ -1,4 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe

View file

@ -1,17 +0,0 @@
[
{
"doctype": "Blogger",
"full_name": "_Test Blogger",
"short_name": "_Test Blogger"
},
{
"doctype": "Blogger",
"full_name": "_Test Blogger 1",
"short_name": "_Test Blogger 1"
},
{
"doctype": "Blogger",
"full_name": "_Test Blogger 2",
"short_name": "_Test Blogger 2"
}
]

View file

@ -5,21 +5,21 @@ from frappe.tests import IntegrationTestCase
from frappe.utils import set_request from frappe.utils import set_request
from frappe.website.serve import get_response from frappe.website.serve import get_response
EXTRA_TEST_RECORD_DEPENDENCIES = ["Blog Post"] EXTRA_TEST_RECORD_DEPENDENCIES = ["Web Page"]
class TestWebsiteRouteMeta(IntegrationTestCase): class TestWebsiteRouteMeta(IntegrationTestCase):
def test_meta_tag_generation(self): def test_meta_tag_generation(self):
blogs = frappe.get_all( blogs = frappe.get_all(
"Blog Post", fields=["name", "route"], filters={"published": 1, "route": ("!=", "")}, limit=1 "Web Page", fields=["name", "route"], filters={"published": 1, "route": ("!=", "")}, limit=1
) )
blog = blogs[0] blog = blogs[0]
# create meta tags for this route # create meta tags for this route
doc = frappe.new_doc("Website Route Meta") doc = frappe.new_doc("Website Route Meta")
doc.append("meta_tags", {"key": "type", "value": "blog_post"}) doc.append("meta_tags", {"key": "type", "value": "web_page"})
doc.append("meta_tags", {"key": "og:title", "value": "My Blog"}) doc.append("meta_tags", {"key": "og:title", "value": "My Web Page"})
doc.name = blog.route doc.name = blog.route
doc.insert() doc.insert()
@ -31,8 +31,8 @@ class TestWebsiteRouteMeta(IntegrationTestCase):
html = self.normalize_html(response.get_data().decode()) html = self.normalize_html(response.get_data().decode())
self.assertIn(self.normalize_html("""<meta name="type" content="blog_post">"""), html) self.assertIn(self.normalize_html("""<meta name="type" content="web_page">"""), html)
self.assertIn(self.normalize_html("""<meta property="og:title" content="My Blog">"""), html) self.assertIn(self.normalize_html("""<meta property="og:title" content="My Web Page">"""), html)
def tearDown(self): def tearDown(self):
frappe.db.rollback() frappe.db.rollback()

View file

@ -1,6 +1,6 @@
{ {
"charts": [], "charts": [],
"content": "[{\"id\":\"yq1JKyNTFg\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Web Page\",\"col\":3}},{\"id\":\"5GuZo0uP_K\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Web Form\",\"col\":3}},{\"id\":\"292vrD2W3o\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Blog Post\",\"col\":3}},{\"id\":\"xAkA6ItB7O\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"k8RquSngSk\",\"type\":\"card\",\"data\":{\"card_name\":\"Blog\",\"col\":4}},{\"id\":\"qEHBG-BEBI\",\"type\":\"card\",\"data\":{\"card_name\":\"Web Site\",\"col\":4}},{\"id\":\"oUox7d-8lQ\",\"type\":\"card\",\"data\":{\"card_name\":\"Knowledge Base\",\"col\":4}},{\"id\":\"96xAe0QVaV\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"ZvrzvEoYtc\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"FVMFcgsiyK\",\"type\":\"card\",\"data\":{\"card_name\":\"Tracking\",\"col\":4}}]", "content": "[{\"id\":\"yq1JKyNTFg\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Web Page\",\"col\":3}},{\"id\":\"5GuZo0uP_K\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Web Form\",\"col\":3}},{\"id\":\"xAkA6ItB7O\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"qEHBG-BEBI\",\"type\":\"card\",\"data\":{\"card_name\":\"Web Site\",\"col\":4}},{\"id\":\"oUox7d-8lQ\",\"type\":\"card\",\"data\":{\"card_name\":\"Knowledge Base\",\"col\":4}},{\"id\":\"96xAe0QVaV\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"ZvrzvEoYtc\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"FVMFcgsiyK\",\"type\":\"card\",\"data\":{\"card_name\":\"Tracking\",\"col\":4}}]",
"creation": "2020-03-02 14:13:51.089373", "creation": "2020-03-02 14:13:51.089373",
"custom_blocks": [], "custom_blocks": [],
"docstatus": 0, "docstatus": 0,
@ -67,48 +67,6 @@
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{
"hidden": 0,
"icon": "",
"is_query_report": 0,
"label": "Blog",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Blog Post",
"link_count": 0,
"link_to": "Blog Post",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Blogger",
"link_count": 0,
"link_to": "Blogger",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Blog Category",
"link_count": 0,
"link_to": "Blog Category",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{ {
"hidden": 0, "hidden": 0,
"icon": "project", "icon": "project",
@ -279,14 +237,6 @@
"roles": [], "roles": [],
"sequence_id": 14.0, "sequence_id": 14.0,
"shortcuts": [ "shortcuts": [
{
"color": "Green",
"format": "{} Published",
"label": "Blog Post",
"link_to": "Blog Post",
"stats_filter": "{\"published\":\"1\"}",
"type": "DocType"
},
{ {
"color": "Green", "color": "Green",
"format": "{} Published", "format": "{} Published",

View file

@ -1,47 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
from urllib.parse import quote, urljoin
import frappe
from frappe.utils import cstr, escape_html, get_request_site_address, now
no_cache = 1
base_template_path = "www/rss.xml"
def get_context(context):
"""generate rss feed"""
host = get_request_site_address()
blog_list = frappe.get_all(
"Blog Post",
fields=["name", "published_on", "modified", "title", "blog_intro", "route"],
filters={"published": 1},
order_by="published_on desc",
limit=20,
)
for blog in blog_list:
blog.link = urljoin(host, blog.route)
blog.blog_intro = escape_html(blog.blog_intro or "")
blog.title = escape_html(blog.title or "")
if blog_list:
modified = max(blog["modified"] for blog in blog_list)
else:
modified = now()
blog_settings = frappe.get_doc("Blog Settings", "Blog Settings")
context = {
"title": blog_settings.blog_title or "Blog",
"description": blog_settings.blog_introduction or "",
"modified": modified,
"items": blog_list,
"link": host + "/blog",
}
# print context
return context

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>{{ title }}</title>
<description>{{ description }}</description>
<link>{{ link }}</link>
<lastBuildDate>{{ modified }}</lastBuildDate>
<pubDate>{{ modified }}</pubDate>
<ttl>1800</ttl>
{% for i in items %}<item>
<title>{{ i.title }}</title>
<description>{{ i.blog_intro }}</description>
<link>{{ i.link }}</link>
<guid>{{ i.name }}</guid>
<pubDate>{{ i.published_on }}</pubDate>
</item>{% endfor %}
</channel>
</rss>