22 KiB
Staff User Account Feature - Complete Implementation
Overview
The Staff User Account feature enables administrators to create optional user accounts for staff members, allowing them to log into the PX360 system. This implementation provides a complete CRUD interface with user account management capabilities.
Architecture
Model Relationship
The Staff model has an optional one-to-one relationship with the User model:
# apps/organizations/models.py
class Staff(models.Model):
# ... other fields ...
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='staff_profile'
)
Key Features:
- Optional: Staff can exist without a user account
- SET_NULL: If user is deleted, staff record is preserved
- Related name:
user.staff_profileallows reverse lookup
Core Components
1. StaffService (apps/organizations/services.py)
The StaffService class provides all business logic for user account management:
Methods
create_user_for_staff(staff, role='staff', request=None)
Creates a User account for a Staff member.
Process:
- Validates staff doesn't already have a user account
- Requires staff to have an email address
- Generates unique username (format:
firstname.lastnameorfirstname.lastnameN) - Generates secure 12-character password
- Creates User with staff information:
- Email as primary identifier
- Username (optional, for backward compatibility)
- First/last name
- Employee ID
- Hospital and Department
- Active status
- Assigns role via group membership
- Links user to staff
- Logs audit trail
Returns: Created User instance
Raises: ValueError if staff already has user or no email
link_user_to_staff(staff, user_id, request=None)
Links an existing User account to a Staff member.
Process:
- Validates staff doesn't have a user account
- Retrieves user by ID
- Links user to staff
- Updates user's organization data if missing
- Logs audit trail
Returns: Updated Staff instance
Raises: ValueError if staff has user or user not found
unlink_user_from_staff(staff, request=None)
Removes User account association from a Staff member.
Process:
- Validates staff has a user account
- Sets staff.user to None
- Logs audit trail
Returns: Updated Staff instance
Raises: ValueError if staff has no user account
send_credentials_email(staff, password, request)
Sends login credentials email to staff member.
Process:
- Validates staff has email and user account
- Builds absolute login URL
- Renders HTML email template
- Sends email via Django's send_mail
- Logs audit trail
Raises: ValueError if no email or user account
generate_username(staff)
Generates a unique username from staff name.
Format: firstname.lastname (lowercase)
Duplicate Handling: Appends incremental number (e.g., john.smith2)
Returns: Unique username string
generate_password(length=12)
Generates a secure random password.
Characters: ASCII letters + digits + punctuation Length: 12 characters (configurable)
Returns: Secure random password string
get_staff_type_role(staff_type)
Maps staff_type to role name.
Current Mapping: All staff types map to 'staff' role Extensible: Can be enhanced for role-based permissions
Returns: Role name string
2. API ViewSet (apps/organizations/views.py)
The StaffViewSet provides REST API endpoints:
Standard CRUD Operations
GET /api/organizations/staff/- List staff (filtered by user role)POST /api/organizations/staff/- Create new staffGET /api/organizations/staff/{id}/- Retrieve staff detailsPUT /api/organizations/staff/{id}/- Update staffPATCH /api/organizations/staff/{id}/- Partial update staffDELETE /api/organizations/staff/{id}/- Delete staff
Custom Actions
POST /api/organizations/staff/{id}/create_user_account/
Creates a user account for staff member.
Permissions: PX Admin or Hospital Admin
Body: Optional role parameter (defaults to staff type role)
Process:
- Validates staff doesn't have user account
- Checks user permissions
- Creates user via StaffService
- Generates password
- Sends credentials email
- Returns success message with staff data
Response:
{
"message": "User account created and credentials emailed successfully",
"staff": { ...staff data... },
"email": "staff@example.com"
}
POST /api/organizations/staff/{id}/link_user/
Links existing user account to staff member.
Permissions: PX Admin or Hospital Admin
Body: Required user_id parameter
Process:
- Validates staff doesn't have user account
- Checks user permissions
- Links user via StaffService
- Returns success message
Response:
{
"message": "User account linked successfully",
"staff": { ...staff data... }
}
POST /api/organizations/staff/{id}/unlink_user/
Removes user account association from staff member.
Permissions: PX Admin or Hospital Admin Process:
- Validates staff has user account
- Checks user permissions
- Unlinks user via StaffService
- Returns success message
Response:
{
"message": "User account unlinked successfully",
"staff": { ...staff data... }
}
POST /api/organizations/staff/{id}/send_invitation/
Sends credentials email to staff member.
Permissions: PX Admin or Hospital Admin Process:
- Validates staff has user account
- Checks user permissions
- Generates new password
- Updates user password
- Sends email via StaffService
- Returns success message
Response:
{
"message": "Invitation email sent successfully",
"staff": { ...staff data... }
}
3. Admin Interface (apps/organizations/admin.py)
The Django admin interface is enhanced with user account management:
StaffAdmin Features
List Display:
- Staff name, type, job title, employee ID
- Hospital, department
- User account status (✓ Yes / ✗ No)
- Status
Custom Column:
has_user_account: Displays user account status with color coding
Bulk Actions:
-
"Create user accounts for selected staff"
- Creates user accounts for multiple staff members
- Sends credentials emails
- Reports success/failure count
-
"Send credential emails to selected staff"
- Resends credentials to staff with existing accounts
- Generates new passwords
- Reports success/failure count
Filtering & Search:
- Filter by status, hospital, staff type, specialization
- Search by name, Arabic names, employee ID, license, job title
Autocomplete Fields:
- Hospital, Department, User (for linking)
4. UI Templates
Staff List (templates/organizations/staff_list.html)
Features:
- Filters: Hospital, status, staff type, search
- Table displays user account status with badge
- Quick actions for user account management:
- Create user account (for staff without accounts)
- Send invitation email (for staff with accounts)
- Unlink user account (for staff with accounts)
- Modals for confirmation dialogs
- Pagination support
Actions Column:
- View details (always available)
- Create user account (if no user and has email)
- Send invitation (if has user)
- Unlink user (if has user)
- Actions restricted to PX Admin and Hospital Admin
Staff Detail (templates/organizations/staff_detail.html)
User Account Card:
- Displays user account status (alert box)
- Shows user details (username, email, active status, created date)
- Action buttons:
- "Create User Account" (if no account)
- "Resend Invitation Email" (if account exists)
- "Unlink User Account" (if account exists)
- Confirmation modals for each action
Other Cards:
- Personal Information
- Organization Information
- Contact Information
- Status Information
JavaScript Functions:
createUserAccount()- Shows modal and triggers API callconfirmCreateUser()- Executes create user accountsendInvitation()- Shows modal and triggers API callconfirmSendInvitation()- Executes send invitationunlinkUserAccount()- Shows modal and triggers API callconfirmUnlinkUser()- Executes unlink user
5. Email Template (templates/organizations/emails/staff_credentials.html)
Design:
- Professional HTML email with gradient header
- Clean, readable layout
- Responsive design
Content:
- Welcome message
- Credentials box with:
- Username
- Password
- Security notice (change password after first login)
- Login button (links to system)
- Footer with copyright
Styling:
- Purple gradient theme (matches PX360 branding)
- Color-coded credentials box
- Warning banner for security notice
- Mobile-friendly
6. Forms (apps/organizations/forms.py)
StaffForm:
- All standard staff fields
- Hospital filtering based on user role
- Department filtering based on selected hospital
- Email validation (lowercase, trimmed)
- Employee ID uniqueness validation
No User Management in Form:
- User account creation handled via separate actions
- Keeps form focused on staff data
- User management in detail view for better UX
Permission Model
Access Control
PX Admin:
- Can create user accounts for any staff
- Can link/unlink users for any staff
- Can send invitations to any staff
- Full access to all staff management features
Hospital Admin:
- Can create user accounts for staff in their hospital
- Can link/unlink users for staff in their hospital
- Can send invitations to staff in their hospital
- Cannot manage staff from other hospitals
Department Manager:
- Can view staff in their department
- Cannot create/link/unlink user accounts
- Cannot manage user accounts
Regular Staff:
- Can view staff in their hospital
- Cannot create/link/unlink user accounts
- Cannot manage user accounts
Implementation
Permissions enforced in:
- API ViewSet actions
- Admin actions
- UI templates (buttons hidden for unauthorized users)
Workflow Examples
Example 1: Creating Staff and User Account
Step 1: Create Staff Profile
POST /api/organizations/staff/
{
"first_name": "Ahmed",
"last_name": "Al-Saud",
"email": "ahmed.alsaud@hospital.com",
"employee_id": "EMP001",
"hospital": "...",
"department": "...",
"staff_type": "physician"
}
Step 2: Create User Account
POST /api/organizations/staff/{id}/create_user_account/
{
"role": "staff"
}
Result:
- User created with username:
ahmed.alsaud - Password generated:
Xk9#mP2$vL5! - Email sent to ahmed.alsaud@hospital.com
- Staff.user linked to new user
Example 2: Linking Existing User
Step 1: Create Staff Profile (same as above)
Step 2: Link Existing User
POST /api/organizations/staff/{id}/link_user/
{
"user_id": "123e4567-e89b-12d3-a456-426614174000"
}
Result:
- Staff.user linked to existing user
- User's organization data updated if missing
Example 3: Resending Credentials
Scenario: Staff member forgot their password
Action:
POST /api/organizations/staff/{id}/send_invitation/
Result:
- New password generated:
Qw7$rT3!nK9# - User password updated in database
- Email sent with new credentials
Example 4: Removing Login Access
Scenario: Staff member leaves organization
Action:
POST /api/organizations/staff/{id}/unlink_user/
Result:
- Staff.user set to None
- User account still exists but no longer linked
- Staff member cannot log in
Example 5: Bulk User Account Creation
Scenario: Onboarding 10 new staff members
Action:
- Go to Django Admin > Staff
- Select 10 staff members (all have emails)
- Choose "Create user accounts for selected staff"
- Click "Go"
Result:
- 10 user accounts created
- 10 emails sent with credentials
- Admin message: "Created 10 user accounts. Failed: 0"
Technical Details
Username Generation Algorithm
base_username = f"{first_name.lower()}.{last_name.lower()}"
username = base_username
counter = 1
while User.objects.filter(username=username).exists():
username = f"{base_username}{counter}"
counter += 1
Examples:
- John Smith →
john.smith - Duplicate John Smith →
john.smith2 - Another duplicate →
john.smith3
Password Generation
alphabet = string.ascii_letters + string.digits + string.punctuation
password = ''.join(secrets.choice(alphabet) for _ in range(12))
Character Set:
- Uppercase: A-Z
- Lowercase: a-z
- Digits: 0-9
- Punctuation: !"#$%&'()*+,-./:;<=>?@[]^_`{|}~
Strength: Cryptographically secure (uses secrets module)
Email Sending
Configuration Required (settings.py):
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.example.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'noreply@px360.com'
EMAIL_HOST_PASSWORD = 'password'
DEFAULT_FROM_EMAIL = 'PX360 <noreply@px360.com>'
Alternative: Use Email Backend services (SendGrid, Mailgun, etc.)
Audit Logging
All user account operations are logged via AuditService.log_from_request():
Logged Events:
user_creation- When user account is createdother- For link/unlink/send invitation actions
Metadata Includes:
- Staff ID and name
- User ID (for link/unlink)
- Role (for creation)
- Timestamp
- User who performed action
Security Considerations
1. Password Security
- Passwords are hashed using Django's PBKDF2 algorithm
- Generated passwords are only sent via email (never stored in plain text)
- Staff should be instructed to change password after first login
2. Email Security
- Credentials are sent via SMTP with TLS
- Email templates include security warnings
- Passwords are not included in any logs
3. Access Control
- Role-based permissions enforced at all levels
- Hospital admins can only manage their hospital's staff
- Actions require proper CSRF tokens
4. Data Integrity
- Foreign key constraints prevent orphaned records
- SET_NULL on delete preserves staff if user is deleted
- Validation prevents duplicate user accounts
5. Audit Trail
- All user account operations are logged
- Logs include who, when, and what
- Metadata stored for analysis
Database Schema
Staff Model Fields
class Staff(models.Model):
# Personal Information
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
first_name_ar = models.CharField(max_length=100, blank=True)
last_name_ar = models.CharField(max_length=100, blank=True)
# Role Information
staff_type = models.CharField(max_length=20, choices=STAFF_TYPE_CHOICES)
job_title = models.CharField(max_length=100)
license_number = models.CharField(max_length=50, blank=True)
specialization = models.CharField(max_length=100, blank=True)
# Employee Information
employee_id = models.CharField(max_length=50, unique=True)
email = models.EmailField(blank=True, null=True)
# Organization
hospital = models.ForeignKey(Hospital, on_delete=models.PROTECT)
department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True, blank=True)
# User Account (Optional)
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='staff_profile'
)
# Status
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active')
# Metadata
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Indexes
employee_id: Unique indexuser: Unique index (OneToOne)hospital: Foreign key indexdepartment: Foreign key indexemail: Index for lookups
Testing Recommendations
Unit Tests
# Test StaffService methods
def test_create_user_for_staff():
staff = create_test_staff(email="test@example.com")
user = StaffService.create_user_for_staff(staff)
assert staff.user == user
assert user.email == "test@example.com"
def test_generate_username():
staff = create_test_staff(first_name="John", last_name="Smith")
username = StaffService.generate_username(staff)
assert username == "john.smith"
def test_generate_password():
password = StaffService.generate_password()
assert len(password) == 12
# Verify contains characters from each class
def test_link_user_to_staff():
staff = create_test_staff()
user = create_test_user()
result = StaffService.link_user_to_staff(staff, user.id)
assert staff.user == user
def test_unlink_user_from_staff():
staff = create_test_staff_with_user()
result = StaffService.unlink_user_from_staff(staff)
assert staff.user is None
Integration Tests
# Test API endpoints
def test_create_user_account_api():
client = authenticate_as_admin()
staff = create_test_staff()
response = client.post(f'/api/organizations/staff/{staff.id}/create_user_account/')
assert response.status_code == 201
assert staff.user is not None
def test_send_invitation_api():
client = authenticate_as_admin()
staff = create_test_staff_with_user()
response = client.post(f'/api/organizations/staff/{staff.id}/send_invitation/')
assert response.status_code == 200
# Verify email was sent
def test_permissions():
# Test that non-admins cannot create user accounts
client = authenticate_as_staff()
staff = create_test_staff()
response = client.post(f'/api/organizations/staff/{staff.id}/create_user_account/')
assert response.status_code == 403
UI Tests
# Test UI interactions
def test_create_user_button_visible_for_admins():
staff = create_test_staff()
admin_user = create_admin_user()
response = client.get(f'/staff/{staff.id}/')
assert 'Create User Account' in response.content
def test_create_user_button_hidden_for_staff():
staff = create_test_staff()
regular_user = create_regular_user()
response = client.get(f'/staff/{staff.id}/')
assert 'Create User Account' not in response.content
Future Enhancements
Potential Improvements
-
Role-Based Permissions
- Different roles for different staff types
- More granular permissions per role
-
Bulk Import
- Import staff from CSV/Excel
- Auto-create user accounts during import
-
Self-Service
- Allow staff to request user account
- Approval workflow for requests
-
Password Reset
- Integration with Django's password reset
- Self-service password reset
-
2FA Support
- Two-factor authentication for staff
- Enhanced security options
-
Session Management
- Track active sessions
- Force logout from all devices
-
Audit Reports
- Generate audit reports
- Export to PDF/Excel
-
Email Customization
- Customizable email templates
- Multi-language support
Troubleshooting
Common Issues
Issue: "Staff member already has a user account"
- Cause: Attempting to create duplicate user account
- Solution: Check staff.user before creating, or unlink first
Issue: "Staff member must have an email address"
- Cause: Creating user account without email
- Solution: Add email to staff profile first
Issue: Email not sent
- Cause: Email configuration issue
- Solution: Check EMAIL_* settings, verify SMTP credentials
Issue: Username already exists
- Cause: Non-unique username generation
- Solution: The service handles this automatically by appending numbers
Issue: Permission denied
- Cause: User lacks required role
- Solution: Ensure user is PX Admin or Hospital Admin
Issue: User account creation failed
- Cause: Invalid data, constraints, or service error
- Solution: Check error message, validate staff data
API Reference
StaffViewSet Endpoints
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/organizations/staff/ | List staff | Required |
| POST | /api/organizations/staff/ | Create staff | PX Admin / Hospital Admin |
| GET | /api/organizations/staff/{id}/ | Get staff details | Required |
| PUT | /api/organizations/staff/{id}/ | Update staff | PX Admin / Hospital Admin |
| PATCH | /api/organizations/staff/{id}/ | Partial update | PX Admin / Hospital Admin |
| DELETE | /api/organizations/staff/{id}/ | Delete staff | PX Admin / Hospital Admin |
| POST | /api/organizations/staff/{id}/create_user_account/ | Create user account | PX Admin / Hospital Admin |
| POST | /api/organizations/staff/{id}/link_user/ | Link existing user | PX Admin / Hospital Admin |
| POST | /api/organizations/staff/{id}/unlink_user/ | Unlink user | PX Admin / Hospital Admin |
| POST | /api/organizations/staff/{id}/send_invitation/ | Send invitation | PX Admin / Hospital Admin |
Response Formats
Success (201 Created):
{
"message": "User account created and credentials emailed successfully",
"staff": {
"id": "...",
"first_name": "Ahmed",
"last_name": "Al-Saud",
"user": {
"id": "...",
"email": "ahmed.alsaud@hospital.com",
"username": "ahmed.alsaud"
}
},
"email": "ahmed.alsaud@hospital.com"
}
Error (400 Bad Request):
{
"error": "Staff member already has a user account"
}
Error (403 Forbidden):
{
"error": "You do not have permission to create user accounts"
}
Conclusion
The Staff User Account feature provides a complete, production-ready solution for managing staff access to the PX360 system. With robust security, comprehensive audit logging, and a user-friendly interface, administrators can efficiently manage staff user accounts from creation to termination.
Key Features Summary
✅ Optional one-to-one relationship with User model ✅ Automatic username and password generation ✅ Secure credential delivery via email ✅ Link/unlink existing user accounts ✅ Bulk operations in admin interface ✅ Role-based access control ✅ Complete audit trail ✅ RESTful API with comprehensive endpoints ✅ User-friendly web interface ✅ Internationalization support ✅ Professional email templates
The implementation follows Django best practices and is ready for production use.