diff --git a/rowers/alerts.py b/rowers/alerts.py new file mode 100644 index 00000000..0865d07e --- /dev/null +++ b/rowers/alerts.py @@ -0,0 +1,172 @@ +from rowers.models import Alert, Condition, User, Rower, Workout +from rowers.teams import coach_getcoachees +from rowers.dataprep import getsmallrowdata_db,getrowdata_db +import datetime +## BASIC operations + +# create alert +def create_alert(manager, rower, measured,period=7, emailalert=True, + reststrokes=False, workouttype='water', + name='',**kwargs): + + # check if manager is coach of rower. If not return 0 + if manager.rower != rower: + if rower not in coach_getcoachees(manager.rower): + return 0,'You are not allowed to create this alert' + + m = Condition( + metric = measured['metric'], + value1 = measured['value1'], + value2 = measured['value2'], + condition=measured['condition'] + ) + + m.save() + + alert = Alert(name=name, + manager=manager, + rower=rower, + measured=m, + reststrokes=reststrokes, + period=period, + emailalert=emailalert, + workouttype=workouttype + ) + + alert.save() + + if 'filter' in kwargs: + filters = kwargs['filter'] + for f in filters: + if f['metric'] and f['condition']: + m = Condition( + metric = f['metric'], + value1 = f['value1'], + value2 = f['value2'], + condition = f['condition'] + ) + + m.save() + + alert.filter.add(m) + + + return alert.id,'Your alert was created' + + + +# update alert +def alert_add_filters(alert,filters): + for f in alert.filter.all(): + alert.filter.remove(f) + f.delete() + + for f in filters: + metric = f['metric'] + value1 = f['value1'] + value2 = f['value2'] + condition = f['condition'] + + if condition and metric and value1: + m = Condition( + metric = f['metric'], + value1 = f['value1'], + value2 = f['value2'], + condition = f['condition'] + ) + + m.save() + + alert.filter.add(m) + + return 1 + +# get alert stats +# nperiod = 0: current period, i.e. next_run - n days to today +# nperiod = 1: 1 period ago , i.e. next_run -2n days to next_run -n days +def alert_get_stats(alert,nperiod=0): + # get strokes + workstrokesonly = not alert.reststrokes + startdate = (alert.next_run - datetime.timedelta(days=(nperiod+1)*alert.period-1)) + enddate = alert.next_run - datetime.timedelta(days=(nperiod)*alert.period) + columns = [alert.measured.metric] + + for condition in alert.filter.all(): + columns += condition.metric + + workouts = Workout.objects.filter(date__gte=startdate,date__lte=enddate,user=alert.rower, + workouttype=alert.workouttype) + ids = [w.id for w in workouts] + + df = getsmallrowdata_db(columns,ids=ids,doclean=True,workstrokesonly=workstrokesonly) + + if df.empty: + return { + 'workouts':len(workouts), + 'startdate':startdate, + 'enddate':enddate, + 'nr_strokes':0, + 'nr_strokes_qualifying':0, + } + + # check if filters are in columns list + pdcolumns = set(df.columns) + + # drop strokes through filter + if set(columns) <= pdcolumns: + for condition in alert.filter.all(): + if condition.condition == '>': + mask = df[condition.metric] > condition.value1 + df.loc[mask,alert.measured.metric] = np.nan + elif condition.condition == '<': + mask = df[condition.metric] < condition.value1 + df.loc[mask,alert.measured.metric] = np.nan + elif condition.condition == 'between': + mask = df[condition.metric] > condition.value1 + mask2 = df[condition.metric] < condition.value2 + df.loc[mask & mask2,alert.measured.metric] = np.nan + elif condition.condition == '=': + mask = df[condition.metric] == condition.value1 + df.loc[mask,alert.measured.metric] = np.nan + + df.dropna(inplace=True,axis=0) + else: + return { + 'workouts':len(workouts), + 'startdate':startdate, + 'enddate':enddate, + 'nr_strokes':0, + 'nr_strokes_qualifying':0, + } + + + # count strokes + nr_strokes = len(df) + + + # count qualifying + if alert.measured.condition == '>': + mask = df[alert.measured.metric] > alert.measured.value1 + df2 = df[mask].copy() + elif alert.measured.condition == '<': + mask = df[alert.measured.metric] > alert.measured.value1 + df2 = df[mask].copy() + elif alert.measured.condition == 'between': + mask = df[alert.measured.metric] > alert.measured.value1 + mask2 = df[alert.measured.metric] < alert.measured.value2 + df2 = df[mask & mask2].copy() + else: + mask = df[alert.measured.metric] == alert.measured.value1 + df2 = df[mask].copy() + + nr_strokes_qualifying = len(df2) + + return { + 'workouts':len(workouts), + 'startdate':startdate, + 'enddate':enddate, + 'nr_strokes':nr_strokes, + 'nr_strokes_qualifying':nr_strokes_qualifying + } + +# run alert report diff --git a/rowers/management/commands/processalerts.py b/rowers/management/commands/processalerts.py new file mode 100644 index 00000000..b9efe371 --- /dev/null +++ b/rowers/management/commands/processalerts.py @@ -0,0 +1,62 @@ +#!/srv/venv/bin/python + +import sys +import os + +PY3K = sys.version_info >= (3,0) + +from django.core.management.base import BaseCommand +from rowers.models import Alert, Condition, User +from rowers.tasks import handle_send_email_alert + +from rowers import alerts + +from rowers.utils import myqueue + + +import django_rq +queue = django_rq.get_queue('default') +queuelow = django_rq.get_queue('low') +queuehigh = django_rq.get_queue('low') + + +import datetime + +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", + ) + + def handle(self, *args, **options): + if 'testing' in options: + testing = options['testing'] + else: + testing = False + + todaysalerts = Alert.objects.filter(next_run <= datetime.date.today(),emailalert=True) + + for alert in todaysalerts: + stats = alerts.alert_get_stats(alert) + + # send email + job = myqueue(queue,handle_send_email_alert, + alert.manager.email, + alert.manager.first_name, + alert.manager.last_name, + stats,debug=True) + + # advance next_run + if not testing: + alert.next_run = datetime.date.today() + datetime.timedelta(days=alert.period) + alert.save() + + if testing: + print('{nr} alerts found'.format(nr = len(todaysalerts))) + + self.stdout.write(self.style.SUCCESS( + 'Successfully processed alerts')) diff --git a/rowers/models.py b/rowers/models.py index 7b42bf24..9caf9810 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1011,6 +1011,83 @@ class BaseFavoriteFormSet(BaseFormSet): if not yparam2: yparam2 = 'None' + + +class Condition(models.Model): + conditionchoices = ( + ('<','<'), + ('>','>'), + ('=','='), + ('between','between') + ) + metric = models.CharField(max_length=50,choices=parchoicesy1,verbose_name='Metric') + value1 = models.FloatField(default=0) + value2 = models.FloatField(default=0,null=True,blank=True) + condition = models.CharField(max_length=20,choices=conditionchoices,null=True) + +class ConditionEditForm(ModelForm): + class Meta: + model = Condition + fields = ['metric','condition','value1','value2'] + + def clean(self): + cd = self.cleaned_data + if cd['condition'] == 'between' and cd['value2'] is None: + raise forms.ValidationError('When using between, you must fill value 1 and value 2') + +class BaseConditionFormSet(BaseFormSet): + def clean(self): + if any(self.errors): + return + + for form in self.forms: + if form.cleaned_data: + metric = form.cleaned_data['metric'] + condition = form.cleaned_data['condition'] + value1 = form.cleaned_data['value1'] + value2 = form.cleaned_data['value2'] + + + +rowchoices = [] +for key,value in mytypes.workouttypes: + if key in mytypes.rowtypes: + rowchoices.append((key,value)) + + + +class Alert(models.Model): + name = models.CharField(max_length=150,verbose_name='Alert Name',null=True,blank=True) + manager = models.ForeignKey(User, on_delete=models.CASCADE) + rower = models.ForeignKey(Rower, on_delete=models.CASCADE) + measured = models.OneToOneField(Condition,verbose_name='Measuring',on_delete=models.CASCADE, + related_name='measured') + filter = models.ManyToManyField(Condition, related_name='filters',verbose_name='Filters') + reststrokes = models.BooleanField(default=False,null=True,verbose_name='Include Rest Strokes') + period = models.IntegerField(default=7,verbose_name='Reporting Period (days)') + next_run = models.DateField(default=timezone.now) + emailalert = models.BooleanField(default=True,verbose_name='Send email alerts') + workouttype = models.CharField(choices=rowchoices,max_length=50, + verbose_name='Exercise/Boat Class',default='water') + + + def __str__(self): + stri = u'Alert {name} on {metric} for {workouttype}'.format( + name = self.name, + metric = self.measured.metric, + workouttype = self.workouttype + ) + + return stri + +class AlertEditForm(ModelForm): + class Meta: + model = Alert + fields = ['name','reststrokes','period','emailalert','workouttype'] + widgets = { + 'reststrokes':forms.CheckboxInput() + } + class BasePlannedSessionFormSet(BaseFormSet): def clean(self): if any(self.serrors): diff --git a/rowers/opaque.py b/rowers/opaque.py index 949572d9..0927df99 100644 --- a/rowers/opaque.py +++ b/rowers/opaque.py @@ -54,7 +54,7 @@ class OpaqueEncoder: def decode_hex(self, s): """Decode an 8-character hex string, returning the original integer.""" - return self.transcode(int(s, 16)) + return self.transcode(int(str(s), 16)) def decode_base64(self, s): """Decode a 6-character base64 string, returning the original integer.""" diff --git a/rowers/tasks.py b/rowers/tasks.py index 065ecca4..c6f8316c 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -756,6 +756,35 @@ def handle_updatedps(useremail, workoutids, debug=False,**kwargs): return 1 +@app.task +def handle_send_email_alert( + useremail, userfirstname, userlastname, stats, **kwargs): + + if 'debug' in kwargs: + debug = kwargs['debug'] + else: + debug = False + + subject = "Your rowing performance on rowsandall.com ({startdate} to {enddate})".format( + startdate = stats['startdate'], + enddate = stats['enddate'], + ) + + from_email = 'Rowsandall ' + + d = { + 'report':stats, + 'first_name':userfirstname, + 'last_name':userlastname, + 'siteurl':siteurl, + } + + res = send_template_email(from_email,[useremail],subject, + 'alertemail.html', + d,**kwargs) + + return 1 + @app.task def handle_send_email_transaction( username, useremail, amount, **kwargs): diff --git a/rowers/templates/alert_create.html b/rowers/templates/alert_create.html new file mode 100644 index 00000000..d7b1c57a --- /dev/null +++ b/rowers/templates/alert_create.html @@ -0,0 +1,72 @@ +{% extends "newbase.html" %} +{% load staticfiles %} + +{% block title %}Metric Alert{% endblock %} + +{% block main %} +

Alert Create

+ +

+ Alerts are useful to give you a regular update on how you are doing. For example, if you are + worried about rowing too short, you can set an alert on drive length, and the site will automatically + tell you how well you are doing. +

+ +

+ To set an alert on a minimum drive length, you would select "Drive Length (degree)" as the metric in the + form below, then set the condition to ">" (greater than), and value 1 to the minimum drive length + that you find acceptable. The value 2 is only relevant for alerts where you want to have a metric + between two values. Set the workout type to "Standard Racing Shell", or whatever boat class you + want this metric to run for, select the period over which you want to monitor and get regular + reports (7 days). +

+ +

+ Optionally, you can add filters. With filters, the alert considers only those strokes that + fulfill all filters. For example, you could set a filter on power between 200 and 300 Watt, + to only look at drive length in that power zone. +

+ +
+ +
+ + + + + + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_analytics.html' %} +{% endblock %} diff --git a/rowers/templates/alert_delete_confirm.html b/rowers/templates/alert_delete_confirm.html new file mode 100644 index 00000000..dc9a5417 --- /dev/null +++ b/rowers/templates/alert_delete_confirm.html @@ -0,0 +1,29 @@ +{% extends "newbase.html" %} +{% load staticfiles %} + +{% block title %}Planned Session{% endblock %} + +{% block main %} +

Confirm Delete

+

This will permanently delete the alert

+ + + + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_analytics.html' %} +{% endblock %} diff --git a/rowers/templates/alert_edit.html b/rowers/templates/alert_edit.html new file mode 100644 index 00000000..081d9a36 --- /dev/null +++ b/rowers/templates/alert_edit.html @@ -0,0 +1,70 @@ +{% extends "newbase.html" %} +{% load staticfiles %} + +{% block title %}Metric Alert{% endblock %} + +{% block main %} +

+ Alerts are useful to give you a regular update on how you are doing. For example, if you are + worried about rowing too short, you can set an alert on drive length, and the site will automatically + tell you how well you are doing. +

+ +

+ To set an alert on a minimum drive length, you would select "Drive Length (degree)" as the metric in the + form below, then set the condition to ">" (greater than), and value 1 to the minimum drive length + that you find acceptable. The value 2 is only relevant for alerts where you want to have a metric + between two values. Set the workout type to "Standard Racing Shell", or whatever boat class you + want this metric to run for, select the period over which you want to monitor and get regular + reports (7 days). +

+ +

+ Optionally, you can add filters. With filters, the alert considers only those strokes that + fulfill all filters. For example, you could set a filter on power between 200 and 300 Watt, + to only look at drive length in that power zone. +

+ +
+ +
+ + + + + + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_analytics.html' %} +{% endblock %} diff --git a/rowers/templates/alert_stats.html b/rowers/templates/alert_stats.html new file mode 100644 index 00000000..713716f9 --- /dev/null +++ b/rowers/templates/alert_stats.html @@ -0,0 +1,35 @@ +{% extends "newbase.html" %} +{% load staticfiles %} + +{% block title %}Metric Alert{% endblock %} + +{% block main %} + +

+ Previous + {% if nperiod > 0 %} + Next + {% endif %} +

+ + + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_analytics.html' %} +{% endblock %} diff --git a/rowers/templates/alertemail.html b/rowers/templates/alertemail.html new file mode 100644 index 00000000..bd3df931 --- /dev/null +++ b/rowers/templates/alertemail.html @@ -0,0 +1,20 @@ +{% extends "emailbase.html" %} + +{% block body %} +

Dear {{ first_name }},

+ +

+ Here is the report for your alert on rowsandall.com. +

+ +{% for key, value in report.items() %} +

+ {{ key }}: {{ value }} +

+{% endfor %} + +

+ Best Regards, the Rowsandall Team +

+{% endblock %} + diff --git a/rowers/templates/alerts.html b/rowers/templates/alerts.html new file mode 100644 index 00000000..0ea4db1c --- /dev/null +++ b/rowers/templates/alerts.html @@ -0,0 +1,73 @@ +{% extends "newbase.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Rowsandall - Analysis {% endblock %} + +{% block main %} + +

Alerts for {{ rower.user.first_name }} {{ rower.user.last_name }}

+

Set up automatic alerting for your workouts

+ + + + + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_analytics.html' %} +{% endblock %} diff --git a/rowers/templates/laboratory.html b/rowers/templates/laboratory.html index bc9021f7..41fe9cb0 100644 --- a/rowers/templates/laboratory.html +++ b/rowers/templates/laboratory.html @@ -11,7 +11,9 @@

Rower: {{ rower.user.first_name }}

-Be adventurous and try our new Analysis page +

+ Try out Alerts +

{% endblock %} diff --git a/rowers/tests/testdata/testdata.csv.gz b/rowers/tests/testdata/testdata.csv.gz index 9676956f..5625c2c1 100644 Binary files a/rowers/tests/testdata/testdata.csv.gz and b/rowers/tests/testdata/testdata.csv.gz differ diff --git a/rowers/urls.py b/rowers/urls.py index 02d7b112..6e0c64ec 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -417,6 +417,15 @@ urlpatterns = [ name='multi_compare_view'), re_path(r'^multi-compare/workout/(?P\b[0-9A-Fa-f]+\b)/$',views.multi_compare_view,name='multi_compare_view'), re_path(r'^multi-compare/$',views.multi_compare_view,name='multi_compare_view'), + re_path(r'^alerts/(?P\d+)/$',views.alerts_view,name='alerts_view'), + re_path(r'^alerts/$',views.alerts_view,name='alerts_view'), + re_path(r'^alerts/(?P\d+)/delete/$',views.AlertDelete.as_view(),name='alert_delete_view'), + re_path(r'^alerts/(?P\d+)/edit/user/(?P\d+)/$',views.alert_edit_view,name='alert_edit_view'), + re_path(r'^alerts/(?P\d+)/edit/$',views.alert_edit_view,name='alert_edit_view'), + re_path(r'^alerts/new/$',views.alert_create_view, name='alert_create_view'), + re_path(r'^alerts/(?P\d+)/report/user/(?P\d+)/$',views.alert_report_view,name='alert_report_view'), + re_path(r'^alerts/(?P\d+)/report/(?P\d+)/user/(?P\d+)/$',views.alert_report_view,name='alert_report_view'), + re_path(r'^alerts/(?P\d+)/report/$',views.alert_report_view,name='alert_report_view'), re_path(r'^user-boxplot/user/(?P\d+)/$',views.boxplot_view,name='boxplot_view'), re_path(r'^user-boxplot/$',views.boxplot_view,name='boxplot_view'), re_path(r'^user-boxplot-data/$',views.boxplot_view_data,name='boxplot_view_data'), diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 07c4d8a5..546fb652 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -4314,3 +4314,319 @@ def agegrouprecordview(request,sex='male',weightcategory='hwt', 'active':'nav-analysis', 'the_div':div, }) + +# alert overview view +@user_passes_test(ispromember, login_url="/rowers/paidplans", + message="This functionality requires a Pro plan or higher", + redirect_field_name=None) +def alerts_view(request,userid=0): + r = getrequestrower(request,userid=userid) + + alerts = Alert.objects.filter(rower=r).order_by('next_run') + + breadcrumbs = [ + { + 'url':'/rowers/analysis', + 'name': 'Analysis' + }, + { + 'url': reverse('alerts_view'), + 'name': 'Alerts', + }, + ] + + return render(request,'alerts.html', + { + 'breadcrumbs':breadcrumbs, + 'alerts':alerts, + 'rower':r, + }) + +# alert create view +@user_passes_test(ispromember, login_url="/rowers/paidplans", + message="This functionality requires a Pro plan or higher", + redirect_field_name=None) +def alert_create_view(request,userid=0): + r = getrequestrower(request,userid=userid) + FilterFormSet = formset_factory(ConditionEditForm, formset=BaseConditionFormSet,extra=1) + filter_formset = FilterFormSet() + + if request.method == 'POST': + form = AlertEditForm(request.POST) + measuredform = ConditionEditForm(request.POST) + filter_formset = FilterFormSet(request.POST) + if form.is_valid() and measuredform.is_valid() and filter_formset.is_valid(): + ad = form.cleaned_data + measured = measuredform.cleaned_data + + period = ad['period'] + emailalert = ad['emailalert'] + reststrokes = ad['reststrokes'] + workouttype = ad['workouttype'] + name = ad['name'] + + filters = [] + + for filter_form in filter_formset: + metric = filter_form.cleaned_data.get('metric') + condition = filter_form.cleaned_data.get('condition') + value1 = filter_form.cleaned_data.get('value1') + value2 = filter_form.cleaned_data.get('value2') + + filters.append( + { + 'metric':metric, + 'condition':condition, + 'value1':value1, + 'value2':value2, + } + ) + + result,message = create_alert(request.user,r,measured,period=period,emailalert=emailalert, + reststrokes=reststrokes,workouttype=workouttype, + filter = filters, + name=name) + + if result: + messages.info(request,message) + + url = reverse('alert_edit_view',kwargs={'id':result}) + return HttpResponseRedirect(url) + else: + form = AlertEditForm() + measuredform = ConditionEditForm() + + breadcrumbs = [ + { + 'url':'/rowers/analysis', + 'name': 'Analysis' + }, + { + 'url': reverse('alerts_view'), + 'name': 'Alerts', + }, + { + 'url': reverse('alert_create_view'), + 'name': 'Create' + } + ] + + return render(request,'alert_create.html', + { + 'breadcrumbs':breadcrumbs, + 'formset': filter_formset, + 'rower':r, + 'form':form, + 'measuredform':measuredform, + }) + +# alert report view +@user_passes_test(ispromember, login_url="/rowers/paidplans", + message="This functionality requires a Pro plan or higher", + redirect_field_name=None) +def alert_report_view(request,id=0,userid=0,nperiod=0): + r = getrequestrower(request,userid=userid) + if userid == 0: + userid = request.user.id + + alert = Alert.objects.get(id=id) + nperiod = int(nperiod) + + try: + alert = Alert.objects.get(id=id) + except Alert.DoesNotExist: + raise Http404("This alert doesn't exist") + + + if alert.manager != request.user: + raise PermissionDenied('You are not allowed to edit this Alert') + + stats = alert_get_stats(alert,nperiod=nperiod) + + breadcrumbs = [ + { + 'url':'/rowers/analysis', + 'name': 'Analysis' + }, + { + 'url':reverse('alerts_view'), + 'name':'Alerts', + }, + { + 'url': reverse('alert_edit_view', + kwargs={'userid':userid,'id':alert.id}), + 'name': alert.name, + }, + { + 'url': reverse('alert_report_view', + kwargs={'userid':userid,'id':alert.id}), + 'name': 'Report', + }, + ] + return render(request,'alert_stats.html', + { + 'breadcrumbs':breadcrumbs, + 'stats':stats, + 'rower':r, + 'alert':alert, + 'nperiod':nperiod, + }) + +# alert edit view +@user_passes_test(ispromember, login_url="/rowers/paidplans", + message="This functionality requires a Pro plan or higher", + redirect_field_name=None) +def alert_edit_view(request,id=0,userid=0): + r = getrequestrower(request,userid=userid) + + try: + alert = Alert.objects.get(id=id) + except Alert.DoesNotExist: + raise Http404("This alert doesn't exist") + + + if alert.manager != request.user: + raise PermissionDenied('You are not allowed to edit this Alert') + + FilterFormSet = formset_factory(ConditionEditForm, formset=BaseConditionFormSet,extra=0) + if len(alert.filter.all()) == 0: + FilterFormSet = formset_factory(ConditionEditForm, formset=BaseConditionFormSet, extra=1) + + filter_data = [{'metric':m.metric, + 'value1':m.value1, + 'value2':m.value2, + 'condition':m.condition} + for m in alert.filter.all()] + + if request.method == 'POST': + form = AlertEditForm(request.POST) + measuredform = ConditionEditForm(request.POST) + filter_formset = FilterFormSet(request.POST) + if form.is_valid() and measuredform.is_valid() and filter_formset.is_valid(): + ad = form.cleaned_data + measured = measuredform.cleaned_data + + period = ad['period'] + emailalert = ad['emailalert'] + reststrokes = ad['reststrokes'] + workouttype = ad['workouttype'] + name = ad['name'] + + m = alert.measured + m.metric = measured['metric'] + m.value1 = measured['value1'] + m.value2 = measured['value2'] + m.condition = measured['condition'] + m.save() + + alert.period = period + alert.emailalert = emailalert + alert.reststrokes = reststrokes + alert.workouttype = workouttype + alert.name = name + alert.save() + + filters = [] + + for filter_form in filter_formset: + metric = filter_form.cleaned_data.get('metric') + condition = filter_form.cleaned_data.get('condition') + value1 = filter_form.cleaned_data.get('value1') + value2 = filter_form.cleaned_data.get('value2') + + filters.append( + { + 'metric':metric, + 'condition':condition, + 'value1':value1, + 'value2':value2, + } + ) + + res = alert_add_filters(alert, filters) + messages.info(request,'Alert was changed') + + else: + form = AlertEditForm(instance=alert) + measuredform = ConditionEditForm(instance=alert.measured) + filter_formset = FilterFormSet(initial=filter_data) + + + + breadcrumbs = [ + { + 'url':'/rowers/analysis', + 'name': 'Analysis' + }, + { + 'url':reverse('alerts_view'), + 'name':'Alerts', + }, + { + 'url': reverse('alert_edit_view', + kwargs={'userid':userid,'id':alert.id}), + 'name': alert.name, + }, + ] + + + return render(request,'alert_edit.html', + { + 'breadcrumbs':breadcrumbs, + 'rower':r, + 'form':form, + 'measuredform':measuredform, + 'formset':filter_formset, + }) + +# alert delete view +class AlertDelete(DeleteView): + login_requird = True + model = Alert + template_name = 'alert_delete_confirm.html' + + # extra parameters + def get_context_data(self, **kwargs): + context = super(AlertDelete, self).get_context_data(**kwargs) + + if 'userid' in kwargs: + userid = kwargs['userid'] + else: + userid = 0 + + context['rower'] = getrequestrower(self.request,userid=userid) + context['alert'] = self.object + + breadcrumbs = [ + { + 'url':'/rowers/analysis', + 'name': 'Analysis' + }, + { + 'url':reverse('alerts_view'), + 'name':'Alerts', + }, + { + 'url': reverse('alert_edit_view', + kwargs={'userid':userid,'id':self.object.pk}), + 'name': 'Alert', + }, + { + 'url': reverse('alert_delete_view',kwargs={'pk':self.object.pk}), + 'name': 'Delete' + } + ] + + context['breadcrumbs'] = breadcrumbs + + return context + + def get_success_url(self): + return reverse('alerts_view') + + def get_object(self, *args, **kwargs): + obj = super(AlertDelete, self).get_object(*args, **kwargs) + + # some checks + + return obj diff --git a/rowers/views/statements.py b/rowers/views/statements.py index b3a0745f..06c33325 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -94,6 +94,7 @@ from rowers.models import ( microcyclecheckdates,mesocyclecheckdates,macrocyclecheckdates, TrainingMesoCycleForm, TrainingMicroCycleForm, RaceLogo,RowerBillingAddressForm,PaidPlan, + AlertEditForm, ConditionEditForm, PlannedSessionComment,CoachRequest,CoachOffer,checkaccessplanuser ) from rowers.models import ( @@ -108,10 +109,11 @@ from rowers.models import ( VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm, IndoorVirtualRaceResultForm,IndoorVirtualRaceResult, IndoorVirtualRaceForm,PlannedSessionCommentForm, + Alert, Condition ) from rowers.models import ( FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet, - get_course_timezone + get_course_timezone,BaseConditionFormSet, ) from rowers.metrics import rowingmetrics,defaultfavoritecharts,nometrics from rowers import metrics as metrics @@ -206,6 +208,7 @@ import numpy as np import matplotlib.pyplot as plt from rowers.emails import send_template_email,htmlstrip +from rowers.alerts import * from pytz import timezone as tz,utc from timezonefinder import TimezoneFinder