Private
Public Access
1
0

Merge branch 'release/v10.11'

This commit is contained in:
Sander Roosendaal
2019-08-19 21:09:37 +02:00
16 changed files with 972 additions and 3 deletions

172
rowers/alerts.py Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -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 <info@rowsandall.com>'
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):

View File

@@ -0,0 +1,72 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% block title %}Metric Alert{% endblock %}
{% block main %}
<h1>Alert Create</h1>
<p>
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.
</p>
<p>
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 "&gt;" (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).
</p>
<p>
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.
</p>
<form action="" method="post">
<ul class="main-content">
<li class="grid_2">
<h2>Alert</h2>
<p>
{{ formset.management_form }}
{% csrf_token %}
<table>
{{ form.as_table }}
{{ measuredform.as_table }}
</table>
<input type="submit" value="Save">
</p>
</li>
<li class="grid_2">
{% for filter_form in formset %}
<div class="fav-formset">
<h2>Filter {{ forloop.counter }}</h2>
<table width=100%>
{{ filter_form.as_table }}
</table>
</div>
{% endfor %}
</li>
</ul>
</form>
<!-- Include formset plugin - including jQuery dependency -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="/static/js/jquery.formset.js"></script>
<script>
$('.fav-formset').formset({
addText: '<div>&nbsp;</div><div>add filter</div>',
deleteText: '<div><p>&nbsp;</p></div><div>remove</div>'
});
</script>
{% endblock %}
{% block sidebar %}
{% include 'menu_analytics.html' %}
{% endblock %}

View File

@@ -0,0 +1,29 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% block title %}Planned Session{% endblock %}
{% block main %}
<h1>Confirm Delete</h1>
<p>This will permanently delete the alert</p>
<ul class="main-content">
<li class="grid_2">
<p>
<form action="" method="post">
{% csrf_token %}
<p>Are you sure you want to delete <em>{{ object }}</em>?</p>
<input class="button red" type="submit" value="Confirm">
</form>
</p>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_analytics.html' %}
{% endblock %}

View File

@@ -0,0 +1,70 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% block title %}Metric Alert{% endblock %}
{% block main %}
<p>
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.
</p>
<p>
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 "&gt;" (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).
</p>
<p>
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.
</p>
<form action="" method="post">
<ul class="main-content">
<li class="grid_2">
<h2>Alert</h2>
<p>
{{ formset.management_form }}
{% csrf_token %}
<table>
{{ form.as_table }}
{{ measuredform.as_table }}
</table>
<input type="submit" value="Save">
</p>
</li>
<li class="grid_2">
{% for filter_form in formset %}
<div class="fav-formset">
<h2>Filter {{ forloop.counter }}</h2>
<table width=100%>
{{ filter_form.as_table }}
</table>
</div>
{% endfor %}
</li>
</ul>
</form>
<!-- Include formset plugin - including jQuery dependency -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="/static/js/jquery.formset.js"></script>
<script>
$('.fav-formset').formset({
addText: '<div>&nbsp;</div><div>add filter</div>',
deleteText: '<div><p>&nbsp;</p></div><div>remove</div>'
});
</script>
{% endblock %}
{% block sidebar %}
{% include 'menu_analytics.html' %}
{% endblock %}

View File

@@ -0,0 +1,35 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% block title %}Metric Alert{% endblock %}
{% block main %}
<p>
<a href="/rowers/alerts/{{ alert.id }}/report/{{ nperiod|add:1 }}/user/{{ rower.user.id }}/">Previous</a>
{% if nperiod > 0 %}
<a href="/rowers/alerts/{{ alert.id }}/report/{{ nperiod|add:-1 }}/user/{{ rower.user.id }}/">Next</a>
{% endif %}
</p>
<ul class="main-content">
<li class="grid_2">
<h2>Alert</h2>
<p>{{ alert }}</p>
<p>This is a page under construction. Currently with minimal information</p>
</li>
{% for key, value in stats.items %}
<li>
<h2>{{ key }}</h2>
<p>{{ value }}</p>
</li>
{% endfor %}
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_analytics.html' %}
{% endblock %}

View File

@@ -0,0 +1,20 @@
{% extends "emailbase.html" %}
{% block body %}
<p>Dear <strong>{{ first_name }}</strong>,</p>
<p>
Here is the report for your alert on <a href="{{ siteurl }}">rowsandall.com</a>.
</p>
{% for key, value in report.items() %}
<p>
{{ key }}: {{ value }}
</p>
{% endfor %}
<p>
Best Regards, the Rowsandall Team
</p>
{% endblock %}

View File

@@ -0,0 +1,73 @@
{% extends "newbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Rowsandall - Analysis {% endblock %}
{% block main %}
<h1>Alerts for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
<p>Set up automatic alerting for your workouts</p>
<ul class="main-content">
{% if alerts %}
<li class="grid_4">
<table width="100%" class="listtable shortpadded">
<thead>
<tr>
<th>Name</th>
<th>metric</th>
<th>Workout type</th>
<th>Next Run</th>
</tr>
</thead>
<tbody>
{% for alert in alerts %}
<tr>
<td>{{ alert.name }}</td>
<td>{{ alert.measured.metric }}</td>
<td>{{ alert.workouttype }}</td>
<td>{{ alert.next_run }}</td>
<td>
<a class="small" href="/rowers/alerts/{{ alert.id }}/edit/" title="Edit">
<i class="fas fa-pencil-alt fa-fw"></i>
</a>
</td>
<td>
<a class="small"
href="/rowers/alerts/{{ alert.id }}/report/"
title="Report">
<i class="fal fa-table fa-fw"></i>
</a>
</td>
<td>
<a class="small" href="/rowers/alerts/{{ alert.id }}/delete/"
title="Delete">
<i class="fas fa-trash-alt fa-fw"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</li>
{% else %}
<li class="grid_4">
<p>You have not set any alerts for {{ rower.user.first_name }}</p>
</li>
{% endif %}
<li class="grid_4">
<p>
<a href="/rowers/alerts/new/">Create new alert</a>
</p>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_analytics.html' %}
{% endblock %}

View File

@@ -11,7 +11,9 @@
<p>Rower: {{ rower.user.first_name }}</p>
<a href="/rowers/user-analysis-select">Be adventurous and try our new Analysis page</a>
<p>
<a href="/rowers/alerts/">Try out Alerts</a>
</p>
{% endblock %}

Binary file not shown.

View File

@@ -417,6 +417,15 @@ urlpatterns = [
name='multi_compare_view'),
re_path(r'^multi-compare/workout/(?P<id>\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<userid>\d+)/$',views.alerts_view,name='alerts_view'),
re_path(r'^alerts/$',views.alerts_view,name='alerts_view'),
re_path(r'^alerts/(?P<pk>\d+)/delete/$',views.AlertDelete.as_view(),name='alert_delete_view'),
re_path(r'^alerts/(?P<id>\d+)/edit/user/(?P<userid>\d+)/$',views.alert_edit_view,name='alert_edit_view'),
re_path(r'^alerts/(?P<id>\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<id>\d+)/report/user/(?P<userid>\d+)/$',views.alert_report_view,name='alert_report_view'),
re_path(r'^alerts/(?P<id>\d+)/report/(?P<nperiod>\d+)/user/(?P<userid>\d+)/$',views.alert_report_view,name='alert_report_view'),
re_path(r'^alerts/(?P<id>\d+)/report/$',views.alert_report_view,name='alert_report_view'),
re_path(r'^user-boxplot/user/(?P<userid>\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'),

View File

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

View File

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