def incompletes(lti=lti): enrollment_term = get_enrollment_term() stmt = db.text( """ SELECT DISTINCT u.id AS user_id, u.name, u.login_id, cnt.count FROM course_user_link cl INNER JOIN users u ON cl.user_id = u.id LEFT JOIN ( SELECT g.user_id, count(*) FROM grades g INNER JOIN courses c ON c.id = g.course_id WHERE g.grade = 'I' AND c.enrollment_term_id = :enrollment_term_id GROUP BY g.user_id ) cnt ON cnt.user_id = cl.user_id ORDER BY name; """ ).bindparams(enrollment_term_id=enrollment_term.id) results = db.session.execute(stmt) keys = ["user_id", "name", "email", "incomplete_count"] incompletes = [dict(zip(keys, res)) for res in results] return render_template( "account/incomplete_report.html", incompletes=incompletes, enrollment_term_id=enrollment_term.id, )
def dragon_time(): token = None if 'x-access-tokens' in request.headers: token = request.headers['x-access-tokens'] if not token: return jsonify({'message': 'a valid token is missing'}) try: data = jwt.decode(token, SECRET_API, algorithms='HS256') except Exception as e: return jsonify({'message': 'token is invalid'}) if data["id"] != os.getenv('DRAGON_TIME_ID'): return jsonify({'message': 'Invalid id!'}) enrollment_term = get_enrollment_term() stmt = db.text(""" SELECT u.sis_user_id , g.grade , c.sis_course_id FROM grades g JOIN courses c on c.id = g.course_id JOIN users u on u.id = g.user_id WHERE c.enrollment_term_id = :enrollment_term_id ORDER BY g.id """).bindparams(enrollment_term_id=enrollment_term.id) results = db.session.execute(stmt) return jsonify([dict(row) for row in results])
class OrganizationMembership(db.Model): __tablename__ = 'organization_memberships' __table_args__ = (UniqueConstraint("organization_uuid", "user_uuid"), ) uuid = db.Column(UUID(as_uuid=True), primary_key=True, server_default=db.text('uuid_generate_v4()')) organization_uuid = db.Column(UUID(as_uuid=True), db.ForeignKey('organizations.uuid'), index=True, nullable=False) user_uuid = db.Column(UUID(as_uuid=True), db.ForeignKey('users.uuid'), index=True, nullable=False) role = db.Column('user_role', ENUM('admin', 'write', 'read', name='user_role'), nullable=False) organizations = db.relationship("Organization", backref="memberships", lazy=True) users = db.relationship("User", backref="memberships", lazy=True) def __init__(self, org_uuid, user_uuid, role='read'): self.organization_uuid = org_uuid self.user_uuid = user_uuid self.role = role def save(self): db.session.add(self) db.session.commit() return self
class AnalysisResultField(db.Model): """Represent a single field of a single result in the database.""" __abstract__ = True kind = 'virtual' uuid = db.Column(UUID(as_uuid=True), primary_key=True, server_default=db.text('uuid_generate_v4()')) created_at = db.Column(db.DateTime, nullable=False) field_name = db.Column(db.String(256), index=True, nullable=False) stored_data = db.Column(db.String(MAX_DATA_FIELD_LENGTH), index=False, nullable=False) def __init__( # pylint: disable=too-many-arguments self, analysis_result_uuid, field_name, data=[], created_at=datetime.datetime.utcnow()): """Initialize Analysis Result model.""" self.parent_uuid = analysis_result_uuid self.field_name = field_name self.stored_data = json.dumps(data) self.created_at = created_at @property def data(self): """Return the deserialized data for this field.""" return json.loads(self.stored_data) def set_data(self, data): self.stored_data = json.dumps(data) return self.save() def serializable(self): return { 'analysis_result_field': { 'uuid': self.uuid, 'parent_uuid': self.uuid, 'field_name': self.field_name, 'created_at': self.created_at, }, 'data': self.data, } def serialize(self): return json.dumps(self.serializable()) def save(self): db.session.add(self) db.session.commit() return self
def outcome_stats(course_id): stmt = db.text(""" SELECT title, id, max(cnt) max, min(cnt) min FROM (SELECT o.id, o.title title, count(*) cnt FROM outcome_results ores JOIN outcomes o ON o.id = ores.outcome_id WHERE ores.course_id = :course_id GROUP BY ores.user_id, o.id, o.title) temp GROUP BY id, title ORDER BY max DESC; """) results = db.session.execute(stmt, dict(course_id=course_id)) return results
def course_grades(course_id): stmt = db.text(""" SELECT grade, cast( count(*) AS FLOAT)/cnt AS percent FROM (SELECT u.id, COALESCE(left(g.grade, 1), 'N/A') AS GRADE, count(*) OVER() AS cnt FROM course_user_link cl LEFT JOIN users u ON u.id = cl.user_id LEFT JOIN grades g ON g.course_id = cl.course_id AND g.user_id = cl.user_id WHERE cl.course_id = :course_id) temp GROUP BY grade, cnt ORDER BY grade; """) grades = db.session.execute(stmt, dict(course_id=course_id)) return grades
def get_user_dash_data(user_id): # Get student, which is linked to user-courses relationship table user = User.query.filter(User.id == user_id).first() current_term = EnrollmentTerm.query.filter( EnrollmentTerm.current_term).first() # get student grades grades = (user.grades.join(Course).filter( Course.enrollment_term_id == current_term.id).all()) # Get outcome results outcomes_stmt = db.text(""" SELECT ores.id AS ores_id, ores.score AS ores_score, ores.course_id AS ores_course_id, ores.user_id AS ores_user_id, ores.outcome_id AS ores_outcome_id, ores.alignment_id AS ores_alignment_id, ores.submitted_or_assessed_at AS ores_submitted_or_assessed_at, ores.last_updated AS ores_last_updated, ores.enrollment_term AS ores_enrollment_term, c.id AS c_id, c.name AS c_name, c.enrollment_term_id AS c_enrollment_term_id, c.sis_course_id AS c_sis_course_id, o.id AS o_id, o.title AS o_title, o.display_name AS o_display_name, o.calculation_int AS o_calculation_int, a.id AS a_id, a.name AS a_name FROM outcome_results ores JOIN courses c on c.id = ores.course_id JOIN outcomes o on o.id = ores.outcome_id JOIN alignments a on a.id = ores.alignment_id WHERE ores.user_id = :user_id AND ores.score IS NOT NULL AND c.enrollment_term_id = :current_term ORDER BY ores.course_id, ores.outcome_id; """) outcomes = db.session.execute( outcomes_stmt, dict(user_id=user_id, current_term=current_term.id)) # format outcome results into json format alignments = [alignment_dict(a) for a in outcomes] return alignments, grades, user
class Organization(db.Model): """MetaGenScope Organization model.""" __tablename__ = 'organizations' # pylint: disable=invalid-name id = db.Column(UUID(as_uuid=True), primary_key=True, server_default=db.text('uuid_generate_v4()')) name = db.Column(db.String(128), unique=True, nullable=False) admin_email = db.Column(db.String(128), nullable=False) created_at = db.Column(db.DateTime, nullable=False) # Use association proxy to skip association object for most cases users = association_proxy('organization_users', 'user') admin_memberships = db.relationship( 'OrganizationMembership', primaryjoin= 'and_(Organization.id==OrganizationMembership.organization_id, ' 'OrganizationMembership.role==\'admin\')', viewonly=True) admin_users = association_proxy('admin_memberships', 'user') sample_groups = db.relationship('SampleGroup', backref='organization', lazy='dynamic') def __init__(self, name, admin_email, created_at=datetime.datetime.utcnow()): """Initialize MetaGenScope Organization model.""" self.name = name self.admin_email = admin_email self.created_at = created_at def add_admin(self, admin_user): """Add admin user to organization.""" membership = OrganizationMembership.query.filter_by( user=admin_user).first() if not membership: membership = OrganizationMembership(organization=self, user=admin_user) membership.role = 'admin' db.session.commit()
def dashboard(): form = DashboardYearForm() years = db.session.query(db.extract('year', Expense.date).label('year')) \ .filter(Expense.user_id == current_user.id) \ .distinct() \ .order_by(db.text('year asc')) form.year.choices = [('', 'Year')] + [(y[0], str(y[0])) for y in years] ctx = {} if form.validate_on_submit(): year = int(form.year.data) ctx = get_context(year) elif request.method == 'GET': current_year = datetime.utcnow().year ctx = get_context(current_year) return render_template('main/dashboard.html', form=form, ctx=ctx, title='Dashboard')
class Organization(db.Model): """Represent an orgnization. One to Many relationship to SampleGroups Many to Many relationship with Users Many to One realtionship with a User as a primary admin """ __tablename__ = 'organizations' uuid = db.Column(UUID(as_uuid=True), primary_key=True, server_default=db.text('uuid_generate_v4()')) name = db.Column(db.String(128), nullable=False, unique=True) primary_admin_uuid = db.Column(db.ForeignKey('users.uuid'), nullable=False) users = association_proxy("memberships", "users") sample_groups = db.relationship('SampleGroup', backref='organization', lazy=True) is_deleted = db.Column(db.Boolean, default=False, nullable=False) is_public = db.Column(db.Boolean, default=True, nullable=False) created_at = db.Column(db.DateTime, nullable=False) def __init__(self, primary_admin_uuid, name, is_deleted=False, is_public=True, created_at=datetime.datetime.utcnow()): """Initialize Pangea User model.""" self.primary_admin_uuid = primary_admin_uuid self.name = name self.is_deleted = is_deleted self.is_public = is_public self.created_at = created_at def serializable(self): out = { 'organization': { 'uuid': self.uuid, 'name': self.name, 'is_public': self.is_public, 'is_deleted': self.is_deleted, 'created_at': self.created_at, 'primary_admin_uuid': self.primary_admin_uuid, 'sample_group_uuids': [sg.uuid for sg in self.sample_groups], 'users': [user.uuid for user in self.users], }, } return out def serialize(self): return json.dumps(self.serializable()) def add_user(self, user, role_in_org='read'): OrganizationMembership(self.uuid, user.uuid, role=role_in_org).save() return self def admin_uuids(self): return [ membership.user_uuid for membership in self.memberships if membership.role == 'admin' ] def writer_uuids(self): return [ membership.user_uuid for membership in self.memberships if membership.role in ['admin', 'write'] ] def reader_uuids(self): return [ membership.user_uuid for membership in self.memberships if membership.role in ['admin', 'write', 'read'] ] def sample_group(self, name, description='', is_library=False, is_public=True): """Return a SampleGroup bound to this organization. Create and save the SampleGroup if it does not already exist. """ sample_groups = [sg for sg in self.sample_groups if sg.name == name] if sample_groups: sample_group = sample_groups[0] else: sample_group = SampleGroup(name, self.uuid, description=description, is_library=is_library, is_public=is_public).save() return sample_group def save(self): db.session.add(self) db.session.commit() return self @classmethod def from_user(cls, user, name, is_public=True): org = cls(user.uuid, name, is_public=is_public).save() org.add_user(user, role_in_org='admin') return org.save() @classmethod def from_uuid(cls, uuid): return cls.query.filter_by(uuid=uuid).one() @classmethod def from_name(cls, name): return cls.query.filter_by(name=name).one()
class User(db.Model): """Pangea User model. To make ownership of sample libraries easier, users and organizations are treated as the same entity. """ __tablename__ = 'users' uuid = db.Column(UUID(as_uuid=True), primary_key=True, server_default=db.text('uuid_generate_v4()')) organizations = association_proxy("memberships", "organizations") username = db.Column(db.String(128), nullable=False) email = db.Column(db.String(128), unique=True, nullable=False) is_deleted = db.Column(db.Boolean, default=False, nullable=False) is_fake = db.Column(db.Boolean, default=False, nullable=False) created_at = db.Column(db.DateTime, nullable=False) __table_args__ = (db.Index('_user_lower_username_idx', func.lower(username), unique=True), ) def __init__(self, username, email, is_deleted=False, is_fake=False, created_at=datetime.datetime.utcnow()): """Initialize Pangea User model.""" self.username = username self.email = email self.is_deleted = is_deleted self.is_fake = is_fake self.created_at = created_at def save(self): db.session.add(self) db.session.commit() return self @classmethod def from_uuid(cls, uuid): return cls.query.filter_by(uuid=uuid).one() @classmethod def from_name(cls, name): return cls.query.filter_by(username=name).one() def serializable(self): out = { 'user': { 'uuid': self.uuid, 'username': self.username, 'organizations': [org.uuid for org in self.organizations], 'email': self.email, 'is_deleted': self.is_deleted, 'created_at': self.created_at, }, } return out def serialize(self): return json.dumps(self.serializable())
class AnalysisResult(db.Model): """Represent a single field of a single result in the database. Example: KrakenUniq produces a table of read-classifications and a report. These are stored separately as two separate AnalysisResults. Both ARs have the same `module_name` (e.g. KrakenUniq) Both ARs have the same owner (e.g. sample-123) Both ARs have different a `field_name` (e.g. report or read_class). `owner_uuid` should reference a group or sample. Whether it is a group or sample is determined by `owned_by_group`. This is only enforced in code. The reverse (sample->AR or group->AR) is enforced in SQL. AR Fields carry a status marker. In principle all fields of an ARs always have the same status. """ __abstract__ = True kind = 'virtual' uuid = db.Column(UUID(as_uuid=True), primary_key=True, server_default=db.text('uuid_generate_v4()')) created_at = db.Column(db.DateTime, nullable=False) module_name = db.Column(db.String(256), index=True, nullable=False) status = db.Column(ENUM(*ANALYSIS_RESULT_STATUSES, name='status'), nullable=False) def __init__( # pylint: disable=too-many-arguments self, module_name, parent_uuid, status='PENDING', created_at=datetime.datetime.utcnow()): """Initialize Analysis Result model.""" self.module_name = module_name self.parent_uuid = parent_uuid self.status = status self.created_at = created_at def field(self, field_name): """Return an AR-filed for the module bound to this AR. Create and save the AR-field if it does not already exist. """ ar_fs = [ ar_f for ar_f in self.module_fields if ar_f.field_name == field_name ] if ar_fs: return ar_fs[0] return type(self)._field_type()(self.uuid, field_name).save() def set_status(self, status): """Set status and save. Return self.""" assert status in ANALYSIS_RESULT_STATUSES self.status = status return self.save() def serializable(self): out = { 'analysis_result': { 'uuid': self.uuid, 'parent_uuid': self.uuid, 'module_name': self.module_name, 'kind': self.kind, 'status': self.status, 'created_at': self.created_at, 'fields': {} }, 'data': {}, } for field in self.module_fields: myfield = field.serializable() out['data'][field.field_name] = myfield['data'] out['analysis_result']['fields'][ field.field_name] = myfield['analysis_result_field'] return out def serialize(self): return json.dumps(self.serializable()) def save(self): db.session.add(self) db.session.commit() return self
class SampleGroup(db.Model): # pylint: disable=too-many-instance-attributes """MetaGenScope Sample Group model.""" __tablename__ = 'sample_groups' uuid = db.Column(UUID(as_uuid=True), primary_key=True, server_default=db.text('uuid_generate_v4()')) name = db.Column(db.String(128), index=True, nullable=False) organization_uuid = db.Column(db.ForeignKey('organizations.uuid'), nullable=False) description = db.Column(db.String(300), nullable=False, default='') is_library = db.Column(db.Boolean, default=False, nullable=False) is_public = db.Column(db.Boolean, default=True, nullable=False) created_at = db.Column(db.DateTime, nullable=False) samples = db.relationship('Sample', lazy=True) analysis_results = db.relationship('SampleGroupAnalysisResult', backref='parent', lazy=True) # Duplicate owner properties/indices because we don't know how we will be looking it up __table_args__ = (db.Index('_sample_group_lower_name_idx', func.lower(name), unique=True), ) def __init__( # pylint: disable=too-many-arguments self, name, organization_uuid, description='', is_library=False, is_public=True, created_at=datetime.datetime.utcnow()): """Initialize MetaGenScope User model.""" self.name = name self.organization_uuid = organization_uuid self.description = description self.is_library = is_library self.is_public = is_public self.created_at = created_at def sample(self, sample_name, metadata={}, force_new=False): """Return a sample bound to this library. Create and save the sample if it does not already exist. """ samps = [samp for samp in self.samples if samp.name == sample_name] if samps and not force_new: return samps[0] return Sample(sample_name, self.uuid, metadata=metadata).save() def analysis_result(self, module_name): """Return an AR for the module bound to this sample. Create and save the AR if it does not already exist. """ ars = [ ar for ar in self.analysis_results if ar.module_name == module_name ] if ars: result = ars[0] else: result = SampleGroupAnalysisResult(module_name, self.uuid).save() return result @property def sample_uuids(self): return [sample.uuid for sample in self.samples] @property def tools_present(self): """Return list of names for Tool Results present across all Samples in this group.""" # Cache samples samples = self.samples tools_present_in_all = set([]) for i, sample in enumerate(samples): tool_results = set(sample.tool_result_names) if i == 0: tools_present_in_all |= tool_results else: tools_present_in_all &= tool_results return list(tools_present_in_all) def serializable(self): out = { 'sample_group': { 'uuid': self.uuid, 'name': self.name, 'organization_uuid': self.organization_uuid, 'description': self.description, 'is_library': self.is_library, 'is_public': self.is_public, 'created_at': self.created_at, 'sample_uuids': [sample.uuid for sample in self.samples], 'analysis_result_uuids': [ar.uuid for ar in self.analysis_results], }, } return out def serialize(self): return json.dumps(self.serializable()) def save(self): db.session.add(self) db.session.commit() return self @classmethod def from_uuid(cls, uuid): return cls.query.filter_by(uuid=uuid).one() @classmethod def from_name(cls, name): return cls.query.filter_by(name=name).one()
class SampleGroup(db.Model): # pylint: disable=too-many-instance-attributes """MetaGenScope Sample Group model.""" __tablename__ = 'sample_groups' id = db.Column(UUID(as_uuid=True), primary_key=True, server_default=db.text('uuid_generate_v4()')) organization_id = db.Column(UUID(as_uuid=True), db.ForeignKey('organizations.id')) name = db.Column(db.String(128), unique=True, nullable=False) description = db.Column(db.String(300), nullable=False, default='') access_scheme = db.Column(db.String(128), default='public', nullable=False) theme = db.Column(db.String(16), nullable=False, default='') created_at = db.Column(db.DateTime, nullable=False) sample_placeholders = db.relationship(SamplePlaceholder) sample_ids = association_proxy('sample_placeholders', 'sample_id') analysis_result_uuid = db.Column(UUID(as_uuid=True), nullable=False) def __init__( # pylint: disable=too-many-arguments self, name, analysis_result, description='', access_scheme='public', theme='', created_at=datetime.datetime.utcnow()): """Initialize MetaGenScope User model.""" self.name = name self.description = description self.access_scheme = access_scheme self.theme = theme self.created_at = created_at self.analysis_result_uuid = analysis_result.uuid @property def samples(self): """ Get SampleGroup's associated Samples. This will hit Mongo every time it is called! Responsibility for caching the result lies on the calling method. """ return Sample.objects(uuid__in=self.sample_ids) @samples.setter def samples(self, value): """Set SampleGroup's samples.""" self.sample_ids = [sample.uuid for sample in value] @samples.deleter def samples(self): """Remove SampleGroup's samples.""" self.sample_ids = [] @property def tools_present(self): """Return list of names for Tool Results present across all Samples in this group.""" # Cache samples samples = self.samples tools_present_in_all = set([]) for i, sample in enumerate(samples): tool_results = set(sample.tool_result_names) if i == 0: tools_present_in_all |= tool_results else: tools_present_in_all &= tool_results return list(tools_present_in_all) @property def analysis_result(self): """Get sample group's analysis result model.""" return AnalysisResultMeta.objects.get(uuid=self.analysis_result_uuid) @analysis_result.setter def analysis_result(self, new_analysis_result): """Store new analysis result UUID (caller must still commit session!).""" self.analysis_result_uuid = new_analysis_result.uuid
class User(db.Model): """MetaGenScope User model.""" __tablename__ = 'users' # pylint: disable=invalid-name id = db.Column(UUID(as_uuid=True), primary_key=True, server_default=db.text('uuid_generate_v4()')) username = db.Column(db.String(128), unique=True, nullable=False) email = db.Column(db.String(128), unique=True, nullable=False) password = db.Column(db.String(255), nullable=False) active = db.Column(db.Boolean, default=True, nullable=False) admin = db.Column(db.Boolean, default=False, nullable=False) created_at = db.Column(db.DateTime, nullable=False) # Use association proxy to skip association object for most cases organizations = association_proxy('user_organizations', 'organization') def __init__(self, username, email, password, created_at=datetime.datetime.utcnow()): """Initialize MetaGenScope User model.""" self.username = username self.email = email self.password = bcrypt.generate_password_hash( password, current_app.config.get('BCRYPT_LOG_ROUNDS')).decode() self.created_at = created_at @classmethod def encode_auth_token(cls, user_id): """Generate the auth token.""" try: days = current_app.config.get('TOKEN_EXPIRATION_DAYS') seconds = current_app.config.get('TOKEN_EXPIRATION_SECONDS') payload = { 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=days, seconds=seconds), 'iat': datetime.datetime.utcnow(), 'sub': str(user_id) } return jwt.encode(payload, current_app.config.get('SECRET_KEY'), algorithm='HS256') except Exception as e: # pylint: disable=broad-except return e @staticmethod def decode_auth_token(auth_token): """Decode the auth token - :param auth_token: - :return: UUID|string.""" try: secret = current_app.config.get('SECRET_KEY') payload = jwt.decode(auth_token, secret) return uuid.UUID(payload['sub']) except jwt.ExpiredSignatureError: raise AuthenticationFailed( 'Signature expired. Please log in again.') except jwt.InvalidTokenError: raise AuthenticationFailed('Invalid token. Please log in again.')
class Sample(db.Model): """Represent a sample in the database.""" __tablename__ = 'samples' __table_args__ = ( UniqueConstraint("library_uuid", "name"), ) uuid = db.Column( UUID(as_uuid=True), primary_key=True, server_default=db.text('uuid_generate_v4()') ) created_at = db.Column(db.DateTime, nullable=False) library_uuid = db.Column( db.ForeignKey('sample_groups.uuid'), nullable=False ) name = db.Column(db.String(256), index=True, nullable=False) _sample_metadata = db.Column(db.String(10 * 1000), nullable=True) analysis_results = db.relationship( 'SampleAnalysisResult', backref='parent', lazy=True ) theme = db.Column(db.String(256), default='') def __init__( # pylint: disable=too-many-arguments self, name, library_uuid, metadata={}, created_at=datetime.utcnow()): self.library_uuid = library_uuid self.name = name metadata['name'] = name self._sample_metadata = json.dumps(metadata) self.created_at = created_at def serializable(self): out = { 'sample': { 'uuid': self.uuid, 'name': self.name, 'library_uuid': self.library_uuid, 'created_at': self.created_at, 'analysis_result_uuids': [ar.uuid for ar in self.analysis_results], }, 'sample_metadata': self.sample_metadata, } return out def serialize(self): return json.dumps(self.serializable()) def analysis_result(self, module_name): """Return an AR for the module bound to this sample. Create and save the AR if it does not already exist. """ ars = [ar for ar in self.analysis_results if ar.module_name == module_name] if ars: result = ars[0] else: result = SampleAnalysisResult(module_name, self.uuid).save() return result def set_sample_metadata(self, data): metadata = self.sample_metadata metadata.update(data) self._sample_metadata = json.dumps(metadata) return self.save() @property def sample_metadata(self): return json.loads(self._sample_metadata) def save(self): db.session.add(self) db.session.commit() return self @classmethod def from_uuid(cls, uuid): return cls.query.filter_by(uuid=uuid).one() @classmethod def from_name_library(cls, module_name, library_uuid): return cls.query.filter_by(library_uuid=library_uuid, name=module_name).one()