models.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. import os
  2. import time
  3. from datetime import datetime, timedelta
  4. import dateutil
  5. from django.db import models
  6. from django.utils.translation import ugettext_lazy as _
  7. from django.core.urlresolvers import reverse
  8. from django.contrib.auth.models import User
  9. #from template_utils.markup import formatter
  10. import csv
  11. from directory.models import Point, Place, Town
  12. from utils.moon_phase import moon_phase
  13. class StandardMetadata(models.Model):
  14. """
  15. A basic (abstract) model for metadata.
  16. Included in each model file to maintain application separation.
  17. Subclass new models from 'StandardMetadata' instead of 'models.Model'.
  18. """
  19. created = models.DateTimeField(default=datetime.now, editable=False)
  20. updated = models.DateTimeField(default=datetime.now, editable=False)
  21. class Meta:
  22. abstract = True
  23. def save(self, *args, **kwargs):
  24. self.updated = datetime.now()
  25. super(StandardMetadata, self).save(*args, **kwargs)
  26. class WeatherAlert(models.Model):
  27. ''' WeatherAlert model.
  28. Based on the NOAA weather alert feed. '''
  29. zone_id=models.CharField(_("NOAA zone id"), max_length=6)
  30. zone=models.CharField(_("NOAA zone"), max_length=255)
  31. updated=models.DateTimeField(_("updated"))
  32. summary=models.TextField(_('summary'), blank=True, null=True)
  33. effective=models.DateTimeField(_('effective time'))
  34. expires=models.DateTimeField(_("expiration time"))
  35. status=models.CharField(_("status"), max_length=30, blank=True, null=True)
  36. msg_type=models.CharField(_("message type"), max_length=30, blank=True, null=True)
  37. urgency=models.CharField(_("urgency"), max_length=30, blank=True, null=True)
  38. severity=models.CharField(_("severity"), max_length=30, blank=True, null=True)
  39. certainty=models.CharField(_("certainty"), max_length=30, blank=True, null=True)
  40. area=models.TextField(_("affected area"), blank=True, null=True)
  41. class Meta:
  42. verbose_name=_('weather alert')
  43. verbose_name_plural=_('weather alerts')
  44. ordering=('effective', 'expires',)
  45. get_latest_by='effective'
  46. def __unicode__(self):
  47. return u'Weather alert for %s until %s' % (self.zone, self.expires)
  48. class WeatherConditions(models.Model):
  49. ''' WeatherConditions model.'''
  50. town=models.ForeignKey(Town)
  51. observation_time=models.DateTimeField(_("observation time"))
  52. conditions=models.CharField(_("conditions"), max_length=140)
  53. temperature=models.FloatField(_("temperature (F)"))
  54. wind_dir=models.CharField(_("wind direction"), max_length=5)
  55. wind_speed=models.FloatField(_("wind speed (MPH)"))
  56. pressure=models.CharField(_("pressure (in)"), max_length=30)
  57. humidity=models.CharField(_("humitidy"), max_length=4)
  58. class Meta:
  59. verbose_name=_('weather condition')
  60. verbose_name_plural=_('weather conditions')
  61. ordering=('observation_time',)
  62. get_latest_by='observation_time'
  63. def __unicode__(self):
  64. return u'%s %s' % (self.town, self.observation_time)
  65. class WeatherForecast(models.Model):
  66. town=models.ForeignKey(Town)
  67. observation_time=models.DateTimeField(_("observation time (RFC822)"))
  68. period=models.CharField(_("period"), max_length=15)
  69. conditions=models.CharField(_("conditions"), max_length=255)
  70. high_temperature_f=models.IntegerField(_("high temperature (F)"), max_length=3, blank=True, null=True)
  71. low_temperature_f=models.IntegerField(_("low temperature (F)"), max_length=3, blank=True, null=True)
  72. wind_conditions=models.CharField(_("wind conditions"), max_length=100, blank=True, null=True)
  73. class Meta:
  74. verbose_name=_('weather condition')
  75. verbose_name_plural=_('weather conditions')
  76. ordering=('observation_time',)
  77. get_latest_by='observation_time'
  78. def __unicode__(self):
  79. return u'%s %s' % (self.town, self.observation_time)
  80. HEMISPHERES=(
  81. ('N', _('North')),
  82. ('S', _('South')),
  83. )
  84. MOON_PHASES = (
  85. (0, _('New')),
  86. (1, _('1st Q.')),
  87. (2, _('Full')),
  88. (3, _('3rd Q.')),
  89. )
  90. class AstroChart(models.Model):
  91. year=models.IntegerField(_('year'), max_length=4)
  92. dst_start=models.DateField(_('daylight savings start'))
  93. dst_end=models.DateField(_('daylight savings end'))
  94. hemisphere=models.CharField(max_length=1, choices=HEMISPHERES, default='N')
  95. class Meta:
  96. verbose_name=_('astronomical chart')
  97. verbose_name_plural=_('astronomical charts ')
  98. ordering=('-year',)
  99. get_latest_by='year'
  100. def __unicode__(self):
  101. return u'%s Astronomical Chart for the %s Hemisphere' % (self.year, self.hemisphere)
  102. class Day(models.Model):
  103. date=models.DateField(_('date'))
  104. chart=models.ForeignKey(AstroChart)
  105. sunrise=models.TimeField(_('sunrise'), null=True, blank=True)
  106. sunset=models.TimeField(_('sunset'), null=True, blank=True)
  107. moonrise=models.TimeField(_('moonrise'), null=True, blank=True)
  108. moonset=models.TimeField(_('moonset'), null=True, blank=True)
  109. moonphase=models.IntegerField(_('moon phase'), choices=MOON_PHASES, null=True, blank=True)
  110. moonphase_time=models.TimeField(_('moon phase time'), null=True, blank=True)
  111. moonphase_fraction=models.FloatField(_('moon phase fraction'), null=True, blank=True)
  112. civil_twilight_am=models.TimeField(_('morning civil twilight'), null=True, blank=True)
  113. civil_twilight_pm=models.TimeField(_('evening civil twilight'), null=True, blank=True)
  114. nautical_twilight_am=models.TimeField(_('morning nautical twilight'), null=True, blank=True)
  115. nautical_twilight_pm=models.TimeField(_('evening nautical twilight'), null=True, blank=True)
  116. daily_avg_temp=models.DecimalField(_('daily average temperature'), null=True, blank=True, decimal_places=2, max_digits=5)
  117. daily_high_temp=models.DecimalField(_('daily high temperature'), null=True, blank=True, decimal_places=2, max_digits=5)
  118. daily_low_temp=models.DecimalField(_('daily low temperature'), null=True, blank=True, decimal_places=2, max_digits=5)
  119. monthly_avg_temp=models.DecimalField(_('monthly average temperature'), null=True, blank=True, decimal_places=2, max_digits=5)
  120. yearly_avg_temp=models.DecimalField(_('yearly average temperature'), null=True, blank=True, decimal_places=2, max_digits=5)
  121. class Meta:
  122. verbose_name=_('day')
  123. verbose_name_plural=_('days')
  124. ordering=('date',)
  125. get_latest_by='date'
  126. def __unicode__(self):
  127. return u'%s' % (self.date)
  128. TIDE_STATES = (
  129. ('H', _('High')),
  130. ('L', _('Low')),
  131. )
  132. class TideChart(StandardMetadata):
  133. place=models.ForeignKey(Place)
  134. year=models.CharField(max_length=4)
  135. mean_range=models.FloatField()
  136. spring_range=models.FloatField()
  137. mean_tide_level=models.FloatField()
  138. public=models.BooleanField(_('public'), default=False)
  139. reference=models.ForeignKey('self', blank=True, null=True, related_name='ref_chart')
  140. high_tide_offset=models.FloatField(_('high tide offset'), blank=True, null=True)
  141. low_tide_offest=models.FloatField(_('low tide offset'), blank=True, null=True)
  142. high_time_offset=models.IntegerField(_('high time offset'), blank=True, null=True, max_length=2)
  143. low_time_offset=models.IntegerField(_('low time offset'), blank=True, null=True, max_length=2)
  144. class Meta:
  145. verbose_name = _('tide chart')
  146. verbose_name_plural = _('tide charts')
  147. ordering = ('place',)
  148. def __unicode__(self):
  149. return u'%s %s %s' % (self.place.title, "Tide Chart", self.year)
  150. def get_absolute_url(self):
  151. args = self.place.slug+'-'+self.year
  152. return reverse('al-tide-chart-detail', args=args)
  153. def save(self, *args, **kwargs):
  154. super(TideChart, self).save(*args, **kwargs)
  155. if self.reference:
  156. if not TideDay.objects.filter(tide_chart=self):
  157. # There are no days for this chart and it's referenced from another one
  158. # Let's generate the days
  159. self.generate_tidedays(self.reference.tideday_set.all())
  160. def generate_tidedays(self, tidedays):
  161. fill_tide=Tide.objects.create(time="12:00", level=0.0, state='L')
  162. overflow=False
  163. for ref_day in tidedays:
  164. new_day=TideDay.objects.create(day=ref_day.day, tide_chart=self)
  165. if overflow: # We overflowed from the previous day, so offset tides
  166. new_day.tide_1=self.generate_tide(fill_tide)
  167. new_day.tide_2=self.generate_tide(ref_day.tide_1)
  168. new_day.tide_3=self.generate_tide(ref_day.tide_2)
  169. new_day.tide_4=self.generate_tide(ref_day.tide_3)
  170. overflow=False # Reset the overflow flag
  171. else:
  172. new_day.tide_1=self.generate_tide(ref_day.tide_1)
  173. new_day.tide_2=self.generate_tide(ref_day.tide_2)
  174. new_day.tide_3=self.generate_tide(ref_day.tide_3)
  175. try: # Does ref_day.tide_4 exist?
  176. if ref_day.tide_4.time.hour==23:
  177. if ref_day.tide_4.state=='H':
  178. if (ref_day.tide_4.time.minute + self.high_time_offset) > 59:
  179. # Adding the offest would overflow the day, so save
  180. # the tide to add to the start of the next day.
  181. fill_tide=ref_day.tide_4
  182. overflow=True
  183. else:
  184. # Otherwise just save the high tide
  185. new_day.tide_4=self.generate_tide(ref_day.tide_4)
  186. else:
  187. # Do the same as the overflow scenario above, but for low tides
  188. if (ref_day.tide_4.time.minute + self.low_time_offset) > 59:
  189. fill_tide=ref_day.tide_4
  190. overflow=True
  191. else:
  192. new_day.tide_4=self.generate_tide(ref_day.tide_4)
  193. else: # Tide 4 is not even at 11 and will not trigger an overflow
  194. new_day.tide_4=self.generate_tide(ref_day.tide_4)
  195. except:
  196. pass
  197. new_day.save()
  198. return True
  199. def generate_tide(self, ref_tide):
  200. if ref_tide.state=='H':
  201. d=datetime(1900,1,1,ref_tide.time.hour, ref_tide.time.minute)+timedelta(minutes=self.high_time_offset)
  202. new_time=d.strftime("%H:%M")
  203. new_level=ref_tide.level*self.high_tide_offset
  204. new_state='H'
  205. else:
  206. d=datetime(1908,1,1,ref_tide.time.hour, ref_tide.time.minute)+timedelta(minutes=self.low_time_offset)
  207. new_time=d.strftime("%H:%M")
  208. new_level=ref_tide.level*self.low_tide_offest
  209. new_state='L'
  210. return Tide.objects.create(time=(new_time), level=(new_level), state=new_state)
  211. class Tide(models.Model):
  212. time=models.TimeField(_('time'))
  213. level=models.FloatField(_('level'), blank=True, null=True)
  214. state=models.CharField(max_length=1, choices=TIDE_STATES, blank=True)
  215. def __unicode__(self):
  216. return u'%s' % (self.time)
  217. def get_absolute_url(self):
  218. args = self.state + "-at-" + self.time
  219. return reverse('al-tide-detail', args=args)
  220. class TideDay(models.Model):
  221. day=models.ForeignKey(Day)
  222. tide_chart=models.ForeignKey(TideChart)
  223. tide_1=models.ForeignKey(Tide, related_name='tide_1', blank=True, null=True)
  224. tide_2=models.ForeignKey(Tide, related_name='tide_2', blank=True, null=True)
  225. tide_3=models.ForeignKey(Tide, related_name='tide_3', blank=True, null=True)
  226. tide_4=models.ForeignKey(Tide, related_name='tide_4', blank=True, null=True)
  227. class Meta:
  228. verbose_name=_('tide day')
  229. verbose_name_plural=_('tide days')
  230. ordering=('day',)
  231. get_latest_by='day'
  232. def __unicode__(self):
  233. return u'%s' % (self.day)
  234. class AstroChartUpload(models.Model):
  235. data_file = models.FileField(_('data file (.csv)'), upload_to="temp",
  236. help_text=_('A comma separeted list of tide data for your particular location.'))
  237. chart=models.ForeignKey(AstroChart, help_text=_('Select a chart to add the astronomical data to.'))
  238. dst=models.BooleanField(_('correct for DST'), default=False)
  239. class Meta:
  240. verbose_name = _('astronomical chart upload')
  241. verbose_name_plural = _('astronomical chart uploads')
  242. def save(self, *args, **kwargs):
  243. super(AstroChartUpload, self).save(*args, **kwargs)
  244. self.process_datafile()
  245. super(AstroChartUpload, self).delete()
  246. def process_datafile(self):
  247. try:
  248. data=csv.reader(open(self.data_file.path))
  249. except:
  250. raise Exception('There is a problem with your csv file.')
  251. for row in data:
  252. print row[0]
  253. day=Day.objects.create(date=row[0], chart=self.chart)
  254. if self.dst:
  255. if self.chart.dst_start < datetime.strptime(day.date, "%Y-%m-%d").date() < self.chart.dst_end:
  256. delta=timedelta(hours=1)
  257. else:
  258. delta=timedelta(hours=0)
  259. else:
  260. delta=timedelta(hours=0)
  261. # B/c python datetime blows:
  262. # Take string in csv file, convert to time, convert to datetime, add delta,
  263. # convert to timetuple and convert to string for insertion to db.
  264. day.sunrise=time.strftime("%H:%M", (datetime(*time.strptime(row[1], "%H%M")[0:5])+delta).timetuple())
  265. day.sunset=time.strftime("%H:%M", (datetime(*time.strptime(row[2], "%H%M")[0:5])+delta).timetuple())
  266. if row[3]:
  267. day.moonrise=time.strftime("%H:%M", (datetime(*time.strptime(row[3], "%H%M")[0:5])+delta).timetuple())
  268. if row[4]:
  269. day.moonset=time.strftime("%H:%M", (datetime(*time.strptime(row[4], "%H%M")[0:5])+delta).timetuple())
  270. day.civil_twilight_am=time.strftime("%H:%M", (datetime(*time.strptime(row[5], "%H%M")[0:5])+delta).timetuple())
  271. day.civil_twilight_pm=time.strftime("%H:%M", (datetime(*time.strptime(row[6], "%H%M")[0:5])+delta).timetuple())
  272. day.nautical_twilight_am=time.strftime("%H:%M", (datetime(*time.strptime(row[7], "%H%M")[0:5])+delta).timetuple())
  273. day.nautical_twilight_pm=time.strftime("%H:%M", (datetime(*time.strptime(row[8], "%H%M")[0:5])+delta).timetuple())
  274. if row[9]:
  275. day.moonphase=row[9]
  276. day.moonphase_time=time.strftime("%H:%M", (datetime(*time.strptime(row[10], "%H%M")[0:5])+delta).timetuple())
  277. day.save()
  278. class TideChartUpload(models.Model):
  279. data_file = models.FileField(_('data file (.csv)'), upload_to="temp",
  280. help_text=_('A comma separeted list of tide data for your particular location.'))
  281. chart = models.ForeignKey(TideChart, help_text=_('Select a chart to add the tide data to.'))
  282. class Meta:
  283. verbose_name = _('tide chartupload')
  284. verbose_name_plural = _('tide chart uploads')
  285. def save(self, *args, **kwargs):
  286. super(TideChartUpload, self).save(*args, **kwargs)
  287. self.process_datafile()
  288. super(TideChartUpload, self).delete()
  289. def process_datafile(self):
  290. try:
  291. tides=BeautifulStoneSoup(open(self.data_file.path))
  292. except:
  293. raise Exception('There is a problem with your XML file or you do not have BeautifulSoup installed.')
  294. try:
  295. days = Day.objects.filter(date__year=chart.year)
  296. for day in days:
  297. '''
  298. XML for tide data from tidesandcurrents.noaa.gov:
  299. <item>
  300. <date>YYYY-MM-DD</date>
  301. <time>HH:MM A/P</time>
  302. <level_std>11.2 ft</level>
  303. <level_mtr>2.1 m</leve>
  304. <state>H/L</state>
  305. </item>
  306. '''
  307. days_tides = tides.findAll(text=day.date.replace('/','-'))
  308. tideday=TideDay.objects.create(day=day, tide_chart=self.chart)
  309. tide_1=days_tides[0].parent.parent
  310. tide_2=days_tides[1].parent.parent
  311. tide_3=days_tides[2].parent.parent
  312. try:
  313. tide_4=days_tides[3].parent.parent
  314. except:
  315. pass
  316. tideday.tide_1 = Tide.objects.create(time=tide_1.time.contents[0], level=tide_1.predictions_in_ft.contents[0], state=tide_1.highlow.conetnts[0])
  317. tideday.tide_2 = Tide.objects.create(time=tide_2.time.contents[0], level=tide_2.predictions_in_ft.contents[0], state=tide_2.highlow.conetnts[0])
  318. tideday.tide_3 = Tide.objects.create(time=tide_3.time.contents[0], level=tide_3.predictions_in_ft.contents[0], state=tide_3.highlow.conetnts[0])
  319. if tide_4:
  320. tideday.tide_4 = Tide.objects.create(time=tide_4.time.contents[0], level=tide_4.predictions_in_ft.contents[0], state=tide_4.highlow.conetnts[0])
  321. tideday.save()
  322. except:
  323. raise Exception('Tide data must be loaded after astronomical data for any given date.')