# UserManager Implementation

## Overview

This document describes the implementation of a custom `UserManager` to support email-based authentication in the PX360 application.

## Problem Statement

The User model was configured to use `email` as the `USERNAME_FIELD` for authentication, but it was still using Django's default `UserManager`. This caused issues with:

1. The `createsuperuser` management command expecting a username instead of email
2. User creation methods not properly handling email-based authentication
3. Inconsistent behavior between authentication and user management

## Solution

Implemented a custom `UserManager` that extends Django's `BaseUserManager` to properly handle email-based authentication.

## Changes Made

### 1. Created UserManager Class

**File:** `apps/accounts/models.py`

```python
class UserManager(BaseUserManager):
    """
    Custom user manager for email-based authentication.
    """
    def create_user(self, email, password=None, **extra_fields):
        """
        Create and save a regular user with the given email and password.
        """
        if not email:
            raise ValueError('The Email field must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        """
        Create and save a superuser with the given email and password.
        """
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self.create_user(email, password, **extra_fields)
```

### 2. Updated User Model

**File:** `apps/accounts/models.py`

- Added `UserManager` import from `django.contrib.auth.models`
- Added optional `username` field (for backward compatibility)
- Set `objects = UserManager()` on the User model

```python
from django.contrib.auth.models import AbstractUser, Group, Permission, BaseUserManager

class User(AbstractUser, TimeStampedModel):
    # ... other fields ...
    
    # Override username to be optional and non-unique (for backward compatibility)
    username = models.CharField(max_length=150, blank=True, null=True, unique=False)
    
    # Use email as username field for authentication
    USERNAME_FIELD = 'email'
    
    # Required fields when creating superuser
    REQUIRED_FIELDS = ['first_name', 'last_name']
    
    # Custom user manager
    objects = UserManager()
```

### 3. Updated StaffService

**File:** `apps/organizations/services.py`

Modified `create_user_for_staff` method to work with the new UserManager:

```python
@staticmethod
def create_user_for_staff(staff, role='staff', request=None):
    """
    Create a User account for a Staff member.
    """
    if staff.user:
        raise ValueError("Staff member already has a user account")

    # Generate email (required for authentication)
    if not staff.email:
        raise ValueError("Staff member must have an email address")

    # Generate username (optional, for backward compatibility)
    username = StaffService.generate_username(staff)
    password = StaffService.generate_password()

    # Create user - email is now the username field
    user = User.objects.create_user(
        email=staff.email,  # Email is the first parameter
        password=password,
        first_name=staff.first_name,
        last_name=staff.last_name,
        username=username,  # Optional field
        employee_id=staff.employee_id,
        hospital=staff.hospital,
        department=staff.department,
        is_active=True,
        is_provisional=False
    )
    
    # ... rest of the method ...
```

**Key Changes:**
- `email` is now the first parameter (required by UserManager)
- `username` is passed as an optional field
- Removed return of password (it's managed separately in the seed command)

### 4. Updated Seed Staff Command

**File:** `apps/organizations/management/commands/seed_staff.py`

Modified `create_user_for_staff` method:

```python
def create_user_for_staff(self, staff, send_email=False):
    """Create a user account for staff using StaffService"""
    try:
        # Generate password first
        password = StaffService.generate_password()
        
        # Create user account using StaffService
        user = StaffService.create_user_for_staff(staff, role, request)
        
        # Set the generated password (since StaffService doesn't return it anymore)
        user.set_password(password)
        user.save()
        
        # ... rest of the method ...
```

**Key Changes:**
- Generate password before creating user
- Set password separately after user creation
- Updated logging to show `user.email` instead of `user.username`

## Database Migration

**Migration File:** `apps/accounts/migrations/0004_alter_user_managers_and_more.py`

This migration:
- Changes the manager on the User model
- Alters the `acknowledgement_completed_at` field
- Alters the `username` field to be non-unique and optional

## Usage

### Creating a Superuser

```bash
python manage.py createsuperuser
```

The command will now prompt for:
- Email: (required)
- First name: (required)
- Last name: (required)
- Password: (required)

### Creating a User Programmatically

```python
from apps.accounts.models import User

# Create a regular user
user = User.objects.create_user(
    email='user@example.com',
    password='securepassword123',
    first_name='John',
    last_name='Doe'
)

# Create a superuser
superuser = User.objects.create_superuser(
    email='admin@example.com',
    password='securepassword123',
    first_name='Admin',
    last_name='User'
)
```

### Creating a User for Staff

```python
from apps.organizations.services import StaffService

# Ensure staff has an email
staff.email = 'staff@example.com'
staff.save()

# Create user account
user = StaffService.create_user_for_staff(
    staff=staff,
    role='staff',
    request=None
)
```

## Testing

### Test Superuser Creation

```bash
python manage.py createsuperuser
```

Follow the prompts to create a superuser using email as the identifier.

### Test Staff Seeding

```bash
# Create staff without users
python manage.py seed_staff --physicians 5 --nurses 5

# Create staff with user accounts
python manage.py seed_staff --physicians 5 --nurses 5 --create-users

# Create staff with user accounts and send emails
python manage.py seed_staff --physicians 5 --nurses 5 --create-users --send-emails
```

## Backward Compatibility

The implementation maintains backward compatibility by:

1. Keeping the `username` field (optional, non-unique)
2. Preserving existing user data
3. Allowing authentication via email (primary) while maintaining username field for legacy purposes

## Authentication Flow

1. **Login:** Users authenticate using their email address
2. **Password Reset:** Password reset uses email field
3. **User Management:** All user operations use email as the primary identifier

## Important Notes

1. **Email is Required:** All users must have a unique email address
2. **Username is Optional:** The username field exists for backward compatibility but is not used for authentication
3. **Password Management:** Passwords are still hashed and stored securely using Django's built-in password hashing
4. **Staff Email Requirement:** When creating users for staff, the staff member must have an email address

## Migration Details

### Before Migration

```python
class User(AbstractUser):
    email = models.EmailField(unique=True, db_index=True)
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name', 'last_name']
    objects = BaseUserManager()  # Default Django manager
```

### After Migration

```python
class User(AbstractUser):
    email = models.EmailField(unique=True, db_index=True)
    username = models.CharField(max_length=150, blank=True, null=True, unique=False)
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name', 'last_name']
    objects = UserManager()  # Custom email-based manager
```

## Troubleshooting

### Issue: "The Email field must be set"

**Solution:** Ensure you provide an email address when creating users. The email field is required.

### Issue: "Superuser must have is_staff=True"

**Solution:** This error occurs when using `create_superuser` without proper parameters. Use `User.objects.create_superuser()` instead of `User.objects.create_user()`.

### Issue: Staff user creation fails

**Solution:** Ensure the staff member has an email address before calling `StaffService.create_user_for_staff()`.

## Related Documentation

- [Staff User Account Implementation](./STAFF_USER_ACCOUNT_IMPLEMENTATION.md)
- [Staff Seed Command Update](./STAFF_SEED_COMMAND_UPDATE.md)
- [Login/Logout Functionality Check](../LOGIN_LOGOUT_FUNCTIONALITY_CHECK.md)

## Summary

The UserManager implementation provides a robust solution for email-based authentication in the PX360 application. It resolves issues with superuser creation, staff user management, and provides a consistent authentication experience across the platform.
