diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 6b5bae94..ee9df88f 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1225,7 +1225,10 @@ def handle_nonpainsled(f2, fileformat, summary=''): try: os.remove(f_to_be_deleted) except: - os.remove(f_to_be_deleted + '.gz') + try: + os.remove(f_to_be_deleted + '.gz') + except: + pass return (f2, summary, oarlength, inboard, fileformat) diff --git a/rowers/management/commands/processemail.py b/rowers/management/commands/processemail.py index 23b640b7..95bc028c 100644 --- a/rowers/management/commands/processemail.py +++ b/rowers/management/commands/processemail.py @@ -13,6 +13,7 @@ from django_mailbox.models import Message, MessageAttachment,Mailbox from django.core.urlresolvers import reverse from django.conf import settings +from django.utils import timezone from rowers.models import Workout, Rower from rowingdata import rower as rrower @@ -23,6 +24,8 @@ from rowers.mailprocessing import make_new_workout_from_email, send_confirm import rowers.polarstuff as polarstuff import rowers.c2stuff as c2stuff import rowers.stravastuff as stravastuff +from rowers.models import User,VirtualRace,Workout +from rowers.plannedsessions import email_submit_race workoutmailbox = Mailbox.objects.get(name='workouts') failedmailbox = Mailbox.objects.get(name='Failed') @@ -65,17 +68,42 @@ def processattachment(rower, fileobj, title, uploadoptions,testing=False): if testing: print 'Creating workout from email' + + # set user + if rower.user.is_staff and 'username' in uploadoptions: + users = User.objects.filter(username=uploadoptions['username']) + if len(users)==1: + therower = users[0].rower + else: + return 0 + else: + therower = rower + workoutid = [ - make_new_workout_from_email(rower, filename, title,testing=testing) + make_new_workout_from_email(therower, filename, title,testing=testing) ] + + if 'raceid' in uploadoptions and workoutid[0] and rower.user.is_staff: + if testing and workoutid[0]: + w = Workout.objects.get(id = workoutid[0]) + w.startdatetime = timezone.now() + w.date = timezone.now().date() + w.save() + try: + race = VirtualRace.objects.get(id=uploadoptions['raceid']) + if race.manager == rower.user: + result = email_submit_race(therower,race,workoutid[0]) + except VirtualRace.DoesNotExist: + pass + if testing: print 'Workout id = {workoutid}'.format(workoutid=workoutid) if workoutid[0]: link = settings.SITE_URL+reverse( - rower.defaultlandingpage, + therower.defaultlandingpage, kwargs = { 'id':workoutid[0], } @@ -99,9 +127,9 @@ def processattachment(rower, fileobj, title, uploadoptions,testing=False): ) try: if workoutid and not testing: - if rower.getemailnotifications and not rower.emailbounced: + if therower.getemailnotifications and not therower.emailbounced: email_sent = send_confirm( - rower.user, title, link, + therower.user, title, link, uploadoptions ) time.sleep(10) @@ -109,9 +137,9 @@ def processattachment(rower, fileobj, title, uploadoptions,testing=False): try: time.sleep(10) if workoutid: - if rower.getemailnotifications and not rower.emailbounced: + if therower.getemailnotifications and not therower.emailbounced: email_sent = send_confirm( - rower.user, title, link, + therower.user, title, link, uploadoptions ) except: diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index bd7f1958..1a48c7ab 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -36,6 +36,10 @@ import courses import iso8601 from iso8601 import ParseError from rowers.tasks import handle_check_race_course +from rowers.tasks import ( + handle_sendemail_raceregistration,handle_sendemail_racesubmission + ) +from rowers.utils import totaltime_sec_to_string def get_indoorraces(workout): races1 = VirtualRace.objects.filter( @@ -777,7 +781,6 @@ def race_can_submit(r,race): else: return False - print 'pop' return False def race_can_resubmit(r,race): @@ -877,6 +880,130 @@ def race_can_withdraw(r,race): return True +def email_submit_race(r,race,workoutid): + try: + w = Workout.objects.get(id=workoutid) + except Workout.DoesNotExist: + return 0 + + if race.sessionmode == 'time': + wduration = timefield_to_seconds_duration(w.duration) + delta = wduration - (60.*race.sessionvalue) + + if delta > -2 and delta < 2: + w.duration = totaltime_sec_to_string(60.*race.sessionvalue) + w.save() + + + elif race.sessionmode == 'distance': + delta = w.distance - race.sessionvalue + + if delta > -5 and delta < 5: + w.distance = race.sessionvalue + w.save() + + + if race_can_register(r,race): + teamname = '' + weightcategory = w.weightcategory + sex = r.sex + if sex == 'not specified': + sex = 'male' + + if not r.birthdate: + return 0 + + age = calculate_age(r.birthdate) + + adaptiveclass = r.adaptiveclass + boatclass = w.workouttype + + record = IndoorVirtualRaceResult( + userid = r.id, + teamname=teamname, + race=race, + username = u'{f} {l}'.format( + f = r.user.first_name, + l = r.user.last_name + ), + weightcategory=weightcategory, + adaptiveclass=adaptiveclass, + duration=dt.time(0,0), + boatclass=boatclass, + coursecompleted=False, + sex=sex, + age=age + ) + + record.save() + + result = add_rower_race(r,race) + + otherrecords = IndoorVirtualRaceResult.objects.filter( + race = race) + + + 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 + ) + + + if race_can_submit(r,race): + records = IndoorVirtualRaceResult.objects.filter( + userid = r.id, + race=race + ) + + if not records: + return 0 + + record = records[0] + + workouts = Workout.objects.filter(id=w.id) + + result,comments,errors,jobid = add_workout_indoorrace( + workouts,race,r,recordid=record.id + ) + + + if result: + otherrecords = IndoorVirtualRaceResult.objects.filter( + race = race) + + 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_racesubmission, + otheruser.user.email, othername, + registeredname, + race.name, + race.id + ) + + return 1 + else: + return 0 + else: + + return 0 + + return 0 + + def race_can_register(r,race): if race.sessiontype == 'race': recordobj = VirtualRaceResult diff --git a/rowers/templates/virtualeventranking.html b/rowers/templates/virtualeventranking.html new file mode 100644 index 00000000..dac83880 --- /dev/null +++ b/rowers/templates/virtualeventranking.html @@ -0,0 +1,503 @@ +{% extends "newbase.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Rowsandall Virtual Race{% endblock %} + +{% block scripts %} +{% include "monitorjobs.html" %} + +{% endblock %} + +{% block og_title %}{{ race.name }}{% endblock %} +{% block description %}Virtual Rowing Race {{ race.name }}{% endblock %} + +{% if racelogo %} +{% block og_image %} + + + + +{% endblock %} +{% block image_src %} + +{% endblock %} +{% endif %} + +{% block main %} + + +

{{ race.name }}

+ + +
    +
  • +
    +

    + {% if race|is_final %} +

    Final Results

    + {% else %} +

    Results

    + {% endif %} +

    + {% if results or dns %} +

    + + + + + + + + + + + + {% if race.sessiontype == 'race' %} + + {% endif %} + + + + + + + + {% for result in results %} + + + + + + + + + + {% if race.sessiontype == 'race' %} + + {% endif %} + + + + + + {% endfor %} + {% for result in dns %} + + + + + + + + + + {% if race.sessiontype == 'race' %} + + {% endif %} + + + {% endfor %} + +
     NameTeam Name    ClassBoatTimeDistanceDetails 
    {{ forloop.counter }} + + {{ result.username }}{{ result.teamname }}{{ result.age }}{{ result.sex }}{{ result.weightcategory }} + {% if result.adaptiveclass == 'None' %} +   + {% else %} + {{ result.adaptiveclass }} + {% endif %} + {{ result.boatclass }}{{ result.boattype }}{{ result.duration |durationprint:"%H:%M:%S.%f" }}{{ result.distance }} m + + Details + {% if race.manager == request.user and not race|is_final %} + + Disqualify + + {% else %} +   + {% endif %} +
     {{ result.username }}{{ result.teamname }}{{ result.age }}{{ result.sex }}{{ result.weightcategory }} + {% if result.adaptiveclass == 'None' %} +   + {% else %} + {{ result.adaptiveclass }} + {% endif %} + {{ result.boatclass }}{{ result.boattype }}DNS
    +

    +

    + Compare Results +

    + {% else %} +

    + No results yet +

    + {% endif %} +
    +
  • + {% if form %} +
  • + +

    +

    Filter Results

    +

    +

    + +

    + + {{ form.as_table }} +
    + + {% csrf_token %} + +

    +
  • + {% endif %} + {% if racelogo %} +
  • + {{ racelogo.filename }} + {% if race.manager == request.user %} + Edit Image + {% endif %} +
  • + {% endif %} + {% if race.sessiontype == 'race' %} +
  • +

    +

    Course

    +

    +
    + {{ coursediv|safe }} + + {{ coursescript|safe }} +
    +
  • + {% endif %} +
  • +
    +

    +

    Race Information

    +

    +

    + + + {% if race.sessiontype == 'race' %} + + + + {% else %} + + + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Course{{ race.course }}
    Indoor RaceTo be rowed on a Concept2 ergometer
    Time Zone{{ race.timezone }}
    + {{ race.sessionmode }} challenge + {{ race.sessionvalue }} {{ race.sessionunit }} +
    Registration closure{{ race.registration_closure }}
    Date{{ race.startdate }}
    Race Window{{ race.startdate }} {{ race.start_time }} to {{ race.enddate }} {{ race.end_time }}
    Results Submission Deadline{{ race.evaluation_closure }}
    Organizer{{ race.manager.first_name }} {{ race.manager.last_name }}
    Contact Email{{ race.contact_email }}
    Contact Phone{{ race.contact_phone }}
    Comment{{ race.comment|linebreaks }}
    +

    +
    +
  • +
  • +
    + {% if request.user.is_anonymous %} +

    + Registered users of rowsandall.com can participate in this event. Participation is free, unless specified differently in the race comment above. + {% if race.sessiontype == 'race' %} +

    Register

    + {% else %} +

    Register

    + {% endif %} +

    + {% else %} +

    + See race rules below. Participation to this race is free, + unless specified differently in the race comment above. +

    +

    + {% for button in buttons %} + {% if button == 'registerbutton' %} +

    + {% if race.sessiontype == 'race' %} +

    Register

    + {% else %} +

    Register

    + {% endif %} +

    + {% endif %} + {% if button == 'submitbutton' %} + + + + + + + + + + {% if race.sessiontype == 'indoorrace' %} + + + + + {% endif %} +
    + Submit Workout + + Submit a workout that is already on the site as your race result +
    + Upload your race result + + Upload a new workout to the site and submit it as a result. You + need a workout data file. +
    + Enter your race result manually + + If you don't have a data file, enter the results + manually. If you have a photo of the monitor with the + result, it is recommended to add this to the workout. +
    + {% endif %} + {% if button == 'resubmitbutton' %} +

    + Submit New Result +

    + {% endif %} + {% if button == 'withdrawbutton' %} +

    + Withdraw +

    + {% endif %} + {% if button == 'adddisciplinebutton' %} +

    + + Register New Boat + +

    + {% endif %} + {% if button == 'editbutton' %} +

    + {% if race.sessiontype == 'race' %} + Edit Race + + {% else %} + Edit Race + + {% endif %} +

    + {% endif %} + {% endfor %} + {% endif %} +
    +
  • +
  • +
    + {% if records %} +

    Registered Competitors

    + + + + + + {% if race.sessiontype == 'race' %} + + + {% else %} + + {% endif %} + + + + + + + {% for record in records %} + + + + {% if race.sessiontype == 'race' %} + + {% endif %} + + + + + {% if record.userid == rower.id and 'withdrawbutton' in buttons %} + + {% endif %} + + {% endfor %} + +
    NameTeam NameClassBoatClassAgeGenderWeight CategoryAdaptive
    {{ record.username }} + {{ record.teamname }}{{ record.boatclass }}{{ record.boattype }}{{ record.age }}{{ record.sex }}{{ record.weightcategory }} + {% if record.adaptiveclass == 'None' %} +   + {% else %} + {{ record.adaptiveclass }} + {% endif %} + + Withdraw +
    + {% endif %} +
    + {% for record in records %} + {% if record.userid == request.user.rower.id %} + {% if race.sessiontype == 'race' %} + {% if record.emailnotifications %} + + Unsubscribe from race notifications by email + + {% else %} + + Subscribe to race notifications by email + + {% endif %} + {% else %} + {% if record.emailnotifications %} + + Unsubscribe from race notifications by email + + {% else %} + + Subscribe to race notifications by email + + {% endif %} + {% endif %} + {% endif %} + {% endfor %} +
  • +
  • +
    +

    +

    Rules

    +

    +

    + Virtual races are intended as an informal way to add a + competitive element to training and as a quick way to set + up and manage small regattas. +

    +

    + On the water races are rowed on the course shown. + You cannot submit results rowed + on other bodies of water. +

    +

    + Indoor races are open for all, wherever you live. + However, be aware of the + time zone for the race window. +

    +

    + As a rowsandall.com user, you can + register to take part in this event. Please note the registration + deadline. You must register before this deadline. + You can always withdraw from participating before the registration + deadline or the start of the race window, whichever is earlier. +

    +

    + After the start of the race window and before the submission deadline, + you can submit results by linking the race to one of your uploaded + workouts. The workout start time must be within the race window + and your course must pass through the blue polygons on the course + map (in the right order), for your result to be valid. +

    +

    + The results table has a link to a page where details of your workout + are shown. +

    +

    + Race results are stored permanently and are not deleted when + you delete the respective workout or remove your account. + By registering, you agree with this and the race rules. +

    +

    + If you use a manually added workout for your indoor race result, + please attach a screenshot of the ergometer display for verification. +

    +

    + Virtual Racing on rowsandall.com is honors based. Please be a good + sport, submit real results rowed by you, and make sure you set the + boat type correctly. For (future functionality) age and gender + corrected times, please be sure your gender and birth date are set + correctly in your user settings. +

    +

    + Virtual races are intended as an informal way to add a + competitive element to training. Virtual races are not + refereed or staffed to provide for participants safety. + Individual participants are entirely responsible for their + safety while participating in a virtual race. +

    +

    + Until the evaluation closure time, the race organizer can + review and reject entries. If you are disqualified in this + way, you will receive an email with the reason. +

    +
    +
  • +
+ +

+

+ Share +
+

+ Tweet + {% else %} + data-text="@rowsandall #rowingdata Participate in Indoor Rowing virtual race '{{ race.name }}'">Tweet +{% endif %} +

+ + +

+ + +{% if not racelogo and race.manager == request.user %} +Add Race Logo +{% endif %} +{% endblock %} + +{% block sidebar %} +{% include 'menu_racing.html' %} +{% endblock %} diff --git a/rowers/tests/statements.py b/rowers/tests/statements.py index c3679e3b..14e80178 100644 --- a/rowers/tests/statements.py +++ b/rowers/tests/statements.py @@ -127,6 +127,22 @@ class UserFactory(factory.DjangoModelFactory): first_name = faker.name().split(' ')[0] last_name = faker.name().split(' ')[0] + +class RaceFactory(factory.DjangoModelFactory): + class Meta: + model = VirtualRace + + name = factory.LazyAttribute(lambda _: faker.word()) + registration_closure = timezone.now()+datetime.timedelta(days=1) + evaluation_closure = timezone.now()+datetime.timedelta(days=2) + startdate = timezone.now().date() + start_time = datetime.time() + enddate = (timezone.now()+datetime.timedelta(days=1)).date() + end_time = datetime.time() + preferreddate = timezone.now().date() + sessiontype = 'indoorrace' + sessionvalue = 1 + sessionmode = 'time' class WorkoutFactory(factory.DjangoModelFactory): class Meta: @@ -150,3 +166,20 @@ class SessionFactory(factory.DjangoModelFactory): name = factory.LazyAttribute(lambda _: faker.word()) comment = faker.text() +@pytest.fixture(scope="session", autouse=True) +def cleanup(request): + def remove_test_files(): + + for filename in os.listdir('media/mailbox_attachments'): + path = os.path.join('media/mailbox_attachments/',filename) + if not os.path.isdir(path): + os.remove(path) + + for filename in os.listdir('rowers/tests/testdata/temp'): + path = os.path.join('rowers/tests/testdata/temp/',filename) + if not os.path.isdir(path): + os.remove(path) + + + request.addfinalizer(remove_test_files) + diff --git a/rowers/tests/test_emails.py b/rowers/tests/test_emails.py new file mode 100644 index 00000000..bf3e986c --- /dev/null +++ b/rowers/tests/test_emails.py @@ -0,0 +1,224 @@ +#from __future__ import print_function +from statements import * + +class EmailUpload(TestCase): + def setUp(self): + redis_connection.publish('tasks','KILL') + u = User.objects.create_user('john', + 'sander@ds.ds', + 'koeinsloot') + r = Rower.objects.create(user=u,gdproptin=True, + gdproptindate=timezone.now() + ) + + self.theadmin = UserFactory(is_staff=True) + self.rtheadmin = Rower.objects.create(user=self.theadmin, + birthdate = faker.profile()['birthdate'], + gdproptin=True, + gdproptindate=timezone.now(), + rowerplan='coach') + + nu = datetime.datetime.now() + workoutsbox = Mailbox.objects.create(name='workouts') + workoutsbox.save() + failbox = Mailbox.objects.create(name='Failed') + failbox.save() + + m = Message(mailbox=workoutsbox, + from_header = u.email, + subject = "run", + body = """ +workout run + """) + m.save() + a2 = 'media/mailbox_attachments/colin3.csv' + copyfile('rowers/tests/testdata/emails/colin.csv',a2) + a = MessageAttachment(message=m,document=a2[6:]) + a.save() + + def tearDown(self): + for filename in os.listdir('media/mailbox_attachments'): + path = os.path.join('media/mailbox_attachments/',filename) + if not os.path.isdir(path): + try: + os.remove(path) + except (IOError,WindowsError): + pass + + @patch('rowers.dataprep.create_engine') + @patch('rowers.polarstuff.get_polar_notifications') + @patch('rowers.c2stuff.requests.get', side_effect=mocked_requests) + @patch('rowers.c2stuff.requests.post', side_effect=mocked_requests) + def test_emailupload( + self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post): + out = StringIO() + call_command('processemail', stdout=out,testing=True) + self.assertIn('Successfully processed email attachments',out.getvalue()) + + ws = Workout.objects.filter(name="run") + + self.assertEqual(len(ws),1) + w = ws[0] + self.assertEqual(w.workouttype,'Run') + + + +#@pytest.mark.django_db +class EmailTests(TestCase): + def setUp(self): + redis_connection.publish('tasks','KILL') + u = User.objects.create_user('john', + 'sander@ds.ds', + 'koeinsloot') + r = Rower.objects.create(user=u,gdproptin=True, + gdproptindate=timezone.now() + ) + + self.theadmin = UserFactory(is_staff=True) + self.rtheadmin = Rower.objects.create(user=self.theadmin, + birthdate = faker.profile()['birthdate'], + gdproptin=True, + gdproptindate=timezone.now(), + rowerplan='coach') + + nu = datetime.datetime.now() + workoutsbox = Mailbox.objects.create(name='workouts') + workoutsbox.save() + failbox = Mailbox.objects.create(name='Failed') + failbox.save() + + + for filename in os.listdir(u'rowers/tests/testdata/emails'): + m = Message(mailbox=workoutsbox, + from_header = u.email, + subject = filename, + body=""" +--- +workouttype: water +boattype: 4x +... + """) + m.save() + a2 = 'media/mailbox_attachments/'+filename + copyfile(u'rowers/tests/testdata/emails/'+filename,a2) + a = MessageAttachment(message=m,document=a2[6:]) + a.save() + + m = Message(mailbox=workoutsbox, + from_header = u.email, + subject = "3x(5min/2min)/r2 \r2", + body = """ +workout water + """) + m.save() + a2 = 'media/mailbox_attachments/colin2.csv' + copyfile('rowers/tests/testdata/emails/colin.csv',a2) + a = MessageAttachment(message=m,document=a2[6:]) + a.save() + + m = Message(mailbox=workoutsbox, + from_header = self.theadmin.email, + subject = "johnsworkout", + body = """ +user john +race 1 + """) + m.save() + + def tearDown(self): + for filename in os.listdir('media/mailbox_attachments'): + path = os.path.join('media/mailbox_attachments/',filename) + if not os.path.isdir(path): + try: + os.remove(path) + except (IOError,WindowsError): + pass + + @patch('rowers.dataprep.create_engine') + @patch('rowers.polarstuff.get_polar_notifications') + @patch('rowers.c2stuff.requests.get', side_effect=mocked_requests) + @patch('rowers.c2stuff.requests.post', side_effect=mocked_requests) + def test_emailprocessing( + self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post): + out = StringIO() + call_command('processemail', stdout=out,testing=True) + self.assertIn('Successfully processed email attachments',out.getvalue()) + + + +class EmailAdminUpload(TestCase): + def setUp(self): + redis_connection.publish('tasks','KILL') + u = User.objects.create_user('john', + 'sander@ds.ds', + 'koeinsloot') + r = Rower.objects.create(user=u,gdproptin=True, + gdproptindate=timezone.now(), + birthdate = faker.profile()['birthdate'] + ) + + self.theadmin = UserFactory(is_staff=True) + self.rtheadmin = Rower.objects.create(user=self.theadmin, + birthdate = faker.profile()['birthdate'], + gdproptin=True, + gdproptindate=timezone.now(), + rowerplan='coach') + + self.race = RaceFactory(manager = self.theadmin) + + nu = datetime.datetime.now() + workoutsbox = Mailbox.objects.create(name='workouts') + workoutsbox.save() + failbox = Mailbox.objects.create(name='Failed') + failbox.save() + + m = Message(mailbox=workoutsbox, + from_header = self.theadmin.email, + subject = "johnsworkout", + body = """ +user john +race 1 + """) + m.save() + a2 = 'media/mailbox_attachments/minute.csv' + copyfile('rowers/tests/testdata/minute.csv',a2) + a = MessageAttachment(message=m,document=a2[6:]) + a.save() + + def tearDown(self): + for filename in os.listdir('media/mailbox_attachments'): + path = os.path.join('media/mailbox_attachments/',filename) + if not os.path.isdir(path): + try: + os.remove(path) + except (IOError,WindowsError): + pass + + @patch('rowers.dataprep.create_engine') + @patch('rowers.polarstuff.get_polar_notifications') + @patch('rowers.c2stuff.requests.get', side_effect=mocked_requests) + @patch('rowers.c2stuff.requests.post', side_effect=mocked_requests) + def test_email_admin_upload( + self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post): + out = StringIO() + call_command('processemail', stdout=out,testing=True) + self.assertIn('Successfully processed email attachments',out.getvalue()) + + ws = Workout.objects.filter(name="johnsworkout") + if not len(ws): + for w in Workout.objects.all(): + print w + + self.assertEqual(len(ws),1) + + w = ws[0] + self.assertEqual(w.user.user.username,u'john') + + results = IndoorVirtualRaceResult.objects.filter( + race=self.race) + + self.assertEqual(len(results),1) + result = results[0] + + self.assertTrue(result.coursecompleted) + diff --git a/rowers/tests/test_zemails.py b/rowers/tests/test_zemails.py deleted file mode 100644 index 2d79f37f..00000000 --- a/rowers/tests/test_zemails.py +++ /dev/null @@ -1,164 +0,0 @@ -#from __future__ import print_function -import pytest - -pytestmark = pytest.mark.django_db - -from bs4 import BeautifulSoup -import re -from nose_parameterized import parameterized -from django.test import TestCase, Client,override_settings -from django.core.management import call_command -from django.utils.six import StringIO -from django.test.client import RequestFactory -from rowers.views import checkworkoutuser,c2_open -from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage -from rowers.forms import DocumentsForm,CNsummaryForm,RegistrationFormUniqueEmail -import rowers.plots as plots -import rowers.interactiveplots as iplots -import datetime -from rowingdata import rowingdata as rdata -from rowingdata import rower as rrower -from django.utils import timezone -from rowers.rows import handle_uploaded_file -from django.core.files.uploadedfile import SimpleUploadedFile -from time import strftime,strptime,mktime,time,daylight -import os -from rowers.tasks import handle_makeplot -from rowers.utils import serialize_list,deserialize_list -from rowers.utils import NoTokenError -from shutil import copyfile -from nose.tools import assert_true -from mock import Mock, patch -from minimocktest import MockTestCase -import pandas as pd -import rowers.c2stuff as c2stuff - -import json -import numpy as np - -from rowers import urls -from rowers.views import error500_view,error404_view,error400_view,error403_view - -from rowers.dataprep import delete_strokedata - -from redis import StrictRedis -redis_connection = StrictRedis() - -from rowers.tests.test_imports import mocked_requests - - -from django_mailbox.models import Mailbox,MessageAttachment,Message - -from rowers.tests.mocks import mocked_sqlalchemy - - -@pytest.mark.django_db -class UploadTests(TestCase): - def setUp(self): - redis_connection.publish('tasks','KILL') - u = User.objects.create_user('john', - 'sander@ds.ds', - 'koeinsloot') - r = Rower.objects.create(user=u,gdproptin=True, - gdproptindate=timezone.now(), - getemailnotifications = False, - ) - - nu = datetime.datetime.now() - workoutsbox = Mailbox.objects.create(name='workouts') - workoutsbox.save() - failbox = Mailbox.objects.create(name='Failed') - failbox.save() - m = Message(mailbox=workoutsbox, - from_header = u.email, - subject = "3x(5min/2min)/r2 \r2", - body = """ -workout run - """) - m.save() - a2 = 'media/mailbox_attachments/colin2.csv' - copyfile('rowers/tests/testdata/emails/colin.csv',a2) - a = MessageAttachment(message=m,document=a2[6:]) - a.save() - - def tearDown(self): - for filename in os.listdir('media/mailbox_attachments'): - path = os.path.join('media/mailbox_attachments/',filename) - if not os.path.isdir(path): - try: - os.remove(path) - except (IOError,WindowsError): - pass - - @patch('requests.get', side_effect=mocked_requests) - def test_email_workouttype(self, mock_get): - out = StringIO() - call_command('processemail', stdout=out, testing=True) - w = Workout.objects.get(id=1) - self.assertEqual(w.workouttype,'Run') - -#@pytest.mark.django_db -class EmailTests(TestCase): - def setUp(self): - redis_connection.publish('tasks','KILL') - u = User.objects.create_user('john', - 'sander@ds.ds', - 'koeinsloot') - r = Rower.objects.create(user=u,gdproptin=True, - gdproptindate=timezone.now() - ) - - nu = datetime.datetime.now() - workoutsbox = Mailbox.objects.create(name='workouts') - workoutsbox.save() - failbox = Mailbox.objects.create(name='Failed') - failbox.save() - - for filename in os.listdir(u'rowers/tests/testdata/emails'): - m = Message(mailbox=workoutsbox, - from_header = u.email, - subject = filename, - body=""" ---- -workouttype: water -boattype: 4x -... - """) - m.save() - a2 = 'media/mailbox_attachments/'+filename - copyfile(u'rowers/tests/testdata/emails/'+filename,a2) - a = MessageAttachment(message=m,document=a2[6:]) - a.save() - - m = Message(mailbox=workoutsbox, - from_header = u.email, - subject = "3x(5min/2min)/r2 \r2", - body = """ -workout water - """) - m.save() - a2 = 'media/mailbox_attachments/colin2.csv' - copyfile('rowers/tests/testdata/emails/colin.csv',a2) - a = MessageAttachment(message=m,document=a2[6:]) - a.save() - - - def tearDown(self): - for filename in os.listdir('media/mailbox_attachments'): - path = os.path.join('media/mailbox_attachments/',filename) - if not os.path.isdir(path): - try: - os.remove(path) - except (IOError,WindowsError): - pass - - @patch('rowers.dataprep.create_engine') - @patch('rowers.polarstuff.get_polar_notifications') - @patch('rowers.c2stuff.requests.get', side_effect=mocked_requests) - @patch('rowers.c2stuff.requests.post', side_effect=mocked_requests) - def test_emailprocessing( - self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post): - out = StringIO() - call_command('processemail', stdout=out,testing=True) - self.assertIn('Successfully processed email attachments',out.getvalue()) - diff --git a/rowers/tests/testdata/minute.csv b/rowers/tests/testdata/minute.csv new file mode 100644 index 00000000..9bbf31bb --- /dev/null +++ b/rowers/tests/testdata/minute.csv @@ -0,0 +1,18 @@ +index,TimeStamp (sec), activityIdx, lapIdx, pointIdx, ElapsedTime (sec), Horizontal (meters), Stroke500mPace (sec/500m), Cadence (strokes/min), HRCur (bpm), Power (watts), Calories (kCal), Speed (m/sec), StrokeCount, StrokeDistance (meters), DriveLength (meters), DriveTime (ms), StrokeRecoveryTime (ms), WorkPerStroke (joules), AverageDriveForce (lbs), PeakDriveForce (lbs), DragFactor, ElapsedTimeAtDrive (sec), HorizontalAtDrive (meters), WorkoutType, IntervalType, WorkoutState, RowingState, WorkoutDurationType, WorkoutIntervalCount, Cadence (stokes/min), AverageBoatSpeed (m/s), AverageDriveForce (N), PeakDriveForce (N), Stroke Number,cum_dist,originalvelo +0,1548096824.0,0,0,0,4.18,20.3,93.3506356168139,48,86,436.5048883104004,1,5.3820000000000014,4,6.79,1.36,500,560,0,142.4,222.0,115,0.0,0.0,5,0,1,1,0,0,48.0,5.353892279687333,633.4265280000002,987.50484,2,20.3,5.353892279687333 +1,1548096826.7910905,0,0,1,6.94,35.9,89.62484388832563,45,87,496.4816892895996,2,5.6179999999999986,6,8.15,1.42,490,730,0,142.3,238.8,113,4.18,20.3,5,0,1,1,0,0,45.0,5.587840858292355,632.9817059999998,1062.2349359999996,4,35.9,5.587840858292355 +2,1548096837.108201,0,0,2,17.27,94.5,88.81577825856567,41,96,504.2099300644,8,5.647,13,8.19,1.36,480,770,0,144.0,242.9,112,6.94,35.9,5,0,1,1,0,0,41.0,5.616084465910367,640.54368,1080.4726380000004,11,94.5,5.616084465910367 +3,1548096839.086601,0,0,3,19.25,105.7,88.96490450858349,41,99,504.2099300644,8,5.647,14,8.19,1.39,480,750,0,142.0,244.6,112,17.27,94.5,5,0,1,1,0,0,41.0,5.616084465910367,631.64724,1088.034612,12,105.7,5.616084465910367 +4,1548096842.8994308,0,0,4,23.07,127.2,89.27586762584984,41,104,501.8029914016,11,5.638,17,8.12,1.36,470,760,0,143.6,251.4,112,19.25,105.7,5,0,1,1,0,0,41.0,5.607267018055401,638.7643919999998,1118.282508,15,127.2,5.607267018055401 +5,1548096847.161281,0,0,5,27.31,151.1,89.89298681607487,41,110,489.62041712640007,14,5.5920000000000005,20,8.11,1.36,490,760,0,133.6,231.0,111,23.07,127.2,5,0,1,1,0,0,41.0,5.561116672227786,594.282192,1027.53882,18,151.1,5.561116672227786 +6,1548096850.1555116,0,0,6,30.31,167.8,90.29743877919012,41,112,480.4843223404,15,5.557,22,8.47,1.39,490,820,0,143.9,234.4,111,27.31,151.1,5,0,1,1,0,0,41.0,5.526693931690064,640.0988580000002,1042.662768,20,167.8,5.526693931690064 +7,1548096853.1276512,0,0,7,33.28,184.3,90.39988760767656,40,113,484.1249963508004,17,5.5710000000000015,24,8.24,1.36,490,800,0,138.4,254.1,111,30.31,167.8,5,0,1,1,0,0,40.0,5.540780141843972,615.633648,1130.292702,22,184.3,5.540780141843972 +8,1548096856.0643613,0,0,8,36.21,200.5,90.61884542605642,41,115,477.8950465044004,18,5.5470000000000015,26,8.1,1.36,480,770,0,135.5,227.6,111,33.28,184.3,5,0,1,1,0,0,41.0,5.516936996579499,602.7338100000002,1012.4148720000001,24,200.5,5.516936996579499 +9,1548096859.0091813,0,0,9,39.17,216.8,91.12253138918642,41,116,468.9058576384001,20,5.5120000000000005,28,8.1,1.36,500,770,0,132.7,232.4,111,36.21,200.5,5,0,1,1,0,0,41.0,5.483057352779911,590.2787940000002,1033.766328,26,216.8,5.483057352779911 +10,1548096862.008181,0,0,10,42.16,233.2,91.7159092344983,40,117,462.8074479616,21,5.488,30,8.24,1.33,470,810,0,130.6,218.9,111,39.17,216.8,5,0,1,1,0,0,40.0,5.459111256687411,580.937532,973.715358,28,233.2,5.459111256687411 +11,1548096866.4157307,0,0,11,46.57,257.2,92.13100908526968,41,118,452.5120589444,24,5.447,33,7.89,1.33,470,760,0,133.0,227.5,111,42.16,233.2,5,0,1,1,0,0,41.0,5.417705060136527,591.61326,1011.97005,31,257.2,5.417705060136527 +12,1548096869.206501,0,0,12,49.36,272.4,92.30771272248859,43,118,452.7613310976001,25,5.448,35,7.6,1.3,460,710,0,131.0,208.0,111,46.57,257.2,5,0,1,1,0,0,43.0,5.418879375745096,582.71682,925.2297599999999,33,272.4,5.418879375745096 +13,1548096872.1160307,0,0,13,52.27,288.2,92.39395707918017,42,119,451.2670704864,26,5.442,37,7.96,1.33,480,770,0,132.8,222.3,111,49.36,272.4,5,0,1,1,0,0,42.0,5.413598960589,590.7236160000001,988.839306,35,288.2,5.413598960589 +14,1548096875.085701,0,0,14,55.24,304.2,92.99397114111812,41,119,442.6160316004,28,5.407,39,8.02,1.3,480,790,0,128.7,230.0,111,52.27,288.2,5,0,1,1,0,0,41.0,5.378657487091222,572.485914,1023.0906,37,304.2,5.378657487091222 +15,1548096878.0252109,0,0,15,58.2,319.9,94.06681282362852,41,119,426.8444927264001,29,5.3420000000000005,41,7.95,1.27,490,820,0,123.4,213.1,111,55.24,304.2,5,0,1,1,0,0,41.0,5.314061005420342,548.910348,947.9156820000001,39,319.9,5.314061005420342 +16,1548096880.0612912,0,0,16,60.0,329.3,95.37595742617708,40,120,409.81691239999986,30,5.27,42,8.03,1.27,490,840,0,110.4,193.6,111,58.2,319.9,5,0,10,0,0,0,40.0,5.242738806752646,491.08348800000016,861.175392,40,329.3,5.242738806752646 diff --git a/rowers/tests/testdata/testdata.csv.gz b/rowers/tests/testdata/testdata.csv.gz index a67f9afc..8480fc16 100644 Binary files a/rowers/tests/testdata/testdata.csv.gz and b/rowers/tests/testdata/testdata.csv.gz differ diff --git a/rowers/tests/testdata/testdata.tcx b/rowers/tests/testdata/testdata.tcx index 57cf726e..5842c118 100644 --- a/rowers/tests/testdata/testdata.tcx +++ b/rowers/tests/testdata/testdata.tcx @@ -2502,7 +2502,7 @@ - <Element 'Notes' at 0x13f67128> + <Element 'Notes' at 0x18ae8668> diff --git a/rowers/uploads.py b/rowers/uploads.py index b040c56a..9094d200 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -82,6 +82,27 @@ def matchchart(line): if tester3.match(line.lower()): return 'pieplot' +def matchuser(line): + testert = '^(user)' + tester = re.compile(testert) + if tester.match(line.lower()): + words = line.split() + return words[1] + + return None + +def matchrace(line): + testert = '^(race)' + tester = re.compile(testert) + if tester.match(line.lower()): + words = line.split() + try: + return int(words[1]) + except: + return None + + return None + def matchsync(line): results = [] tester = '((sync)|(synchronization)|(export))' @@ -176,6 +197,22 @@ def getplotoptions_body2(uploadoptions,body): return uploadoptions +def getuseroptions_body2(uploadoptions,body): + for line in body.splitlines(): + user = matchuser(line) + if user: + uploadoptions['username'] = user + + return uploadoptions + +def getraceoptions_body2(uploadoptions,body): + for line in body.splitlines(): + raceid = matchrace(line) + if raceid: + uploadoptions['raceid'] = raceid + + return uploadoptions + def getsyncoptions_body2(uploadoptions,body): result = [] for line in body.splitlines(): @@ -261,6 +298,20 @@ def getboattype(uploadoptions,value,key): return uploadoptions +def getuser(uploadoptions,value,key): + uploadoptions['username'] = value + + return uploadoptions + +def getrace(uploadoptions,value,key): + try: + raceid = int(value) + uploadoptions['raceid'] = raceid + except: + pass + + return uploadoptions + def getsource(uploadoptions,value,key): workoutsource = 'unknown' for type,verb in workoutsources: @@ -306,6 +357,10 @@ def upload_options(body): uploadoptions = getboattype(uploadoptions,value,'boattype') if 'source' in lowkey: uploadoptions = getsource(uploadoptions,value,'workoutsource') + if 'username' in lowkey: + uploadoptions = getuser(uploadoptions,value,'username') + if 'raceid' in lowkey: + uploadoptions = getraceid(uploadoptions,value,'raceid') except AttributeError: #pass raise yaml.YAMLError @@ -317,6 +372,8 @@ def upload_options(body): uploadoptions = gettypeoptions_body2(uploadoptions,body) uploadoptions = getstravaid(uploadoptions,body) uploadoptions = getworkoutsources(uploadoptions,body) + uploadoptions = getuseroptions_body2(uploadoptions,body) + uploadoptions = getraceoptions_body2(uploadoptions,body) except IOError: pm = exc.problem_mark strpm = str(pm) diff --git a/rowers/urls.py b/rowers/urls.py index ef652679..3972e842 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -149,6 +149,7 @@ urlpatterns = [ url(r'^indoorraceregistration/togglenotification/(?P\d+)/$', views.indoorvirtualevent_toggle_email_view), url(r'^virtualevent/(?P\d+)/$',views.virtualevent_view), + url(r'^virtualevent/(?P\d+)/ranking$',views.virtualevent_ranking_view), url(r'^virtualevent/(?P\d+)/edit/$',views.virtualevent_edit_view), url(r'^virtualevent/(?P\d+)/editindoor/$',views.indoorvirtualevent_edit_view), url(r'^virtualevent/(?P\d+)/register/$',views.virtualevent_register_view), diff --git a/rowers/views.py b/rowers/views.py index 9ca1329c..d92f71dd 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -16128,6 +16128,186 @@ def virtualevent_view(request,id=0): 'active':'nav-racing', }) +def virtualevent_ranking_view(request,id=0): + + results = [] + + if not request.user.is_anonymous(): + r = getrower(request.user) + else: + r = None + + + try: + race = VirtualRace.objects.get(id=id) + except VirtualRace.DoesNotExist: + raise Http404("Virtual Race does not exist") + + if race.sessiontype == 'race': + script,div = course_map(race.course) + resultobj = VirtualRaceResult + else: + script = '' + div = '' + resultobj = IndoorVirtualRaceResult + + records = resultobj.objects.filter(race=race) + + + buttons = [] + + # to-do - add DNS + dns = [] + if timezone.now() > race.evaluation_closure: + dns = resultobj.objects.filter( + race=race, + workoutid__isnull=True, + ) + + + if not request.user.is_anonymous(): + if race_can_register(r,race): + buttons += ['registerbutton'] + + if race_can_adddiscipline(r,race): + buttons += ['adddisciplinebutton'] + + if race_can_submit(r,race): + buttons += ['submitbutton'] + + if race_can_resubmit(r,race): + buttons += ['resubmitbutton'] + + if race_can_withdraw(r,race): + buttons += ['withdrawbutton'] + + if race_can_edit(r,race): + buttons += ['editbutton'] + + if request.method == 'POST': + form = RaceResultFilterForm(request.POST,records=records) + if form.is_valid(): + cd = form.cleaned_data + try: + sex = cd['sex'] + except KeyError: + sex = ['female','male','mixed'] + + try: + boattype = cd['boattype'] + except KeyError: + boattype = mytypes.waterboattype + + try: + boatclass = cd['boatclass'] + except KeyError: + if race.sessiontype == 'race': + boatclass = [t for t in mytypes.otwtypes] + else: + boatclass = [t for t in mytypes.otetypes] + + age_min = cd['age_min'] + age_max = cd['age_max'] + + try: + weightcategory = cd['weightcategory'] + except KeyError: + weightcategory = ['hwt','lwt'] + + try: + adaptiveclass = cd['adaptiveclass'] + except KeyError: + adaptiveclass = ['None','PR1','PR2','PR3','FES'] + + if race.sessiontype == 'race': + results = resultobj.objects.filter( + race=race, + workoutid__isnull=False, + boatclass__in=boatclass, + boattype__in=boattype, + sex__in=sex, + weightcategory__in=weightcategory, + adaptiveclass__in=adaptiveclass, + age__gte=age_min, + age__lte=age_max + ).order_by("duration") + else: + results = resultobj.objects.filter( + race=race, + workoutid__isnull=False, + boatclass__in=boatclass, + sex__in=sex, + weightcategory__in=weightcategory, + adaptiveclass__in=adaptiveclass, + age__gte=age_min, + age__lte=age_max + ).order_by("duration","-distance") + + + # to-do - add DNS + dns = [] + if timezone.now() > race.evaluation_closure: + dns = resultobj.objects.filter( + race=race, + workoutid__isnull=True, + boatclass__in=boatclass, + sex__in=sex, + weightcategory__in=weightcategory, + adaptiveclass__in=adaptiveclass, + age__gte=age_min, + age__lte=age_max + ) + else: + results = resultobj.objects.filter( + race=race, + workoutid__isnull=False, + coursecompleted=True, + ).order_by("duration","-distance") + + if results: + form = RaceResultFilterForm(records=records) + else: + form = None + + + + breadcrumbs = [ + { + 'url':reverse(virtualevents_view), + 'name': 'Racing' + }, + { + 'url':reverse(virtualevent_view, + kwargs={'id':race.id} + ), + 'name': race.name + } + ] + + racelogos = race.logos.all() + + if racelogos: + racelogo = racelogos[0] + else: + racelogo = None + + return render(request,'virtualeventranking.html', + { + 'coursescript':script, + 'coursediv':div, + 'breadcrumbs':breadcrumbs, + 'race':race, + 'rower':r, + 'results':results, + 'buttons':buttons, + 'dns':dns, + 'records':records, + 'racelogo':racelogo, + 'form':form, + 'active':'nav-racing', + }) + + @login_required() def virtualevent_withdraw_view(request,id=0,recordid=None): r = getrower(request.user)