Skip to content

Conversation

@gaumrab
Copy link

@gaumrab gaumrab commented Nov 12, 2025

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

Fixes #1212

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.

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.

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

2 participants