diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 6524b600..7fcb1540 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -246,6 +246,9 @@ def interactive_boxchart(datadf,fieldname,extratitle=''): return script,div +import holoviews as hv +hv.extension('bokeh') +renderer = hv.renderer('bokeh') def interactive_activitychart(workouts,startdate,enddate,stack='type'): @@ -321,8 +324,8 @@ def interactive_activitychart(workouts,startdate,enddate,stack='type'): df.sort_values('date_sorting',inplace=True) - p = Bar(df,values='duration', - label = CatAttr(columns=['date'], sort=False), + p = hv.Bars(df,values='duration', +# label = CatAttr(columns=['date'], sort=False), xlabel='Date', ylabel='Time', title='Activity {d1} to {d2}'.format( @@ -336,24 +339,29 @@ def interactive_activitychart(workouts,startdate,enddate,stack='type'): ) - for legend in p.legend: - new_items = [] - for legend_item in legend.items: - it = legend_item.label['value'] - tot = df[df[stack]==it].duration.sum() - if tot != 0: - new_items.append(legend_item) - legend.items = new_items + # for legend in p.legend: + # new_items = [] + # for legend_item in legend.items: + # it = legend_item.label['value'] + # tot = df[df[stack]==it].duration.sum() + # if tot != 0: + # new_items.append(legend_item) + # legend.items = new_items - p.legend.location = "top_left" - p.legend.background_fill_alpha = 0.7 + # p.legend.location = "top_left" + # p.legend.background_fill_alpha = 0.7 #p.sizing_mode = 'scale_width' - p.sizing_mode = 'stretch_both' + #p.sizing_mode = 'stretch_both' - p.yaxis.axis_label = 'Minutes' - - script, div = components(p) + #p.yaxis.axis_label = 'Minutes' + + try: + p = renderer.get_plot(p).state + script, div = components(p) + except: + script = '' + div = '' return script,div diff --git a/rowers/models.py b/rowers/models.py index de3ee877..a4fa5447 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -4,6 +4,8 @@ from __future__ import print_function from __future__ import unicode_literals from __future__ import unicode_literals, absolute_import +from django.utils.encoding import python_2_unicode_compatible + from django.db import models,IntegrityError from django.contrib.auth.models import User from django.core.validators import validate_email @@ -274,6 +276,7 @@ class PowerTimeFitnessMetric(models.Model): class Meta: db_table = 'powertimefitnessmetric' +@python_2_unicode_compatible class C2WorldClassAgePerformance(models.Model): weightcategories = ( ('hwt','heavy-weight'), @@ -304,7 +307,7 @@ class C2WorldClassAgePerformance(models.Model): class Meta: unique_together = ('age','sex','weightcategory','distance') - def __unicode__(self): + def __str__(self): thestring = '{s} {w} {n} age {a} ({season}) {distance}m {duration} seconds'.format( s = self.sex, w = self.weightcategory, @@ -325,6 +328,7 @@ def is_not_basic(user): # For future Team functionality +@python_2_unicode_compatible class Team(models.Model): choices = ( ('private','private'), @@ -345,7 +349,7 @@ class Team(models.Model): viewing = models.CharField(max_length=30,choices=viewchoices,default='allmembers',verbose_name='Sharing Behavior') - def __unicode__(self): + def __str__(self): return self.name def save(self, *args, **kwargs): @@ -610,6 +614,7 @@ paymentprocessors = ( ('braintree','BrainTree') ) +@python_2_unicode_compatible class PaidPlan(models.Model): shortname = models.CharField(max_length=50,choices=plans) name = models.CharField(max_length=200) @@ -627,7 +632,7 @@ class PaidPlan(models.Model): clubsize = models.IntegerField(default=0) - def __unicode__(self): + def __str__(self): return '{name} - {shortname} at {price:.2f} EURO ({paymenttype} payment)'.format( name = self.name, shortname = self.shortname, @@ -636,16 +641,18 @@ class PaidPlan(models.Model): paymentprocessor = self.paymentprocessor, ) +@python_2_unicode_compatible class CoachingGroup(models.Model): name = models.CharField(default='group',max_length=30,null=True,blank=True) - def __unicode__(self): + def __str__(self): return 'Coaching Group {id}: {name}'.format( id = self.pk, name = self.name ) # Extension of User with rowing specific data +@python_2_unicode_compatible class Rower(models.Model): adaptivetypes = mytypes.adaptivetypes stravatypes = ( @@ -862,9 +869,6 @@ class Rower(models.Model): def __str__(self): return self.user.first_name+' '+self.user.last_name - - def __unicode__(self): - return self.user.first_name+' '+self.user.last_name def clean_email(self): return self.user.email.lower() @@ -1099,13 +1103,14 @@ timezones = ( # models related to geo data (points, polygon, courses) +@python_2_unicode_compatible class GeoCourse(models.Model): manager = models.ForeignKey(Rower,null=True,on_delete=models.SET_NULL) distance = models.IntegerField(default=0) name = models.CharField(max_length=150,blank=True) country = models.CharField(max_length=150,blank=True) notes = models.CharField(blank=True,max_length=200,verbose_name='Course Notes') - def __unicode__(self): + def __str__(self): name = self.name country = self.country d = self.distance @@ -1129,12 +1134,13 @@ class GeoCourseEditForm(ModelForm): 'notes': forms.Textarea, } +@python_2_unicode_compatible class GeoPolygon(models.Model): name = models.CharField(max_length=150,blank=True) course = models.ForeignKey(GeoCourse, blank=True,on_delete=models.CASCADE) order_in_course = models.IntegerField(default=0) - def __unicode__(self): + def __str__(self): name = self.name coursename = self.course.name @@ -1169,7 +1175,7 @@ class TrainingTarget(models.Model): default=half_year_from_now) notes = models.TextField(max_length=300,blank=True) - def __unicode__(self): + def __str__(self): date = self.date name = self.name id = self.pk @@ -1221,7 +1227,7 @@ class TrainingTargetForm(ModelForm): - +@python_2_unicode_compatible class TrainingPlan(models.Model): statuschoices = ( @@ -1239,7 +1245,7 @@ class TrainingPlan(models.Model): enddate = models.DateField( default=half_year_from_now) - def __unicode__(self): + def __str__(self): name = self.name startdate = self.startdate enddate = self.enddate @@ -1603,6 +1609,7 @@ def macrocyclecheckdates(plan): pass cycles = cycles[1:] +@python_2_unicode_compatible class TrainingMacroCycle(models.Model): plan = models.ForeignKey(TrainingPlan,on_delete=models.CASCADE) name = models.CharField(max_length=150,blank=True) @@ -1625,7 +1632,7 @@ class TrainingMacroCycle(models.Model): actualrscore = models.IntegerField(default=0,verbose_name='Actual rScore') actualtrimp = models.IntegerField(default=0,verbose_name='Actual TRIMP') - def __unicode__(self): + def __str__(self): stri = 'Macro Cycle - {n} ({sd} - {ed})'.format( n = self.name, sd = self.startdate, @@ -1691,6 +1698,7 @@ class TrainingMacroCycleForm(ModelForm): 'enddate': AdminDateWidget() } +@python_2_unicode_compatible class TrainingMesoCycle(models.Model): plan = models.ForeignKey(TrainingMacroCycle,on_delete=models.CASCADE) name = models.CharField(max_length=150,blank=True) @@ -1713,7 +1721,7 @@ class TrainingMesoCycle(models.Model): actualtrimp = models.IntegerField(default=0,verbose_name='Actual TRIMP') - def __unicode__(self): + def __str__(self): stri = 'Meso Cycle - {n} ({sd} - {ed})'.format( n = self.name, sd = self.startdate, @@ -1767,6 +1775,7 @@ class TrainingMesoCycle(models.Model): createmicrofillers(self) +@python_2_unicode_compatible class TrainingMicroCycle(models.Model): plan = models.ForeignKey(TrainingMesoCycle,on_delete=models.CASCADE) name = models.CharField(max_length=150,blank=True) @@ -1791,7 +1800,7 @@ class TrainingMicroCycle(models.Model): - def __unicode__(self): + def __str__(self): stri = 'Micro Cycle - {n} ({sd} - {ed})'.format( n = self.name, sd = self.startdate, @@ -1862,6 +1871,7 @@ regularsessiontypechoices = ( ) # model for Planned Session (Workout, Challenge, Test) +@python_2_unicode_compatible class PlannedSession(models.Model): sessiontypechoices = ( @@ -1964,7 +1974,7 @@ class PlannedSession(models.Model): hasranking = models.BooleanField(default=False) - def __unicode__(self): + def __str__(self): name = self.name startdate = self.startdate @@ -2040,6 +2050,7 @@ registerchoices = ( ('manual','Manual - select below'), ) +@python_2_unicode_compatible class VirtualRace(PlannedSession): # has_registration = models.BooleanField(default=False) registration_form = models.CharField( @@ -2069,7 +2080,7 @@ class VirtualRace(PlannedSession): contact_email = models.EmailField(max_length=254, validators=[validate_email],blank=True) - def __unicode__(self): + def __str__(self): name = self.name startdate = self.startdate @@ -2490,6 +2501,7 @@ class PlannedSessionFormSmall(ModelForm): boattypes = mytypes.boattypes # Workout +@python_2_unicode_compatible class Workout(models.Model): workouttypes = mytypes.workouttypes workoutsources = mytypes.workoutsources @@ -2559,7 +2571,7 @@ class Workout(models.Model): rankingpiece = models.BooleanField(default=False,verbose_name='Ranking Piece') duplicate = models.BooleanField(default=False,verbose_name='Duplicate Workout') - def __unicode__(self): + def __str__(self): date = self.date name = self.name @@ -2658,6 +2670,7 @@ def auto_delete_strokedata_on_delete(sender, instance, **kwargs): engine.dispose() # Virtual Race results (for keeping results when workouts are deleted) +@python_2_unicode_compatible class VirtualRaceResult(models.Model): boatclasses = (type for type in mytypes.workouttypes if type[0] in mytypes.otwtypes) userid = models.IntegerField(default=0) @@ -2692,7 +2705,7 @@ class VirtualRaceResult(models.Model): emailnotifications = models.BooleanField(default=True, verbose_name = 'Receive race notifications by email') - def __unicode__(self): + def __str__(self): rr = Rower.objects.get(id=self.userid) name = '{u1} {u2}'.format( u1 = rr.user.first_name, @@ -2717,6 +2730,7 @@ class VirtualRaceResult(models.Model): ) # Virtual Race results (for keeping results when workouts are deleted) +@python_2_unicode_compatible class IndoorVirtualRaceResult(models.Model): boatclasses = (type for type in mytypes.workouttypes if type[0] in mytypes.otetypes) userid = models.IntegerField(default=0) # ID of rower object @@ -2747,7 +2761,7 @@ class IndoorVirtualRaceResult(models.Model): emailnotifications = models.BooleanField(default=True, verbose_name = 'Receive race notifications by email') - def __unicode__(self): + def __str__(self): rr = Rower.objects.get(id=self.userid) name = '{u1} {u2}'.format( u1 = rr.user.first_name, @@ -2904,6 +2918,7 @@ class ergcpdata(models.Model): app_label = 'rowers' # A wrapper around the png files +@python_2_unicode_compatible class GraphImage(models.Model): filename = models.CharField(default='',max_length=150,blank=True,null=True) creationdatetime = models.DateTimeField() @@ -3449,6 +3464,7 @@ class SiteAnnouncement(models.Model): return super(SiteAnnouncement,self).save(*args, **kwargs) # A comment by a user on a training +@python_2_unicode_compatible class WorkoutComment(models.Model): comment = models.TextField(max_length=300) created = models.DateTimeField(default=timezone.now) @@ -3457,7 +3473,7 @@ class WorkoutComment(models.Model): user = models.ForeignKey(User,on_delete=models.PROTECT) workout = models.ForeignKey(Workout,on_delete=models.CASCADE) - def __unicode__(self): + def __str__(self): return u'Comment to: {w} by {u1} {u2}'.format( w=self.workout, u1 = self.user.first_name, @@ -3474,6 +3490,7 @@ class WorkoutCommentForm(ModelForm): } # A comment by a user on a training +@python_2_unicode_compatible class PlannedSessionComment(models.Model): comment = models.TextField(max_length=300) created = models.DateTimeField(default=timezone.now) @@ -3482,7 +3499,7 @@ class PlannedSessionComment(models.Model): user = models.ForeignKey(User,on_delete=models.PROTECT) plannedsession = models.ForeignKey(PlannedSession,on_delete=models.CASCADE) - def __unicode__(self): + def __str__(self): return u'Comment to: {w} by {u1} {u2}'.format( w=self.workout, u1 = self.user.first_name,