From 956124575614554d8f3826d8558b8ab812c9696f Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 7 Nov 2017 16:09:37 +0100 Subject: [PATCH 1/8] zip file now with enumerated workout title --- rowers/dataprep.py | 29 +++++++++++++++------ rowers/management/commands/processemail.py | 13 +++++++-- rowers/testdata/emails/testdata.ZIP | Bin 0 -> 8029 bytes 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 rowers/testdata/emails/testdata.ZIP diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 62ff6923..5771c12e 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -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 @@ -975,14 +978,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 diff --git a/rowers/management/commands/processemail.py b/rowers/management/commands/processemail.py index 996d6bbc..4719bee3 100644 --- a/rowers/management/commands/processemail.py +++ b/rowers/management/commands/processemail.py @@ -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() @@ -119,9 +128,9 @@ 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) + title = name+' ('+str(id)+')' workoutid = processattachment( rower, datafile, title, uploadoptions, testing=testing diff --git a/rowers/testdata/emails/testdata.ZIP b/rowers/testdata/emails/testdata.ZIP new file mode 100644 index 0000000000000000000000000000000000000000..c8da5efc578c9eb2427a9e3ce080fb2bb6bac6fe GIT binary patch literal 8029 zcmZ{J1xy{>vh~4T5AGCqr%>G84qCLh6nA%bw}U%HiffBI#ogWA9bUitC;z=Kxo_`e z@9gZ%N+y}itXZQh2L+7<{X+qv5qI-4>ZJGts< ziBuD}qO1~L5<3D!(5WI5=ZB5vGjzM81tBSh+&A%fKhzRH6@#psAMY=7pVTR%Og^4> zUYb9=i~PLbE~K7U7Czn{uUCYGrZ+lYAD>q`XMT42dB2YOJ*B=spM8*S%&?FebbLG_ zbv!-3%7Cu?QcuF;a)sv995Y_C!zPM)3ID7ca`Gm0vOABWs|Kvo}HpShWmDq=5CZeleg zT_pASPg34IF{O;8zJQCa9KyE3q0^ifNTKe!&LoBVWF%?Ub+Ta-5}v~^B1vrMlEnwqadLT3hay06rh$ScN_cs7?P+ooHol!0KB2Jv9Fy+P>;4A*;JV(j}SD*fvKXl zr&k;Pz+Nl3(!>=_TPFUYfUAy`eZuP)VB3TGhdf@)No zpN4p94H5^cWrU%MmSi2`>$zr=71cr}N5uX~pWr?38X5-}8D|n+SG7HqdP12DQiMma zS;S|0sMeoN_RlHXB|VPJD6YbCS=5Zf2Q2jzmBW+vo#g9eX!KGc(n8l>MjH*7IaZx=t-5v(0s-XacB{S zQU?M7ZWa8y2sM&gec5m;j~o}=xIj{=CB)`KFNY2F88u&nv;@(bMxx+9(^C29D+p@= z`TGqd^ImG|$zK+;lTxPp_E5^WDT2mA7ag#;aO>KEH>h+h zAxAUr7YA7wciU{CT?mLU3i(SB(b_!v13;7Ac&uU$6bJR+HDf*ZL@xT#4i-a1-{Xdf z!DPvaDkTQ-Z3aI(vU+={5%pfXn}Q8}0^ti}ymh~|DbLjJp*&$(nfj744M>RqC4Mk8 z7#u6T69O{@h;6WZu$%yxkY8Owq4F-vYQ=ysXD0N*N#yVGNL?e5@RrD^;sI~kv`5-w&`$Dgu@8KR1!4`_qbG3ATIN*a1aEnyQ*z*(1;6_X`?=U?h71bAT4?n> zHJKB(^uz`us&H#RjP|??>`vj?EhbpVWZ=3LT&ivpv7)7Q2YUSOx)SVlFoCj1g}kyZ z<$=521dWo92sBH5f^)a4Rz`#nUf*V1x;y42)kDXovMA4Ts>2L_+>>#7pXFlC(ougnT8bC+}&wH+nPkmD|)!aCQwa8HPl)AX8nfN zI{Bi5y3(F8=tVmAoqefi_foBwF7QXNPvNLzFa90YALdy1^|6>*w?Ql97=Aj|ZyW7( zhSjq|bHm%eu+(GY0-K_(YPM#&oTjO*389}3N6CyB?J(7@jxoY@K^-CQ`6=oa+%kavwPc*g5u_V_0uwi%Ps2nI9zVyfLM;neW! zF0r0%)7p-NGgF6L?ud6xo1W@g*1&?Ig~6~zHmeLZCI;*IoRs1O#Z$LFM20K$O^b-s z_GtlQB_GgyP66M(R9Wmx`v-iAnHgy-4e5Z#wc~celQ5j~huLG~h_#Kbv)`9;;=&}{qyi5W<}SaqU1;p%&e5@m2hnS*(@N_^ zAVsY2nURVU#m7@VL{@arx862T#Q?p(RL{Bun9pDWzI;FRa~O486K$O19-1=Q@%o(+ zKbfCxy~+l@_?~2?`BQ)Fw7AA_lT;>(F?~PksF2jwsdEqTa(EJ75fLz7AU;vORS$v& zX6U7y42q-1@b%?8srQTsqP`XaSnI}*@@X1>mBwnp9NeQ|bP-R5(@3TZ{uCM6LXl`nz}>@BMZqvFdq<#nq*G zj4Emv$^!mc4mqO5wRA3*52N?(Sbrrc_hTlM9?AXu4=)UlwQr}*F16s^W-jA6AJ@zU zo@fXhRzJds)FZvCtMO*sPJVwsfb#!mSlI%QGULC(f6~JN0B1k|0Ov2mQnfcVH8Hj` zG`3(jbvLxJxBeH|n$sG!B^<(#@gS?G>|q;ReyUEB!{r$pRc zY<+*3b$Uc+iI?D>68#ibTG2jU{zY%r>9nFkHUvz4I8o0VZknd z?ST#e3YxI|26x?!XB)HQN>ThiP{0z3u$=TmPyA?kw!mZ@)>|B|=7z2Afzs0;Z z2^*}fe+#c+E+5UGx*#ypPFpRG=R1WG)fD~VgLh3wCEFi)Y}5EPa(cQ`bP|O8fujft zchIA+6=4$l!%9P&WdffrMxRpF`WvDfg&5uzU03PCQOYG29aJbJ!$Ymh8&lahJ zu^IKli)(BrRg2)@$__X>)-pL_m#pE_-;e|H6fG5acYV0Bd>>qjj&P!~9g@W4z-Ui} z!ci2QQVnAp6Y$G&L2#~xIfV&@Akt`)rbkbW#C?vXZ|pthSsrpoA?xZ_Xo3HvKm>^z zI1+nImnm*&=>MKLc{uf~O!V3LwgH6Ivp3Jxqb^&(hALYYDi^VBIxg<;>wEFcTs&*& zK5ZUA!r0+U>v#1?B)LQ4UTjdI#19ZV6Go(4kv0*L=8;LGCQ%BWqvh0B6?-4)seow9 z=y^4U(#$G&B%5eS#(Rdvfe;HvDHsvkE%DVR zq_y2}aWBWO@x^lZEcVO8&hlvzE(Iv+dm^9DP0f1(Dgqhdr%dPVt`Acu-| z=Oao}p)yCY!qxun8y*jKHr;&{mRPqoufE_v9M8~RU;<-94~-%a!$msjS+H2SFPpa{ zBFq(?fV6~$9J*z2ZwZu$SAHUbWMuDFMUdg7bop8#tGBxTu)|HEHW;g9(#sl3am%sR z7ll}ybB80{_v_GyP!X*laW&L^4qtGObJPt}j|2qaoR~UbF!RN};e$Pf@I7WH`;{Z= zlR+)4=n>Rr2xdWeh(Cg|R_kKF$5I@aF<7So5%UQMsnanMdovAlp23`#n`M%+Tr9un zQLO3x-HQua!k&A)0dhV4;+ojhbcwt4WiSPnZ8hMc@Omw(bSO~aaB+zfBp(^2UDPgn zywcPU+J4FxM^w)0AFt8QinN=T*Zx5=?_3=?)$_hgfmfOJ}ft(eh2MT_ngu?9I9?fK}G@M|jZ z67^AI)0qh-7q_(t<$X3G?We_Fs<#QZJ@8_YUI<|fs$>ti+XtB-O@`2|D&zsJ&1NXn?nLGyyhNFnlooYJ`fRB$K$`jV(1zvYI5E zn-!E%gu4LQ22~If#R*?x8oKTDG%Hfh9d;GOz8K?66g4@Jz3#C0%-EtumI)(C0$soM z?A-wJW;a9=2jdPy`7kDs_uzl0{R_pUK>`H_4u7=9eiKv}7a9`9kapS({HZTv78v=5 z8+yLtMh*y6qeM!PpWVTjA`ocB>7t-fIyz{nq+NhaH^_nT0=Gy+cT=C2%zPrLV(-1S z@z+VT{S+&`mZXbnQZLIslYNp}HJv0^VD|OZYiJnDLfT%vCK6>n?WTZ25AO|iCNdi| zR`w-MARMFMEArz0oRUhT9)bIWyPa=trI^MP#lco zl>vD(8kOx(jhtOJg6zJO8tSCSzJNU8f2IxQm`oLQ9+wydyv5F(H2ifmP(I*& z0z(5m20t6;4M|WbpjU$PZsyhR1H+#93HRUCwbaK%F5<4Ds}}|U7{>qr{uWq(v#FD_ ziJ`NhlZ%awp`*t?I!2Y&itP#yx(~~w-w+%t#06GvP(Slz0qyen-k7otHMX@(Xyxkj zotGKyDCpF=-6267YjA(dp4mLKwVb=0?u7EW)CNnvzvbPS}AI_>u56Vv>x4S zuGl+&KfZ6rgXzIL3r?knFAm0#MKTTUR>ht|*eRwgk5ox7fZv{AS+Luv9C@*FQN7c= zh2uo-@x+HCqT@`(>e1Tr|7yWjs~ZI0ZU78T*P-{p)}7_T&ykMR!h}$I_DEb4FUhXe9pZggYJhS^xG@@f zVLOh^hyAYDP>o<{CGMXCf29%2E{nKQb(rY%Fh*XK7bMoSGh@AbOMdGdMAM-7BvLA> zSVg&;q>_n+kB@$nX*o;g#`ffHY|Vw-4@|k=mUiZ&r9!2tOp7|daAfh$eC|`o8M;j2 z1*W@uzYNsjczlzA^`6BnZ@0K%yObK{^v4U1#%)6X5~)SYPDkdV$WB;1j%TLFl3IZf zjOvSsaTw9h=82INhI6}XssB-yT{+kn$;}cfh4eVXvnsZ}kmP7>d*#BTAVa!8i2rdIn4j zKqmLt^h76kLSod=B+#lPVw)(jaTW}#0-v^kJ=b0~b}~zMrV(OyJid`p@F@;Z^8GT| z^ACkAEYBQIf9zHOm1=ZvTSD07qiHU4RKqd%5xrmbpa+g&zn&B+@->>uUH&ZNA= zKZ(;9h(JfoP@Hmh&vXUydV4KQ#GL%jG4kl47bi0k3*bNw34n1b>(B#y6NxIGKL*K^ zs@Dor>V0xdPJ*r9q&qqGu@(}Fs56t+v&cuMr~y*Ju|t<8=FD!B%Z;mUrk$=gqNzLS zaJj5#TCl;f__V%<5nrg8aUwc7B1dMqs2O9GF(WE@6ov*+ML!+ws5BntWpB)6Zl7~H z>PY{A2o=8*bp=6v;;nj?1v__gcj%Uh`)=^1Gh zn-=y1h#Q3FzaDZZ;WbW0qSeEiTYmAYWZCQLWpD!5uX)XL^1wqg(yVa^mWBpID2xKL zs)Pk)5d6nUD8O6LTAxp0_3H-_wGX@=FOeJb?Bq~BnuC<=@q>DOPP=n;2_Yd+LLWdQV&(c%`$OU$%M-^@yk5SJ%`iV4!#n{4`h`R#|ns z8MCA|i5?MBkeetK0|zA#OV9l%5!g^zp>ghR@eM-k9qHdW|4oBJwiKW+;qie7X z|Iw@+?cD4g?YRDx>(f>eu?8_hFCsRHoe#hu2~+iX@>VgjT12D>e~C8d=p{q9MPLrT ze4ge_21%0nxPoLay@1CD0(u8i&G%qH6-&Y=M8=8;SM(fQrNnQM` z->n;$Lf7v0nNO>0CMI6ad~x4BJD0;==I-T^EG_DigJ#(y8wrZ;7&Alt4!!xGM^7~4 zcG@4(2AfJ2$1ff#z&B%OTQf6bww-NW^{8C=cKItfG7us$_;WCL3<5eiljYQ>Yp)YmTf?dS2 zg-eJTs;pEhJK^N0%~lmDihwrmQ0yu#qtn`RwcT!G>xHT&36Z~q z^#EyBbdy^1YC5?x_4X!4;#&5>HrAW!JH9gF-L#46&_i93BgYVGQ_9h0;O1rf|6C&t zBGLBf%~ZIAE|}JK`CTDkR>73EPKT8*4CXJ&habHZ<2%RwGU|xKmNBi`s^DsmzoKA_ zFTh(%nI{}y7m=!1g}+s!9l&=o;BlClGKuh$qsf<2>u90j727uXyUU%F)ZU-0kL z5)8*q<|QHTFSGV3-TJl3utmv9Bj8kE|4<}t25yq)CjnHnK(Bjg{-e_?xav_Aid$COioHXF*Bq(Qbl`@O4rNw|v? zj)*P(1Nk#?u4UpB32pdCVarFkHbJ!*^Qe|pYc?5b3MZ?5-#0y}(JI9~b&0|wUpyIm zOt2es(4P#Pa!2OyNTeG~K?K)s`XVyE4<@Cjg-kMZ&n;e7>fyZNFHBpH$!+${9LA|v z6$yTngD}5!K3xF?jWs#4MR@zPgC%1cyc)S&$%%Gbw>p`&z_U1cUe34jVPlWg&~r5r zar@I-#wbyOa#lZ=N;{(tFgv2)pTa&_=CO)73T#rmj+{@Ldpn9*f8na6Cq3ft)b9I< zO&~lSPX78i`!Rw!4v|6z1CO98{PPGSPcmc#o**vHmXzCbdTUp*H9Ly_CfxJlR0i7} zEIFR`G3ZNyopV*sJ8+eB7tDnKV%6oOfcP?#q*fzY&hR-Y+c1Pk_ys`)$w_r8(exQv zBZJ^kXZFvfbn9WaUx`Wi997pF+;SSIIdr^aHFLz-4#7{20EeFS`VZd5Uc3(;LpB6# zu+p18Ic*S4=}?KpwIzgJXsO%~V5>lIB$H!*YrBUGGu&}|8+oN? zo?wKCudaj<8#V1{#ON_*$D=WUd;bcJ7i;~!J0Xn$ljSL8G|s9zy?=B~vB`$CkFoY2 zLC5}C)FJO%t|WDL66Ov;G(1XRcr7z!&{?>7H3ki0ZynD|8Ub>W>9Rk5SV!&b2#FJ9$0ID}VOo0DxgeA|l%St#6L$s8O{#;~+8Th6BB@^8 zF4Poi2n&B&WI-6yDidudWDY1&*Xu6R5wSuBIS&kWb@toFf-*^jCz!qQNZi{L`kocL zh3Wqxt#`$+xujSEgk_}}>b)>OW>He_BxZSfBQ>wEyI|(g9}62|L0h~zsu_Ecm6>UN zGz^HLm`X-3NI*=rO9#EdC+$9(IAU=`J27&%98u36i;p`B#gI~`MmN*&J{*!I9`gr2a7o(R1Hmog^}4^{nS31bJ#$ z(|jbtz9E~27x@9GEhP!hJ_7HboCOPu%S|^!q5w$n4EfK>FbY*&b_43~b^-5%%5o5p zSb+a~qWrf&|6OGNI{(*v`9BT+n^5}yEdDVx`a7Wh@3QHCV*Iyu@ZT6+=>N4j{7-=Y g#x4F0(C}Bh{ST(0EC&Po&nNWX{qXlzCi&;}KQUev-~a#s literal 0 HcmV?d00001 From 5061cbcd1cd4335a606f41532a4792fbc023edb4 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 7 Nov 2017 17:00:18 +0100 Subject: [PATCH 2/8] work in progress: now has alternative way to find chart settings --- rowers/uploads.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/rowers/uploads.py b/rowers/uploads.py index a6fab4a1..e05eca04 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -19,6 +19,9 @@ import yamllint from subprocess import call import re +from verbal_expressions import VerEx + + import django_rq queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') @@ -45,6 +48,29 @@ def cleanbody(body): return body +def matchchart(line): + results = [] + tester = VerEx().start_of_line().find('chart') + tester2 = VerEx().start_of_line().find('chart').anything().find('distance') + tester3 = VerEx().start_of_line().find('chart').anything().find('time') + tester4 = VerEx().start_of_line().find('chart').anything().find('pie') + if tester.match(line): + if tester2.match(line): + return 'distanceplot' + if tester3.match(line): + return 'timeplot' + if tester3.match(line): + return 'pieplot' + +def get_plotoptions_body2(uploadoptions,body): + for line in body.splitlines(): + chart = matchchart(line) + if chart: + uploadoptions['make_plot'] = True + uploadoptions['plottype'] = chart + + + def getsyncoptions(uploadoptions,values): try: value = values.lower() From 70b55290290940debd63c9e1102abc77ab705c30 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 7 Nov 2017 22:21:01 +0100 Subject: [PATCH 3/8] alternative natural language for plot,sync,privacy --- rowers/uploads.py | 97 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 79 insertions(+), 18 deletions(-) diff --git a/rowers/uploads.py b/rowers/uploads.py index e05eca04..a7bd5622 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -19,8 +19,8 @@ import yamllint from subprocess import call import re -from verbal_expressions import VerEx - +from verbalexpressions import VerEx +import re import django_rq queue = django_rq.get_queue('default') @@ -48,29 +48,85 @@ def cleanbody(body): return body +# currently only matches one chart def matchchart(line): results = [] - tester = VerEx().start_of_line().find('chart') - tester2 = VerEx().start_of_line().find('chart').anything().find('distance') - tester3 = VerEx().start_of_line().find('chart').anything().find('time') - tester4 = VerEx().start_of_line().find('chart').anything().find('pie') - if tester.match(line): - if tester2.match(line): + 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): + if tester3.match(line.lower()): return 'timeplot' - if tester3.match(line): + if tester3.match(line.lower()): return 'pieplot' -def get_plotoptions_body2(uploadoptions,body): +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() @@ -147,13 +203,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' From a8eae5911dcbc16297d5cfda82e0b097c6657d2b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 8 Nov 2017 09:22:15 +0100 Subject: [PATCH 4/8] upload options work in background upload --- rowers/management/commands/processemail.py | 6 +++++- rowers/templates/document_form.html | 6 +++--- rowers/uploads.py | 2 ++ rowers/views.py | 8 +++++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/rowers/management/commands/processemail.py b/rowers/management/commands/processemail.py index 4719bee3..5adffab1 100644 --- a/rowers/management/commands/processemail.py +++ b/rowers/management/commands/processemail.py @@ -114,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 diff --git a/rowers/templates/document_form.html b/rowers/templates/document_form.html index 7ad85c58..c7d2cda2 100644 --- a/rowers/templates/document_form.html +++ b/rowers/templates/document_form.html @@ -53,7 +53,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.

-

Select Files with the File button or drag them on the marked area

+

Select Files with the File button or drag them on the marked area

@@ -141,7 +141,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 +240,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); diff --git a/rowers/uploads.py b/rowers/uploads.py index a7bd5622..8dab7755 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -191,6 +191,8 @@ def upload_options(body): body = cleanbody(body) try: yml = (yaml.load(body)) + if 'fromuploadform' in yml: + return yml try: for key, value in yml.iteritems(): lowkey = key.lower() diff --git a/rowers/views.py b/rowers/views.py index bfa49541..619a99db 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -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 @@ -8551,9 +8552,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) From 7e0e8dcfea9ec9432b150923c3386e98e9f75a3b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 8 Nov 2017 10:20:45 +0100 Subject: [PATCH 5/8] added watermark to drag area --- rowers/templates/document_form.html | 20 +++++++++++++++++--- static/css/rowsandall.css | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/rowers/templates/document_form.html b/rowers/templates/document_form.html index c7d2cda2..23888508 100644 --- a/rowers/templates/document_form.html +++ b/rowers/templates/document_form.html @@ -15,7 +15,10 @@ {% endblock %} - {% block content %} +{% block content %} +
@@ -25,7 +28,7 @@ Upload?

{% endif %} {% if form.errors %} -

+

Please correct the error{{ form.errors|pluralize }} below.

{% endif %} @@ -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 ) { diff --git a/static/css/rowsandall.css b/static/css/rowsandall.css index fe739c64..0362df15 100644 --- a/static/css/rowsandall.css +++ b/static/css/rowsandall.css @@ -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%; } From cc9a004e92d8acca50aa44d4c1423f4e402b44f8 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 8 Nov 2017 11:41:30 +0100 Subject: [PATCH 6/8] fix --- rowers/uploads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/uploads.py b/rowers/uploads.py index 8dab7755..ea29c505 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -191,7 +191,7 @@ def upload_options(body): body = cleanbody(body) try: yml = (yaml.load(body)) - if 'fromuploadform' in yml: + if yml and 'fromuploadform' in yml: return yml try: for key, value in yml.iteritems(): From 1489e9c47a83ef0f76095021d5ae52dff6384f17 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 8 Nov 2017 13:19:25 +0100 Subject: [PATCH 7/8] checks email for uniqueness upon change --- rowers/dataprep.py | 7 ++++++- rowers/models.py | 41 +++++++++++++++++++++++++++++++++++++++-- rowers/views.py | 5 +++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 5771c12e..98a55ae2 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -29,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, @@ -866,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) diff --git a/rowers/models.py b/rowers/models.py index ef3d25e0..ab94a5d7 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -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): diff --git a/rowers/views.py b/rowers/views.py index 619a99db..a94c8089 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -17,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, @@ -9703,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 @@ -9720,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 From 9f96a875d27893472633509663f433a517a73deb Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 8 Nov 2017 13:33:25 +0100 Subject: [PATCH 8/8] better numbering for zip file content --- rowers/management/commands/processemail.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rowers/management/commands/processemail.py b/rowers/management/commands/processemail.py index 5adffab1..d35156fe 100644 --- a/rowers/management/commands/processemail.py +++ b/rowers/management/commands/processemail.py @@ -134,7 +134,11 @@ class Command(BaseCommand): zip_file = zipfile.ZipFile(attachment.document) for id,filename in enumerate(zip_file.namelist()): datafile = zip_file.extract(filename, path='media/') - title = name+' ('+str(id)+')' + if id>0: + title = name+' ('+str(id+1)+')' + else: + title = name + workoutid = processattachment( rower, datafile, title, uploadoptions, testing=testing