Digitale bierlijst

models.py 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. """
  2. Defines database models used by the server.
  3. """
  4. import datetime
  5. from sqlalchemy import func
  6. from sqlalchemy.exc import SQLAlchemyError
  7. from piket_server.flask import db
  8. class Person(db.Model):
  9. """ Represents a person to be shown on the lists. """
  10. __tablename__ = "people"
  11. person_id = db.Column(db.Integer, primary_key=True)
  12. full_name = db.Column(db.String, nullable=False)
  13. display_name = db.Column(db.String, nullable=True)
  14. aardbei_id = db.Column(db.Integer, nullable=True)
  15. active = db.Column(db.Boolean, nullable=False, default=False)
  16. consumptions = db.relationship("Consumption", backref="person", lazy=True)
  17. def __repr__(self) -> str:
  18. return f"<Person {self.person_id}: {self.full_name}>"
  19. @property
  20. def as_dict(self) -> dict:
  21. return {
  22. "person_id": self.person_id,
  23. "active": self.active,
  24. "full_name": self.full_name,
  25. "display_name": self.display_name,
  26. "consumptions": {
  27. ct.consumption_type_id: Consumption.query.filter_by(person=self)
  28. .filter_by(settlement=None)
  29. .filter_by(consumption_type=ct)
  30. .filter_by(reversed=False)
  31. .count()
  32. for ct in ConsumptionType.query.all()
  33. },
  34. }
  35. class Export(db.Model):
  36. """ Represents a set of exported Settlements. """
  37. __tablename__ = "exports"
  38. export_id = db.Column(db.Integer, primary_key=True)
  39. created_at = db.Column(
  40. db.DateTime, default=datetime.datetime.utcnow, nullable=False
  41. )
  42. settlements = db.relationship("Settlement", backref="export", lazy=True)
  43. @property
  44. def as_dict(self) -> dict:
  45. return {
  46. "export_id": self.export_id,
  47. "created_at": self.created_at.isoformat(),
  48. "settlement_ids": [s.settlement_id for s in self.settlements],
  49. }
  50. class Settlement(db.Model):
  51. """ Represents a settlement of the list. """
  52. __tablename__ = "settlements"
  53. settlement_id = db.Column(db.Integer, primary_key=True)
  54. name = db.Column(db.String, nullable=False)
  55. export_id = db.Column(db.Integer, db.ForeignKey("exports.export_id"), nullable=True)
  56. consumptions = db.relationship("Consumption", backref="settlement", lazy=True)
  57. def __repr__(self) -> str:
  58. return f"<Settlement {self.settlement_id}: {self.name}>"
  59. @property
  60. def as_dict(self) -> dict:
  61. return {
  62. "settlement_id": self.settlement_id,
  63. "name": self.name,
  64. "consumption_summary": self.consumption_summary,
  65. "unique_people": self.unique_people,
  66. }
  67. @property
  68. def unique_people(self) -> int:
  69. q = (
  70. Consumption.query.filter_by(settlement=self)
  71. .filter_by(reversed=False)
  72. .group_by(Consumption.person_id)
  73. .count()
  74. )
  75. return q
  76. @property
  77. def consumption_summary(self) -> dict:
  78. q = (
  79. Consumption.query.filter_by(settlement=self)
  80. .filter_by(reversed=False)
  81. .group_by(Consumption.consumption_type_id)
  82. .order_by(ConsumptionType.name)
  83. .outerjoin(ConsumptionType)
  84. .with_entities(
  85. Consumption.consumption_type_id,
  86. ConsumptionType.name,
  87. func.count(Consumption.consumption_id),
  88. )
  89. .all()
  90. )
  91. return {r[0]: {"name": r[1], "count": r[2]} for r in q}
  92. @property
  93. def per_person(self) -> dict:
  94. # Get keys of seen consumption_types
  95. c_types = self.consumption_summary.keys()
  96. result = {}
  97. for type in c_types:
  98. c_type = ConsumptionType.query.get(type)
  99. result[type] = {"consumption_type": c_type.as_dict, "counts": {}}
  100. q = (
  101. Consumption.query.filter_by(settlement=self)
  102. .filter_by(reversed=False)
  103. .filter_by(consumption_type=c_type)
  104. .group_by(Consumption.person_id)
  105. .order_by(Person.full_name)
  106. .outerjoin(Person)
  107. .with_entities(
  108. Person.person_id,
  109. Person.full_name,
  110. func.count(Consumption.consumption_id),
  111. )
  112. .all()
  113. )
  114. for row in q:
  115. result[type]["counts"][row[0]] = {"name": row[1], "count": row[2]}
  116. return result
  117. class ConsumptionType(db.Model):
  118. """ Represents a type of consumption to be counted. """
  119. __tablename__ = "consumption_types"
  120. consumption_type_id = db.Column(db.Integer, primary_key=True)
  121. name = db.Column(db.String, nullable=False)
  122. icon = db.Column(db.String)
  123. active = db.Column(db.Boolean, default=True)
  124. consumptions = db.relationship("Consumption", backref="consumption_type", lazy=True)
  125. def __repr__(self) -> str:
  126. return f"<ConsumptionType: {self.name}>"
  127. @property
  128. def as_dict(self) -> dict:
  129. return {
  130. "consumption_type_id": self.consumption_type_id,
  131. "name": self.name,
  132. "icon": self.icon,
  133. }
  134. class Consumption(db.Model):
  135. """ Represent one consumption to be counted. """
  136. __tablename__ = "consumptions"
  137. consumption_id = db.Column(db.Integer, primary_key=True)
  138. person_id = db.Column(db.Integer, db.ForeignKey("people.person_id"), nullable=True)
  139. consumption_type_id = db.Column(
  140. db.Integer,
  141. db.ForeignKey("consumption_types.consumption_type_id"),
  142. nullable=False,
  143. )
  144. settlement_id = db.Column(
  145. db.Integer, db.ForeignKey("settlements.settlement_id"), nullable=True
  146. )
  147. created_at = db.Column(
  148. db.DateTime, default=datetime.datetime.utcnow, nullable=False
  149. )
  150. reversed = db.Column(db.Boolean, default=False, nullable=False)
  151. def __repr__(self) -> str:
  152. return (
  153. f"<Consumption: {self.consumption_type.name} for {self.person.full_name}>"
  154. )
  155. @property
  156. def as_dict(self) -> dict:
  157. return {
  158. "consumption_id": self.consumption_id,
  159. "person_id": self.person_id,
  160. "consumption_type_id": self.consumption_type_id,
  161. "settlement_id": self.settlement_id,
  162. "created_at": self.created_at.isoformat(),
  163. "reversed": self.reversed,
  164. }