Merge branch 'release/v20.7.0'
This commit is contained in:
@@ -112,24 +112,27 @@ def send_template_email(from_email, to_email, subject,
|
||||
else:
|
||||
emailbounced = False
|
||||
|
||||
for recipient in to_email:
|
||||
try:
|
||||
soup = BeautifulSoup(html_content)
|
||||
createmessage = kwargs.get('createmessage', True)
|
||||
|
||||
s2 = soup.body
|
||||
|
||||
usr = User.objects.get(email=recipient)
|
||||
umsg = UserMessage(
|
||||
receiver = usr.rower,
|
||||
datetime = timezone.now(),
|
||||
text = '{text}'.format(text=s2),
|
||||
subject=subject,
|
||||
)
|
||||
umsg.save()
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
except Exception as e:
|
||||
pass
|
||||
if createmessage:
|
||||
for recipient in to_email:
|
||||
try:
|
||||
soup = BeautifulSoup(html_content)
|
||||
|
||||
s2 = soup.body
|
||||
|
||||
usr = User.objects.get(email=recipient)
|
||||
umsg = UserMessage(
|
||||
receiver = usr.rower,
|
||||
datetime = timezone.now(),
|
||||
text = '{text}'.format(text=s2),
|
||||
subject=subject,
|
||||
)
|
||||
umsg.save()
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if not emailbounced:
|
||||
res = msg.send()
|
||||
@@ -152,7 +155,7 @@ def send_confirm(user, name, link, options): # pragma: no cover
|
||||
_ = send_template_email('Rowsandall <info@rowsandall.com>',
|
||||
[fullemail],
|
||||
subject, 'confirmemail.html',
|
||||
d
|
||||
d, createmessage=False
|
||||
)
|
||||
|
||||
return 1
|
||||
|
||||
@@ -1623,11 +1623,13 @@ timezones = (
|
||||
|
||||
class GeoCourse(models.Model):
|
||||
manager = models.ForeignKey(Rower, null=True, on_delete=models.SET_NULL)
|
||||
followers = models.ManyToManyField(Rower, related_name='followed_courses')
|
||||
distance = models.IntegerField(default=0)
|
||||
name = models.CharField(max_length=150, blank=True)
|
||||
country = models.CharField(max_length=150, blank=True)
|
||||
notes = models.CharField(blank=True, max_length=200,
|
||||
verbose_name='Course Notes')
|
||||
updated = models.DateTimeField(default=timezone.now, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
name = self.name
|
||||
@@ -1644,6 +1646,11 @@ class GeoCourse(models.Model):
|
||||
d=d,
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.update = timezone.now()
|
||||
super(GeoCourse, self).save(*args, **kwargs)
|
||||
self.followers.add(self.manager)
|
||||
|
||||
@property
|
||||
def coord(self):
|
||||
return course_coord_center(self)
|
||||
|
||||
@@ -34,6 +34,21 @@
|
||||
<th>Manager</th><td>{{ course.manager }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
{% if course in rower.followed_courses.all %}
|
||||
You have liked this course. If you use <a href="https://performancephones.com/">CrewNerd</a>, you can synchronize this course to your phone in the app.
|
||||
{% else %}
|
||||
By clicking on the link below, you can add the course to your "liked" list, which can be synchronized with the
|
||||
<a href="https://performancephones.com/">CrewNerd</a> app (from your phone).
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
{% if course in rower.followed_courses.all %}
|
||||
<a href="unfollow">Remove this course from your list of liked courses</a>
|
||||
{% else %}
|
||||
<a href="follow">Like this course</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
<div class="mapdiv">
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Rowsandall Courses List{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<style>
|
||||
#mypointer {
|
||||
@@ -20,7 +18,7 @@
|
||||
<h1>Courses</h1>
|
||||
|
||||
<ul class="main-content">
|
||||
<li class="grid_3">
|
||||
<li class="grid_4">
|
||||
{% if courses %}
|
||||
<p>
|
||||
<form enctype="multipart/form-data" method="post">
|
||||
@@ -33,6 +31,8 @@
|
||||
<th> Country</th>
|
||||
<th> Name</th>
|
||||
<th> Distance</th>
|
||||
<th> Updated on</th>
|
||||
<th> Like</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -46,7 +46,15 @@
|
||||
<td>
|
||||
{{ course.distance }} m
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{{ course.updated|date:"Y-m-d" }}
|
||||
</td>
|
||||
<td>
|
||||
{% if course in rower.followed_courses.all %}
|
||||
<a class="unfollow" href="/rowers/courses/{{ course.id }}/unfollow"><i class="fas fa-star"></i></a>
|
||||
{% else %}
|
||||
<a class="follow" href="/rowers/courses/{{ course.id }}/follow"><i class="far fa-star"></i></a>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
@@ -74,6 +82,9 @@
|
||||
<p>
|
||||
<a href="/rowers/list-courses/">All courses</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="/rowers/list-courses/?liked=true">Courses I like</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<a href="/rowers/courses/upload/">Add Courses</a>
|
||||
@@ -143,6 +154,55 @@
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script type='text/javascript'
|
||||
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$("a.follow").click(function(event) {
|
||||
event.preventDefault();
|
||||
var ajaxEndpoint = $(this).attr('href');
|
||||
var $ajaxLink = $(this);
|
||||
|
||||
$.ajax({
|
||||
url: ajaxEndpoint,
|
||||
method: 'GET',
|
||||
success: function(response) {
|
||||
console.log(response);
|
||||
$ajaxLink.html('<i class="fas fa-star"></i>');
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
$("a.unfollow").click(function(event) {
|
||||
event.preventDefault();
|
||||
var ajaxEndpoint = $(this).attr('href');
|
||||
var $ajaxLink = $(this);
|
||||
|
||||
$.ajax({
|
||||
url: ajaxEndpoint,
|
||||
method: 'GET',
|
||||
success: function(response) {
|
||||
console.log(response);
|
||||
$ajaxLink.html('<i class="far fa-star"></i>');
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
|
||||
@@ -177,19 +177,20 @@ class URLTests(TestCase):
|
||||
'/rowers/workout/upload/team/',
|
||||
'/rowers/workouts-join/',
|
||||
'/rowers/workouts-join-select/',
|
||||
'/rowers/api/courses/',
|
||||
'/rowers/api/courses/?name=Brno',
|
||||
'/rowers/api/courses/?course_distance=2000',
|
||||
'/rowers/api/courses/?course_distance=aap',
|
||||
'/rowers/api/courses/?distance_from=52&latitude=42&longitude=7',
|
||||
'/rowers/api/courses/?distance_from=50&latitude=42&longitude=-aap',
|
||||
'/rowers/api/courses/',
|
||||
'/rowers/api/courses/1/',
|
||||
'/rowers/list-workouts/?selectworkouts=true',
|
||||
'/rowers/api/courses/kml/',
|
||||
'/rowers/api/courses/kml/?id=1',
|
||||
'/rowers/api/courses/kml?id=1&id=4/',
|
||||
'/rowers/api/courses/kml/?id=aap',
|
||||
# '/rowers/api/courses/',
|
||||
# '/rowers/api/courses/?name=Brno',
|
||||
# '/rowers/api/courses/?course_distance=2000',
|
||||
# '/rowers/api/courses/?course_distance=aap',
|
||||
# '/rowers/api/courses/?distance_from=52&latitude=42&longitude=7',
|
||||
# '/rowers/api/courses/?distance_from=50&latitude=42&longitude=-aap',
|
||||
# '/rowers/api/courses/',
|
||||
# '/rowers/api/courses/1/',
|
||||
# '/rowers/list-workouts/?selectworkouts=true',
|
||||
# '/rowers/api/courses/kml/',
|
||||
# '/rowers/api/courses/kml/liked/',
|
||||
# '/rowers/api/courses/kml/?id=1',
|
||||
# '/rowers/api/courses/kml?id=1&id=4/',
|
||||
# '/rowers/api/courses/kml/?id=aap',
|
||||
]
|
||||
|
||||
|
||||
|
||||
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
Binary file not shown.
@@ -253,6 +253,7 @@ urlpatterns = [
|
||||
name='strokedata_tcx'),
|
||||
re_path(r'^api/courses/$', views.course_list, name='course_list'),
|
||||
re_path(r'^api/courses/(?P<id>\d+)/$', views.get_crewnerd_kml, name='get_crewnerd_kml'),
|
||||
re_path(r'^api/courses/kml/liked/$', views.get_crewnerd_liked, name='get_crewnerd_liked'),
|
||||
re_path(r'^api/courses/kml/$', views.get_crewnerd_multiple, name='get_crewnerd_multiple'),
|
||||
re_path(r'^500v/$', views.error500_view, name='error500_view'),
|
||||
re_path(r'^500q/$', views.servererror_view, name='servererror_view'),
|
||||
@@ -361,6 +362,10 @@ urlpatterns = [
|
||||
views.course_update_confirm, name='course_update_confirm'),
|
||||
re_path(r'^courses/(?P<id>\d+)/update/',
|
||||
views.course_upload_replace_view, name='course_upload_replace_view'),
|
||||
re_path(r'^courses/(?P<id>\d+)/follow/',
|
||||
views.course_follow_view, name='course_follow_view'),
|
||||
re_path(r'^courses/(?P<id>\d+)/unfollow/',
|
||||
views.course_unfollow_view, name='course_unfollow_view'),
|
||||
re_path(r'^standards/upload/$', views.standards_upload_view,
|
||||
name='standards_upload_view'),
|
||||
re_path(r'^standards/upload/(?P<id>\d+)/$',
|
||||
|
||||
@@ -252,7 +252,7 @@ Optional, not for CN
|
||||
- Update one or more new courses from KML
|
||||
"""
|
||||
@api_view(["GET"])
|
||||
@permission_classes([AllowAny])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def course_list(request):
|
||||
if request.method != 'GET': # pragma: no cover
|
||||
dologging('apilog.log','{m} request to KML endpoint'.format(m=request.method))
|
||||
@@ -305,7 +305,7 @@ def course_list(request):
|
||||
return JsonResponse(response_dict, content_type='application/json; charset=utf8')
|
||||
|
||||
@api_view(["GET"])
|
||||
@permission_classes([AllowAny])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_crewnerd_kml(request,id=0):
|
||||
if request.method != 'GET': # pragma: no cover
|
||||
dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method))
|
||||
@@ -318,11 +318,11 @@ def get_crewnerd_kml(request,id=0):
|
||||
|
||||
kml = coursetokml(c, cn=True)
|
||||
|
||||
return HttpResponse(kml)
|
||||
return HttpResponse(kml, content_type='text/xml')
|
||||
|
||||
#@csrf_exempt
|
||||
@csrf_exempt
|
||||
@api_view(["GET"])
|
||||
@permission_classes([AllowAny])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_crewnerd_multiple(request):
|
||||
if request.method != 'GET': # pragma: no cover
|
||||
dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method))
|
||||
@@ -344,7 +344,34 @@ def get_crewnerd_multiple(request):
|
||||
|
||||
kml = coursestokml(ids, cn=True)
|
||||
|
||||
return HttpResponse(kml)
|
||||
return HttpResponse(kml, content_type='text/xml')
|
||||
|
||||
@csrf_exempt
|
||||
@api_view(["GET"])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_crewnerd_liked(request):
|
||||
r = getrower(request.user)
|
||||
if request.method != 'GET': # pragma: no cover
|
||||
dologging('apilog.log','{m} request to CrewNerd KML endpoint'.format(m=request.method))
|
||||
return HttpResponseNotAllowed("Method not supported")
|
||||
|
||||
ids = request.GET.get('id')
|
||||
if ids is not None:
|
||||
tdict = dict(request.GET.lists())
|
||||
idsnew = []
|
||||
for id in tdict['id']:
|
||||
try:
|
||||
idsnew.append(int(id))
|
||||
except ValueError:
|
||||
pass
|
||||
ids = idsnew
|
||||
else:
|
||||
gcs = GeoCourse.objects.filter(followers=r)
|
||||
ids = [c.id for c in gcs]
|
||||
|
||||
kml = coursestokml(ids, cn=True)
|
||||
|
||||
return HttpResponse(kml, content_type='text/xml')
|
||||
|
||||
# Stroke data views
|
||||
|
||||
|
||||
@@ -49,10 +49,14 @@ def courses_view(request):
|
||||
|
||||
courses = GeoCourse.objects.all().order_by("country", "name", "distance")
|
||||
nearby = request.GET.get('nearby')
|
||||
liked = request.GET.get('liked')
|
||||
|
||||
if nearby and lat_lon is not None: # pragma: no cover
|
||||
courses = getnearestcourses(lat_lon, courses)
|
||||
|
||||
if liked:
|
||||
courses = GeoCourse.objects.filter(followers=r)
|
||||
|
||||
# add search processing
|
||||
query = request.GET.get('q')
|
||||
if query: # pragma: no cover
|
||||
@@ -226,8 +230,39 @@ def course_edit_view(request, id=0):
|
||||
}
|
||||
)
|
||||
|
||||
# @login_required()
|
||||
@login_required()
|
||||
def course_follow_view(request, id=0):
|
||||
try:
|
||||
course = GeoCourse.objects.get(id=id)
|
||||
except GeoCourse.DoesNotExist: # pragma: no cover
|
||||
raise Http404("Course doesn't exist")
|
||||
|
||||
r = getrower(request.user)
|
||||
course.followers.add(r)
|
||||
messages.info(request,"You have liked {c}. If you use CrewNerd, you can sync your course list to CrewNerd".format(c=course))
|
||||
|
||||
if request_is_ajax(request):
|
||||
return JSONResponse({"course": course.id})
|
||||
|
||||
url = reverse("course_view", kwargs={'id':course.id})
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@login_required()
|
||||
def course_unfollow_view(request, id=0):
|
||||
try:
|
||||
course = GeoCourse.objects.get(id=id)
|
||||
except GeoCourse.DoesNotExist: # pragma: no cover
|
||||
raise Http404("Course doesn't exist")
|
||||
|
||||
r = getrower(request.user)
|
||||
course.followers.remove(r)
|
||||
messages.info(request,"You have stopped following {c}.".format(c=course))
|
||||
|
||||
if request_is_ajax(request):
|
||||
return JSONResponse({"course": course.id})
|
||||
|
||||
url = reverse("courses_view")
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
def course_view(request, id=0):
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user