Exemplo n.º 1
0
    class ActivitySchema(colander.MappingSchema):
        """ Schema for CustomContent. """

        engagement_id = colander.SchemaNode(
            colander.Integer(),
            title=_(u"Engagement"),
            widget=deferred_engagement_select_widget)

        start_dt = colander.SchemaNode(
            colander.DateTime(
                default_tzinfo=FixedOffset(8, 0, 'Asia/Kuala_Lumpur')),
            title=_(u'Start'),
            widget=DateTimeInputWidget(time_options=(('format', 'h:i A'),
                                                     ('interval', 60))),
            default=deferred_default_dt)
        end_dt = colander.SchemaNode(
            colander.DateTime(
                default_tzinfo=FixedOffset(8, 0, 'Asia/Kuala_Lumpur')),
            title=_(u'End'),
            widget=DateTimeInputWidget(time_options=(('format', 'h:i A'),
                                                     ('interval', 60))),
            default=deferred_default_dt)

        summary = colander.SchemaNode(
            colander.String(),
            title=_('Activity Summary'),
            widget=RichTextWidget(),
            missing=u"",
        )

        issues = colander.SchemaNode(
            colander.String(),
            title=_('Issues Faced'),
            widget=RichTextWidget(),
            missing=u"",
        )

        tags = colander.SchemaNode(
            ObjectType(),
            title=_('Tags'),
            widget=deferred_tag_it_widget,
            missing=[],
        )
Exemplo n.º 2
0
class OptionalPendulumNodeUTC(SchemaNode):
    """
    Colander node containing an optional :class:`Pendulum` date/time, in which
    the date/time is assumed to be UTC.
    """
    @staticmethod
    def schema_type() -> SchemaType:
        return AllowNoneType(PendulumType(use_local_tz=False))

    default = None
    missing = None
    widget = DateTimeInputWidget(
        date_options=DEFAULT_WIDGET_DATE_OPTIONS_FOR_PENDULUM,
        time_options=DEFAULT_WIDGET_TIME_OPTIONS_FOR_PENDULUM,
    )
Exemplo n.º 3
0
class BlogEntrySchema(DocumentSchema):
    date = colander.SchemaNode(
        colander.DateTime(),
        title=_(u'Date'),
        description=_(u'Choose date of the blog entry. '
                      u'If you leave this empty the creation date is used.'),
        validator=colander.Range(
            min=datetime.datetime(2012,
                                  1,
                                  1,
                                  0,
                                  0,
                                  tzinfo=colander.iso8601.Utc()),
            min_err=_('${val} is earlier than earliest datetime ${min}')),
        widget=DateTimeInputWidget(),
        missing=deferred_date_missing,
    )
Exemplo n.º 4
0
class User(Base):
    __tablename__ = "user"
    __table_args__ = {"schema": _schema}
    __colanderalchemy_config__ = {"title": _("User"), "plural": _("Users")}
    __c2cgeoform_config__ = {"duplicate": True}
    item_type = Column("type",
                       String(10),
                       nullable=False,
                       info={"colanderalchemy": {
                           "widget": HiddenWidget()
                       }})
    __mapper_args__ = {
        "polymorphic_on": item_type,
        "polymorphic_identity": "user"
    }

    id = Column(Integer,
                primary_key=True,
                info={"colanderalchemy": {
                    "widget": HiddenWidget()
                }})
    username = Column(Unicode,
                      unique=True,
                      nullable=False,
                      info={"colanderalchemy": {
                          "title": _("Username")
                      }})
    _password = Column("password",
                       Unicode,
                       nullable=False,
                       info={"colanderalchemy": {
                           "exclude": True
                       }})
    temp_password = Column("temp_password",
                           Unicode,
                           nullable=True,
                           info={"colanderalchemy": {
                               "exclude": True
                           }})
    tech_data = Column(MutableDict.as_mutable(HSTORE),
                       info={"colanderalchemy": {
                           "exclude": True
                       }})
    email = Column(
        Unicode,
        nullable=False,
        info={"colanderalchemy": {
            "title": _("Email"),
            "validator": Email()
        }})
    is_password_changed = Column(Boolean,
                                 default=False,
                                 info={
                                     "colanderalchemy": {
                                         "title":
                                         _("The user changed his password")
                                     }
                                 })

    settings_role_id = Column(
        Integer,
        info={
            "colanderalchemy": {
                "title":
                _("Settings from role"),
                "description":
                "Only used for settings not for permissions",
                "widget":
                RelationSelect2Widget(Role,
                                      "id",
                                      "name",
                                      order_by="name",
                                      default_value=("", _("- Select -"))),
            }
        },
    )

    settings_role = relationship(
        Role,
        foreign_keys="User.settings_role_id",
        primaryjoin="Role.id==User.settings_role_id",
        info={
            "colanderalchemy": {
                "title": _("Settings role"),
                "exclude": True
            }
        },
    )

    roles = relationship(
        Role,
        secondary=user_role,
        secondaryjoin=Role.id == user_role.c.role_id,
        backref=backref("users",
                        order_by="User.username",
                        info={"colanderalchemy": {
                            "exclude": True
                        }}),
        info={"colanderalchemy": {
            "title": _("Roles"),
            "exclude": True
        }},
    )

    last_login = Column(
        DateTime(timezone=True),
        info={
            "colanderalchemy": {
                "title": _("Last login"),
                "missing": drop,
                "widget": DateTimeInputWidget(readonly=True),
            }
        },
    )

    expire_on = Column(
        DateTime(timezone=True),
        info={"colanderalchemy": {
            "title": _("Expiration date")
        }})

    deactivated = Column(Boolean,
                         default=False,
                         info={"colanderalchemy": {
                             "title": _("Deactivated")
                         }})

    def __init__(
        self,
        username: str = "",
        password: str = "",
        email: str = "",
        is_password_changed: bool = False,
        settings_role: Role = None,
        roles: List[Role] = None,
        expire_on: datetime = None,
        deactivated: bool = False,
    ) -> None:
        self.username = username
        self.password = password
        self.tech_data = {}
        self.email = email
        self.is_password_changed = is_password_changed
        if settings_role:
            self.settings_role = settings_role
        self.roles = roles or []
        self.expire_on = expire_on
        self.deactivated = deactivated

    @property
    def password(self) -> str:
        """returns password"""
        return self._password  # type: ignore

    @password.setter
    def password(self, password: str) -> None:
        """encrypts password on the fly."""
        self._password = self.__encrypt_password(password)

    def set_temp_password(self, password: str) -> None:
        """encrypts password on the fly."""
        self.temp_password = self.__encrypt_password(password)

    @staticmethod
    def __encrypt_password_legacy(password: str) -> str:
        """Hash the given password with SHA1."""
        return sha1(password.encode("utf8")).hexdigest()  # nosec

    @staticmethod
    def __encrypt_password(password: str) -> str:
        return crypt.crypt(password, crypt.METHOD_SHA512)

    def validate_password(self, passwd: str) -> bool:
        """Check the password against existing credentials.
        this method _MUST_ return a boolean.

        @param passwd: the password that was provided by the user to
        try and authenticate. This is the clear text version that we will
        need to match against the (possibly) encrypted one in the database.
        """
        if self._password.startswith("$"):
            # new encryption method
            if compare_hash(self._password,
                            crypt.crypt(passwd, self._password)):
                return True
        else:
            # legacy encryption method
            if compare_hash(self._password,
                            self.__encrypt_password_legacy(passwd)):
                # convert to the new encryption method
                self._password = self.__encrypt_password(passwd)
                return True

        if (self.temp_password is not None and self.temp_password != ""
                and compare_hash(self.temp_password,
                                 crypt.crypt(passwd, self.temp_password))):
            self._password = self.temp_password
            self.temp_password = None
            self.is_password_changed = False
            return True
        return False

    def expired(self) -> bool:
        return self.expire_on is not None and self.expire_on < datetime.now(
            pytz.utc)

    def update_last_login(self) -> None:
        self.last_login = datetime.now(pytz.utc)

    def __str__(self) -> str:
        return self.username or ""
Exemplo n.º 5
0
class User(Base):
    __tablename__ = 'user'
    __table_args__ = {'schema': _schema}
    __colanderalchemy_config__ = {'title': _('User'), 'plural': _('Users')}
    __c2cgeoform_config__ = {'duplicate': True}
    item_type = Column('type',
                       String(10),
                       nullable=False,
                       info={'colanderalchemy': {
                           'widget': HiddenWidget()
                       }})
    __mapper_args__ = {
        'polymorphic_on': item_type,
        'polymorphic_identity': 'user',
    }

    id = Column(Integer,
                primary_key=True,
                info={'colanderalchemy': {
                    'widget': HiddenWidget()
                }})
    username = Column(Unicode,
                      unique=True,
                      nullable=False,
                      info={'colanderalchemy': {
                          'title': _('Username')
                      }})
    _password = Column('password',
                       Unicode,
                       nullable=False,
                       info={'colanderalchemy': {
                           'exclude': True
                       }})
    temp_password = Column('temp_password',
                           Unicode,
                           nullable=True,
                           info={'colanderalchemy': {
                               'exclude': True
                           }})
    email = Column(Unicode,
                   nullable=False,
                   info={
                       'colanderalchemy': {
                           'title': _('Email'),
                           'validator': colander.Email()
                       }
                   })
    is_password_changed = Column(Boolean,
                                 default=False,
                                 info={'colanderalchemy': {
                                     'exclude': True
                                 }})
    role_name = Column(String,
                       info={
                           'colanderalchemy': {
                               'title':
                               _('Role'),
                               'widget':
                               deform_ext.RelationSelect2Widget(
                                   Role,
                                   'name',
                                   'name',
                                   order_by='name',
                                   default_value=('', _('- Select -')))
                           }
                       })
    _cached_role_name = None  # type: str
    _cached_role = None  # type: Optional[Role]

    last_login = Column(DateTime(timezone=True),
                        info={
                            'colanderalchemy': {
                                'title': _('Last login'),
                                'missing': colander.drop,
                                'widget': DateTimeInputWidget(readonly=True)
                            }
                        })

    expire_on = Column(
        DateTime(timezone=True),
        info={'colanderalchemy': {
            'title': _('Expiration date')
        }})

    deactivated = Column(Boolean,
                         default=False,
                         info={'colanderalchemy': {
                             'title': _('Deactivated')
                         }})

    @property
    def role(self) -> Optional[Role]:
        if self._cached_role_name == self.role_name:
            return self._cached_role

        if self.role_name is None or self.role_name == '':  # pragma: no cover
            self._cached_role_name = self.role_name
            self._cached_role = None
            return None

        result = self._sa_instance_state.session.query(Role).filter(
            Role.name == self.role_name).all()
        if len(result) == 0:  # pragma: no cover
            self._cached_role = None
        else:
            self._cached_role = result[0]

        self._cached_role_name = self.role_name
        return self._cached_role

    def __init__(self,
                 username: str = '',
                 password: str = '',
                 email: str = '',
                 is_password_changed: bool = False,
                 role: Role = None,
                 expire_on: datetime = None,
                 deactivated: bool = False) -> None:
        self.username = username
        self.password = password
        self.email = email
        self.is_password_changed = is_password_changed
        if role is not None:
            self.role_name = role.name
        self.expire_on = expire_on
        self.deactivated = deactivated

    @property
    def password(self) -> str:
        """returns password"""
        return self._password  # pragma: no cover

    @password.setter
    def password(self, password: str) -> None:
        """encrypts password on the fly."""
        self._password = self.__encrypt_password(password)

    def set_temp_password(self, password: str) -> None:
        """encrypts password on the fly."""
        self.temp_password = self.__encrypt_password(password)

    @staticmethod
    def __encrypt_password(password: str) -> str:
        """Hash the given password with SHA1."""
        return sha1(password.encode('utf8')).hexdigest()

    def validate_password(self, passwd: str) -> bool:
        """Check the password against existing credentials.
        this method _MUST_ return a boolean.

        @param passwd: the password that was provided by the user to
        try and authenticate. This is the clear text version that we will
        need to match against the (possibly) encrypted one in the database.
        @type password: string
        """
        if self._password == self.__encrypt_password(passwd):
            return True
        if \
                self.temp_password is not None and \
                self.temp_password != '' and \
                self.temp_password == self.__encrypt_password(passwd):
            self._password = self.temp_password
            self.temp_password = None
            self.is_password_changed = False
            return True
        return False

    def expired(self) -> bool:
        return self.expire_on is not None and self.expire_on < datetime.now(
            pytz.utc)

    def update_last_login(self) -> None:
        self.last_login = datetime.now(pytz.utc)

    def __unicode__(self) -> str:
        return self.username or ''  # pragma: no cover
Exemplo n.º 6
0
class User(Base):
    __tablename__ = 'user'
    __table_args__ = {'schema': _schema}
    __colanderalchemy_config__ = {
        'title': _('User'),
        'plural': _('Users')
    }
    __c2cgeoform_config__ = {
        'duplicate': True
    }
    item_type = Column('type', String(10), nullable=False, info={
        'colanderalchemy': {
            'widget': HiddenWidget()
        }
    })
    __mapper_args__ = {
        'polymorphic_on': item_type,
        'polymorphic_identity': 'user',
    }

    id = Column(Integer, primary_key=True, info={
        'colanderalchemy': {
            'widget': HiddenWidget()
        }
    })
    username = Column(Unicode, unique=True, nullable=False, info={
        'colanderalchemy': {
            'title': _('Username')
        }
    })
    _password = Column('password', Unicode, nullable=False,
                       info={'colanderalchemy': {'exclude': True}})
    temp_password = Column('temp_password', Unicode, nullable=True,
                           info={'colanderalchemy': {'exclude': True}})
    email = Column(Unicode, nullable=False, info={
        'colanderalchemy': {
            'title': _('Email'),
            'validator': colander.Email()
        }
    })
    is_password_changed = Column(Boolean, default=False,
                                 info={'colanderalchemy': {'exclude': True}})

    settings_role_id = Column(Integer, info={
        'colanderalchemy': {
            'title': _('Settings from role'),
            'description': 'Only used for settings not for permissions',
            'widget': deform_ext.RelationSelect2Widget(
                Role,
                'id',
                'name',
                order_by='name',
                default_value=('', _('- Select -'))
            )
        }
    })

    settings_role = relationship(
        Role,
        foreign_keys='User.settings_role_id',
        primaryjoin='Role.id==User.settings_role_id',
        info={
            'colanderalchemy': {
                'title': _('Settings role'),
                'exclude': True
            }
        })

    roles = relationship(
        Role,
        secondary=user_role,
        secondaryjoin=Role.id == user_role.c.role_id,
        backref=backref('users', info={'colanderalchemy': {'exclude': True}}),
        info={
            'colanderalchemy': {
                'title': _('Roles'),
                'exclude': True
            }
        }
    )

    last_login = Column(DateTime(timezone=True), info={
        'colanderalchemy': {
            'title': _('Last login'),
            'missing': colander.drop,
            'widget': DateTimeInputWidget(readonly=True)
        }
    })

    expire_on = Column(DateTime(timezone=True), info={
        'colanderalchemy': {
            'title': _('Expiration date')
        }
    })

    deactivated = Column(Boolean, default=False, info={
        'colanderalchemy': {
            'title': _('Deactivated')
        }
    })

    def __init__(
        self, username: str = '', password: str = '', email: str = '', is_password_changed: bool = False,
        settings_role: Role = None, roles: List[Role] = [], expire_on: datetime = None,
        deactivated: bool = False
    ) -> None:
        self.username = username
        self.password = password
        self.email = email
        self.is_password_changed = is_password_changed
        if settings_role:
            self.settings_role = settings_role
        self.roles = roles
        self.expire_on = expire_on
        self.deactivated = deactivated

    @property
    def password(self) -> str:
        """returns password"""
        return self._password  # pragma: no cover

    @password.setter
    def password(self, password: str) -> None:
        """encrypts password on the fly."""
        self._password = self.__encrypt_password(password)

    def set_temp_password(self, password: str) -> None:
        """encrypts password on the fly."""
        self.temp_password = self.__encrypt_password(password)

    @staticmethod
    def __encrypt_password_legacy(password: str) -> str:
        """Hash the given password with SHA1."""
        return sha1(password.encode('utf8')).hexdigest()

    @staticmethod
    def __encrypt_password(password: str) -> str:
        # TODO: remove pylint disable when https://github.com/PyCQA/pylint/issues/3047 is fixed
        return crypt.crypt(password, crypt.METHOD_SHA512)  # pylint: disable=E1101

    def validate_password(self, passwd: str) -> bool:
        """Check the password against existing credentials.
        this method _MUST_ return a boolean.

        @param passwd: the password that was provided by the user to
        try and authenticate. This is the clear text version that we will
        need to match against the (possibly) encrypted one in the database.
        """
        if self._password.startswith('$'):
            # new encryption method
            if compare_hash(self._password, crypt.crypt(passwd, self._password)):
                return True
        else:
            # legacy encryption method
            if compare_hash(self._password, self.__encrypt_password_legacy(passwd)):
                # convert to the new encryption method
                self._password = self.__encrypt_password(passwd)
                return True

        if \
                self.temp_password is not None and \
                self.temp_password != '' and \
                compare_hash(self.temp_password, crypt.crypt(passwd, self.temp_password)):
            self._password = self.temp_password
            self.temp_password = None
            self.is_password_changed = False
            return True
        return False

    def expired(self) -> bool:
        return self.expire_on is not None and self.expire_on < datetime.now(pytz.utc)

    def update_last_login(self) -> None:
        self.last_login = datetime.now(pytz.utc)

    def __unicode__(self) -> str:
        return self.username or ''  # pragma: no cover
Exemplo n.º 7
0
class SoftwareProjectSchema(DocumentSchema):

    choices = (
        ('', '- Select -'),
        ('use_entered', 'Used entered description (can be blank)'),
        ('use_pypi_summary', 'Use summary in PyPI data'),
        ('use_pypi_description', 'Use description in PyPI data'),
        ('use_github_description', 'Use description in GitHub data'),
        ('use_bitbucket_description', 'Use description in Bitbucket data'))
    desc_handling_choice = colander.SchemaNode(
        colander.String(),
        default='use_entered',
        missing='use_entered',
        title=_(u'Description Handling'),
        widget=SelectWidget(values=choices))

    pypi_url = colander.SchemaNode(
        colander.String(),
        title=_(u'PyPI URL'),
        description=_(u'Enter unless doing a manual entry.'),
        missing=_(''),)

    choices = (
        ('', '- Select -'),
        ('use_entered', 'Use entered date'),
        ('use_pypi_date', 'Use date in PyPI data'),
        ('use_github_date', 'Use date in GitHub data'),
        ('use_bitbucket_date', 'Use date in Bitbucket data'),
        ('use_now', 'Use current date and time'))
    date_handling_choice = colander.SchemaNode(
        colander.String(),
        default='use_pypi_date',
        title=_(u'Date Handling'),
        widget=SelectWidget(values=choices))
    date = colander.SchemaNode(
        colander.DateTime(),
        title=_(u'Date'),
        description=_(u'Enter date only if date handling = use_entered.'),
        validator=colander.Range(
            min=datetime.datetime(2012, 1, 1, 0, 0,
                                  tzinfo=colander.iso8601.Utc()),
            min_err=_('${val} is too early; min date now is ${min}')),
        widget=DateTimeInputWidget(),
        missing=deferred_date_missing,)

    home_page_url = colander.SchemaNode(
        colander.String(),
        title=_(u'Home Page URL'),
        description=_(u'Leave blank usually, and the URL will be fetched.'),
        missing=_(''),)
    overwrite_home_page_url = colander.SchemaNode(
        colander.Boolean(),
        description='Overwrite home page from PyPI',
        default=True,
        missing=True,
        widget=CheckboxWidget(),
        title='')

    docs_url = colander.SchemaNode(
        colander.String(),
        title=_(u'Docs URL'),
        description=_(u'Leave blank usually, and the URL will be fetched.'),
        missing=_(''),)
    overwrite_docs_url = colander.SchemaNode(
        colander.Boolean(),
        description='Overwrite docs URL from PyPI',
        default=True,
        missing=True,
        widget=CheckboxWidget(),
        title='')

    package_url = colander.SchemaNode(
        colander.String(),
        title=_(u'Download URL'),
        description=_(u'Leave blank usually, and the URL will be fetched.'),
        missing=_(''),)
    overwrite_package_url = colander.SchemaNode(
        colander.Boolean(),
        description='Overwrite package URL from PyPI',
        default=True,
        missing=True,
        widget=CheckboxWidget(),
        title='')

    bugtrack_url = colander.SchemaNode(
        colander.String(),
        title=_(u'Bugtracker URL'),
        description=_(u'Leave blank usually, and the URL will be fetched.'),
        missing=_(''),)
    overwrite_bugtrack_url = colander.SchemaNode(
        colander.Boolean(),
        description='Overwrite bugtracker URL from PyPI',
        default=True,
        missing=True,
        widget=CheckboxWidget(),
        title='')

    github_owner = colander.SchemaNode(
        colander.String(),
        title=_(u'GitHub Owner'),
        description=_(u'Name of the owner on GitHub for the project repo.'),
        missing=_(''),)

    github_repo = colander.SchemaNode(
        colander.String(),
        title=_(u'GitHub Repo'),
        description=_(u'Name of the repo on GitHub for the project.'),
        missing=_(''),)

    bitbucket_owner = colander.SchemaNode(
        colander.String(),
        title=_(u'Bitbucket Owner'),
        description=_(u'Name of the owner on Bitbucket for the project repo.'),
        missing=_(''),)

    bitbucket_repo = colander.SchemaNode(
        colander.String(),
        title=_(u'Bitbucket Repo'),
        description=_(u'Name of the repo on Bitbucket for the project.'),
        missing=_(''),)
Exemplo n.º 8
0
    def __init__(self,
                 title,
                 name,
                 round=1,
                 missing='',
                 widget=None,
                 mapinit=None,
                 processing_form=None,
                 desc=None,
                 table_reduce=False,
                 rank=0,
                 required=False,
                 missing_msg='champ requis',
                 primary_key=False):

        # Form display
        self.title = title
        self.name = name
        self.required = required
        self.missing_msg = missing_msg
        self.type = str
        self.desc = desc
        self.round = round

        if widget is None:
            keyw = {
                'key_date': '{}-date'.format(self.name),
                'key_time': '{}-time'.format(self.name)
            }
            self.widget = DateTimeInputWidget(**keyw)
            self.mapinit = {'date': None, 'time': None}
            processing_form = lambda x: pd.Timestamp('{} {}'.format(
                x['date'], x['time']))

        else:
            self.widget = widget
            self.mapinit = mapinit

        # Db column
        self.dbcol_ = (name, Colsql(Strsql(250), primary_key=primary_key))

        # Table display
        self.table_reduce, self.rank, = table_reduce, rank

        self.processing_form = {
            'form': processing_form,
            'db': lambda x: pd.Timestamp(x)
        }
        self.processing_db = {
            'upload': lambda x: str(pd.Timestamp(x)),
            'download': lambda x: str(x)
        }

        self.sn = sn(DateTime(),
                     title=self.title,
                     name=name,
                     widget=self.widget,
                     description=desc,
                     required=self.required,
                     missing_msg=self.missing_msg)

        if not required:
            self.sn.missing = missing
Exemplo n.º 9
0
 def widget(self, kw: dict):
     widget = DateTimeInputWidget()
     schema = widget._pstruct_schema
     schema['date_submit'].missing = null  # Fix readonly template bug
     schema['time_submit'].missing = null
     return widget
Exemplo n.º 10
0
class User(Base):  # type: ignore
    """The user table representation."""

    __tablename__ = "user"
    __table_args__ = {"schema": _schema}
    __colanderalchemy_config__ = {
        "title":
        _("User"),
        "plural":
        _("Users"),
        "description":
        Literal(
            _("""
            <div class="help-block">
                <p>Each user may have from 1 to n roles, but each user has a default role from
                    which are taken some settings. The default role (defined through the
                    "Settings from role" selection) has an influence on the role extent and on some
                    functionalities regarding their configuration.</p>

                <p>Role extents for users can only be set in one role, because the application
                    is currently not able to check multiple extents for one user, thus it is the
                    default role which defines this unique extent.</p>

                <p>Any functionality specified as <b>single</b> can be defined only once per user.
                    Hence, these functionalities have to be defined in the default role.</p>

                <p>By default, functionalities are not specified as <b>single</b>. Currently, the
                    following functionalities are of <b>single</b> type:</p>

                <ul>
                    <li><code>default_basemap</code></li>
                    <li><code>default_theme</code></li>
                    <li><code>preset_layer_filter</code></li>
                    <li><code>open_panel</code></li>
                </ul>

                <p>Any other functionality (with <b>single</b> not set or set to <code>false</code>) can
                    be defined in any role linked to the user.</p>
                <hr>
            </div>
                """)),
    }
    __c2cgeoform_config__ = {"duplicate": True}
    item_type = Column("type",
                       String(10),
                       nullable=False,
                       info={"colanderalchemy": {
                           "widget": HiddenWidget()
                       }})
    __mapper_args__ = {
        "polymorphic_on": item_type,
        "polymorphic_identity": "user"
    }

    id = Column(Integer,
                primary_key=True,
                info={"colanderalchemy": {
                    "widget": HiddenWidget()
                }})
    username = Column(
        Unicode,
        unique=True,
        nullable=False,
        info={
            "colanderalchemy": {
                "title": _("Username"),
                "description":
                _("Name used for authentication (must be unique)."),
            }
        },
    )
    _password = Column("password",
                       Unicode,
                       nullable=False,
                       info={"colanderalchemy": {
                           "exclude": True
                       }})
    temp_password = Column("temp_password",
                           Unicode,
                           nullable=True,
                           info={"colanderalchemy": {
                               "exclude": True
                           }})
    tech_data = Column(MutableDict.as_mutable(HSTORE),
                       info={"colanderalchemy": {
                           "exclude": True
                       }})
    email = Column(
        Unicode,
        nullable=False,
        info={
            "colanderalchemy": {
                "title":
                _("Email"),
                "description":
                _("Used to send emails to the user, for example in case of password recovery."
                  ),
                "validator":
                Email(),
            }
        },
    )
    is_password_changed = Column(
        Boolean,
        default=False,
        info={
            "colanderalchemy": {
                "title": _("The user changed his password"),
                "description":
                _("Indicates if user has changed his password."),
            }
        },
    )

    settings_role_id = Column(
        Integer,
        info={
            "colanderalchemy": {
                "title":
                _("Settings from role"),
                "description":
                _("Used to get some settings for the user (not for permissions)."
                  ),
                "widget":
                RelationSelect2Widget(Role,
                                      "id",
                                      "name",
                                      order_by="name",
                                      default_value=("", _("- Select -"))),
            }
        },
    )

    settings_role = relationship(
        Role,
        foreign_keys="User.settings_role_id",
        primaryjoin="Role.id==User.settings_role_id",
        info={
            "colanderalchemy": {
                "title": _("Settings role"),
                "exclude": True
            }
        },
    )

    roles = relationship(
        Role,
        secondary=user_role,
        secondaryjoin=Role.id == user_role.c.role_id,
        backref=backref(
            "users",
            order_by="User.username",
            info={
                "colanderalchemy": {
                    "title": _("Users"),
                    "description": _("Users granted with this role."),
                    "exclude": True,
                }
            },
        ),
        info={
            "colanderalchemy": {
                "title": _("Roles"),
                "description": _("Roles granted to the user."),
                "exclude": True,
            }
        },
    )

    last_login = Column(
        DateTime(timezone=True),
        info={
            "colanderalchemy": {
                "title": _("Last login"),
                "description": _("Date of the user's last login."),
                "missing": drop,
                "widget": DateTimeInputWidget(readonly=True),
            }
        },
    )

    expire_on = Column(
        DateTime(timezone=True),
        info={
            "colanderalchemy": {
                "title":
                _("Expiration date"),
                "description":
                _("After this date the user will not be able to login anymore."
                  ),
            }
        },
    )

    deactivated = Column(
        Boolean,
        default=False,
        info={
            "colanderalchemy": {
                "title":
                _("Deactivated"),
                "description":
                _("Deactivate a user without removing it completely."),
            }
        },
    )

    def __init__(  # nosec
        self,
        username: str = "",
        password: str = "",
        email: str = "",
        is_password_changed: bool = False,
        settings_role: Optional[Role] = None,
        roles: Optional[List[Role]] = None,
        expire_on: Optional[datetime] = None,
        deactivated: bool = False,
    ) -> None:
        self.username = username
        self.password = password
        self.tech_data = {}
        self.email = email
        self.is_password_changed = is_password_changed
        if settings_role:
            self.settings_role = settings_role
        self.roles = roles or []
        self.expire_on = expire_on
        self.deactivated = deactivated

    @property
    def password(self) -> str:
        """Get the password."""
        return self._password  # type: ignore

    @password.setter
    def password(self, password: str) -> None:
        """Encrypt password on the fly."""
        self._password = self.__encrypt_password(password)

    def set_temp_password(self, password: str) -> None:
        """Encrypt password on the fly."""
        self.temp_password = self.__encrypt_password(password)

    @staticmethod
    def __encrypt_password_legacy(password: str) -> str:
        """Hash the given password with SHA1."""
        return sha1(password.encode("utf8")).hexdigest()  # nosec

    @staticmethod
    def __encrypt_password(password: str) -> str:
        return crypt.crypt(password, crypt.METHOD_SHA512)

    def validate_password(self, passwd: str) -> bool:
        """
        Check the password against existing credentials. this method _MUST_ return a boolean.

        @param passwd: the password that was provided by the user to
        try and authenticate. This is the clear text version that we will
        need to match against the (possibly) encrypted one in the database.
        """
        if self._password.startswith("$"):
            # new encryption method
            if compare_hash(self._password,
                            crypt.crypt(passwd, self._password)):
                return True
        else:
            # legacy encryption method
            if compare_hash(self._password,
                            self.__encrypt_password_legacy(passwd)):
                # convert to the new encryption method
                self._password = self.__encrypt_password(passwd)
                return True

        if (self.temp_password is not None
                and self.temp_password != ""  # nosec
                and compare_hash(self.temp_password,
                                 crypt.crypt(passwd, self.temp_password))):
            self._password = self.temp_password
            self.temp_password = None
            self.is_password_changed = False
            return True
        return False

    def expired(self) -> bool:
        return self.expire_on is not None and self.expire_on < datetime.now(
            pytz.utc)

    def update_last_login(self) -> None:
        self.last_login = datetime.now(pytz.utc)

    def __str__(self) -> str:
        return self.username or ""