Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions buzz/api/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@

LAYOUT_FIELDTYPES = set(display_fieldtypes)

EVENT_PROPOSAL_EXCLUDE_FIELDS = DEFAULT_FIELDS | {
"naming_series",
"amended_from",
"host",
"host_company",
"host_company_logo",
"additional_notes",
"status",
"submitted_by",
}

STANDARD_EXCLUDE_FIELDS = DEFAULT_FIELDS | {
"additional_fields",
"event",
Expand Down Expand Up @@ -279,3 +290,55 @@ def submit_custom_form(
)

doc.insert(ignore_permissions=True)


def validate_event_proposal_settings():
settings = frappe.get_cached_doc("Buzz Settings")
if not settings.accept_event_proposals:
frappe.throw(_("Event Proposals are not being accepted"), frappe.DoesNotExistError)

if not settings.allow_guest_event_proposals and frappe.session.user == "Guest":
frappe.throw(_("Please log in to submit a proposal"), frappe.AuthenticationError)

return settings


@frappe.whitelist(allow_guest=True) # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
def get_event_proposal_form_data() -> dict:
settings = validate_event_proposal_settings()
form_fields = get_form_fields("Event Proposal", EVENT_PROPOSAL_EXCLUDE_FIELDS)

return {
"form_fields": form_fields,
"form_title": _("Event Proposal"),
"banner_title": settings.event_proposal_banner_title or _("Propose an Event"),
"success_title": settings.event_proposal_success_title or _("Thank you!"),
"success_message": settings.event_proposal_success_message or "",
}


@frappe.whitelist(allow_guest=True) # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
def submit_event_proposal(data: dict | str) -> None:
validate_event_proposal_settings()

data = frappe.parse_json(data) or {}

doc_data = {"doctype": "Event Proposal"}

if frappe.session.user != "Guest":
doc_data["submitted_by"] = frappe.session.user

allowed_fieldnames = {
f["fieldname"] for f in get_form_fields("Event Proposal", EVENT_PROPOSAL_EXCLUDE_FIELDS)
}
for fieldname, value in data.items():
if fieldname in allowed_fieldnames:
doc_data[fieldname] = value

meta = frappe.get_meta("Event Proposal")
for df in meta.fields:
if df.fieldtype == "Table" and df.fieldname not in EVENT_PROPOSAL_EXCLUDE_FIELDS:
if df.fieldname in data and isinstance(data[df.fieldname], list):
doc_data[df.fieldname] = data[df.fieldname]

frappe.get_doc(doc_data).insert(ignore_permissions=True)
62 changes: 61 additions & 1 deletion buzz/events/doctype/buzz_settings/buzz_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
"allow_add_ons_change_before_event_start_days",
"column_break_hagy",
"allow_ticket_cancellation_request_before_event_start_days",
"proposals_tab",
"event_proposals_section",
"accept_event_proposals",
"event_proposal_banner_title",
"column_break_lxjh",
"allow_guest_event_proposals",
"success_section",
"event_proposal_success_title",
"event_proposal_success_message",
"login_tab",
"login_banner_section",
"login_banner",
Expand Down Expand Up @@ -76,6 +85,47 @@
"label": "Support Email",
"options": "Email"
},
{
"fieldname": "proposals_tab",
"fieldtype": "Tab Break",
"label": "Proposals"
},
{
"fieldname": "event_proposals_section",
"fieldtype": "Section Break",
"label": "Event Proposals"
},
{
"default": "0",
"fieldname": "accept_event_proposals",
"fieldtype": "Check",
"label": "Accept Event Proposals"
},
{
"default": "0",
"depends_on": "eval:doc.accept_event_proposals",
"fieldname": "allow_guest_event_proposals",
"fieldtype": "Check",
"label": "Allow Guest Submission"
},
{
"depends_on": "eval:doc.accept_event_proposals",
"fieldname": "event_proposal_banner_title",
"fieldtype": "Data",
"label": "Banner Title"
},
{
"depends_on": "eval:doc.accept_event_proposals",
"fieldname": "event_proposal_success_title",
"fieldtype": "Data",
"label": "Success Title"
},
{
"depends_on": "eval:doc.accept_event_proposals",
"fieldname": "event_proposal_success_message",
"fieldtype": "Markdown Editor",
"label": "Success Message"
},
{
"fieldname": "login_tab",
"fieldtype": "Tab Break",
Expand Down Expand Up @@ -161,13 +211,23 @@
"fieldname": "custom_fields_go_after_this",
"fieldtype": "HTML",
"label": "Custom Fields Go After This"
},
{
"fieldname": "column_break_lxjh",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.accept_event_proposals",
"fieldname": "success_section",
"fieldtype": "Section Break",
"label": "Success"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-03-26 17:39:40.326309",
"modified": "2026-03-29 09:46:29.455686",
"modified_by": "Administrator",
"module": "Events",
"name": "Buzz Settings",
Expand Down
5 changes: 5 additions & 0 deletions buzz/events/doctype/buzz_settings/buzz_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@ class BuzzSettings(Document):
if TYPE_CHECKING:
from frappe.types import DF

accept_event_proposals: DF.Check
allow_add_ons_change_before_event_start_days: DF.Int
allow_guest_event_proposals: DF.Check
allow_ticket_cancellation_request_before_event_start_days: DF.Int
allow_transfer_ticket_before_event_start_days: DF.Int
auto_send_pitch_deck: DF.Check
default_sponsor_deck_cc: DF.SmallText | None
default_sponsor_deck_email_template: DF.Link | None
default_sponsor_deck_reply_to: DF.Data | None
default_ticket_email_template: DF.Link | None
event_proposal_banner_title: DF.Data | None
event_proposal_success_message: DF.MarkdownEditor | None
event_proposal_success_title: DF.Data | None
login_banner: DF.MarkdownEditor | None
support_email: DF.Data | None
# end: auto-generated types
Expand Down
28 changes: 24 additions & 4 deletions buzz/proposals/doctype/event_proposal/event_proposal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
# For license information, please see license.txt

import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.utils.data import get_url_to_form
from frappe.utils.data import get_url_to_form, getdate, today


class EventProposal(Document):
Expand Down Expand Up @@ -35,9 +36,28 @@ class EventProposal(Document):
title: DF.Data
# end: auto-generated types

def validate(self):
self.validate_dates()
self.validate_times()

def validate_dates(self):
if getdate(self.start_date) < getdate(today()):
frappe.throw(_("Start Date cannot be in the past."))

if self.end_date and getdate(self.end_date) < getdate(self.start_date):
frappe.throw(_("End Date cannot be before Start Date."))

def validate_times(self):
if not self.start_time or not self.end_time:
return

same_day = not self.end_date or getdate(self.end_date) == getdate(self.start_date)
if same_day and self.end_time <= self.start_time:
frappe.throw(_("End Time must be after Start Time for same-day events."))

def before_submit(self):
if self.status not in ("Approved", "Rejected"):
frappe.throw(frappe._("Only Approved or Rejected proposals can be submitted."))
frappe.throw(_("Only Approved or Rejected proposals can be submitted."))

self.create_event()

Expand All @@ -46,7 +66,7 @@ def create_event(self):
return

if not self.host:
frappe.throw(frappe._("Please create or set a Host before submitting the proposal."))
frappe.throw(_("Please create or set a Host before submitting the proposal."))

buzz_event = get_mapped_doc(
"Event Proposal", self.name, {"Event Proposal": {"doctype": "Buzz Event"}}
Expand All @@ -57,7 +77,7 @@ def create_event(self):
self.status = "Event Created"

frappe.msgprint(
frappe._("Buzz Event {0} created successfully.").format(
_("Buzz Event {0} created successfully.").format(
f'<a href="{get_url_to_form("Buzz Event", buzz_event.name)}">{buzz_event.title}</a>'
)
)
1 change: 1 addition & 0 deletions dashboard/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ declare module 'vue' {
LanguageSwitcher: typeof import('./src/components/LanguageSwitcher.vue')['default']
LoginDialog: typeof import('./src/components/LoginDialog.vue')['default']
LoginRequired: typeof import('./src/components/LoginRequired.vue')['default']
LucideAlertCircle: typeof import('~icons/lucide/alert-circle')['default']
Navbar: typeof import('./src/components/Navbar.vue')['default']
OfflinePaymentDialog: typeof import('./src/components/OfflinePaymentDialog.vue')['default']
PaymentGatewayDialog: typeof import('./src/components/PaymentGatewayDialog.vue')['default']
Expand Down
9 changes: 4 additions & 5 deletions dashboard/src/components/BaseCustomEventForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,12 @@
</div>

<div v-else-if="loadError" class="text-center">
<div class="bg-surface-red-1 border border-outline-red-1 rounded-lg p-8">
<LucideXCircle class="w-16 h-16 text-ink-red-2 mx-auto mb-4" />
<h2 class="text-ink-red-3 font-semibold text-xl mb-2">
<div class="bg-surface-amber-1 border border-outline-amber-1 rounded-lg p-8">
<LucideAlertCircle class="w-16 h-16 text-ink-amber-3 mx-auto mb-4" />
<h2 class="text-ink-amber-3 font-semibold text-xl mb-2">
{{ __("Not Found") }}
</h2>
<p class="text-ink-red-2">
<p class="text-ink-amber-2">
{{ loadError }}
</p>
</div>
Expand Down Expand Up @@ -163,7 +163,6 @@ import { marked } from "marked";
import { computed, reactive, ref } from "vue";
import LucideAlertCircle from "~icons/lucide/alert-circle";
import LucideCheckCircle from "~icons/lucide/check-circle";
import LucideXCircle from "~icons/lucide/x-circle";

const props = defineProps({
eventRoute: {
Expand Down
13 changes: 13 additions & 0 deletions dashboard/src/components/CustomFieldInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
/>
</div>

<div v-else-if="field.fieldtype === 'Time'" class="space-y-1.5">
<label class="text-xs text-ink-gray-5 block">
{{ __(field.label) }}
<span v-if="field.mandatory" class="text-ink-red-4">*</span>
</label>
<TimePicker
:model-value="modelValue"
@update:model-value="$emit('update:modelValue', $event)"
:placeholder="getFieldPlaceholder(field)"
/>
</div>

<div v-else-if="field.fieldtype === 'Multi Select'" class="space-y-1.5">
<label class="text-xs text-ink-gray-5 block">
{{ __(field.label) }}
Expand Down Expand Up @@ -165,6 +177,7 @@ import {
DatePicker,
DateTimePicker,
FileUploader,
TimePicker,
MultiSelect,
Rating,
Textarea,
Expand Down
Loading
Loading