class GcnTag(Base): """Store qualitative tags for events.""" update = delete = AccessibleIfUserMatches('sent_by') sent_by_id = sa.Column( sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False, index=True, doc="The ID of the User who created this GcnTag.", ) sent_by = relationship( "User", foreign_keys=sent_by_id, back_populates="gcntags", doc="The user that saved this GcnTag", ) dateobs = sa.Column( sa.ForeignKey('gcnevents.dateobs', ondelete="CASCADE"), nullable=False, index=True, ) text = sa.Column(sa.Unicode, nullable=False)
class UserNotification(Base): read = update = delete = AccessibleIfUserMatches('user') user_id = sa.Column( sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, doc="ID of the associated User", ) user = relationship( "User", back_populates="notifications", doc="The associated User", ) text = sa.Column( sa.String(), nullable=False, doc="The notification text to display", ) viewed = sa.Column( sa.Boolean, nullable=False, default=False, index=True, doc="Boolean indicating whether notification has been viewed.", ) url = sa.Column( sa.String(), nullable=True, doc="URL to which to direct upon click, if relevant", )
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 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 Listing(Base): create = read = update = delete = AccessibleIfUserMatches("user") user_id = sa.Column( sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False, index=True, doc="The ID of the User who created this Listing.", ) user = relationship( "User", foreign_keys=user_id, back_populates="listings", doc="The user that saved this object/listing", ) obj_id = sa.Column( sa.ForeignKey('objs.id', ondelete='CASCADE'), nullable=False, index=True, doc="The ID of the object that is on this Listing", ) obj = relationship( "Obj", doc="The object referenced by this listing", ) list_name = sa.Column( sa.String, index=True, nullable=False, doc="Name of the list, e.g., 'favorites'. ", )
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 Classification(Base): """Classification of an Obj.""" create = ok_if_tax_and_obj_readable read = accessible_by_groups_members & ok_if_tax_and_obj_readable update = delete = AccessibleIfUserMatches('author') classification = sa.Column(sa.String, nullable=False, index=True, doc="The assigned class.") taxonomy_id = sa.Column( sa.ForeignKey('taxonomies.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the Taxonomy in which this Classification was made.", ) taxonomy = relationship( 'Taxonomy', back_populates='classifications', doc="Taxonomy in which this Classification was made.", ) probability = sa.Column( sa.Float, doc='User-assigned probability of belonging to this class', nullable=True, index=True, ) author_id = sa.Column( sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the User that made this Classification", ) author = relationship('User', doc="The User that made this classification.") author_name = sa.Column( sa.String, nullable=False, doc="User.username or Token.id " "of the Classification's author.", ) obj_id = sa.Column( sa.ForeignKey('objs.id', ondelete='CASCADE'), nullable=False, index=True, doc="ID of the Classification's Obj.", ) obj = relationship('Obj', back_populates='classifications', doc="The Classification's Obj.") groups = relationship( "Group", secondary="group_classifications", cascade="save-update, merge, refresh-expire, expunge", passive_deletes=True, doc="Groups that can access this Classification.", )
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 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 GroupAdmissionRequest(Base): """Table tracking requests from users to join groups.""" read = AccessibleIfUserMatches('user') | accessible_by_group_admins create = delete = AccessibleIfUserMatches('user') update = accessible_by_group_admins user_id = sa.Column( sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, doc="ID of the User requesting to join the group", ) user = relationship( "User", foreign_keys=[user_id], back_populates="group_admission_requests", doc="The User requesting to join a group", ) group_id = sa.Column( sa.ForeignKey("groups.id", ondelete="CASCADE"), nullable=False, index=True, doc="ID of the Group to which admission is requested", ) group = relationship( "Group", foreign_keys=[group_id], back_populates="admission_requests", doc="The Group to which admission is requested", ) status = sa.Column( sa.Enum( "pending", "accepted", "declined", name="admission_request_status", validate_strings=True, ), nullable=False, default="pending", doc=("Admission request status. Can be one of either 'pending', " "'accepted', or 'declined'."), )
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 Stream(Base): """A data stream producing alerts that can be programmatically filtered using a Filter.""" read = AccessibleIfUserMatches('users') create = update = delete = restricted name = sa.Column(sa.String, unique=True, nullable=False, doc="Stream name.") altdata = sa.Column( JSONB, nullable=True, doc="Misc. metadata stored in JSON format, e.g. " "`{'collection': 'ZTF_alerts', selector: [1, 2]}`", ) groups = relationship( 'Group', secondary='group_streams', back_populates='streams', passive_deletes=True, doc="The Groups with access to this Stream.", ) users = relationship( 'User', secondary='stream_users', back_populates='streams', passive_deletes=True, doc="The users with access to this stream.", ) filters = relationship( 'Filter', back_populates='stream', passive_deletes=True, doc="The filters with access to this stream.", ) photometry = relationship( "Photometry", secondary="stream_photometry", back_populates="streams", cascade="save-update, merge, refresh-expire, expunge", passive_deletes=True, doc='The photometry associated with this stream.', ) photometric_series = relationship( "PhotometricSeries", secondary="stream_photometric_series", back_populates="streams", cascade="save-update, merge, refresh-expire, expunge", passive_deletes=True, doc='Photometric series associated with this stream.', )
class Candidate(Base): "An Obj that passed a Filter, becoming scannable on the Filter's scanning page." create = read = update = delete = AccessibleIfUserMatches( 'filter.group.group_users.user') obj_id = sa.Column( sa.ForeignKey("objs.id", ondelete="CASCADE"), nullable=False, index=True, doc="ID of the Obj", ) obj = relationship( "Obj", foreign_keys=[obj_id], back_populates="candidates", doc="The Obj that passed a filter", ) filter_id = sa.Column( sa.ForeignKey("filters.id", ondelete="CASCADE"), nullable=False, index=True, doc="ID of the filter the candidate passed", ) filter = relationship( "Filter", foreign_keys=[filter_id], back_populates="candidates", doc="The filter that the Candidate passed", ) passed_at = sa.Column( sa.DateTime, nullable=False, index=True, doc="ISO UTC time when the Candidate passed the Filter.", ) passing_alert_id = sa.Column( sa.BigInteger, index=True, doc="ID of the latest Stream alert that passed the Filter.", ) uploader_id = sa.Column( sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, doc="ID of the user that posted the candidate", )
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 Invitation(Base): read = update = delete = AccessibleIfUserMatches('invited_by') token = sa.Column(sa.String(), nullable=False, unique=True) role_id = sa.Column( sa.ForeignKey('roles.id'), nullable=False, ) role = relationship( "Role", cascade="save-update, merge, refresh-expire, expunge", passive_deletes=True, uselist=False, ) groups = relationship( "Group", secondary="group_invitations", cascade="save-update, merge, refresh-expire, expunge", passive_deletes=True, ) streams = relationship( "Stream", secondary="stream_invitations", cascade="save-update, merge, refresh-expire, expunge", passive_deletes=True, ) admin_for_groups = sa.Column(psql.ARRAY(sa.Boolean), nullable=False) can_save_to_groups = sa.Column(psql.ARRAY(sa.Boolean), nullable=False) user_email = sa.Column(EmailType(), nullable=True) invited_by = relationship( "User", secondary="user_invitations", cascade="save-update, merge, refresh-expire, expunge", passive_deletes=True, uselist=False, ) used = sa.Column(sa.Boolean, nullable=False, default=False) user_expiration_date = sa.Column(sa.DateTime, nullable=True)
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 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 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.", )
target_groups = relationship( 'Group', secondary='default_observationplan_groups', passive_deletes=True, doc= 'Groups to share the resulting data from this default request with.', ) DefaultObservationPlanRequestTargetGroup = join_model( 'default_observationplan_groups', DefaultObservationPlanRequest, Group) DefaultObservationPlanRequestTargetGroup.create = ( DefaultObservationPlanRequestTargetGroup.update ) = DefaultObservationPlanRequestTargetGroup.delete = ( AccessibleIfUserMatches('defaultobservationplanrequest.requester') & DefaultObservationPlanRequestTargetGroup.read) 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(
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
class GcnEvent(Base): """Event information, including an event ID, mission, and time of the event.""" update = delete = AccessibleIfUserMatches('sent_by') sent_by_id = sa.Column( sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False, index=True, doc="The ID of the User who created this GcnEvent.", ) sent_by = relationship( "User", foreign_keys=sent_by_id, back_populates="gcnevents", doc="The user that saved this GcnEvent", ) dateobs = sa.Column(sa.DateTime, doc='Event time', unique=True, nullable=False) gcn_notices = relationship("GcnNotice", order_by=GcnNotice.date) _tags = relationship( "GcnTag", order_by=( sa.func.lower(GcnTag.text).notin_( {'fermi', 'swift', 'amon', 'lvc'}), sa.func.lower(GcnTag.text).notin_({'long', 'short'}), sa.func.lower(GcnTag.text).notin_({'grb', 'gw', 'transient'}), ), ) localizations = relationship("Localization") observationplan_requests = relationship( 'ObservationPlanRequest', back_populates='gcnevent', cascade='delete', passive_deletes=True, doc="Observation plan requests of the event.", ) comments = relationship( 'CommentOnGCN', back_populates='gcn', cascade='save-update, merge, refresh-expire, expunge, delete', passive_deletes=True, order_by="CommentOnGCN.created_at", doc="Comments posted about this GCN event.", ) @hybrid_property def tags(self): """List of tags.""" return [tag.text for tag in self._tags] @tags.expression def tags(cls): """List of tags.""" return (DBSession().query( GcnTag.text).filter(GcnTag.dateobs == cls.dateobs).subquery()) @hybrid_property def retracted(self): """Check if event is retracted.""" return 'retracted' in self.tags @retracted.expression def retracted(cls): """Check if event is retracted.""" return sa.literal('retracted').in_(cls.tags) @property def lightcurve(self): """GRB lightcurve URL.""" try: notice = self.gcn_notices[0] except IndexError: return None root = lxml.etree.fromstring(notice.content) elem = root.find(".//Param[@name='LightCurve_URL']") if elem is None: return None else: try: return elem.attrib.get('value', '').replace('http://', 'https://') except Exception: return None @property def gracesa(self): """Event page URL.""" try: notice = self.gcn_notices[0] except IndexError: return None root = lxml.etree.fromstring(notice.content) elem = root.find(".//Param[@name='EventPage']") if elem is None: return None else: try: return elem.attrib.get('value', '') except Exception: return None @property def graceid(self): try: notice = self.gcn_notices[0] except IndexError: return None root = lxml.etree.fromstring(notice.content) elem = root.find(".//Param[@name='GraceID']") if elem is None: return None else: return elem.attrib.get('value', '') @property def ned_gwf(self): """NED URL.""" return "https://ned.ipac.caltech.edu/gwf/events" @property def HasNS(self): """Checking if GW event contains NS.""" notice = self.gcn_notices[0] root = lxml.etree.fromstring(notice.content) elem = root.find(".//Param[@name='HasNS']") if elem is None: return None else: try: return 'HasNS: ' + elem.attrib.get('value', '') except Exception: return None @property def HasRemnant(self): """Checking if GW event has remnant matter.""" notice = self.gcn_notices[0] root = lxml.etree.fromstring(notice.content) elem = root.find(".//Param[@name='HasRemnant']") if elem is None: return None else: try: return 'HasRemnant: ' + elem.attrib.get('value', '') except Exception: return None @property def FAR(self): """Returning event false alarm rate.""" notice = self.gcn_notices[0] root = lxml.etree.fromstring(notice.content) elem = root.find(".//Param[@name='FAR']") if elem is None: return None else: try: return 'FAR: ' + elem.attrib.get('value', '') except Exception: return None
return cls( obj_id=obj_id, instrument_id=instrument_id, type=type, label=label, observed_at=observed_at, altdata=header, **spec_data, ) SpectrumReducer = join_model("spectrum_reducers", Spectrum, User) SpectrumObserver = join_model("spectrum_observers", Spectrum, User) SpectrumReducer.create = ( SpectrumReducer.delete ) = SpectrumReducer.update = AccessibleIfUserMatches('spectrum.owner') SpectrumObserver.create = ( SpectrumObserver.delete ) = SpectrumObserver.update = AccessibleIfUserMatches('spectrum.owner') # should be accessible only by spectrumowner ^^ SpectrumReducer.external_reducer = sa.Column( sa.String, nullable=True, doc="The actual reducer for the spectrum, provided as free text if the " "reducer is not a user in the database. Separate from the point-of-contact " "user designated as reducer", ) SpectrumObserver.external_observer = sa.Column( sa.String,
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
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 FollowupRequestTargetGroup = join_model('request_groups', FollowupRequest, Group) FollowupRequestTargetGroup.create = ( FollowupRequestTargetGroup.update) = FollowupRequestTargetGroup.delete = ( AccessibleIfUserMatches('followuprequest.requester') & FollowupRequestTargetGroup.read)
class GcnNotice(Base): """Records of ingested GCN notices""" update = delete = AccessibleIfUserMatches('sent_by') sent_by_id = sa.Column( sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False, index=True, doc="The ID of the User who created this GcnNotice.", ) sent_by = relationship( "User", foreign_keys=sent_by_id, back_populates="gcnnotices", doc="The user that saved this GcnNotice", ) ivorn = sa.Column(sa.String, unique=True, index=True, doc='Unique identifier of VOEvent') notice_type = sa.Column( sa.Enum(gcn.NoticeType), nullable=False, doc='GCN Notice type', ) stream = sa.Column(sa.String, nullable=False, doc='Event stream or mission (i.e., "Fermi")') date = sa.Column(sa.DateTime, nullable=False, doc='UTC message timestamp') dateobs = sa.Column( sa.ForeignKey('gcnevents.dateobs', ondelete="CASCADE"), nullable=False, doc='UTC event timestamp', ) content = deferred( sa.Column(sa.LargeBinary, nullable=False, doc='Raw VOEvent content')) def _get_property(self, property_name, value=None): root = lxml.etree.fromstring(self.content) path = f".//Param[@name='{property_name}']" elem = root.find(path) value = float(elem.attrib.get('value', '')) * 100 return value @property def has_ns(self): return self._get_property(property_name="HasNS") @property def has_remnant(self): return self._get_property(property_name="HasRemnant") @property def far(self): return self._get_property(property_name="FAR") @property def bns(self): return self._get_property(property_name="BNS") @property def nsbh(self): return self._get_property(property_name="NSBH") @property def bbh(self): return self._get_property(property_name="BBH") @property def mass_gap(self): return self._get_property(property_name="MassGap") @property def noise(self): return self._get_property(property_name="Terrestrial")
GroupUser.admin.is_(True)).subquery()) query = query.join( group_user_subq, sa.and_( Group.id == group_user_subq.c.group_id, User.id == group_user_subq.c.user_id, ), ) return query accessible_by_group_admins = AccessibleIfGroupUserIsAdminAndUserMatches( 'group.group_users.user') accessible_by_admins = AccessibleIfGroupUserIsAdminAndUserMatches( 'group_users.user') accessible_by_members = AccessibleIfUserMatches('users') accessible_by_stream_members = AccessibleIfUserMatches('stream.users') accessible_by_streams_members = AccessibleIfUserMatches('streams.users') accessible_by_groups_members = AccessibleIfGroupUserMatches('groups.users') accessible_by_group_members = AccessibleIfGroupUserMatches('group.users') def delete_group_access_logic(cls, user_or_token): """User can delete a group that is not the sitewide public group, is not a single user group, and that they are an admin member of.""" user_id = UserAccessControl.user_id_from_user_or_token(user_or_token) query = (DBSession().query(cls).join(GroupUser).filter( cls.name != cfg['misc']['public_group_name']).filter( cls.single_user_group.is_(False))) if not user_or_token.is_system_admin: query = query.filter(GroupUser.user_id == user_id,
class Localization(Base): """Localization information, including the localization ID, event ID, right ascension, declination, error radius (if applicable), and the healpix map. The healpix map is a multi-order healpix skymap, and this representation of the skymap has many tiles (in the LocalizationTile table). Healpix decomposes the sky into a set of equal area tiles each with a unique index, convenient for decomposing the sphere into subdivisions.""" update = delete = AccessibleIfUserMatches('sent_by') sent_by_id = sa.Column( sa.ForeignKey('users.id', ondelete='CASCADE'), nullable=False, index=True, doc="The ID of the User who created this Localization.", ) sent_by = relationship( "User", foreign_keys=sent_by_id, back_populates="localizations", doc="The user that saved this Localization", ) nside = 512 # HEALPix resolution used for flat (non-multiresolution) operations. dateobs = sa.Column( sa.ForeignKey('gcnevents.dateobs', ondelete="CASCADE"), nullable=False, index=True, doc='UTC event timestamp', ) localization_name = sa.Column(sa.String, doc='Localization name', index=True) uniq = deferred( sa.Column( sa.ARRAY(sa.BigInteger), nullable=False, doc='Multiresolution HEALPix UNIQ pixel index array', )) probdensity = deferred( sa.Column( sa.ARRAY(sa.Float), nullable=False, doc='Multiresolution HEALPix probability density array', )) distmu = deferred( sa.Column(sa.ARRAY(sa.Float), doc='Multiresolution HEALPix distance mu array')) distsigma = deferred( sa.Column(sa.ARRAY(sa.Float), doc='Multiresolution HEALPix distance sigma array')) distnorm = deferred( sa.Column( sa.ARRAY(sa.Float), doc='Multiresolution HEALPix distance normalization array', )) contour = deferred(sa.Column(JSONB, doc='GeoJSON contours')) observationplan_requests = relationship( 'ObservationPlanRequest', back_populates='localization', cascade='delete', passive_deletes=True, doc="Observation plan requests of the localization.", ) @hybrid_property def is_3d(self): return (self.distmu is not None and self.distsigma is not None and self.distnorm is not None) @is_3d.expression def is_3d(cls): return sa.and_( cls.distmu.isnot(None), cls.distsigma.isnot(None), cls.distnorm.isnot(None), ) @property def table_2d(self): """Get multiresolution HEALPix dataset, probability density only.""" return Table( [np.asarray(self.uniq, dtype=np.int64), self.probdensity], names=['UNIQ', 'PROBDENSITY'], ) @property def table(self): """Get multiresolution HEALPix dataset, probability density and distance.""" if self.is_3d: return Table( [ np.asarray(self.uniq, dtype=np.int64), self.probdensity, self.distmu, self.distsigma, self.distnorm, ], names=[ 'UNIQ', 'PROBDENSITY', 'DISTMU', 'DISTSIGMA', 'DISTNORM' ], ) else: return self.table_2d @property def flat_2d(self): """Get flat resolution HEALPix dataset, probability density only.""" order = healpy.nside2order(Localization.nside) result = ligo_bayestar.rasterize(self.table_2d, order)['PROB'] return healpy.reorder(result, 'NESTED', 'RING') @property def flat(self): """Get flat resolution HEALPix dataset, probability density and distance.""" if self.is_3d: order = healpy.nside2order(Localization.nside) t = ligo_bayestar.rasterize(self.table, order) result = t['PROB'], t['DISTMU'], t['DISTSIGMA'], t['DISTNORM'] return healpy.reorder(result, 'NESTED', 'RING') else: return (self.flat_2d, )
GroupCommentOnShift = join_model("group_comments_on_shifts", Group, CommentOnShift) GroupCommentOnShift.__doc__ = "Join table mapping Groups to CommentOnShift." GroupCommentOnShift.delete = GroupCommentOnShift.update = ( accessible_by_group_admins & GroupCommentOnShift.read) GroupInvitation = join_model('group_invitations', Group, Invitation) GroupSourceNotification = join_model('group_notifications', Group, SourceNotification) GroupSourceNotification.create = ( GroupSourceNotification.read) = accessible_by_group_members GroupSourceNotification.update = ( GroupSourceNotification.delete ) = accessible_by_group_admins | AccessibleIfUserMatches( 'sourcenotification.sent_by') GroupStream = join_model('group_streams', Group, Stream) GroupStream.__doc__ = "Join table mapping Groups to Streams." GroupStream.update = restricted GroupStream.delete = ( # only admins can delete streams from groups accessible_by_group_admins & GroupStream.read ) & CustomUserAccessControl( # Can only delete a stream from the group if none of the group's filters # are operating on the stream. lambda cls, user_or_token: DBSession().query(cls).outerjoin(Stream). outerjoin( Filter, sa.and_(Filter.stream_id == Stream.id, Filter.group_id == cls.group_id