Files
SUST/migrations/sqlalchemy_models.py

197 lines
6.4 KiB
Python

from sqlalchemy import Column, String, Integer, DateTime, Text, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
Base = declarative_base()
# OPTION 1: Correct relationship setup
class MemberStates(Base):
__tablename__ = 'member_states'
state_code = Column(String(1), primary_key=True)
state_name = Column(String(20), nullable=False)
description = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow)
# Backref will create 'members' attribute on MemberStates
# members = relationship('Members', back_populates='current_state')
class Members(Base):
__tablename__ = 'members'
id = Column(Integer, primary_key=True) # Assuming 'id' is your primary key
# ... other member fields ...
# Add foreign key constraint
current_state_code = Column(String(1), ForeignKey('member_states.state_code'), index=True)
current_state_changed_at = Column(DateTime)
# Correct relationship definition
current_state = relationship('MemberStates', back_populates='members')
# Update MemberStates to complete the bidirectional relationship
MemberStates.members = relationship('Members', back_populates='current_state')
class MemberStateLog(Base):
__tablename__ = 'member_state_log'
log_id = Column(Integer, primary_key=True)
member_id = Column(Integer, ForeignKey('members.id'), nullable=False)
state_code = Column(String(1), ForeignKey('member_states.state_code'), nullable=False)
changed_at = Column(DateTime, default=datetime.utcnow)
changed_by = Column(Integer) # Optional: user ID who made the change
reason = Column(Text)
# Relationships
member = relationship('Members', backref='state_history')
state = relationship('MemberStates')
# OPTION 2: Alternative approach with primaryjoin (if you can't add foreign key)
class MembersAlternative(Base):
__tablename__ = 'members'
id = Column(Integer, primary_key=True)
current_state_code = Column(String(1), index=True) # No foreign key
current_state_changed_at = Column(DateTime)
# Use primaryjoin to specify the join condition
current_state = relationship(
'MemberStates',
primaryjoin='Members.current_state_code == MemberStates.state_code',
foreign_keys=[current_state_code]
)
# USAGE EXAMPLES:
# Example 1: Query members with their current state
def get_members_with_state():
from sqlalchemy.orm import sessionmaker
Session = sessionmaker()
session = Session()
members = session.query(Members).join(Members.current_state).all()
for member in members:
print(f"Member: {member.name}")
print(f"State: {member.current_state.state_name}")
print(f"Description: {member.current_state.description}")
session.close()
# Example 2: Get member state name directly
def get_member_state_name(member):
if member.current_state:
return member.current_state.state_name
return "Unknown"
# Example 3: Filter members by state
def get_active_members():
from sqlalchemy.orm import sessionmaker
Session = sessionmaker()
session = Session()
active_members = session.query(Members).filter(
Members.current_state_code == 'A'
).all()
session.close()
return active_members
# Example 4: Count members by state
def count_members_by_state():
from sqlalchemy.orm import sessionmaker
from sqlalchemy import func
Session = sessionmaker()
session = Session()
counts = session.query(
MemberStates.state_name,
func.count(Members.id).label('count')
).outerjoin(Members).group_by(
MemberStates.state_code,
MemberStates.state_name
).all()
session.close()
return counts
# Example 5: Update member state (with logging)
def change_member_state(session, member_id, new_state_code, changed_by=None, reason=None):
# Get the member
member = session.query(Members).get(member_id)
if not member:
raise ValueError(f"Member {member_id} not found")
# Validate state exists
state = session.query(MemberStates).get(new_state_code)
if not state:
raise ValueError(f"Invalid state code: {new_state_code}")
# Log the state change
log_entry = MemberStateLog(
member_id=member_id,
state_code=new_state_code,
changed_by=changed_by,
reason=reason
)
session.add(log_entry)
# Update member's current state
member.current_state_code = new_state_code
member.current_state_changed_at = datetime.utcnow()
session.commit()
# Example 6: Serialization for API responses
def serialize_member_with_state(member):
return {
'id': member.id,
'name': member.name, # Assuming you have a name field
'current_state_code': member.current_state_code,
'current_state_name': member.current_state.state_name if member.current_state else None,
'current_state_description': member.current_state.description if member.current_state else None,
'current_state_changed_at': member.current_state_changed_at.isoformat() if member.current_state_changed_at else None
}
# Example 7: Query with eager loading to avoid N+1 queries
def get_all_members_with_states_optimized():
from sqlalchemy.orm import sessionmaker, joinedload
Session = sessionmaker()
session = Session()
members = session.query(Members).options(
joinedload(Members.current_state)
).all()
# Now you can access member.current_state.state_name without additional queries
result = [serialize_member_with_state(member) for member in members]
session.close()
return result
# Example 8: Custom property for easier access
class MembersWithProperty(Base):
__tablename__ = 'members'
id = Column(Integer, primary_key=True)
current_state_code = Column(String(1), ForeignKey('member_states.state_code'), index=True)
current_state_changed_at = Column(DateTime)
current_state = relationship('MemberStates')
@property
def state_name(self):
return self.current_state.state_name if self.current_state else "Unknown"
@property
def is_active(self):
return self.current_state_code == 'A'
@property
def is_inactive(self):
return self.current_state_code == 'I'
@property
def is_deactivated(self):
return self.current_state_code == 'D'