diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 07a84620..faa5c3c0 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -2047,6 +2047,147 @@ def leaflet_chart(lat,lon,name=""):
+ return script,div
+
+def leaflet_chart_compare(workoutids,labeldict={},startenddict={}):
+ data = []
+ for id in workoutids:
+ w = Workout.objects.get(id=id)
+ rowdata = rdata(w.csvfilename)
+ df = pd.DataFrame({
+ 'id':id,
+ 'lat':rowdata.df[' latitude'],
+ 'lon':rowdata.df[' longitude']
+ })
+ data.append(f)
+
+ df = pd.concat(data,axis=0)
+
+
+
+
+ # Throw out 0,0
+ df = df.replace(0,np.nan)
+ df = df.loc[(df!=0).any(axis=1)]
+ df.fillna(method='bfill',axis=0,inplace=True)
+ df.fillna(method='ffill',axis=0,inplace=True)
+ lat = df['lat']
+ lon = df['lon']
+ if lat.empty or lon.empty:
+ return [0,"invalid coordinate data"]
+
+
+
+ coordinates = zip(lat,lon)
+
+ scoordinates = "["
+
+ for x,y in coordinates:
+ scoordinates += """[{x},{y}],
+ """.format(
+ x=x,
+ y=y
+ )
+
+ scoordinates += "]"
+
+ script = """
+
+ """.format(
+ latmean=latmean,
+ lonmean=lonmean,
+ latbegin = latbegin,
+ latend=latend,
+ longbegin=longbegin,
+ longend=longend,
+ scoordinates=scoordinates,
+ )
+
+ div = """
+
+ """
+
+
+
return script,div
def leaflet_chart2(lat,lon,name=""):
@@ -5057,7 +5198,7 @@ def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line',
'time','pace','workoutstate',
'workoutid']
- datadf = dataprep.getsmallrowdata_db(columns,ids=ids)
+ datadf = dataprep.getsmallrowdata_db(columns,ids=ids,doclean=False,compute=False)
datadf.dropna(axis=1,how='all',inplace=True)
datadf.dropna(axis=0,how='any',inplace=True)
diff --git a/rowers/models.py b/rowers/models.py
index 3fea4dbb..c7cbf391 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -2917,6 +2917,17 @@ def update_duplicates_on_delete(sender, instance, **kwargs):
# conn.close()
# engine.dispose()
+class VirtualRaceFollower(models.Model):
+ user = models.ForeignKey(User,on_delete=models.CASCADE,null=True)
+ race = models.ForeignKey(VirtualRace,on_delete=models.CASCADE)
+ emailaddress = models.EmailField(max_length=254,blank=True,null=True,
+ verbose_name="Email Address")
+
+class FollowerForm(ModelForm):
+ class Meta:
+ model = VirtualRaceFollower
+ fields = ['emailaddress']
+
# Virtual Race results (for keeping results when workouts are deleted)
@python_2_unicode_compatible
class VirtualRaceResult(models.Model):
diff --git a/rowers/templates/followerform.html b/rowers/templates/followerform.html
new file mode 100644
index 00000000..15b2231c
--- /dev/null
+++ b/rowers/templates/followerform.html
@@ -0,0 +1,39 @@
+{% extends "newbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+{% load tz %}
+
+
+
+{% block title %}Follow Virtual Challenge{% endblock %}
+
+{% block main %}
+
+Follow {{ plannedession.name }}
+
+
+
+ -
+
To follow this challenge and receive all news per email, fill out your
+ email address and submit the form.
+
+ -
+
+
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% if 'racing' in active %}
+{% include 'menu_racing.html' %}
+{% else %}
+{% include 'menu_plan.html' %}
+{% endif %}
+{% endblock %}
diff --git a/rowers/templates/mapcompare.html b/rowers/templates/mapcompare.html
new file mode 100644
index 00000000..e9ca1e92
--- /dev/null
+++ b/rowers/templates/mapcompare.html
@@ -0,0 +1,49 @@
+{% extends "newbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}View Comparison {% endblock %}
+
+{% block main %}
+
+
+
+
+{{ interactiveplot |safe }}
+
+Interactive Comparison
+
+
+
+ -
+
+ {{ the_div|safe }}
+
+
+
+ -
+
+
+
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% if active == 'nav-racing' %}
+{% include 'menu_racing.html' %}
+{% else %}
+{% include 'menu_workouts.html' %}
+{% endif %}
+{% endblock %}
diff --git a/rowers/templates/menu_racing.html b/rowers/templates/menu_racing.html
index 8dd74087..67e72c77 100644
--- a/rowers/templates/menu_racing.html
+++ b/rowers/templates/menu_racing.html
@@ -65,7 +65,9 @@
{% endif %}
{% if button == 'resubmitbutton' %}
- Submit New Result
+
+ Submit New Result
+
{% endif %}
{% if button == 'withdrawbutton' %}
diff --git a/rowers/templates/virtualevent.html b/rowers/templates/virtualevent.html
index 3e2297ce..86e61402 100644
--- a/rowers/templates/virtualevent.html
+++ b/rowers/templates/virtualevent.html
@@ -160,12 +160,16 @@
{% if request.user.is_anonymous %}
- Registered users of rowsandall.com can participate in this challenge. Participation is free, unless specified differently in the race comment above.
+ Registered users of rowsandall.com can participate in this challenge.
+ Participation is free, unless specified differently in the race comment above.
{% if race.sessiontype == 'race' %}
Register
{% else %}
Register
{% endif %}
+ {% if request.user|isfollower:race.id %}
+ Follow this challenge
+ {% endif %}
{% else %}
@@ -184,6 +188,9 @@
{% else %}
Register
{% endif %}
+ {% if request.user|isfollower:race.id %}
+ Follow this challenge
+ {% endif %}
{% endif %}
{% if button == 'submitbutton' %}
diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py
index 84b5c947..6a60d032 100644
--- a/rowers/templatetags/rowerfilters.py
+++ b/rowers/templatetags/rowerfilters.py
@@ -12,7 +12,8 @@ from rowers.utils import calculate_age
from rowers.models import (
course_length,WorkoutComment,
TrainingMacroCycle,TrainingMesoCycle, TrainingMicroCycle,
- Rower,Workout,SiteAnnouncement, TeamInvite, TeamRequest, CoachOffer,CoachRequest
+ Rower,Workout,SiteAnnouncement, TeamInvite, TeamRequest, CoachOffer,CoachRequest,
+ VirtualRaceFollower,VirtualRace,
)
from rowers.plannedsessions import (
race_can_register, race_can_submit,race_rower_status
@@ -40,6 +41,21 @@ from django.template.defaultfilters import stringfilter
from six import string_types
+@register.filter
+def isfollower(user,id):
+
+ if user.is_anonymous:
+ return True
+ try:
+ race = VirtualRace.objects.get(id=id)
+ except VirtualRace.DoesNotExist:
+ return False
+
+ followers = VirtualRaceFollower.objects.filter(race=race,user=user)
+
+
+ return followers.count()==0
+
@register.filter
def adaptive(s):
u = s
diff --git a/rowers/urls.py b/rowers/urls.py
index 1997e73f..b7fdeeb3 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -346,6 +346,8 @@ urlpatterns = [
views.virtualevent_uploadimage_view,name='virtualevent_uploadimage_view'),
re_path(r'^virtualevent/(?P
\d+)/setimage/(?P\d+)/$',
views.virtualevent_setlogo_view,name='virtualevent_setlog_view'),
+ re_path(r'^virtualevent/(?P\d+)/follow/$',
+ views.addfollower_view,name='addfollower_view'),
re_path(r'^logo/(?P\d+)/delete/$',
views.logo_delete_view,name='logo_delete_view'),
re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/darkskywind/$',views.workout_downloadwind_view,name='workout_downloadwind_view'),
diff --git a/rowers/views/racesviews.py b/rowers/views/racesviews.py
index ed1c53db..e2b06a4d 100644
--- a/rowers/views/racesviews.py
+++ b/rowers/views/racesviews.py
@@ -1625,6 +1625,39 @@ def virtualevent_addboat_view(request,id=0):
"You have successfully registered for this race. Good luck!"
)
+ otherrecords = VirtualRaceResult.objects.filter(
+ race = race).exclude(userid = r.id)
+
+ for otherrecord in otherrecords:
+ otheruser = Rower.objects.get(id=otherrecord.userid)
+ othername = otheruser.user.first_name+' '+otheruser.user.last_name
+ registeredname = r.user.first_name+' '+r.user.last_name
+ if otherrecord.emailnotifications:
+ job = myqueue(
+ queue,
+ handle_sendemail_raceregistration,
+ otheruser.user.email, othername,
+ registeredname,
+ race.name,
+ race.id
+ )
+
+ followers = VirtualRaceFollower.objects.filter(race = race)
+
+ for follower in followers:
+ othername = ''
+ if follower.user:
+ othername = follower.user.first_name+' '+follower.user.last_name
+
+ registeredname = r.user.first_name+' '+r.user.last_name
+ email = follower.emailaddress
+ job = myqueue(
+ queue,
+ handle_sendemail_raceregistration,
+ email, othername,
+ registeredname,race.name,race.id,
+ )
+
url = reverse('virtualevent_view',
kwargs = {
'id':race.id
@@ -1804,7 +1837,12 @@ def virtualevent_register_view(request,id=0):
add_rower_race(r,race)
- otherrecords = IndoorVirtualRaceResult.objects.filter(
+ # remove followers
+ myfollows = VirtualRaceFollower.objects.filter(user=r.user,race=race)
+ for f in myfollows:
+ f.delete()
+
+ otherrecords = VirtualRaceResult.objects.filter(
race = race).exclude(userid = r.id)
for otherrecord in otherrecords:
@@ -1821,6 +1859,22 @@ def virtualevent_register_view(request,id=0):
race.id
)
+ followers = VirtualRaceFollower.objects.filter(race = race)
+
+ for follower in followers:
+ othername = ''
+ if follower.user:
+ othername = follower.user.first_name+' '+follower.user.last_name
+
+ registeredname = r.user.first_name+' '+r.user.last_name
+ email = follower.emailaddress
+ job = myqueue(
+ queue,
+ handle_sendemail_raceregistration,
+ email, othername,
+ registeredname,race.name,race.id,
+ )
+
messages.info(
request,
@@ -2039,6 +2093,11 @@ def indoorvirtualevent_register_view(request,id=0):
add_rower_race(r,race)
+ # remove followers
+ myfollows = VirtualRaceFollower.objects.filter(user=r.user,race=race)
+ for f in myfollows:
+ f.delete()
+
otherrecords = IndoorVirtualRaceResult.objects.filter(
race = race).exclude(userid = r.id)
@@ -2056,6 +2115,22 @@ def indoorvirtualevent_register_view(request,id=0):
race.id
)
+ followers = VirtualRaceFollower.objects.filter(race = race)
+
+ for follower in followers:
+ othername = ''
+ if follower.user:
+ othername = follower.user.first_name+' '+follower.user.last_name
+
+ registeredname = r.user.first_name+' '+r.user.last_name
+ email = follower.emailaddress
+ job = myqueue(
+ queue,
+ handle_sendemail_raceregistration,
+ email, othername,
+ registeredname,race.name,race.id,
+ )
+
messages.info(
request,
@@ -2734,6 +2809,7 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
if not jobid:
messages.info(request,"Result submitted successfully.")
+
for otherrecord in otherrecords:
try:
otheruser = Rower.objects.get(id=otherrecord.userid)
@@ -2751,7 +2827,21 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
except Rower.DoesNotExist:
pass
+ followers = VirtualRaceFollower.objects.filter(race = race)
+ for follower in followers:
+ othername = ''
+ if follower.user:
+ othername = follower.user.first_name+' '+follower.user.last_name
+
+ registeredname = r.user.first_name+' '+r.user.last_name
+ email = follower.emailaddress
+ job = myqueue(
+ queue,
+ handle_sendemail_racesubmission,
+ email, othername,
+ registeredname,race.name,race.id,
+ )
# redirect to race page
@@ -2820,3 +2910,71 @@ def virtualevent_submit_result_view(request,id=0,workoutid=0):
'rower':r,
'w_form':w_form,
})
+
+def addfollower_view(request,id=0):
+ try:
+ race = VirtualRace.objects.get(id=id)
+ except VirtualRace.DoesNotExist:
+ raise Http404("Virtual Challenge does not exist")
+
+ if not request.user.is_anonymous:
+ follower = VirtualRaceFollower(
+ user=request.user,
+ race=race,
+ emailaddress = request.user.email
+ )
+ follower.save()
+ messages.info(request,"You will receive challenge notifications per email")
+ url = reverse(virtualevent_view,
+ kwargs={'id':id})
+
+ return HttpResponseRedirect(url)
+
+ # Anonymous
+ if request.method == 'POST':
+ form = FollowerForm(request.POST)
+ if form.is_valid():
+ email = form.cleaned_data['emailaddress']
+
+ follower = VirtualRaceFollower(
+ race=race,
+ emailaddress = email
+ )
+ follower.save()
+
+ messages.info(request,"You will receive challenge notifications per email")
+
+ url = reverse(virtualevent_view,
+ kwargs={'id':id})
+
+ return HttpResponseRedirect(url)
+
+ else:
+ form = FollowerForm()
+
+ breadcrumbs = [
+ {
+ 'url':reverse('virtualevents_view'),
+ 'name': 'Challenges'
+ },
+ {
+ 'url':reverse('virtualevent_view',
+ kwargs={'id':race.id}
+ ),
+ 'name': race.name
+ },
+ {
+ 'url': reverse(addfollower_view,
+ kwargs = {'id':race.id}
+ ),
+ 'name': 'Follow'
+ }
+ ]
+
+ return render(request,'followerform.html',
+ {
+ 'form':form,
+ 'active':'nav-racing',
+ 'breadcrumbs':breadcrumbs,
+ }
+ )
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 2a931d32..2bd13221 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -116,6 +116,7 @@ from rowers.models import (
PlannedSessionComment,CoachRequest,CoachOffer,
VideoAnalysis,ShareKey,
StandardCollection,CourseStandard,
+ VirtualRaceFollower,
)
from rowers.models import (
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
@@ -129,7 +130,8 @@ from rowers.models import (
VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm,
IndoorVirtualRaceResultForm,IndoorVirtualRaceResult,
IndoorVirtualRaceForm,PlannedSessionCommentForm,
- Alert, Condition, StaticChartRowerForm
+ Alert, Condition, StaticChartRowerForm,
+ FollowerForm,
)
from rowers.models import (
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet,