"""SQLAlchemy-powered model definition for groups."""
# pylint: disable = super-on-old-class
from lxml import etree
from sqlalchemy import Table, Column, ForeignKey, types
from sqlalchemy.orm import relationship
from ..lib.i18n import _
from ..lib.utils import normalize_name, normalize_spaces, make_id, wrap
from . import Base, DBSession, ID_LEN, LABEL_LEN, DESCRIPTION_LEN
from .users import PERM_SCOPES, USER_PERMS, User
GROUP_USER = Table(
'group_user', Base.metadata,
Column(
'group_id', types.String(ID_LEN),
ForeignKey('group.group_id', ondelete='CASCADE'), primary_key=True),
Column(
'user_id', types.Integer,
ForeignKey('user.user_id', ondelete='CASCADE'), primary_key=True),
mysql_engine='InnoDB')
XML_NS = '{http://www.w3.org/XML/1998/namespace}'
# =============================================================================
[docs]class Group(Base):
"""SQLAlchemy-powered group model."""
__tablename__ = 'group'
__table_args__ = {'mysql_engine': 'InnoDB'}
group_id = Column(types.String(ID_LEN), primary_key=True)
label = Column(types.String(LABEL_LEN), unique=True, nullable=False)
description = Column(types.String(DESCRIPTION_LEN))
perms = relationship('GroupPerm', cascade='all, delete')
users = relationship(
'User', secondary=GROUP_USER, backref='groups', cascade='all, delete')
# -------------------------------------------------------------------------
def __init__(self, group_id, label, description=None):
"""Constructor method."""
super(Group, self).__init__()
self.group_id = make_id(group_id, 'token', ID_LEN)
self.label = normalize_spaces(label, LABEL_LEN)
self.description = normalize_spaces(description, DESCRIPTION_LEN)
# -------------------------------------------------------------------------
[docs] @classmethod
def load(cls, group_elt, error_if_exists=True):
"""Load a group from a XML element.
:param group_elt: (:class:`lxml.etree.Element` instance)
Group XML element.
:param error_if_exists: (boolean, default=True)
It returns an error if group already exists.
:return: (:class:`pyramid.i18n.TranslationString` or ``None``)
Error message or ``None``.
"""
# Check if already exists
group_id = make_id(group_elt.get('%sid' % XML_NS), 'token', ID_LEN)
label = normalize_spaces(group_elt.findtext('label'), LABEL_LEN)
group = DBSession.query(cls).filter_by(group_id=group_id).first()
if group is None:
group = DBSession.query(cls).filter_by(label=label).first()
if group is not None:
if error_if_exists:
return _('Group "${l} (${i})" already exists.',
{'i': group_id, 'l': label})
return None
# Create group
group = cls(group_id, label, group_elt.findtext('description'))
DBSession.add(group)
DBSession.commit()
# Add permissions
done = []
for item in group_elt.findall('permissions/permission'):
if item.get('scope') not in done:
group.perms.append(GroupPerm(
group.group_id, item.get('scope'), item.text))
done.append(item.get('scope'))
# Add members
done = []
for item in group_elt.findall('members/member'):
login = normalize_name(item.text)[0:ID_LEN]
if login not in done:
user = DBSession.query(User).filter_by(login=login).first()
if user is not None:
group.users.append(user)
done.append(login)
DBSession.commit()
return None
# -------------------------------------------------------------------------
[docs] def xml(self):
"""Serialize a group to a XML representation.
:return: (:class:`lxml.etree.Element`)
"""
group_elt = etree.Element('group')
group_elt.set('%sid' % XML_NS, self.group_id)
etree.SubElement(group_elt, 'label').text = self.label
if self.description:
etree.SubElement(group_elt, 'description').text = \
wrap(self.description, indent=8)
# Permissions
if self.perms:
elt = etree.SubElement(group_elt, 'permissions')
for perm in self.perms:
etree.SubElement(
elt, 'permission', scope=perm.scope).text = perm.level
# Members
if self.users:
members_elt = etree.SubElement(group_elt, 'members')
for user in self.users:
user = DBSession.query(User.login).filter_by(
user_id=user.user_id).first()
elt = etree.SubElement(members_elt, 'member').text = user[0]
return group_elt
# =============================================================================
[docs]class GroupPerm(Base):
"""SQLAlchemy-powered group permission class."""
# pylint: disable = too-few-public-methods
__tablename__ = 'group_perm'
__table_args__ = {'mysql_engine': 'InnoDB'}
group_id = Column(
types.String(ID_LEN),
ForeignKey('group.group_id', ondelete='CASCADE'), primary_key=True)
scope = Column(
types.Enum(*PERM_SCOPES.keys(), name='perm_scope_enum'),
primary_key=True)
level = Column(types.Enum(*USER_PERMS.keys(), name='grp_perms_enum'))
# -------------------------------------------------------------------------
def __init__(self, group_id, scope, level):
"""Constructor method."""
super(GroupPerm, self).__init__()
self.group_id = make_id(group_id, 'token', ID_LEN)
self.scope = scope
self.level = level