Ejemplo n.º 1
0
class Product(db.Model):
    """Product describes an item in the InnoStore that a user may purchase."""
    __tablename__ = 'products'
    __table_args__ = __table_args__ = (
        db.UniqueConstraint('name', 'type',
                            name='unique product'),
    )

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128), nullable=False)
    type = db.Column(db.String(128), nullable=True)
    description = db.Column(db.String(1024), nullable=False)
    varieties = db.relationship('Variety',
                                cascade='all, delete-orphan',
                                passive_deletes=True,
                                back_populates='product')
    price = db.Column(db.Integer,
                      db.CheckConstraint('price >= 0', name='non-negative price'),
                      nullable=False)
    addition_time = db.Column(db.DateTime(timezone=True), nullable=False, default=tz_aware_now)

    def __str__(self):
        """Human-readable representation of a product."""
        if self.type is None:
            return self.name
        return f"'{self.name}' {self.type}"
Ejemplo n.º 2
0
class Application(db.Model):
    """Represents a volunteering application."""
    __tablename__ = 'applications'
    __table_args__ = (
        db.UniqueConstraint('applicant_email', 'activity_id',
                            name='only one application'),
    )

    id = db.Column(db.Integer, primary_key=True)
    applicant_email = db.Column(db.String(128),
                                db.ForeignKey('accounts.email', ondelete='CASCADE'),
                                nullable=False)
    applicant = db.relationship('Account', back_populates='applications')
    activity_id = db.Column(db.Integer,
                            db.ForeignKey('activities.id', ondelete='CASCADE'),
                            nullable=False)
    activity = db.relationship('Activity',
                               uselist=False,
                               single_parent=True,
                               back_populates='applications')
    comment = db.Column(db.String(1024), nullable=True)
    application_time = db.Column(db.DateTime(timezone=True), nullable=False, default=tz_aware_now)
    telegram_username = db.Column(db.String(32), nullable=True)
    status = db.Column(db.Enum(ApplicationStatus),
                       nullable=False,
                       default=ApplicationStatus.pending)
    actual_hours = db.Column(db.Integer, nullable=False)
    reports = db.relationship('VolunteeringReport',
                              cascade='all, delete-orphan',
                              back_populates='application')
    feedback = db.relationship('Feedback',
                               uselist=False,
                               cascade='all, delete-orphan',
                               passive_deletes=True,
                               back_populates='application')
Ejemplo n.º 3
0
class Variety(db.Model):
    """Represents various types of one product."""
    __tablename__ = 'varieties'
    __table_args__ = (
        # Warning: this index requires a manually written migration.
        # In upgrade() use:
        #   op.create_index('unique varieties', 'varieties',
        #                   ['product_id',
        #                    sa.text("coalesce(color, '')"),
        #                    sa.text("coalesce(size, '')")],
        #                   unique=True)
        #
        # In downgrade() use:
        #   op.drop_index('unique varieties', 'varieties')
        db.Index('unique varieties',
                 'product_id',
                 db.text("coalesce(color, '')"),
                 db.text("coalesce(size, '')"),
                 unique=True), )

    id = db.Column(db.Integer, primary_key=True)
    product_id = db.Column(db.Integer,
                           db.ForeignKey('products.id', ondelete='CASCADE'),
                           nullable=False)
    product = db.relationship('Product', back_populates='varieties')
    size = db.Column(db.String(3),
                     db.ForeignKey('sizes.value', ondelete='CASCADE'),
                     nullable=True)
    color = db.Column(db.String(6),
                      db.ForeignKey('colors.value', ondelete='CASCADE'),
                      nullable=True)
    images = db.relationship('ProductImage',
                             cascade='all, delete-orphan',
                             passive_deletes=True,
                             back_populates='variety')
    stock_changes = db.relationship('StockChange',
                                    cascade='all, delete-orphan',
                                    passive_deletes=True,
                                    back_populates='variety')

    @property
    def amount(self):
        """Return the amount of items of this variety, computed
           from the StockChange instances."""
        return db.session.query(db.func.sum(StockChange.amount)).filter(
            StockChange.variety_id == self.id,
            StockChange.status != StockChangeStatus.rejected).scalar() or 0

    @property
    def purchases(self):
        """Return the amount of purchases of this variety, computed
           from the StockChange instances."""
        # pylint: disable=invalid-unary-operand-type
        return -(db.session.query(db.func.sum(StockChange.amount)).join(
            StockChange.account).filter(
                StockChange.variety_id == self.id,
                StockChange.status != StockChangeStatus.rejected,
                StockChange.amount < 0, ~Account.is_admin).scalar() or 0)
Ejemplo n.º 4
0
class Account(UserMixin, db.Model):
    """Represents an account of a logged in user."""
    __tablename__ = 'accounts'

    full_name = db.Column(db.String(256), nullable=False)
    group = db.Column(db.String(64), nullable=True)
    email = db.Column(db.String(128), primary_key=True)
    telegram_username = db.Column(db.String(32), nullable=True)
    is_admin = db.Column(db.Boolean, nullable=False)
    created_projects = db.relationship('Project',
                                       cascade='all, delete-orphan',
                                       passive_deletes=True,
                                       back_populates='creator')
    notification_settings = db.Column(JSONB,
                                      nullable=False,
                                      default=DEFAULT_NOTIFICATIONS)
    moderated_projects = db.relationship('Project',
                                         secondary='project_moderation',
                                         back_populates='moderators')
    stock_changes = db.relationship('StockChange',
                                    cascade='all, delete-orphan',
                                    passive_deletes=True,
                                    back_populates='account')
    transactions = db.relationship('Transaction',
                                   cascade='all, delete-orphan',
                                   passive_deletes=True,
                                   back_populates='account')
    applications = db.relationship('Application',
                                   cascade='all, delete-orphan',
                                   passive_deletes=True,
                                   back_populates='applicant')
    static_files = db.relationship('StaticFile',
                                   cascade='all, delete-orphan',
                                   passive_deletes=True,
                                   back_populates='owner')
    reports = db.relationship('VolunteeringReport',
                              cascade='all, delete-orphan',
                              passive_deletes=True,
                              back_populates='reporter')
    notifications = db.relationship('Notification',
                                    cascade='all, delete-orphan',
                                    passive_deletes=True,
                                    back_populates='recipient')

    def get_id(self):
        """Return the user's e-mail."""
        return self.email

    @property
    def balance(self):
        """Return the user's innopoints balance."""
        return db.session.query(db.func.sum(Transaction.change)).filter(
            Transaction.account_email == self.email).scalar() or 0
Ejemplo n.º 5
0
class Project(db.Model):
    """Represents a project for volunteering."""
    __tablename__ = 'projects'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128), nullable=True)
    image_id = db.Column(db.Integer,
                         db.ForeignKey('static_files.id'),
                         nullable=True)
    image = db.relationship('StaticFile', back_populates='cover_for')
    creation_time = db.Column(db.DateTime(timezone=True),
                              nullable=False,
                              default=tz_aware_now)
    activities = db.relationship('Activity',
                                 cascade='all, delete-orphan',
                                 passive_deletes=True,
                                 back_populates='project')
    moderators = db.relationship('Account',
                                 secondary='project_moderation',
                                 back_populates='moderated_projects')
    creator_email = db.Column(db.String(128),
                              db.ForeignKey('accounts.email',
                                            ondelete='CASCADE'),
                              nullable=False)
    creator = db.relationship('Account', back_populates='created_projects')
    admin_feedback = db.Column(db.String(1024), nullable=True)
    review_status = db.Column(db.Enum(ReviewStatus), nullable=True)
    lifetime_stage = db.Column(db.Enum(LifetimeStage),
                               nullable=False,
                               default=LifetimeStage.draft)
    tags = db.relationship('Tag', secondary='project_tags')

    @property
    def start_date(self):
        """Returns the project start date as the earliest start_time of its activities."""
        return db.session.query(db.func.min(Activity.start_date), ).filter(
            Activity.project_id == self.id, ).scalar()

    @property
    def end_date(self):
        """Returns the project end date as the earliest start_time of its activities."""
        return db.session.query(db.func.max(Activity.end_date), ).filter(
            Activity.project_id == self.id, ).scalar()

    @property
    def image_url(self):
        """Return an image URL constructed from the ID."""
        if self.image_id is None:
            return None
        return f'/file/{self.image_id}'
Ejemplo n.º 6
0
class Color(db.Model):
    """Represents colors of items in the store."""
    __tablename__ = 'colors'

    value = db.Column(db.String(6), primary_key=True)
    varieties = db.relationship('Variety',
                                cascade='all, delete-orphan',
                                passive_deletes=True)
Ejemplo n.º 7
0
class StaticFile(db.Model):
    """Represents the user-uploaded static files."""
    __tablename__ = 'static_files'

    id = db.Column(db.Integer, primary_key=True)
    mimetype = db.Column(db.String(255), nullable=False)
    owner_email = db.Column(db.String(128),
                            db.ForeignKey('accounts.email',
                                          ondelete='CASCADE'),
                            nullable=False)
    owner = db.relationship('Account', back_populates='static_files')
    product_image = db.relationship('ProductImage',
                                    uselist=False,
                                    cascade='all, delete-orphan',
                                    passive_deletes=True,
                                    back_populates='image')
    project_file = db.relationship('ProjectFile',
                                   uselist=False,
                                   cascade='all, delete-orphan',
                                   passive_deletes=True)
    cover_for = db.relationship('Project', back_populates='image')
Ejemplo n.º 8
0
class VolunteeringReport(db.Model):
    """Represents a moderator's report about a certain occurence of work
       done by a volunteer."""
    __tablename__ = 'reports'
    __table_args__ = (db.PrimaryKeyConstraint('application_id',
                                              'reporter_email'), )

    application_id = db.Column(
        db.Integer, db.ForeignKey('applications.id', ondelete='CASCADE'))
    application = db.relationship('Application', back_populates='reports')
    reporter_email = db.Column(db.String(128),
                               db.ForeignKey('accounts.email',
                                             ondelete='CASCADE'),
                               nullable=False)
    reporter = db.relationship('Account', back_populates='reports')
    time = db.Column(db.DateTime(timezone=True),
                     nullable=False,
                     default=tz_aware_now)
    rating = db.Column(db.Integer,
                       db.CheckConstraint('rating <= 5 AND rating >= 1'),
                       nullable=False)
    content = db.Column(db.String(1024), nullable=True)
Ejemplo n.º 9
0
class Notification(db.Model):
    """Represents a notification about a certain event."""
    __tablename__ = 'notifications'

    id = db.Column(db.Integer, primary_key=True)
    recipient_email = db.Column(db.String(128),
                                db.ForeignKey('accounts.email',
                                              ondelete='CASCADE'),
                                nullable=False)
    recipient = db.relationship('Account', back_populates='notifications')
    is_read = db.Column(db.Boolean, nullable=False, default=False)
    payload = db.Column(JSONB, nullable=True)
    timestamp = db.Column(db.DateTime(timezone=True),
                          nullable=False,
                          default=tz_aware_now)
    type = db.Column(db.Enum(NotificationType), nullable=False)
Ejemplo n.º 10
0
class StockChange(db.Model):
    """Represents the change in the amount of variety available."""
    __tablename__ = 'stock_changes'

    id = db.Column(db.Integer, primary_key=True)
    amount = db.Column(db.Integer, nullable=False)
    time = db.Column(db.DateTime(timezone=True),
                     nullable=False,
                     default=tz_aware_now)
    status = db.Column(db.Enum(StockChangeStatus), nullable=False)
    account_email = db.Column(db.String(128),
                              db.ForeignKey('accounts.email',
                                            ondelete='CASCADE'),
                              nullable=False)
    account = db.relationship('Account', back_populates='stock_changes')
    variety_id = db.Column(db.Integer,
                           db.ForeignKey('varieties.id', ondelete='CASCADE'),
                           nullable=False)
    variety = db.relationship('Variety', back_populates='stock_changes')
    transaction = db.relationship('Transaction',
                                  uselist=False,
                                  single_parent=True,
                                  back_populates='stock_change')
Ejemplo n.º 11
0
class Feedback(db.Model):
    """Represents a volunteer's feedback on an activity."""
    __tablename__ = 'feedback'

    application_id = db.Column(db.Integer,
                               db.ForeignKey('applications.id',
                                             ondelete='CASCADE'),
                               unique=True,
                               primary_key=True)
    application = db.relationship('Application',
                                  back_populates='feedback',
                                  uselist=False,
                                  single_parent=True)
    competences = db.relationship('Competence',
                                  secondary='feedback_competence')
    time = db.Column(db.DateTime(timezone=True),
                     nullable=False,
                     default=tz_aware_now)
    answers = db.Column(db.ARRAY(db.String(1024)), nullable=False)
    transaction = db.relationship('Transaction',
                                  uselist=False,
                                  single_parent=True,
                                  back_populates='feedback')
Ejemplo n.º 12
0
class Transaction(db.Model):
    """Represents a change in the innopoints balance for a certain user."""
    __tablename__ = 'transactions'
    __table_args__ = (
        db.CheckConstraint('(stock_change_id IS NULL) OR (feedback_id IS NULL)',
                           name='not(feedback and stock_change)'),
    )

    id = db.Column(db.Integer, primary_key=True)
    account_email = db.Column(db.String(128),
                              db.ForeignKey('accounts.email', ondelete='CASCADE'),
                              nullable=False)
    account = db.relationship('Account', back_populates='transactions')
    change = db.Column(db.Integer, nullable=False)
    stock_change_id = db.Column(db.Integer,
                                db.ForeignKey('stock_changes.id', ondelete='SET NULL'),
                                nullable=True)
    stock_change = db.relationship('StockChange',
                                   back_populates='transaction')
    feedback_id = db.Column(db.Integer,
                            db.ForeignKey('feedback.application_id', ondelete='SET NULL'),
                            nullable=True)
    feedback = db.relationship('Feedback',
                               back_populates='transaction')
Ejemplo n.º 13
0
class Tag(db.Model):
    """Represents tags for grouping projects in the statistics."""
    __tablename__ = 'tags'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128), nullable=False, unique=True)
Ejemplo n.º 14
0
"""The many-to-many relationship between Project and its moderators – Account."""

from innopoints.extensions import db


project_moderation = db.Table(
    'project_moderation',
    db.Column('project_id', db.Integer,
              db.ForeignKey('projects.id', ondelete='CASCADE'),
              primary_key=True),
    db.Column('account_email', db.String(128),
              db.ForeignKey('accounts.email', ondelete='CASCADE'),
              primary_key=True)
)
Ejemplo n.º 15
0
class Activity(db.Model):
    """Represents a volunteering activity in the project."""
    __tablename__ = 'activities'
    __table_args__ = (
        db.CheckConstraint('working_hours == NULL OR working_hours >= 0',
                           name='working hours are non-negative'),
        db.CheckConstraint('people_required == NULL OR people_required >= 0',
                           name='people required are unset or non-negative'),
        db.CheckConstraint(
            'draft OR working_hours != NULL',
            name='working hours are not nullable for non-drafts'),
        db.CheckConstraint(
            f'draft OR (fixed_reward AND working_hours = 1) '
            f'OR (NOT fixed_reward AND reward_rate = {IPTS_PER_HOUR})',
            name='reward policy'),
    )

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128), nullable=True)
    description = db.Column(db.String(1024), nullable=True)
    start_date = db.Column(db.DateTime(timezone=True), nullable=True)
    end_date = db.Column(db.DateTime(timezone=True), nullable=True)
    project_id = db.Column(db.Integer,
                           db.ForeignKey('projects.id', ondelete='CASCADE'),
                           nullable=False)
    project = db.relationship('Project', back_populates='activities')
    working_hours = db.Column(db.Integer, nullable=True, default=1)
    reward_rate = db.Column(db.Integer, nullable=False, default=IPTS_PER_HOUR)
    fixed_reward = db.Column(db.Boolean, nullable=False, default=False)
    people_required = db.Column(db.Integer, nullable=True)
    telegram_required = db.Column(db.Boolean, nullable=False, default=False)
    competences = db.relationship('Competence',
                                  secondary='activity_competence')
    application_deadline = db.Column(db.DateTime(timezone=True), nullable=True)
    feedback_questions = db.Column(db.ARRAY(db.String(1024)),
                                   nullable=False,
                                   default=DEFAULT_QUESTIONS)
    internal = db.Column(db.Boolean, nullable=False, default=False)
    draft = db.Column(db.Boolean, nullable=False, default=True)
    applications = db.relationship('Application',
                                   cascade='all, delete-orphan',
                                   passive_deletes=True,
                                   back_populates='activity')

    @property
    def dates(self):
        """Return the activity dates as a single JSON object."""
        return {
            'start': self.start_date.isoformat(),
            'end': self.end_date.isoformat()
        }

    @property
    def accepted_applications(self):
        """Return the amount of accepted applications."""
        return Application.query.filter_by(
            activity_id=self.id, status=ApplicationStatus.approved).count()

    @property
    def vacant_spots(self):
        """Return the amount of vacant spots for the activity."""
        if self.people_required is None:
            return -1

        return self.people_required - self.accepted_applications

    def has_application_from(self, user):
        """Return whether the given user has applied for this activity."""
        application = Application.query.filter_by(applicant=user,
                                                  activity_id=self.id)
        return db.session.query(application.exists()).scalar()

    @property
    def is_complete(self):
        """Return whether the all the required fields for an activity have been filled out."""
        return (self.name is not None and not self.name.isspace()
                and self.start_date is not None and self.end_date is not None
                and self.start_date <= self.end_date
                and self.working_hours is not None
                and self.reward_rate is not None
                and len(self.competences) in range(1, 4))
Ejemplo n.º 16
0
class Competence(db.Model):
    """Represents volunteers' competences."""
    __tablename__ = 'competences'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128), nullable=False, unique=True)