diff --git a/requirements_win.txt b/requirements_win.txt new file mode 100644 index 00000000..b0454d00 --- /dev/null +++ b/requirements_win.txt @@ -0,0 +1,192 @@ +amqp==2.4.2 +apipkg==1.5 +appdirs==1.4.3 +arcgis==1.6.0 +arrow==0.13.1 +asn1crypto==0.24.0 +atomicwrites==1.3.0 +attrs==19.1.0 +backcall==0.1.0 +beautifulsoup4==4.7.1 +billiard==3.5.0.5 +bleach==3.1.0 +bokeh==1.0.4 +boto==2.49.0 +braintree==3.51.0 +cairocffi==1.0.2 +celery==4.2.2 +certifi==2019.3.9 +cffi==1.12.2 +chardet==3.0.4 +Click==7.0 +colorama==0.4.1 +colorclass==2.2.0 +cookies==2.2.1 +coreapi==2.3.3 +coreschema==0.0.4 +coverage==4.5.3 +cryptography==2.6.1 +cycler==0.10.0 +dask==1.1.4 +decorator==4.4.0 +defusedxml==0.5.0 +Django==1.9.5 +django-analytical==2.5.0 +django-async-messages==0.3.1 +django-braces==1.13.0 +django-classy-tags==0.8.0 +django-cookie-law==2.0.1 +django-cors-headers==2.4.0 +django-countries==5.3.3 +django-datetime-widget==0.9.3 +django-debug-toolbar==1.4 +django-extensions==2.1.6 +django-htmlmin==0.10.0 +django-leaflet==0.24.0 +django-mailbox==4.7.1 +django-oauth-toolkit==0.10.0 +django-oauth2-provider==0.2.6.1 +django-rest-framework==0.1.0 +django-rest-swagger==2.2.0 +django-rq==1.3.0 +django-rq-dashboard==0.3.3 +django-ses==0.8.10 +django-shell-plus==1.1.7 +django-social-share==1.3.2 +django-suit==0.2.26 +django-suit-rq==1.0.1 +django-tz-detect==0.2.9 +djangorestframework==3.5.4 +docopt==0.6.2 +docutils==0.14 +entrypoints==0.3 +execnet==1.5.0 +factory-boy==2.11.1 +Faker==1.0.4 +fitparse==1.1.0 +future==0.17.1 +GDAL==2.3.3 +geocoder==1.38.1 +holoviews==1.11.3 +html5lib==1.0.1 +htmlmin==0.1.12 +HTMLParser==0.0.2 +httplib2==0.12.1 +icalendar==4.0.3 +idna==2.8 +image==1.5.27 +importlib-resources==1.0.2 +ipykernel==5.1.0 +ipython==7.3.0 +ipython-genutils==0.2.0 +ipywidgets==7.4.2 +iso8601==0.1.12 +isodate==0.6.0 +itypes==1.1.0 +jedi==0.13.3 +jeepney==0.4 +Jinja2==2.10 +jsonschema==3.0.1 +jupyter==1.0.0 +jupyter-client==5.2.4 +jupyter-console==6.0.0 +jupyter-core==4.4.0 +jupyterlab==0.35.4 +jupyterlab-server==0.2.0 +keyring==18.0.0 +kiwisolver==1.0.1 +kombu==4.3.0 +lxml==4.3.2 +Markdown==3.0.1 +MarkupSafe==1.1.1 +matplotlib==3.0.3 +MiniMockTest==0.5 +mistune==0.8.4 +mock==2.0.0 +more-itertools==6.0.0 +mpld3==0.3 +nbconvert==5.4.1 +nbformat==4.4.0 +nose==1.3.7 +nose-parameterized==0.6.0 +notebook==5.7.6 +numpy==1.16.2 +oauth2==1.9.0.post1 +oauthlib==1.0.3 +openapi-codec==1.3.2 +packaging==19.0 +pandas==0.24.2 +pandocfilters==1.4.2 +param==1.8.2 +parso==0.3.4 +pathspec==0.5.9 +pbr==5.1.3 +pexpect==4.6.0 +pickleshare==0.7.5 +Pillow==5.4.1 +pip-upgrader==1.4.6 +pluggy==0.9.0 +prometheus-client==0.6.0 +prompt-toolkit==2.0.9 +ptyprocess==0.6.0 +py==1.8.0 +pycparser==2.19 +Pygments==2.3.1 +pyparsing==2.3.1 +pyrsistent==0.14.11 +pyshp==2.1.0 +pytest==4.3.1 +pytest-django==3.4.8 +pytest-forked==1.0.2 +pytest-runner==4.4 +pytest-sugar==0.9.2 +pytest-xdist==1.27.0 +python-dateutil==2.8.0 +python-memcached==1.59 +python-twitter==3.5 +pytz==2018.9 +pyviz-comms==0.7.1 +pywin32-ctypes==0.2.0 +pywinpty==0.5.5 +PyYAML==5.1 +pyzmq==18.0.1 +qtconsole==4.4.3 +ratelim==0.1.6 +redis==3.2.1 +requests==2.21.0 +requests-oauthlib==1.2.0 +rowingdata==2.2.3 +rowingphysics==0.5.0 +rq==0.13.0 +scipy==1.2.1 +SecretStorage==3.1.1 +Send2Trash==1.5.0 +shell==1.0.1 +shortuuid==0.5.0 +simplejson==3.16.0 +six==1.12.0 +soupsieve==1.8 +SQLAlchemy==1.3.1 +sqlparse==0.3.0 +stravalib==0.10.2 +termcolor==1.1.0 +terminado==0.8.1 +terminaltables==3.1.0 +testpath==0.4.2 +text-unidecode==1.2 +timezonefinder==4.0.1 +tornado==6.0.1 +tqdm==4.31.1 +traitlets==4.3.2 +units==0.7 +uritemplate==3.0.0 +urllib3==1.24.1 +VerbalExpressions==0.0.2 +vine==1.3.0 +wcwidth==0.1.7 +webencodings==0.5.1 +widgetsnbextension==3.4.2 +winkerberos==0.7.0 +xmltodict==0.12.0 +yamjam==0.1.7 +yamllint==1.15.0 diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index ebe1d45b..3d390b6b 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -108,9 +108,16 @@ def get_c2_workouts(rower): for item in res.json()['data']: alldata[item['id']] = item - knownc2ids = uniqify([ + knownc2ids = [ w.uploadedtoc2 for w in Workout.objects.filter(user=rower) - ]) + ] + + tombstones = [ + t.uploadedtoc2 for t in TombStone.objects.filter(user=rower) + ] + + knownc2ids = uniqify(knownc2ids+tombstones) + newids = [c2id for c2id in c2ids if not c2id in knownc2ids] for c2id in newids: diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 3e7e767c..692b9359 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1270,6 +1270,7 @@ def new_workout_from_file(r, f2, message = None try: fileformat = get_file_type(f2) + print(fileformat,'aa') except IOError: os.remove(f2) message = "Rowsandall could not process this file. The extension is supported but the file seems corrupt. Contact info@rowsandall.com if you think this is incorrect." @@ -1327,7 +1328,13 @@ def new_workout_from_file(r, f2, # worth supporting if fileformat == 'unknown': message = "We couldn't recognize the file type" - f4 = f2[:-5]+'a'+f2[-5:] + extension = os.path.splitext(f2)[1] + filename = os.path.splitext(f2)[0] + if extension == '.gz': + filename = os.path.splitext(filename)[0] + extension2 = os.path.splitext(filename)[1]+extension + extension = extension2 + f4 = filename+'a'+extension copyfile(f2,f4) job = myqueue(queuehigh, handle_sendemail_unrecognized, @@ -1335,6 +1342,9 @@ def new_workout_from_file(r, f2, r.user.email) return (0, message, f2) + if fileformat == 'att': + # email attachment which can safely be ignored + return (0, '', f2) # handle non-Painsled by converting it to painsled compatible CSV if (fileformat != 'csv'): diff --git a/rowers/forms.py b/rowers/forms.py index cf130391..d0608bb1 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -63,8 +63,8 @@ class LoginForm(forms.Form): class SearchForm(forms.Form): q = forms.CharField(max_length=255,required=False, widget=forms.TextInput( - attrs={'placeholder': 'Search'}), - label='Search') + attrs={'placeholder': 'keyword or leave empty'}), + label='Filter by Keyword') diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 106be7bf..1152682a 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -3227,6 +3227,13 @@ def interactive_flex_chart2(id=0,promember=0, background_fill_color='white', text_color='black', ) + + sliderlabel = Label(x=10,y=470,x_units='screen',y_units='screen', + text='', + background_fill_alpha=0.7, + background_fill_color='white', + text_color='black',text_font_size='10pt', + ) if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): @@ -3236,6 +3243,7 @@ def interactive_flex_chart2(id=0,promember=0, plot.add_layout(y1means) plot.add_layout(annolabel) + plot.add_layout(sliderlabel) try: yaxlabel = axlabels[yparam1] @@ -3381,6 +3389,7 @@ def interactive_flex_chart2(id=0,promember=0, y2label=y2label, xlabel=xlabel, annolabel=annolabel, + sliderlabel=sliderlabel, y2means=y2means, ), code=""" var data = source.data @@ -3412,6 +3421,11 @@ def interactive_flex_chart2(id=0,promember=0, var maxdist = maxdist.value var minwork = minwork.value var maxwork = maxwork.value + + sliderlabel.text = 'SPM: '+minspm.toFixed(0)+'-'+maxspm.toFixed(0) + sliderlabel.text += ', Dist: '+mindist.toFixed(0)+'-'+maxdist.toFixed(0) + sliderlabel.text += ', WpS: '+minwork.toFixed(0)+'-'+maxwork.toFixed(0) + var xm = 0 var ym1 = 0 var ym2 = 0 diff --git a/rowers/mailprocessing.py b/rowers/mailprocessing.py index 0618edb4..32e7f3b8 100644 --- a/rowers/mailprocessing.py +++ b/rowers/mailprocessing.py @@ -137,6 +137,8 @@ def make_new_workout_from_email(rower, datafile, name, cntr=0,testing=False): # handle non-Painsled + if fileformat == 'att': + return 0 if fileformat != 'csv': filename_mediadir, summary, oarlength, inboard,fileformat = dataprep.handle_nonpainsled( 'media/' + datafilename, fileformat, summary) diff --git a/rowers/models.py b/rowers/models.py index 997ea37e..f124ac36 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -2506,7 +2506,6 @@ class PlannedSessionFormSmall(ModelForm): boattypes = mytypes.boattypes # Workout -@python_2_unicode_compatible class Workout(models.Model): workouttypes = mytypes.workouttypes workoutsources = mytypes.workoutsources @@ -2611,7 +2610,28 @@ class Workout(models.Model): ) return stri + +class TombStone(models.Model): + user = models.ForeignKey(Rower,on_delete=models.CASCADE) + uploadedtoc2 = models.IntegerField(default=0) + uploadedtostrava = models.BigIntegerField(default=0) + uploadedtosporttracks = models.BigIntegerField(default=0) + uploadedtounderarmour = models.BigIntegerField(default=0) + uploadedtotp = models.BigIntegerField(default=0) + uploadedtorunkeeper = models.BigIntegerField(default=0) +@receiver(models.signals.pre_delete,sender=Workout) +def create_tombstone_on_delete(sender, instance, **kwargs): + t = TombStone( + user=instance.user, + uploadedtoc2 = instance.uploadedtoc2, + uploadedtostrava = instance.uploadedtostrava, + uploadedtounderarmour = instance.uploadedtounderarmour, + uploadedtotp = instance.uploadedtotp, + uploadedtorunkeeper = instance.uploadedtorunkeeper, + ) + t.save() + # delete files belonging to workout instance # related GraphImage objects should be deleted automatically @receiver(models.signals.post_delete,sender=Workout) diff --git a/rowers/stravastuff.py b/rowers/stravastuff.py index 15a9a511..87d8f185 100644 --- a/rowers/stravastuff.py +++ b/rowers/stravastuff.py @@ -165,9 +165,15 @@ def get_strava_workouts(rower): w.uploadedtostrava = int(stravaid) w.save() - knownstravaids = uniqify([ + knownstravaids = [ w.uploadedtostrava for w in Workout.objects.filter(user=rower) - ]) + ] + + tombstones = [ + t.uploadedtostrava for t in TombStone.objects.filter(user=rower) + ] + + knownstravaids = uniqify(knownstravaids+tombstones) newids = [stravaid for stravaid in stravaids if not stravaid in knownstravaids] diff --git a/rowers/tasks.py b/rowers/tasks.py index 01def2df..6a8ad981 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -1026,7 +1026,7 @@ def handle_sendemail_breakthrough(workoutid, useremail, d = { 'first_name':userfirstname, 'siteurl':siteurl, - 'workoutid':workoutid, + 'workoutid':encoder.encode_hex(workoutid), 'btvalues':tablevalues, } @@ -1071,7 +1071,7 @@ def handle_sendemail_hard(workoutid, useremail, d = { 'first_name':userfirstname, 'siteurl':siteurl, - 'workoutid':workoutid, + 'workoutid':encoder.encode_hex(workoutid), 'btvalues':tablevalues, } @@ -1661,6 +1661,8 @@ def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename, haspower = row.df[' Power (watts)'].mean() > 50 except TypeError: haspower = True + except KeyError: + haspower = False nr_rows = len(row.df) if (plotnr in [1, 2, 4, 5, 8, 11, 9, 12]) and (nr_rows > 1200): diff --git a/rowers/templates/fitnessmetric.html b/rowers/templates/fitnessmetric.html index 7939ed15..6958985d 100644 --- a/rowers/templates/fitnessmetric.html +++ b/rowers/templates/fitnessmetric.html @@ -72,16 +72,13 @@ {% if rower.user %} -

{{ rower.user.first_name }} Power Estimates

+

Power Progress for {{ rower.user.first_name }}

{% else %} -

{{ user.first_name }} Power Estimates

+

Power Progress for {{ user.first_name }}

{% endif %}