from __future__ import unicode_literals from django.db import models from django.contrib.auth.models import User from django import forms from django.forms import ModelForm from django.dispatch import receiver from django.forms.widgets import SplitDateTimeWidget from django.forms.formsets import BaseFormSet from datetimewidget.widgets import DateTimeWidget import os import twitter from django.conf import settings from sqlalchemy import create_engine import sqlalchemy as sa from sqlite3 import OperationalError from django.utils import timezone import datetime from rowsandall_app.settings import ( TWEET_ACCESS_TOKEN_KEY, TWEET_ACCESS_TOKEN_SECRET, TWEET_CONSUMER_KEY, TWEET_CONSUMER_SECRET, ) tweetapi = twitter.Api(consumer_key=TWEET_CONSUMER_KEY, consumer_secret=TWEET_CONSUMER_SECRET, access_token_key=TWEET_ACCESS_TOKEN_KEY, access_token_secret=TWEET_ACCESS_TOKEN_SECRET) user = settings.DATABASES['default']['USER'] password = settings.DATABASES['default']['PASSWORD'] database_name = settings.DATABASES['default']['NAME'] host = settings.DATABASES['default']['HOST'] port = settings.DATABASES['default']['PORT'] database_url = 'mysql://{user}:{password}@{host}:{port}/{database_name}'.format( user=user, password=password, database_name=database_name, host=host, port=port, ) if settings.DEBUG or user=='': database_url = 'sqlite:///db.sqlite3' # Create your models here. class Team(models.Model): name = models.CharField(max_length=150) notes = models.CharField(blank=True,max_length=200) class Rower(models.Model): weightcategories = ( ('hwt','heavy-weight'), ('lwt','light-weight'), ) user = models.OneToOneField(User) max = models.IntegerField(default=192,verbose_name="Max Heart Rate") rest = models.IntegerField(default=48,verbose_name="Resting Heart Rate") ut2 = models.IntegerField(default=105,verbose_name="UT2 band lower HR") ut1 = models.IntegerField(default=146,verbose_name="UT1 band lower HR") at = models.IntegerField(default=160,verbose_name="AT band lower HR") tr = models.IntegerField(default=167,verbose_name="TR band lower HR") an = models.IntegerField(default=180,verbose_name="AN band lower HR") weightcategory = models.CharField(default="hwt", max_length=30, choices=weightcategories) ftp = models.IntegerField(default=226,verbose_name="Functional Threshold Power") c2token = models.CharField(default='',max_length=200,blank=True,null=True) tokenexpirydate = models.DateTimeField(blank=True,null=True) c2refreshtoken = models.CharField(default='',max_length=200,blank=True,null=True) sporttrackstoken = models.CharField(default='',max_length=200,blank=True,null=True) sporttrackstokenexpirydate = models.DateTimeField(blank=True,null=True) sporttracksrefreshtoken = models.CharField(default='',max_length=200, blank=True,null=True) stravatoken = models.CharField(default='',max_length=200,blank=True,null=True) plans = ( ('basic','basic'), ('pro','pro'), ('coach','coach') ) rowerplan = models.CharField(default='basic',max_length=30, choices=plans) friends = models.ManyToManyField("self",blank=True) team = models.ForeignKey(Team,blank=True,null=True) def __str__(self): return self.user.username class FavoriteChart(models.Model): y1params = ( ('hr','Heart Rate'), ('pace','Pace'), ('spm','SPM'), ('driveenergy','Work per Stroke'), ('power','Power'), ('drivelength','Drivelength'), ('averageforce','Average Force'), ('peakforce','Peak Force'), ('forceratio','Average/Peak Force Ratio'), ('drivespeed','Drive Speed'), ('wash','Wash'), ('slip','Slip'), ('catch','Catch Angle'), ('finish','Finish Angle'), ('peakforceangle','Peak Force Angle') ) y2params = ( ('hr','Heart Rate'), ('spm','SPM'), ('driveenergy','Work per Stroke'), ('power','Power'), ('drivelength','Drivelength'), ('averageforce','Average Force'), ('peakforce','Peak Force'), ('forceratio','Average/Peak Force Ratio'), ('drivespeed','Drive Speed'), ('wash','Wash'), ('slip','Slip'), ('catch','Catch Angle'), ('finish','Finish Angle'), ('peakforceangle','Peak Force Angle'), ('None','None') ) xparams = ( ('time','Time'), ('distance','Distance'), ('hr','Heart Rate'), ('spm','SPM'), ('driveenergy','Work per Stroke'), ('power','Power'), ('drivelength','Drivelength'), ('averageforce','Average Force'), ('peakforce','Peak Force'), ('forceratio','Average/Peak Force Ratio'), ('drivespeed','Drive Speed'), ('wash','Wash'), ('slip','Slip'), ('catch','Catch Angle'), ('finish','Finish Angle'), ('peakforceangle','Peak Force Angle'), ) workouttypechoices = ( ('ote','Erg/SkiErg'), ('otw','On The Water'), ('both','both') ) plottypes = ( ('line','Line Chart'), ('scatter','Scatter Chart') ) yparam1 = models.CharField(max_length=50,choices=y1params,verbose_name='Y1') yparam2 = models.CharField(max_length=50,choices=y2params,verbose_name='Y2',default='None',blank=True) xparam = models.CharField(max_length=50,choices=xparams,verbose_name='X') plottype = models.CharField(max_length=50,choices=plottypes, default='line', verbose_name='Chart Type') workouttype = models.CharField(max_length=50,choices=workouttypechoices, default='both', verbose_name='Workout Type') reststrokes = models.BooleanField(default=True,verbose_name="Incl. Rest") user = models.ForeignKey(Rower) class FavoriteForm(ModelForm): class Meta: model = FavoriteChart fields = ['xparam','yparam1','yparam2', 'plottype','workouttype','reststrokes'] class BaseFavoriteFormSet(BaseFormSet): def clean(self): if any(self.errors): return for form in self.forms: if form.cleaned_data: xparam = form.cleaned_data['xparam'] yparam1 = form.cleaned_data['yparam1'] yparam2 = form.cleaned_data['yparam2'] plottype = form.cleaned_data['plottype'] reststrokes = form.cleaned_data['reststrokes'] if not xparam: raise forms.ValidationError( 'Must have x parameter.', code='missing_xparam' ) if not yparam1: raise forms.ValidationError( 'Must have Y1 parameter.', code='missing_yparam1' ) if not yparam2: yparam2 = 'None' class Workout(models.Model): workouttypes = ( ('water','On-water'), ('rower','Indoor Rower'), ('skierg','Ski Erg'), ('dynamic','Dynamic Indoor Rower'), ('slides','Indoor Rower on Slides'), ('paddle','Paddle Adapter'), ('snow','On-snow'), ) boattypes = ( ('1x', '1x (single)'), ('2x', '2x (double)'), ('2-', '2- (pair)'), ('4x', '4x (quad)'), ('4-', '4- (four)'), ('8+', '8+ (eight)'), ) user = models.ForeignKey(Rower) team = models.ForeignKey(Team,blank=True,null=True) name = models.CharField(max_length=150) date = models.DateField() workouttype = models.CharField(choices=workouttypes,max_length=50) boattype = models.CharField(choices=boattypes,max_length=50, default='1x (single)', verbose_name = 'Boat Type') starttime = models.TimeField(blank=True,null=True) startdatetime = models.DateTimeField(blank=True,null=True) distance = models.IntegerField(default=0,blank=True) duration = models.TimeField(default=1,blank=True) weightcategory = models.CharField(default="hwt",max_length=10) weightvalue = models.FloatField(default=80.0,blank=True,verbose_name = 'Average Crew Weight (kg)') csvfilename = models.CharField(blank=True,max_length=150) uploadedtoc2 = models.IntegerField(default=0) averagehr = models.IntegerField(blank=True,null=True) maxhr = models.IntegerField(blank=True,null=True) uploadedtostrava = models.IntegerField(default=0) uploadedtosporttracks = models.IntegerField(default=0) notes = models.CharField(blank=True,null=True,max_length=200) summary = models.TextField(blank=True) def __str__(self): date = self.date name = self.name str = date.strftime('%Y-%m-%d')+'_'+name return str # delete files belonging to workout instance # related GraphImage objects should be deleted automatically @receiver(models.signals.post_delete,sender=Workout) def auto_delete_file_on_delete(sender, instance, **kwargs): # delete CSV file if instance.csvfilename: if os.path.isfile(instance.csvfilename): os.remove(instance.csvfilename) if instance.csvfilename+'.gz': if os.path.isfile(instance.csvfilename+'.gz'): os.remove(instance.csvfilename+'.gz') @receiver(models.signals.post_delete,sender=Workout) def auto_delete_strokedata_on_delete(sender, instance, **kwargs): if instance.id: query = sa.text('DELETE FROM strokedata WHERE workoutid={id};'.format( id=instance.id, )) engine = create_engine(database_url, echo=False) with engine.connect() as conn, conn.begin(): try: result = conn.execute(query) except: print "Database Locked" conn.close() engine.dispose() class StrokeData(models.Model): class Meta: db_table = 'strokedata' index_together = ['workoutid'] workoutid = models.IntegerField(null=True) time = models.FloatField(null=True) hr = models.IntegerField(null=True) pace = models.FloatField(null=True) workoutstate = models.IntegerField(null=True,default=1) spm = models.FloatField(null=True) cumdist = models.FloatField(null=True) ftime = models.CharField(max_length=30) fpace = models.CharField(max_length=30) driveenergy = models.FloatField(null=True) power = models.FloatField(null=True) averageforce = models.FloatField(null=True) drivelength = models.FloatField(null=True) peakforce = models.FloatField(null=True) forceratio = models.FloatField(null=True) distance = models.FloatField(null=True) drivespeed = models.FloatField(null=True) hr_ut2 = models.IntegerField(null=True) hr_ut1 = models.IntegerField(null=True) hr_at = models.IntegerField(null=True) hr_tr = models.IntegerField(null=True) hr_an = models.IntegerField(null=True) hr_max = models.IntegerField(null=True) hr_bottom = models.IntegerField(null=True) x_right = models.FloatField(null=True) ergpace = models.FloatField(null=True) nowindpace = models.FloatField(null=True) equivergpower = models.FloatField(null=True) fergpace = models.CharField(max_length=30) fnowindpace = models.CharField(max_length=30) catch = models.FloatField(default=0,null=True) slip = models.FloatField(default=0,null=True) finish = models.FloatField(default=0,null=True) wash = models.FloatField(default=0,null=True) peakforceangle = models.FloatField(default=0,null=True) class GraphImage(models.Model): filename = models.CharField(default='',max_length=150,blank=True,null=True) creationdatetime = models.DateTimeField() workout = models.ForeignKey(Workout) def __str__(self): return self.filename # delete related file object @receiver(models.signals.post_delete,sender=GraphImage) def auto_delete_image_on_delete(sender,instance, **kwargs): if instance.filename: if os.path.isfile(instance.filename): os.remove(instance.filename) else: print "couldn't find the file "+instance.filename class DateInput(forms.DateInput): input_type = 'date' class WorkoutForm(ModelForm): duration = forms.TimeInput(format='%H:%M:%S.%f') class Meta: model = Workout fields = ['name','date','starttime','duration','distance','workouttype','boattype','notes'] widgets = { 'date': DateInput(), 'notes': forms.Textarea, 'duration': forms.TimeInput(format='%H:%M:%S.%f'), } def __init__(self, *args, **kwargs): super(WorkoutForm, self).__init__(*args, **kwargs) if self.instance.workouttype != 'water': del self.fields['boattype'] class AdvancedWorkoutForm(ModelForm): class Meta: model = Workout fields = ['boattype','weightvalue'] class RowerPowerForm(ModelForm): class Meta: model = Rower fields = ['ftp'] class RowerForm(ModelForm): class Meta: model = Rower fields = ['rest','ut2','ut1','at','tr','an','max','weightcategory'] def clean_rest(self): rest = self.cleaned_data['rest'] if rest<10: self.data['rest']=10 raise forms.ValidationError("Resting heart rate should be higher than 10 bpm") if rest>250: self.data['rest'] = 250 raise forms.ValidationError("Resting heart rate should be lower than 250 bpm") return rest def clean_ut2(self): ut2 = self.cleaned_data['ut2'] if ut2<10: raise forms.ValidationError("UT2 heart rate should be higher than 10 bpm") if ut2>250: raise forms.ValidationError("UT2 heart rate should be lower than 250 bpm") return ut2 def clean_ut1(self): ut1 = self.cleaned_data['ut1'] if ut1<10: raise forms.ValidationError("UT1 heart rate should be higher than 10 bpm") if ut1>250: raise forms.ValidationError("Resting heart rate should be lower than 250 bpm") return ut1 def clean_at(self): at = self.cleaned_data['at'] if at<10: raise forms.ValidationError("AT heart rate should be higher than 10 bpm") if at>250: raise forms.ValidationError("AT heart rate should be lower than 250 bpm") return at def clean_tr(self): tr = self.cleaned_data['tr'] if tr<10: raise forms.ValidationError("TR heart rate should be higher than 10 bpm") if tr>250: raise forms.ValidationError("TR heart rate should be lower than 250 bpm") return tr def clean_an(self): an = self.cleaned_data['an'] if an<10: raise forms.ValidationError("AN heart rate should be higher than 10 bpm") if an>250: raise forms.ValidationError("AN heart rate should be lower than 250 bpm") return an def clean_max(self): max = self.cleaned_data['max'] if max<10: raise forms.ValidationError("Max heart rate should be higher than 10 bpm") if max>250: raise forms.ValidationError("Max heart rate should be lower than 250 bpm") return max def clean(self): try: rest = self.cleaned_data['rest'] except: rest = int(self.data['rest']) try: ut2 = self.cleaned_data['ut2'] except: ut2 = self.data['ut2'] try: ut1 = self.cleaned_data['ut1'] except: ut1 = self.data['ut1'] try: at = self.cleaned_data['at'] except: at = self.data['at'] try: an = self.cleaned_data['an'] except: an = self.data['an'] try: tr = self.cleaned_data['tr'] except: tr = self.data['tr'] try: max = self.cleaned_data['max'] except: max = self.data['max'] if rest>=ut2: raise forms.ValidationError("Resting heart rate should be lower than UT2") if ut2>=ut1: raise forms.ValidationError("UT2 should be lower than UT1") if ut2>=ut1: raise forms.ValidationError("UT2 should be lower than UT1") if ut1>=at: raise forms.ValidationError("UT1 should be lower than AT") if at>=tr: raise forms.ValidationError("AT should be lower than TR") if tr>=an: raise forms.ValidationError("TR should be lower than AN") if an>=max: raise forms.ValidationError("AN should be lower than Max") class SiteAnnouncement(models.Model): created = models.DateField(default=timezone.now) announcement = models.TextField(max_length=140) expires = models.DateField(default=timezone.now) modified = models.DateField(default=timezone.now) dotweet = models.BooleanField(default=False) def save(self, *args, **kwargs): if not self.id: self.created = timezone.now() self.expires = timezone.now()+datetime.timedelta(days=10) self.modified = timezone.now() if self.dotweet: try: status = tweetapi.PostUpdate(self.announcement) except: try: status = tweetapi.PostUpdate(self.announcement[:135]) except: pass return super(SiteAnnouncement,self).save(*args, **kwargs)