User Documents¶
Think of a document as a flexible container for your data. Unlike SQL tables with rigid columns, MongoDB documents are like JSON objects. You can add, remove, or change fields without breaking existing data. In Mongoz, documents are Python classes that automatically become MongoDB collections.
Ravyn provides pre-built User documents so you can focus on building features, not reinventing authentication.
What You'll Learn¶
- Using Ravyn's built-in User documents
- Creating and managing users
- Password hashing and validation
- Leveraging settings for cleaner code
- Best practices for user authentication
Quick Start¶
from ravyn.contrib.auth.mongoz.documents import User
from ravyn import RavynSettings
# Create a user
user = await User.query.create_user(
email="user@example.com",
password="securepassword123",
first_name="John"
)
# Verify password
is_valid = user.check_password("securepassword123") # True
User Documents Overview¶
Ravyn provides two user documents for Mongoz integration:
AbstractUser- Base class with all user fields and functionalityUser- Ready-to-use subclass ofAbstractUser
You can use User directly or extend AbstractUser for custom fields.
Basic Usage¶
Using the Default User Document¶
from datetime import datetime
from enum import Enum
import mongoz
from mongoz import Registry
from ravyn.contrib.auth.mongoz.base_user import User as BaseUser
database = "mongodb://localhost:27017"
registry = Registry(database)
class UserType(str, Enum):
ADMIN = "admin"
USER = "user"
OTHER = "other"
class Role(mongoz.EmbeddedDocument):
name: str = mongoz.String(max_length=255, default=UserType.USER)
class User(BaseUser):
"""
Inherits from the BaseUser all the fields and adds extra unique ones.
"""
date_of_birth: datetime = mongoz.Date()
is_verified: bool = mongoz.Boolean(default=False)
role: Role = mongoz.Embed(Role)
class Meta:
registry = registry
database = "my_db"
def __str__(self):
return f"{self.email} - {self.role.name}"
# Using the manager
user = await User.objects.create(is_active=False)
user = await User.objects.get(id=user.id)
print(user)
# User(id=ObjectId(...))
This gives you a complete user authentication system with minimal code.
User Document Fields¶
The User document includes these Django-inspired fields:
| Field | Type | Description |
|---|---|---|
first_name |
String | User's first name |
last_name |
String | User's last name |
username |
String | Unique username |
email |
User's email address | |
password |
String | Hashed password |
last_login |
DateTime | Last login timestamp |
is_active |
Boolean | Account active status |
is_staff |
Boolean | Staff access flag |
is_superuser |
Boolean | Superuser access flag |
Leveraging Settings¶
Instead of repeating database configuration, use Ravyn settings to centralize your setup:
from mongoz import Registry
from ravyn.conf.global_settings import RavynSettings
class AppSettings(RavynSettings):
@property
def registry(self) -> Registry:
database = "<YOUR-SQL-QUERY-STRING"
return Registry(database)
from datetime import datetime
from enum import Enum
import mongoz
from ravyn.conf import settings
from ravyn.contrib.auth.mongoz.base_user import User as BaseUser
registry = settings.registry
class UserType(str, Enum):
ADMIN = "admin"
USER = "user"
OTHER = "other"
class Role(mongoz.EmbeddedDocument):
name: str = mongoz.String(max_length=255, default=UserType.USER)
class User(BaseUser):
"""
Inherits from the BaseUser all the fields and adds extra unique ones.
"""
date_of_birth: datetime = mongoz.Date()
is_verified: bool = mongoz.Boolean(default=False)
role: Role = mongoz.Embed(Role)
class Meta:
registry = registry
database = "my_db"
def __str__(self):
return f"{self.email} - {self.role.name}"
This approach lets you:
- Access database configuration anywhere in your codebase
- Avoid repeating yourself
- Easily switch between environments
- Share configuration across multiple apps
User Management Functions¶
Warning
The following examples assume you're using settings as described above.
create_user¶
Create a regular user with automatic password hashing:
from pydantic import EmailStr
from ravyn.conf import settings
from ravyn.contrib.auth.mongoz.base_user import User as BaseUser
registry = settings.registry
class User(BaseUser):
"""
Inherits from the BaseUser all the fields and adds extra unique ones.
"""
class Meta:
registry = registry
database = "my_db"
def __str__(self):
return f"{self.email} - {self.last_login}"
async def create_user(
first_name: str, last_name: str, username: str, email: EmailStr, password: str
) -> User:
"""
Creates a user in the database.
"""
user = await User.create_user(
username=username,
password=password,
email=email,
first_name=first_name,
last_name=last_name,
)
return user
What happens behind the scenes:
- Password is automatically hashed using bcrypt
- User is created in the database
- Returns the User instance
create_superuser¶
Create an admin user with elevated privileges:
from pydantic import EmailStr
from ravyn.conf import settings
from ravyn.contrib.auth.mongoz.base_user import User as BaseUser
registry = settings.registry
class User(BaseUser):
"""
Inherits from the BaseUser all the fields and adds extra unique ones.
"""
class Meta:
registry = registry
database = "my_db"
def __str__(self):
return f"{self.email} - {self.last_login}"
async def create_superuser(
first_name: str, last_name: str, username: str, email: EmailStr, password: str
) -> User:
"""
Creates a superuser in the database.
"""
user = await User.create_user(
username=username,
password=password,
email=email,
first_name=first_name,
last_name=last_name,
)
return user
Automatically sets:
- is_staff = True
- is_superuser = True
- is_active = True
check_password¶
Verify a user's password:
from pydantic import EmailStr
from ravyn.conf import settings
from ravyn.contrib.auth.mongoz.base_user import User as BaseUser
registry = settings.registry
class User(BaseUser):
"""
Inherits from the BaseUser all the fields and adds extra unique ones.
"""
class Meta:
registry = registry
database = "my_db"
def __str__(self):
return f"{self.email} - {self.last_login}"
# Check if password is valid or correct
async def check_password(email: EmailStr, password: str) -> bool:
"""
Check if the password of a user is correct.
"""
user: User = await User.objects.get(email=email)
is_valid_password = await user.check_password(password)
return is_valid_password
Security features:
- Compares against hashed password
- Constant-time comparison (prevents timing attacks)
- Returns boolean (True/False)
set_password¶
Change a user's password:
from pydantic import EmailStr
from ravyn.conf import settings
from ravyn.contrib.auth.mongoz.base_user import User as BaseUser
registry = settings.registry
class User(BaseUser):
"""
Inherits from the BaseUser all the fields and adds extra unique ones.
"""
class Meta:
registry = registry
database = "my_db"
def __str__(self):
return f"{self.email} - {self.last_login}"
# Update password
async def set_password(email: EmailStr, password: str) -> None:
"""
Set the password of a user is correct.
"""
user: User = await User.query.get(email=email)
await user.set_password(password)
What happens:
- New password is hashed
- Old password is replaced
- Document instance is updated (remember to save!)
Password Hashing¶
Ravyn uses secure password hashing out of the box.
Default Hashers¶
@property
def password_hashers(self) -> list[str]:
return [
"ravyn.contrib.auth.hashers.BcryptPasswordHasher",
]
Ravyn uses passlib under the hood for secure password hashing.
Custom Hashers¶
Override the password_hashers property in your settings:
from typing import List
from ravyn import RavynSettings
from ravyn.contrib.auth.hashers import BcryptPasswordHasher
class CustomHasher(BcryptPasswordHasher):
"""
All the hashers inherit from BasePasswordHasher
"""
salt_entropy = 3000
class MySettings(RavynSettings):
@property
def password_hashers(self) -> List[str]:
return ["myapp.hashers.CustomHasher"]
Common Pitfalls & Fixes¶
Pitfall 1: Forgetting to Save After set_password¶
Problem: Password change doesn't persist.
# Wrong - password not saved
user.set_password("newpassword")
# User logs out, can't log back in!
Solution: Always save after setting password:
# Correct
user.set_password("newpassword")
await user.save()
Pitfall 2: Storing Plain Text Passwords¶
Problem: Manually setting password field.
# Wrong - plain text password!
user = await User.query.create(
email="user@example.com",
password="plaintext123" # Not hashed!
)
Solution: Use create_user or set_password:
# Correct
user = await User.query.create_user(
email="user@example.com",
password="plaintext123" # Automatically hashed
)
Pitfall 3: Not Using Unique Indexes¶
Problem: Duplicate emails or usernames.
Solution: Add unique indexes in your document:
class CustomUser(AbstractUser):
email = fields.Email()
username = fields.String(max_length=150)
class Meta:
registry = registry
database = "myapp"
indexes = [
mongoz.Index("email", unique=True),
mongoz.Index("username", unique=True)
]
Best Practices¶
1. Use create_user for User Creation¶
# Good - automatic password hashing
user = await User.query.create_user(
email="user@example.com",
password="secure123"
)
2. Validate Before Creating¶
# Good - validate input first
from pydantic import BaseModel, EmailStr
class UserCreate(BaseModel):
email: EmailStr
password: str
@validator('password')
def password_strength(cls, v):
if len(v) < 8:
raise ValueError('Password must be at least 8 characters')
return v
# Then create user
data = UserCreate(email="user@example.com", password="securepass123")
user = await User.query.create_user(**data.dict())
3. Use Settings for Registry¶
# Good - centralized configuration
from ravyn.conf import settings
class User(AbstractUser):
class Meta:
registry = settings.mongo_registry
database = "myapp"
4. Add Indexes for Performance¶
class User(AbstractUser):
class Meta:
registry = registry
database = "myapp"
indexes = [
"email", # Single field
[("email", 1), ("is_active", -1)] # Compound
]
MongoDB Advantages¶
No Migrations Needed¶
Unlike SQL, you can add fields without migrations:
# Start with basic user
class User(AbstractUser):
pass
# Later, add fields without migrations
class User(AbstractUser):
phone = fields.String() # Just add it!
preferences = fields.Object() # Flexible nested data
Flexible Schema¶
Store varying data structures:
# Different users can have different fields
user1 = User(email="user1@example.com", bio="Short bio")
user2 = User(email="user2@example.com", social_links={...})
Learn More¶
- Mongoz Documentation - Complete Mongoz guide
- MongoDB Documentation - MongoDB reference
- Passlib Documentation - Password hashing
Next Steps¶
- JWT Middleware - Secure your APIs
- Complete Example - Full authentication tutorial
- Edgy Models - SQL alternative