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
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
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)'), ))
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' ]), )
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), ))
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)
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'
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'