Private
Public Access
1
0

Merge branch 'release/v5.02'

This commit is contained in:
Sander Roosendaal
2017-11-08 13:53:09 +01:00
8 changed files with 229 additions and 29 deletions

View File

@@ -8,6 +8,8 @@ from rowingdata import rowingdata as rrdata
from rowingdata import rower as rrower from rowingdata import rower as rrower
from shutil import copyfile
from rowingdata import get_file_type, get_empower_rigging from rowingdata import get_file_type, get_empower_rigging
from rowers.tasks import handle_sendemail_unrecognized from rowers.tasks import handle_sendemail_unrecognized
@@ -17,6 +19,7 @@ from pandas import DataFrame, Series
from django.utils import timezone from django.utils import timezone
from django.utils.timezone import get_current_timezone from django.utils.timezone import get_current_timezone
from django_mailbox.models import Message,Mailbox,MessageAttachment
from time import strftime from time import strftime
import arrow import arrow
@@ -26,7 +29,7 @@ from rowingdata import (
TCXParser, RowProParser, ErgDataParser, TCXParser, RowProParser, ErgDataParser,
CoxMateParser, CoxMateParser,
BoatCoachParser, RowPerfectParser, BoatCoachAdvancedParser, BoatCoachParser, RowPerfectParser, BoatCoachAdvancedParser,
MysteryParser, BoatCoachOTWParser, MysteryParser, BoatCoachOTWParser,QuiskeParser,
painsledDesktopParser, speedcoachParser, ErgStickParser, painsledDesktopParser, speedcoachParser, ErgStickParser,
SpeedCoach2Parser, FITParser, fitsummarydata, SpeedCoach2Parser, FITParser, fitsummarydata,
make_cumvalues,cumcpdata, make_cumvalues,cumcpdata,
@@ -863,6 +866,11 @@ def handle_nonpainsled(f2, fileformat, summary=''):
row = MysteryParser(f2) row = MysteryParser(f2)
hasrecognized = True hasrecognized = True
# handle Quiske
if (fileformat == 'quiske'):
row = QuiskeParser(f2)
hasrecognized = True
# handle RowPerfect # handle RowPerfect
if (fileformat == 'rowperfect3'): if (fileformat == 'rowperfect3'):
row = RowPerfectParser(f2) row = RowPerfectParser(f2)
@@ -975,14 +983,24 @@ def new_workout_from_file(r, f2,
inboard = 0.88 inboard = 0.88
if len(fileformat) == 3 and fileformat[0] == 'zip': if len(fileformat) == 3 and fileformat[0] == 'zip':
f_to_be_deleted = f2 f_to_be_deleted = f2
title = os.path.basename(f2) workoutsbox = Mailbox.objects.filter(name='workouts')[0]
res = myqueue( msg = Message(mailbox=workoutsbox,
queuelow, from_header=r.user.email,
handle_zip_file, subject = title)
r.user.email, msg.save()
title, f3 = 'media/mailbox_attachments/'+f2[6:]
f2 copyfile(f2,f3)
) f3 = f3[6:]
a = MessageAttachment(message=msg,document=f3)
a.save()
# res = myqueue(
# queuelow,
# handle_zip_file,
# r.user.email,
# title,
# f2
# )
return -1, message, f2 return -1, message, f2

View File

@@ -93,6 +93,15 @@ def processattachment(rower, fileobj, title, uploadoptions,testing=False):
return workoutid return workoutid
class Command(BaseCommand): class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
'--testing',
action='store_true',
dest='testing',
default=False,
help="Run in testing mode, don't send emails",
)
"""Run the Email processing command """ """Run the Email processing command """
def handle(self, *args, **options): def handle(self, *args, **options):
attachments = MessageAttachment.objects.all() attachments = MessageAttachment.objects.all()
@@ -105,7 +114,11 @@ class Command(BaseCommand):
extension = attachment.document.name[-3:].lower() extension = attachment.document.name[-3:].lower()
try: try:
message = Message.objects.get(id=attachment.message_id) message = Message.objects.get(id=attachment.message_id)
body = "\n".join(message.text.splitlines()) if message.text:
body = "\n".join(message.text.splitlines())
else:
body = message.body
uploadoptions = uploads.upload_options(body) uploadoptions = uploads.upload_options(body)
from_address = message.from_address[0].lower() from_address = message.from_address[0].lower()
name = message.subject name = message.subject
@@ -119,9 +132,13 @@ class Command(BaseCommand):
for rower in rowers: for rower in rowers:
if extension == 'zip': if extension == 'zip':
zip_file = zipfile.ZipFile(attachment.document) zip_file = zipfile.ZipFile(attachment.document)
for filename in zip_file.namelist(): for id,filename in enumerate(zip_file.namelist()):
datafile = zip_file.extract(filename, path='media/') datafile = zip_file.extract(filename, path='media/')
title = os.path.basename(datafile) if id>0:
title = name+' ('+str(id+1)+')'
else:
title = name
workoutid = processattachment( workoutid = processattachment(
rower, datafile, title, uploadoptions, rower, datafile, title, uploadoptions,
testing=testing testing=testing

View File

@@ -2,6 +2,8 @@ from __future__ import unicode_literals
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django import forms from django import forms
from django.forms import ModelForm from django.forms import ModelForm
from django.dispatch import receiver from django.dispatch import receiver
@@ -941,15 +943,50 @@ class AccountRowerForm(ModelForm):
'defaulttimezone','showfavoritechartnotes', 'defaulttimezone','showfavoritechartnotes',
'defaultlandingpage'] 'defaultlandingpage']
def clean_email(self):
email = self.cleaned_data.get('email')
try:
validate_email(email)
except ValidationError:
raise forms.ValidationError(
'Please enter a valid email address')
try:
match = User.objects.get(email__iexact=email)
if self.instance.user == match:
return email
except User.DoesNotExist:
return email
raise forms.ValidationError('This email address is not allowed')
class UserForm(ModelForm): class UserForm(ModelForm):
class Meta: class Meta:
model = User model = User
fields = ['first_name','last_name','email'] fields = ['first_name','last_name','email']
def clean_email(self):
email = self.cleaned_data.get('email')
def clean(self): try:
cleaned_data = super(UserForm, self).clean() validate_email(email)
except ValidationError:
raise forms.ValidationError(
'Please enter a valid email address')
try:
match = User.objects.get(email__iexact=email)
if self.instance == match:
return email
except User.DoesNotExist:
return email
raise forms.ValidationError('This email address is not allowed')
# Form to set rower's Heart Rate zones, including test routines # Form to set rower's Heart Rate zones, including test routines
# to enable consistency # to enable consistency
class RowerForm(ModelForm): class RowerForm(ModelForm):

View File

@@ -15,7 +15,10 @@
</script> </script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="id_dropregion" class="grid_12 alpha watermark invisible">
<p>Drag and drop files here </p>
</div>
<div id="id_drop-files" class="grid_12 alpha drop-files"> <div id="id_drop-files" class="grid_12 alpha drop-files">
<form id="file_form" enctype="multipart/form-data" action="{{ formloc }}" method="post"> <form id="file_form" enctype="multipart/form-data" action="{{ formloc }}" method="post">
<div id="left" class="grid_6 alpha"> <div id="left" class="grid_6 alpha">
@@ -25,7 +28,7 @@
Upload?</a></p> Upload?</a></p>
{% endif %} {% endif %}
{% if form.errors %} {% if form.errors %}
<p style="color: red;"> <p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below. Please correct the error{{ form.errors|pluralize }} below.
</p> </p>
{% endif %} {% endif %}
@@ -53,7 +56,7 @@
If you check "make private", this workout will not be visible to your followers and will not show up in your teams' workouts list. With the Landing Page option, you can select to which (workout related) page you will be If you check "make private", this workout will not be visible to your followers and will not show up in your teams' workouts list. With the Landing Page option, you can select to which (workout related) page you will be
taken after a successfull upload. taken after a successfull upload.
</p> </p>
<p>Select Files with the File button or drag them on the marked area</p> <p><b>Select Files with the File button or drag them on the marked area</b></p>
</div> </div>
@@ -79,6 +82,10 @@
formdatasetok = false; formdatasetok = false;
} }
if (!formdatasetok) {
$("#id_dropregion").remove();
}
if (formdatasetok) { if (formdatasetok) {
$(document).ready(function() { $(document).ready(function() {
@@ -99,7 +106,14 @@
console.log("Loading dropper"); console.log("Loading dropper");
jQuery.event.props.push('dataTransfer'); jQuery.event.props.push('dataTransfer');
$(window).on('dragenter', function() {
$("#id_drop-files").css("background-color","#E9E9E4");
$("#id_dropregion").addClass("watermark").removeClass("invisible");})
$(window).on('dragleave', function() {
$("#id_drop-files").css("background-color","#FFFFFF");
$("#id_dropregion").removeClass("watermark").addClass("invisible");})
var frm = $("#file_form"); var frm = $("#file_form");
if( window.FormData === undefined ) { if( window.FormData === undefined ) {
@@ -141,7 +155,7 @@
$('#id_offline').prop('checked','True'); $('#id_offline').prop('checked','True');
data.set($('#id_offline').attr('name'),$('#id_offline').prop('checked')); data.set($('#id_offline').attr('name'),$('#id_offline').prop('checked'));
console.log("Set offline to True"); console.log("Set offline to True");
$('#extra_message').text('Because of the large size, we recommend to use background processing. You will receive email when it is done. The extra actions will not be performed.'); $('#extra_message').text('Because of the large size, we recommend to use background processing. You will receive email when it is done.');
$('#extra_message').addClass('message'); $('#extra_message').addClass('message');
} }
} }
@@ -240,7 +254,7 @@
$('#id_offline').prop('checked','True'); $('#id_offline').prop('checked','True');
data.set($('#id_offline').attr('name'),$('#id_offline').prop('checked')); data.set($('#id_offline').attr('name'),$('#id_offline').prop('checked'));
console.log("Set offline to True"); console.log("Set offline to True");
$('#extra_message').text('Because of the large size, we recommend to use background processing. You will receive email when it is done. The extra actions will not be performed.'); $('#extra_message').text('Because of the large size, we recommend to use background processing. You will receive email when it is done.');
$('#extra_message').addClass('message'); $('#extra_message').addClass('message');
} }
data.set("file",f); data.set("file",f);

BIN
rowers/testdata/emails/testdata.ZIP vendored Normal file

Binary file not shown.

View File

@@ -19,6 +19,9 @@ import yamllint
from subprocess import call from subprocess import call
import re import re
from verbalexpressions import VerEx
import re
import django_rq import django_rq
queue = django_rq.get_queue('default') queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low') queuelow = django_rq.get_queue('low')
@@ -45,6 +48,85 @@ def cleanbody(body):
return body return body
# currently only matches one chart
def matchchart(line):
results = []
tester = VerEx().start_of_line().find('chart').OR().find('plot')
tester2 = VerEx().start_of_line().find('chart').OR().find('plot').anything().find('distance')
tester3 = VerEx().start_of_line().find('chart').OR().find('plot').anything().find('time')
tester4 = VerEx().start_of_line().find('chart').OR().find('plot').anything().find('pie')
if tester.match(line.lower()):
if tester2.match(line.lower()):
return 'distanceplot'
if tester3.match(line.lower()):
return 'timeplot'
if tester3.match(line.lower()):
return 'pieplot'
def matchsync(line):
results = []
tester = '((sync)|(synchronization)|(export))'
tester2 = tester+'(.*)((c2)|(concept2)|(logbook))'
tester3 = tester+'(.*)((tp)|(trainingpeaks))'
tester4 = tester+'(.*)(strava)'
tester5 = tester+'(.*)((st)|(sporttracks))'
tester6 = tester+'(.*)((rk)|(runkeeper))'
tester7 = tester+'(.*)((mapmyfitness)|(underarmour)|(ua))'
tester = re.compile(tester)
if tester.match(line.lower()):
testers = [
('upload_to_C2',re.compile(tester2)),
('upload_totp',re.compile(tester3)),
('upload_to_Strava',re.compile(tester4)),
('upload_to_SportTracks',re.compile(tester5)),
('upload_to_RunKeeper',re.compile(tester6)),
('upload_to_MapMyFitness',re.compile(tester7)),
]
for t in testers:
if t[1].match(line.lower()):
results.append(t[0])
return results
def getprivateoptions_body2(uploadoptions,body):
tester = re.compile('^(priva)')
for line in body.splitlines():
if tester.match(line.lower()):
v = True
negs = ['false','False','None','no']
for neg in negs:
tstr = re.compile('^(.*)'+neg)
if tstr.match(line.lower()):
v = False
uploadoptions['makeprivate'] = v
return uploadoptions
def getplotoptions_body2(uploadoptions,body):
for line in body.splitlines():
chart = matchchart(line)
if chart:
uploadoptions['make_plot'] = True
uploadoptions['plottype'] = chart
return uploadoptions
def getsyncoptions_body2(uploadoptions,body):
result = []
for line in body.splitlines():
result = result+matchsync(line)
result = list(set(result))
for r in result:
uploadoptions[r] = True
return uploadoptions
def getsyncoptions(uploadoptions,values): def getsyncoptions(uploadoptions,values):
try: try:
value = values.lower() value = values.lower()
@@ -109,6 +191,8 @@ def upload_options(body):
body = cleanbody(body) body = cleanbody(body)
try: try:
yml = (yaml.load(body)) yml = (yaml.load(body))
if yml and 'fromuploadform' in yml:
return yml
try: try:
for key, value in yml.iteritems(): for key, value in yml.iteritems():
lowkey = key.lower() lowkey = key.lower()
@@ -121,13 +205,18 @@ def upload_options(body):
except AttributeError: except AttributeError:
pass pass
except yaml.YAMLError as exc: except yaml.YAMLError as exc:
pm = exc.problem_mark try:
strpm = str(pm) uploadoptions = getplotoptions_body2(uploadoptions,body)
pbm = "Your email has an issue on line {} at position {}. The error is: ".format( uploadoptions = getsyncoptions_body2(uploadoptions,body)
pm.line+1, uploadoptions = getprivateoptions_body2(uploadoptions,body)
pm.column+1, except IOError:
pm = exc.problem_mark
strpm = str(pm)
pbm = "Your email has an issue on line {} at position {}. The error is: ".format(
pm.line+1,
pm.column+1,
)+strpm )+strpm
return {'error':pbm} return {'error':pbm}
if uploadoptions == {}: if uploadoptions == {}:
uploadoptions['message'] = 'No parsing issue. No valid commands detected' uploadoptions['message'] = 'No parsing issue. No valid commands detected'

View File

@@ -8,6 +8,7 @@ import pytz
import operator import operator
import warnings import warnings
import urllib import urllib
import yaml
from PIL import Image from PIL import Image
from numbers import Number from numbers import Number
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
@@ -16,6 +17,7 @@ from django import template
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render from django.shortcuts import render
from django.http import ( from django.http import (
HttpResponse, HttpResponseRedirect, HttpResponse, HttpResponseRedirect,
@@ -8551,9 +8553,14 @@ def workout_upload_view(request,
) )
else: else:
workoutsbox = Mailbox.objects.filter(name='workouts')[0] workoutsbox = Mailbox.objects.filter(name='workouts')[0]
uploadoptions['fromuploadform'] = True
bodyyaml = yaml.safe_dump(
uploadoptions,
default_flow_style=False
)
msg = Message(mailbox=workoutsbox, msg = Message(mailbox=workoutsbox,
from_header=r.user.email, from_header=r.user.email,
subject = t) subject = t,body=bodyyaml)
msg.save() msg.save()
f3 = 'media/mailbox_attachments/'+f2[6:] f3 = 'media/mailbox_attachments/'+f2[6:]
copyfile(f2,f3) copyfile(f2,f3)
@@ -9697,7 +9704,7 @@ def rower_edit_view(request,message=""):
}) })
elif request.method == 'POST' and "weightcategory" in request.POST: elif request.method == 'POST' and "weightcategory" in request.POST:
accountform = AccountRowerForm(request.POST) accountform = AccountRowerForm(request.POST)
userform = UserForm(request.POST) userform = UserForm(request.POST,instance=request.user)
if accountform.is_valid() and userform.is_valid(): if accountform.is_valid() and userform.is_valid():
# process # process
cd = accountform.cleaned_data cd = accountform.cleaned_data
@@ -9714,7 +9721,7 @@ def rower_edit_view(request,message=""):
if len(first_name): if len(first_name):
u.first_name = first_name u.first_name = first_name
u.last_name = last_name u.last_name = last_name
if len(email): if len(email): ## and check_email_freeforuse(u,email):
u.email = email u.email = email

View File

@@ -26,6 +26,24 @@
background-image: url("/static/img/landing8b.jpg"); background-image: url("/static/img/landing8b.jpg");
} }
.watermark {
position: absolute;
float: center;
opacity: 0.25;
font-size: 3em;
width: 100%;
top: 50%;
left: 50%;
transform: translateX(-25%) translateY(-50%);
text-align: center;
vertical-align: middle;
z-index: 1000;
}
.invisible {
display: none
}
html { html {
font-size: 62.5%; font-size: 62.5%;
} }