diff --git a/rowers/dataprep.py b/rowers/dataprep.py index bfd4921a..2e1ea17e 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -217,6 +217,9 @@ def workout_goldmedalstandard(workout, reset=False): def check_marker(workout): r = workout.user + if workout.workoutsource == 'strava': + return None + gmstandard, gmseconds = workout_goldmedalstandard(workout) if gmseconds < 60: return None diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 4e4d4c74..2f0d2a5d 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -550,7 +550,7 @@ def goldmedalscorechart(user, startdate=None, enddate=None): workouts = Workout.objects.filter(user=user.rower, date__gte=startdate, date__lte=enddate, workouttype__in=mytypes.rowtypes, - duplicate=False).order_by('date') + duplicate=False).order_by('date').exclude(workoutsource='strava') markerworkouts = workouts.filter(rankingpiece=True) outids = [w.id for w in markerworkouts] diff --git a/rowers/models.py b/rowers/models.py index 5ddc51dc..7c196450 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1423,9 +1423,26 @@ parchoicesy1 = list(sorted(favchartlabelsy1.items(), key=lambda x: x[1])) parchoicesy2 = list(sorted(favchartlabelsy2.items(), key=lambda x: x[1])) parchoicesx = list(sorted(favchartlabelsx.items(), key=lambda x: x[1])) +# special filter for workouts to exclude strava workouts by default +class WorkoutQuerySet(models.QuerySet): + def filter(self, *args, exclude_strava=True, **kwargs): + queryset = super().filter(*args, **kwargs) + if exclude_strava: + queryset = queryset.exclude(workoutsource='strava') + + return queryset + + def get(self, *args, **kwargs): + queryset = self + + return super().get(*args, **kwargs) + + +class WorkoutManager(models.Manager): + def get_queryset(self): + return WorkoutQuerySet(self.model, using=self._db) + # Saving a chart as a favorite chart - - class FavoriteChart(models.Model): workouttypechoices = [ ('ote', 'Erg/SkiErg'), @@ -3704,6 +3721,9 @@ class Workout(models.Model): default=False, verbose_name='Duplicate Workout') impeller = models.BooleanField(default=False, verbose_name='Impeller') + # attach the WorkoutManager + #objects = WorkoutManager() + def url(self): str = '/rowers/workout/{id}/'.format( id=encoder.encode_hex(self.id) diff --git a/rowers/rower_rules.py b/rowers/rower_rules.py index 05a16994..ed5cd2dd 100644 --- a/rowers/rower_rules.py +++ b/rowers/rower_rules.py @@ -451,6 +451,11 @@ def is_workout_user(user, workout): except AttributeError: # pragma: no cover return False + if workout.privacy == 'hidden': + return user == workout.user.user + if workout.workoutsource == 'strava': + return user == workout.user.user + if workout.user == r: return True @@ -469,6 +474,11 @@ def is_workout_team(user, workout): except AttributeError: # pragma: no cover return False + if workout.privacy == 'hidden': + return user == workout.user.user + if workout.workoutsource == 'strava': + return user == workout.user.user + if workout.user == r: return True diff --git a/rowers/tests/test_api.py b/rowers/tests/test_api.py index 8563eb11..8bd91a55 100644 --- a/rowers/tests/test_api.py +++ b/rowers/tests/test_api.py @@ -102,13 +102,17 @@ class StravaPrivacy(TestCase): self.user_workouts = WorkoutFactory.create_batch(5, user=self.r) for w in self.user_workouts: - if w.id <= 3: + if w.id <= 2: w.workoutsource = 'strava' w.privacy = 'hidden' + elif w.id == 3: # user can change privacy but cannot change workoutsource + w.workoutsource = 'strava' + w.privacy = 'visible' else: w.workoutsource = 'concept2' w.privacy = 'visible' w.team.add(self.team) + w.csvfilename = get_random_file(filename='rowers/tests/testdata/thyro.csv')['filename'] w.save() # r2 coaches r @@ -116,6 +120,13 @@ class StravaPrivacy(TestCase): self.factory = APIRequestFactory() + def tearDown(self): + for workout in self.user_workouts: + try: + os.remove(workout.csvfilename) + except (OSError, FileNotFoundError, IOError): + pass + # Test if workout with workoutsource strava and privacy hidden can be seen by coach def test_privacy_coach(self): login = self.c.login(username=self.u2.username, password=self.password2) @@ -126,6 +137,16 @@ class StravaPrivacy(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,403) + # Same test as above but for 'workout_edit_view' + def test_privacy_coach_edit(self): + login = self.c.login(username=self.u2.username, password=self.password2) + self.assertTrue(login) + + w = self.user_workouts[0] + url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) + response = self.c.get(url) + self.assertEqual(response.status_code,403) + # Test if workout with workoutsource strava and privacy hidden can be seen by team member def test_privacy_member(self): login = self.c.login(username=self.u3.username, password=self.password3) @@ -136,6 +157,16 @@ class StravaPrivacy(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,403) + # Same test as above but for 'workout_edit_view' + def test_privacy_member_edit(self): + login = self.c.login(username=self.u3.username, password=self.password3) + self.assertTrue(login) + + w = self.user_workouts[0] + url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) + response = self.c.get(url) + self.assertEqual(response.status_code,403) + # same test as above but with user r and the response code should be 200 def test_privacy_owner(self): login = self.c.login(username=self.u.username, password=self.password) @@ -146,6 +177,16 @@ class StravaPrivacy(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,200) + # same test as above but for 'workout_edit_view' + def test_privacy_owner_edit(self): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + w = self.user_workouts[0] + url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + # test if list_workouts returns all workouts for user r @@ -168,6 +209,7 @@ class StravaPrivacy(TestCase): 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) self.assertEqual(len(workouts),5) + # same test as above but list_workouts with team id = self.team.id def test_list_workouts_team(self): @@ -211,6 +253,27 @@ class StravaPrivacy(TestCase): self.assertEqual(len(workouts),2) + # same test as above but with without the teamid kwarg but with a rowerid=self.r.id + def test_list_workouts_team_coach2(self): + login = self.c.login(username=self.u2.username, password=self.password2) + self.assertTrue(login) + + url = reverse('workouts_view',kwargs={'rowerid':self.r.id}) + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + # the response.content is html, so we need to parse it + soup = BeautifulSoup(response.content, 'html.parser') + # the workouts look like ... and there should be 5 unique ids + # the id is a hex string + workouts = set([a['href'].split('/')[3] for a in soup.find_all('a') if a['href'].startswith('/rowers/workout/')]) + + # throw out "c2import", "nkimport", "stravaimport", "concept2import", "sporttracksimport" from the set + workouts = set([w for w in workouts if w not in [ + 'upload', 'addmanual', 'c2import', 'polarimport', 'rp3import', 'nkimport', 'stravaimport', 'concept2import', 'sporttracksimport']]) + + self.assertEqual(len(workouts),2) + # same test as the previous one but with self.r3 and the number of workouts found should 0 def test_list_workouts_team_member(self): login = self.c.login(username=self.u3.username, password=self.password3) @@ -307,8 +370,9 @@ class StravaPrivacy(TestCase): # test some analysis, should only use the workouts with workoutsource != strava - @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) - def test_workouts_analysis(self, mocked_read_data): + #@patch('rowers.dataprep.read_data', side_effect=mocked_read_data) + #def test_workouts_analysis(self, mocked_read_data): + def test_workouts_analysis(self): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -388,7 +452,7 @@ class StravaPrivacy(TestCase): self.assertTrue(data.startswith('data = [')) self.assertTrue(data.endswith(']')) # count the number of commas between the brackets - self.assertEqual(data.count(','),1377) + self.assertEqual(data.count(','),2062) diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index b3368972..c11ef6c0 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -199,14 +199,14 @@ def analysis_new(request, startdatetime__lte=enddate, workouttype__in=modalities, rankingpiece__in=rankingtypes, - ) + ).exclude(workoutsource='strava') elif theteam is not None and theteam.viewing == 'coachonly': # pragma: no cover workouts = Workout.objects.filter(team=theteam, user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, workouttype__in=modalities, rankingpiece__in=rankingtypes, - ) + ).exclude(workoutsource='strava') elif thesession is not None: workouts = get_workouts_session(r, thesession) else: @@ -363,6 +363,7 @@ def trendflexdata(workouts, options, userid=0): savedata = options.get('savedata',False) + workouts = workouts.exclude(workoutsource='strava') fieldlist, fielddict = dataprep.getstatsfields() fieldlist = [xparam, yparam, groupby, @@ -566,6 +567,8 @@ def flexalldata(workouts, options): trendline = options['trendline'] promember = True + workouts = workouts.exclude(workoutsource='strava') + workstrokesonly = not includereststrokes userid = options['userid'] @@ -612,6 +615,9 @@ def histodata(workouts, options): workmax = options['workmax'] userid = options['userid'] + workouts = workouts.exclude(workoutsource='strava') + + if userid == 0: # pragma: no cover extratitle = '' else: @@ -645,7 +651,8 @@ def cpdata(workouts, options): u = User.objects.get(id=userid) r = u.rower - + + delta, cpvalue, avgpower, workoutnames, urls = dataprep.fetchcp_new( r, workouts) @@ -798,6 +805,8 @@ def cpdata(workouts, options): def statsdata(workouts, options): + workouts = workouts.exclude(workoutsource='strava') + includereststrokes = options['includereststrokes'] ids = options['ids'] @@ -872,12 +881,13 @@ def statsdata(workouts, options): def comparisondata(workouts, options): + workouts = workouts.exclude(workoutsource='strava') includereststrokes = options['includereststrokes'] xparam = options['xaxis'] yparam1 = options['yaxis1'] plottype = options['plottype'] promember = True - + workstrokesonly = not includereststrokes ids = [w.id for w in workouts] @@ -915,6 +925,7 @@ def comparisondata(workouts, options): def boxplotdata(workouts, options): + workouts = workouts.exclude(workoutsource='strava') includereststrokes = options['includereststrokes'] spmmin = options['spmmin'] @@ -926,7 +937,7 @@ def boxplotdata(workouts, options): plotfield = options['plotfield'] workstrokesonly = not includereststrokes - + datemapping = { w.id: w.date for w in workouts } @@ -1020,11 +1031,14 @@ def analysis_view_data(request, userid=0): for id in ids: try: - workouts.append(Workout.objects.get(id=id)) + w = Workout.objects.get(id=id) + if w.workoutsource != 'strava': + workouts.append(w) except Workout.DoesNotExist: # pragma: no cover pass + if function == 'boxplot': script, div = boxplotdata(workouts, options) elif function == 'trendflex': # pragma: no cover @@ -1069,7 +1083,7 @@ def create_marker_workouts_view(request, userid=0, workouts = Workout.objects.filter(user=theuser.rower, date__gte=startdate, date__lte=enddate, workouttype__in=mytypes.rowtypes, - duplicate=False).order_by('date') + duplicate=False).order_by('date').exclude(workoutsource='strava') for workout in workouts: _ = dataprep.check_marker(workout) @@ -1113,7 +1127,7 @@ def goldmedalscores_view(request, userid=0, theuser, startdate=startdate, enddate=enddate, ) - bestworkouts = Workout.objects.filter(id__in=ids).order_by('-date') + bestworkouts = Workout.objects.filter(id__in=ids).order_by('-date').exclude(workoutsource='strava') breadcrumbs = [ { @@ -1311,7 +1325,7 @@ def performancemanager_view(request, userid=0, mode='rower', user = therower, date__gte=startdate-datetime.timedelta(days=90), date__lte=enddate, duplicate=False, - rankingpiece=True, workouttype__in=mytypes.rowtypes).order_by('date') + rankingpiece=True, workouttype__in=mytypes.rowtypes).order_by('date').exclude(workoutsource='strava') ids = [w.id for w in markerworkouts] form = PerformanceManagerForm(initial={ @@ -1323,7 +1337,7 @@ def performancemanager_view(request, userid=0, mode='rower', ids = pd.Series(ids, dtype='int').dropna().values - bestworkouts = Workout.objects.filter(id__in=ids).order_by('-date') + bestworkouts = Workout.objects.filter(id__in=ids).order_by('-date').exclude(workoutsource='strava') breadcrumbs = [ { diff --git a/rowers/views/statements.py b/rowers/views/statements.py index d134013d..eeb04039 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -28,6 +28,7 @@ from rest_framework.response import Response from rq.job import Job from rules.contrib.views import permission_required, objectgetter from django.core.cache import cache +from django.db import models from django.utils.crypto import get_random_string from rq.registry import StartedJobRegistry from rq.exceptions import NoSuchJobError diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 063e4751..67a655af 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -2204,25 +2204,25 @@ def workouts_view(request, message='', successmessage='', team=theteam, startdatetime__gte=startdate, startdatetime__lte=enddate, - privacy='visible').order_by("-date", "-starttime") + privacy='visible').order_by("-date", "-starttime").exclude(workoutsource='strava') g_workouts = Workout.objects.filter( team=theteam, startdatetime__gte=activity_startdate, startdatetime__lte=activity_enddate, duplicate=False, - privacy='visible').order_by("-date", "-starttime") + privacy='visible').order_by("-date", "-starttime").exclude(workoutsource='strava') elif theteam.viewing == 'coachonly': # pragma: no cover workouts = Workout.objects.filter( team=theteam, user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, - privacy='visible').order_by("-startdatetime") + privacy='visible').order_by("-startdatetime").exclude(workoutsource='strava') g_workouts = Workout.objects.filter( team=theteam, user=r, startdatetime__gte=activity_startdate, startdatetime__lte=activity_enddate, duplicate=False, - privacy='visible').order_by("-startdatetime") + privacy='visible').order_by("-startdatetime").exclude(workoutsource='strava') elif request.user != r.user: theteam = None @@ -2230,13 +2230,13 @@ def workouts_view(request, message='', successmessage='', user=r, startdatetime__gte=startdate, startdatetime__lte=enddate, - privacy='visible').order_by("-date", "-starttime") + privacy='visible').order_by("-date", "-starttime").exclude(workoutsource='strava') g_workouts = Workout.objects.filter( user=r, startdatetime__gte=activity_startdate, startdatetime__lte=activity_enddate, duplicate=False, - privacy='visible').order_by("-startdatetime") + privacy='visible').order_by("-startdatetime").exclude(workoutsource='strava') else: theteam = None workouts = Workout.objects.filter( @@ -2252,7 +2252,7 @@ def workouts_view(request, message='', successmessage='', if g_workouts.count() == 0: g_workouts = Workout.objects.filter( user=r, - startdatetime__gte=timezone.now()-timedelta(days=15)).order_by("-startdatetime") + startdatetime__gte=timezone.now()-timedelta(days=15)).order_by("-startdatetime").exclude(workoutsource='strava') g_enddate = timezone.now() g_startdate = (timezone.now()-timedelta(days=15)) @@ -2266,7 +2266,8 @@ def workouts_view(request, message='', successmessage='', reduce(operator.and_, (Q(name__icontains=q) for q in query_list)) | reduce(operator.and_, - (Q(notes__icontains=q) for q in query_list)) + (Q(notes__icontains=q) for q in query_list)), + exclude_strava=False, ) searchform = SearchForm(initial={'q': query}) else: