models.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. """
  2. SQL Alchemy models declaration.
  3. https://docs.sqlalchemy.org/en/14/orm/declarative_styles.html#example-two-dataclasses-with-declarative-table
  4. Dataclass style for powerful autocompletion support.
  5. https://alembic.sqlalchemy.org/en/latest/tutorial.html
  6. Note, it is used by alembic migrations logic, see `alembic/env.py`
  7. Alembic shortcuts:
  8. # create migration
  9. alembic revision --autogenerate -m "migration_name"
  10. # apply all migrations
  11. alembic upgrade head
  12. """
  13. from typing import Optional
  14. import uuid
  15. from datetime import datetime
  16. from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Table
  17. from sqlalchemy.dialects.postgresql import UUID
  18. from sqlalchemy.ext.hybrid import hybrid_property
  19. from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
  20. from sqlalchemy.sql import func
  21. class Base(DeclarativeBase):
  22. pass
  23. def uid_column() -> Mapped[str]:
  24. """Returns a postgreSQL UUID column for SQL ORM"""
  25. return mapped_column(UUID(as_uuid=False), primary_key=True, default=lambda _: str(uuid.uuid4()))
  26. class User(Base):
  27. __tablename__ = "user_model"
  28. id: Mapped[str] = uid_column()
  29. email: Mapped[str] = mapped_column(String(254), nullable=False, unique=True, index=True)
  30. hashed_password: Mapped[str] = mapped_column(String(128), nullable=False)
  31. class Project(Base):
  32. __tablename__ = "projects"
  33. id: Mapped[str] = uid_column()
  34. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  35. updated_at: Mapped[datetime] = mapped_column(
  36. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  37. )
  38. name: Mapped[str] = mapped_column(String(128), nullable=False, unique=True, index=True)
  39. is_public: Mapped[bool] = mapped_column(Boolean())
  40. volunteers: Mapped[list["Volunteer"]] = relationship(
  41. back_populates="project", cascade="delete, delete-orphan"
  42. )
  43. slots: Mapped[list["Slot"]] = relationship(
  44. back_populates="project", cascade="delete, delete-orphan"
  45. )
  46. sms: Mapped[list["Sms"]] = relationship(
  47. back_populates="project", cascade="delete, delete-orphan"
  48. )
  49. templates: Mapped[list["SlotTemplate"]] = relationship(
  50. back_populates="project", cascade="delete, delete-orphan"
  51. )
  52. tags: Mapped[list["SlotTag"]] = relationship(
  53. back_populates="project", cascade="delete, delete-orphan"
  54. )
  55. association_table_volunteer_slot = Table(
  56. "association_volunteer_slot",
  57. Base.metadata,
  58. Column(
  59. "volunteer_id",
  60. ForeignKey("volunteers.id", ondelete="CASCADE"),
  61. primary_key=True,
  62. ),
  63. Column("slot_id", ForeignKey("slots.id", ondelete="CASCADE"), primary_key=True),
  64. )
  65. class Volunteer(Base):
  66. __tablename__ = "volunteers"
  67. id: Mapped[str] = uid_column()
  68. project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
  69. project: Mapped["Project"] = relationship(back_populates="volunteers")
  70. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  71. updated_at: Mapped[datetime] = mapped_column(
  72. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  73. )
  74. name: Mapped[str] = mapped_column(String(128))
  75. surname: Mapped[str] = mapped_column(String(128))
  76. email: Mapped[str] = mapped_column(String(128))
  77. phone_number: Mapped[str] = mapped_column(String(128))
  78. automatic_sms: Mapped[bool] = mapped_column(Boolean(), default=False)
  79. slots: Mapped[list["Slot"]] = relationship(
  80. secondary=association_table_volunteer_slot, back_populates="volunteers"
  81. )
  82. comment: Mapped[str] = mapped_column(String(), default="")
  83. sms: Mapped[list["Sms"]] = relationship(back_populates="volunteer", cascade="all, delete")
  84. @hybrid_property
  85. def slots_id(self) -> list[str]:
  86. return [s.id for s in self.slots]
  87. class Slot(Base):
  88. __tablename__ = "slots"
  89. id: Mapped[str] = uid_column()
  90. project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
  91. project: Mapped["Project"] = relationship(back_populates="slots")
  92. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  93. updated_at: Mapped[datetime] = mapped_column(
  94. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  95. )
  96. title: Mapped[str] = mapped_column(String(128), nullable=False)
  97. starting_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
  98. ending_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
  99. required_volunteers: Mapped[int] = mapped_column(Integer, default=0)
  100. volunteers: Mapped[list[Volunteer]] = relationship(
  101. secondary=association_table_volunteer_slot, back_populates="slots"
  102. )
  103. template_id: Mapped[Optional[str]] = mapped_column(
  104. ForeignKey("slot_templates.id", ondelete="SET NULL"), nullable=True
  105. )
  106. template: Mapped["SlotTemplate"] = relationship(back_populates="slots")
  107. @hybrid_property
  108. def volunteers_id(self) -> list[str]:
  109. return [v.id for v in self.volunteers]
  110. association_table_template_tags = Table(
  111. "association_description_tag",
  112. Base.metadata,
  113. Column(
  114. "description_id",
  115. ForeignKey("slot_templates.id", ondelete="CASCADE"),
  116. primary_key=True,
  117. ),
  118. Column("tag_id", ForeignKey("slot_tags.id", ondelete="CASCADE"), primary_key=True),
  119. )
  120. class SlotTag(Base):
  121. __tablename__ = "slot_tags"
  122. id: Mapped[str] = uid_column()
  123. project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
  124. project: Mapped["Project"] = relationship(back_populates="tags")
  125. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  126. updated_at: Mapped[datetime] = mapped_column(
  127. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  128. )
  129. title: Mapped[str] = mapped_column(String(), default="")
  130. templates: Mapped[list["SlotTemplate"]] = relationship(
  131. secondary=association_table_template_tags, back_populates="tags"
  132. )
  133. @hybrid_property
  134. def templates_id(self) -> list[str]:
  135. return [s.id for s in self.templates]
  136. class SlotTemplate(Base):
  137. __tablename__ = "slot_templates"
  138. id: Mapped[str] = uid_column()
  139. project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
  140. project: Mapped["Project"] = relationship(back_populates="templates")
  141. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  142. updated_at: Mapped[datetime] = mapped_column(
  143. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  144. )
  145. title: Mapped[str] = mapped_column(String(), default="")
  146. description: Mapped[str] = mapped_column(String(), default="")
  147. place: Mapped[str] = mapped_column(String(), default="")
  148. responsible_contact: Mapped[str] = mapped_column(String(), default="")
  149. slots: Mapped[list[Slot]] = relationship(back_populates="template")
  150. tags: Mapped[list[SlotTag]] = relationship(
  151. secondary=association_table_template_tags, back_populates="templates"
  152. )
  153. comment: Mapped[str] = mapped_column(String(), default="")
  154. @hybrid_property
  155. def slots_id(self) -> list[str]:
  156. return [s.id for s in self.slots]
  157. @hybrid_property
  158. def tags_id(self) -> list[str]:
  159. return [s.id for s in self.tags]
  160. class Sms(Base):
  161. __tablename__ = "sms"
  162. id: Mapped[str] = mapped_column(
  163. UUID(as_uuid=False), primary_key=True, default=lambda _: str(uuid.uuid4())
  164. )
  165. project_id: Mapped[str] = mapped_column(ForeignKey("projects.id", ondelete="CASCADE"))
  166. project: Mapped["Project"] = relationship(back_populates="sms")
  167. volunteer_id: Mapped[str] = mapped_column(
  168. ForeignKey("volunteers.id", ondelete="CASCADE", onupdate="CASCADE"),
  169. nullable=True,
  170. )
  171. volunteer: Mapped["Volunteer"] = relationship(back_populates="sms", cascade="all, delete")
  172. created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
  173. updated_at: Mapped[datetime] = mapped_column(
  174. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  175. )
  176. content: Mapped[str] = mapped_column(String(), nullable=False)
  177. phone_number: Mapped[str] = mapped_column(String(24))
  178. sending_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now)
  179. send_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=True)