class MissionReportVehicle(Base, ContentMixin, HiddenFromPublicExtension): __tablename__ = 'mission_report_vehicles' #: the public id of the vehicle id = Column(UUID, nullable=False, primary_key=True, default=uuid4) #: the short id of the vehicle name = Column(Text, nullable=False) #: the longer name of the vehicle description = Column(Text, nullable=False) #: symbol of the vehicle symbol = associated(MissionReportFile, 'symbol', 'one-to-one') #: a website describing the vehicle website = Column(Text, nullable=True) uses = relationship('MissionReportVehicleUse') @property def title(self): return f'{self.name} - {self.description}' @property def readable_website(self): if self.website: return self.website.replace('https://', '').replace('http://', '')
class PayableManyTimes(PayableBase): """ Same as :class:`Payable`, but using a list of payments instead of a single one (proper many-to-many payments). """ payments = associated(Payment, 'payments', 'many-to-many', uselist=True)
class Payable(PayableBase): """ Links the parent model with 0 to n :class:`~onegov.pay.models.Payment` records through an automatically generated association table. """ payment = associated(Payment, 'payment', 'many-to-many', uselist=False)
class MissionReport(Base, ContentMixin, HiddenFromPublicExtension): __tablename__ = 'mission_reports' #: the public id of the mission_report id = Column(UUID, nullable=False, primary_key=True, default=uuid4) #: the date of the report date = Column(UTCDateTime, nullable=False) #: how long the mission lasted, in hours duration = Column(Numeric(precision=6, scale=2), nullable=False) #: the nature of the mission nature = Column(Text, nullable=False) #: the location of the mission location = Column(Text, nullable=False) #: actually active personnel personnel = Column(Integer, nullable=False) #: backup personnel backup = Column(Integer, nullable=False) #: the Zivilschutz was involved civil_defence = Column(Boolean, nullable=False, default=False) #: the vehicle use of the mission report used_vehicles = relationship( 'MissionReportVehicleUse', cascade="all, delete-orphan") #: pictures of the mission pictures = associated(MissionReportFile, 'pictures', 'one-to-many') @property def title(self): return self.nature @property def readable_duration(self): return str(self.duration).rstrip('.0') + 'h' @property def local_date(self): return to_timezone(self.date, 'Europe/Zurich')
class ExtendedAgency(Agency, HiddenFromPublicExtension): """ An extended version of the standard agency from onegov.people. """ __mapper_args__ = {'polymorphic_identity': 'extended'} es_type_name = 'extended_agency' @property def es_public(self): return not self.is_hidden_from_public #: Defines which fields of a membership and person should be exported to #: the PDF. The fields are expected to contain two parts seperated by a #: point. The first part is either `membership` or `person`, the second #: the name of the attribute (e.g. `membership.title`). export_fields = meta_property(default=list) #: The PDF for the agency and all its suborganizations. pdf = associated(AgencyPdf, 'pdf', 'one-to-one') trait = 'agency' @property def pdf_file(self): """ Returns the PDF content for the agency (and all its suborganizations). """ if self.pdf: return self.pdf.reference.file @pdf_file.setter def pdf_file(self, value): """ Sets the PDF content for the agency (and all its suborganizations). Automatically sets a nice filename. Replaces only the reference, if possible. """ filename = '{}.pdf'.format(normalize_for_url(self.title)) if self.pdf: self.pdf.reference = as_fileintent(value, filename) self.pdf.name = filename else: pdf = AgencyPdf(id=random_token()) pdf.reference = as_fileintent(value, filename) pdf.name = filename self.pdf = pdf @property def portrait_html(self): """ Returns the portrait as HTML. """ return '<p>{}</p>'.format(linkify(self.portrait).replace('\n', '<br>')) def proxy(self): """ Returns a proxy object to this agency allowing alternative linking paths. """ return AgencyProxy(self) def add_person(self, person_id, title, **kwargs): """ Appends a person to the agency with the given title. """ order_within_agency = kwargs.pop('order_within_agency', 2**16) session = object_session(self) orders_for_person = session.query( ExtendedAgencyMembership.order_within_person).filter_by( person_id=person_id).all() orders_for_person = list( (o.order_within_person for o in orders_for_person)) if orders_for_person: try: order_within_person = max(orders_for_person) + 1 except ValueError: order_within_person = 0 assert len(orders_for_person) == max(orders_for_person) + 1 else: order_within_person = 0 self.memberships.append( ExtendedAgencyMembership(person_id=person_id, title=title, order_within_agency=order_within_agency, order_within_person=order_within_person, **kwargs)) for order, membership in enumerate(self.memberships): membership.order_within_agency = order
class Message(Base): """ A single chat message bound to channel. """ __tablename__ = 'messages' #: the public id of the message - uses ulid for absolute ordering id = Column(Text, primary_key=True, default=ulid) #: channel to which this message belongs -> this might one day be #: linked to an actual channel record - for now it's just a string that #: binds all messages with the same string together channel_id = Column(Text, index=True, nullable=False) #: optional owner of the message -> this is just an identifier, it isn't #: necessarily linked to the user table owner = Column(Text, nullable=True) #: the polymorphic type of the message type = Column(Text, nullable=True) #: meta information specific to this message and maybe its type -> we #: don't use the meta/content mixin yet as we might not need the content #: property meta = Column(JSON, nullable=False, default=dict) #: the text of the message, maybe None for certain use cases (say if the # content of the message is generated from the meta property) text = Column(Text, nullable=True) #: the time this message was created - not taken from the timestamp mixin #: because here we don't want it to be deferred created = Column(UTCDateTime, default=sedate.utcnow) #: the time this message was modified - not taken from the timestamp mixin #: because here we don't want it to be deferred modified = Column(UTCDateTime, onupdate=sedate.utcnow) #: a single optional file associated with this message file = associated(File, 'file', 'one-to-one') __mapper_args__ = { 'order_by': id } # we need to override __hash__ and __eq__ to establish the equivalence of # polymorphic subclasses that differ - we need to compare the base class # with subclasses to work around a limitation of the association proxy # (see backref in onegov.core.orm.abstract.associable.associated) def __hash__(self): return super().__hash__() def __eq__(self, other): if isinstance(other, self.__class__) \ and self.id == other.id\ and self.channel_id == other.channel_id: return True return super().__eq__(other) @property def subtype(self): """ An optional subtype for this message used for separating messages of a type further (currently for UI). Should be made unique, but there's no guarantee. """ return None def get(self, request): """ Code rendering a message should call this method to get the actual text of the message. It might be rendered from meta or it might be returned directly from the text column. How this is done is up to the polymorphic Message. """ return self.text @hybrid_property def edited(self): # use != instead of "is not" as we want this translated into SQL return self.modified != None @classmethod def bound_messages(cls, session): """ A message collection bound to the polymorphic identity of this message. """ from onegov.chat import MessageCollection # XXX circular import return MessageCollection( session=session, type=cls.__mapper_args__['polymorphic_identity'] )