Exemplo n.º 1
0
class EnumeratorOnlySubmission(Submission):

    """An EnumeratorOnlySubmission must have an enumerator.

    Use an EnumeratorOnlySubmission for an EnumeratorOnlySurvey.
    """

    __tablename__ = 'submission_enumerator_only'

    id = util.pk()
    the_survey_id = sa.Column(pg.UUID, nullable=False)
    enumerator_user_id = sa.Column(
        pg.UUID, util.fk('auth_user.id'), nullable=False
    )
    enumerator = relationship('User')

    __mapper_args__ = {'polymorphic_identity': 'enumerator_only_submission'}
    __table_args__ = (
        sa.ForeignKeyConstraint(
            ['id', 'the_survey_id'], ['submission.id', 'submission.survey_id']
        ),
        sa.ForeignKeyConstraint(
            ['the_survey_id', 'enumerator_user_id'],
            ['enumerator.enumerator_only_survey_id', 'enumerator.user_id']
        ),
    )

    def _asdict(self) -> OrderedDict:
        result = super()._default_asdict()
        result['enumerator_user_id'] = self.enumerator_user_id
        result['enumerator_user_name'] = self.enumerator.name
        return result
Exemplo n.º 2
0
class PublicSubmission(Submission):

    """A PublicSubmission might have an enumerator.

    Use a PublicSubmission for a Survey.
    """

    __tablename__ = 'submission_public'

    id = util.pk()
    enumerator_user_id = sa.Column(pg.UUID, util.fk('auth_user.id'))
    enumerator = relationship('User')
    survey_type = sa.Column(survey_type_enum, nullable=False)

    __table_args__ = (
        sa.ForeignKeyConstraint(
            ['id', 'survey_type'],
            ['submission.id', 'submission.survey_type'],
            onupdate='CASCADE', ondelete='CASCADE'
        ),
        sa.CheckConstraint("survey_type::TEXT = 'public'"),
    )

    __mapper_args__ = {'polymorphic_identity': 'public_submission'}

    def _asdict(self):
        result = super()._default_asdict()
        if self.enumerator_user_id is not None:
            result['enumerator_user_id'] = self.enumerator_user_id
            result['enumerator_user_name'] = self.enumerator.name
        return result
Exemplo n.º 3
0
class PhotoAnswer(_AnswerMixin, Answer):
    """A photo answer (the id of a Photo)."""

    __tablename__ = 'answer_photo'
    main_answer = sa.Column(pg.UUID, unique=True)
    actual_photo_id = sa.Column(pg.UUID, util.fk('photo.id'))
    answer = synonym('main_answer')
    photo = relationship('Photo')
    __mapper_args__ = {'polymorphic_identity': 'photo'}
    __table_args__ = (_answer_mixin_table_args() + (sa.CheckConstraint(
        '(actual_photo_id IS NULL) != (main_answer = actual_photo_id)'), ))
Exemplo n.º 4
0
class NonAnswerableSurveyNode(SurveyNode):
    """Contains a Node which is not answerable (e.g., a Note)."""

    __tablename__ = 'survey_node_non_answerable'

    id = util.pk()
    the_node_id = sa.Column(pg.UUID, util.fk('note.id'), nullable=False)
    the_type_constraint = sa.Column(node_type_enum, nullable=False)
    node = relationship('Note')

    __mapper_args__ = {'polymorphic_identity': 'non_answerable'}
    __table_args__ = (sa.ForeignKeyConstraint(
        ['id', 'the_node_id', 'the_type_constraint'], [
            'survey_node.id', 'survey_node.node_id',
            'survey_node.type_constraint'
        ]), )
Exemplo n.º 5
0
class Email(Base):

    """Models an e-mail address."""

    __tablename__ = 'email'

    id = util.pk()
    address = sa.Column(
        pg.TEXT, sa.CheckConstraint("address ~ '.*@.*'"),
        nullable=False, unique=True
    )
    user_id = sa.Column(pg.UUID, util.fk('auth_user.id'), nullable=False)
    last_update_time = util.last_update_time()

    def _asdict(self) -> OrderedDict:
        return OrderedDict((
            ('id', self.id),
            ('address', self.address),
            ('user', self.user.name),
            ('last_update_time', self.last_update_time),
        ))
Exemplo n.º 6
0
class Survey(Base):
    """A Survey has a list of SurveyNodes.

    Use an EnumeratorOnlySurvey to restrict submissions to enumerators.
    """

    __tablename__ = 'survey'

    id = util.pk()
    containing_id = sa.Column(pg.UUID,
                              unique=True,
                              server_default=sa.func.uuid_generate_v4())
    languages = util.languages_column('languages')
    title = util.json_column('title')
    url_slug = sa.Column(
        pg.TEXT,
        sa.CheckConstraint("url_slug != '' AND "
                           "url_slug !~ '[%%#;/?:@&=+$,\s]' AND "
                           "url_slug !~ '{}'".format(util.UUID_REGEX),
                           name='url_safe_slug'),
        unique=True,
    )
    default_language = sa.Column(
        pg.TEXT,
        sa.CheckConstraint("default_language != ''",
                           name='non_empty_default_language'),
        nullable=False,
        server_default='English',
    )
    survey_type = sa.Column(survey_type_enum, nullable=False)
    administrators = relationship(
        'Administrator',
        secondary=_administrator_table,
        backref='admin_surveys',
        passive_deletes=True,
    )
    submissions = relationship(
        'Submission',
        order_by='Submission.save_time',
        backref='survey',
        cascade='all, delete-orphan',
        passive_deletes=True,
    )

    # dokomoforms.models.column_properties
    # num_submissions
    # earliest_submission_time
    # latest_submission_time

    # TODO: expand upon this
    version = sa.Column(sa.Integer, nullable=False, server_default='1')
    # ODOT
    creator_id = sa.Column(pg.UUID,
                           util.fk('administrator.id'),
                           nullable=False)

    # This is survey_metadata rather than just metadata because all models
    # have a metadata attribute which is important for SQLAlchemy.
    survey_metadata = util.json_column('survey_metadata', default='{}')

    created_on = sa.Column(
        pg.TIMESTAMP(timezone=True),
        nullable=False,
        server_default=current_timestamp(),
    )
    nodes = relationship(
        'SurveyNode',
        order_by='SurveyNode.node_number',
        collection_class=ordering_list('node_number'),
        cascade='all, delete-orphan',
        passive_deletes=True,
    )
    last_update_time = util.last_update_time()

    __mapper_args__ = {
        'polymorphic_on': survey_type,
        'polymorphic_identity': 'public',
    }
    __table_args__ = (
        sa.Index(
            'unique_survey_title_in_default_language_per_user',
            sa.column(quoted_name('(title->>default_language)', quote=False)),
            'creator_id',
            unique=True,
        ),
        sa.UniqueConstraint('id', 'containing_id', 'survey_type'),
        sa.UniqueConstraint('id', 'containing_id', 'languages'),
        util.languages_constraint('title', 'languages'),
        sa.CheckConstraint("languages @> ARRAY[default_language]",
                           name='default_language_in_languages_exists'),
        sa.CheckConstraint("(title->>default_language) != ''",
                           name='title_in_default_langauge_non_empty'),
    )

    def _asdict(self) -> OrderedDict:
        return OrderedDict((
            ('id', self.id),
            ('deleted', self.deleted),
            ('languages', self.languages),
            ('title', OrderedDict(sorted(self.title.items()))),
            ('url_slug', self.url_slug),
            ('default_language', self.default_language),
            ('survey_type', self.survey_type),
            ('version', self.version),
            ('creator_id', self.creator_id),
            ('creator_name', self.creator.name),
            ('metadata', self.survey_metadata),
            ('created_on', self.created_on),
            ('last_update_time', self.last_update_time),
            ('nodes', self.nodes),
        ))

    def _sequentialize(self, *, include_non_answerable=True):
        """Generate a pre-order traversal of this survey's nodes.

        https://en.wikipedia.org/wiki/Tree_traversal#Depth-first
        """
        for node in self.nodes:
            if isinstance(node, NonAnswerableSurveyNode):
                if include_non_answerable:
                    yield node
                else:
                    # See https://bitbucket.org/ned/coveragepy/issues/198/
                    continue  # pragma: no cover
            else:
                yield node
                for sub_survey in node.sub_surveys:
                    yield from Survey._sequentialize(
                        sub_survey,
                        include_non_answerable=include_non_answerable)
Exemplo n.º 7
0
from dokomoforms.exc import NoSuchBucketTypeError

survey_type_enum = sa.Enum(
    'public',
    'enumerator_only',
    name='enumerator_only_enum',
    inherit_schema=True,
)

_administrator_table = sa.Table(
    'survey_administrator',
    Base.metadata,
    sa.Column(
        'survey_id',
        pg.UUID,
        util.fk('survey.id'),
        nullable=False,
    ),
    sa.Column('user_id', pg.UUID, util.fk('administrator.id'), nullable=False),
    sa.UniqueConstraint('survey_id', 'user_id'),
)


class Survey(Base):
    """A Survey has a list of SurveyNodes.

    Use an EnumeratorOnlySurvey to restrict submissions to enumerators.
    """

    __tablename__ = 'survey'
Exemplo n.º 8
0

survey_type_enum = sa.Enum(
    'public', 'enumerator_only',
    name='enumerator_only_enum',
    inherit_schema=True,
)


_administrator_table = sa.Table(
    'survey_administrator',
    Base.metadata,
    sa.Column(
        'survey_id',
        pg.UUID,
        util.fk('survey.id'),
        nullable=False,
    ),
    sa.Column('user_id', pg.UUID, util.fk('administrator.id'), nullable=False),
    sa.UniqueConstraint('survey_id', 'user_id'),
)


class Survey(Base):

    """A Survey has a list of SurveyNodes.

    Use an EnumeratorOnlySurvey to restrict submissions to enumerators.
    """

    __tablename__ = 'survey'