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.visit("/app/todo"); // Make sure ToDo filters are cleared.
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.visit("/app/website"); // Go to some other page.
cy.visit("/app/build"); // Go to some other page.
});
beforeEach(() => {
@ -53,19 +53,19 @@ context("Awesome Bar", () => {
});
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.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.location("search").should("be.empty");
});
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.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", () => {

View file

@ -43,7 +43,7 @@ context("Sidebar", () => {
.window()
.its("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) => {
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", () => {

View file

@ -8,7 +8,7 @@ context("Table MultiSelect", () => {
it("select value from multiselect dropdown", () => {
cy.new_form("Assignment Rule");
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.fill_field("assign_condition", 'status=="Open"', "Code");
cy.get('input[data-fieldname="users"]').focus().as("input");

View file

@ -5,11 +5,16 @@ import json
import frappe
from frappe.templates.includes.comments.comments import add_comment
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.website.doctype.blog_post.test_blog_post import make_test_blog
EXTRA_TEST_RECORD_DEPENDENCIES = ["Web Page"]
class TestComment(IntegrationTestCase):
def setUp(self):
setup_for_tests()
def test_comment_creation(self):
test_doc = frappe.get_doc(doctype="ToDo", description="test")
test_doc.insert()
@ -42,16 +47,16 @@ class TestComment(IntegrationTestCase):
# test via blog
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 = {
"comment": "Good comment with 10 chars",
"comment_email": "test@test.com",
"comment_by": "Good Tester",
"reference_doctype": test_blog.doctype,
"reference_name": test_blog.name,
"route": test_blog.route,
"route": f"blog/{test_blog.doctype}/{test_blog.name}",
}
add_comment(**add_comment_args)
@ -64,7 +69,7 @@ class TestComment(IntegrationTestCase):
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(**add_comment_args)
@ -81,7 +86,7 @@ class TestComment(IntegrationTestCase):
)
# 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(**add_comment_args)
@ -96,26 +101,10 @@ class TestComment(IntegrationTestCase):
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):
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"):
self.assertRaises(
frappe.ValidationError,
@ -123,7 +112,7 @@ class TestComment(IntegrationTestCase):
comment="Good comment with 10 chars",
comment_email=some_system_user,
comment_by="Good Tester",
reference_doctype="Blog Post",
reference_doctype="Web Page",
reference_name=test_blog.name,
route=test_blog.route,
)

View file

@ -845,11 +845,6 @@
"link_doctype": "Contact",
"link_fieldname": "user"
},
{
"group": "Profile",
"link_doctype": "Blogger",
"link_fieldname": "user"
},
{
"group": "Logs",
"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.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):
@ -23,6 +23,7 @@ class TestUserPermission(IntegrationTestCase):
frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabPerson`")
frappe.delete_doc_if_exists("DocType", "Doc A")
frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabDoc A`")
setup_for_tests()
def test_default_user_permission_validation(self):
user = create_user("test_default_permission@example.com")
@ -39,27 +40,27 @@ class TestUserPermission(IntegrationTestCase):
add_user_permissions(param)
# create a duplicate entry with default
perm_user = create_user("test_default_corectness2@example.com")
test_blog = make_test_blog()
param = get_params(perm_user, "Blog Post", test_blog.name, is_default=1, hide_descendants=1)
test_blog = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
param = get_params(perm_user, "Test Blog Post", test_blog.name, is_default=1, hide_descendants=1)
add_user_permissions(param)
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):
frappe.set_user("Administrator")
user = create_user("test_user_perm1@example.com", "Website Manager")
for category in ["general", "public"]:
if not frappe.db.exists("Blog Category", category):
frappe.get_doc({"doctype": "Blog Category", "title": category}).insert()
if not frappe.db.exists("Test Blog Category", category):
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)
param = get_params(user, "Blog Category", "public")
param = get_params(user, "Test Blog Category", "public")
add_user_permissions(param)
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")
frappe.set_user("Administrator")

View file

@ -1,6 +1,5 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
"""Use blog post test to test user permissions logic"""
import json
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.tests import IntegrationTestCase
EXTRA_TEST_RECORD_DEPENDENCIES = ["User"]
EXTRA_TEST_RECORD_DEPENDENCIES = ["User", "Web Page"]
class TestToDo(IntegrationTestCase):
@ -93,8 +93,8 @@ class TestToDo(IntegrationTestCase):
frappe.set_user("Administrator")
test_user.add_roles("Blogger")
add_permission("ToDo", "Blogger")
test_user.add_roles("Website Manager")
add_permission("ToDo", "Website Manager")
frappe.set_user("test4@example.com")
@ -103,7 +103,7 @@ class TestToDo(IntegrationTestCase):
self.assertFalse(todo1.has_permission("write"))
frappe.set_user("Administrator")
test_user.remove_roles("Blogger")
test_user.remove_roles("Website Manager")
reset_perms("ToDo")
clear_permissions_cache("ToDo")
frappe.db.rollback()

View file

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

View file

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

View file

@ -613,7 +613,7 @@ class Test_OpenLDAP(LDAP_TestCase, TestCase):
"ldap_group": "Administrators",
"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"},
]
LDAP_USERNAME_FIELD = "uid"
@ -637,7 +637,7 @@ class Test_ActiveDirectory(LDAP_TestCase, TestCase):
"ldap_group": "Domain Administrators",
"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",
"ldap_group": "Enterprise Administrators",

View file

@ -6,6 +6,7 @@ def execute():
"Social Module/ Energy Points System": ("eps", "system"),
"Offsite Backup Integrations (Google Drive, S3, Dropbox)": ("offsite_backups", "intergration"),
"Newsletter": ("newsletter", "functionality"),
"Blogs": ("blogs", "functionality"),
}
for module, (app, system_type) in module_app_map.items():
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 "web_form";
@import "page_builder";
@import "blog";
@import "markdown";
@import "sidebar";
@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.rate_limiter import rate_limit
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
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)
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)
@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):
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
if reference_doctype == "Blog Post" and not frappe.db.get_single_value(
"Blog Settings", "allow_guest_to_comment"
):
return
if not guest_allowed:
frappe.throw(_("Please login to post a comment."))
if frappe.db.exists("User", comment_email):
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:
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)
template = frappe.get_template("templates/includes/comments/comment.html")
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):
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(
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.query_builder import Column
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.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
@ -41,15 +42,16 @@ def setup_test_user(set_user=False):
@contextmanager
def setup_patched_blog_post():
add_child_table_to_blog_post()
make_property_setter("Blog Post", "published", "permlevel", 1, "Int")
reset("Blog Post")
add("Blog Post", "Website Manager", 1)
update("Blog Post", "Website Manager", 1, "write", 1)
make_property_setter("Test Blog Post", "published", "permlevel", 1, "Int")
reset("Test Blog Post")
add("Test Blog Post", "Website Manager", 1)
update("Test Blog Post", "Website Manager", 1, "write", 1)
yield
class TestDBQuery(IntegrationTestCase):
def setUp(self):
setup_for_tests()
frappe.set_user("Administrator")
def test_basic(self):
@ -192,14 +194,14 @@ class TestDBQuery(IntegrationTestCase):
todo.delete()
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.add_roles("Blogger")
frappe.set_user("test2@example.com")
# 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
# get as filters
@ -207,20 +209,20 @@ class TestDBQuery(IntegrationTestCase):
# get as conditions
self.assertEqual(build_match_conditions(as_condition=True), "")
add_user_permission("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", "test2@example.com", True)
add_user_permission("Test Blog Post", "_Test Blog Post 1", "test2@example.com", True)
# After applying user permission
# get as filters
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)
)
# get as conditions
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:
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)
@ -848,7 +850,7 @@ class TestDBQuery(IntegrationTestCase):
def test_permlevel_fields(self):
with setup_patched_blog_post(), setup_test_user(set_user=True):
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
fields=["name", "published"],
limit=1,
@ -858,7 +860,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1)
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
fields=["name", "`published`"],
limit=1,
@ -868,9 +870,9 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1)
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
fields=["name", "`tabBlog Post`.`published`"],
fields=["name", "`tabTest Blog Post`.`published`"],
limit=1,
)
self.assertFalse("published" in data[0])
@ -878,7 +880,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1)
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
fields=["name", "`tabTest Child`.`test_field`"],
limit=1,
@ -888,7 +890,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1)
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
fields=["name", "MAX(`published`)"],
limit=1,
@ -897,7 +899,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1)
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
fields=["name", "LAST(published)"],
limit=1,
@ -906,7 +908,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 1)
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
fields=["name", "MAX(`modified`)"],
limit=1,
@ -916,7 +918,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 2)
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
fields=["name", "now() abhi"],
limit=1,
@ -925,7 +927,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 2)
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
fields=["name", "'LABEL'"],
limit=1,
@ -935,7 +937,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 2)
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
fields=["name", "COUNT(*) as count"],
limit=1,
@ -946,7 +948,7 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 2)
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
fields=["name", "COUNT(*) count"],
limit=1,
@ -957,17 +959,18 @@ class TestDBQuery(IntegrationTestCase):
self.assertEqual(len(data[0]), 2)
data = frappe.get_list(
"Blog Post",
"Test Blog Post",
fields=[
"name",
"blogger.full_name as blogger_full_name",
"blog_category.description",
"blog_category.title",
],
limit=1,
)
print(data[0])
self.assertTrue("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):
from frappe.core.doctype.doctype.test_doctype import new_doctype
@ -1296,10 +1299,10 @@ class TestReportView(IntegrationTestCase):
user.remove_roles(*user_roles)
user.add_roles("Blogger")
make_property_setter("Blog Post", "published", "permlevel", 1, "Int")
reset("Blog Post")
add("Blog Post", "Website Manager", 1)
update("Blog Post", "Website Manager", 1, "write", 1)
make_property_setter("Test Blog Post", "published", "permlevel", 1, "Int")
reset("Test Blog Post")
add("Test Blog Post", "Website Manager", 1)
update("Test Blog Post", "Website Manager", 1, "write", 1)
frappe.set_user(user.name)
@ -1308,7 +1311,7 @@ class TestReportView(IntegrationTestCase):
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"doctype": "Test Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"],
}
)
@ -1318,7 +1321,7 @@ class TestReportView(IntegrationTestCase):
self.assertListEqual(response["keys"], ["title"])
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"doctype": "Test Blog Post",
"fields": ["*"],
}
)
@ -1335,7 +1338,7 @@ class TestReportView(IntegrationTestCase):
# Admin should be able to see access all fields
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"doctype": "Test Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"],
}
)
@ -1377,7 +1380,7 @@ class TestReportView(IntegrationTestCase):
frappe.local.request.method = "POST"
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"doctype": "Test Blog Post",
"fields": ["published", "title", "`tabTest Child`.`test_field`"],
}
)
@ -1387,7 +1390,7 @@ class TestReportView(IntegrationTestCase):
self.assertListEqual(response["keys"], ["title"])
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"doctype": "Test Blog Post",
"fields": ["*"],
}
)
@ -1396,7 +1399,7 @@ class TestReportView(IntegrationTestCase):
self.assertNotIn("published", response["keys"])
# 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):
# Admin should be able to see access all fields
@ -1405,7 +1408,7 @@ class TestReportView(IntegrationTestCase):
frappe.local.request.method = "POST"
frappe.local.form_dict = frappe._dict(
{
"doctype": "Blog Post",
"doctype": "Test Blog Post",
"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)
clear_custom_fields("Blog Post")
add_custom_field("Blog Post", "child_table", "Table", child_table.name)
clear_custom_fields("Test Blog Post")
add_custom_field("Test Blog Post", "child_table", "Table", child_table.name)
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)
def test_user_permission_defaults(self):
# 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")
set_global_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.desk.form.load import get_docinfo, getdoc, getdoctype
from frappe.tests import IntegrationTestCase
from frappe.tests.test_helpers import setup_for_tests
from frappe.utils.file_manager import save_file
EXTRA_TEST_RECORD_DEPENDENCIES = ["Blog Category", "Blogger"]
class TestFormLoad(IntegrationTestCase):
def test_load(self):
@ -23,10 +22,11 @@ class TestFormLoad(IntegrationTestCase):
self.assertTrue(meta.get("__calendar_js"))
def test_fieldlevel_permissions_in_load(self):
setup_for_tests()
blog = frappe.get_doc(
{
"doctype": "Blog Post",
"blog_category": "-test-blog-category-1",
"doctype": "Test Blog Post",
"blog_category": "_Test Blog Category 1",
"blog_intro": "Test Blog Intro",
"blogger": "_Test Blogger 1",
"content": "Test Blog Content",
@ -43,8 +43,8 @@ class TestFormLoad(IntegrationTestCase):
user.remove_roles(*user_roles)
user.add_roles("Blogger")
blog_post_property_setter = make_property_setter("Blog Post", "published", "permlevel", 1, "Int")
reset("Blog Post")
blog_post_property_setter = make_property_setter("Test Blog Post", "published", "permlevel", 1, "Int")
reset("Test Blog Post")
# test field level permission before role level permissions are defined
frappe.set_user(user.name)
@ -63,8 +63,8 @@ class TestFormLoad(IntegrationTestCase):
# test field level permission after role level permissions are defined
frappe.set_user("Administrator")
add("Blog Post", "Website Manager", 1)
update("Blog Post", "Website Manager", 1, "write", 1)
add("Test Blog Post", "Website Manager", 1)
update("Test Blog Post", "Website Manager", 1, "write", 1)
frappe.set_user(user.name)
blog_doc = get_blog(blog.name)
@ -86,7 +86,7 @@ class TestFormLoad(IntegrationTestCase):
user.add_roles("Website Manager")
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.save()
@ -196,5 +196,5 @@ class TestFormLoad(IntegrationTestCase):
def get_blog(blog_name):
frappe.response.docs = []
getdoc("Blog Post", blog_name)
getdoc("Test Blog Post", blog_name)
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.defaults
import frappe.model.meta
import frappe.permissions
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.page.permission_manager.permission_manager import add, remove, reset, update
from frappe.desk.form.load import getdoc
from frappe.installer import _delete_doctypes
from frappe.permissions import (
ALL_USER_ROLE,
AUTOMATIC_ROLES,
@ -23,17 +25,19 @@ from frappe.permissions import (
update_permission_property,
)
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.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):
@classmethod
def setUpClass(cls):
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.add_roles("Website Manager")
user.add_roles("System Manager")
@ -48,10 +52,10 @@ class TestPermissions(IntegrationTestCase):
user.add_roles("Website Manager")
def setUp(self):
frappe.clear_cache(doctype="Blog Post")
frappe.clear_cache(doctype="Test Blog Post")
reset("Blogger")
reset("Blog Post")
reset("Test Blogger")
reset("Test Blog Post")
frappe.db.delete("User Permission")
@ -59,11 +63,11 @@ class TestPermissions(IntegrationTestCase):
def tearDown(self):
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("Blog Post")
clear_user_permissions_for_doctype("Blogger")
clear_user_permissions_for_doctype("Test Blog Category")
clear_user_permissions_for_doctype("Test Blog Post")
clear_user_permissions_for_doctype("Test Blogger")
@staticmethod
def set_strict_user_permissions(ignore):
@ -73,119 +77,124 @@ class TestPermissions(IntegrationTestCase):
ss.save()
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"))
def test_select_permission(self):
# grant only select perm to blog post
add_permission("Blog Post", "Sales User", 0)
update_permission_property("Blog Post", "Sales User", 0, "select", 1)
update_permission_property("Blog Post", "Sales User", 0, "read", 0)
update_permission_property("Blog Post", "Sales User", 0, "write", 0)
add_permission("Test Blog Post", "Sales User", 0)
update_permission_property("Test Blog Post", "Sales User", 0, "select", 1)
update_permission_property("Test Blog Post", "Sales User", 0, "read", 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")
# 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"))
# validate does not have read and write perm
self.assertFalse(post.has_permission("read"))
self.assertRaises(frappe.PermissionError, post.save)
permitted_record = frappe.get_list("Blog Post", fields="*", limit=1)[0]
full_record = frappe.get_all("Blog Post", fields="*", limit=1)[0]
permitted_record = frappe.get_list("Test 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.assertSequenceSubset(post.meta.get_search_fields(), permitted_record)
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")
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(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(get_doc_permissions(post1).get("read"))
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")
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.assertFalse("-test-blog-post" in names)
self.assertTrue("_Test Blog Post 1" in names)
self.assertFalse("_Test Blog Post" in names)
def test_default_values(self):
doc = frappe.new_doc("Blog Post")
doc = frappe.new_doc("Test Blog Post")
self.assertFalse(doc.get("blog_category"))
# 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")
doc = frappe.new_doc("Blog Post")
self.assertEqual(doc.get("blog_category"), "-test-blog-category-1")
doc = frappe.new_doc("Test Blog Post")
self.assertEqual(doc.get("blog_category"), "_Test Blog Category 1")
# Don't fetch default if user permissions is more than 1
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()
doc = frappe.new_doc("Blog Post")
doc = frappe.new_doc("Test Blog Post")
self.assertFalse(doc.get("blog_category"))
# Fetch user permission set as default from multiple user permission
add_user_permission(
"Blog Category",
"-test-blog-category-2",
"Test Blog Category",
"_Test Blog Category 2",
"test2@example.com",
ignore_permissions=True,
is_default=1,
)
frappe.clear_cache()
doc = frappe.new_doc("Blog Post")
self.assertEqual(doc.get("blog_category"), "-test-blog-category-2")
doc = frappe.new_doc("Test Blog Post")
self.assertEqual(doc.get("blog_category"), "_Test Blog Category 2")
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.save()
frappe.permissions.add_user_permission("Test Blogger", blogger.name, blogger.user)
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"))
post1 = frappe.get_doc("Blog Post", "-test-blog-post-1")
post1 = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
self.assertFalse(post1.has_permission("read"))
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.save()
frappe.permissions.add_user_permission("Test Blogger", blogger.name, blogger.user)
frappe.set_user("test2@example.com")
names = [d.name for d in frappe.get_list("Blog Post", fields=["name", "owner"])]
self.assertTrue("-test-blog-post-2" in names)
self.assertFalse("-test-blog-post-1" in names)
names = [d.name for d in frappe.get_list("Test Blog Post", fields=["name", "owner"])]
self.assertTrue("_Test Blog Post 2" in names)
self.assertFalse("_Test Blog Post 1" in names)
def test_set_user_permissions(self):
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):
frappe.set_user("test2@example.com")
# this user can't add user permissions
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):
@ -194,11 +203,11 @@ class TestPermissions(IntegrationTestCase):
frappe.set_user("test2@example.com")
# 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"))
# 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"))
def test_not_allowed_to_remove_user_permissions(self):
@ -210,24 +219,24 @@ class TestPermissions(IntegrationTestCase):
self.assertRaises(
frappe.PermissionError,
remove_user_permission,
"Blog Post",
"-test-blog-post",
"Test Blog Post",
"_Test Blog Post",
"test2@example.com",
)
def test_user_permissions_if_applied_on_doc_being_evaluated(self):
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"))
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")
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"))
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"))
def test_set_standard_fields_manually(self):
@ -257,8 +266,8 @@ class TestPermissions(IntegrationTestCase):
self.assertRaises(frappe.CannotChangeConstantError, user.save)
def test_set_only_once(self):
blog_post = frappe.get_meta("Blog Post")
doc = frappe.get_doc("Blog Post", "-test-blog-post-1")
blog_post = frappe.get_meta("Test Blog Post")
doc = frappe.get_doc("Test Blog Post", "_Test Blog Post 1")
doc.db_set("title", "Old")
blog_post.get_field("title").set_only_once = 1
doc.title = "New"
@ -268,7 +277,7 @@ class TestPermissions(IntegrationTestCase):
def test_set_only_once_child_table_rows(self):
doctype_meta = frappe.get_meta("DocType")
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
doc.fields = doc.fields[:-1]
@ -278,51 +287,50 @@ class TestPermissions(IntegrationTestCase):
def test_set_only_once_child_table_row_value(self):
doctype_meta = frappe.get_meta("DocType")
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
doc.fields[-1].fieldtype = "Check"
doc.fields[-3].fieldtype = "Check"
self.assertRaises(frappe.CannotChangeConstantError, doc.save)
frappe.clear_cache(doctype="DocType")
def test_set_only_once_child_table_okay(self):
doctype_meta = frappe.get_meta("DocType")
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()
self.assertFalse(doc.validate_set_only_once())
frappe.clear_cache(doctype="DocType")
def test_user_permission_doctypes(self):
add_user_permission("Blog Category", "-test-blog-category-1", "test2@example.com")
add_user_permission("Blogger", "_Test Blogger 1", "test2@example.com")
add_user_permission("Test Blog Category", "_Test Blog Category 1", "test2@example.com")
add_user_permission("Test Blogger", "_Test Blogger 1", "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"))
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"))
frappe.clear_cache(doctype="Blog Post")
frappe.clear_cache(doctype="Test Blog Post")
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("Blogger", "_Test Blogger 1", "test2@example.com")
add_user_permission("Test Blog Category", "_Test Blog Category 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):
"""If `If Owner` is checked for a Role, check if that document
is allowed to be read, updated, submitted, etc. except be created,
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()
@ -330,8 +338,8 @@ class TestPermissions(IntegrationTestCase):
doc = frappe.get_doc(
{
"doctype": "Blog Post",
"blog_category": "-test-blog-category",
"doctype": "Test Blog Post",
"blog_category": "_Test Blog Category",
"blogger": "_Test Blogger 1",
"title": "_Test Blog Post Title",
"content": "_Test Blog Post Content",
@ -341,34 +349,35 @@ class TestPermissions(IntegrationTestCase):
self.assertRaises(frappe.PermissionError, doc.insert)
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")
doc.insert()
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")
doc = frappe.get_doc(doc.doctype, doc.name)
self.assertTrue(doc.has_permission("read"))
self.assertTrue(doc.has_permission("write"))
self.assertFalse(doc.has_permission("create"))
# delete created record
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):
"""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")
doc = frappe.get_doc(
{
"doctype": "Blog Post",
"blog_category": "-test-blog-category-2",
"doctype": "Test Blog Post",
"blog_category": "_Test Blog Category 2",
"blogger": "_Test Blogger 1",
"title": "_Test Blog Post Title",
"content": "_Test Blog Post Content",
@ -378,7 +387,7 @@ class TestPermissions(IntegrationTestCase):
self.assertFalse(doc.has_permission("write"))
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")
self.assertTrue(doc.has_permission("write"))
@ -428,9 +437,9 @@ class TestPermissions(IntegrationTestCase):
clear_user_permissions_for_doctype("Contact")
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")
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"))
frappe.set_user("Administrator")
@ -444,20 +453,22 @@ class TestPermissions(IntegrationTestCase):
def test_contextual_user_permission(self):
# 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
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
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
self.assertEqual(len(posts), 4)
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
# 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):
# 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
update("Blog Post", "Blogger", 0, "if_owner", 1)
update("Blog Post", "Blogger", 0, "read", 1, 1)
update("Blog Post", "Blogger", 0, "write", 1, 1)
update("Blog Post", "Blogger", 0, "delete", 1, 1)
update("Test Blog Post", "Blogger", 0, "if_owner", 1)
update("Test Blog Post", "Blogger", 0, "read", 1, 1)
update("Test Blog Post", "Blogger", 0, "write", 1, 1)
update("Test Blog Post", "Blogger", 0, "delete", 1, 1)
# currently test2 user has not created any document
# still he should be able to do get_list query which should
# not raise permission error but simply return empty list
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")
# 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)
add_permission("Blog Post", "Blogger")
frappe.clear_cache(doctype="Blog Post")
add_permission("Test Blog Post", "Blogger")
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")
doc = frappe.get_doc(
{
"doctype": "Blog Post",
"blog_category": "-test-blog-category",
"doctype": "Test Blog Post",
"blog_category": "_Test Blog Category",
"blogger": "_Test Blogger 1",
"title": "_Test Blog Post Title",
"content": "_Test Blog Post Content",
@ -523,21 +534,21 @@ class TestPermissions(IntegrationTestCase):
self.assertTrue(doc.has_permission("delete"))
# 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):
update("Blog Post", "Blogger", 0, "if_owner", 1)
update("Blog Post", "Blogger", 0, "read", 1)
update("Blog Post", "Blogger", 0, "write", 1)
update("Blog Post", "Blogger", 0, "delete", 1)
frappe.clear_cache(doctype="Blog Post")
update("Test Blog Post", "Blogger", 0, "if_owner", 1)
update("Test Blog Post", "Blogger", 0, "read", 1)
update("Test Blog Post", "Blogger", 0, "write", 1)
update("Test Blog Post", "Blogger", 0, "delete", 1)
frappe.clear_cache(doctype="Test Blog Post")
frappe.set_user("test1@example.com")
doc = frappe.get_doc(
{
"doctype": "Blog Post",
"blog_category": "-test-blog-category",
"doctype": "Test Blog Post",
"blog_category": "_Test Blog Category",
"blogger": "_Test Blogger 1",
"title": "_Test Blog Post Title New",
"content": "_Test Blog Post Content",
@ -546,18 +557,18 @@ class TestPermissions(IntegrationTestCase):
doc.insert()
getdoc("Blog Post", doc.name)
getdoc("Test Blog Post", doc.name)
doclist = [d.name for d in frappe.response.docs]
self.assertTrue(doc.name in doclist)
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):
doc = frappe.get_doc(
{
"doctype": "Blog Post",
"blog_category": "-test-blog-category",
"doctype": "Test Blog Post",
"blog_category": "_Test Blog Category",
"blogger": "_Test Blogger 1",
"title": "_Test If Owner Permissions on Get List",
"content": "_Test Blog Post Content",
@ -566,39 +577,39 @@ class TestPermissions(IntegrationTestCase):
doc.insert(ignore_if_duplicate=True)
update("Blog Post", "Blogger", 0, "if_owner", 1)
update("Blog Post", "Blogger", 0, "read", 1)
update("Test Blog Post", "Blogger", 0, "if_owner", 1)
update("Test Blog Post", "Blogger", 0, "read", 1)
user = frappe.get_doc("User", "test2@example.com")
user.add_roles("Website Manager")
frappe.clear_cache(doctype="Blog Post")
frappe.clear_cache(doctype="Test Blog Post")
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
frappe.set_user("test1@example.com")
user.remove_roles("Website Manager")
frappe.clear_cache(doctype="Blog Post")
frappe.clear_cache(doctype="Test Blog Post")
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):
update("Blog Post", "Blogger", 0, "if_owner", 1)
update("Blog Post", "Blogger", 0, "read", 1, 1)
update("Blog Post", "Blogger", 0, "write", 1, 1)
update("Blog Post", "Blogger", 0, "delete", 1, 1)
update("Test Blog Post", "Blogger", 0, "if_owner", 1)
update("Test Blog Post", "Blogger", 0, "read", 1, 1)
update("Test Blog Post", "Blogger", 0, "write", 1, 1)
update("Test Blog Post", "Blogger", 0, "delete", 1, 1)
# 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"):
doc = frappe.get_doc(
{
"doctype": "Blog Post",
"blog_category": "-test-blog-category",
"doctype": "Test Blog Post",
"blog_category": "_Test Blog Category",
"blogger": "_Test Blogger 1",
"title": "_Test Blog Post Title New 1",
"content": "_Test Blog Post Content",
@ -607,46 +618,46 @@ class TestPermissions(IntegrationTestCase):
doc.insert()
getdoc("Blog Post", doc.name)
getdoc("Test Blog Post", doc.name)
doclist = [d.name for d in frappe.response.docs]
self.assertTrue(doc.name in doclist)
with self.set_user("testperm@example.com"):
# 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]
self.assertTrue(doc.name in doclist)
# 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"):
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):
current_user = frappe.session.user
frappe.set_user("Administrator")
clear_user_permissions_for_doctype("Blog Category", "test2@example.com")
clear_user_permissions_for_doctype("Blog Post", "test2@example.com")
clear_user_permissions_for_doctype("Test Blog Category", "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("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 Post", "_Test Blog Post 1", "test2@example.com")
add_user_permission("Test Blog Post", "_Test Blog Post 2", "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)
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)
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)

View file

@ -13,10 +13,11 @@ from frappe.tests.test_db_query import (
setup_patched_blog_post,
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.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():
@ -63,6 +64,9 @@ def create_tree_docs():
class TestQuery(IntegrationTestCase):
def setUp(self):
setup_for_tests()
@run_only_if(db_type_is.MARIADB)
def test_multiple_tables_in_filters(self):
self.assertEqual(
@ -719,31 +723,30 @@ class TestQuery(IntegrationTestCase):
def test_build_match_conditions(self):
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.add_roles("Blogger")
frappe.set_user("test2@example.com")
# 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))
# Add user permissions
add_user_permission("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", "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
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
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":
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")
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")
def test_ignore_permissions_for_query(self):
@ -763,17 +766,17 @@ class TestQuery(IntegrationTestCase):
# Create a test blog post
test_post = frappe.get_doc(
{
"doctype": "Blog Post",
"doctype": "Test Blog Post",
"title": "Test Permission Post",
"content": "Test Content",
"blog_category": "-test-blog-category",
"blog_category": "_Test Blog Category",
"published": 1,
}
).insert(ignore_permissions=True, ignore_mandatory=True)
# Without proper permission, published field should be filtered out
data = frappe.qb.get_query(
"Blog Post",
"Test Blog Post",
filters={"name": test_post.name},
fields=["name", "published", "title"],
ignore_permissions=False,
@ -787,7 +790,7 @@ class TestQuery(IntegrationTestCase):
# With Administrator, all fields should be accessible
frappe.set_user("Administrator")
data = frappe.qb.get_query(
"Blog Post",
"Test Blog Post",
filters={"name": test_post.name},
fields=["name", "published", "title"],
ignore_permissions=False,
@ -1055,10 +1058,10 @@ class TestQuery(IntegrationTestCase):
# Create a test blog post
test_post = frappe.get_doc(
{
"doctype": "Blog Post",
"doctype": "Test Blog Post",
"title": "Test Filter Permission Post",
"content": "Test Content",
"blog_category": "-test-blog-category",
"blog_category": "_Test Blog Category",
"published": 1, # permlevel 1
}
).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:
frappe.qb.get_query(
"Blog Post",
"Test Blog Post",
filters={"title": test_post.title},
ignore_permissions=False,
user=user.name,
@ -1078,7 +1081,7 @@ class TestQuery(IntegrationTestCase):
# Try filtering on non-permitted field (published - permlevel 1)
with self.assertRaises(frappe.PermissionError) as cm:
frappe.qb.get_query(
"Blog Post",
"Test Blog Post",
filters={"published": 1},
ignore_permissions=False,
user=user.name,

View file

@ -5,11 +5,6 @@ from frappe.utils import get_html_for_route
class TestSitemap(IntegrationTestCase):
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")
self.assertTrue("/about</loc>" in xml)
self.assertTrue("/contact</loc>" in xml)
self.assertTrue(blogs[0].route in xml)

View file

@ -1,6 +1,7 @@
import frappe
from frappe import _
from frappe.permissions import AUTOMATIC_ROLES
from frappe.tests.test_helpers import create_test_blog_category
from frappe.utils import add_to_date, now
UI_TEST_USER = "frappe@example.com"
@ -85,6 +86,13 @@ def prepare_webform_test():
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
def create_communication_record():
doc = frappe.get_doc(
@ -397,33 +405,6 @@ def insert_translations():
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
def create_test_user(username=None):
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.website.serve import get_response
EXTRA_TEST_RECORD_DEPENDENCIES = ["Blog Post"]
EXTRA_TEST_RECORD_DEPENDENCIES = ["Web Page"]
class TestWebsiteRouteMeta(IntegrationTestCase):
def test_meta_tag_generation(self):
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]
# create meta tags for this route
doc = frappe.new_doc("Website Route Meta")
doc.append("meta_tags", {"key": "type", "value": "blog_post"})
doc.append("meta_tags", {"key": "og:title", "value": "My Blog"})
doc.append("meta_tags", {"key": "type", "value": "web_page"})
doc.append("meta_tags", {"key": "og:title", "value": "My Web Page"})
doc.name = blog.route
doc.insert()
@ -31,8 +31,8 @@ class TestWebsiteRouteMeta(IntegrationTestCase):
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 property="og:title" content="My Blog">"""), html)
self.assertIn(self.normalize_html("""<meta name="type" content="web_page">"""), html)
self.assertIn(self.normalize_html("""<meta property="og:title" content="My Web Page">"""), html)
def tearDown(self):
frappe.db.rollback()

View file

@ -1,6 +1,6 @@
{
"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",
"custom_blocks": [],
"docstatus": 0,
@ -67,48 +67,6 @@
"onboard": 0,
"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,
"icon": "project",
@ -279,14 +237,6 @@
"roles": [],
"sequence_id": 14.0,
"shortcuts": [
{
"color": "Green",
"format": "{} Published",
"label": "Blog Post",
"link_to": "Blog Post",
"stats_filter": "{\"published\":\"1\"}",
"type": "DocType"
},
{
"color": "Green",
"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>