/traffic_log/models.py

https://code.google.com/p/chirpradio/ · Python · 279 lines · 183 code · 50 blank · 46 comment · 34 complexity · 53838d69af6855659f2b5fa206007f0a MD5 · raw file

  1. ###
  2. ### Copyright 2009 The Chicago Independent Radio Project
  3. ### All Rights Reserved.
  4. ###
  5. ### Licensed under the Apache License, Version 2.0 (the "License");
  6. ### you may not use this file except in compliance with the License.
  7. ### You may obtain a copy of the License at
  8. ###
  9. ### http://www.apache.org/licenses/LICENSE-2.0
  10. ###
  11. ### Unless required by applicable law or agreed to in writing, software
  12. ### distributed under the License is distributed on an "AS IS" BASIS,
  13. ### WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. ### See the License for the specific language governing permissions and
  15. ### limitations under the License.
  16. ###
  17. import random
  18. import datetime
  19. import logging
  20. from google.appengine.ext import db, search
  21. from django.core.urlresolvers import reverse
  22. from auth.models import User
  23. from traffic_log import constants
  24. from common.autoretry import AutoRetry
  25. from common import time_util
  26. log = logging.getLogger()
  27. class SpotAtConstraint(object):
  28. """A spot within its constraint."""
  29. def __init__(self, spot_constraint, spot):
  30. self.spot = spot
  31. q = (TrafficLogEntry.all()
  32. .filter("log_date =", time_util.chicago_now().date())
  33. .filter("spot =", spot)
  34. .filter("hour =", spot_constraint.hour)
  35. .filter("slot =", spot_constraint.slot)
  36. .filter("dow =", spot_constraint.dow))
  37. if AutoRetry(q).count(1):
  38. self.finished = True
  39. else:
  40. self.finished = False
  41. class SpotConstraint(db.Model):
  42. dow = db.IntegerProperty(verbose_name="Day of Week", choices=constants.DOW)
  43. hour = db.IntegerProperty(verbose_name="Hour", choices=constants.HOUR)
  44. slot = db.IntegerProperty(verbose_name="Spot", choices=constants.SLOT)
  45. spots = db.ListProperty(db.Key)
  46. def iter_spots(self):
  47. for spot in AutoRetry(Spot).get(self.spots):
  48. if spot is None:
  49. # there was a bug where deleted spots had lingering constraints.
  50. # See http://code.google.com/p/chirpradio/issues/detail?id=103
  51. continue
  52. copy, is_logged = spot.get_spot_copy(self.dow, self.hour, self.slot)
  53. if copy is None:
  54. # probably a spot with expired copy (or copy not yet created)
  55. continue
  56. yield spot
  57. def iter_spots_at_constraint(self):
  58. for spot in self.iter_spots():
  59. yield SpotAtConstraint(self, spot)
  60. def as_query_string(self):
  61. return "hour=%d&dow=%d&slot=%d" % (self.hour, self.dow, self.slot)
  62. def url_to_finish_spot(self, spot):
  63. url = ""
  64. if len(spot.random_spot_copies) > 0:
  65. url = reverse('traffic_log.finishReadingSpotCopy', args=(spot.random_spot_copies[0],))
  66. url = "%s?%s" % (url, self.as_query_string())
  67. return url
  68. @property
  69. def readable_slot_time(self):
  70. min_slot = str(self.slot)
  71. if min_slot == '0':
  72. min_slot = '00'
  73. meridian = 'am'
  74. hour = self.hour
  75. if hour > 12:
  76. meridian = 'pm'
  77. hour = hour - 12
  78. # exceptions:
  79. if hour == 12:
  80. meridian = 'pm'
  81. if hour == 0:
  82. hour = 12
  83. return "%s:%s%s" % (hour, min_slot, meridian)
  84. def __init__(self, *args, **kw):
  85. key_name = "%d:%d:%d" % (kw['dow'], kw['hour'], kw['slot'])
  86. super(SpotConstraint, self).__init__(*args, **kw)
  87. class Spot(db.Model):
  88. """
  89. """
  90. active = db.BooleanProperty(default=True)
  91. title = db.StringProperty(verbose_name="Spot Title", required=True)
  92. type = db.StringProperty(verbose_name="Spot Type", required=True, choices=constants.SPOT_TYPE)
  93. created = db.DateTimeProperty(auto_now_add=True)
  94. updated = db.DateTimeProperty(auto_now=True)
  95. random_spot_copies = db.ListProperty(db.Key)
  96. def all_spot_copy(self):
  97. # two queries (since there is no OR statement).
  98. # One for copy that does not expire and one for not-yet-expired copy
  99. q = SpotCopy.all().filter("spot =", self).filter("expire_on =", None)
  100. active_spots = [c for c in AutoRetry(q)]
  101. q = SpotCopy.all().filter("spot =", self).filter("expire_on >", datetime.datetime.now())
  102. for c in AutoRetry(q):
  103. active_spots.append(c)
  104. return active_spots
  105. def add_spot_copy(self, spot_copy):
  106. self.random_spot_copies.append(spot_copy.key())
  107. AutoRetry(self).save()
  108. def _expunge_expired_spot_copies(self, random_spot_copies):
  109. """Check to see if any of the cached spot copies have expired.
  110. if so, expunge them and save the spot with a new list.
  111. """
  112. one_expired = False
  113. q = SpotCopy.all().filter("spot =", self)
  114. q = q.filter("__key__ in", random_spot_copies)
  115. expired_spot_copy_keys = []
  116. for copy in q:
  117. if copy.expire_on and copy.expire_on <= datetime.datetime.now():
  118. expired_spot_copy_keys.append(copy.key())
  119. for expired_key in expired_spot_copy_keys:
  120. for k in random_spot_copies:
  121. one_expired = True
  122. if str(k) == str(expired_key):
  123. random_spot_copies.remove(k)
  124. if one_expired:
  125. # only save if we have to since expunging will be rare
  126. self.random_spot_copies = random_spot_copies
  127. AutoRetry(self).save()
  128. def shuffle_spot_copies(self, prev_spot_copy=None):
  129. """Shuffle list of spot copy keys associated with this spot."""
  130. spot_copies = [spot_copy.key() for spot_copy in self.all_spot_copy()]
  131. random.shuffle(spot_copies)
  132. # Get spot copies that have been read in the last period (two hours).
  133. date = datetime.datetime.now().date() - datetime.timedelta(hours=2)
  134. query = TrafficLogEntry.all().filter('log_date >=', date)
  135. recent_spot_copies = []
  136. for entry in query:
  137. recent_spot_copies.append(entry.spot_copy.key())
  138. # Iterate through list, moving spot copies that have been read in the past period to the
  139. # end of the list.
  140. for i in range(len(spot_copies)):
  141. if spot_copies[0] in recent_spot_copies:
  142. spot_copies.append(spot_copies.pop(0))
  143. # If all spot copies were read in the last period, the first item in the new shuffled list
  144. # may by chance be the last one read. If so, move to the end.
  145. if prev_spot_copy and spot_copies[0] == prev_spot_copy:
  146. spot_copies.append(spot_copies.pop(0))
  147. self.random_spot_copies = spot_copies
  148. def get_spot_copy(self, dow, hour, slot):
  149. spot_copy = None
  150. is_logged = False
  151. # If random spot copy list for this spot is empty, fill and shuffle.
  152. if len(self.random_spot_copies) == 0:
  153. self.shuffle_spot_copies()
  154. AutoRetry(self).save()
  155. self._expunge_expired_spot_copies(self.random_spot_copies)
  156. # if spot copies exist and none have expired...
  157. if len(self.random_spot_copies) > 0:
  158. # Return the spot copy that a DJ just read (even though the
  159. # finish link will be disabled)
  160. # or return the next random one for reading
  161. today = time_util.chicago_now().date()
  162. q = (TrafficLogEntry.all()
  163. .filter("log_date =", today)
  164. .filter("spot =", self)
  165. .filter("dow =", dow)
  166. .filter("hour =", hour)
  167. .filter("slot =", slot))
  168. # Spot copy exists for dow, hour, and slot. Return it.
  169. if AutoRetry(q).count(1):
  170. existing_logged_spot = AutoRetry(q).fetch(1)[0]
  171. spot_copy = existing_logged_spot.spot_copy
  172. is_logged = True
  173. # Return next random spot copy.
  174. else:
  175. spot_copy = AutoRetry(db).get(self.random_spot_copies[0])
  176. return spot_copy, is_logged
  177. def finish_spot_copy(self):
  178. # Pop off spot copy from this spot's shuffled list of spot copies.
  179. spot_copy = self.random_spot_copies.pop(0)
  180. # If shuffled spot copy list is empty, regenerate.
  181. if len(self.random_spot_copies) == 0:
  182. self.shuffle_spot_copies(spot_copy)
  183. AutoRetry(self).save()
  184. @property
  185. def constraints(self):
  186. return SpotConstraint.gql("where spots =:1 order by dow, hour, slot", self.key())
  187. def get_add_copy_url(self):
  188. return reverse('traffic_log.views.addCopyForSpot', args=(self.key(),))
  189. def get_absolute_url(self):
  190. return '/traffic_log/spot/%s/' % self.key()
  191. class SpotCopy(db.Model):
  192. spot = db.ReferenceProperty(Spot)
  193. underwriter = db.TextProperty(required=False)
  194. body = db.TextProperty(verbose_name="Spot Copy", required=True)
  195. expire_on = db.DateTimeProperty(verbose_name="Expire Date", required=False, default=None)
  196. author = db.ReferenceProperty(User)
  197. created = db.DateTimeProperty(auto_now_add=True)
  198. updated = db.DateTimeProperty(auto_now=True)
  199. def __unicode__(self):
  200. body_words = self.body.split(" ")
  201. def shorten(words, maxlen=55):
  202. s = ' '.join(words)
  203. if len(s) > maxlen:
  204. words.pop()
  205. return shorten(words)
  206. else:
  207. return s
  208. shortened_body = shorten([w for w in body_words])
  209. return u"%s..." % shortened_body
  210. __str__ = __unicode__
  211. def get_absolute_url(self):
  212. return '/traffic_log/spot-copy/%s/' % self.key()
  213. def get_delete_url(self):
  214. return reverse('traffic_log.deleteSpotCopy', args=(self.key(),))
  215. def get_edit_url(self):
  216. return reverse('traffic_log.editSpotCopy', args=(self.key(),))
  217. ## there can only be one entry per date, hour, slot
  218. class TrafficLogEntry(db.Model):
  219. log_date = db.DateProperty()
  220. spot = db.ReferenceProperty(Spot)
  221. spot_copy = db.ReferenceProperty(SpotCopy)
  222. dow = db.IntegerProperty()
  223. hour = db.IntegerProperty()
  224. slot = db.IntegerProperty()
  225. scheduled = db.ReferenceProperty(SpotConstraint)
  226. readtime = db.DateTimeProperty()
  227. reader = db.ReferenceProperty(User)
  228. created = db.DateTimeProperty(auto_now_add=True)