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." )