예제 #1
0
class User(UserMixin, SurrogatePK, Model):

    __tablename__ = 'users'
    email = Column(db.String(80), unique=True, nullable=False, index=True)
    created_at = Column(db.DateTime,
                        nullable=False,
                        default=dt.datetime.utcnow)
    first_name = Column(db.String(30), nullable=True)
    last_name = Column(db.String(30), nullable=True)
    department = Column(db.String(255), nullable=False)
    active = Column(db.Boolean(), default=True)
    role_id = ReferenceCol('roles', ondelete='SET NULL', nullable=True)

    @property
    def full_name(self):
        return "{0} {1}".format(self.first_name, self.last_name)

    def __repr__(self):
        return '<User({email!r})>'.format(email=self.email)

    def __unicode__(self):
        return self.email

    def get_starred(self):
        return [i.id for i in self.contracts_starred]

    def get_following(self):
        return [i.id for i in self.contracts_following]
예제 #2
0
class CompanyContact(Model):
    __tablename__ = 'company_contact'

    id = Column(db.Integer, primary_key=True, index=True)
    company = db.relationship(
        'Company',
        backref=backref('contacts', lazy='dynamic', cascade='all, delete-orphan')
    )
    company_id = ReferenceCol('company', ondelete='cascade')
    first_name = Column(db.String(255))
    last_name = Column(db.String(255))
    addr1 = Column(db.String(255))
    addr2 = Column(db.String(255))
    city = Column(db.String(255))
    state = Column(db.String(255))
    zip_code = Column(db.Integer)
    phone_number = Column(db.String(255))
    fax_number = Column(db.String(255))
    email = Column(db.String(255))

    def __unicode__(self):
        return '{first} {last} - {email}'.format(
            first=self.first_name, last=self.last_name,
            email=self.email
        )
예제 #3
0
class Category(Model):
    __tablename__ = 'category'

    id = Column(db.Integer, primary_key=True, index=True)
    nigp_code = Column(db.Integer)
    category = Column(db.String(255))
    subcategory = Column(db.String(255))

    def __unicode__(self):
        return '{sub} (in {main})'.format(sub=self.subcategory,
                                          main=self.category)
예제 #4
0
class StageProperty(Model):
    __tablename__ = 'stage_property'

    id = Column(db.Integer, primary_key=True, index=True)
    stage = db.relationship('Stage', backref=backref(
        'properties', lazy='dynamic', cascade='all, delete-orphan'
    ))
    stage_id = ReferenceCol('stage', ondelete='CASCADE')
    key = Column(db.String(255), nullable=False)
    value = Column(db.String(255))

    def __unicode__(self):
        return '{key}: {value}'.format(key=self.key, value=self.value)
class ContractProperty(RefreshSearchViewMixin, Model):
    '''Model for contract properties

    The contract property model effectively serves as a key-value
    storage unit for properties that exist on a subset of contracts.
    For example, a common unit for County contracts is the so-called
    "spec number", an identified used by Allegheny County for their
    electronic bidding system. Other contract types (such as PA state and
    COSTARS contracts), do not have this property but do have others
    (such as manufacturers offered, etc.). Therefore, we use this
    model as an extended key-value store for the base
    :py:class:`~purchasing.data.contracts.ContractBase` model

    Attributes:
        id: Primary key unique ID
        contract: Sqlalchemy relationship to
            :py:class:`~purchasing.data.contracts.ContractBase`
        contract_id: Foreign key to
            :py:class:`~purchasing.data.contracts.ContractBase`
        key: The key for the property (for example, Spec Number)
        value: The value for the property (for example, 7137)
    '''
    __tablename__ = 'contract_property'

    id = Column(db.Integer, primary_key=True, index=True)
    contract = db.relationship('ContractBase',
                               backref=backref('properties',
                                               lazy='joined',
                                               cascade='all, delete-orphan'))
    contract_id = ReferenceCol('contract', ondelete='CASCADE')
    key = Column(db.String(255), nullable=False)
    value = Column(db.Text)

    def __unicode__(self):
        return u'{key}: {value}'.format(key=self.key, value=self.value)
class Company(RefreshSearchViewMixin, Model):
    '''Model for individual Compnaies

    Attributes:
        id: Primary key unique ID
        company_name: Name of the company
        contracts: Many-to-many relationship with the :py:class:`
            purchasing.data.contracts.ContractBase` model
    '''
    __tablename__ = 'company'

    id = Column(db.Integer, primary_key=True, index=True)
    company_name = Column(db.String(255),
                          nullable=False,
                          unique=True,
                          index=True)
    contracts = db.relationship(
        'ContractBase',
        secondary=company_contract_association_table,
        backref='companies',
    )

    def __repr__(self):
        return self.company_name

    def __unicode__(self):
        return self.company_name

    @classmethod
    def all_companies_query_factory(cls):
        '''Query factory of all company ids and names ordered by name
        '''
        return db.session.query(
            db.distinct(cls.id).label('id'),
            cls.company_name).order_by(cls.company_name)
예제 #7
0
class Role(SurrogatePK, Model):
    '''Model to handle view-based permissions

    Attributes:
        id: primary key
        name: role name
    '''
    __tablename__ = 'roles'
    name = Column(db.String(80), unique=True, nullable=False)

    def __repr__(self):
        return '<Role({name})>'.format(name=self.name)

    def __unicode__(self):
        return self.name

    @classmethod
    def query_factory(cls):
        '''Generates a query of all roles

        Returns:
            `sqla query`_ of all roles
        '''
        return cls.query

    @classmethod
    def no_admins(cls):
        '''Generates a query of non-admin roles

        Returns:
            `sqla query`_ of roles without administrative access
        '''
        return cls.query.filter(cls.name != 'superadmin')
예제 #8
0
class Stage(Model):
    '''Model for individual conductor stages

    Attributes:
        id: Primary key unique ID
        name: Name of the stage
        post_opportunities: Whether you can post
            :py:class:`~purchasing.opportunities.models.Opportunity`
            objects to :doc:`/beacon` from this stage
        default_message: Message to autopopulate the
            :py:class:`~purchasing.conductor.forms.SendUpdateForm`
            message body
    '''
    __tablename__ = 'stage'

    id = Column(db.Integer, primary_key=True, index=True)
    name = Column(db.String(255))
    post_opportunities = Column(db.Boolean, default=False, nullable=False)
    default_message = Column(db.Text)

    def __unicode__(self):
        return self.name

    @classmethod
    def choices_factory(cls):
        '''Return a two-tuple of (stage id, stage name) for all stages
        '''
        return [(i.id, i.name) for i in cls.query.all()]
예제 #9
0
class ContractStageActionItem(Model):
    __tablename__ = 'contract_stage_action_item'

    id = Column(db.Integer, primary_key=True, index=True)
    contract_stage_id = ReferenceCol('contract_stage', ondelete='CASCADE', index=True)
    contract_stage = db.relationship('ContractStage', backref=backref(
        'contract_stage_actions', lazy='dynamic', cascade='all, delete-orphan'
    ))
    action_type = Column(db.String(255))
    action_detail = Column(JSON)
    taken_at = Column(db.DateTime, default=datetime.datetime.now())
    taken_by = ReferenceCol('users', ondelete='SET NULL', nullable=True)

    def __unicode__(self):
        return self.action

    def get_sort_key(self):
        # if we are reversion, we need to get the timestamps from there
        if self.action_type == 'reversion':
            return datetime.datetime.strptime(
                self.action_detail['timestamp'],
                '%Y-%m-%dT%H:%M:%S'
            )
        # otherwise, return the taken_at time
        else:
            return self.taken_at if self.taken_at else datetime.datetime(1970, 1, 1)
예제 #10
0
class Category(Model):
    '''Category model for opportunities and Vendor signups

    Categories are based on the codes created by the `National Institute
    of Government Purchasing (NIGP) <http://www.nigp.org/eweb/StartPage.aspx>`_.
    The names of the categories have been re-written a bit to make them more
    human-readable and in some cases a bit more modern.

    Attributes:
        id: Primary key unique ID
        nigp_codes: Array of integers refering to NIGP codes.
        category: parent top-level category
        subcategory: NIGP designated subcategory name
        category_friendly_name: Rewritten, more human-readable subcategory name
        examples: Pipe-delimited examples of items that fall in each subcategory
        examples_tsv: TSVECTOR of the examples for that subcategory

    See Also:
        The :ref:`nigp-importer` contains more information about how NIGP codes
        are imported into the system.
    '''
    __tablename__ = 'category'

    id = Column(db.Integer, primary_key=True, index=True)
    nigp_codes = Column(ARRAY(db.Integer()))
    category = Column(db.String(255))
    subcategory = Column(db.String(255))
    category_friendly_name = Column(db.Text)
    examples = Column(db.Text)
    examples_tsv = Column(TSVECTOR)

    def __unicode__(self):
        return u'{sub} (in {main})'.format(sub=self.category_friendly_name,
                                           main=self.category)

    @classmethod
    def parent_category_query_factory(cls):
        '''Query factory to return a query of all of the distinct top-level categories
        '''
        return db.session.query(db.distinct(
            cls.category).label('category')).order_by('category')

    @classmethod
    def query_factory(cls):
        '''Query factory that returns all category/subcategory pairs
        '''
        return cls.query
예제 #11
0
class AppStatus(Model):
    __tablename__ = 'app_status'

    id = Column(db.Integer, primary_key=True)
    status = Column(db.String(255))
    last_updated = Column(db.DateTime)
    county_max_deadline = Column(db.DateTime)
    message = Column(db.Text)
예제 #12
0
class RequiredBidDocument(Model):
    '''Model for documents that a vendor would be required to provide

    There are two types of documents associated with an opportunity -- documents
    that the City will provide (RFP/IFB/RFQ, Q&A documents, etc.), and documents
    that the bidder will need to provide upon bidding (Insurance certificates,
    Bid bonds, etc.). This model describes the latter.

    See Also:
        These models get rendered into a select multi with the descriptions rendered
        in tooltips. For more on how this works, see the
        :py:func:`~purchasing.opportunities.utils.select_multi_checkbox`.

    Attributes:
        id: Primary key unique ID
        display_name: Display name for the document
        description: Description of what the document is, rendered in a tooltip
        form_href: A link to an example document
    '''
    __tablename__ = 'document'

    id = Column(db.Integer, primary_key=True, index=True)
    display_name = Column(db.String(255), nullable=False)
    description = Column(db.Text, nullable=False)
    form_href = Column(db.String(255))

    def get_choices(self):
        '''Builds a custom two-tuple for the CHOICES.

        Returns:
            Two-tuple of (ID, [name, description, href]), which can then be
            passed to :py:func:`~purchasing.opportunities.utils.select_multi_checkbox`
            to generate multi-checkbox fields
        '''
        return (self.id, [self.display_name, self.description, self.form_href])

    @classmethod
    def generate_choices(cls):
        '''Builds a list of custom CHOICES

        Returns:
            List of two-tuples described in the
            :py:meth:`RequiredBidDocument.get_choices`
            method
        '''
        return [i.get_choices() for i in cls.query.all()]
class LineItem(RefreshSearchViewMixin, Model):
    '''Model for contract line items

    Attributes:
        id: Primary key unique ID
        contract: Sqlalchemy relationship to
            :py:class:`~purchasing.data.contracts.ContractBase`
        contract_id: Foreign key to
            :py:class:`~purchasing.data.contracts.ContractBase`
        description: Description of the line item in question
        manufacturer: Name of the manufacturer of the line item
        model_number: A model number for the item
        quantity: The quantity of the item on contract
        unit_of_measure: The unit of measure (for example EACH)
        unit_cost: Cost on a per-unit basis
        total_cost: Total cost (unit_cost * quantity)
        percentage: Whether or not the unit cost should be represented
            as a percentage (NOTE: on the BidNet system, there is no
            differentiation between a percentage discount off of an item
            and actual unit cost for an item)
        company_name: Name of the company that is providing the good
        company_id: Foreign key to
            :py:class:`~purchasing.data.companies.Company`
    '''
    __tablename__ = 'line_item'

    id = Column(db.Integer, primary_key=True, index=True)
    contract = db.relationship('ContractBase',
                               backref=backref('line_items',
                                               lazy='dynamic',
                                               cascade='all, delete-orphan'))
    contract_id = ReferenceCol('contract', ondelete='CASCADE')
    description = Column(db.Text, nullable=False, index=True)
    manufacturer = Column(db.Text)
    model_number = Column(db.Text)
    quantity = Column(db.Integer)
    unit_of_measure = Column(db.String(255))
    unit_cost = Column(db.Float)
    total_cost = Column(db.Float)
    percentage = Column(db.Boolean)
    company_name = Column(db.String(255), nullable=True)
    company_id = ReferenceCol('company', nullable=True)

    def __unicode__(self):
        return self.description
예제 #14
0
class Role(SurrogatePK, RoleMixin, Model):
    '''Model to handle view-based permissions

    Attributes:
        id: primary key
        name: role name
        description: description of an individual role
    '''
    __tablename__ = 'roles'
    name = Column(db.String(80), unique=True, nullable=False)
    description = Column(db.String(255), nullable=True)

    def __repr__(self):
        return '<Role({name})>'.format(name=self.name)

    def __unicode__(self):
        return self.name

    @classmethod
    def query_factory(cls):
        '''Generates a query of all roles

        Returns:
            `sqla query`_ of all roles
        '''
        return cls.query

    @classmethod
    def no_admins(cls):
        '''Generates a query of non-admin roles

        Returns:
            `sqla query`_ of roles without administrative access
        '''
        return cls.query.filter(cls.name != 'superadmin')

    @classmethod
    def staff_factory(cls):
        '''Factory to return the staff role

        Returns:
            Role object with the name 'staff'
        '''
        return cls.query.filter(cls.name == 'staff')
예제 #15
0
class Stage(Model):
    __tablename__ = 'stage'

    id = Column(db.Integer, primary_key=True, index=True)
    name = Column(db.String(255))
    send_notifs = Column(db.Boolean, default=False, nullable=False)
    post_opportunities = Column(db.Boolean, default=False, nullable=False)

    def __unicode__(self):
        return self.name
예제 #16
0
class Role(SurrogatePK, Model):
    __tablename__ = 'roles'
    name = Column(db.String(80), unique=True, nullable=False)
    users = db.relationship('User', lazy='dynamic', backref='role')

    def __repr__(self):
        return '<Role({name})>'.format(name=self.name)

    def __unicode__(self):
        return self.name
class CompanyContact(RefreshSearchViewMixin, Model):
    '''Model for Company Contacts

    Attributes:
        id: Primary key unique ID
        company_id: Foreign key relationship to a :py:class:`~purchasing.data.companies.Company`
        company: Sqlalchemy relationship with a :py:class:`~purchasing.data.companies.Company`
        first_name: First name of the contact
        last_name: Last name of the contact
        addr1: First line of the contact's address
        addr2: Second line of the contract's address
        city: Contact address city
        state: Contact address state
        zip_code: Contact address zip code
        phone_number: Contact phone number
        fax_number: Contact fax number
        email: Contact email
    '''
    __tablename__ = 'company_contact'

    id = Column(db.Integer, primary_key=True, index=True)
    company = db.relationship(
        'Company',
        backref=backref('contacts', lazy='dynamic', cascade='all, delete-orphan')
    )
    company_id = ReferenceCol('company', ondelete='cascade')
    first_name = Column(db.String(255))
    last_name = Column(db.String(255))
    addr1 = Column(db.String(255))
    addr2 = Column(db.String(255))
    city = Column(db.String(255))
    state = Column(db.String(255))
    zip_code = Column(db.String(255))
    phone_number = Column(db.String(255))
    fax_number = Column(db.String(255))
    email = Column(db.String(255))

    def __unicode__(self):
        return '{first} {last}'.format(
            first=self.first_name, last=self.last_name
        )
예제 #18
0
class LineItem(Model):
    __tablename__ = 'line_item'

    id = Column(db.Integer, primary_key=True, index=True)
    contract = db.relationship('ContractBase', backref=backref(
        'line_items', lazy='dynamic', cascade='all, delete-orphan'
    ))
    contract_id = ReferenceCol('contract', ondelete='CASCADE')
    description = Column(db.Text, nullable=False, index=True)
    manufacturer = Column(db.Text)
    model_number = Column(db.Text)
    quantity = Column(db.Integer)
    unit_of_measure = Column(db.String(255))
    unit_cost = Column(db.Float)
    total_cost = Column(db.Float)
    percentage = Column(db.Boolean)
    company_name = Column(db.String(255), nullable=True)
    company_id = ReferenceCol('company', nullable=True)

    def __unicode__(self):
        return self.description
예제 #19
0
class Company(Model):
    __tablename__ = 'company'

    id = Column(db.Integer, primary_key=True, index=True)
    company_name = Column(db.String(255), nullable=False, unique=True, index=True)
    contracts = db.relationship(
        'ContractBase',
        secondary=company_contract_association_table,
        backref='companies',
    )

    def __unicode__(self):
        return self.company_name
예제 #20
0
class Department(SurrogatePK, Model):
    '''Department model

    Attributes:
        name: Name of department
    '''
    __tablename__ = 'department'

    name = Column(db.String(255), nullable=False, unique=True)

    def __unicode__(self):
        return self.name

    @classmethod
    def query_factory(cls):
        '''Generate a department query factory.

        Returns:
            Department query with new users filtered out
        '''
        return cls.query.filter(cls.name != 'New User')

    @classmethod
    def get_dept(cls, dept_name):
        '''Query Department by name.

        Arguments:
            dept_name: name used for query

        Returns:
            an instance of Department
        '''
        return cls.query.filter(
            db.func.lower(cls.name) == dept_name.lower()).first()

    @classmethod
    def choices(cls, blank=False):
        '''Query available departments by name and id.

        Arguments:
            blank: adds none choice to list when True,
                only returns Departments when False. Defaults to False.

        Returns:
            list of (department id, department name) tuples
        '''
        departments = [(i.id, i.name) for i in cls.query_factory().all()]
        if blank:
            departments = [(None, '-----')] + departments
        return departments
예제 #21
0
class Opportunity(Model):
    __tablename__ = 'opportunity'

    id = Column(db.Integer, primary_key=True)
    contract_id = ReferenceCol('contract', ondelete='cascade')
    created_at = Column(db.DateTime, default=datetime.datetime.utcnow())

    # Also the opportunity open date
    title = Column(db.String(255))
    department = Column(db.String(255))
    # Autopopulated using title and department plus boilerplate copy?
    description = Column(db.Text)

    category_id = ReferenceCol('category', ondelete='SET NULL')
    category = db.relationship('Category', lazy='subquery')

    # Date department opens bids
    bid_open = Column(db.DateTime)

    # Created from contract
    created_from = db.relationship('ContractBase',
                                   lazy='subquery',
                                   backref='opportunities')
class SearchView(Model):
    '''SearchView is a materialized view with all of our text columns

    See Also:
        For more detailed information about how this materialized view
        is set up, please refer to `Multi-Table Full Text Search with Postgres,
        Flask, and Sqlalchemy (Part I)
        <http://bensmithgall.com/blog/full-text-search-flask-sqlalchemy/>`_.

        For more information about Postgres Full-text search and TSVectors, refer to
        the `Postgres documentation about full-text search
        <http://www.postgresql.org/docs/current/static/textsearch-intro.html>`_

    Attributes:
        id: Primary key unique ID for result
        contract_id: Unique ID for one contract
        company_id: Unique ID for one company
        financial_id: Financial ID for a contract
        expiration_date: Date a contract expires
        contract_description: Description of the goods or services
            provided by a :py:class:`~purchasing.data.contracts.ContractBase`
        tsv_contract_description: `TSVECTOR`_ of the contract description
        company_name: Name of the company providing services from the
            :py:class:`~purchasing.data.companies.Company` model
        tsv_company_name: `TSVECTOR`_ of the company name
        detail_key: :py:class:`~purchasing.data.contracts.ContractProperty` key
        detail_value: :py:class:`~purchasing.data.contracts.ContractProperty` value
        tsv_detail_value: `TSVECTOR`_ of the detail_value
        line_item_description: Description of a line item from the
            :py:class:`~purchasing.data.contracts.LineItem` model
        tsv_line_item_description: `TSVECTOR`_ of the line item description

    '''
    __tablename__ = 'search_view'

    id = Column(db.Text, primary_key=True, index=True)
    contract_id = Column(db.Integer)
    company_id = Column(db.Integer)
    financial_id = Column(db.String(255))
    expiration_date = Column(db.Date)
    contract_description = Column(db.Text)
    tsv_contract_description = Column(TSVECTOR)
    company_name = Column(db.Text)
    tsv_company_name = Column(TSVECTOR)
    detail_key = Column(db.Text)
    detail_value = Column(db.Text)
    tsv_detail_value = Column(TSVECTOR)
    line_item_description = Column(db.Text)
    tsv_line_item_description = Column(TSVECTOR)
class ContractType(Model):
    '''Model for contract types

    Attributes:
        id: Primary key unique ID
        name: Name of the contract type
        allow_opportunities: Boolean flag as to whether to allow
            opportunities to be posted
        managed_by_conductor: Boolean flag as to whether contracts
            of these types are managed through :doc:`/conductor`
        opportunity_response_instructions: HTML string of instructions
            for bidders on how to respond to opportunities of this
            type
    '''
    __tablename__ = 'contract_type'

    id = Column(db.Integer, primary_key=True, index=True)
    name = Column(db.String(255))
    allow_opportunities = Column(db.Boolean, default=False)
    managed_by_conductor = Column(db.Boolean, default=False)
    opportunity_response_instructions = Column(db.Text)

    def __unicode__(self):
        return self.name if self.name else ''

    @classmethod
    def opportunity_type_query(cls):
        '''Query factory filtered to include only types that allow opportunities
        '''
        return cls.query.filter(cls.allow_opportunities == True)

    @classmethod
    def query_factory_all(cls):
        '''Query factory to return all contract types
        '''
        return cls.query.order_by(cls.name)

    @classmethod
    def get_type(cls, type_name):
        '''Get an individual type based on a passed type name

        Arguments:
            type_name: Name of the type to look up

        Returns:
            One :py:class:`~purchasing.data.contracts.ContractType` object
        '''
        return cls.query.filter(
            db.func.lower(cls.name) == type_name.lower()).first()
예제 #24
0
class ContractBase(Model):
    __tablename__ = 'contract'

    id = Column(db.Integer, primary_key=True)
    financial_id = Column(db.Integer)
    created_at = Column(db.DateTime, default=datetime.datetime.utcnow())
    updated_at = Column(db.DateTime, default=datetime.datetime.utcnow(), onupdate=db.func.now())
    contract_type = Column(db.String(255))
    expiration_date = Column(db.Date)
    description = Column(db.Text, index=True)
    contract_href = Column(db.Text)
    current_flow = db.relationship('Flow', lazy='subquery')
    flow_id = ReferenceCol('flow', ondelete='SET NULL', nullable=True)
    current_stage = db.relationship('Stage', lazy='subquery')
    current_stage_id = ReferenceCol('stage', ondelete='SET NULL', nullable=True)
    followers = db.relationship(
        'User',
        secondary=contract_user_association_table,
        backref='contracts_following',
    )
    starred = db.relationship(
        'User',
        secondary=contract_starred_association_table,
        backref='contracts_starred',
    )
    assigned_to = ReferenceCol('users', ondelete='SET NULL', nullable=True)
    assigned = db.relationship('User', backref=backref(
        'assignments', lazy='dynamic', cascade='none'
    ))
    is_archived = Column(db.Boolean, default=False, nullable=False)
    parent_id = Column(db.Integer, db.ForeignKey('contract.id'))
    children = db.relationship('ContractBase', backref=backref(
        'parent', remote_side=[id]
    ))

    def __unicode__(self):
        return self.description

    def get_spec_number(self):
        '''Returns the spec number for a given contract
        '''
        try:
            return [i for i in self.properties if i.key.lower() == 'spec number'][0]
        except IndexError:
            return ContractProperty()
예제 #25
0
class JobStatus(Model):
    '''Model to track nightly job status and reporting

    JobStatus has a primary compound key of name + date

    Attributes:
        name: Name of the job
        date: Date the job is scheduled for
        status: String of the job status, defaults to 'new',
            set to 'started', 'success', 'failure', or 'skipped'
        info: Any additional reporting about the job status,
            such as an error message if the job fails
    '''
    __tablename__ = 'job_status'

    name = db.Column(db.String(255), primary_key=True)
    date = db.Column(db.DateTime, primary_key=True)
    status = db.Column(db.String, default='new')
    info = db.Column(db.Text)
예제 #26
0
class OpportunityDocument(Model):
    '''Model for bid documents associated with opportunities

    Attributes:
        id: Primary key unique ID
        opportunity_id: Foreign Key relationship back to the related
            :py:class:`~purchasing.opportunities.models.Opportunity`
        opportunity: Sqlalchemy relationship back to the related
            :py:class:`~purchasing.opportunities.models.Opportunity`
        name: Name of the document for display
        href: Link to the document
    '''
    __tablename__ = 'opportunity_document'

    id = Column(db.Integer, primary_key=True, index=True)
    opportunity_id = ReferenceCol('opportunity', ondelete='cascade')
    opportunity = db.relationship('Opportunity',
                                  backref=backref(
                                      'opportunity_documents',
                                      lazy='dynamic',
                                      cascade='all, delete-orphan'))

    name = Column(db.String(255))
    href = Column(db.Text())

    def get_href(self):
        '''Builds link to the file

        Returns:
            S3 link if using S3, local filesystem link otherwise
        '''
        if current_app.config['UPLOAD_S3']:
            return self.href
        else:
            if self.href.startswith('http'):
                return self.href
            return 'file://{}'.format(self.href)

    def clean_name(self):
        '''Replaces underscores with spaces
        '''
        return self.name.replace('_', ' ')
class AppStatus(Model):
    '''Model of current application status

    Attributes:
        id: Primary key
        status: Current application status
        last_updated: Datetime of the last time the status was updated
        county_max_deadline: Datetime of the last time the county scraper
            was updated
        message: If the status is an error, the message will have more
            information about the nature of the error
        last_beacon_newsletter: Datetime of the last time a beacon
            newsletter was sent
    '''
    __tablename__ = 'app_status'

    id = Column(db.Integer, primary_key=True)
    status = Column(db.String(255))
    last_updated = Column(db.DateTime)
    county_max_deadline = Column(db.DateTime)
    message = Column(db.Text)
    last_beacon_newsletter = Column(db.DateTime)
class AcceptedEmailDomains(Model):
    '''Model of permitted email domains for new user creation

    Because authentication is handled by `persona
    <https://login.persona.org/about>`_, we still need to control
    some level of authorization. We do this on two levels. First,
    we use Role-based permissions using the :py:class:`~purchasing.data.models.Role`
    class and the :py:func:`~purchasing.decorators.requires_roles` method.
    We also do this by restricting new user creation to people who have
    a certain set of email domains.

    See Also:
        :ref:`persona`

    Attributes:
        id (int): Primary key
        domain (str): string of an acceptable domain (for example, ``pittsburghpa.gov``)
    '''
    __tablename__ = 'accepted_domains'

    id = Column(db.Integer, primary_key=True)
    domain = Column(db.String(255), unique=True)

    @classmethod
    def valid_domain(cls, domain_to_lookup):
        '''Check if a domain is in the valid domains

        Args:
            domain_to_lookup (str): string of domain to be checked

        Returns:
            bool: True if domain is valid, False otherwise
        '''
        return cls.query.filter(
            str(domain_to_lookup).lower() == db.func.lower(
                cls.domain)).count() > 0
예제 #29
0
class Vendor(Model):
    __tablename__ = 'vendor'

    id = Column(db.Integer, primary_key=True, index=True)
    business_name = Column(db.String(255), nullable=False)
    email = Column(db.String(80), unique=True, nullable=False)
    created_at = Column(db.DateTime,
                        nullable=False,
                        default=datetime.datetime.utcnow)
    first_name = Column(db.String(30), nullable=True)
    last_name = Column(db.String(30), nullable=True)
    phone_number = Column(db.String(20))
    fax_number = Column(db.String(20))
    minority_owned = Column(db.Boolean())
    veteran_owned = Column(db.Boolean())
    woman_owned = Column(db.Boolean())
    disadvantaged_owned = Column(db.Boolean())
    categories = db.relationship('Category',
                                 secondary=category_vendor_association_table,
                                 backref='vendors')

    def __unicode__(self):
        return self.email
예제 #30
0
class Vendor(Model):
    '''Base Vendor model for businesses interested in Beacon

    The primary driving thought behind Beacon is that it should be as
    easy as possible to sign up to receive updates about new opportunities.
    Therefore, there are no Vendor accounts or anything like that, just
    email addresses and business names.

    Attributes:
        id: Primary key unique ID
        business_name: Name of the business, required
        email: Email address for the vendor, required
        first_name: First name of the vendor
        last_name: Last name of the vendor
        phone_number: Phone number for the vendor
        fax_number: Fax number for the vendor
        minority_owned: Whether the vendor is minority owned
        veteran_owned: Whether the vendor is veteran owned
        woman_owned: Whether the vendor is woman owned
        disadvantaged_owned: Whether the vendor is any class
            of Disadvantaged Business Enterprise (DBE)
        categories: Many-to-many relationship with
            :py:class:`~purchasing.opportunities.models.Category`;
            describes what the vendor is subscribed to
        opportunities: Many-to-many relationship with
            :py:class:`~purchasing.opportunities.models.Opportunity`;
            describes what opportunities the vendor is subscribed to
        subscribed_to_newsletter: Whether the vendor is subscribed to
            receive the biweekly newsletter of all opportunities
    '''
    __tablename__ = 'vendor'

    id = Column(db.Integer, primary_key=True, index=True)
    business_name = Column(db.String(255), nullable=False)
    email = Column(db.String(80), unique=True, nullable=False)
    first_name = Column(db.String(30), nullable=True)
    last_name = Column(db.String(30), nullable=True)
    phone_number = Column(db.String(20))
    fax_number = Column(db.String(20))
    minority_owned = Column(db.Boolean())
    veteran_owned = Column(db.Boolean())
    woman_owned = Column(db.Boolean())
    disadvantaged_owned = Column(db.Boolean())
    categories = db.relationship('Category',
                                 secondary=category_vendor_association_table,
                                 backref='vendors',
                                 collection_class=set)
    opportunities = db.relationship(
        'Opportunity',
        secondary=opportunity_vendor_association_table,
        backref='vendors',
        collection_class=set)

    subscribed_to_newsletter = Column(db.Boolean(),
                                      default=False,
                                      nullable=False)

    @classmethod
    def newsletter_subscribers(cls):
        '''Query to return all vendors signed up to the newsletter
        '''
        return cls.query.filter(cls.subscribed_to_newsletter == True).all()

    def build_downloadable_row(self):
        '''Take a Vendor object and build a list for a .tsv download

        Returns:
            List of all vendor fields in order for a bulk vendor download
        '''
        return [
            self.first_name, self.last_name, self.business_name, self.email,
            self.phone_number, self.minority_owned, self.woman_owned,
            self.veteran_owned, self.disadvantaged_owned,
            build_downloadable_groups('category_friendly_name',
                                      self.categories),
            build_downloadable_groups('title', self.opportunities)
        ]

    def __unicode__(self):
        return self.email