-
Notifications
You must be signed in to change notification settings - Fork 5
feat:support link fields #12
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 |
|---|---|---|
|
|
@@ -60,6 +60,24 @@ def get_form(form_id: str) -> dict: | |
| } | ||
|
|
||
|
|
||
| @frappe.whitelist(allow_guest=True) | ||
| def get_link_field_options( | ||
| doctype: str, | ||
| filters: dict | None = None, | ||
| page_length: int = 20, | ||
| ) -> list[str]: | ||
| meta = frappe.get_meta(doctype) | ||
| title_field = meta.title_field or "name" | ||
|
|
||
| results = frappe.get_all( | ||
| doctype=doctype, | ||
| filters=filters or {}, | ||
| page_length=page_length, | ||
| fields=["name as value", f"{title_field} as label"], | ||
| ) | ||
| return results | ||
|
Comment on lines
+64
to
+78
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. Incorrect return type annotation. The function is annotated to return 📝 Fix the return type+from typing import TypedDict
+
+class LinkOption(TypedDict):
+ value: str
+ label: str
+
@frappe.whitelist(allow_guest=True)
def get_link_field_options(
doctype: str,
filters: dict | None = None,
page_length: int = 20,
-) -> list[str]:
+) -> list[LinkOption]:
🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| @frappe.whitelist() | ||
| def get_form_shared_with(form_id: str) -> list[frappe.Any]: | ||
| """ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,11 @@ | ||||||||||||||||||||||||||||||||||||||||
| <script setup lang="ts"> | ||||||||||||||||||||||||||||||||||||||||
| import { computed } from "vue"; | ||||||||||||||||||||||||||||||||||||||||
| import { computed, ref, watch, onMounted } from "vue"; | ||||||||||||||||||||||||||||||||||||||||
| import { Asterisk } from "lucide-vue-next"; | ||||||||||||||||||||||||||||||||||||||||
| import RenderField from "../RenderField.vue"; | ||||||||||||||||||||||||||||||||||||||||
| import { createResource } from "frappe-ui"; | ||||||||||||||||||||||||||||||||||||||||
| import { useSubmissionForm } from "@/stores/submissionForm"; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const submissionFormStore = useSubmissionForm(); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const props = defineProps({ | ||||||||||||||||||||||||||||||||||||||||
| field: { | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -47,7 +51,15 @@ const getClasses = computed(() => { | |||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const getOptions = () => { | ||||||||||||||||||||||||||||||||||||||||
| type SelectOption = { | ||||||||||||||||||||||||||||||||||||||||
| label: string; | ||||||||||||||||||||||||||||||||||||||||
| value: string; | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Reactive ref to store options | ||||||||||||||||||||||||||||||||||||||||
| const selectOptions = ref<string[] | SelectOption[] | null>(null); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const getOptions = async () => { | ||||||||||||||||||||||||||||||||||||||||
| if (!fieldData.value.options) { | ||||||||||||||||||||||||||||||||||||||||
| return ""; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -56,8 +68,42 @@ const getOptions = () => { | |||||||||||||||||||||||||||||||||||||||
| return fieldData.value.options.split("\n"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (fieldData.value.fieldtype === "Link") { | ||||||||||||||||||||||||||||||||||||||||
| const _options = createResource({ | ||||||||||||||||||||||||||||||||||||||||
| url: "forms_pro.api.form.get_link_field_options", | ||||||||||||||||||||||||||||||||||||||||
| makeParams: () => { | ||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||
| doctype: fieldData.value.options, | ||||||||||||||||||||||||||||||||||||||||
| filters: {}, | ||||||||||||||||||||||||||||||||||||||||
| page_length: 999, | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
| await _options.fetch(); | ||||||||||||||||||||||||||||||||||||||||
| return _options.data; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| return fieldData.value.options; | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Load options when component mounts or field data changes | ||||||||||||||||||||||||||||||||||||||||
| const loadOptions = async () => { | ||||||||||||||||||||||||||||||||||||||||
| selectOptions.value = await getOptions(); | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Watch for changes to field type or options | ||||||||||||||||||||||||||||||||||||||||
| watch( | ||||||||||||||||||||||||||||||||||||||||
| () => [fieldData.value.fieldtype, fieldData.value.options], | ||||||||||||||||||||||||||||||||||||||||
| () => { | ||||||||||||||||||||||||||||||||||||||||
| loadOptions(); | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
| { immediate: true } | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Also load on mount | ||||||||||||||||||||||||||||||||||||||||
| onMounted(() => { | ||||||||||||||||||||||||||||||||||||||||
| loadOptions(); | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+95
to
+106
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. Duplicate option loading on mount. The ⚡ Remove duplicate call // Watch for changes to field type or options
watch(
() => [fieldData.value.fieldtype, fieldData.value.options],
() => {
loadOptions();
},
{ immediate: true }
);
-
-// Also load on mount
-onMounted(() => {
- loadOptions();
-});The watcher with 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| </script> | ||||||||||||||||||||||||||||||||||||||||
| <template> | ||||||||||||||||||||||||||||||||||||||||
| <div :class="getClasses" v-if="fieldData.fieldtype == 'Switch'"> | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -151,7 +197,7 @@ const getOptions = () => { | |||||||||||||||||||||||||||||||||||||||
| :field="fieldData" | ||||||||||||||||||||||||||||||||||||||||
| :class="{ 'pointer-events-none': inEditMode }" | ||||||||||||||||||||||||||||||||||||||||
| :disabled="disabled" | ||||||||||||||||||||||||||||||||||||||||
| :options="getOptions()" | ||||||||||||||||||||||||||||||||||||||||
| :options="selectOptions" | ||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||
| <small class="text-gray-500"> | ||||||||||||||||||||||||||||||||||||||||
| {{ fieldData.description }} | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
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.
CRITICAL: Guest access without permission checks exposes sensitive data.
This endpoint allows unauthenticated users to query any doctype without permission validation. This is a serious security vulnerability that could expose:
🔒 Recommended security fixes
Option 1: Remove guest access and add permission checks (recommended)
Option 2: If guest access is required, implement a whitelist of allowed doctypes
@frappe.whitelist(allow_guest=True) def get_link_field_options( doctype: str, filters: dict | None = None, page_length: int = 20, ) -> list[str]: + # Only allow specific doctypes for guest access + ALLOWED_DOCTYPES = ["Country", "State", "City"] # Add safe doctypes here + if doctype not in ALLOWED_DOCTYPES: + frappe.throw(_("Access to this doctype is not allowed")) + meta = frappe.get_meta(doctype) title_field = meta.title_field or "name"🤖 Prompt for AI Agents