diff --git a/buzz/api/events/test_events.py b/buzz/api/events/test_events.py index d3e690af..3bce6620 100644 --- a/buzz/api/events/test_events.py +++ b/buzz/api/events/test_events.py @@ -306,27 +306,6 @@ def test_a_non_member_cannot_create_events(self): with self.assertRaises(CannotCreateEvents): create_event_endpoint(self.payload()) - def test_a_venue_from_another_team_is_refused(self): - """The reported vector: a manager naming a venue that belongs to someone else. - - Event Venue is autonamed by prompt, so another team's venue name is guessable, - and the booking confirmation reads the linked venue's address without a - permission check. - """ - stranger = create_user("create-event-venue-stranger@example.com", "Stranger") - their_team = create_owned_team("Create Event Other Team", stranger) - theirs = frappe.get_doc( - { - "doctype": "Event Venue", - "__newname": "Create Event Other Team Hall", - "address": "1 Test Street", - "team": their_team, - } - ).insert(ignore_permissions=True) - - with self.assertRaises(frappe.exceptions.ValidationError): - create_event_endpoint(self.payload(venue=str(theirs.name))) - def test_zoom_is_refused_when_the_app_is_missing(self): if is_app_installed("zoom_integration"): self.skipTest("zoom_integration is installed on this site") diff --git a/buzz/events/doctype/buzz_event/buzz_event.py b/buzz/events/doctype/buzz_event/buzz_event.py index 220208b4..86b41409 100644 --- a/buzz/events/doctype/buzz_event/buzz_event.py +++ b/buzz/events/doctype/buzz_event/buzz_event.py @@ -106,7 +106,6 @@ def validate(self): self.validate_guest_verification_config() self.validate_custom_forms() self.clear_unused_location() - self.validate_venue_team() self.validate_co_hosts() self.set_time_zone_label() @@ -122,21 +121,6 @@ def clear_unused_location(self): else: self.meeting_link = None - def validate_venue_team(self): - """A venue may only be linked by the team that owns it. - - Nothing downstream re-checks this: the booking confirmation and the calendar - invite both read the linked venue's address without a permission check, so a - cross-team link publishes the other team's address. - """ - if not self.venue: - return - - venue_team = frappe.db.get_value("Event Venue", self.venue, "team") - # An unstamped venue predates the team backfill; role permissions still gate it. - if venue_team and venue_team != self.team: - frappe.throw(_("Venue {0} belongs to another team.").format(self.venue)) - def validate_co_hosts(self): hosts = [row.host for row in self.co_hosts] duplicate = next((host for host in hosts if hosts.count(host) > 1), None) diff --git a/buzz/events/doctype/buzz_event/test_buzz_event.py b/buzz/events/doctype/buzz_event/test_buzz_event.py index 049a37f9..6a39e276 100644 --- a/buzz/events/doctype/buzz_event/test_buzz_event.py +++ b/buzz/events/doctype/buzz_event/test_buzz_event.py @@ -155,7 +155,7 @@ def test_unreserved_route_is_accepted(self): event.insert() self.assertEqual(event.route, "my-conference-2026") - # ==================== Venue Team Tests ==================== + # ==================== Venue Tests ==================== def _make_team(self, team_name: str) -> str: """Per test, not per class: tearDown rolls back everything setUpClass inserts.""" @@ -189,39 +189,6 @@ def _make_event_with_venue(self, venue: str, team: str | None): } ) - def test_a_venue_from_another_team_is_rejected(self): - """A venue carries its team's address, so linking one across teams leaks it. - - Event Venue is autonamed by prompt, so the docname is the venue's own name and - therefore guessable; nothing else stops a manager naming another team's venue. - """ - team = self._make_team("Venue Test Team") - theirs = self._make_venue("Venue Test Other Team Hall", self._make_team("Venue Test Other Team")) - event = self._make_event_with_venue(theirs, team) - - with self.assertRaises(frappe.exceptions.ValidationError): - event.validate_venue_team() - - def test_the_teams_own_venue_is_accepted(self): - team = self._make_team("Venue Test Team") - ours = self._make_venue("Venue Test Own Hall", team) - event = self._make_event_with_venue(ours, team) - - event.validate_venue_team() - - def test_a_venue_without_a_team_is_accepted(self): - """An unstamped venue predates the team backfill; role permissions still gate it. - - Same convention as `has_team_access`, which abstains on an unstamped row rather - than refusing one. - """ - team = self._make_team("Venue Test Team") - unstamped = self._make_venue("Venue Test Unstamped Hall", team) - frappe.db.set_value("Event Venue", unstamped, "team", None) - event = self._make_event_with_venue(unstamped, team) - - event.validate_venue_team() - def test_turning_an_event_online_drops_its_venue(self): """The venue outlives the medium otherwise. diff --git a/buzz/events/doctype/buzz_team/buzz_team.py b/buzz/events/doctype/buzz_team/buzz_team.py index 9b17b83e..670fe743 100644 --- a/buzz/events/doctype/buzz_team/buzz_team.py +++ b/buzz/events/doctype/buzz_team/buzz_team.py @@ -32,8 +32,8 @@ def create_default_team_for(user: str) -> "BuzzTeam": def set_team_from_sole_membership(doc, event=None): """Fill an empty team from the user's only enabled membership. - Zero or several memberships leave it empty, so reqd raises rather than this - picking a team on the user's behalf. + Zero or several memberships leave it empty rather than this picking a team on + the user's behalf; where the field is reqd, that raises. """ if doc.team: return diff --git a/buzz/events/doctype/buzz_team/test_buzz_team.py b/buzz/events/doctype/buzz_team/test_buzz_team.py index 13452990..57d0e136 100644 --- a/buzz/events/doctype/buzz_team/test_buzz_team.py +++ b/buzz/events/doctype/buzz_team/test_buzz_team.py @@ -177,8 +177,9 @@ def test_leaves_team_empty_when_the_user_has_two_teams(self): create_owned_team("Two Teams A", user) create_owned_team("Two Teams B", user) - with self.assertRaises(frappe.MandatoryError): - self.insert_as(user, "Event Venue", "Ambiguous") + doc = self.insert_as(user, "Event Venue", "Ambiguous") + + self.assertFalse(doc.team) def test_explicit_team_wins_for_a_multi_team_user(self): user = create_user("picks-a-team@example.com", "Tenant") diff --git a/buzz/events/doctype/event_venue/event_venue.json b/buzz/events/doctype/event_venue/event_venue.json index 39912868..f9b461bc 100644 --- a/buzz/events/doctype/event_venue/event_venue.json +++ b/buzz/events/doctype/event_venue/event_venue.json @@ -76,14 +76,13 @@ "fieldtype": "Link", "label": "Team", "options": "Buzz Team", - "reqd": 1, "search_index": 1 } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2026-07-31 12:00:00.000000", + "modified": "2026-09-08 12:00:00.000000", "modified_by": "Administrator", "module": "Events", "name": "Event Venue", diff --git a/buzz/events/doctype/event_venue/event_venue.py b/buzz/events/doctype/event_venue/event_venue.py index 5a411553..6e79c24c 100644 --- a/buzz/events/doctype/event_venue/event_venue.py +++ b/buzz/events/doctype/event_venue/event_venue.py @@ -20,7 +20,7 @@ class EventVenue(Document): google_maps_embed_code: DF.Code | None latitude: DF.Float longitude: DF.Float - team: DF.Link + team: DF.Link | None type: DF.Literal["Embed Google Maps", "Open Street Map"] # end: auto-generated types diff --git a/dashboard/src/types/Events/BuzzEvent.ts b/dashboard/src/types/Events/BuzzEvent.ts index 0909066b..0633e14a 100644 --- a/dashboard/src/types/Events/BuzzEvent.ts +++ b/dashboard/src/types/Events/BuzzEvent.ts @@ -1,4 +1,5 @@ import type { SponsorshipDeckItem } from "../Proposals/SponsorshipDeckItem" +import type { EventCoHost } from "./EventCoHost" import type { EventFeaturedSpeaker } from "./EventFeaturedSpeaker" import type { EventPaymentGateway } from "./EventPaymentGateway" import type { ScheduleItem } from "./ScheduleItem" @@ -25,7 +26,9 @@ export interface BuzzEvent { /** Banner Image : Attach Image */ banner_image?: string /** Host : Link - Event Host */ - host: string + host?: string + /** Co-hosts : Table - Event CoHost */ + co_hosts?: EventCoHost[] /** Venue : Link - Event Venue */ venue?: string /** Start Date : Date */ diff --git a/dashboard/src/types/Events/EventCoHost.ts b/dashboard/src/types/Events/EventCoHost.ts new file mode 100644 index 00000000..13f6e501 --- /dev/null +++ b/dashboard/src/types/Events/EventCoHost.ts @@ -0,0 +1,14 @@ +export interface EventCoHost { + name: string + creation: string + modified: string + owner: string + modified_by: string + docstatus: 0 | 1 | 2 + parent?: string + parentfield?: string + parenttype?: string + idx?: number + /** Host : Link - Event Host */ + host: string +}