Private
Public Access
1
0

Merge branch 'release/v18.6.7'

This commit is contained in:
Sander Roosendaal
2022-09-04 17:53:37 +02:00
15 changed files with 97 additions and 54 deletions
+7
View File
@@ -531,6 +531,13 @@ class TeamInviteForm(ModelForm):
user = UserFullnameChoiceField(queryset=User.objects.all(), required=False) user = UserFullnameChoiceField(queryset=User.objects.all(), required=False)
email = forms.EmailField(required=False) email = forms.EmailField(required=False)
def __init__(self, *args, **kwargs):
userid = kwargs.pop('userid',0)
super(TeamInviteForm, self).__init__(*args, **kwargs)
if userid:
self.fields['user'].initial = userid
class Meta: class Meta:
model = TeamInvite model = TeamInvite
fields = ['user', 'email'] fields = ['user', 'email']
+11 -2
View File
@@ -138,6 +138,12 @@ def is_staff(user): # pragma: no cover
@rules.predicate @rules.predicate
def is_coach(user): def is_coach(user):
if user is None:
return False
if user.is_anonymous:
return False
if user.rower.coachtrialexpires >= timezone.now().date():
return True
return user.rower.rowerplan in ['coach', 'freecoach'] return user.rower.rowerplan in ['coach', 'freecoach']
@@ -157,6 +163,9 @@ def is_planmember(user):
except AttributeError: # pragma: no cover except AttributeError: # pragma: no cover
return False return False
if user.rower.coachtrialexpires >= timezone.now().date():
return True
return r.rowerplan in ['coach', 'plan'] # freecoach? return r.rowerplan in ['coach', 'plan'] # freecoach?
@@ -198,7 +207,7 @@ def is_coachtrial(user):
return False # pragma: no cover return False # pragma: no cover
ispromember = is_promember | is_protrial ispromember = is_promember | is_protrial | is_coach | is_coachtrial
can_have_teams = ispromember | is_coach | is_coachtrial can_have_teams = ispromember | is_coach | is_coachtrial
@@ -251,7 +260,7 @@ def is_plantrial(user):
isplanmember = is_planmember | is_plantrial isplanmember = is_planmember | is_plantrial
iscoachmember = is_coach | is_coachtrial iscoachmember = is_coachtrial | is_coach
@rules.predicate @rules.predicate
+15
View File
@@ -314,6 +314,21 @@ def summaryfromsplitdata(splitdata, data, filename, sep='|', workouttype='rower'
return sums, sa, results return sums, sa, results
@app.task
def handle_remove_workouts_team(ws, t, debug=False, **kwargs):
for w in ws:
w.team.remove(t)
return 1
@app.task
def handle_add_workouts_team(ws, t, debug=False, **kwargs):
for w in ws:
w.team.add(t)
return 1
@app.task @app.task
def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs): def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs):
authorizationstring = str('Bearer ' + workout.user.tptoken) authorizationstring = str('Bearer ' + workout.user.tptoken)
+11 -16
View File
@@ -42,20 +42,7 @@ queuehigh = django_rq.get_queue('low')
inviteduration = 14 # days inviteduration = 14 # days
from rowers.tasks import handle_remove_workouts_team, handle_add_workouts_team
def handle_remove_workouts_team(ws, t):
for w in ws:
w.team.remove(t)
return 1
def handle_add_workouts_team(ws, t):
for w in ws:
w.team.add(t)
return 1
def update_team(t, name, manager, private, notes, viewing): def update_team(t, name, manager, private, notes, viewing):
@@ -128,10 +115,17 @@ def add_member(id, rower):
except ValidationError as e: # pragma: no cover except ValidationError as e: # pragma: no cover
return(0, "Couldn't add member: "+str(e.message)) return(0, "Couldn't add member: "+str(e.message))
# if team manager is coach
if is_coach(t.manager):
result = add_coach(t.manager.rower, rower)
# code to add all workouts # code to add all workouts
ws = Workout.objects.filter(user=rower) ws = Workout.objects.filter(user=rower)
_ = handle_add_workouts_team(ws, t) _ = myqueue(
queuehigh,
handle_add_workouts_team,
ws, t)
# code to add plannedsessions # code to add plannedsessions
plannedsessions = PlannedSession.objects.filter( plannedsessions = PlannedSession.objects.filter(
@@ -154,7 +148,8 @@ def remove_member(id, rower):
# remove the team from rower's workouts: # remove the team from rower's workouts:
ws = Workout.objects.filter(user=rower, team=t) ws = Workout.objects.filter(user=rower, team=t)
_ = handle_remove_workouts_team(ws, t)
_ = myqueue(queuehigh,handle_remove_workouts_team,ws, t)
# set_teamplanexpires(rower) # set_teamplanexpires(rower)
return (id, 'Member removed') return (id, 'Member removed')
+21 -21
View File
@@ -25,7 +25,7 @@
<div id="id_drop-files" class="drop-files"> <div id="id_drop-files" class="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">
<h1>Upload Workout File</h1> <h1>Upload Workout File</h1>
{% if user.is_authenticated and user|is_manager %} {% if user.is_authenticated and user|coach_rowers %}
<p>Looking for <a href="/rowers/workout/upload/team/">Team Manager <p>Looking for <a href="/rowers/workout/upload/team/">Team Manager
Upload?</a></p> Upload?</a></p>
{% endif %} {% endif %}
@@ -50,7 +50,7 @@
<p> <p>
<table> <table>
{{ optionsform.as_table }} {{ optionsform.as_table }}
</table> </table>
</p> </p>
<p> <p>
@@ -60,7 +60,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> <p>
If you don't have a workout file but have written down the splits, If you don't have a workout file but have written down the splits,
you can create a workout file yourself from <a href="/static/dummy_workout_template.xls">this template</a> you can create a workout file yourself from <a href="/static/dummy_workout_template.xls">this template</a>
@@ -68,7 +68,7 @@
<p><b>Select Files with the File button or drag them on the marked area</b></p> <p><b>Select Files with the File button or drag them on the marked area</b></p>
</li> </li>
@@ -91,16 +91,16 @@ $( document ).ready(function() {
|| $(this).val() == 'churchboat' || $(this).val() == 'churchboat'
) { ) {
$('#id_boattype').toggle(true); $('#id_boattype').toggle(true);
} else { } else {
$('#id_boattype').toggle(false); $('#id_boattype').toggle(false);
$('#id_boattype').val('1x'); $('#id_boattype').val('1x');
} }
}); });
$('#id_workouttype').change(); $('#id_workouttype').change();
}); });
</script> </script>
<script> <script>
var td = new FormData(); var td = new FormData();
var formdatasetok = false; var formdatasetok = false;
@@ -117,13 +117,13 @@ $('#id_workouttype').change();
if (!formdatasetok) { if (!formdatasetok) {
$("#id_dropregion").remove(); $("#id_dropregion").remove();
} }
if (formdatasetok) { if (formdatasetok) {
$(document).ready(function() { $(document).ready(function() {
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val(); var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
console.log("CSRF token",csrftoken); console.log("CSRF token",csrftoken);
function csrfSafeMethod(method) { function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection // these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
@@ -141,11 +141,11 @@ $('#id_workouttype').change();
$(window).on('dragenter', function() { $(window).on('dragenter', function() {
$("#id_drop-files").css("background-color","#E9E9E4"); $("#id_drop-files").css("background-color","#E9E9E4");
$("#id_dropregion").addClass("watermark").removeClass("invisible");}) $("#id_dropregion").addClass("watermark").removeClass("invisible");})
$(window).on('dragleave', function() { $(window).on('dragleave', function() {
$("#id_drop-files").css("background-color","#FFFFFF"); $("#id_drop-files").css("background-color","#FFFFFF");
$("#id_dropregion").removeClass("watermark").addClass("invisible");}) $("#id_dropregion").removeClass("watermark").addClass("invisible");})
var frm = $("#file_form"); var frm = $("#file_form");
if( window.FormData === undefined ) { if( window.FormData === undefined ) {
@@ -154,7 +154,7 @@ $('#id_workouttype').change();
} else { } else {
console.log('we have formdata'); console.log('we have formdata');
} }
var data = new FormData(frm[0]); var data = new FormData(frm[0]);
$('#id_file').on('change', function(evt) { $('#id_file').on('change', function(evt) {
@@ -187,11 +187,11 @@ $('#id_workouttype').change();
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.'); $('#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');
} }
} }
}); });
$('input').each(function( i ) { $('input').each(function( i ) {
$(this).change(function() { $(this).change(function() {
@@ -211,8 +211,8 @@ $('#id_workouttype').change();
$(this).change(function() { $(this).change(function() {
data.set($(this).attr('name'),$(this).val()); data.set($(this).attr('name'),$(this).val());
console.log($(this).attr('id'),$(this).attr('name'),$(this).val()); console.log($(this).attr('id'),$(this).attr('name'),$(this).val());
});}); });});
$('select').each(function( i ) { $('select').each(function( i ) {
console.log($(this).attr('name'),$(this).val()); console.log($(this).attr('name'),$(this).val());
$(this).change(function() { $(this).change(function() {
@@ -253,7 +253,7 @@ $('#id_workouttype').change();
console.log(result," reloading"); console.log(result," reloading");
location.reload(); location.reload();
}; };
} }
}); });
return false; return false;
}); });
@@ -295,9 +295,9 @@ $('#id_workouttype').change();
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.'); $('#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);
// data.append("file",f); // data.append("file",f);
$("#id_file").replaceWith('<div id="id_file">'+files[0].name+'&nbsp; <a class="remove" href="javascript:void(0);"><b><font color="red">X</font></b></a></div>'); $("#id_file").replaceWith('<div id="id_file">'+files[0].name+'&nbsp; <a class="remove" href="javascript:void(0);"><b><font color="red">X</font></b></a></div>');
+1 -1
View File
@@ -115,6 +115,7 @@
{% if user.is_authenticated and user|is_manager and rower %} {% if user.is_authenticated and user|is_manager and rower %}
<p>&nbsp;</p> <p>&nbsp;</p>
{% endif %}
{% if user|coach_rowers %} {% if user|coach_rowers %}
<ul class="cd-accordion-menu animated"> <ul class="cd-accordion-menu animated">
@@ -137,7 +138,6 @@
</li> </li>
</ul> </ul>
{% endif %} {% endif %}
{% endif %}
{% if user|user_teams %} {% if user|user_teams %}
<p>&nbsp;</p> <p>&nbsp;</p>
+1 -1
View File
@@ -65,6 +65,7 @@
{% if user.is_authenticated and user|is_manager and rower %} {% if user.is_authenticated and user|is_manager and rower %}
<p>&nbsp;</p> <p>&nbsp;</p>
{% endif %}
{% if user|coach_rowers %} {% if user|coach_rowers %}
<ul class="cd-accordion-menu animated"> <ul class="cd-accordion-menu animated">
@@ -87,7 +88,6 @@
</li> </li>
</ul> </ul>
{% endif %} {% endif %}
{% endif %}
{% if teams %} {% if teams %}
<p>&nbsp;</p> <p>&nbsp;</p>
+9 -2
View File
@@ -49,7 +49,6 @@ from six import string_types
register = template.Library() register = template.Library()
@register.filter @register.filter
def workoutdate(id): # pragma: no cover def workoutdate(id): # pragma: no cover
try: try:
@@ -711,7 +710,8 @@ def team_members(user):
try: try:
therower = Rower.objects.get(user=user) therower = Rower.objects.get(user=user)
if therower.rowerplan == 'basic': # pragma: no cover if therower.rowerplan == 'basic': # pragma: no cover
return [] if therower.coachtrialexpires < timezone.now().date():
return []
teams = Team.objects.filter(manager=user) teams = Team.objects.filter(manager=user)
members = Rower.objects.filter( members = Rower.objects.filter(
team__in=teams).distinct().order_by( team__in=teams).distinct().order_by(
@@ -757,13 +757,20 @@ def team_rowers(user): # pragma: no cover
return [] return []
@register.filter
def length(l):
return len(l)
@register.filter @register.filter
def coach_rowers(user): def coach_rowers(user):
if user.is_anonymous:
return []
if user.rower.rowerplan != 'freecoach': if user.rower.rowerplan != 'freecoach':
thelist = [user.rower]+[c for c in coach_getcoachees(user.rower)] thelist = [user.rower]+[c for c in coach_getcoachees(user.rower)]
else: # pragma: no cover else: # pragma: no cover
thelist = [c for c in coach_getcoachees(user.rower)] thelist = [c for c in coach_getcoachees(user.rower)]
return thelist return thelist
+1
View File
@@ -69,6 +69,7 @@ class OwnApi(TestCase):
# response must be json # response must be json
strokedata = json.loads(response.content) strokedata = json.loads(response.content)
df = pd.DataFrame(strokedata) df = pd.DataFrame(strokedata)
self.assertFalse(df.empty) self.assertFalse(df.empty)
form_data = { form_data = {
+6
View File
@@ -261,8 +261,12 @@ urlpatterns = [
name='rower_update_empower_view'), name='rower_update_empower_view'),
re_path(r'^agegroupcp/(?P<age>\d+)/$', re_path(r'^agegroupcp/(?P<age>\d+)/$',
views.agegroupcpview, name='agegroupcpview'), views.agegroupcpview, name='agegroupcpview'),
re_path(r'^agegroupcp/(?P<age>\d+)/user/(?P<userid>\d+)/$',
views.agegroupcpview, name='agegroupcpview'),
re_path(r'^agegroupcp/(?P<age>\d+)/(?P<normalize>\d+)/$', re_path(r'^agegroupcp/(?P<age>\d+)/(?P<normalize>\d+)/$',
views.agegroupcpview, name='agegroupcpview'), views.agegroupcpview, name='agegroupcpview'),
re_path(r'^agegroupcp/(?P<age>\d+)/(?P<normalize>\d+)/user/(?P<userid>\d+)/$',
views.agegroupcpview, name='agegroupcpview'),
re_path(r'^ajax_agegroup/(?P<age>\d+)/(?P<weightcategory>\w+.*)/(?P<sex>\w+.*)/(?P<userid>\d+)/$', re_path(r'^ajax_agegroup/(?P<age>\d+)/(?P<weightcategory>\w+.*)/(?P<sex>\w+.*)/(?P<userid>\d+)/$',
views.ajax_agegrouprecords, name='ajax_agegrouprecords'), views.ajax_agegrouprecords, name='ajax_agegrouprecords'),
re_path(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)/(?P<distance>\d+)m/$', re_path(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)/(?P<distance>\d+)m/$',
@@ -429,6 +433,8 @@ urlpatterns = [
name='workout_toggle_ranking'), name='workout_toggle_ranking'),
re_path(r'^workout/upload/team/$', views.team_workout_upload_view, re_path(r'^workout/upload/team/$', views.team_workout_upload_view,
name='team_workout_upload_view'), name='team_workout_upload_view'),
re_path(r'^workout/upload/team/user/(?P<userid>\d+)/$', views.team_workout_upload_view,
name='team_workout_upload_view'),
re_path(r'^workout/upload/(?P<raceid>\d+)/$', re_path(r'^workout/upload/(?P<raceid>\d+)/$',
views.workout_upload_view, name='workout_upload_view'), views.workout_upload_view, name='workout_upload_view'),
re_path(r'^workout/upload/$', views.workout_upload_view, re_path(r'^workout/upload/$', views.workout_upload_view,
+1 -1
View File
@@ -1763,7 +1763,7 @@ def otwcp_toadmin_view(request, theuser=0,
return response return response
def agegroupcpview(request, age, normalize=0): def agegroupcpview(request, age, normalize=0, userid=0):
script, div = interactive_agegroupcpchart(age, normalized=normalize) script, div = interactive_agegroupcpchart(age, normalized=normalize)
response = render(request, 'agegroupcp.html', response = render(request, 'agegroupcp.html',
+3 -3
View File
@@ -763,7 +763,7 @@ def workout_nkimport_view(request, userid=0, after=0, before=0):
messages.info( messages.info(
request, request,
'Your NK logbook workouts will be imported in the background.' 'Your NK logbook workouts will be imported in the background.'
' It may take a few minutes before it appears.') ' It may take a few minutes before they appear.')
url = reverse('workouts_view') url = reverse('workouts_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except KeyError: except KeyError:
@@ -1270,7 +1270,7 @@ def workout_stravaimport_view(request, message="", userid=0):
# done, redirect to workouts list # done, redirect to workouts list
messages.info(request, messages.info(request,
'Your Strava workouts will be imported in the background.' 'Your Strava workouts will be imported in the background.'
' It may take a few minutes before it appears.') ' It may take a few minutes before they appear.')
url = reverse('workouts_view') url = reverse('workouts_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except KeyError: # pragma: no cover except KeyError: # pragma: no cover
@@ -1815,7 +1815,7 @@ def workout_c2import_view(request, page=1, userid=0, message=""):
messages.info( messages.info(
request, request,
'Your Concept2 workouts will be imported in the background.' 'Your Concept2 workouts will be imported in the background.'
' It may take a few minutes before it appears.') ' It may take a few minutes before they appear.')
url = reverse('workouts_view') url = reverse('workouts_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except KeyError: # pragma: no cover except KeyError: # pragma: no cover
+1 -1
View File
@@ -811,7 +811,7 @@ def useractivate(request, uidb64, token): # pragma: no cover
['roosendaalsander@gmail.com']) ['roosendaalsander@gmail.com'])
messages.info( messages.info(
request, 'Thank you for your email confirmation. YOu are now signed in to your account.') request, 'Thank you for your email confirmation. You are now signed in to your account.')
login(request, user, backend=settings.AUTHENTICATION_BACKENDS[0]) login(request, user, backend=settings.AUTHENTICATION_BACKENDS[0])
url = reverse('workouts_view') url = reverse('workouts_view')
# if user.rower.rowerplan == 'freecoach': # if user.rower.rowerplan == 'freecoach':
+7 -4
View File
@@ -1235,11 +1235,14 @@ def iscoachmember(user):
r = Rower(user=user) r = Rower(user=user)
r.save() r.save()
result = user.is_authenticated and ('coach' in r.rowerplan) if user.is_authenticated:
else: # pragma: no cover if 'coach' in r.rowerplan:
result = False return True
elif r.coachtrialexpires >= timezone.now().date():
return True
return result
return False
# More User/Rower utils # More User/Rower utils
+2 -2
View File
@@ -5398,7 +5398,7 @@ def workout_upload_view(request,
# This is the main view for processing uploaded files # This is the main view for processing uploaded files
@user_passes_test(iscoachmember, login_url="/rowers/paidplans", redirect_field_name=None, @user_passes_test(iscoachmember, login_url="/rowers/paidplans", redirect_field_name=None,
message="This functionality requires a Coach plan or higher") message="This functionality requires a Coach plan or higher")
def team_workout_upload_view(request, message="", def team_workout_upload_view(request, userid=0, message="",
successmessage="", successmessage="",
uploadoptions={ uploadoptions={
'make_plot': False, 'make_plot': False,
@@ -5572,7 +5572,7 @@ def team_workout_upload_view(request, message="",
else: else:
form = DocumentsForm() form = DocumentsForm()
optionsform = TeamUploadOptionsForm(initial=uploadoptions) optionsform = TeamUploadOptionsForm(initial=uploadoptions)
rowerform = TeamInviteForm() rowerform = TeamInviteForm(userid=userid)
rowerform.fields.pop('email') rowerform.fields.pop('email')
rowers = Rower.objects.filter( rowers = Rower.objects.filter(