Example #1
0
class Tier(db.Model):
    """This model defines tier of support that commercial users can sign up to."""
    __tablename__ = 'tier'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode, nullable=False)
    short_desc = db.Column(db.UnicodeText)
    long_desc = db.Column(db.UnicodeText)
    price = db.Column(db.Numeric(11, 2), nullable=False)  # per month

    # Users can sign up only to available tiers on their own. If tier is not
    # available, it should be hidden from the website.
    available = db.Column(db.Boolean, nullable=False, default=False)

    # Primary tiers are shown first on the signup page. Secondary plans (along
    # with repeating primary plans) are listed on the "view all tiers" page
    # that lists all available tiers.
    primary = db.Column(db.Boolean, nullable=False, default=False)

    users = db.relationship("User", backref='tier', lazy="dynamic")

    def __unicode__(self):
        return "%s (#%s)" % (self.name, self.id)

    def __str__(self):
        return unicode(self).encode('utf-8')

    @classmethod
    def create(cls, **kwargs):
        new_tier = cls(
            name=kwargs.pop('name'),
            short_desc=kwargs.pop('short_desc', None),
            long_desc=kwargs.pop('long_desc', None),
            price=kwargs.pop('price'),
            available=kwargs.pop('available', False),
            primary=kwargs.pop('primary', False),
        )
        db.session.add(new_tier)
        db.session.commit()
        return new_tier

    @classmethod
    def get(cls, **kwargs):
        return cls.query.filter_by(**kwargs).first()

    @classmethod
    def get_available(cls, sort=False, sort_desc=False):
        """Returns list of tiers that are available for sign up.

        You can also sort returned list by price of the tier.
        """
        query = cls.query.filter(cls.available == True)
        if sort:
            query = query.order_by(cls.price.desc()) if sort_desc else \
                    query.order_by(cls.price.asc())
        return query.all()

    def get_featured_users(self, **kwargs):
        return User.get_featured(tier_id=self.id, **kwargs)
Example #2
0
class User(db.Model, UserMixin):
    """User model is used for users of MetaBrainz services like Live Data Feed.

    Users are either commercial or non-commercial (see `is_commercial`). Their
    access to the API is determined by their `state` (active, pending, waiting,
    or rejected). All non-commercial users have active state by default, but
    commercial users need to be approved by one of the admins first.
    """
    __tablename__ = 'user'

    # Common columns used by both commercial and non-commercial users:
    id = db.Column(db.Integer, primary_key=True)
    is_commercial = db.Column(db.Boolean, nullable=False)
    musicbrainz_id = db.Column(
        db.Unicode, unique=True)  # MusicBrainz account that manages this user
    created = db.Column(db.DateTime(timezone=True), default=datetime.utcnow)
    state = db.Column(postgres.ENUM(STATE_ACTIVE,
                                    STATE_PENDING,
                                    STATE_WAITING,
                                    STATE_REJECTED,
                                    STATE_LIMITED,
                                    name='state_types'),
                      nullable=False)
    contact_name = db.Column(db.Unicode, nullable=False)
    contact_email = db.Column(db.Unicode, nullable=False)
    data_usage_desc = db.Column(db.UnicodeText)

    # Columns specific to commercial users:
    org_name = db.Column(db.Unicode)
    org_logo_url = db.Column(db.Unicode)
    website_url = db.Column(db.Unicode)
    api_url = db.Column(db.Unicode)
    org_desc = db.Column(db.UnicodeText)
    address_street = db.Column(db.Unicode)
    address_city = db.Column(db.Unicode)
    address_state = db.Column(db.Unicode)
    address_postcode = db.Column(db.Unicode)
    address_country = db.Column(db.Unicode)
    tier_id = db.Column(
        db.Integer,
        db.ForeignKey('tier.id', ondelete="SET NULL", onupdate="CASCADE"))
    amount_pledged = db.Column(db.Numeric(11, 2))

    # Administrative columns:
    good_standing = db.Column(db.Boolean, nullable=False, default=True)
    in_deadbeat_club = db.Column(db.Boolean, nullable=False, default=False)
    featured = db.Column(db.Boolean, nullable=False, default=False)

    tokens = db.relationship("Token", backref="owner", lazy="dynamic")
    token_log_records = db.relationship("TokenLog",
                                        backref="user",
                                        lazy="dynamic")

    def __unicode__(self):
        if self.is_commercial:
            return "%s (#%s)" % (self.org_name, self.id)
        else:
            if self.musicbrainz_id:
                return "#%s (MBID: %s)" % (self.id, self.musicbrainz_id)
            else:
                return str(self.id)

    @property
    def token(self):
        return Token.get(owner_id=self.id, is_active=True)

    @classmethod
    def add(cls, **kwargs):
        new_user = cls(
            is_commercial=kwargs.pop('is_commercial'),
            musicbrainz_id=kwargs.pop('musicbrainz_id'),
            contact_name=kwargs.pop('contact_name'),
            contact_email=kwargs.pop('contact_email'),
            data_usage_desc=kwargs.pop('data_usage_desc'),
            org_desc=kwargs.pop('org_desc', None),
            org_name=kwargs.pop('org_name', None),
            org_logo_url=kwargs.pop('org_logo_url', None),
            website_url=kwargs.pop('website_url', None),
            api_url=kwargs.pop('api_url', None),
            address_street=kwargs.pop('address_street', None),
            address_city=kwargs.pop('address_city', None),
            address_state=kwargs.pop('address_state', None),
            address_postcode=kwargs.pop('address_postcode', None),
            address_country=kwargs.pop('address_country', None),
            tier_id=kwargs.pop('tier_id', None),
            amount_pledged=kwargs.pop('amount_pledged', None),
        )
        new_user.state = STATE_ACTIVE if not new_user.is_commercial else STATE_PENDING
        if kwargs:
            raise TypeError('Unexpected **kwargs: %r' % kwargs)
        db.session.add(new_user)
        db.session.commit()

        if new_user.is_commercial:
            send_user_signup_notification(new_user)

        return new_user

    @classmethod
    def get(cls, **kwargs):
        return cls.query.filter_by(**kwargs).first()

    @classmethod
    def get_all(cls, **kwargs):
        return cls.query.filter_by(**kwargs).all()

    @classmethod
    def get_all_commercial(cls, limit=None, offset=None):
        query = cls.query.filter(cls.is_commercial == True).order_by(
            cls.org_name)
        count = query.count()  # Total count should be calculated before limits
        if limit is not None:
            query = query.limit(limit)
        if offset is not None:
            query = query.offset(offset)
        return query.all(), count

    @classmethod
    def get_featured(cls, limit=None, **kwargs):
        """Get list of featured users which is randomly sorted.

        Args:
            limit: Max number of users to return.
            in_deadbeat_club: Returns only users from deadbeat club if set to True.
            with_logos: True if need only users with logo URLs specified, False if
                only users without logo URLs, None if it's irrelevant.
            tier_id: Returns only users from tier with a specified ID.

        Returns:
            List of users according to filters described above.
        """
        query = cls.query.filter(cls.featured == True)
        query = query.filter(
            cls.in_deadbeat_club == kwargs.pop('in_deadbeat_club', False))
        with_logos = kwargs.pop('with_logos', None)
        if with_logos:
            query = query.filter(cls.org_logo_url != None)
        tier_id = kwargs.pop('tier_id', None)
        if tier_id:
            query = query.filter(cls.tier_id == tier_id)
        if kwargs:
            raise TypeError('Unexpected **kwargs: %r' % kwargs)
        return query.order_by(func.random()).limit(limit).all()

    @classmethod
    def search(cls, value):
        """Search users by their musicbrainz_id, org_name, contact_name,
        or contact_email.
        """
        query = cls.query.filter(
            or_(
                cls.musicbrainz_id.ilike('%' + value + '%'),
                cls.org_name.ilike('%' + value + '%'),
                cls.contact_name.ilike('%' + value + '%'),
                cls.contact_email.ilike('%' + value + '%'),
            ))
        return query.limit(20).all()

    def generate_token(self):
        """Generates new access token for this user."""
        if self.state == STATE_ACTIVE:
            return Token.generate_token(self.id)
        else:
            raise InactiveUserException(
                "Can't generate token for inactive user.")

    def update(self, **kwargs):
        contact_name = kwargs.pop('contact_name')
        if contact_name is not None:
            self.contact_name = contact_name
        contact_email = kwargs.pop('contact_email')
        if contact_email is not None:
            self.contact_email = contact_email
        if kwargs:
            raise TypeError('Unexpected **kwargs: %r' % kwargs)
        db.session.commit()

    def set_state(self, state):
        old_state = self.state
        self.state = state
        db.session.commit()
        if old_state != self.state:
            # TODO: Send additional info about new state.
            state_name = "ACTIVE" if self.state == STATE_ACTIVE else \
                         "REJECTED" if self.state == STATE_REJECTED else \
                         "PENDING" if self.state == STATE_PENDING else \
                         "WAITING" if self.state == STATE_WAITING else \
                         "LIMITED" if self.state == STATE_LIMITED else \
                         self.state
            send_mail(
                subject="[MetaBrainz] Your account has been updated",
                text=
                'State of your MetaBrainz account has been changed to "%s".' %
                state_name,
                recipients=[self.contact_email],
            )
Example #3
0
class Token(db.Model):
    __tablename__ = 'token'

    value = db.Column(db.String, primary_key=True)
    is_active = db.Column(db.Boolean, nullable=False, default=True)
    owner_id = db.Column(
        db.Integer,
        db.ForeignKey('user.id', ondelete="SET NULL", onupdate="CASCADE"))
    created = db.Column(db.DateTime(timezone=True), default=datetime.utcnow)

    log_records = db.relationship(TokenLog, backref="token", lazy="dynamic")

    @classmethod
    def get(cls, **kwargs):
        return cls.query.filter_by(**kwargs).first()

    @classmethod
    def get_all(cls, **kwargs):
        return cls.query.filter_by(**kwargs).all()

    @classmethod
    def search_by_value(cls, value):
        return cls.query.filter(cls.value.like('%' + value + '%')).all()

    @classmethod
    def generate_token(cls, owner_id):
        """Generates new token for a specified user and revokes all other
        tokens owned by this user.

        Returns:
            Value of the new token.
        """
        if owner_id is not None:
            last_hour_q = cls.query.filter(
                cls.owner_id == owner_id,
                cls.created > datetime.utcnow() - timedelta(hours=1),
            )
            if last_hour_q.count() > 0:
                raise TokenGenerationLimitException(
                    "Can't generate more than one token per hour.")
            cls.revoke_tokens(owner_id)

        new_token = cls(
            value=generate_string(TOKEN_LENGTH),
            owner_id=owner_id,
        )
        db.session.add(new_token)
        db.session.commit()

        TokenLog.create_record(new_token.value, token_log.ACTION_CREATE)

        return new_token.value

    @classmethod
    def revoke_tokens(cls, owner_id):
        """Revokes all tokens owned by a specified user.

        Args:
            owner_id: ID of a user.
        """
        tokens = db.session.query(cls).filter(cls.owner_id == owner_id,
                                              cls.is_active == True)
        for token in tokens:
            token.revoke()

    @classmethod
    def is_valid(cls, token_value):
        """Checks if token exists and is active."""
        token = cls.get(value=token_value)
        return token and token.is_active

    def revoke(self):
        self.is_active = False
        db.session.commit()
        TokenLog.create_record(self.value, token_log.ACTION_DEACTIVATE)