Merge branch 'release/v5.02'
This commit is contained in:
@@ -8,6 +8,8 @@ from rowingdata import rowingdata as rrdata
|
||||
|
||||
from rowingdata import rower as rrower
|
||||
|
||||
from shutil import copyfile
|
||||
|
||||
from rowingdata import get_file_type, get_empower_rigging
|
||||
|
||||
from rowers.tasks import handle_sendemail_unrecognized
|
||||
@@ -17,6 +19,7 @@ from pandas import DataFrame, Series
|
||||
|
||||
from django.utils import timezone
|
||||
from django.utils.timezone import get_current_timezone
|
||||
from django_mailbox.models import Message,Mailbox,MessageAttachment
|
||||
|
||||
from time import strftime
|
||||
import arrow
|
||||
@@ -26,7 +29,7 @@ from rowingdata import (
|
||||
TCXParser, RowProParser, ErgDataParser,
|
||||
CoxMateParser,
|
||||
BoatCoachParser, RowPerfectParser, BoatCoachAdvancedParser,
|
||||
MysteryParser, BoatCoachOTWParser,
|
||||
MysteryParser, BoatCoachOTWParser,QuiskeParser,
|
||||
painsledDesktopParser, speedcoachParser, ErgStickParser,
|
||||
SpeedCoach2Parser, FITParser, fitsummarydata,
|
||||
make_cumvalues,cumcpdata,
|
||||
@@ -863,6 +866,11 @@ def handle_nonpainsled(f2, fileformat, summary=''):
|
||||
row = MysteryParser(f2)
|
||||
hasrecognized = True
|
||||
|
||||
# handle Quiske
|
||||
if (fileformat == 'quiske'):
|
||||
row = QuiskeParser(f2)
|
||||
hasrecognized = True
|
||||
|
||||
# handle RowPerfect
|
||||
if (fileformat == 'rowperfect3'):
|
||||
row = RowPerfectParser(f2)
|
||||
@@ -975,14 +983,24 @@ def new_workout_from_file(r, f2,
|
||||
inboard = 0.88
|
||||
if len(fileformat) == 3 and fileformat[0] == 'zip':
|
||||
f_to_be_deleted = f2
|
||||
title = os.path.basename(f2)
|
||||
res = myqueue(
|
||||
queuelow,
|
||||
handle_zip_file,
|
||||
r.user.email,
|
||||
title,
|
||||
f2
|
||||
)
|
||||
workoutsbox = Mailbox.objects.filter(name='workouts')[0]
|
||||
msg = Message(mailbox=workoutsbox,
|
||||
from_header=r.user.email,
|
||||
subject = title)
|
||||
msg.save()
|
||||
f3 = 'media/mailbox_attachments/'+f2[6:]
|
||||
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
|
||||
|
||||
|
||||
@@ -93,6 +93,15 @@ def processattachment(rower, fileobj, title, uploadoptions,testing=False):
|
||||
return workoutid
|
||||
|
||||
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 """
|
||||
def handle(self, *args, **options):
|
||||
attachments = MessageAttachment.objects.all()
|
||||
@@ -105,7 +114,11 @@ class Command(BaseCommand):
|
||||
extension = attachment.document.name[-3:].lower()
|
||||
try:
|
||||
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)
|
||||
from_address = message.from_address[0].lower()
|
||||
name = message.subject
|
||||
@@ -119,9 +132,13 @@ class Command(BaseCommand):
|
||||
for rower in rowers:
|
||||
if extension == 'zip':
|
||||
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/')
|
||||
title = os.path.basename(datafile)
|
||||
if id>0:
|
||||
title = name+' ('+str(id+1)+')'
|
||||
else:
|
||||
title = name
|
||||
|
||||
workoutid = processattachment(
|
||||
rower, datafile, title, uploadoptions,
|
||||
testing=testing
|
||||
|
||||
@@ -2,6 +2,8 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
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.forms import ModelForm
|
||||
from django.dispatch import receiver
|
||||
@@ -941,15 +943,50 @@ class AccountRowerForm(ModelForm):
|
||||
'defaulttimezone','showfavoritechartnotes',
|
||||
'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 Meta:
|
||||
model = User
|
||||
fields = ['first_name','last_name','email']
|
||||
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data.get('email')
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(UserForm, self).clean()
|
||||
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 == 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
|
||||
# to enable consistency
|
||||
class RowerForm(ModelForm):
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
</script>
|
||||
{% 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">
|
||||
<form id="file_form" enctype="multipart/form-data" action="{{ formloc }}" method="post">
|
||||
<div id="left" class="grid_6 alpha">
|
||||
@@ -25,7 +28,7 @@
|
||||
Upload?</a></p>
|
||||
{% endif %}
|
||||
{% if form.errors %}
|
||||
<p style="color: red;">
|
||||
<p style="color: red;">
|
||||
Please correct the error{{ form.errors|pluralize }} below.
|
||||
</p>
|
||||
{% 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
|
||||
taken after a successfull upload.
|
||||
</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>
|
||||
|
||||
@@ -79,6 +82,10 @@
|
||||
formdatasetok = false;
|
||||
}
|
||||
|
||||
if (!formdatasetok) {
|
||||
$("#id_dropregion").remove();
|
||||
}
|
||||
|
||||
if (formdatasetok) {
|
||||
|
||||
$(document).ready(function() {
|
||||
@@ -99,7 +106,14 @@
|
||||
console.log("Loading dropper");
|
||||
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");
|
||||
|
||||
if( window.FormData === undefined ) {
|
||||
@@ -141,7 +155,7 @@
|
||||
$('#id_offline').prop('checked','True');
|
||||
data.set($('#id_offline').attr('name'),$('#id_offline').prop('checked'));
|
||||
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');
|
||||
}
|
||||
}
|
||||
@@ -240,7 +254,7 @@
|
||||
$('#id_offline').prop('checked','True');
|
||||
data.set($('#id_offline').attr('name'),$('#id_offline').prop('checked'));
|
||||
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');
|
||||
}
|
||||
data.set("file",f);
|
||||
|
||||
BIN
rowers/testdata/emails/testdata.ZIP
vendored
Normal file
BIN
rowers/testdata/emails/testdata.ZIP
vendored
Normal file
Binary file not shown.
@@ -19,6 +19,9 @@ import yamllint
|
||||
from subprocess import call
|
||||
import re
|
||||
|
||||
from verbalexpressions import VerEx
|
||||
import re
|
||||
|
||||
import django_rq
|
||||
queue = django_rq.get_queue('default')
|
||||
queuelow = django_rq.get_queue('low')
|
||||
@@ -45,6 +48,85 @@ def cleanbody(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):
|
||||
try:
|
||||
value = values.lower()
|
||||
@@ -109,6 +191,8 @@ def upload_options(body):
|
||||
body = cleanbody(body)
|
||||
try:
|
||||
yml = (yaml.load(body))
|
||||
if yml and 'fromuploadform' in yml:
|
||||
return yml
|
||||
try:
|
||||
for key, value in yml.iteritems():
|
||||
lowkey = key.lower()
|
||||
@@ -121,13 +205,18 @@ def upload_options(body):
|
||||
except AttributeError:
|
||||
pass
|
||||
except yaml.YAMLError as exc:
|
||||
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,
|
||||
try:
|
||||
uploadoptions = getplotoptions_body2(uploadoptions,body)
|
||||
uploadoptions = getsyncoptions_body2(uploadoptions,body)
|
||||
uploadoptions = getprivateoptions_body2(uploadoptions,body)
|
||||
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
|
||||
return {'error':pbm}
|
||||
return {'error':pbm}
|
||||
|
||||
if uploadoptions == {}:
|
||||
uploadoptions['message'] = 'No parsing issue. No valid commands detected'
|
||||
|
||||
@@ -8,6 +8,7 @@ import pytz
|
||||
import operator
|
||||
import warnings
|
||||
import urllib
|
||||
import yaml
|
||||
from PIL import Image
|
||||
from numbers import Number
|
||||
from django.views.generic.base import TemplateView
|
||||
@@ -16,6 +17,7 @@ from django import template
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.http import (
|
||||
HttpResponse, HttpResponseRedirect,
|
||||
@@ -8551,9 +8553,14 @@ def workout_upload_view(request,
|
||||
)
|
||||
else:
|
||||
workoutsbox = Mailbox.objects.filter(name='workouts')[0]
|
||||
uploadoptions['fromuploadform'] = True
|
||||
bodyyaml = yaml.safe_dump(
|
||||
uploadoptions,
|
||||
default_flow_style=False
|
||||
)
|
||||
msg = Message(mailbox=workoutsbox,
|
||||
from_header=r.user.email,
|
||||
subject = t)
|
||||
subject = t,body=bodyyaml)
|
||||
msg.save()
|
||||
f3 = 'media/mailbox_attachments/'+f2[6:]
|
||||
copyfile(f2,f3)
|
||||
@@ -9697,7 +9704,7 @@ def rower_edit_view(request,message=""):
|
||||
})
|
||||
elif request.method == 'POST' and "weightcategory" in 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():
|
||||
# process
|
||||
cd = accountform.cleaned_data
|
||||
@@ -9714,7 +9721,7 @@ def rower_edit_view(request,message=""):
|
||||
if len(first_name):
|
||||
u.first_name = first_name
|
||||
u.last_name = last_name
|
||||
if len(email):
|
||||
if len(email): ## and check_email_freeforuse(u,email):
|
||||
u.email = email
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,24 @@
|
||||
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 {
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user