Esempio n. 1
0
class Confirm(db.Model):
    """
    Holds the data for a confirm action.

    :param action: The key of the wanted action.
    :param expires: Defines when the confirm should expire. This is only
                    accurate to one day. This may either be a datetime or date,
                    a timedelta or an integer, which is then considered the
                    time until expiration in days. If the link is called after
                    it is expired, the user is rejected.
    :param data: Data which is passed to the registered function when the link
                 is accessed. It is saved pickled, so make sure your object is
                 pickleable.
    """

    __tablename__ = 'core_confirm'
    __mapper_args__ = {
        'extension': ConfirmMapperExtension(),
    }
    query = db.session.query_property(ConfirmQuery)

    _key_length = 32
    _key_chars = string.ascii_letters + string.digits

    id = db.Column(db.Integer, primary_key=True)
    key = db.Column(db.Unicode(32), unique=True)
    action = db.Column(db.Unicode(40))
    data = db.Column(db.PickleType)
    expires = db.Column(db.Date)

    def __init__(self, action, data, expires):
        if action not in CONFIRM_ACTIONS:
            raise KeyError('Action %r is not registered.' % action)

        if isinstance(expires, (datetime, date)):
            self.expires = expires
        elif isinstance(expires, timedelta):
            self.expires = date.today() + expires
        else:
            self.expires = date.today() + timedelta(days=expires)

        self.action = action
        self.data = data

    def __repr__(self):
        return '<Confirm %s %s>' % (self.action, getattr(self, 'key', ''))

    @classmethod
    def _make_key(cls):
        return u''.join(
            random.choice(cls._key_chars) for _ in xrange(cls._key_length))

    @property
    def url(self):
        return href('portal/confirm', key=self.key, _external=True)

    @property
    def is_expired(self):
        return self.expires < date.today()
Esempio n. 2
0
class Group(db.Model):
    __tablename__ = 'core_group'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode(40), unique=True)

    parents = db.relationship(
        'Group',
        secondary=group_group,
        backref=db.backref('children', collection_class=set, lazy='subquery'),
        primaryjoin=(id == group_group.c.group_id),
        secondaryjoin=(group_group.c.parent_id == id),
        foreign_keys=[group_group.c.group_id, group_group.c.parent_id],
        collection_class=set,
        cascade='all')

    def get_parents(self):
        if not self.parents:
            return
        parents = []
        for group in self.parents:
            parents.append(group)
            if group.parents:
                parents.extend(group.get_parents())
        return parents

    @cached_property
    def grant_parent(self):
        return self.get_parents()[-1]

    def get_url_values(self, action='view'):
        values = {
            'view': 'portal/group',
        }
        return values[action], {'name': self.name}
Esempio n. 3
0
class UserProfile(db.Model):
    """The profile for an user.

    The user profile contains various information about the user
    e.g the real name, his website and various contact information.

    This model provides basic fields but is extendable to provide much
    more information if required.

    """
    __tablename__ = 'core_userprofile'

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.ForeignKey(User.id))
    user = db.relationship(User,
                           backref=db.backref('profile',
                                              uselist=False,
                                              lazy='joined',
                                              innerjoin=True))

    # profile properties.  We implement them here to get a better documented
    # and tested model structure.  Feel free to advance the attributes!

    # personal attributes
    real_name = db.Column(db.Unicode(200))
    website = db.Column(db.Unicode(200))
    location = db.Column(db.Unicode(200))
    interests = db.Column(db.Unicode(200))
    occupation = db.Column(db.Unicode(200))
    signature = db.Column(db.Text)

    # communication channels
    jabber = db.Column(db.Unicode(200))
    skype = db.Column(db.Unicode(200))

    def get_url_values(self, action='view'):
        values = {
            'view': ('portal/profile', {
                'username': self.user.username
            }),
            'edit': ('portal/profile_edit', {}),
        }

        return values[action][0], values[action][1]

    def clear(self):
        self.real_name = u''
        self.website = u''
        self.location = u''
        self.intersts = u''
        self.occupation = u''
        self.signature = u''
Esempio n. 4
0
class Tag(db.Model, SerializableObject):
    __tablename__ = 'core_tag'
    __mapper_args__ = {'extension': db.SlugGenerator('slug', 'name')}

    query = db.session.query_property(TagQuery)

    #: serializer attributes
    object_type = 'core.tag'
    public_fields = ('id', 'name', 'slug')

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode(20), nullable=False, index=True)
    slug = db.Column(db.Unicode(20), nullable=False, unique=True)
    #: Number of items tagged
    tagged = db.Column(db.Integer, nullable=False, default=0)
    #: Flag if the tag is public or internal.  If a tag is not public
    #: it's probably used by internal functions to ensure special
    #: permission flags.  It's not shown in any public interface (e.g tag-cloud)
    public = db.Column(db.Boolean, nullable=False, default=True)

    def __unicode__(self):
        return self.name

    @db.validates('name')
    def validate_tag(self, key, tag):
        if not tag_re.match(tag):
            raise ValueError(u'Invalid tag name %s' % tag)
        return tag

    def get_url_values(self, action='view'):
        values = {
            'view': 'portal/tag',
            'edit': 'portal/tag_edit',
            'delete': 'portal/tag_delete',
        }
        return values[action], {'slug': self.slug}
Esempio n. 5
0
class User(db.Model, SerializableObject):
    __tablename__ = 'core_user'
    __mapper_args__ = {'extension': AutomaticUserProfile()}

    query = db.session.query_property(UserQuery)

    # serializer properties
    object_type = 'inyoka.user'
    public_fields = ('id', 'username', 'pw_hash', 'status', 'real_name')

    #: The internal ID of the user.  Even if an external Auth system is
    #: used, we're storing the users a second time internal so that we
    #: can easilier work with relations.
    id = db.Column(db.Integer, primary_key=True)

    #: The username of the user.  For external auth systems it makes a
    #: lot of sense to allow the user to chose a name on first login.
    username = db.Column(db.Unicode(40), unique=True)

    #: The email of the user.  If an external auth system is used, the
    #: login code should update that information automatically on login
    email = db.Column(db.Unicode(250), index=True, unique=True)

    #: The password hash.  This might not be used by every auth system.
    #: the OpenID auth for example does not use it at all.  But also
    #: external auth systems might not store the password here.
    pw_hash = db.Column(db.Unicode(60))

    #: When the user registered himself
    date_joined = db.Column(db.DateTime,
                            nullable=False,
                            default=datetime.utcnow)

    #: When the user recently joined the webpage
    last_login = db.Column(db.DateTime,
                           nullable=False,
                           default=datetime.utcnow)

    #: The status of the user. 0: inactive, 1: normal, 2: banned, 3: deleted
    _status = db.Column('status', db.Integer, nullable=False, default=0)

    groups = db.relationship(Group,
                             secondary=user_group,
                             backref=db.backref('users', lazy='dynamic'))

    def __init__(self, *args, **kwargs):
        # we just ignore applied pw_hashes so we calculate those ourselfs
        kwargs.pop('pw_hash', None)
        self.set_password(kwargs.pop('password', u''))
        db.Model.__init__(self, *args, **kwargs)

    def set_password(self, raw_password):
        """Set a new sha1 generated password hash"""
        salt = u'%05x' % random.getrandbits(20)
        hsh = get_hexdigest(salt, raw_password)
        self.pw_hash = u'%s$%s' % (salt, hsh)

    def check_password(self, raw_password):
        """
        Returns a boolean of whether the raw_password was correct.
        """
        salt, hsh = self.pw_hash.split(u'$')
        return hsh == get_hexdigest(salt, raw_password)

    def _set_status(self, status):
        self._status = USER_STATUS_MAP[status]

    def _get_status(self):
        return USER_STATUS_MAP[self._status]

    status = db.synonym('_status',
                        descriptor=property(_get_status, _set_status))

    is_active = property(lambda self: self.status == u'normal')

    @property
    def display_name(self):
        return self.username

    @property
    def is_anonymous(self):
        name = ctx.cfg['anonymous_name']
        return self.username == name

    def subscribed(self, type, subject_id=None):
        return subscribed(type, self, subject_id)

    def get_url_values(self, action='profile'):
        if action == 'profile':
            return 'portal/profile', {'username': self.username}

    def deactivate(self):
        """Deactivates the user for ever. Use with care!!

        This method does not commit the changes to the database.
        """
        self._status = 3
        self.profile.clear()

    def __repr__(self):
        i = '#%d' % self.id if self.id else '[no id]'
        return '<User %s %r>' % (i, self.username)

    def __unicode__(self):
        return self.display_name
Esempio n. 6
0
class Subscription(db.Model):
    __tablename__ = 'core_subscription'

    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.ForeignKey(User.id), nullable=False)
    type_name = db.Column('type', db.Unicode(20), nullable=False)
    count = db.Column(db.Integer, nullable=False, default=0)
    subject_id = db.Column(db.Integer)
    first_unread_object_id = db.Column(db.Integer)

    user = db.relationship(User)

    unread_object_ids = association_proxy(
        '_unread_object_ids',
        'object_id',
        creator=_create_subscriptionunreadobjects_by_object_id)

    @staticmethod
    def new(object, action):
        """
        Sends notifications for a new object, saves it as `first_unread_object`
        or in `unread_objects` (depending on the type's mode) and increments
        the unread count.

        Must be called whenever a new object is created or moved to the scope
        of a SubscriptionType (e.g. when a topic is moved to another forum).

        :param object: The added object.
        :param action: The action which lead to the creation of the object.
                       It is used to select the SubscriptionTypes that need to
                       be considered and the action's :meth:`notify` method is
                       called.
        """
        #XXX: if `object` is removed before the task is run, subscriptions are
        #     marked as notified though the user didn't receive a notification

        if isinstance(action, basestring):
            action = SubscriptionAction.by_name(action)

        #: store ids of Subscription objects for which we need to notify
        subscriptions = []

        for t in SubscriptionType.by_action(action):
            subjects = t.get_subjects(object)
            if not subjects:
                continue
            subject_ids = [getattr(s, 'id', s) for s in subjects]

            base_cond = (Subscription.type_name == t.name)
            if t.subject_type is not None:
                base_cond &= Subscription.subject_id.in_(subject_ids)

            if t.mode == 'sequent':
                #: increment unread count where there already are unread objects
                cond = base_cond & (Subscription.count > 0)
                q = db.update(Subscription.__table__, cond,
                              {'count': Subscription.count + 1})
                db.session.execute(q)
                db.session.commit()

                #: then set the first unread object and notify the user
                #: where there were no new objects since the last visit
                cond = base_cond & (Subscription.count == 0)
                for s in Subscription.query.filter(cond):
                    subscriptions.append(s.id)
                    s.first_unread_object_id = object.id
                    s.count = 1
                db.session.commit()

            if t.mode == 'multiple':
                for s in Subscription.query.filter(base_cond):
                    subscriptions.append(s.id)
                    s.unread_object_ids.add(object.id)
                    s.count = len(s.unread_object_ids)
                db.session.commit()

        from inyoka.core import tasks
        tasks.send_notifications(object, action.name, subscriptions)

    @staticmethod
    def accessed(user, **kwargs):
        """
        Mark subscriptions as read.
        This means to remove objects from the `unread_objects` or unset
        `first_unread_object` (depending on the type's mode) and decrement the
        unread count.

        Must be called whenever an object is accessed.

        :param object: Mark all subscriptions with this object as read.
        :param subject: Mark all subscriptions with this subject as read.
        """
        # enforce usage of keywords
        object = kwargs.pop('object', None)
        subject = kwargs.pop('subject', None)
        assert not kwargs, 'Invalid Arguments %r' % kwargs.keys()

        if object is not None:
            for t in SubscriptionType.by_object_type(type(object)):
                subjects = t.get_subjects(object)
                if not subjects:
                    continue
                subject_ids = [getattr(s, 'id', s) for s in subjects]

                cond = ((Subscription.type_name == t.name) &
                        (Subscription.user == user))
                if t.subject_type is not None:
                    cond &= Subscription.subject_id.in_(subject_ids)
                subscriptions = Subscription.query.filter(cond).all()
                for s in subscriptions:
                    if t.mode == 'sequent':
                        s.first_unread_object_id = None
                        s.count = 0
                    elif t.mode == 'multiple':
                        try:
                            s.unread_object_ids.remove(object.id)
                        except KeyError:
                            pass
                        else:
                            s.count -= 1
                db.session.commit()

        if subject is not None:
            for t in SubscriptionType.by_subject_type(type(subject)):
                try:
                    s = Subscription.query.filter_by(type_name=t.name,
                                                     subject_id=subject.id,
                                                     user=user).one()
                except db.NoResultFound:
                    continue

                s.count = 0
                if t.mode == 'sequent':
                    s.first_unread_object_id = None
                elif t.mode == 'multiple':
                    s.unread_object_ids = []
                db.session.commit()

    @staticmethod
    def subscribe(user, type_, subject=None):
        """
        Safely subscribe a user to a subject.
        Returns False if the Subscription already exists, else True.

        :param user: A user object or a user id.
        :param type_: The subscription type to be used. May be a subclass of
                      :class:`SubscriptionType` or a name of a subscription
                      type.
        :param subject: The subject to be used (id or instance). May be None
                        if the type has not ``subject_type``.
        """
        if isinstance(type_, basestring):
            type_ = SubscriptionType.by_name(type_)

        subject_type = type_ and type_.subject_type or type(None)
        if not isinstance(subject, subject_type):
            raise ValueError('subject (%r) does not match the subject_type '
                             '(%r) of given SubscriptionType' %
                             (subject, subject_type))

        subject_id = None if subject is None else subject.id
        args = {
            'user': user,
            'type_name': type_.name,
            'subject_id': subject_id,
        }

        if Subscription.query.filter_by(**args).count():
            return False

        Subscription(**args)
        db.session.commit()
        return True

    @staticmethod
    def unsubscribe(user_or_subscription, type_=None, subject=None):
        """
        Safely unsubscribe a user from a subject.
        Returns False if the Subscription did not exist, else True.

        :param user_or_subscription: A user object or a user id.
                                     May also be a Subscription object, in
                                     this case the other parameters must not
                                     be given.
        :param type_: The subscription type to be used. May be a subclass of
                      :class:`SubscriptionType` or a name of a subscription
                      type.
        :param subject: The subject to be used (id or instance). May be None
                        if the type has not ``subject_type``.
        """
        if isinstance(user_or_subscription, Subscription):
            assert type_ is None and subject is None
            db.session.delete(user_or_subscription)
            db.session.commit()
            return True
        else:
            user = user_or_subscription

        if isinstance(type_, basestring):
            type_ = SubscriptionType.by_name(type_)

        s = Subscription.query.filter_by(user=user,
                                         type_name=type_.name,
                                         subject_id=getattr(
                                             subject, 'id', subject)).all()

        if not len(s):
            return False
        if len(s) > 1:
            raise ValueError('Duplicate found!')

        db.session.delete(s[0])
        db.session.commit()
        return True

    @property
    def type(self):
        return SubscriptionType.by_name(self.type_name)

    @property
    def subject(self):
        if None not in (self.type.subject_type, self.subject_id):
            return self.type.subject_type.query.get(self.subject_id)
Esempio n. 7
0
class Storage(db.Model):
    __tablename__ = 'core_storage'

    key = db.Column(db.Unicode(200), primary_key=True, index=True)
    value = db.Column(db.PickleType)
Esempio n. 8
0
class Cache(db.Model):
    __tablename__ = 'core_cache'

    key = db.Column(db.Unicode(60), primary_key=True, nullable=False)
    value = db.Column(db.PickleType, nullable=False)
    expires = db.Column(db.DateTime, nullable=False)