Private
Public Access
1
0

Merge branch 'develop' into feature/django2

This commit is contained in:
Sander Roosendaal
2019-03-31 16:02:36 +02:00
20 changed files with 1917 additions and 48 deletions

192
requirements_win.txt Normal file
View File

@@ -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

View File

@@ -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:

View File

@@ -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'):

View File

@@ -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')

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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]

View File

@@ -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):

View File

@@ -72,16 +72,13 @@
</script>
{% if rower.user %}
<h1>{{ rower.user.first_name }} Power Estimates</h1>
<h1>Power Progress for {{ rower.user.first_name }} </h1>
{% else %}
<h1>{{ user.first_name }} Power Estimates</h1>
<h1>Power Progress for {{ user.first_name }} </h1>
{% endif %}
<ul class="main-content">
<li class="grid_4">
{{ the_div|safe }}
</li>
<li class="grid_2">
<form enctype="multipart/form-data" action="/rowers/fitness-progress/user/{{ rower.user.id }}/" method="post">
<table>
@@ -92,6 +89,9 @@
</form>
</li>
<li class="grid_4">
{{ the_div|safe }}
</li>
</ul>

View File

@@ -35,7 +35,7 @@
<ul class="main-content">
<li class="grid_4">
<div id="theplot" class="flexplot" style="min-width:300px;">
<div id="theplot" class="flexplot">
{{ the_div|safe }}
</div>
</li>
@@ -51,8 +51,8 @@
{{ optionsform.as_table }}
</table>
<p>
<input name="chartform" class="button green" type="submit"
value="Submit">
<input name="chartform" type="submit"
value="Update Chart">
</p>
</form>
</li>

View File

@@ -69,27 +69,20 @@
{{ the_div |safe }}
</li>
<li class="grid_2">
<p>
<p>Filter on date
<form enctype="multipart/form-data" method="post">
<table>
{{ dateform.as_table }}
</table>
{% csrf_token %}
<input name='daterange' type="submit" value="Submit">
</form>
</p>
{% if team %}
<p>and keyword</p>
<p>
<form id="searchform" action="/rowers/list-workouts/team/{{ team.id }}/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}/"
method="get" accept-charset="utf-8">
{% else %}
<form id="searchform" action="/rowers/list-workouts/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}/"
method="get" accept-charset="utf-8">
{% endif %}
{{ searchform }}
<input type="submit" value="GO">
</input>
</form>
{{ searchform }}
</p>
<p>
<input name='daterange' type="submit" value="Select workouts">
</form>
</p>
<p>
{% if rankingonly and not team %}

View File

@@ -283,7 +283,7 @@ class URLTests(TestCase):
for u in urls:
if u not in tested and 'rowers' in u and 'http' not in u and 'authorize' not in u and 'import' not in u and 'logout' not in u:
response2 = self.c.get(u)
if response2.status_code not in [200,302]:
if response2.status_code not in [200,302,301]:
print(len(tested))
print(response.templates[0].name)
print(url)
@@ -291,7 +291,7 @@ class URLTests(TestCase):
print(response2.status_code)
tested.append(u)
self.assertIn(response2.status_code,
[200,302])
[200,302,301])
else:
tested.append(u)

View File

@@ -0,0 +1,3 @@

File diff suppressed because it is too large Load Diff

View File

@@ -611,11 +611,24 @@ def fitnessmetric_view(request,id=0,mode='rower',
enddate=enddate,
)
breadcrumbs = [
{
'url':'/rowers/analysis',
'name':'Analysis'
},
{
'url':reverse('fitnessmetric_view'),
'name': 'Power Progress'
}
]
return render(request,'fitnessmetric.html',
{
'rower':therower,
'active':'nav-analysis',
'chartscript':script,
'breadcrumbs':breadcrumbs,
'the_div':thediv,
'mode':mode,
'form':form,

View File

@@ -522,9 +522,11 @@ def rower_register_view(request):
title='New User Sample Data',
notes='This is an example workout to get you started')
newworkoutid = response[0]
w = Workout.objects.get(id=newworkoutid)
w.startdatetime = timezone.now()
w.save()
if newworkoutid:
w = Workout.objects.get(id=newworkoutid)
w.startdatetime = timezone.now()
w.date = timezone.now().date()
w.save()
# Create and send email
fullemail = first_name + " " + last_name + " " + "<" + email + ">"

View File

@@ -1178,12 +1178,15 @@ def workouts_view(request,message='',successmessage='',
startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
query = None
if request.method == 'POST':
dateform = DateRangeForm(request.POST)
searchform = SearchForm(request.POST)
if dateform.is_valid():
startdate = dateform.cleaned_data['startdate']
enddate = dateform.cleaned_data['enddate']
if searchform.is_valid():
query = searchform.cleaned_data['q']
else:
dateform = DateRangeForm(initial={
'startdate':startdate,
@@ -1293,7 +1296,6 @@ def workouts_view(request,message='',successmessage='',
for w in workoutsnohr:
res = dataprep.workout_trimp(w)
query = request.GET.get('q')
if query:
query_list = query.split()
workouts = workouts.filter(

View File

@@ -70,7 +70,7 @@ body {
}
.yellow {
color: #cccc00;
color: #1c75bc;
font-size: 1.2em;
height: auto;
padding: 0px;
@@ -1005,6 +1005,7 @@ th.rotate > div > span {
z-index: 10;
}
a.wh:link {
color: #e9e9e9;
}

View File

@@ -564,28 +564,28 @@
@media (min-height: 600px) {
@media only screen and (min-height: 600px) {
.maxheight {
max-height: 450px;
overflow: scroll;
}
}
@media (min-height: 600px) {
@media only screen and (min-height: 600px) {
.maxheight {
max-height: 450px;
overflow: scroll;
}
}
@media (min-height: 800px) {
@media only screen and (min-height: 800px) {
.maxheight {
max-height: 600px;
overflow: scroll;
}
}
@media (min-height: 1000px) {
@media only screen and (min-height: 1000px) {
.maxheight {
max-height: 800px;
overflow: scroll;
@@ -593,7 +593,7 @@
}
@media (max-width: 600px) {
@media only screen and (max-width: 600px) {
nav a {
font-size: 0px;
}
@@ -601,9 +601,22 @@
nav a i {
font-size: 20px;
}
#theplot .bk-grid-column {
display: none;
}
#theplot .bk-plot-layout {
position: auto;
width: 100%;
height: auto;
display: inline;
}
}
@media (min-width: 450px) {
@media only screen and (min-width: 450px) {
.wrapper {
grid-template-columns: 1fr 3fr;
grid-template-areas:
@@ -648,7 +661,7 @@
}
@media (min-width: 768px) {
@media only screen and (min-width: 768px) {
.wrapper {
grid-template-columns: 1fr 4fr 1fr;
grid-template-areas:
@@ -727,7 +740,7 @@
page-break-after: avoid;
}
ul, img {
ul, img, table {
page-break-inside: avoid;
}