Another Ham Radio Logbook -- Web, Multi-user multiple-logbook, with eQSL upload support
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

models.py 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. import datetime
  2. from libqth import is_valid_qth, qth_to_coords, coords_to_qth
  3. from flask_security import SQLAlchemyUserDatastore, UserMixin, RoleMixin
  4. from flask_sqlalchemy import SQLAlchemy, BaseQuery
  5. from geohelper import distance
  6. from sqlalchemy.sql import func
  7. from sqlalchemy_searchable import make_searchable, SearchQueryMixin
  8. from sqlalchemy import event
  9. from slugify import slugify
  10. from sqlalchemy_utils.types import TSVectorType
  11. db = SQLAlchemy()
  12. make_searchable(db.metadata)
  13. class LogQuery(BaseQuery, SearchQueryMixin):
  14. pass
  15. roles_users = db.Table(
  16. "roles_users",
  17. db.Column("user_id", db.Integer(), db.ForeignKey("user.id")),
  18. db.Column("role_id", db.Integer(), db.ForeignKey("role.id")),
  19. )
  20. class Role(db.Model, RoleMixin):
  21. id = db.Column(db.Integer(), primary_key=True)
  22. name = db.Column(db.String(80), unique=True, nullable=False, info={"label": "Name"})
  23. description = db.Column(db.String(255), info={"label": "Description"})
  24. __mapper_args__ = {"order_by": name}
  25. class User(db.Model, UserMixin):
  26. id = db.Column(db.Integer, primary_key=True)
  27. email = db.Column(db.String(255), unique=True, nullable=False, info={"label": "Email"})
  28. name = db.Column(db.String(255), unique=True, nullable=False, info={"label": "Name"})
  29. password = db.Column(db.String(255), nullable=False, info={"label": "Password"})
  30. active = db.Column(db.Boolean())
  31. confirmed_at = db.Column(db.DateTime())
  32. callsign = db.Column(db.String(32))
  33. locator = db.Column(db.String(16))
  34. firstname = db.Column(db.String(32))
  35. lastname = db.Column(db.String(32))
  36. lotw_name = db.Column(db.String(32))
  37. lotw_password = db.Column(db.String(255))
  38. eqsl_name = db.Column(db.String(32))
  39. eqsl_password = db.Column(db.String(255))
  40. hamqth_name = db.Column(db.String(32))
  41. hamqth_password = db.Column(db.String(255))
  42. timezone = db.Column(db.String(255), nullable=False, default="UTC") # Managed and fed by pytz
  43. swl = db.Column(db.Boolean(), nullable=False, default=False)
  44. zone = db.Column(db.String(10), nullable=False, default="iaru1")
  45. slug = db.Column(db.String(255), unique=True, nullable=True)
  46. roles = db.relationship("Role", secondary=roles_users, backref=db.backref("users", lazy="dynamic"))
  47. logbooks = db.relationship("Logbook", backref="user", lazy="dynamic", cascade="delete")
  48. logs = db.relationship("Log", backref="user", lazy="dynamic", cascade="delete")
  49. notes = db.relationship("Note", backref="user", lazy="dynamic", cascade="delete")
  50. apitokens = db.relationship("Apitoken", backref="user", lazy="dynamic", cascade="delete")
  51. contacts = db.relationship("Contact", backref="user", lazy="dynamic", cascade="delete")
  52. user_loggings = db.relationship("UserLogging", backref="user", lazy="dynamic", cascade="delete")
  53. loggings = db.relationship("Logging", backref="user", lazy="dynamic", cascade="delete")
  54. __mapper_args__ = {"order_by": name}
  55. def join_roles(self, string):
  56. return string.join([i.description for i in self.roles])
  57. def qth_to_coords(self):
  58. qth = is_valid_qth(self.locator, 6)
  59. if not qth:
  60. return None
  61. qth = qth_to_coords(self.locator, 6)
  62. if not qth:
  63. return None
  64. return qth
  65. # Give a cute name <3 More like "name - callsign" or "callsign"
  66. def cutename(self):
  67. cute = ""
  68. if self.name:
  69. cute += self.name
  70. cute += " - "
  71. cute += self.callsign
  72. return cute
  73. def zone_str(self):
  74. if self.zone == "iaru1":
  75. return "IARU Zone 1"
  76. elif self.zone == "iaru2":
  77. return "IARU Zone 2"
  78. elif self.zone == "iaru3":
  79. return "IARU Zone 3"
  80. else:
  81. return "You should not do that"
  82. class Apitoken(db.Model):
  83. id = db.Column(db.Integer, primary_key=True)
  84. user_id = db.Column(db.Integer(), db.ForeignKey("user.id"), nullable=False)
  85. token = db.Column(db.String(255), unique=True, nullable=False, info={"label": "Token"})
  86. secret = db.Column(db.String(255), unique=True, nullable=False, info={"label": "Secret"})
  87. user_datastore = SQLAlchemyUserDatastore(db, User, Role)
  88. class Cat(db.Model):
  89. __tablename__ = "cat"
  90. id = db.Column(db.Integer, primary_key=True)
  91. radio = db.Column(db.String(250), nullable=False)
  92. frequency = db.Column(db.Integer(), nullable=False)
  93. mode = db.Column(db.String(10), nullable=False)
  94. timestamp = db.Column(db.DateTime(timezone=False), nullable=False)
  95. slug = db.Column(db.String(255), unique=True, nullable=True)
  96. class Config(db.Model):
  97. __tablename__ = "config"
  98. id = db.Column(db.Integer, primary_key=True)
  99. lotw_download_url = db.Column(db.String(255), default=None)
  100. lotw_upload_url = db.Column(db.String(255), default=None)
  101. lotw_rcvd_mark = db.Column(db.String(255), default=None)
  102. lotw_login_url = db.Column(db.String(255), default=None)
  103. eqsl_download_url = db.Column(db.String(255), default=None)
  104. eqsl_upload_url = db.Column(db.String(255), default=None)
  105. eqsl_rcvd_mark = db.Column(db.String(255), default=None)
  106. clublog_api_key = db.Column(db.String(255), default=None)
  107. class ContestTemplate(db.Model):
  108. __tablename__ = "contest_template"
  109. id = db.Column(db.Integer, primary_key=True)
  110. name = db.Column(db.String(255), nullable=False, index=True)
  111. band_160 = db.Column(db.String(20), nullable=False)
  112. band_80 = db.Column(db.String(20), nullable=False)
  113. band_40 = db.Column(db.String(20), nullable=False)
  114. band_20 = db.Column(db.String(20), nullable=False)
  115. band_15 = db.Column(db.String(20), nullable=False)
  116. band_10 = db.Column(db.String(20), nullable=False)
  117. band_6m = db.Column(db.String(20), nullable=False)
  118. band_4m = db.Column(db.String(20), nullable=False)
  119. band_2m = db.Column(db.String(20), nullable=False)
  120. band_70cm = db.Column(db.String(20), nullable=False)
  121. band_23cm = db.Column(db.String(20), nullable=False)
  122. mode_ssb = db.Column(db.String(20), nullable=False)
  123. mode_cw = db.Column(db.String(20), nullable=False)
  124. serial = db.Column(db.String(20), nullable=False)
  125. point_per_km = db.Column(db.String(20), nullable=False)
  126. qra = db.Column(db.String(20), nullable=False)
  127. other_exch = db.Column(db.String(255), nullable=False)
  128. scoring = db.Column(db.String(255), nullable=False)
  129. slug = db.Column(db.String(255), unique=True, nullable=True)
  130. class Contest(db.Model):
  131. __tablename__ = "contests"
  132. id = db.Column(db.Integer, primary_key=True)
  133. name = db.Column(db.String(255), nullable=False)
  134. start = db.Column(db.DateTime(timezone=False), nullable=False)
  135. end = db.Column(db.DateTime(timezone=False), nullable=False)
  136. template = db.Column(db.Integer(), nullable=False)
  137. serial_num = db.Column(db.Integer(), nullable=False)
  138. slug = db.Column(db.String(255), unique=True, nullable=True)
  139. # possible FK template->contest_template
  140. class Contact(db.Model):
  141. __tablename__ = "contact"
  142. id = db.Column(db.Integer, primary_key=True)
  143. callsign = db.Column(db.String(32), nullable=False)
  144. gridsquare = db.Column(db.String(32))
  145. distance = db.Column(db.Float)
  146. bearing = db.Column(db.Float)
  147. bearing_star = db.Column(db.String(32))
  148. longitude = db.Column(db.Float)
  149. latitude = db.Column(db.Float)
  150. slug = db.Column(db.String(255), unique=True, nullable=True)
  151. user_id = db.Column(db.Integer(), db.ForeignKey("user.id"), nullable=False)
  152. class Logbook(db.Model):
  153. __tablename__ = "logbook"
  154. id = db.Column(db.Integer, primary_key=True)
  155. name = db.Column(db.String(255), nullable=False)
  156. callsign = db.Column(db.String(32), nullable=False)
  157. locator = db.Column(db.String(16), nullable=False)
  158. swl = db.Column(db.Boolean, default=False)
  159. default = db.Column(db.Boolean, default=False)
  160. public = db.Column(db.Boolean, default=True)
  161. eqsl_qth_nickname = db.Column(db.String(255))
  162. slug = db.Column(db.String(255), unique=True, nullable=True)
  163. old = db.Column(db.Boolean, default=False)
  164. logs = db.relationship("Log", backref="logbook", lazy="dynamic", cascade="delete")
  165. user_loggings = db.relationship("UserLogging", backref="logbook", lazy="dynamic", cascade="delete")
  166. user_id = db.Column(db.Integer(), db.ForeignKey("user.id"), nullable=False)
  167. class Picture(db.Model):
  168. __tablename__ = "picture"
  169. id = db.Column(db.Integer, primary_key=True)
  170. name = db.Column(db.String(255), nullable=False)
  171. filename = db.Column(db.String(255), unique=False, nullable=True)
  172. slug = db.Column(db.String(255), unique=True, nullable=True)
  173. log_id = db.Column(db.Integer(), db.ForeignKey("log.id"), nullable=False)
  174. class Log(db.Model):
  175. query_class = LogQuery
  176. __tablename__ = "log"
  177. id = db.Column(db.Integer, primary_key=True)
  178. address = db.Column(db.String(255), default=None)
  179. age = db.Column(db.Integer, default=None)
  180. a_index = db.Column(db.Float, default=None)
  181. ant_az = db.Column(db.Float, default=None)
  182. ant_el = db.Column(db.Float, default=None)
  183. ant_path = db.Column(db.String(2), default=None)
  184. arrl_sect = db.Column(db.String(10), default=None)
  185. band_id = db.Column(db.Integer(), db.ForeignKey("bands.id"), nullable=False)
  186. band_rx = db.Column(db.String(10), default=None)
  187. biography = db.Column(db.Text)
  188. call = db.Column(db.String(32), default=None, index=True)
  189. check = db.Column(db.String(8), default=None)
  190. klass = db.Column(db.String(8), default=None)
  191. cnty = db.Column(db.String(32), default=None)
  192. comment = db.Column(db.UnicodeText())
  193. cont = db.Column(db.String(6), default=None, index=True)
  194. contacted_op = db.Column(db.String(32), default=None)
  195. contest_id = db.Column(db.String(32), default=None)
  196. country = db.Column(db.String(64), default=None)
  197. cqz = db.Column(db.Integer, default=None)
  198. distance = db.Column(db.Float, default=None)
  199. dxcc = db.Column(db.String(6), default=None, index=True)
  200. email = db.Column(db.String(255), default=None)
  201. eq_call = db.Column(db.String(32), default=None)
  202. eqsl_qslrdate = db.Column(db.DateTime(timezone=False), default=None)
  203. eqsl_qslsdate = db.Column(db.DateTime(timezone=False), default=None)
  204. eqsl_qsl_rcvd = db.Column(db.String(2), default=None)
  205. eqsl_qsl_sent = db.Column(db.String(2), default=None)
  206. eqsl_status = db.Column(db.String(255), default=None)
  207. force_init = db.Column(db.Integer, default=None)
  208. freq = db.Column(db.Integer, default=None)
  209. freq_rx = db.Column(db.Integer, default=None)
  210. gridsquare = db.Column(db.String(12), default=None)
  211. cache_gridsquare = db.Column(db.String(12), default=None)
  212. heading = db.Column(db.Float, default=None)
  213. iota = db.Column(db.String(10), default=None, index=True)
  214. ituz = db.Column(db.Integer, default=None)
  215. k_index = db.Column(db.Float, default=None)
  216. lat = db.Column(db.Float, default=None)
  217. lon = db.Column(db.Float, default=None)
  218. lotw_qslrdate = db.Column(db.DateTime(timezone=False), default=None)
  219. lotw_qslsdate = db.Column(db.DateTime(timezone=False), default=None)
  220. lotw_qsl_rcvd = db.Column(db.String(2), default=None)
  221. lotw_qsl_sent = db.Column(db.String(2), default=None)
  222. lotw_status = db.Column(db.String(255), default=None)
  223. max_bursts = db.Column(db.Integer, default=None)
  224. mode_id = db.Column(db.Integer(), db.ForeignKey("modes.id"), nullable=False)
  225. ms_shower = db.Column(db.String(32), default=None)
  226. my_city = db.Column(db.String(32), default=None)
  227. my_cnty = db.Column(db.String(32), default=None)
  228. my_country = db.Column(db.String(64), default=None)
  229. my_cq_zone = db.Column(db.Integer, default=None)
  230. my_gridsquare = db.Column(db.String(12), default=None)
  231. my_iota = db.Column(db.String(10), default=None)
  232. my_itu_zone = db.Column(db.String(11), default=None)
  233. my_lat = db.Column(db.Float, default=None)
  234. my_lon = db.Column(db.Float, default=None)
  235. my_name = db.Column(db.String(255), default=None)
  236. my_postal_code = db.Column(db.String(24), default=None)
  237. my_rig = db.Column(db.String(255), default=None)
  238. my_sig = db.Column(db.String(32), default=None)
  239. my_sig_info = db.Column(db.String(64), default=None)
  240. my_state = db.Column(db.String(32), default=None)
  241. my_street = db.Column(db.String(64), default=None)
  242. name = db.Column(db.String(128), default=None)
  243. notes = db.Column(db.Text, default=None)
  244. nr_bursts = db.Column(db.Integer, default=None)
  245. nr_pings = db.Column(db.Integer, default=None)
  246. operator = db.Column(db.String(32), default=None)
  247. owner_callsign = db.Column(db.String(32), default=None)
  248. pfx = db.Column(db.String(32), default=None, index=True)
  249. precedence = db.Column(db.String(32), default=None)
  250. prop_mode = db.Column(db.String(8), default=None)
  251. public_key = db.Column(db.String(255), default=None)
  252. qslmsg = db.Column(db.String(255), default=None)
  253. qslrdate = db.Column(db.DateTime(timezone=False), default=None)
  254. qslsdate = db.Column(db.DateTime(timezone=False), default=None)
  255. qsl_rcvd = db.Column(db.String(2), default=None)
  256. qsl_rcvd_via = db.Column(db.String(2), default=None)
  257. qsl_sent = db.Column(db.String(2), default=None)
  258. qsl_sent_via = db.Column(db.String(2), default=None)
  259. qsl_via = db.Column(db.String(64), default=None)
  260. qso_complete = db.Column(db.String(6), default=None)
  261. qso_random = db.Column(db.String(11), default=None)
  262. qth = db.Column(db.String(64), default=None)
  263. rig = db.Column(db.String(255), default=None)
  264. rst_rcvd = db.Column(db.String(32), default=None)
  265. rst_sent = db.Column(db.String(32), default=None)
  266. rx_pwr = db.Column(db.Float, default=None)
  267. sat_mode = db.Column(db.String(32), default=None, index=True)
  268. sat_name = db.Column(db.String(32), default=None, index=True)
  269. sfi = db.Column(db.Float, default=None)
  270. sig = db.Column(db.String(32), default=None)
  271. sig_info = db.Column(db.String(64), default=None)
  272. srx = db.Column(db.String(11), default=None)
  273. srx_string = db.Column(db.String(32), default=None)
  274. state = db.Column(db.String(32), default=None)
  275. station_callsign = db.Column(db.String(32), default=None)
  276. stx = db.Column(db.String(11), default=None)
  277. stx_info = db.Column(db.String(32), default=None)
  278. swl = db.Column(db.Integer, default=None)
  279. ten_ten = db.Column(db.Integer, default=None)
  280. time_off = db.Column(db.DateTime(timezone=False), default=None)
  281. time_on = db.Column(db.DateTime(timezone=False), default=None, index=True)
  282. tx_pwr = db.Column(db.Float, default=None)
  283. web = db.Column(db.String(255), default=None)
  284. user_defined_0 = db.Column(db.String(64), default=None)
  285. user_defined_1 = db.Column(db.String(64), default=None)
  286. user_defined_2 = db.Column(db.String(64), default=None)
  287. user_defined_3 = db.Column(db.String(64), default=None)
  288. user_defined_4 = db.Column(db.String(64), default=None)
  289. user_defined_5 = db.Column(db.String(64), default=None)
  290. user_defined_6 = db.Column(db.String(64), default=None)
  291. user_defined_7 = db.Column(db.String(64), default=None)
  292. user_defined_8 = db.Column(db.String(64), default=None)
  293. user_defined_9 = db.Column(db.String(64), default=None)
  294. credit_granted = db.Column(db.String(64), default=None)
  295. credit_submitted = db.Column(db.String(64), default=None)
  296. slug = db.Column(db.String(255), unique=True, nullable=True)
  297. qsl_comment = db.Column(db.UnicodeText(), default=None)
  298. consolidated_hamqth = db.Column(db.Boolean(), default=False, nullable=False)
  299. user_id = db.Column(db.Integer(), db.ForeignKey("user.id"), nullable=False)
  300. logbook_id = db.Column(db.Integer(), db.ForeignKey("logbook.id"), nullable=False)
  301. pictures = db.relationship("Picture", backref="log", lazy="dynamic")
  302. user_loggings = db.relationship("UserLogging", backref="log", lazy="dynamic", cascade="delete")
  303. search_vector = db.Column(
  304. TSVectorType(
  305. "call",
  306. "comment",
  307. "country",
  308. "email",
  309. "name",
  310. "notes",
  311. "operator",
  312. "owner_callsign",
  313. "qslmsg",
  314. "station_callsign",
  315. "web",
  316. "qsl_comment",
  317. )
  318. )
  319. __mapper_args__ = {"order_by": time_on.desc()}
  320. # Give a cute name <3 More like "name - callsign" or "callsign"
  321. def cutename(self):
  322. cutename(self.call, self.name)
  323. def country_grid_coords(self):
  324. return ham_country_grid_coords(self.call)
  325. def country_grid(self):
  326. q = ham_country_grid_coords(self.call)
  327. if q:
  328. return coords_to_qth(q["latitude"], q["longitude"], 6)["qth"]
  329. else:
  330. return None
  331. def distance_from_user(self):
  332. if not self.gridsquare and not self.cache_gridsquare:
  333. qso_gs = self.country_grid()
  334. elif not self.gridsquare:
  335. qso_gs = self.cache_gridsquare
  336. else:
  337. qso_gs = self.gridsquare
  338. if not qso_gs or not self.user.locator:
  339. return None
  340. if not is_valid_qth(self.user.locator, 6) or not is_valid_qth(qso_gs, 6):
  341. return None
  342. _f = qth_to_coords(self.user.locator, 6) # precision, latitude, longitude
  343. _t = qth_to_coords(qso_gs, 6) # precision, latitude, longitude
  344. return distance.haversine_km(_f["latitude"], _f["longitude"], _t["latitude"], _t["longitude"])
  345. class Note(db.Model):
  346. __tablename__ = "notes"
  347. id = db.Column(db.Integer, primary_key=True)
  348. cat = db.Column(db.String(255), nullable=False)
  349. title = db.Column(db.String(255), nullable=False)
  350. note = db.Column(db.Text, nullable=False)
  351. timestamp = db.Column(db.DateTime(timezone=False), server_default=func.now(), onupdate=func.now())
  352. slug = db.Column(db.String(255), unique=True, nullable=True)
  353. user_id = db.Column(db.Integer(), db.ForeignKey("user.id"), nullable=False)
  354. __mapper_args__ = {"order_by": id.desc()}
  355. def timestamp_tz(self):
  356. t = self.timestamp
  357. if self.user.timezone.offset < 0:
  358. return t - datetime.timedelta(hours=self.user.timezone.offset)
  359. else:
  360. return t + datetime.timedelta(hours=self.user.timezone.offset)
  361. class Mode(db.Model):
  362. __tablename__ = "modes"
  363. id = db.Column(db.Integer, primary_key=True)
  364. mode = db.Column(db.String(255), nullable=False)
  365. submode = db.Column(db.String(255), nullable=True) # Unused as now
  366. logs = db.relationship("Log", backref="mode", lazy="dynamic")
  367. class Band(db.Model):
  368. __tablename__ = "bands"
  369. id = db.Column(db.Integer, primary_key=True)
  370. modes = db.Column(db.String(255), nullable=True)
  371. name = db.Column(db.String(255), nullable=False)
  372. lower = db.Column(db.BigInteger(), nullable=True)
  373. upper = db.Column(db.BigInteger(), nullable=True)
  374. start = db.Column(db.BigInteger(), nullable=True)
  375. # Zone format 'iaru1', 'iaru2', 'iaru3'
  376. zone = db.Column(db.String(10), nullable=False, default="iaru1")
  377. logs = db.relationship("Log", backref="band", lazy="dynamic")
  378. class DxccEntities(db.Model):
  379. __tablename__ = "dxcc_entities"
  380. id = db.Column(db.Integer, primary_key=True)
  381. adif = db.Column(db.Integer, nullable=False, index=True)
  382. name = db.Column(db.String(150), default=None)
  383. prefix = db.Column(db.String(30), nullable=False)
  384. cqz = db.Column(db.Float, nullable=False)
  385. ituz = db.Column(db.Float, nullable=False)
  386. cont = db.Column(db.String(5), nullable=False)
  387. long = db.Column(db.Float, nullable=False)
  388. lat = db.Column(db.Float, nullable=False)
  389. start = db.Column(db.DateTime(timezone=False), default=None)
  390. end = db.Column(db.DateTime(timezone=False), default=None)
  391. class DxccExceptions(db.Model):
  392. __tablename__ = "dxcc_exceptions"
  393. id = db.Column(db.Integer, primary_key=True)
  394. record = db.Column(db.Integer, nullable=False, index=True)
  395. call = db.Column(db.String(30), default=None)
  396. entity = db.Column(db.String(255), nullable=False)
  397. adif = db.Column(db.Integer, nullable=False)
  398. cqz = db.Column(db.Float, nullable=False)
  399. cont = db.Column(db.String(5), default=None)
  400. long = db.Column(db.Float, default=None)
  401. lat = db.Column(db.Float, default=None)
  402. start = db.Column(db.DateTime(timezone=False), default=None)
  403. end = db.Column(db.DateTime(timezone=False), default=None)
  404. class DxccPrefixes(db.Model):
  405. __tablename__ = "dxcc_prefixes"
  406. id = db.Column(db.Integer, primary_key=True)
  407. record = db.Column(db.Integer, nullable=False, index=True)
  408. call = db.Column(db.String(30), default=None)
  409. entity = db.Column(db.String(255), nullable=False)
  410. adif = db.Column(db.Integer, nullable=False)
  411. cqz = db.Column(db.Float, nullable=False)
  412. cont = db.Column(db.String(5), default=None)
  413. long = db.Column(db.Float, default=None)
  414. lat = db.Column(db.Float, default=None)
  415. start = db.Column(db.DateTime(timezone=False), default=None)
  416. end = db.Column(db.DateTime(timezone=False), default=None)
  417. class Logging(db.Model):
  418. __tablename__ = "logging"
  419. id = db.Column(db.Integer, primary_key=True)
  420. category = db.Column(db.String(255), nullable=False, default="General")
  421. level = db.Column(db.String(255), nullable=False, default="INFO")
  422. message = db.Column(db.Text, nullable=False)
  423. timestamp = db.Column(db.DateTime(timezone=False), server_default=func.now(), onupdate=func.now())
  424. user_id = db.Column(db.Integer(), db.ForeignKey("user.id"), nullable=True)
  425. class UserLogging(db.Model):
  426. __tablename__ = "user_logging"
  427. id = db.Column(db.Integer, primary_key=True)
  428. category = db.Column(db.String(255), nullable=False, default="General")
  429. level = db.Column(db.String(255), nullable=False, default="INFO")
  430. message = db.Column(db.Text, nullable=False)
  431. timestamp = db.Column(db.DateTime(timezone=False), server_default=func.now(), onupdate=func.now())
  432. log_id = db.Column(db.Integer(), db.ForeignKey("log.id"), nullable=True)
  433. logbook_id = db.Column(db.Integer(), db.ForeignKey("logbook.id"), nullable=True)
  434. user_id = db.Column(db.Integer(), db.ForeignKey("user.id"), nullable=False)
  435. # Utils functions
  436. def ham_country_grid_coords(call):
  437. if "sqlite" in db.engine.driver:
  438. q = (
  439. DxccPrefixes.query.filter(DxccPrefixes.call == func.substr(call, 1, func.LENGTH(DxccPrefixes.call)))
  440. .order_by(func.length(DxccPrefixes.call).asc())
  441. .limit(1)
  442. )
  443. else:
  444. q = (
  445. DxccPrefixes.query.filter(DxccPrefixes.call == func.substring(call, 1, func.LENGTH(DxccPrefixes.call)))
  446. .order_by(func.length(DxccPrefixes.call).asc())
  447. .limit(1)
  448. )
  449. if q.count() <= 0:
  450. return None
  451. else:
  452. qth = coords_to_qth(q[0].lat, q[0].long, 6)
  453. return {"qth": qth["qth"], "latitude": q[0].lat, "longitude": q[0].long}
  454. # Give a cute name <3 More like "name - callsign" or "callsign"
  455. def cutename(call, name=None):
  456. cute = ""
  457. if name:
  458. cute += name
  459. cute += " - "
  460. cute += call
  461. return cute
  462. @event.listens_for(User, "after_update")
  463. @event.listens_for(User, "after_insert")
  464. def make_user_slug(mapper, connection, target):
  465. title = "{0} {1}".format(target.id, target.name)
  466. slug = slugify(title)
  467. connection.execute(User.__table__.update().where(User.__table__.c.id == target.id).values(slug=slug))
  468. @event.listens_for(Cat, "after_update")
  469. @event.listens_for(Cat, "after_insert")
  470. def make_cat_slug(mapper, connection, target):
  471. title = "{0} {1}".format(target.id, target.radio)
  472. slug = slugify(title)
  473. connection.execute(Cat.__table__.update().where(Cat.__table__.c.id == target.id).values(slug=slug))
  474. @event.listens_for(ContestTemplate, "after_update")
  475. @event.listens_for(ContestTemplate, "after_insert")
  476. def make_contestt_slug(mapper, connection, target):
  477. title = "{0} {1}".format(target.id, target.name)
  478. slug = slugify(title)
  479. connection.execute(
  480. ContestTemplate.__table__.update().where(ContestTemplate.__table__.c.id == target.id).values(slug=slug)
  481. )
  482. @event.listens_for(Contest, "after_update")
  483. @event.listens_for(Contest, "after_insert")
  484. def make_contest_slug(mapper, connection, target):
  485. title = "{0} {1}".format(target.id, target.name)
  486. slug = slugify(title)
  487. connection.execute(Contest.__table__.update().where(Contest.__table__.c.id == target.id).values(slug=slug))
  488. @event.listens_for(Contact, "after_update")
  489. @event.listens_for(Contact, "after_insert")
  490. def make_contact_slug(mapper, connection, target):
  491. title = "{0} {1}".format(target.id, target.callsign)
  492. slug = slugify(title)
  493. connection.execute(Contact.__table__.update().where(Contact.__table__.c.id == target.id).values(slug=slug))
  494. @event.listens_for(Logbook, "after_update")
  495. @event.listens_for(Logbook, "after_insert")
  496. def make_logbook_slug(mapper, connection, target):
  497. title = "{0} {1}".format(target.id, target.name)
  498. slug = slugify(title)
  499. connection.execute(Logbook.__table__.update().where(Logbook.__table__.c.id == target.id).values(slug=slug))
  500. @event.listens_for(Picture, "after_update")
  501. @event.listens_for(Picture, "after_insert")
  502. def make_picture_slug(mapper, connection, target):
  503. title = "{0} {1}".format(target.id, target.name)
  504. slug = slugify(title)
  505. connection.execute(Picture.__table__.update().where(Picture.__table__.c.id == target.id).values(slug=slug))
  506. @event.listens_for(Log, "after_update")
  507. @event.listens_for(Log, "after_insert")
  508. def make_log_slug(mapper, connection, target):
  509. title = "{0} {1}".format(target.id, target.call)
  510. slug = slugify(title)
  511. connection.execute(Log.__table__.update().where(Log.__table__.c.id == target.id).values(slug=slug))
  512. @event.listens_for(Note, "after_update")
  513. @event.listens_for(Note, "after_insert")
  514. def make_note_slug(mapper, connection, target):
  515. title = "{0} {1}".format(target.id, target.title)
  516. slug = slugify(title)
  517. connection.execute(Note.__table__.update().where(Note.__table__.c.id == target.id).values(slug=slug))
  518. # Always at the end !
  519. db.configure_mappers()