Skip to main content

Overview

The Roles catalog manages user role definitions for access control and permissions. Roles define what actions users can perform in the system.

Data Model

The Role model is defined in app/models/role.py:4:
class Role(db.Model):
    __tablename__ = 'roles'
    
    id_role = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), nullable=False, unique=True)
    active = db.Column(db.Boolean, nullable=False, default=True)
    
    created_at = db.Column(db.TIMESTAMP, server_default=func.current_timestamp())
    updated_at = db.Column(db.TIMESTAMP, server_onupdate=func.current_timestamp())
    deleted_at = db.Column(db.TIMESTAMP, nullable=True)
    
    created_by = db.Column(db.String(100), nullable=True)
    updated_by = db.Column(db.String(100), nullable=True)
    deleted_by = db.Column(db.String(100), nullable=True)

Fields

FieldTypeDescription
id_roleIntegerPrimary key
nameString(50)Unique role name
activeBooleanSoft delete flag
created_atTimestampRecord creation date
updated_atTimestampLast modification date
deleted_atTimestampLogical deletion date
Roles are currently simple name-based categories. Future enhancements may include granular permission assignments.

Service Layer

The RoleService class in app/catalogs/roles/services.py:13 provides business logic:

Get All Roles

Retrieves all active roles:
roles = RoleService.get_all()
Implementation from services.py:17:
@staticmethod
def get_all() -> list[Role]:
    return Role.query.filter_by(active=True).all()

Create Role

Creates a new role:
data = {"name": "Production Manager"}
role = RoleService.create(data)
Implementation from services.py:27:
@staticmethod
def create(data: dict) -> dict:
    name = data.get("name")
    
    if not name or not name.strip():
        raise ValidationError("El nombre del rol es requerido")
    
    name = name.strip()
    
    existing = Role.query.filter_by(name=name).first()
    if existing:
        raise ConflictError(f"Ya existe un rol con el nombre '{name}'")
    
    role = Role(name=name)
    db.session.add(role)
    db.session.commit()
    
    return role.to_dict()

Get Role by ID

Retrieves a specific role:
role = RoleService.get_by_id(id_role)
Implementation from services.py:64:
@staticmethod
def get_by_id(id_role: int) -> Role:
    role = Role.query.get(id_role)
    if not role:
        raise NotFoundError(f"No se encontró el rol con ID {id_role}")
    return role

Update Role

Updates an existing role:
data = {"name": "Senior Production Manager"}
RoleService.update(id_role, data)
Implementation from services.py:83:
@staticmethod
def update(id_role: int, data: dict) -> dict:
    role = RoleService.get_by_id(id_role)
    
    name = data.get("name")
    if not name or not name.strip():
        raise ValidationError("El nombre del rol es requerido")
    
    name = name.strip()
    
    existing = Role.query.filter(
        Role.name == name, 
        Role.id_role != id_role
    ).first()
    if existing:
        raise ConflictError(f"Ya existe un rol con el nombre '{name}'")
    
    role.name = name
    db.session.commit()
    
    return role.to_dict()

Delete Role

Performs a soft delete:
RoleService.delete(id_role)
Implementation from services.py:123:
@staticmethod
def delete(id_role: int) -> None:
    role = RoleService.get_by_id(id_role)
    role.active = False
    role.deleted_at = func.current_timestamp()
    db.session.commit()
Be cautious when deleting roles that may be assigned to active users.

Routes

The routes module (app/catalogs/roles/routes.py) defines HTTP endpoints:

List Roles

Endpoint: GET /roles/ From routes.py:13:
@roles_bp.route("/", methods=["GET"])
def list_roles():
    roles = RoleService.get_all()
    return render_template("roles/list.html", roles=roles)

Create Role

Endpoint: GET/POST /roles/create From routes.py:25:
@roles_bp.route("/create", methods=["GET", "POST"])
def create_role():
    form = RoleForm()
    
    if form.validate_on_submit():
        data = {"name": form.name.data}
        try:
            RoleService.create(data)
            flash("Rol creado exitosamente", "success")
            return redirect(url_for("roles.create_role"))
        except ConflictError as e:
            flash(e.message, "error")
    
    return render_template("roles/create.html", form=form)

Edit Role

Endpoint: GET/POST /roles/<id_role>/edit From routes.py:51:
@roles_bp.route("/<int:id_role>/edit", methods=["GET", "POST"])
def edit_role(id_role: int):
    try:
        role = RoleService.get_by_id(id_role)
    except NotFoundError as e:
        flash(e.message, "error")
        return redirect(url_for("roles.list_roles"))
    
    form = RoleForm()
    
    if form.validate_on_submit():
        data = {"name": form.name.data}
        try:
            RoleService.update(id_role, data)
            flash("Rol actualizado exitosamente", "success")
            return redirect(url_for("roles.list_roles"))
        except (ConflictError, ValidationError) as e:
            flash(e.message, "error")
    
    elif request.method == "GET":
        form.name.data = role.name
    
    return render_template("roles/edit.html", form=form, role=role)

Delete Role

Endpoint: POST /roles/<id_role>/delete From routes.py:87:
@roles_bp.route("/<int:id_role>/delete", methods=["POST"])
def delete_role(id_role: int):
    try:
        RoleService.delete(id_role)
        flash("Rol eliminado exitosamente", "success")
    except NotFoundError as e:
        flash(e.message, "error")
    
    return redirect(url_for("roles.list_roles"))

Form Definition

The RoleForm class in app/catalogs/roles/forms.py:10 defines the input form:
class RoleForm(FlaskForm):
    name = StringField(
        "Nombre",
        validators=[
            DataRequired(message="El nombre del rol es requerido"),
            Length(max=50, message="El nombre no puede exceder 50 caracteres"),
        ],
    )

Usage Examples

Common Role Definitions

Typical roles in a furniture manufacturing system:

Administrative

  • Administrator
  • Manager
  • Supervisor

Operational

  • Production Worker
  • Inventory Clerk
  • Quality Inspector

Sales

  • Sales Representative
  • Customer Service

Technical

  • System Operator
  • Viewer (Read-only)

Creating Roles

from app.catalogs.roles.services import RoleService

# Create standard roles
roles = [
    {"name": "Administrator"},
    {"name": "Production Manager"},
    {"name": "Inventory Manager"},
    {"name": "Production Worker"},
    {"name": "Viewer"}
]

for role_data in roles:
    try:
        result = RoleService.create(role_data)
        print(f"Created role: {result['name']}")
    except ConflictError:
        print(f"Role already exists: {role_data['name']}")

Querying Roles

# Get all active roles
roles = RoleService.get_all()
for role in roles:
    print(f"Role ID {role.id_role}: {role.name}")

# Get specific role
role = RoleService.get_by_id(1)
print(f"Role: {role.name}")

Best Practices

1

Use Clear Role Names

Choose descriptive, unambiguous names like “Production Manager” rather than “PM”
2

Plan Role Hierarchy

Consider creating roles that reflect organizational structure
3

Avoid Over-Segmentation

Start with broader roles and refine as needed
4

Document Permissions

Maintain documentation of what each role can access
  • Use Title Case: “Production Manager”, not “production manager”
  • Be specific: “Inventory Manager” vs. “Manager”
  • Avoid abbreviations: “Administrator” not “Admin”
  • Use singular: “Worker” not “Workers”
Roles are designed to integrate with future authentication systems:
# Future implementation
user = User.query.get(user_id)
if user.role.name == "Administrator":
    # Grant admin access
    pass

Security Considerations

Deleting Roles: Before deleting a role, ensure no users are currently assigned to it. Consider implementing a check:
# Future implementation
def delete(id_role: int) -> None:
    role = RoleService.get_by_id(id_role)
    
    # Check if role is in use
    if role.users.count() > 0:
        raise ConflictError("Cannot delete role with assigned users")
    
    role.active = False
    role.deleted_at = func.current_timestamp()
    db.session.commit()

Build docs developers (and LLMs) love