adding functionality to comment on planned sessions and races
This commit is contained in:
@@ -3343,3 +3343,28 @@ class WorkoutCommentForm(ModelForm):
|
|||||||
'comment': forms.Textarea,
|
'comment': forms.Textarea,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# A comment by a user on a training
|
||||||
|
class PlannedSessionComment(models.Model):
|
||||||
|
comment = models.TextField(max_length=300)
|
||||||
|
created = models.DateTimeField(default=timezone.now)
|
||||||
|
read = models.BooleanField(default=False)
|
||||||
|
notification = models.BooleanField(default=True,verbose_name="Subscribe to new comment notifications")
|
||||||
|
user = models.ForeignKey(User)
|
||||||
|
plannedsession = models.ForeignKey(PlannedSession)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'Comment to: {w} by {u1} {u2}'.format(
|
||||||
|
w=self.workout,
|
||||||
|
u1 = self.user.first_name,
|
||||||
|
u2 = self.user.last_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlannedSessionCommentForm(ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = PlannedSessionComment
|
||||||
|
fields = ['comment','notification']
|
||||||
|
widgets = {
|
||||||
|
'comment': forms.Textarea,
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1737,7 +1737,7 @@ def handle_sendemailnewresponse(first_name, last_name,
|
|||||||
debug=False,**kwargs):
|
debug=False,**kwargs):
|
||||||
fullemail = email
|
fullemail = email
|
||||||
from_email = 'Rowsandall <info@rowsandall.com>'
|
from_email = 'Rowsandall <info@rowsandall.com>'
|
||||||
subject = 'New comment on workout ' + workoutname
|
subject = 'New comment on session ' + workoutname
|
||||||
|
|
||||||
comment = u''+comment
|
comment = u''+comment
|
||||||
|
|
||||||
@@ -1745,6 +1745,13 @@ def handle_sendemailnewresponse(first_name, last_name,
|
|||||||
if debug:
|
if debug:
|
||||||
siteurl = SITE_URL_DEV
|
siteurl = SITE_URL_DEV
|
||||||
|
|
||||||
|
sessiontype = 'workout'
|
||||||
|
if 'sessiontype' in kwargs:
|
||||||
|
sessiontype=kwargs.pop('sessiontype')
|
||||||
|
|
||||||
|
commentlink = '/rowers/workout/{workoutid}/comment/'.format(workoutid=workoutid)
|
||||||
|
if 'commentlink' in kwargs:
|
||||||
|
commentlink = kwargs.pop('commentlink')
|
||||||
|
|
||||||
d = {
|
d = {
|
||||||
'first_name':first_name,
|
'first_name':first_name,
|
||||||
@@ -1754,10 +1761,14 @@ def handle_sendemailnewresponse(first_name, last_name,
|
|||||||
'workoutname':workoutname,
|
'workoutname':workoutname,
|
||||||
'siteurl':siteurl,
|
'siteurl':siteurl,
|
||||||
'workoutid':workoutid,
|
'workoutid':workoutid,
|
||||||
'commentid':commentid
|
'commentid':commentid,
|
||||||
|
'sessiontype':sessiontype,
|
||||||
|
'commentlink':commentlink,
|
||||||
}
|
}
|
||||||
|
|
||||||
res = send_template_email(from_email,[fullemail],subject,'teamresponseemail.html',d,**kwargs)
|
res = send_template_email(from_email,
|
||||||
|
[fullemail],
|
||||||
|
subject,'teamresponseemail.html',d,**kwargs)
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
@@ -1776,7 +1787,7 @@ def handle_sendemailnewcomment(first_name,
|
|||||||
|
|
||||||
fullemail = email
|
fullemail = email
|
||||||
from_email = 'Rowsandall <info@rowsandall.com>'
|
from_email = 'Rowsandall <info@rowsandall.com>'
|
||||||
subject = 'New comment on workout ' + workoutname
|
subject = 'New comment on session ' + workoutname
|
||||||
|
|
||||||
comment = u''+comment
|
comment = u''+comment
|
||||||
|
|
||||||
@@ -1784,6 +1795,13 @@ def handle_sendemailnewcomment(first_name,
|
|||||||
if debug:
|
if debug:
|
||||||
siteurl = SITE_URL_DEV
|
siteurl = SITE_URL_DEV
|
||||||
|
|
||||||
|
sessiontype = 'workout'
|
||||||
|
if 'sessiontype' in kwargs:
|
||||||
|
sessiontype=kwargs.pop('sessiontype')
|
||||||
|
|
||||||
|
commentlink = '/rowers/workout/{workoutid}/comment/'.format(workoutid=workoutid)
|
||||||
|
if 'commentlink' in kwargs:
|
||||||
|
commentlink = kwargs.pop('commentlink')
|
||||||
|
|
||||||
d = {
|
d = {
|
||||||
'first_name':first_name,
|
'first_name':first_name,
|
||||||
@@ -1793,6 +1811,8 @@ def handle_sendemailnewcomment(first_name,
|
|||||||
'workoutname':workoutname,
|
'workoutname':workoutname,
|
||||||
'siteurl':siteurl,
|
'siteurl':siteurl,
|
||||||
'workoutid':workoutid,
|
'workoutid':workoutid,
|
||||||
|
'sessiontype':sessiontype,
|
||||||
|
'commentlink':commentlink,
|
||||||
}
|
}
|
||||||
|
|
||||||
res = send_template_email(from_email,[fullemail],subject,
|
res = send_template_email(from_email,[fullemail],subject,
|
||||||
|
|||||||
47
rowers/templates/plannedsession_comments.html
Normal file
47
rowers/templates/plannedsession_comments.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
{% load tz %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% include "monitorjobs.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}Comment Session {% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<h1>Comments {{ plannedession.name }}</h1>
|
||||||
|
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_2">
|
||||||
|
{% for c in comments %}
|
||||||
|
<p>
|
||||||
|
{{ c.created }}
|
||||||
|
<b>{{ c.user.first_name }} {{ c.user.last_name }}</b>
|
||||||
|
<div class="talk-bubble tri-right left-top">
|
||||||
|
<div class="talktext">
|
||||||
|
{{ c.comment }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
<form enctype="multipart/form-data" method="post">
|
||||||
|
<table width=100%>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" value="Save">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% if 'racing' in active %}
|
||||||
|
{% include 'menu_racing.html' %}
|
||||||
|
{% else %}
|
||||||
|
{% include 'menu_plan.html' %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@@ -135,6 +135,20 @@
|
|||||||
title="Compare the workouts of all athletes who did this session">Compare Workouts</a>
|
title="Compare the workouts of all athletes who did this session">Compare Workouts</a>
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<div id="comments">
|
||||||
|
<h2>Comments ({{ comments|length }})</h2>
|
||||||
|
{% for c in comments %}
|
||||||
|
<p>
|
||||||
|
<em>{{ c.user.first_name }} {{ c.user.last_name }}</em> - {{ c.created }}</br>
|
||||||
|
<q>{{ c.comment }}</q>
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
<p>
|
||||||
|
<a href="/rowers/sessions/{{ race.id }}/comments/">Add a comment</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
{% if coursescript %}
|
{% if coursescript %}
|
||||||
<li class="grid_2">
|
<li class="grid_2">
|
||||||
<h2>Course</h2>
|
<h2>Course</h2>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
{{ commenter_first_name }} {{ commenter_last_name }} has written
|
{{ commenter_first_name }} {{ commenter_last_name }} has written
|
||||||
a new comment on workout {{ workoutname }}
|
a new comment on {{ sessiontype }} {{ workoutname }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{ comment }}
|
{{ comment }}
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
You can read the comment here:
|
You can read the comment here:
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ siteurl }}/rowers/workout/{{ workoutid }}/comment">
|
<a href="{{ siteurl }} {{ commentlink }}">
|
||||||
{{ siteurl }}/rowers/workout/{{ workoutid }}/comment</a>
|
{{ siteurl }}{{ commentlink }}</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -423,6 +423,20 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</li>
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<div id="comments">
|
||||||
|
<h2>Comments ({{ comments|length }})</h2>
|
||||||
|
{% for c in comments %}
|
||||||
|
<p>
|
||||||
|
<em>{{ c.user.first_name }} {{ c.user.last_name }}</em> - {{ c.created }}</br>
|
||||||
|
<q>{{ c.comment }}</q>
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
<p>
|
||||||
|
<a href="/rowers/sessions/{{ race.id }}/comments/">Add a comment</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
<li class="grid_4">
|
<li class="grid_4">
|
||||||
<div id="rules">
|
<div id="rules">
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -486,6 +486,25 @@ class SessionCompleteTest(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(verdict,'on target')
|
self.assertEqual(verdict,'on target')
|
||||||
|
|
||||||
|
def test_session_comment(self):
|
||||||
|
login = self.c.login(username=self.u.username, password=self.password)
|
||||||
|
self.assertTrue(login)
|
||||||
|
|
||||||
|
|
||||||
|
url = reverse('plannedsession_comment_view',kwargs={'id':self.ps_rscore.id})
|
||||||
|
|
||||||
|
response = self.c.get(url)
|
||||||
|
self.assertEqual(response.status_code,200)
|
||||||
|
|
||||||
|
form_data = {
|
||||||
|
'comment': faker.text()
|
||||||
|
}
|
||||||
|
|
||||||
|
form = WorkoutCommentForm(form_data)
|
||||||
|
self.assertTrue(form.is_valid())
|
||||||
|
|
||||||
|
response = self.c.post(url,form_data,follow=True)
|
||||||
|
self.assertEqual(response.status_code,200)
|
||||||
|
|
||||||
|
|
||||||
def test_session1_exact_complete(self):
|
def test_session1_exact_complete(self):
|
||||||
|
|||||||
BIN
rowers/tests/testdata/testdata.csv.gz
vendored
BIN
rowers/tests/testdata/testdata.csv.gz
vendored
Binary file not shown.
@@ -608,6 +608,10 @@ urlpatterns = [
|
|||||||
name='plannedsessions_coach_view'),
|
name='plannedsessions_coach_view'),
|
||||||
url(r'^sessions/print/?/$',views.plannedsessions_print_view,
|
url(r'^sessions/print/?/$',views.plannedsessions_print_view,
|
||||||
name='plannedsessions_print_view'),
|
name='plannedsessions_print_view'),
|
||||||
|
url(r'^sessions/(?P<id>\d+)/comments/user/(?P<userid>\d+)/$',views.plannedsession_comment_view,
|
||||||
|
name='plannedsession_comment_view'),
|
||||||
|
url(r'^sessions/(?P<id>\d+)/comments/$',views.plannedsession_comment_view,
|
||||||
|
name='plannedsession_comment_view'),
|
||||||
url(r'^sessions/print/user/(?P<userid>\d+)/$',views.plannedsessions_print_view,
|
url(r'^sessions/print/user/(?P<userid>\d+)/$',views.plannedsessions_print_view,
|
||||||
name='plannedsessions_print_view'),
|
name='plannedsessions_print_view'),
|
||||||
url(r'^sessions/sendcalendar/$',views.plannedsessions_icsemail_view,
|
url(r'^sessions/sendcalendar/$',views.plannedsessions_icsemail_view,
|
||||||
|
|||||||
@@ -1,5 +1,149 @@
|
|||||||
from statements import *
|
from statements import *
|
||||||
|
|
||||||
|
@login_required()
|
||||||
|
def plannedsession_comment_view(request,id=0,userid=0):
|
||||||
|
r = getrequestrower(request,userid=userid)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ps = PlannedSession.objects.get(id=id)
|
||||||
|
except PlannedSession.DoesNotExist:
|
||||||
|
raise Http404("Planned Session does not exist")
|
||||||
|
|
||||||
|
m = ps.manager
|
||||||
|
mm = Rower.objects.get(user=m)
|
||||||
|
|
||||||
|
if ps.manager != request.user and ps.sessiontype not in ['race','indoorrace']:
|
||||||
|
if r.rowerplan == 'coach' and r not in ps.rower.all():
|
||||||
|
teams = Team.objects.filter(manager=request.user)
|
||||||
|
members = Rower.objects.filter(team__in=teams).distinct()
|
||||||
|
teamusers = [m.user for m in members]
|
||||||
|
if ps.manager not in teamusers:
|
||||||
|
raise PermissionDenied("You do not have access to this session")
|
||||||
|
elif r not in ps.rower.all():
|
||||||
|
raise PermissionDenied("You do not have access to this session")
|
||||||
|
|
||||||
|
comments = PlannedSessionComment.objects.filter(plannedsession=ps).order_by("created")
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
manager = ps.manager
|
||||||
|
form = PlannedSessionCommentForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
cd = form.cleaned_data
|
||||||
|
comment = cd['comment']
|
||||||
|
comment = bleach.clean(comment)
|
||||||
|
if isinstance(comment,unicode):
|
||||||
|
comment = comment.encode('utf8')
|
||||||
|
elif isinstance(comment, str):
|
||||||
|
comment = comment.decode('utf8')
|
||||||
|
|
||||||
|
notification = cd['notification']
|
||||||
|
c = PlannedSessionComment(plannedsession=ps,user=request.user,comment=comment,
|
||||||
|
notification=notification)
|
||||||
|
c.save()
|
||||||
|
url = reverse('plannedsession_comment_view',
|
||||||
|
kwargs={
|
||||||
|
'id':id
|
||||||
|
})
|
||||||
|
message = '{name} says: <a href="{url}">{comment}</a>'.format(
|
||||||
|
name = request.user.first_name,
|
||||||
|
comment = comment,
|
||||||
|
url = url,
|
||||||
|
)
|
||||||
|
if request.user != manager:
|
||||||
|
a_messages.info(r.user,message.encode('ascii','ignore'))
|
||||||
|
|
||||||
|
sessiontype = 'training session'
|
||||||
|
if ps.sessiontype == 'race':
|
||||||
|
sessiontype = 'online virtual race'
|
||||||
|
elif ps.sessiontype == 'indoorrace':
|
||||||
|
sessiontype = 'indoor online virtual race'
|
||||||
|
|
||||||
|
res = myqueue(queuehigh,
|
||||||
|
handle_sendemailnewcomment,r.user.first_name,
|
||||||
|
r.user.last_name,
|
||||||
|
r.user.email,
|
||||||
|
request.user.first_name,
|
||||||
|
request.user.last_name,
|
||||||
|
comment,ps.name,ps.id,
|
||||||
|
emailbounced = r.emailbounced,
|
||||||
|
sessiontype = sessiontype,
|
||||||
|
commentlink = url
|
||||||
|
)
|
||||||
|
|
||||||
|
commenters = {oc.user for oc in comments if oc.notification}
|
||||||
|
for u in commenters:
|
||||||
|
a_messages.info(u,message)
|
||||||
|
if u != request.user and u != r.user:
|
||||||
|
ocr = Rower.objects.get(user=u)
|
||||||
|
res = myqueue(queuelow,
|
||||||
|
handle_sendemailnewresponse,
|
||||||
|
u.first_name,
|
||||||
|
u.last_name,
|
||||||
|
u.email,
|
||||||
|
request.user.first_name,
|
||||||
|
request.user.last_name,
|
||||||
|
comment,
|
||||||
|
ps.name,
|
||||||
|
ps.id,
|
||||||
|
c.id,
|
||||||
|
emailbounced = ocr.emailbounced,
|
||||||
|
sessiontype = sessiontype,
|
||||||
|
commentlink = url
|
||||||
|
)
|
||||||
|
|
||||||
|
url = reverse('plannedsession_comment_view',kwargs={'id':ps.id})
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
|
||||||
|
form = WorkoutCommentForm()
|
||||||
|
|
||||||
|
rower = getrower(request.user)
|
||||||
|
|
||||||
|
if ps.sessiontype in ['race','indoorrace']:
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':reverse('virtualevents_view'),
|
||||||
|
'name': 'Races'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': reverse('virtualevent_view',kwargs={'id':ps.id}),
|
||||||
|
'name': ps.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('plannedsession_comment_view',kwargs={'id':ps.id}),
|
||||||
|
'name': 'Comments'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
active = 'nav-racing'
|
||||||
|
|
||||||
|
else:
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':reverse('virtualevents_view'),
|
||||||
|
'name': 'Races'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': reverse('virtualevent_view',kwargs={'id':ps.id}),
|
||||||
|
'name': ps.name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('plannedsession_comment_view',kwargs={'id':ps.id}),
|
||||||
|
'name': 'Comments'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
active = 'nav-plan'
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'plannedsession_comments.html',
|
||||||
|
{'plannedsession':ps,
|
||||||
|
'rower':rower,
|
||||||
|
'breadcrumbs':breadcrumbs,
|
||||||
|
'active':active,
|
||||||
|
'comments':comments,
|
||||||
|
'form':form,
|
||||||
|
})
|
||||||
|
|
||||||
# Cloning sessions
|
# Cloning sessions
|
||||||
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/",
|
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/",
|
||||||
@@ -1385,7 +1529,7 @@ def plannedsession_view(request,id=0,userid=0):
|
|||||||
raise Http404("Planned Session does not exist")
|
raise Http404("Planned Session does not exist")
|
||||||
|
|
||||||
if ps.sessiontype in ['race','indoorrace']:
|
if ps.sessiontype in ['race','indoorrace']:
|
||||||
url = reverse(virtualevent_view,
|
url = reverse('virtualevent_view',
|
||||||
kwargs={'id':ps.id}
|
kwargs={'id':ps.id}
|
||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
@@ -1522,6 +1666,8 @@ def plannedsession_view(request,id=0,userid=0):
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
comments = PlannedSessionComment.objects.filter(plannedsession=ps).order_by("created")
|
||||||
|
|
||||||
return render(request,'plannedsessionview.html',
|
return render(request,'plannedsessionview.html',
|
||||||
{
|
{
|
||||||
'psdict': psdict,
|
'psdict': psdict,
|
||||||
@@ -1544,7 +1690,8 @@ def plannedsession_view(request,id=0,userid=0):
|
|||||||
'timeperiod':timeperiod,
|
'timeperiod':timeperiod,
|
||||||
'ranking':ranking,
|
'ranking':ranking,
|
||||||
'coursescript': coursescript,
|
'coursescript': coursescript,
|
||||||
'coursediv': coursediv
|
'coursediv': coursediv,
|
||||||
|
'comments': comments,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -882,6 +882,9 @@ def virtualevent_view(request,id=0):
|
|||||||
else:
|
else:
|
||||||
racelogo = None
|
racelogo = None
|
||||||
|
|
||||||
|
comments = PlannedSessionComment.objects.filter(plannedsession=race).order_by("created")
|
||||||
|
|
||||||
|
|
||||||
return render(request,'virtualevent.html',
|
return render(request,'virtualevent.html',
|
||||||
{
|
{
|
||||||
'coursescript':script,
|
'coursescript':script,
|
||||||
@@ -896,6 +899,7 @@ def virtualevent_view(request,id=0):
|
|||||||
'racelogo':racelogo,
|
'racelogo':racelogo,
|
||||||
'form':form,
|
'form':form,
|
||||||
'active':'nav-racing',
|
'active':'nav-racing',
|
||||||
|
'comments':comments
|
||||||
})
|
})
|
||||||
|
|
||||||
def virtualevent_ranking_view(request,id=0):
|
def virtualevent_ranking_view(request,id=0):
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ from rowers.models import (
|
|||||||
microcyclecheckdates,mesocyclecheckdates,macrocyclecheckdates,
|
microcyclecheckdates,mesocyclecheckdates,macrocyclecheckdates,
|
||||||
TrainingMesoCycleForm, TrainingMicroCycleForm,
|
TrainingMesoCycleForm, TrainingMicroCycleForm,
|
||||||
RaceLogo,RowerBillingAddressForm,PaidPlan,
|
RaceLogo,RowerBillingAddressForm,PaidPlan,
|
||||||
|
PlannedSessionComment,
|
||||||
)
|
)
|
||||||
from rowers.models import (
|
from rowers.models import (
|
||||||
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
|
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
|
||||||
@@ -97,7 +98,7 @@ from rowers.models import (
|
|||||||
PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace,
|
PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace,
|
||||||
VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm,
|
VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm,
|
||||||
IndoorVirtualRaceResultForm,IndoorVirtualRaceResult,
|
IndoorVirtualRaceResultForm,IndoorVirtualRaceResult,
|
||||||
IndoorVirtualRaceForm,
|
IndoorVirtualRaceForm,PlannedSessionCommentForm,
|
||||||
)
|
)
|
||||||
from rowers.models import (
|
from rowers.models import (
|
||||||
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet,
|
FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet,
|
||||||
|
|||||||
Reference in New Issue
Block a user