class AnnotationOnSpectrum(Base, AnnotationMixin): __tablename__ = 'annotations_on_spectra' create = AccessibleIfRelatedRowsAreAccessible(obj='read', spectrum='read') read = accessible_by_groups_members & AccessibleIfRelatedRowsAreAccessible( obj='read', spectrum='read', ) update = delete = AccessibleIfUserMatches('author') spectrum_id = sa.Column( sa.ForeignKey('spectra.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the Annotation's Spectrum.", ) spectrum = relationship( 'Spectrum', back_populates='annotations', doc="The Spectrum referred to by this annotation.", ) __table_args__ = (UniqueConstraint('spectrum_id', 'origin'), )
class ObjAnalysis(Base, AnalysisMixin, WebhookMixin): """Analysis on an Obj with a set of results as JSON""" __tablename__ = 'obj_analyses' create = AccessibleIfRelatedRowsAreAccessible(obj='read') read = accessible_by_groups_members & AccessibleIfRelatedRowsAreAccessible( obj='read' ) update = delete = AccessibleIfUserMatches('author') @declared_attr def obj_id(cls): return sa.Column( sa.ForeignKey('objs.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the ObjAnalysis's Obj.", ) @declared_attr def obj(cls): return relationship( 'Obj', back_populates=cls.backref_name(), doc="The ObjAnalysis's Obj.", )
class Comment(Base, CommentMixin): """A comment made by a User or a Robot (via the API) on a Source.""" create = AccessibleIfRelatedRowsAreAccessible(obj='read') read = accessible_by_groups_members & AccessibleIfRelatedRowsAreAccessible( obj='read') update = delete = AccessibleIfUserMatches('author')
class Annotation(Base, AnnotationMixin): """A sortable/searchable Annotation on a source, made by a filter or other robot, with a set of data as JSON""" create = AccessibleIfRelatedRowsAreAccessible(obj='read') read = accessible_by_groups_members & AccessibleIfRelatedRowsAreAccessible( obj='read') update = delete = AccessibleIfUserMatches('author') __table_args__ = (UniqueConstraint('obj_id', 'origin'), )
class SourceNotification(Base): create = read = AccessibleIfRelatedRowsAreAccessible(source='read') update = delete = AccessibleIfUserMatches('sent_by') groups = relationship( "Group", secondary="group_notifications", cascade="save-update, merge, refresh-expire, expunge", passive_deletes=True, ) sent_by_id = sa.Column( sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, doc="The ID of the User who sent this notification.", ) sent_by = relationship( "User", back_populates="source_notifications", foreign_keys=[sent_by_id], doc="The User who sent this notification.", ) source_id = sa.Column( sa.ForeignKey("objs.id", ondelete="CASCADE"), nullable=False, index=True, doc="ID of the target Obj.", ) source = relationship('Obj', back_populates='obj_notifications', doc='The target Obj.') additional_notes = sa.Column(sa.String(), nullable=True) level = sa.Column(sa.String(), nullable=False)
class SurveyEfficiencyForObservations(Base, SurveyEfficiencyAnalysisMixin): """A request for an SurveyEfficiencyAnalysis from a set of observations.""" __tablename__ = 'survey_efficiency_for_observations' create = AccessibleIfRelatedRowsAreAccessible(gcnevent='read') read = accessible_by_groups_members & AccessibleIfRelatedRowsAreAccessible( gcnevent='read') gcnevent = relationship( 'GcnEvent', back_populates='survey_efficiency_analyses', doc="The target GcnEvent.", ) gcnevent_id = sa.Column( sa.ForeignKey('gcnevents.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the target GcnEvent.", ) localization = relationship( 'Localization', back_populates='survey_efficiency_analyses', doc="The target Localization.", ) localization_id = sa.Column( sa.ForeignKey('localizations.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the target Localization.", ) instrument_id = sa.Column( sa.ForeignKey('instruments.id', ondelete="CASCADE"), nullable=False, doc='Instrument ID', ) instrument = relationship( "Instrument", foreign_keys=instrument_id, doc="The Instrument that this efficiency analysis belongs to", )
class SurveyEfficiencyForObservationPlan(Base, SurveyEfficiencyAnalysisMixin): """A request for an SurveyEfficiencyAnalysis from an observation plan.""" __tablename__ = 'survey_efficiency_for_observation_plans' create = AccessibleIfRelatedRowsAreAccessible(observation_plan='read') read = accessible_by_groups_members & AccessibleIfRelatedRowsAreAccessible( observation_plan='read') observation_plan_id = sa.Column( sa.ForeignKey('eventobservationplans.id', ondelete="CASCADE"), nullable=False, doc='Event observation plan ID', ) observation_plan = relationship( "EventObservationPlan", foreign_keys=observation_plan_id, doc= "The EventObservationPlan that this survey efficiency analysis belongs to", )
class Comment(Base, CommentMixin): """A comment made by a User or a Robot (via the API) on a Source.""" create = AccessibleIfRelatedRowsAreAccessible(obj='read') read = accessible_by_groups_members & AccessibleIfRelatedRowsAreAccessible( obj='read') update = delete = AccessibleIfUserMatches('author') obj_id = sa.Column( sa.ForeignKey('objs.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the Comment's Obj.", ) obj = relationship( 'Obj', back_populates='comments', doc="The Comment's Obj.", )
class CommentOnGCN(Base, CommentMixin): __tablename__ = 'comments_on_gcns' create = AccessibleIfRelatedRowsAreAccessible(gcn='read') read = accessible_by_groups_members & AccessibleIfRelatedRowsAreAccessible( spectrum='read', ) update = delete = AccessibleIfUserMatches('author') gcn_id = sa.Column( sa.ForeignKey('gcnevents.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the Comment's GCN.", ) gcn = relationship( 'GcnEvent', back_populates='comments', doc="The GcnEvent referred to by this comment.", )
class CommentOnShift(Base, CommentMixin): __tablename__ = 'comments_on_shifts' create = AccessibleIfRelatedRowsAreAccessible(shift='read') read = accessible_by_groups_members & AccessibleIfRelatedRowsAreAccessible( shift='read', ) update = delete = AccessibleIfUserMatches('author') shift_id = sa.Column( sa.ForeignKey('shifts.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the Comment's Shift.", ) shift = relationship( 'Shift', back_populates='comments', doc="The Shift referred to by this comment.", )
class CommentOnSpectrum(Base, CommentMixin): __tablename__ = 'comments_on_spectra' create = AccessibleIfRelatedRowsAreAccessible(obj='read', spectrum='read') read = accessible_by_groups_members & AccessibleIfRelatedRowsAreAccessible( obj='read', spectrum='read', ) update = delete = AccessibleIfUserMatches('author') obj_id = sa.Column( sa.ForeignKey('objs.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the Comment's Obj.", ) obj = relationship( 'Obj', back_populates='comments_on_spectra', doc="The Comment's Obj.", ) spectrum_id = sa.Column( sa.ForeignKey('spectra.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the Comment's Spectrum.", ) spectrum = relationship( 'Spectrum', back_populates='comments', doc="The Spectrum referred to by this comment.", )
class Filter(Base): """An alert filter that operates on a Stream. A Filter is associated with exactly one Group, and a Group may have multiple operational Filters. """ # TODO: Track filter ownership and allow owners to update, delete filters create = (read) = ( update ) = delete = accessible_by_group_members & AccessibleIfRelatedRowsAreAccessible( stream="read") name = sa.Column(sa.String, nullable=False, unique=False, doc="Filter name.") stream_id = sa.Column( sa.ForeignKey("streams.id", ondelete="CASCADE"), nullable=False, index=True, doc="ID of the Filter's Stream.", ) stream = relationship( "Stream", foreign_keys=[stream_id], back_populates="filters", doc="The Filter's Stream.", ) group_id = sa.Column( sa.ForeignKey("groups.id", ondelete="CASCADE"), nullable=False, index=True, doc="ID of the Filter's Group.", ) group = relationship( "Group", foreign_keys=[group_id], back_populates="filters", doc="The Filter's Group.", ) candidates = relationship( 'Candidate', back_populates='filter', cascade='save-update, merge, refresh-expire, expunge', passive_deletes=True, order_by="Candidate.passed_at", doc="Candidates that have passed the filter.", )
class DefaultObservationPlanRequest(Base): """A default request for an EventObservationPlan.""" # TODO: Make read-accessible via target groups create = read = AccessibleIfRelatedRowsAreAccessible(allocation="read") update = delete = ( (AccessibleIfUserMatches('allocation.group.users') | AccessibleIfUserMatches('requester')) & read) | CustomUserAccessControl(updatable_by_token_with_listener_acl) requester_id = sa.Column( sa.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, index=True, doc= "ID of the User who requested the default observation plan request.", ) requester = relationship( User, back_populates='default_observationplan_requests', doc="The User who requested the default requests.", foreign_keys=[requester_id], ) payload = sa.Column( psql.JSONB, nullable=False, doc="Content of the default observation plan request.", ) allocation_id = sa.Column(sa.ForeignKey('allocations.id', ondelete='CASCADE'), nullable=False, index=True) allocation = relationship('Allocation', back_populates='default_observation_plans') target_groups = relationship( 'Group', secondary='default_observationplan_groups', passive_deletes=True, doc= 'Groups to share the resulting data from this default request with.', )
class Thumbnail(Base): """Thumbnail image centered on the location of an Obj.""" create = read = AccessibleIfRelatedRowsAreAccessible(obj='read') # TODO delete file after deleting row type = sa.Column(thumbnail_types, doc='Thumbnail type (e.g., ref, new, sub, dr8, ps1, ...)') file_uri = sa.Column( sa.String(), nullable=True, index=False, unique=False, doc="Path of the Thumbnail on the machine running SkyPortal.", ) public_url = sa.Column( sa.String(), nullable=True, index=False, unique=False, doc="Publically accessible URL of the thumbnail.", ) origin = sa.Column(sa.String, nullable=True, doc="Origin of the Thumbnail.") obj = relationship( 'Obj', back_populates='thumbnails', uselist=False, doc="The Thumbnail's Obj.", ) obj_id = sa.Column( sa.ForeignKey('objs.id', ondelete='CASCADE'), index=True, nullable=False, doc="ID of the thumbnail's obj.", ) is_grayscale = sa.Column( sa.Boolean(), nullable=False, default=False, doc= "Boolean indicating whether the thumbnail is (mostly) grayscale or not.", )
class Allocation(Base): """An allocation of observing time on a robotic instrument.""" create = (read) = ( update ) = delete = accessible_by_group_members & AccessibleIfRelatedRowsAreAccessible( instrument='read') pi = sa.Column(sa.String, doc="The PI of the allocation's proposal.") proposal_id = sa.Column( sa.String, doc="The ID of the proposal associated with this allocation.") start_date = sa.Column(sa.DateTime, doc='The UTC start date of the allocation.') end_date = sa.Column(sa.DateTime, doc='The UTC end date of the allocation.') hours_allocated = sa.Column(sa.Float, nullable=False, doc='The number of hours allocated.') requests = relationship( 'FollowupRequest', back_populates='allocation', doc='The requests made against this allocation.', passive_deletes=True, ) observation_plans = relationship( 'ObservationPlanRequest', back_populates='allocation', doc='The observing plan requests for this allocation.', passive_deletes=True, ) group_id = sa.Column( sa.ForeignKey('groups.id', ondelete='CASCADE'), index=True, doc='The ID of the Group the allocation is associated with.', nullable=False, ) group = relationship( 'Group', back_populates='allocations', doc='The Group the allocation is associated with.', ) instrument_id = sa.Column( sa.ForeignKey('instruments.id', ondelete='CASCADE'), index=True, doc="The ID of the Instrument the allocation is associated with.", nullable=False, ) instrument = relationship( 'Instrument', back_populates='allocations', doc="The Instrument the allocation is associated with.", ) _altdata = sa.Column( EncryptedType(JSONType, cfg['app.secret_key'], AesEngine, 'pkcs5')) @property def altdata(self): if self._altdata is None: return {} else: return json.loads(self._altdata) @altdata.setter def altdata(self, value): self._altdata = value
class ObservationPlanRequest(Base): """A request for an EventObservationPlan.""" # TODO: Make read-accessible via target groups create = read = AccessibleIfRelatedRowsAreAccessible(gcnevent="read", allocation="read") update = delete = ( (AccessibleIfUserMatches('allocation.group.users') | AccessibleIfUserMatches('requester')) & read) | CustomUserAccessControl(updatable_by_token_with_listener_acl) requester_id = sa.Column( sa.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, index=True, doc="ID of the User who requested the follow-up.", ) requester = relationship( User, back_populates='observationplan_requests', doc="The User who requested the follow-up.", foreign_keys=[requester_id], ) last_modified_by_id = sa.Column( sa.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, doc="The ID of the User who last modified the request.", ) last_modified_by = relationship( User, doc="The user who last modified the request.", foreign_keys=[last_modified_by_id], ) gcnevent = relationship( 'GcnEvent', back_populates='observationplan_requests', doc="The target GcnEvent.", ) gcnevent_id = sa.Column( sa.ForeignKey('gcnevents.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the target GcnEvent.", ) localization = relationship( 'Localization', back_populates='observationplan_requests', doc="The target Localization.", ) localization_id = sa.Column( sa.ForeignKey('localizations.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the target Localization.", ) payload = sa.Column(psql.JSONB, nullable=False, doc="Content of the observation plan request.") status = sa.Column( sa.String(), nullable=False, default="pending submission", index=True, doc="The status of the request.", ) allocation_id = sa.Column(sa.ForeignKey('allocations.id', ondelete='CASCADE'), nullable=False, index=True) allocation = relationship('Allocation', back_populates='observation_plans') observation_plans = relationship( 'EventObservationPlan', passive_deletes=True, doc='Observation plans associated with this request.', ) target_groups = relationship( 'Group', secondary='observationplan_groups', passive_deletes=True, doc='Groups to share the resulting data from this request with.', ) transactions = relationship( 'FacilityTransaction', back_populates='observation_plan_request', passive_deletes=True, order_by="FacilityTransaction.created_at.desc()", ) @property def instrument(self): return self.allocation.instrument
Returns ------- tax : `skyportal.models.Taxonomy` The requested Taxonomy. """ return (Taxonomy.query.filter(Taxonomy.id == taxonomy_id).filter( Taxonomy.groups.any( Group.id.in_([g.id for g in user_or_token.accessible_groups]))).all()) # To create or read a classification, you must have read access to the # underlying taxonomy, and be a member of at least one of the # classification's target groups ok_if_tax_and_obj_readable = AccessibleIfRelatedRowsAreAccessible( taxonomy='read', obj='read') class Taxonomy(Base): """An ontology within which Objs can be classified.""" # TODO: Add ownership logic to taxonomy read = accessible_by_groups_members __tablename__ = 'taxonomies' name = sa.Column( sa.String, nullable=False, doc='Short string to make this taxonomy memorable to end users.', ) hierarchy = sa.Column(
class FollowupRequest(Base): """A request for follow-up data (spectroscopy, photometry, or both) using a robotic instrument.""" # TODO: Make read-accessible via target groups create = read = AccessibleIfRelatedRowsAreAccessible(obj="read", allocation="read") update = delete = ( (AccessibleIfUserMatches('allocation.group.users') | AccessibleIfUserMatches('requester')) & read) | CustomUserAccessControl(updatable_by_token_with_listener_acl) requester_id = sa.Column( sa.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, index=True, doc="ID of the User who requested the follow-up.", ) requester = relationship( User, back_populates='followup_requests', doc="The User who requested the follow-up.", foreign_keys=[requester_id], ) last_modified_by_id = sa.Column( sa.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, doc="The ID of the User who last modified the request.", ) last_modified_by = relationship( User, doc="The user who last modified the request.", foreign_keys=[last_modified_by_id], ) obj = relationship('Obj', back_populates='followup_requests', doc="The target Obj.") obj_id = sa.Column( sa.ForeignKey('objs.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the target Obj.", ) payload = sa.Column(psql.JSONB, nullable=False, doc="Content of the followup request.") status = sa.Column( sa.String(), nullable=False, default="pending submission", index=True, doc="The status of the request.", ) allocation_id = sa.Column(sa.ForeignKey('allocations.id', ondelete='CASCADE'), nullable=False, index=True) allocation = relationship('Allocation', back_populates='requests') transactions = relationship( 'FacilityTransaction', back_populates='followup_request', passive_deletes=True, order_by="FacilityTransaction.created_at.desc()", ) target_groups = relationship( 'Group', secondary='request_groups', passive_deletes=True, doc='Groups to share the resulting data from this request with.', ) photometry = relationship('Photometry', back_populates='followup_request') spectra = relationship('Spectrum', back_populates='followup_request') @property def instrument(self): return self.allocation.instrument
class ClassicalAssignment(Base): """Assignment of an Obj to an Observing Run as a target.""" create = read = update = delete = AccessibleIfRelatedRowsAreAccessible( obj='read', run='read') requester_id = sa.Column( sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, doc="The ID of the User who created this assignment.", ) requester = relationship( "User", back_populates="assignments", foreign_keys=[requester_id], doc="The User who created this assignment.", ) last_modified_by_id = sa.Column( sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True, default=None, index=True, ) last_modified_by = relationship("User", foreign_keys=[last_modified_by_id]) obj = relationship('Obj', back_populates='assignments', doc='The assigned Obj.') obj_id = sa.Column( sa.ForeignKey('objs.id', ondelete='CASCADE'), nullable=False, index=True, doc='ID of the assigned Obj.', ) comment = sa.Column( sa.String(), doc="A comment on the assignment. " "Typically a justification for the request, " "or instructions for taking the data.", ) status = sa.Column( sa.String(), nullable=False, default="pending", doc='Status of the assignment [done, not done, pending].', ) priority = sa.Column( followup_priorities, nullable=False, doc='Priority of the request (1 = lowest, 5 = highest).', ) spectra = relationship( "Spectrum", back_populates="assignment", doc="Spectra produced by the assignment.", ) photometry = relationship( "Photometry", back_populates="assignment", doc="Photometry produced by the assignment.", ) run = relationship( 'ObservingRun', back_populates='assignments', doc="The ObservingRun this target was assigned to.", ) run_id = sa.Column( sa.ForeignKey('observingruns.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the ObservingRun this target was assigned to.", ) @hybrid_property def instrument(self): """The instrument in use on the assigned ObservingRun.""" return self.run.instrument @property def rise_time(self): """The UTC time at which the object rises on this run.""" target = self.obj.target return self.run.rise_time(target) @property def set_time(self): """The UTC time at which the object sets on this run.""" target = self.obj.target return self.run.set_time(target)