models.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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. comment: Mapped[str] = mapped_column(String(), default="")
  95. @hybrid_property
  96. def slots_id(self) -> list[str]:
  97. return [s.id for s in self.slots]
  98. class Slot(Base):
  99. __tablename__ = "slots"
  100. id: Mapped[str] = uid_column()
  101. project_id: Mapped[str] = mapped_column(
  102. ForeignKey("projects.id", ondelete="CASCADE")
  103. )
  104. project: Mapped["Project"] = relationship(back_populates="slots")
  105. created_at: Mapped[datetime] = mapped_column(
  106. DateTime(timezone=True), server_default=func.now()
  107. )
  108. updated_at: Mapped[datetime] = mapped_column(
  109. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  110. )
  111. title: Mapped[str] = mapped_column(String(128), nullable=False)
  112. starting_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
  113. ending_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
  114. required_volunteers: Mapped[int] = mapped_column(Integer, default=0)
  115. volunteers: Mapped[list[Volunteer]] = relationship(
  116. secondary=association_table_volunteer_slot, back_populates="slots"
  117. )
  118. template_id: Mapped[Optional[str]] = mapped_column(
  119. ForeignKey("slot_templates.id", ondelete="SET NULL"), nullable=True
  120. )
  121. template: Mapped["SlotTemplate"] = relationship(back_populates="slots")
  122. @hybrid_property
  123. def volunteers_id(self) -> list[str]:
  124. return [v.id for v in self.volunteers]
  125. association_table_template_tags = Table(
  126. "association_description_tag",
  127. Base.metadata,
  128. Column(
  129. "description_id",
  130. ForeignKey("slot_templates.id", ondelete="CASCADE"),
  131. primary_key=True,
  132. ),
  133. Column("tag_id", ForeignKey("slot_tags.id", ondelete="CASCADE"), primary_key=True),
  134. )
  135. class SlotTag(Base):
  136. __tablename__ = "slot_tags"
  137. id: Mapped[str] = uid_column()
  138. project_id: Mapped[str] = mapped_column(
  139. ForeignKey("projects.id", ondelete="CASCADE")
  140. )
  141. project: Mapped["Project"] = relationship(back_populates="tags")
  142. created_at: Mapped[datetime] = mapped_column(
  143. DateTime(timezone=True), server_default=func.now()
  144. )
  145. updated_at: Mapped[datetime] = mapped_column(
  146. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  147. )
  148. title: Mapped[str] = mapped_column(String(), default="")
  149. templates: Mapped[list["SlotTemplate"]] = relationship(
  150. secondary=association_table_template_tags, back_populates="tags"
  151. )
  152. @hybrid_property
  153. def templates_id(self) -> list[str]:
  154. return [s.id for s in self.templates]
  155. class SlotTemplate(Base):
  156. __tablename__ = "slot_templates"
  157. id: Mapped[str] = uid_column()
  158. project_id: Mapped[str] = mapped_column(
  159. ForeignKey("projects.id", ondelete="CASCADE")
  160. )
  161. project: Mapped["Project"] = relationship(back_populates="templates")
  162. created_at: Mapped[datetime] = mapped_column(
  163. DateTime(timezone=True), server_default=func.now()
  164. )
  165. updated_at: Mapped[datetime] = mapped_column(
  166. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  167. )
  168. title: Mapped[str] = mapped_column(String(), default="")
  169. description: Mapped[str] = mapped_column(String(), default="")
  170. place: Mapped[str] = mapped_column(String(), default="")
  171. responsible_contact: Mapped[str] = mapped_column(String(), default="")
  172. slots: Mapped[list[Slot]] = relationship(back_populates="template")
  173. tags: Mapped[list[SlotTag]] = relationship(
  174. secondary=association_table_template_tags, back_populates="templates"
  175. )
  176. comment: Mapped[str] = mapped_column(String(), default="")
  177. @hybrid_property
  178. def slots_id(self) -> list[str]:
  179. return [s.id for s in self.slots]
  180. @hybrid_property
  181. def tags_id(self) -> list[str]:
  182. return [s.id for s in self.tags]
  183. class Sms(Base):
  184. __tablename__ = "sms"
  185. id: Mapped[str] = mapped_column(
  186. UUID(as_uuid=False), primary_key=True, default=lambda _: str(uuid.uuid4())
  187. )
  188. project_id: Mapped[str] = mapped_column(
  189. ForeignKey("projects.id", ondelete="CASCADE")
  190. )
  191. project: Mapped["Project"] = relationship(back_populates="sms")
  192. volunteer_id: Mapped[str] = mapped_column(
  193. ForeignKey("volunteers.id"), nullable=True
  194. )
  195. created_at: Mapped[datetime] = mapped_column(
  196. DateTime(timezone=True), server_default=func.now()
  197. )
  198. updated_at: Mapped[datetime] = mapped_column(
  199. DateTime(timezone=True), default=datetime.now, onupdate=func.now()
  200. )
  201. content: Mapped[str] = mapped_column(String(), nullable=False)
  202. phone_number: Mapped[str] = mapped_column(String(24))
  203. sending_time: Mapped[datetime] = mapped_column(
  204. DateTime(timezone=True), default=datetime.now
  205. )
  206. send_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=True)