models.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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(
  26. UUID(as_uuid=False), primary_key=True, default=lambda _: str(uuid.uuid4())
  27. )
  28. class User(Base):
  29. __tablename__ = "user_model"
  30. id: Mapped[str] = uid_column()
  31. email: Mapped[str] = mapped_column(
  32. String(254), nullable=False, unique=True, index=True
  33. )
  34. hashed_password: Mapped[str] = mapped_column(String(128), nullable=False)
  35. class Project(Base):
  36. __tablename__ = "projects"
  37. id: Mapped[str] = uid_column()
  38. created_at: Mapped[datetime] = mapped_column(
  39. DateTime(timezone=True), server_default=func.now()
  40. )
  41. updated_at: Mapped[datetime] = mapped_column(
  42. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  43. )
  44. name: Mapped[str] = mapped_column(
  45. String(128), nullable=False, unique=True, index=True
  46. )
  47. is_public: Mapped[bool] = mapped_column(Boolean())
  48. volunteers: Mapped[list["Volunteer"]] = relationship(
  49. back_populates="project", cascade="delete, delete-orphan"
  50. )
  51. slots: Mapped[list["Slot"]] = relationship(
  52. back_populates="project", cascade="delete, delete-orphan"
  53. )
  54. sms: Mapped[list["Sms"]] = relationship(
  55. back_populates="project", cascade="delete, delete-orphan"
  56. )
  57. templates: Mapped[list["SlotTemplate"]] = relationship(
  58. back_populates="project", cascade="delete, delete-orphan"
  59. )
  60. tags: Mapped[list["SlotTag"]] = relationship(
  61. back_populates="project", cascade="delete, delete-orphan"
  62. )
  63. association_table_volunteer_slot = Table(
  64. "association_volunteer_slot",
  65. Base.metadata,
  66. Column(
  67. "volunteer_id",
  68. ForeignKey("volunteers.id", ondelete="CASCADE"),
  69. primary_key=True,
  70. ),
  71. Column("slot_id", ForeignKey("slots.id", ondelete="CASCADE"), primary_key=True),
  72. )
  73. class Volunteer(Base):
  74. __tablename__ = "volunteers"
  75. id: Mapped[str] = uid_column()
  76. project_id: Mapped[str] = mapped_column(
  77. ForeignKey("projects.id", ondelete="CASCADE")
  78. )
  79. project: Mapped["Project"] = relationship(back_populates="volunteers")
  80. created_at: Mapped[datetime] = mapped_column(
  81. DateTime(timezone=True), server_default=func.now()
  82. )
  83. updated_at: Mapped[datetime] = mapped_column(
  84. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  85. )
  86. name: Mapped[str] = mapped_column(String(128))
  87. surname: Mapped[str] = mapped_column(String(128))
  88. email: Mapped[str] = mapped_column(String(128))
  89. phone_number: Mapped[str] = mapped_column(String(128))
  90. automatic_sms: Mapped[bool] = mapped_column(Boolean(), default=False)
  91. slots: Mapped[list["Slot"]] = relationship(
  92. secondary=association_table_volunteer_slot, back_populates="volunteers"
  93. )
  94. @hybrid_property
  95. def slots_id(self) -> list[str]:
  96. return [s.id for s in self.slots]
  97. class Slot(Base):
  98. __tablename__ = "slots"
  99. id: Mapped[str] = uid_column()
  100. project_id: Mapped[str] = mapped_column(
  101. ForeignKey("projects.id", ondelete="CASCADE")
  102. )
  103. project: Mapped["Project"] = relationship(back_populates="slots")
  104. created_at: Mapped[datetime] = mapped_column(
  105. DateTime(timezone=True), server_default=func.now()
  106. )
  107. updated_at: Mapped[datetime] = mapped_column(
  108. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  109. )
  110. title: Mapped[str] = mapped_column(String(128), nullable=False)
  111. starting_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
  112. ending_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
  113. required_volunteers: Mapped[int] = mapped_column(Integer, default=0)
  114. volunteers: Mapped[list[Volunteer]] = relationship(
  115. secondary=association_table_volunteer_slot, back_populates="slots"
  116. )
  117. template_id: Mapped[Optional[str]] = mapped_column(
  118. ForeignKey("slot_templates.id", ondelete="SET NULL"), nullable=True
  119. )
  120. template: Mapped["SlotTemplate"] = relationship(back_populates="slots")
  121. @hybrid_property
  122. def volunteers_id(self) -> list[str]:
  123. return [v.id for v in self.volunteers]
  124. association_table_template_tags = Table(
  125. "association_description_tag",
  126. Base.metadata,
  127. Column(
  128. "description_id",
  129. ForeignKey("slot_templates.id", ondelete="CASCADE"),
  130. primary_key=True,
  131. ),
  132. Column("tag_id", ForeignKey("slot_tags.id", ondelete="CASCADE"), primary_key=True),
  133. )
  134. class SlotTag(Base):
  135. __tablename__ = "slot_tags"
  136. id: Mapped[str] = uid_column()
  137. project_id: Mapped[str] = mapped_column(
  138. ForeignKey("projects.id", ondelete="CASCADE")
  139. )
  140. project: Mapped["Project"] = relationship(back_populates="tags")
  141. created_at: Mapped[datetime] = mapped_column(
  142. DateTime(timezone=True), server_default=func.now()
  143. )
  144. updated_at: Mapped[datetime] = mapped_column(
  145. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  146. )
  147. title: Mapped[str] = mapped_column(String(), default="")
  148. templates: Mapped[list["SlotTemplate"]] = relationship(
  149. secondary=association_table_template_tags, back_populates="tags"
  150. )
  151. @hybrid_property
  152. def templates_id(self) -> list[str]:
  153. return [s.id for s in self.templates]
  154. class SlotTemplate(Base):
  155. __tablename__ = "slot_templates"
  156. id: Mapped[str] = uid_column()
  157. project_id: Mapped[str] = mapped_column(
  158. ForeignKey("projects.id", ondelete="CASCADE")
  159. )
  160. project: Mapped["Project"] = relationship(back_populates="templates")
  161. created_at: Mapped[datetime] = mapped_column(
  162. DateTime(timezone=True), server_default=func.now()
  163. )
  164. updated_at: Mapped[datetime] = mapped_column(
  165. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  166. )
  167. title: Mapped[str] = mapped_column(String(), default="")
  168. description: Mapped[str] = mapped_column(String(), default="")
  169. place: Mapped[str] = mapped_column(String(), default="")
  170. responsible_contact: Mapped[str] = mapped_column(String(), default="")
  171. slots: Mapped[list[Slot]] = relationship(back_populates="template")
  172. tags: Mapped[list[SlotTag]] = relationship(
  173. secondary=association_table_template_tags, back_populates="templates"
  174. )
  175. @hybrid_property
  176. def slots_id(self) -> list[str]:
  177. return [s.id for s in self.slots]
  178. @hybrid_property
  179. def tags_id(self) -> list[str]:
  180. return [s.id for s in self.tags]
  181. class Sms(Base):
  182. __tablename__ = "sms"
  183. id: Mapped[str] = mapped_column(
  184. UUID(as_uuid=False), primary_key=True, default=lambda _: str(uuid.uuid4())
  185. )
  186. project_id: Mapped[str] = mapped_column(
  187. ForeignKey("projects.id", ondelete="CASCADE")
  188. )
  189. project: Mapped["Project"] = relationship(back_populates="sms")
  190. volunteer_id: Mapped[str] = mapped_column(
  191. ForeignKey("volunteers.id"), nullable=True
  192. )
  193. created_at: Mapped[datetime] = mapped_column(
  194. DateTime(timezone=True), server_default=func.now()
  195. )
  196. updated_at: Mapped[datetime] = mapped_column(
  197. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  198. )
  199. content: Mapped[str] = mapped_column(String(), nullable=False)
  200. phone_number: Mapped[str] = mapped_column(String(24))
  201. sending_time: Mapped[datetime] = mapped_column(
  202. DateTime(timezone=True), default=datetime.now
  203. )
  204. send_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=True)