Skip to content

Conversation

@gaumrab
Copy link

@gaumrab gaumrab commented Nov 12, 2025

Fixes #1212

Problem (Root Cause)

When users exist in the system with None as their email address (which can happen with certain authentication backends or data migrations), the admin interface displayed several issues:

  1. Display Issue: The email field showed the literal string "None" instead of a placeholder or empty value
  2. Link Issue: Clicking on user entries with None email would incorrectly link to the superuser's email address
  3. Form Issue: The form validation didn't properly handle None email values, causing potential errors when editing such users

This created a poor user experience and could lead to confusion when managing users in the admin panel.

Solution

This PR implements a comprehensive fix across both the form logic and template rendering:

1. Form Handling (app/eventyay/control/forms/users.py)

  • None Email Detection: Added logic in UserEditForm.__init__() to detect when an existing user has None email
  • Empty Field Display: Convert None email values to empty strings in the form initial data, so the form shows an empty field instead of "None"
  • Optional Email for Existing Users: Made email field optional for existing users without email addresses
  • Improved Validation: Updated clean_email() to:
    • Handle empty strings gracefully by converting them back to None
    • Only check for duplicate emails when an email is actually provided
    • Use .get() with default to safely handle missing email values

2. Template Display (app/eventyay/control/templates/pretixcontrol/admin/users/)

  • User Form Template: Updated the user detail page header to show "—" (em dash) instead of "None" when email is missing
  • User List Template:
    • Updated email column to show "—" placeholder instead of "None" or empty string
    • Updated fullname column to use default:"—" instead of default_if_none:"" for consistent placeholder display

Changes Made

  • app/eventyay/control/forms/users.py: Enhanced form initialization and email validation
  • app/eventyay/control/templates/pretixcontrol/admin/users/form.html: Added default filter for email display
  • app/eventyay/control/templates/pretixcontrol/admin/users/index.html: Added default filters for email and fullname display

Testing

  • Users with None email now display "—" in the admin interface
  • Clicking on users without email no longer links to incorrect email addresses
  • Editing users without email works correctly with optional email field
  • Form validation properly handles empty email values

Related Issue

Before:
Screenshot From 2025-11-12 23-23-41

After:
Screenshot From 2025-11-12 23-49-36

Summary by Sourcery

Improve the Django admin user interface and form logic to properly handle users with null email addresses by displaying placeholders and adjusting form requirements and validation

Bug Fixes:

  • Prevent displaying the literal string “None” or incorrect email links for users without email in the admin interface
  • Fix form validation errors when editing users who have no email by making the email field optional

Enhancements:

  • Convert null email values to empty strings in the user edit form and clean empty inputs back to null
  • Show an em dash placeholder for missing email and fullname fields in user list and detail templates

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Nov 12, 2025

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

This PR updates the admin user edit form to properly detect and handle None email values by converting them to empty strings, making the field optional when appropriate, and refining validation; it also standardizes template displays to show an em dash placeholder for missing emails and full names.

Class diagram for updated UserEditForm email handling

classDiagram
    class UserEditForm {
        __init__(*args, **kwargs)
        clean_email()
        fields
        initial
    }
    UserEditForm : __init__ detects None email and sets initial['email'] = ''
    UserEditForm : email field required only if email exists
    UserEditForm : clean_email() converts empty string to None
    UserEditForm : clean_email() checks for duplicates only if email is provided
Loading

File-Level Changes

Change Details Files
Enhanced form logic to handle users with None email
  • Detect instance.email is None in init and set initial value to empty string
  • Toggle email field .required flag based on presence of an existing email
  • Strip input in clean_email and convert empty string back to None
  • Only enforce uniqueness check when an email is provided
app/eventyay/control/forms/users.py
Updated templates to display a consistent placeholder for missing values
  • Apply default:"—" filter to user email links in the list view
  • Use default:"—" for fullname column in the index
  • Show em dash instead of literal None in the form header
app/eventyay/control/templates/pretixcontrol/admin/users/index.html
app/eventyay/control/templates/pretixcontrol/admin/users/form.html

Assessment against linked issues

Issue Objective Addressed Explanation
#1212 Users with no email should display a blank or clearly labeled value such as '—' or 'No email' in the admin list and detail views.
#1212 Clicking on a user with no email should not display or reference the superuser’s email; each user record should reflect only that user’s stored data.
#1212 Ensure that form logic and validation properly handle users with missing emails, allowing editing and preventing incorrect associations.

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> `app/eventyay/control/forms/users.py:79-81` </location>
<code_context>

     def clean_email(self):
-        email = self.cleaned_data['email']
+        email = self.cleaned_data.get('email', '').strip()
+        # Convert empty string to None for users without email
+        if not email:
+            return None
+        # Check for duplicate emails only if email is provided
</code_context>

<issue_to_address>
**suggestion:** Stripping whitespace from email may cause issues for legitimate emails with leading/trailing spaces.

Consider validating and rejecting emails with leading or trailing spaces, rather than silently removing them, to avoid masking user input errors.
</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.

Comment on lines +79 to +81
email = self.cleaned_data.get('email', '').strip()
# Convert empty string to None for users without email
if not email:
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Stripping whitespace from email may cause issues for legitimate emails with leading/trailing spaces.

Consider validating and rejecting emails with leading or trailing spaces, rather than silently removing them, to avoid masking user input errors.

Copy link
Member

Choose a reason for hiding this comment

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

This seems reasonable. Please also show an error message for invalid emails.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for your feedback!

To show an error message for invalid emails, you can enhance the clean_email method in your form to use Django's built-in email validation. This way, if a user enters an email with leading/trailing spaces or an invalid format, the form will display a clear error message.

Here's how you can update your clean_email method:

from django.core.validators import validate_email
from django.core.exceptions import ValidationError

    def clean_email(self):
        email = self.cleaned_data.get('email', '')
        if email:
            # Check for leading/trailing spaces
            if email != email.strip():
                raise forms.ValidationError(_('Email address must not have leading or trailing spaces.'), code='invalid')
            try:
                validate_email(email)
            except ValidationError:
                raise forms.ValidationError(_('Enter a valid email address.'), code='invalid')
        else:
            return None
        # Check for duplicate emails only if email is provided
        if User.objects.filter(Q(email__iexact=email) & ~Q(pk=self.instance.pk)).exists():
            raise forms.ValidationError(
                self.error_messages['duplicate_identifier'],
                code='duplicate_identifier',
            )
        return email

This will:

  • Reject emails with leading/trailing spaces and show a specific error
  • Use Django's email validator for format checking
  • Continue to check for duplicates as before

Let me know if you need a full patch or further help integrating this!

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 fixes issues with displaying and editing users who have None as their email address in the Django admin interface. The fix prevents the literal string "None" from appearing and ensures proper form validation for users without email addresses.

Key Changes:

  • Enhanced form logic to detect and handle None email values by converting them to empty strings in the form display
  • Made email field optional for existing users without email addresses while keeping it required for new users
  • Updated templates to display "—" (em dash) placeholder instead of "None" or empty strings

Reviewed Changes

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

File Description
app/eventyay/control/forms/users.py Added logic in __init__ to handle None emails and updated clean_email() to convert empty strings back to None while safely checking for duplicates
app/eventyay/control/templates/pretixcontrol/admin/users/form.html Added default:"—" filter to user email display in the page header
app/eventyay/control/templates/pretixcontrol/admin/users/index.html Added default:"—" filters to both email and fullname columns for consistent placeholder display
Comments suppressed due to low confidence (1)

app/eventyay/control/forms/users.py:89

  • The new logic for handling users with None email addresses lacks test coverage. Consider adding tests for the following scenarios:
  1. Editing an existing user with email = None (native auth) - should show empty field and allow saving without email
  2. Editing an existing user with email = None (non-native auth) - should show disabled empty field
  3. Converting an email to None by clearing the field for a user without email
  4. Attempting to save duplicate emails when one user has None email

This would help ensure the fix works correctly and prevents regressions.

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Handle None email values - show empty field instead of "None" text
        if self.instance and self.instance.pk and self.instance.email is None:
            self.initial['email'] = ''
            # Make email not required for existing users without email
            self.fields['email'].required = False
        else:
            self.fields['email'].required = True
        self.fields['last_login'].disabled = True
        if self.instance and self.instance.auth_backend != 'native':
            del self.fields['new_pw']
            del self.fields['new_pw_repeat']
            self.fields['email'].disabled = True

    def clean_email(self):
        email = self.cleaned_data.get('email', '').strip()
        # Convert empty string to None for users without email
        if not email:
            return None
        # Check for duplicate emails only if email is provided
        if User.objects.filter(Q(email__iexact=email) & ~Q(pk=self.instance.pk)).exists():
            raise forms.ValidationError(
                self.error_messages['duplicate_identifier'],
                code='duplicate_identifier',
            )
        return email

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

mariobehling and others added 16 commits November 13, 2025 16:16
…fossasia#1157)

* Fix navigation button border radius inconsistency

- Added border-radius: 0 to .header-nav class in orga/_layout.css
- Makes Talk component navigation buttons match Tickets component style
- Ensures consistent sharp corners across all navigation buttons
- Maintains visual consistency throughout the platform

Fixes fossasia#1156

* Add inset shadow on hover to match Tickets component

- Added hover and active states with inset box-shadow
- Matches the hover effect from btn-success in Tickets component
- Uses rgba(0, 128, 0, 0.25) for green inset shadow

* Fix navigation button active state to match Tickets component

- Added .header-nav.active state with proper inset shadow
- Fixed depth and consistency of hover, active, and current page states
- Current page button now has same darker border effect as Tickets
- All navigation buttons now have identical visual feedback

* Improve CSS: use variables and remove important declarations

* Fix CSS indentation formatting

* Update app/eventyay/static/orga/css/_layout.css

* Update app/eventyay/static/orga/css/_layout.css

* Fix navigation buttons: sharp corners and inset shadow to match Tickets component

---------

Co-authored-by: Mario Behling <[email protected]>
* talk: remove ticket/video URL fields

* talk: normalize ticket base URL

* Update app/eventyay/agenda/views/talk.py

---------

Co-authored-by: Mario Behling <[email protected]>
* fix unable to save config

* Update app/eventyay/webapp/src/views/admin/config/main.vue

---------

Co-authored-by: Mario Behling <[email protected]>
…eferences with 'eventyay' in activity log system (fossasia#1310)

* Replace legacy 'pretalx' references with 'eventyay' in activity logs fossasia#1305

* Replace all 'pretix/pretalx' references with 'eventyay' in activity log system

- Updated log_prefix in 9 talk models (Room, Tag, Track, Submission, etc.)
- Replaced 47+ hardcoded pretix.* log_action calls with eventyay.* across views
- Fixed critical bug: Added logdisplay module import to ControlConfig.ready()
- Added comprehensive PRETIX_LEGACY_ALIASES for backward compatibility
- Added room action display strings for talk system logs

* Fix signal handler bugs

* Address Copilot review

* added some missing

* Update app/eventyay/control/logdisplay.py

* Update app/eventyay/base/models/information.py

* Update app/eventyay/control/logdisplay.py

* Fix duplicate dictionary keys in plains dict

* Update app/eventyay/control/logdisplay.py

* Add 6 final missing legacy aliases

---------
Co-authored-by: Mario Behling <[email protected]>
…as well as views and exporter error fix (fossasia#1177)

* email tz fix

* ensure browser timezone in views for all pages and export page error fix

* applied suggestions

* implemented suggestions

* removed pytz dependency

* Update app/eventyay/base/exporters/json.py

Co-authored-by: Copilot <[email protected]>

* Update app/eventyay/plugins/reports/exporters.py

Co-authored-by: Copilot <[email protected]>

* Update app/eventyay/control/forms/filter.py

Co-authored-by: Copilot <[email protected]>

* added suggestions

* Update app/eventyay/static/common/js/timezone.js

Co-authored-by: Copilot <[email protected]>

* added other suggestions

---------

Co-authored-by: Mario Behling <[email protected]>
Co-authored-by: Copilot <[email protected]>
…ssasia#1323)

Updates the requirements on [reportlab](https://www.reportlab.com/) to permit the latest version.

---
updated-dependencies:
- dependency-name: reportlab
  dependency-version: 4.4.5
  dependency-type: direct:production
...

Signed-off-by: Mario Behling <[email protected]>
* remove duplicate settings

* remove duplicate settings

---------

Co-authored-by: Srivatsav Auswin <[email protected]>
Co-authored-by: Mario Behling <[email protected]>
…sasia#1327)

Updates the requirements on [stripe](https://github.com/stripe/stripe-python) to permit the latest version.
- [Release notes](https://github.com/stripe/stripe-python/releases)
- [Changelog](https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md)
- [Commits](stripe/stripe-python@v13.2.0a1...v14.0.0)

---
updated-dependencies:
- dependency-name: stripe
  dependency-version: 14.0.0
  dependency-type: direct:production
...

Signed-off-by: Mario Behling <[email protected]>
…ossasia#1328)

Updates the requirements on [sentry-sdk](https://github.com/getsentry/sentry-python) to permit the latest version.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md)
- [Commits](getsentry/sentry-python@2.44.0...2.45.0)

---
updated-dependencies:
- dependency-name: sentry-sdk
  dependency-version: 2.45.0
  dependency-type: direct:production
...

Signed-off-by: Mario Behling <[email protected]>
* fix: standardize sidebar icon sizes to prevent layout shift on expand/collapse

- Set consistent icon size (14px/1.28571429em) across all sidebar states
- Fixed row height to 41px for both collapsed and expanded states
- Used flexbox alignment to maintain icon position
- Prevent icon size changes between minimized, hover, and mobile states
- Removed font-size inherit which caused icon scaling issues

fossasia#1215

* WIP: sidebar icon text hiding - template changes not rendering

- Added sidebar-text span wrappers to navigation labels in both templates
- Updated CSS to hide .sidebar-text when sidebar is minimized
- CSS compiles correctly but template changes not reflecting in browser
- Issue: Django template caching preventing changes from appearing

Status: Work in progress - debugging template rendering issue

* fix: prevent sidebar icon layout shift on expand/collapse

- Standardize icon sizes to 14px in both collapsed and expanded states
- Wrap menu labels in .sidebar-text spans for proper visibility control
- Use display:none for text hiding instead of font-size manipulation
- Maintain consistent padding and alignment in all sidebar states
- Match stable behavior from Talk component sidebar

* fix: hide TEST MODE text when sidebar is minimized

Wrap TEST MODE label in sidebar-text span to ensure it hides properly
when sidebar is collapsed, maintaining consistent behavior with other
menu items.

* fix: simplify sidebar hover behavior to match Talk component

- Remove complex flex layouts, transitions, and positioning
- Use simple display:none/inline pattern like Talk component
- Remove fixed heights and line-heights causing layout shifts
- Keep consistent 10px 15px padding in all states
- Icon sizes remain stable at 14px
- Clean hover expansion without visual glitches

* just text adjustment now

* done

* when fully expanded also using 3 lines show same now

* Fix sidebar 3 line expansion same width

* Revert information.py and logdisplay.py to match enext branch - only keep sidebar CSS/HTML fixes

* added last line
* Fix: socialauth URLs were not resolved

* Custom path converter for URLs

---------

Co-authored-by: Mario Behling <[email protected]>
Saksham-Sirohi and others added 3 commits November 20, 2025 20:03
* added translations

* implemented suggestions

---------

Co-authored-by: Mario Behling <[email protected]>
* added unscheduled rooms

* implemented suggestions

* added suggestions by copilot

* fix(migrations): add missing migrations into base/0002

---------

Co-authored-by: Sak1012 <[email protected]>
…1333)

Updates the requirements on [geoip2](https://github.com/maxmind/GeoIP2-python) to permit the latest version.
- [Release notes](https://github.com/maxmind/GeoIP2-python/releases)
- [Changelog](https://github.com/maxmind/GeoIP2-python/blob/main/HISTORY.rst)
- [Commits](maxmind/GeoIP2-python@v4.0.0...v5.2.0)

---
updated-dependencies:
- dependency-name: geoip2
  dependency-version: 5.2.0
  dependency-type: direct:production
...

Signed-off-by: Mario Behling <[email protected]>
dependabot bot and others added 8 commits November 22, 2025 11:40
…ia#1329)

Updates the requirements on [redis](https://github.com/redis/redis-py) to permit the latest version.
- [Release notes](https://github.com/redis/redis-py/releases)
- [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES)
- [Commits](redis/redis-py@v7.0.0b1...v7.1.0)

---
updated-dependencies:
- dependency-name: redis
  dependency-version: 7.1.0
  dependency-type: direct:production
...

Signed-off-by: Mario Behling <[email protected]>
* Fix HTTP 500 error handling during room deletion

* fix on basis of sourcery review

* Address reviewd exception handling

---------

Co-authored-by: Srivatsav Auswin <[email protected]>
Co-authored-by: Mario Behling <[email protected]>
* Fix fossasia#1252: HTTP 500 error when using gift cards in checkout

- Changed from .get() to .filter(secret__iexact=...).first()
- Added explicit null check: if not gc: raise GiftCard.DoesNotExist()
- Case-insensitive matching now works (YSV9AYRKW7HP == ysv9ayrkw7hp)
- More robust querying on annotated querysets
- Consistent with voucher code handling

- GiftCardPayment.checkout_prepare (line ~1257)
- GiftCardPayment.payment_prepare (line ~1338)

-Backward compatible

* refactor:  Sourcery suggestions

Instead of raising GiftCard.DoesNotExist and catching it in except block,
handle the not-found case inline with immediate user feedback. This:
- Provides more direct error handling flow
- Reduces exception overhead
- Improves code readability
- Maintains the voucher vs gift card check logic

Changes:
- Handle gc=None case inline with messages.error/warning
- Remove now-redundant GiftCard.DoesNotExist except blocks
- Keep GiftCard.MultipleObjectsReturned handling
- Preserve voucher detection logic

* Fix gift card payment: case-sensitive lookup and proper error handling

* Remove generic Exception handlers

---------

Co-authored-by: Mario Behling <[email protected]>
* Fix CSV Export Returns 500 Error

* refactor minor fixes

* fix logging usage

---------

Co-authored-by: Mario Behling <[email protected]>
* Export orders with Wikimedia username

* Avoid logic of finding column index to insert

---------

Co-authored-by: Mario Behling <[email protected]>
* image rescaling fix

* implemented copilot suggestions

---------

Co-authored-by: Mario Behling <[email protected]>
…s components (fossasia#1259)

* fix(ui): match top menu bar between Eventyay Talk and Tickets components

* update font weight

---------

Co-authored-by: Mario Behling <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

Bug: User Entries with “None” as Email Display Incorrectly in Admin and Link to Superuser Email

7 participants