Skip to content

Conversation

@Gagan-Ram
Copy link
Member

@Gagan-Ram Gagan-Ram commented Oct 31, 2025

fixes: #984

Summary by Sourcery

Switch event visibility from the deprecated is_public flag to a new live flag and ensure it is used consistently as the default live mode across the Talk codebase

Bug Fixes:

  • Fix default event live mode by renaming and redirecting is_public logic to live

Enhancements:

  • Replace all model, fixture, and default event creation uses of is_public with live

Tests:

  • Update API serializers, views, templates, permissions, rules, middleware, and management commands to use live instead of is_public
  • Rename and adjust all tests and assertions to refer to event.live in place of event.is_public

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Oct 31, 2025

Reviewer's Guide

This PR systematically replaces the is_public event flag with a new live attribute, updating test suites, serializers, view logic, permission rules, templates and middleware to reflect and enforce live-mode visibility across the Talk application.

Entity relationship diagram for Event visibility change

erDiagram
    EVENT {
        bool live
        string other_fields
    }
Loading

Class diagram for updated Event model visibility attribute

classDiagram
class Event {
  +live: bool
  ...other attributes...
}
Loading

Class diagram for updated event serializers

classDiagram
class EventSerializer {
  +live: bool
  ...other fields...
}
Loading

File-Level Changes

Change Details Files
Replace is_public with live across event tests and fixtures
  • Rename test functions and assertions from is_public to live
  • Update parameterized test cases and fixtures to default to live
  • Refactor test setup for toggling event visibility to use the live flag
talk/src/tests/orga/views/test_orga_views_event.py
talk/src/tests/api/test_api_events.py
talk/src/tests/event/test_event_model.py
talk/src/tests/conftest.py
Update serializers and API filtering to use live
  • Replace is_public field with live in output serializers
  • Adjust API endpoint filtering so anonymous users only see live events
  • Refactor related API tests to assert on live instead of is_public
talk/src/pretalx/api/serializers/event.py
talk/src/tests/api/test_api_events.py
talk/src/tests/api/test_api_access_code.py
Adapt view activation/deactivation logic to check live
  • Modify POST handler to toggle event.live instead of is_public
  • Update messaging and log actions to reflect live state
  • Rename variables and branches in the organza event view
app/eventyay/orga/views/event.py
Revise permission rules, middleware, and context processors
  • Change rules predicates to use event.live for visibility
  • Update domain middleware to filter on live
  • Adjust context processors and CFV dispatch to block non-live events
app/eventyay/talk_rules/event.py
app/eventyay/common/middleware/domains.py
app/eventyay/cfp/views/auth.py
app/eventyay/orga/context_processors.py
Refresh templates to conditionally present live mode
  • Replace request.event.is_public checks with request.event.live
  • Ensure title blocks and buttons reflect live/offline states
  • Adjust dashboard and widget templates for live-mode status
app/eventyay/orga/templates/orga/event/live.html
app/eventyay/orga/templates/orga/includes/dashboard_block_event.html
app/eventyay/common/templates/common/base.html

Assessment against linked issues

Issue Objective Addressed Explanation
#984 All components (Tickets, Talk, Video) start in offline mode when a new event is created. The PR only changes the 'Talk' component's event mode field from 'is_public' to 'live' and updates related logic and tests. It does not implement or enforce the initial offline mode for all components, nor does it address the Tickets or Video components.
#984 Unify the mode/state logic and transitions across Tickets, Talk, and Video components, including backend fields and API endpoints. The PR only renames the 'is_public' field to 'live' for the Talk component and updates related code. It does not unify the mode/state logic or transitions across all components, nor does it update backend fields or API endpoints for Tickets or Video.
#984 Update the dashboard UI to show the current mode of each component and provide an actionable link/button to change it, matching the proposed design. The PR does not implement any UI changes to the dashboard to show the state of each component or provide links/buttons to change modes. It only updates backend and template logic for the Talk component.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `talk/src/tests/orga/views/test_orga_views_event.py:311` </location>
<code_context>

 @pytest.mark.django_db
-def test_toggle_event_is_public_without_warnings(
+def test_toggle_event_live_without_warnings(
     event, orga_client, default_submission_type, question, submission_type
 ):
</code_context>

<issue_to_address>
**suggestion (testing):** Test could assert that no warning messages are present after activation.

Add an explicit assertion to verify that the response contains no warning or error messages, clarifying the test's intent.
</issue_to_address>

### Comment 2
<location> `talk/src/tests/api/test_api_events.py:66-69` </location>
<code_context>
 @pytest.mark.django_db
-def test_orga_can_see_nonpublic_events(client, event, other_event, orga_user_token):
-    event.is_public = False
+def test_orga_can_see_nonlive_events(client, event, other_event, orga_user_token):
+    event.live = False
     event.save()
</code_context>

<issue_to_address>
**suggestion (testing):** Test could assert that orga users see both live and non-live events explicitly.

Add assertions to confirm that both live and non-live events appear in the response, with their 'live' status accurately shown.

```suggestion
    event.live = False
    event.save()
    other_event.live = True
    other_event.save()

    response = client.get(event.api_urls.base, follow=True)
    assert response.status_code == 200
    data = response.json()
    event_ids = [e["id"] for e in data]
    # Check both events are present
    assert event.id in event_ids
    assert other_event.id in event_ids
    # Check their 'live' status
    for e in data:
        if e["id"] == event.id:
            assert e["live"] is False
        if e["id"] == other_event.id:
            assert e["live"] is True
```
</issue_to_address>

### Comment 3
<location> `talk/src/tests/api/test_api_access_code.py:28` </location>
<code_context>


[email protected]("is_public", (True, False))
[email protected]("live", (True, False))
 @pytest.mark.django_db
-def test_cannot_see_access_codes(client, event, is_public):
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding a test for orga or privileged users accessing access codes for non-live events.

The current test only checks anonymous access. Please add tests for orga or privileged users to confirm correct permission handling for non-live events.

Suggested implementation:

```python
@pytest.mark.parametrize("live", (True, False))
@pytest.mark.django_db
def test_cannot_see_access_codes(client, event, live):
    with scope(event=event):
        event.live = live
        event.save()
    response = client.get(event.api_urls.access_codes, follow=True)
    assert response.status_code == 401


@pytest.mark.parametrize("user_type", ("orga", "privileged"))
@pytest.mark.django_db
def test_privileged_user_access_codes_for_non_live_event(request, event, user_type):
    # Assume there are fixtures or helper functions to get orga/privileged clients
    client = request.getfixturevalue(f"{user_type}_client")
    with scope(event=event):
        event.live = False
        event.save()
    response = client.get(event.api_urls.access_codes, follow=True)
    # Adjust the expected status code as per your permission logic (403 or 200)
    assert response.status_code in (200, 403)

```

- Ensure you have fixtures named `orga_client` and `privileged_client` available in your test suite. If not, you will need to implement them to provide authenticated clients for orga and privileged users.
- Adjust the expected status code in the assertion (`200` or `403`) to match your application's permission logic for orga/privileged users accessing access codes for non-live events.
</issue_to_address>

### Comment 4
<location> `talk/src/tests/api/test_api_mail.py:25` </location>
<code_context>


[email protected]("is_public", (True, False))
[email protected]("live", (True, False))
 @pytest.mark.django_db
-def test_cannot_see_access_codes(client, event, is_public):
</code_context>

<issue_to_address>
**suggestion (testing):** Consider testing mail template visibility for orga users on non-live events.

Add a test to confirm orga users can view mail templates for non-live events if this is expected behavior.
</issue_to_address>

### Comment 5
<location> `talk/src/tests/api/test_api_speaker_information.py:39` </location>
<code_context>


[email protected]("is_public", (True, False))
[email protected]("live", (True, False))
 @pytest.mark.django_db
-def test_cannot_see_access_codes(client, event, is_public):
</code_context>

<issue_to_address>
**suggestion (testing):** Consider testing speaker information visibility for orga users on non-live events.
</issue_to_address>

### Comment 6
<location> `app/eventyay/orga/views/event.py:190` </location>
<code_context>
    def post(self, request, *args, **kwargs):
        event = request.event
        action = request.POST.get('action')
        if action == 'activate':
            if event.live:
                messages.success(request, _('This event was already live.'))
            else:
                responses = activate_event.send_robust(event, request=request)
                exceptions = [response[1] for response in responses if isinstance(response[1], Exception)]
                if exceptions:
                    from eventyay.base.templatetags.rich_text import render_markdown

                    messages.error(
                        request,
                        mark_safe('\n'.join(render_markdown(e) for e in exceptions)),
                    )
                else:
                    event.live = True
                    event.save()
                    event.log_action(
                        'eventyay.event.activate',
                        person=self.request.user,
                        orga=True,
                        data={},
                    )
                    messages.success(request, _('This event is now public.'))
                    for response in responses:
                        if isinstance(response[1], str):
                            messages.success(request, response[1])
        else:  # action == 'deactivate'
            if not event.live:
                messages.success(request, _('This event was already hidden.'))
            else:
                event.live = False
                event.save()
                event.log_action(
                    'eventyay.event.deactivate',
                    person=self.request.user,
                    orga=True,
                    data={},
                )
                messages.success(request, _('This event is now hidden.'))
        return redirect(event.orga_urls.base)

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Merge else clause's nested if statement into elif ([`merge-else-if-into-elif`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/merge-else-if-into-elif/))
- Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
- Swap if/else branches ([`swap-if-else-branches`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/swap-if-else-branches/))
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.


@pytest.mark.django_db
def test_toggle_event_is_public_without_warnings(
def test_toggle_event_live_without_warnings(
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Test could assert that no warning messages are present after activation.

Add an explicit assertion to verify that the response contains no warning or error messages, clarifying the test's intent.



@pytest.mark.parametrize("is_public", (True, False))
@pytest.mark.parametrize("live", (True, False))
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Consider testing speaker information visibility for orga users on non-live events.

def post(self, request, *args, **kwargs):
event = request.event
action = request.POST.get('action')
if action == 'activate':
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): We've found these issues:

@Gagan-Ram
Copy link
Member Author

Gagan-Ram commented Oct 31, 2025

This PR partially fixes #984
@hongquan @mariobehling The talk component overlapped with ticket component's is_public field. I have made talk component to align with ticket component's live field.
Although this seems work, I think there are potential issues:

  1. If talk component goes live, ticket component also goes live even if there products and payment gateway is not created and vice-versa.
  2. Going forward we will implement the creation process of each component individually, the same above problem will again appear when user choose to enable any other component after the successfully setting up everything to make 1st component to go live.

so my suggestion is that we will have a new event model field named talk_live which acts similar to live field for tickets.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors the codebase to replace uses of the is_public field with the live field for determining event visibility. The changes systematically update test files, view logic, templates, permissions, and API serializers to use event.live instead of event.is_public when checking whether an event should be publicly accessible.

Key Changes

  • Updated all test fixtures and assertions to use event.live instead of event.is_public
  • Modified permissions and business logic across views, middleware, and context processors to check live status
  • Updated API serializers to expose live field instead of is_public in event list responses
  • Renamed test functions and parameters to reflect the new live terminology

Reviewed Changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated no comments.

Show a summary per file
File Description
Multiple test files Updated test fixtures and assertions from is_public to live
app/eventyay/orga/views/event.py Changed event activation/deactivation logic to use live field
app/eventyay/api/serializers/event.py Updated serializer to expose live instead of is_public
app/eventyay/talk_rules/*.py Updated permission rules to check live status
app/eventyay/agenda/permissions.py Modified visibility checks to use live field
Multiple template files Updated conditional rendering to check event.live
app/eventyay/common/middleware/domains.py Changed custom domain logic to filter by live
app/eventyay/cfp/views/auth.py Updated access blocking to check live status

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@hongquan
Copy link
Member

hongquan commented Nov 2, 2025

Based on #984:

  • The ticket system should have 3 states: offline, test mode, live mode.
  • The talk & video system each has 2 states: offline, live.
  • Talk & video systems can not be live if ticket system is offline.
  • Talk & video states are independent, but depending on ticket system.

If so, I propose to add these fields to Event model:

  • mode, take the value from this enum:
class EventMode(TextChoices):
    OFFLINE = 'o'
    TEST = 't'
    LIVE = 'l'
  • is_talk_live (bool)
  • is_video_live (bool)

The later two fields are boolean field because boolean type has two values.

@mariobehling I wonder if live is terminology correct, because when I hear it, I often think as in "live broadcast".

@mariobehling
Copy link
Member

Yes, live sounds correct.

@Gagan-Ram
Copy link
Member Author

  • Talk & video systems can not be live if ticket system is offline.

In future, we will give an option to enable ticketing component later. How do we handle event 'live' if ticket component is created later?

  • Talk & video states are independent, but depending on ticket system.

Not really when only Talk component is created independently and ticket component is created later or not created at all.

@Gagan-Ram
Copy link
Member Author

TLDR; The Talk component can only go live if the Tickets component is already live. However, if the Tickets component fails to go live (for example, due to missing quotas or unconfigured payments), the Talk component cannot go live either.

My understanding is that an event can be initially created with either the Talk or Ticket component, or with both simultaneously.

  • The Video component depends on the Event itself.
  • The Talk and Ticket components can function independently when created on their own.

Given that, it’s not entirely clear which component should act as the base. For example, Talk and Video are independent, but both depend on the Ticket system in certain scenarios.

To simplify this setup, I propose the following approach:


Ticketing Component Conditions

For the Ticketing component to go live, two conditions must be met:

  1. At least one quota must be configured.
  2. A payment gateway must be enabled.

Talk Component Conditions

The Talk component does not have strict prerequisites for going live. The system automatically creates a default sessionType when the component is enabled.


Possible Scenarios

  1. User enables both Talk and Tickets together:
    Everything works smoothly—no special handling required.

  2. User enables Tickets first, then Talk:
    The event can stay live since Talk can be activated without additional setup. The Talk component simply needs its default session type, which is created automatically.

  3. User enables Talk first, then Tickets:
    In this case, once the user enables Ticketing, the event must automatically switch to offline. The system should prompt the user to set up the necessary ticketing configurations (quota, product, payment gateway). The event can only go live again after these conditions are met.


Model Changes

We introduce three new fields in the Event model:

  • is_tickets_enabled
  • is_talk_enabled
  • is_ticket_created_earlier

These fields update automatically when the user enables or disables components.


Logic Overview

def enable_ticket_component():
    if self.is_ticket_created_earlier:
        # Verify quota and payment setup
        # If missing, mark event as offline
        self.live = False
    else:
        self.is_ticket_created_earlier = True
        self.is_tickets_enabled = True
        self._build_initial_ticket_data()  # Defaults to live=False on first setup

def enable_talk_component():
    # No strict conditions required for activation
    pass

def take_event_live():
    if self.is_tickets_enabled or (self.is_tickets_enabled and self.is_talk_enabled):
        # Require quota and payment setup before going live
        
    elif self.is_talk_enabled and not self.is_tickets_enabled:
        # Allow going live (Talk has minimal default setup)
        

@mariobehling
Copy link
Member

mariobehling commented Nov 3, 2025

I think we need to define more clearly what “go live” means versus “is published.”

We are running into conceptual and practical issues when “live” is tied too closely to the Tickets component. As mentioned in issue #1014, the root page of an event should, in future, always display the basic event information. Although this data technically resides in the Tickets component, it should be treated as part of a common layer of the event.

This has several implications:

  • The event info (root page) should be viewable when the event is published, even if the Tickets component is not live or not used at all.
  • Organizers should be able to publish or unpublish an event independently from enabling Tickets or Talks.
  • Once an event is over and ticketing is no longer relevant (e.g., payments disabled), the event info page should remain accessible in a published state.
  • Similarly, even before tickets go on sale, the Call for Speakers or other content should be accessible via this main event page.

To achieve this separation, we may need an additional status field, e.g., is_published, distinct from is_live.

Key question: How do we implement this distinction so that the event’s root page can be published (or unpublished) and is accessible (or inaccessible) even when the Tickets component is not live?

@Gagan-Ram
Copy link
Member Author

Gagan-Ram commented Nov 4, 2025

root page of an event

When you referred root page of an event, is it /common/event/<org_slug>/<event_slug> or presale page where currently user buys tickets from?

@mariobehling
Copy link
Member

This is an example URL of a root page https://next.eventyay.com/a-team/summit/

Screenshot from 2025-11-04 09-35-00

@hongquan
Copy link
Member

hongquan commented Nov 5, 2025

I think that the Event model should have these fields:

published_at = DateTimeField(null=True)  # For the `Event` itself.
is_tickets_live = BooleanField(default=False)  # For Ticket subsystem
is_talk_live = BooleanField(default=False)  # For Talk subsystem
is_video_live = BooleanField(default=False)  # For video subsystem

The is_published will be a property, computed from published_at (is_published = (published_at is not None)).
The is_tickets_live is boolean (two values) instead of 3-variant enum because I don't see the diference between "offline" and "test mode".
I wish I can use the name is_on_sale instead of is_tickets_live, but I don't know what name should be a replacement for the other two field.
I'm not sure if we need to track when the tickets / talk / video subsystems went live. If yes, we may follow the same pattern as published_at.

@mariobehling
Copy link
Member

@hongquan

If an event is not live, it means it is still in draft status and ticket sales are not yet possible — no one can place orders or access the checkout.

When an event is live but in test mode, the event is publicly visible, allowing organizers or testers to simulate the full purchasing process. However, all resulting orders are test orders that do not count as real sales and can be freely deleted once testing is complete.

In contrast, once an event is live and in normal mode, all ticket sales are real transactions — they can no longer be deleted, only canceled or refunded according to the organizer’s policies.

@hongquan
Copy link
Member

hongquan commented Nov 5, 2025

Thanks. Then the tickets subsystem will have 3 states. Instead of is_tickets_live, we will use this field:

class TicketingMode(TextChoices):
    OFFLINE = 'o', 'Offline'
    TEST = 't', 'Test'
    LIVE = 'l', 'Live'

ticketing_mode = CharField(max_length=1, choices=TicketingMode.choices, default=TicketingMode.OFFLINE)

Event model will also have published_at, is_talk_live, is_video_live fields as the above comment.

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.

Unify Event Modes Across Components (Tickets, Talk, Video)

3 participants