import os import time from datetime import datetime, timedelta import dateutil from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse from django.contrib.auth.models import User #from template_utils.markup import formatter import csv from directory.models import Point, Place, Town from utils.moon_phase import moon_phase class StandardMetadata(models.Model): """ A basic (abstract) model for metadata. Included in each model file to maintain application separation. Subclass new models from 'StandardMetadata' instead of 'models.Model'. """ created = models.DateTimeField(default=datetime.now, editable=False) updated = models.DateTimeField(default=datetime.now, editable=False) class Meta: abstract = True def save(self, *args, **kwargs): self.updated = datetime.now() super(StandardMetadata, self).save(*args, **kwargs) class WeatherAlert(models.Model): ''' WeatherAlert model. Based on the NOAA weather alert feed. ''' zone_id=models.CharField(_("NOAA zone id"), max_length=6) zone=models.CharField(_("NOAA zone"), max_length=255) updated=models.DateTimeField(_("updated")) summary=models.TextField(_('summary'), blank=True, null=True) effective=models.DateTimeField(_('effective time')) expires=models.DateTimeField(_("expiration time")) status=models.CharField(_("status"), max_length=30, blank=True, null=True) msg_type=models.CharField(_("message type"), max_length=30, blank=True, null=True) urgency=models.CharField(_("urgency"), max_length=30, blank=True, null=True) severity=models.CharField(_("severity"), max_length=30, blank=True, null=True) certainty=models.CharField(_("certainty"), max_length=30, blank=True, null=True) area=models.TextField(_("affected area"), blank=True, null=True) class Meta: verbose_name=_('weather alert') verbose_name_plural=_('weather alerts') ordering=('effective', 'expires',) get_latest_by='effective' def __unicode__(self): return u'Weather alert for %s until %s' % (self.zone, self.expires) class WeatherConditions(models.Model): ''' WeatherConditions model.''' town=models.ForeignKey(Town) observation_time=models.DateTimeField(_("observation time")) conditions=models.CharField(_("conditions"), max_length=140) temperature=models.FloatField(_("temperature (F)")) wind_dir=models.CharField(_("wind direction"), max_length=5) wind_speed=models.FloatField(_("wind speed (MPH)")) pressure=models.CharField(_("pressure (in)"), max_length=30) humidity=models.CharField(_("humitidy"), max_length=4) class Meta: verbose_name=_('weather condition') verbose_name_plural=_('weather conditions') ordering=('observation_time',) get_latest_by='observation_time' def __unicode__(self): return u'%s %s' % (self.town, self.observation_time) class WeatherForecast(models.Model): town=models.ForeignKey(Town) observation_time=models.DateTimeField(_("observation time (RFC822)")) period=models.CharField(_("period"), max_length=15) conditions=models.CharField(_("conditions"), max_length=255) high_temperature_f=models.IntegerField(_("high temperature (F)"), max_length=3, blank=True, null=True) low_temperature_f=models.IntegerField(_("low temperature (F)"), max_length=3, blank=True, null=True) wind_conditions=models.CharField(_("wind conditions"), max_length=100, blank=True, null=True) class Meta: verbose_name=_('weather condition') verbose_name_plural=_('weather conditions') ordering=('observation_time',) get_latest_by='observation_time' def __unicode__(self): return u'%s %s' % (self.town, self.observation_time) HEMISPHERES=( ('N', _('North')), ('S', _('South')), ) MOON_PHASES = ( (0, _('New')), (1, _('1st Q.')), (2, _('Full')), (3, _('3rd Q.')), ) class AstroChart(models.Model): year=models.IntegerField(_('year'), max_length=4) dst_start=models.DateField(_('daylight savings start')) dst_end=models.DateField(_('daylight savings end')) hemisphere=models.CharField(max_length=1, choices=HEMISPHERES, default='N') class Meta: verbose_name=_('astronomical chart') verbose_name_plural=_('astronomical charts ') ordering=('-year',) get_latest_by='year' def __unicode__(self): return u'%s Astronomical Chart for the %s Hemisphere' % (self.year, self.hemisphere) class Day(models.Model): date=models.DateField(_('date')) chart=models.ForeignKey(AstroChart) sunrise=models.TimeField(_('sunrise'), null=True, blank=True) sunset=models.TimeField(_('sunset'), null=True, blank=True) moonrise=models.TimeField(_('moonrise'), null=True, blank=True) moonset=models.TimeField(_('moonset'), null=True, blank=True) moonphase=models.IntegerField(_('moon phase'), choices=MOON_PHASES, null=True, blank=True) moonphase_time=models.TimeField(_('moon phase time'), null=True, blank=True) moonphase_fraction=models.FloatField(_('moon phase fraction'), null=True, blank=True) civil_twilight_am=models.TimeField(_('morning civil twilight'), null=True, blank=True) civil_twilight_pm=models.TimeField(_('evening civil twilight'), null=True, blank=True) nautical_twilight_am=models.TimeField(_('morning nautical twilight'), null=True, blank=True) nautical_twilight_pm=models.TimeField(_('evening nautical twilight'), null=True, blank=True) daily_avg_temp=models.DecimalField(_('daily average temperature'), null=True, blank=True, decimal_places=2, max_digits=5) daily_high_temp=models.DecimalField(_('daily high temperature'), null=True, blank=True, decimal_places=2, max_digits=5) daily_low_temp=models.DecimalField(_('daily low temperature'), null=True, blank=True, decimal_places=2, max_digits=5) monthly_avg_temp=models.DecimalField(_('monthly average temperature'), null=True, blank=True, decimal_places=2, max_digits=5) yearly_avg_temp=models.DecimalField(_('yearly average temperature'), null=True, blank=True, decimal_places=2, max_digits=5) class Meta: verbose_name=_('day') verbose_name_plural=_('days') ordering=('date',) get_latest_by='date' def __unicode__(self): return u'%s' % (self.date) TIDE_STATES = ( ('H', _('High')), ('L', _('Low')), ) class TideChart(StandardMetadata): place=models.ForeignKey(Place) year=models.CharField(max_length=4) mean_range=models.FloatField() spring_range=models.FloatField() mean_tide_level=models.FloatField() public=models.BooleanField(_('public'), default=False) reference=models.ForeignKey('self', blank=True, null=True, related_name='ref_chart') high_tide_offset=models.FloatField(_('high tide offset'), blank=True, null=True) low_tide_offest=models.FloatField(_('low tide offset'), blank=True, null=True) high_time_offset=models.IntegerField(_('high time offset'), blank=True, null=True, max_length=2) low_time_offset=models.IntegerField(_('low time offset'), blank=True, null=True, max_length=2) class Meta: verbose_name = _('tide chart') verbose_name_plural = _('tide charts') ordering = ('place',) def __unicode__(self): return u'%s %s %s' % (self.place.title, "Tide Chart", self.year) def get_absolute_url(self): args = self.place.slug+'-'+self.year return reverse('al-tide-chart-detail', args=args) def save(self, *args, **kwargs): super(TideChart, self).save(*args, **kwargs) if self.reference: if not TideDay.objects.filter(tide_chart=self): # There are no days for this chart and it's referenced from another one # Let's generate the days self.generate_tidedays(self.reference.tideday_set.all()) def generate_tidedays(self, tidedays): fill_tide=Tide.objects.create(time="12:00", level=0.0, state='L') overflow=False for ref_day in tidedays: new_day=TideDay.objects.create(day=ref_day.day, tide_chart=self) if overflow: # We overflowed from the previous day, so offset tides new_day.tide_1=self.generate_tide(fill_tide) new_day.tide_2=self.generate_tide(ref_day.tide_1) new_day.tide_3=self.generate_tide(ref_day.tide_2) new_day.tide_4=self.generate_tide(ref_day.tide_3) overflow=False # Reset the overflow flag else: new_day.tide_1=self.generate_tide(ref_day.tide_1) new_day.tide_2=self.generate_tide(ref_day.tide_2) new_day.tide_3=self.generate_tide(ref_day.tide_3) try: # Does ref_day.tide_4 exist? if ref_day.tide_4.time.hour==23: if ref_day.tide_4.state=='H': if (ref_day.tide_4.time.minute + self.high_time_offset) > 59: # Adding the offest would overflow the day, so save # the tide to add to the start of the next day. fill_tide=ref_day.tide_4 overflow=True else: # Otherwise just save the high tide new_day.tide_4=self.generate_tide(ref_day.tide_4) else: # Do the same as the overflow scenario above, but for low tides if (ref_day.tide_4.time.minute + self.low_time_offset) > 59: fill_tide=ref_day.tide_4 overflow=True else: new_day.tide_4=self.generate_tide(ref_day.tide_4) else: # Tide 4 is not even at 11 and will not trigger an overflow new_day.tide_4=self.generate_tide(ref_day.tide_4) except: pass new_day.save() return True def generate_tide(self, ref_tide): if ref_tide.state=='H': d=datetime(1900,1,1,ref_tide.time.hour, ref_tide.time.minute)+timedelta(minutes=self.high_time_offset) new_time=d.strftime("%H:%M") new_level=ref_tide.level*self.high_tide_offset new_state='H' else: d=datetime(1908,1,1,ref_tide.time.hour, ref_tide.time.minute)+timedelta(minutes=self.low_time_offset) new_time=d.strftime("%H:%M") new_level=ref_tide.level*self.low_tide_offest new_state='L' return Tide.objects.create(time=(new_time), level=(new_level), state=new_state) class Tide(models.Model): time=models.TimeField(_('time')) level=models.FloatField(_('level'), blank=True, null=True) state=models.CharField(max_length=1, choices=TIDE_STATES, blank=True) def __unicode__(self): return u'%s' % (self.time) def get_absolute_url(self): args = self.state + "-at-" + self.time return reverse('al-tide-detail', args=args) class TideDay(models.Model): day=models.ForeignKey(Day) tide_chart=models.ForeignKey(TideChart) tide_1=models.ForeignKey(Tide, related_name='tide_1', blank=True, null=True) tide_2=models.ForeignKey(Tide, related_name='tide_2', blank=True, null=True) tide_3=models.ForeignKey(Tide, related_name='tide_3', blank=True, null=True) tide_4=models.ForeignKey(Tide, related_name='tide_4', blank=True, null=True) class Meta: verbose_name=_('tide day') verbose_name_plural=_('tide days') ordering=('day',) get_latest_by='day' def __unicode__(self): return u'%s' % (self.day) class AstroChartUpload(models.Model): data_file = models.FileField(_('data file (.csv)'), upload_to="temp", help_text=_('A comma separeted list of tide data for your particular location.')) chart=models.ForeignKey(AstroChart, help_text=_('Select a chart to add the astronomical data to.')) dst=models.BooleanField(_('correct for DST'), default=False) class Meta: verbose_name = _('astronomical chart upload') verbose_name_plural = _('astronomical chart uploads') def save(self, *args, **kwargs): super(AstroChartUpload, self).save(*args, **kwargs) self.process_datafile() super(AstroChartUpload, self).delete() def process_datafile(self): try: data=csv.reader(open(self.data_file.path)) except: raise Exception('There is a problem with your csv file.') for row in data: print row[0] day=Day.objects.create(date=row[0], chart=self.chart) if self.dst: if self.chart.dst_start < datetime.strptime(day.date, "%Y-%m-%d").date() < self.chart.dst_end: delta=timedelta(hours=1) else: delta=timedelta(hours=0) else: delta=timedelta(hours=0) # B/c python datetime blows: # Take string in csv file, convert to time, convert to datetime, add delta, # convert to timetuple and convert to string for insertion to db. day.sunrise=time.strftime("%H:%M", (datetime(*time.strptime(row[1], "%H%M")[0:5])+delta).timetuple()) day.sunset=time.strftime("%H:%M", (datetime(*time.strptime(row[2], "%H%M")[0:5])+delta).timetuple()) if row[3]: day.moonrise=time.strftime("%H:%M", (datetime(*time.strptime(row[3], "%H%M")[0:5])+delta).timetuple()) if row[4]: day.moonset=time.strftime("%H:%M", (datetime(*time.strptime(row[4], "%H%M")[0:5])+delta).timetuple()) day.civil_twilight_am=time.strftime("%H:%M", (datetime(*time.strptime(row[5], "%H%M")[0:5])+delta).timetuple()) day.civil_twilight_pm=time.strftime("%H:%M", (datetime(*time.strptime(row[6], "%H%M")[0:5])+delta).timetuple()) day.nautical_twilight_am=time.strftime("%H:%M", (datetime(*time.strptime(row[7], "%H%M")[0:5])+delta).timetuple()) day.nautical_twilight_pm=time.strftime("%H:%M", (datetime(*time.strptime(row[8], "%H%M")[0:5])+delta).timetuple()) if row[9]: day.moonphase=row[9] day.moonphase_time=time.strftime("%H:%M", (datetime(*time.strptime(row[10], "%H%M")[0:5])+delta).timetuple()) day.save() class TideChartUpload(models.Model): data_file = models.FileField(_('data file (.csv)'), upload_to="temp", help_text=_('A comma separeted list of tide data for your particular location.')) chart = models.ForeignKey(TideChart, help_text=_('Select a chart to add the tide data to.')) class Meta: verbose_name = _('tide chartupload') verbose_name_plural = _('tide chart uploads') def save(self, *args, **kwargs): super(TideChartUpload, self).save(*args, **kwargs) self.process_datafile() super(TideChartUpload, self).delete() def process_datafile(self): try: tides=BeautifulStoneSoup(open(self.data_file.path)) except: raise Exception('There is a problem with your XML file or you do not have BeautifulSoup installed.') try: days = Day.objects.filter(date__year=chart.year) for day in days: ''' XML for tide data from tidesandcurrents.noaa.gov: YYYY-MM-DD 11.2 ft 2.1 m H/L ''' days_tides = tides.findAll(text=day.date.replace('/','-')) tideday=TideDay.objects.create(day=day, tide_chart=self.chart) tide_1=days_tides[0].parent.parent tide_2=days_tides[1].parent.parent tide_3=days_tides[2].parent.parent try: tide_4=days_tides[3].parent.parent except: pass 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]) 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]) 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]) if tide_4: 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]) tideday.save() except: raise Exception('Tide data must be loaded after astronomical data for any given date.')