Skip to content

feat(listings): list partner events on buzz.events - #658

Merged
harshtandiya merged 6 commits into
developfrom
feat/external-event-sync
Oct 25, 2026
Merged

harshtandiya merged 6 commits into
developfrom
feat/external-event-sync

Conversation

@harshtandiya

Copy link
Copy Markdown
Collaborator

What changed

Partners who run Buzz on their own sites can now list their events on their community page on buzz.events. Closes #613.

On buzz.events

  • New Integration team role. An integration user is a member of the partner's team, but every permission helper ignores the membership. It reads and writes no team data, does not show in rosters or mentions, and only a System Manager can grant, change or remove it. The dashboard never offers the role.
  • New buzz.api.listings.upsert_event and remove_event. The integration user's API key keeps an approved external Community Event Request in sync, keyed by a new source_id field. If the community takes a listing down, later pushes update its details but do not approve it again.
  • Guest buzz.api.listings.openapi returns an OpenAPI 3.1 document for the contract, generated from the pydantic schema.

On a partner site

  • New "buzz.events" tab in Buzz Settings: enable switch, server URL (must be https), API key and API secret.
  • Saving a Buzz Event queues a push: published and not ended → upsert, otherwise → remove. Deleting an event removes its listing. Turning listing on, or changing the key, syncs every upcoming event.

Setup for a partner (System Manager on buzz.events)

  1. Create a Website User for the partner and generate its API key and secret.
  2. Add a Buzz Team Membership for that user on the partner's team with the Integration role. The team must be a published community.
  3. Send the key and secret to the partner. They paste it into Buzz Settings → buzz.events.

Not in this PR: a retry queue (re-saving the settings re-syncs everything), a self-serve key panel for team admins, partner events on /events.

Demo

Backend only, no UI changes.

Testing

  • buzz/api/listings/test_listings.py: upsert creates and updates one request, system time zone conversion, a taken-down request stays down, remove is idempotent, non-integration users are refused, the Integration member gets no team access, the role is hidden and not assignable, only a System Manager can grant or delete it, and the OpenAPI document.
  • buzz/events/test_buzz_events_listing.py: the payload, upsert versus remove, and nothing runs when listing is disabled.
  • Not run locally, because the worktree is not installed in a bench. CI runs the suite.

An integration user holds a membership so a partner site can act for
its team, but every permission helper ignores the row: it reads and
writes nothing, never shows in rosters or mentions, and only a System
Manager can grant, change or remove it.
upsert_event and remove_event let a team's integration user keep an
approved external Community Event Request in sync, keyed by a new
source_id. A request the community took down stays down. A guest
openapi endpoint describes the contract as OpenAPI 3.1, generated from
the pydantic schema.
A buzz.events tab in Buzz Settings takes the integration user's API key.
Saving a Buzz Event then lists it on buzz.events, or takes it off once
unpublished, ended or deleted. Turning listing on syncs every upcoming
event.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Backend test coverage: 93%

Commit efbc73e. Test modules are excluded.

Coverage by file
Name Stmts Miss Cover
buzz/__init__.py 5 5 0%
buzz/analytics.py 32 2 94%
buzz/api/account/__init__.py 32 2 94%
buzz/api/account/exceptions.py 8 0 100%
buzz/api/account/schemas.py 7 0 100%
buzz/api/account/services.py 15 1 93%
buzz/api/auth/__init__.py 11 0 100%
buzz/api/auth/schemas.py 3 0 100%
buzz/api/booking/__init__.py 48 2 96%
buzz/api/booking/coupons.py 25 0 100%
buzz/api/booking/details.py 98 6 94%
buzz/api/booking/downloads.py 37 0 100%
buzz/api/booking/event_data.py 49 4 92%
buzz/api/booking/exceptions.py 34 0 100%
buzz/api/booking/guests.py 71 10 86%
buzz/api/booking/holds.py 64 1 98%
buzz/api/booking/my_bookings.py 9 3 67%
buzz/api/booking/schemas.py 47 0 100%
buzz/api/booking/services.py 233 16 93%
buzz/api/campaigns/__init__.py 34 19 44%
buzz/api/campaigns/exceptions.py 11 0 100%
buzz/api/campaigns/schemas.py 2 0 100%
buzz/api/checkin/__init__.py 21 1 95%
buzz/api/checkin/exceptions.py 17 0 100%
buzz/api/checkin/schemas.py 9 0 100%
buzz/api/checkin/services.py 85 9 89%
buzz/api/comments/__init__.py 29 0 100%
buzz/api/comments/activity.py 55 1 98%
buzz/api/comments/email_content.py 90 1 99%
buzz/api/comments/exceptions.py 17 0 100%
buzz/api/comments/schemas.py 17 0 100%
buzz/api/comments/services.py 85 3 96%
buzz/api/communications/__init__.py 16 0 100%
buzz/api/communications/exceptions.py 11 0 100%
buzz/api/communications/schemas.py 6 0 100%
buzz/api/communications/services.py 64 1 98%
buzz/api/communities/__init__.py 62 1 98%
buzz/api/communities/creation.py 26 1 96%
buzz/api/communities/exceptions.py 19 0 100%
buzz/api/communities/notifications.py 34 2 94%
buzz/api/communities/queries.py 36 0 100%
buzz/api/communities/reviews.py 39 1 97%
buzz/api/communities/schemas.py 14 0 100%
buzz/api/communities/submissions.py 54 1 98%
buzz/api/events/__init__.py 52 0 100%
buzz/api/events/collaborators.py 100 4 96%
buzz/api/events/coupons.py 41 0 100%
buzz/api/events/exceptions.py 41 0 100%
buzz/api/events/schemas.py 98 0 100%
buzz/api/events/services.py 236 10 96%
buzz/api/events/taxes.py 19 0 100%
buzz/api/events/ticket_types.py 52 0 100%
buzz/api/events/zoom.py 15 1 93%
buzz/api/exceptions.py 23 0 100%
buzz/api/filters/conditions.py 106 2 98%
buzz/api/filters/exceptions.py 5 0 100%
buzz/api/filters/schemas.py 6 0 100%
buzz/api/forms/__init__.py 24 0 100%
buzz/api/forms/answers.py 77 14 82%
buzz/api/forms/exceptions.py 27 0 100%
buzz/api/forms/fields.py 82 3 96%
buzz/api/forms/schemas.py 7 0 100%
buzz/api/forms/services.py 119 1 99%
buzz/api/imports/__init__.py 13 0 100%
buzz/api/imports/exceptions.py 20 0 100%
buzz/api/imports/schemas.py 28 0 100%
buzz/api/imports/services.py 65 5 92%
buzz/api/imports/sponsors.py 165 2 99%
buzz/api/imports/spreadsheet.py 41 3 93%
buzz/api/listings/__init__.py 9 0 100%
buzz/api/listings/exceptions.py 4 0 100%
buzz/api/listings/schemas.py 3 0 100%
buzz/api/listings/services.py 33 0 100%
buzz/api/maps/__init__.py 20 0 100%
buzz/api/maps/exceptions.py 11 0 100%
buzz/api/maps/schemas.py 9 0 100%
buzz/api/maps/services.py 65 0 100%
buzz/api/notifications/__init__.py 12 0 100%
buzz/api/notifications/schemas.py 9 0 100%
buzz/api/notifications/services.py 47 0 100%
buzz/api/payments/__init__.py 10 1 90%
buzz/api/payments/currencies.py 7 2 71%
buzz/api/payments/schemas.py 2 0 100%
buzz/api/playground/__init__.py 24 1 96%
buzz/api/playground/exceptions.py 9 0 100%
buzz/api/playground/schemas.py 6 0 100%
buzz/api/playground/services.py 91 5 95%
buzz/api/proposals/__init__.py 18 0 100%
buzz/api/proposals/exceptions.py 5 0 100%
buzz/api/proposals/schemas.py 26 0 100%
buzz/api/proposals/services.py 106 1 99%
buzz/api/schemas.py 13 0 100%
buzz/api/sponsorships/__init__.py 41 0 100%
buzz/api/sponsorships/enquiries.py 74 2 97%
buzz/api/sponsorships/exceptions.py 22 0 100%
buzz/api/sponsorships/forms.py 64 2 97%
buzz/api/sponsorships/manage.py 22 1 95%
buzz/api/sponsorships/schemas.py 21 0 100%
buzz/api/sponsorships/services.py 67 1 99%
buzz/api/sponsorships/sponsors.py 29 0 100%
buzz/api/tags/__init__.py 9 0 100%
buzz/api/tags/desk.py 35 9 74%
buzz/api/tags/exceptions.py 8 0 100%
buzz/api/tags/schemas.py 2 0 100%
buzz/api/tags/services.py 56 0 100%
buzz/api/teams/__init__.py 51 2 96%
buzz/api/teams/calendar.py 28 0 100%
buzz/api/teams/exceptions.py 26 0 100%
buzz/api/teams/invitations.py 34 0 100%
buzz/api/teams/public_page.py 26 8 69%
buzz/api/teams/schemas.py 14 0 100%
buzz/api/teams/services.py 105 1 99%
buzz/api/tickets/__init__.py 15 0 100%
buzz/api/tickets/emails.py 14 9 36%
buzz/api/tickets/exceptions.py 29 0 100%
buzz/api/tickets/schemas.py 4 0 100%
buzz/api/tickets/services.py 85 0 100%
buzz/api/tickets/windows.py 12 0 100%
buzz/booking_permissions.py 47 3 94%
buzz/buzz/doctype/buzz_custom_field/buzz_custom_field.py 20 4 80%
buzz/buzz/doctype/buzz_notification_subscription/buzz_notification_subscription.py 3 0 100%
buzz/buzz/web_form/apply_for_sponsorship/apply_for_sponsorship.py 3 3 0%
buzz/buzz/web_form/propose_a_talk/propose_a_talk.py 3 3 0%
buzz/buzz_marketing/doctype/buzz_campaign/buzz_campaign.py 21 7 67%
buzz/comments.py 81 4 95%
buzz/emails.py 19 0 100%
buzz/events/banner_pattern.py 35 0 100%
buzz/events/buzz_events_listing.py 52 11 79%
buzz/events/countdown.py 13 0 100%
buzz/events/doctype/additional_event_page/additional_event_page.py 9 0 100%
buzz/events/doctype/buzz_event/buzz_event.py 278 22 92%
buzz/events/doctype/buzz_event_collaborator/buzz_event_collaborator.py 47 2 96%
buzz/events/doctype/buzz_event_form/buzz_event_form.py 11 4 64%
buzz/events/doctype/buzz_form_field/buzz_form_field.py 3 0 100%
buzz/events/doctype/buzz_price/buzz_price.py 10 0 100%
buzz/events/doctype/buzz_settings/buzz_settings.py 26 4 85%
buzz/events/doctype/buzz_tag/buzz_tag.py 30 2 93%
buzz/events/doctype/buzz_tag_link/buzz_tag_link.py 14 2 86%
buzz/events/doctype/buzz_team/buzz_team.py 102 3 97%
buzz/events/doctype/buzz_team/team_map.py 23 0 100%
buzz/events/doctype/buzz_team/team_page.py 99 2 98%
buzz/events/doctype/buzz_team_membership/buzz_team_membership.py 93 1 99%
buzz/events/doctype/buzz_team_settings/buzz_team_settings.py 45 0 100%
buzz/events/doctype/buzz_theme/buzz_theme.py 50 3 94%
buzz/events/doctype/buzz_theme/tokens.py 29 1 97%
buzz/events/doctype/buzz_theme_token/buzz_theme_token.py 3 0 100%
buzz/events/doctype/community_event_request/community_event_request.py 59 13 78%
buzz/events/doctype/event_category/event_category.py 9 0 100%
buzz/events/doctype/event_check_in/event_check_in.py 10 0 100%
buzz/events/doctype/event_cohost/event_cohost.py 4 0 100%
buzz/events/doctype/event_communication/event_communication.py 16 0 100%
buzz/events/doctype/event_external_link/event_external_link.py 4 0 100%
buzz/events/doctype/event_featured_speaker/event_featured_speaker.py 4 0 100%
buzz/events/doctype/event_feedback/event_feedback.py 4 0 100%
buzz/events/doctype/event_host/event_host.py 4 0 100%
buzz/events/doctype/event_payment_gateway/event_payment_gateway.py 4 0 100%
buzz/events/doctype/event_sponsor/event_sponsor.py 15 2 87%
buzz/events/doctype/event_talk/event_talk.py 8 0 100%
buzz/events/doctype/event_template/event_template.py 43 4 91%
buzz/events/doctype/event_template_add_on/event_template_add_on.py 4 0 100%
buzz/events/doctype/event_template_custom_field/event_template_custom_field.py 4 0 100%
buzz/events/doctype/event_template_ticket_category/event_template_ticket_category.py 4 0 100%
buzz/events/doctype/event_template_ticket_type/event_template_ticket_type.py 4 0 100%
buzz/events/doctype/event_topic/event_topic.py 18 5 72%
buzz/events/doctype/event_topic/topic_page.py 32 32 0%
buzz/events/doctype/event_topic_link/event_topic_link.py 3 0 100%
buzz/events/doctype/event_track/event_track.py 4 0 100%
buzz/events/doctype/event_user_preferences/event_user_preferences.py 4 4 0%
buzz/events/doctype/event_venue/details.py 37 1 97%
buzz/events/doctype/event_venue/event_venue.py 95 1 99%
buzz/events/doctype/event_venue/geocode.py 72 9 88%
buzz/events/doctype/event_venue/map_link.py 106 5 95%
buzz/events/doctype/event_venue_location/event_venue_location.py 3 0 100%
buzz/events/doctype/offline_payment_method/offline_payment_method.py 11 0 100%
buzz/events/doctype/schedule_item/schedule_item.py 4 0 100%
buzz/events/doctype/social_media_link/social_media_link.py 3 0 100%
buzz/events/doctype/speaker_profile/speaker_profile.py 8 0 100%
buzz/events/doctype/sponsor_enquiry_form/sponsor_enquiry_form.py 54 5 91%
buzz/events/doctype/sponsorship_tier/sponsorship_tier.py 27 1 96%
buzz/events/doctype/talk_speaker/talk_speaker.py 3 0 100%
buzz/events/doctype/utm_parameter/utm_parameter.py 4 0 100%
buzz/events/og_image.py 141 7 95%
buzz/events/og_text.py 66 1 98%
buzz/events/online_meeting.py 42 0 100%
buzz/events/report/event_attendance_summary/event_attendance_summary.py 63 32 49%
buzz/events/report/event_overview/event_overview.py 27 0 100%
buzz/hooks.py 41 0 100%
buzz/html_lines.py 66 1 98%
buzz/install.py 66 32 52%
buzz/integrations/zoom.py 3 3 0%
buzz/islands.py 9 1 89%
buzz/payments.py 96 33 66%
buzz/permissions.py 182 13 93%
buzz/playground/__init__.py 32 9 72%
buzz/playground/checklist.py 64 43 33%
buzz/playground/cleanup.py 43 5 88%
buzz/playground/demo_data.py 95 3 97%
buzz/playground/doctype/playground_email/playground_email.py 4 0 100%
buzz/playground/doctype/playground_sandbox/playground_sandbox.py 6 0 100%
buzz/playground/inbox.py 67 9 87%
buzz/proposals/description.py 68 0 100%
buzz/proposals/doctype/event_proposal/event_proposal.py 71 5 93%
buzz/proposals/doctype/proposal_speaker/proposal_speaker.py 4 0 100%
buzz/proposals/doctype/sponsorship_deck_item/sponsorship_deck_item.py 4 0 100%
buzz/proposals/doctype/sponsorship_enquiry/sponsorship_enquiry.py 81 5 94%
buzz/proposals/doctype/talk_proposal/talk_proposal.py 96 5 95%
buzz/proposals/doctype/talk_proposal_status/talk_proposal_status.py 4 0 100%
buzz/subscriptions.py 45 1 98%
buzz/tasks.py 4 4 0%
buzz/telemetry.py 46 0 100%
buzz/telemetry_scan.py 55 8 85%
buzz/ticketing/doctype/additional_field/additional_field.py 4 0 100%
buzz/ticketing/doctype/attendee_ticket_add_on/attendee_ticket_add_on.py 3 0 100%
buzz/ticketing/doctype/buzz_coupon_code/buzz_coupon_code.py 153 2 99%
buzz/ticketing/doctype/buzz_coupon_ticket_type/buzz_coupon_ticket_type.py 4 0 100%
buzz/ticketing/doctype/event_booking/event_booking.py 351 17 95%
buzz/ticketing/doctype/event_booking_attendee/event_booking_attendee.py 20 7 65%
buzz/ticketing/doctype/event_booking_refund/event_booking_refund.py 56 0 100%
buzz/ticketing/doctype/event_booking_refund_ticket/event_booking_refund_ticket.py 4 0 100%
buzz/ticketing/doctype/event_payment/event_payment.py 4 0 100%
buzz/ticketing/doctype/event_ticket/event_ticket.py 73 7 90%
buzz/ticketing/doctype/event_ticket_category/event_ticket_category.py 27 0 100%
buzz/ticketing/doctype/event_ticket_type/event_ticket_type.py 91 0 100%
buzz/ticketing/doctype/ticket_add_on/ticket_add_on.py 9 1 89%
buzz/ticketing/doctype/ticket_add_on_value/ticket_add_on_value.py 3 0 100%
buzz/ticketing/doctype/ticket_cancellation_item/ticket_cancellation_item.py 4 0 100%
buzz/ticketing/doctype/ticket_cancellation_request/ticket_cancellation_request.py 19 1 95%
buzz/ticketing/doctype/ticket_hold/ticket_hold.py 44 1 98%
buzz/ticketing/report/detailed_event_registrations/detailed_event_registrations.py 113 5 96%
buzz/ticketing/report/event_add_ons_overview/event_add_ons_overview.py 22 3 86%
buzz/uninstall.py 10 2 80%
buzz/utils.py 139 22 84%
buzz/www/community.py 24 0 100%
buzz/www/dashboard.py 27 3 89%
buzz/www/event/banner_color.py 20 6 70%
buzz/www/event/date_range.py 42 5 88%
buzz/www/event/dot_map.py 38 2 95%
buzz/www/event/index.py 163 6 96%
buzz/www/event/link_icons.py 1 0 100%
buzz/www/event/meta.py 64 5 92%
buzz/www/event/registration.py 84 15 82%
buzz/www/event/venue_map.py 35 1 97%
buzz/www/events.py 102 9 91%
buzz/www/events_sitemap.py 11 0 100%
buzz/www/site_header.py 40 1 98%
buzz/www/user_profile.py 36 0 100%
TOTAL 9863 687 93%

104 empty files skipped.

change_settings on System Settings fails on its mandatory language field
in CI.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

Nothing calls it yet; a project-wide document can come later.
- Pushes to buzz.events give up after 30 seconds instead of holding a worker.
- Deleting a never-published event no longer posts a removal.
- Payload reuses EventMeta for the end time and event URL.
- Rename PEOPLE to HUMAN_MEMBER_FILTERS, post to post_to_buzz_events,
  ListingPush.push to sync_listing, find to find_request_name.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@harshtandiya
harshtandiya merged commit 594b63d into develop Oct 25, 2026
9 checks passed
@harshtandiya
harshtandiya deleted the feat/external-event-sync branch October 25, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Push Pro sites (Frappe) to show events on the buzz.events site

1 participant