-
Notifications
You must be signed in to change notification settings - Fork 7
Frappe Draw: optional Frappe Drive integration (register diagram as a Drive file) #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| # OPTIONAL Frappe Drive integration: register a Draw diagram as a Drive file so it | ||
| # shows up in the user's Drive, like a Sheets/Slides/Writer doc. Deliberately | ||
| # soft-coupled — Draw does NOT list Drive in required_apps, so it stays installable | ||
| # standalone (e.g. on Frappe Cloud without Drive). Every entry point no-ops cleanly | ||
| # when Drive is absent or not yet set up (no team). | ||
| # | ||
| # A diagram is registered as a Drive "native document" File: a File row with | ||
| # content_doctype="Draw Diagram" + content_docname=<name> (Drive's own pattern for | ||
| # app-owned docs). Rendering the diagram INSIDE Drive needs a Drive-side viewer for | ||
| # this content type (a Drive change), which is out of scope here. | ||
|
|
||
| import frappe | ||
|
|
||
| DIAGRAM_MIME = "frappe_doc" | ||
|
|
||
|
|
||
| def drive_installed() -> bool: | ||
| return "drive" in frappe.get_installed_apps() | ||
|
|
||
|
|
||
| def _default_team() -> str | None: | ||
| """A Drive team to place the file in: the first the user belongs to, else the | ||
| first team on the site. None when Drive has no team yet (not set up).""" | ||
| if frappe.db.has_column("Drive Team Member", "user"): | ||
| mine = frappe.get_all( | ||
| "Drive Team Member", filters={"user": frappe.session.user}, pluck="parent", limit=1 | ||
| ) | ||
| if mine: | ||
| return mine[0] | ||
| teams = frappe.get_all("Drive Team", pluck="name", limit=1) | ||
| return teams[0] if teams else None | ||
|
|
||
|
|
||
| def register_in_drive(diagram_name: str, team: str | None = None) -> str | None: | ||
| """Ensure a Drive File links to this diagram; returns the File name (or None | ||
| when Drive is unavailable / has no team). Idempotent — reuses an existing link.""" | ||
| if not drive_installed() or not frappe.db.exists("Draw Diagram", diagram_name): | ||
| return None | ||
| team = team or _default_team() | ||
| if not team: | ||
| return None # Drive present but not set up (no team) — nothing to do yet. | ||
|
|
||
| existing = frappe.db.exists( | ||
| "File", {"content_doctype": "Draw Diagram", "content_docname": diagram_name} | ||
| ) | ||
| if existing: | ||
| return existing | ||
|
|
||
| diagram = frappe.get_doc("Draw Diagram", diagram_name) | ||
| drive_file = frappe.get_doc( | ||
| { | ||
| "doctype": "File", | ||
| "file_name": diagram.title or diagram_name, | ||
| "is_private": 1, | ||
| "team": team, | ||
| "content_doctype": "Draw Diagram", | ||
| "content_docname": diagram_name, | ||
| "mime_type": DIAGRAM_MIME, | ||
| } | ||
| ).insert(ignore_permissions=True) | ||
| return drive_file.name | ||
|
|
||
|
|
||
| @frappe.whitelist() | ||
| def add_to_drive(name: str) -> dict: | ||
| """Whitelisted entry point for an "Add to Drive" action. Returns whether Drive | ||
| is available and the linked File name if created.""" | ||
| file_name = register_in_drive(name) | ||
| return {"drive_installed": drive_installed(), "file": file_name} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,6 +108,43 @@ def test_get_shares_reports_level_for_dialog(self): | |
| self.assertEqual(row["level"], "edit") | ||
| self.assertTrue(row["can_edit"]) | ||
|
|
||
| def test_register_in_drive_when_available(self): | ||
| # Optional Drive integration — only exercised when Drive is installed | ||
| # (CI's fresh site has no Drive, so this skips there). | ||
| from draw.api.drive_integration import drive_installed, register_in_drive | ||
|
|
||
| if not drive_installed(): | ||
| self.skipTest("Frappe Drive not installed") | ||
|
|
||
| teams = frappe.get_all("Drive Team", pluck="name") | ||
| team = ( | ||
| teams[0] | ||
| if teams | ||
| else frappe.get_doc({"doctype": "Drive Team", "title": "Draw CI Team"}).insert( | ||
| ignore_permissions=True | ||
| ).name | ||
| ) | ||
| doc = self._make("unified", {"schemaVersion": 1, "diagramType": "unified"}) | ||
|
|
||
| file_name = register_in_drive(doc.name, team=team) | ||
| self.addCleanup(lambda: frappe.delete_doc("File", file_name, force=True, ignore_permissions=True)) | ||
| self.assertTrue(file_name) | ||
| self.assertTrue( | ||
| frappe.db.exists( | ||
| "File", {"content_doctype": "Draw Diagram", "content_docname": doc.name} | ||
| ) | ||
| ) | ||
| # Idempotent — a second call reuses the same File. | ||
| self.assertEqual(register_in_drive(doc.name, team=team), file_name) | ||
|
|
||
| def test_drive_registration_noops_without_team(self): | ||
| # Registration is a safe no-op when Drive isn't set up / not installed. | ||
| from draw.api import drive_integration | ||
|
|
||
|
Comment on lines
+136
to
+143
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| doc = self._make("block", {"schemaVersion": 1, "diagramType": "block"}) | ||
| if not drive_integration.drive_installed(): | ||
| self.assertIsNone(drive_integration.register_in_drive(doc.name)) | ||
|
|
||
| def test_unshare_revokes_access(self): | ||
| from draw.api.share import get_diagram_shares, share_diagram, unshare_diagram | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
frappe.db.exists("Draw Diagram", diagram_name)bypasses Frappe's permission layer, so any authenticated user can calladd_to_drivewith any diagram name (including private diagrams they can't read) and link it into their Drive. Addfrappe.has_permission("Draw Diagram", doc=name, throw=True)at the top ofadd_to_drivebefore callingregister_in_drive.