Merge branch 'release/v20.2.2'
This commit is contained in:
@@ -8,7 +8,7 @@ from .models import (
|
||||
WorkoutComment, C2WorldClassAgePerformance, PlannedSession,
|
||||
GeoCourse, GeoPolygon, GeoPoint, VirtualRace, VirtualRaceResult,
|
||||
PaidPlan, IndoorVirtualRaceResult, ShareKey,
|
||||
CourseStandard, StandardCollection, InstantPlan,
|
||||
CourseStandard, StandardCollection, InstantPlan, UserMessage
|
||||
)
|
||||
|
||||
# Register your models here so you can use them in the Admin module
|
||||
@@ -170,6 +170,9 @@ class CourseStandardAdmin(admin.ModelAdmin):
|
||||
class InstantPlanAdmin(admin.ModelAdmin):
|
||||
list_display = ('name','duration','price')
|
||||
|
||||
class UserMessageAdmin(admin.ModelAdmin):
|
||||
list_display = ('receiver','datetime','subject')
|
||||
|
||||
admin.site.unregister(User)
|
||||
admin.site.register(User, UserAdmin)
|
||||
admin.site.register(Workout, WorkoutAdmin)
|
||||
@@ -192,3 +195,4 @@ admin.site.register(ShareKey, ShareKeyAdmin)
|
||||
admin.site.register(CourseStandard, CourseStandardAdmin)
|
||||
admin.site.register(StandardCollection, StandardCollectionAdmin)
|
||||
admin.site.register(InstantPlan, InstantPlanAdmin)
|
||||
admin.site.register(UserMessage, UserMessageAdmin)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.contrib.staticfiles import finders
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
import os
|
||||
import time
|
||||
import gc
|
||||
@@ -112,17 +112,22 @@ def send_template_email(from_email, to_email, subject,
|
||||
else:
|
||||
emailbounced = False
|
||||
|
||||
try:
|
||||
usr = User.objects.get(email=to_email)
|
||||
umsg = UserMessage(
|
||||
receiver = usr.rower,
|
||||
datetime = timezone.now(),
|
||||
text = text_content,
|
||||
subject=subject,
|
||||
)
|
||||
umsg.save()
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
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
|
||||
|
||||
if not emailbounced:
|
||||
res = msg.send()
|
||||
|
||||
@@ -1247,6 +1247,14 @@ class UserMessage(models.Model):
|
||||
isread = models.BooleanField(default=False)
|
||||
text = models.CharField(max_length=1000)
|
||||
subject = models.CharField(max_length=100,default='Message')
|
||||
|
||||
def __str__(self):
|
||||
return '{r1} {r2} {d} {subject}'.format(
|
||||
r1 = self.receiver.user.first_name,
|
||||
r2 = self.receiver.user.last_name,
|
||||
d = self.datetime,
|
||||
subject = self.subject
|
||||
)
|
||||
|
||||
# requestor is user
|
||||
|
||||
|
||||
24
rowers/templates/contactformemail.html
Normal file
24
rowers/templates/contactformemail.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends "emailbase.html" %}
|
||||
|
||||
{% block body %}
|
||||
<p>New Contact Form Message</p>
|
||||
|
||||
<p>
|
||||
From: {{ firstname }} {{ lastname }}
|
||||
</p>
|
||||
<p>
|
||||
Email: {{ email }}
|
||||
</p>
|
||||
<p>
|
||||
Subject: {{ subject }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ message }}
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
Best Regards, the Rowsandall Team
|
||||
</p>
|
||||
{% endblock %}
|
||||
@@ -2,6 +2,14 @@
|
||||
{% load rowerfilters %}
|
||||
<h1><a href="/rowers/me/edit/">Profile</a></h1>
|
||||
<ul class="cd-accordion-menu animated">
|
||||
<li id="manage-messgs">
|
||||
<a href="/rowers/me/messages/">
|
||||
<i class="fas fa-envelope fa-fw"></i> Messages
|
||||
{% if rower|usermessages %}
|
||||
( {{ rower|usermessages }} )
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
<li id="manage-prefs-simple">
|
||||
<a href="/rowers/me/prefs/">
|
||||
<i class="fas fa-sliders-v-square fa-fw"></i> Threshold
|
||||
|
||||
59
rowers/templates/user_messages.html
Normal file
59
rowers/templates/user_messages.html
Normal file
@@ -0,0 +1,59 @@
|
||||
{% extends "newbase.html" %}
|
||||
|
||||
{% block title %}Messages{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<h1>{{ rower.user.first_name }}'s New Messages</h1>
|
||||
|
||||
<ul class="main-content">
|
||||
{% for usermessage in usermessages %}
|
||||
{% if not usermessage.isread %}
|
||||
<li class="rounder">
|
||||
<h2>{{ usermessage.subject }}</h2>
|
||||
<p><em>{{ usermessage.datetime }}</em></p>
|
||||
<p>
|
||||
{{ usermessage.text|safe }}
|
||||
</p>
|
||||
{% if request.user.rower == usermessage.receiver %}
|
||||
<p>
|
||||
<a href="/rowers/me/messages/{{ usermessage.id }}/delete/">
|
||||
<i class="fas fa-trash-alt fa-fw"></i>
|
||||
</a>
|
||||
<a href="/rowers/me/messages/{{ usermessage.id }}/markread/">
|
||||
<i class="fas fa-check fa-fw"></i>
|
||||
</a>
|
||||
</p>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h1>{{ rower.user.first_name }}'s Previous Messages</h1>
|
||||
|
||||
<ul class="main-content">
|
||||
{% for usermessage in usermessages %}
|
||||
{% if usermessage.isread %}
|
||||
<li class="rounder">
|
||||
<h2>{{ usermessage.subject }}</h2>
|
||||
<p><em>{{ usermessage.datetime }}</em></p>
|
||||
<p>
|
||||
{{ usermessage.text|safe }}
|
||||
</p>
|
||||
{% if request.user.rower == usermessage.receiver %}
|
||||
<p>
|
||||
<a href="/rowers/me/messages/{{ usermessage.id }}/delete/">
|
||||
<i class="fas fa-trash-alt fa-fw"></i>
|
||||
</a>
|
||||
</p>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% include 'menu_profile.html' %}
|
||||
{% endblock %}
|
||||
@@ -15,7 +15,7 @@ from rowers.models import (
|
||||
course_length, WorkoutComment,
|
||||
TrainingMacroCycle, TrainingMesoCycle, TrainingMicroCycle,
|
||||
Rower, Workout, SiteAnnouncement, TeamInvite, TeamRequest, CoachOffer, CoachRequest,
|
||||
VirtualRaceFollower, VirtualRace, favanalysischoices,
|
||||
VirtualRaceFollower, VirtualRace, favanalysischoices, UserMessage,
|
||||
Team, TrainingPlan, TrainingTarget)
|
||||
|
||||
from rowers.plannedsessions import (
|
||||
@@ -56,6 +56,15 @@ def workoutdate(id): # pragma: no cover
|
||||
except Workout.DoesNotExist:
|
||||
return 'unknown'
|
||||
|
||||
@register.filter
|
||||
def usermessages(rower):
|
||||
try:
|
||||
msgs = UserMessage.objects.filter(receiver=rower, isread=False)
|
||||
return msgs.count()
|
||||
except UserMessage.DoesNotExist:
|
||||
pass
|
||||
|
||||
return 0
|
||||
|
||||
@register.filter
|
||||
def isfollower(user, id):
|
||||
|
||||
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
Binary file not shown.
@@ -650,6 +650,10 @@ urlpatterns = [
|
||||
re_path(r'^alerts/(?P<id>\d+)/report/$',
|
||||
views.alert_report_view, name='alert_report_view'),
|
||||
re_path(r'^me/deactivate/$', views.deactivate_user, name='deactivate_user'),
|
||||
re_path(r'^me/messages/$', views.user_messages, name='user_messages'),
|
||||
re_path(r'^me/messages/(?P<id>\d+)/delete/$', views.user_message_delete, name='user_message_delete'),
|
||||
re_path(r'^me/messages/(?P<id>\d+)/markread/$', views.user_message_markread, name='user_message_markread'),
|
||||
re_path(r'^me/messages/user/(?P<userid>\d+)/$', views.user_messages, name='user_messages'),
|
||||
re_path(r'^me/delete/$', views.remove_user, name='remove_user'),
|
||||
re_path(r'^survey/$', views.survey, name='survey'),
|
||||
re_path(r'^me/gdpr-optin-confirm/?/$',
|
||||
|
||||
@@ -158,7 +158,8 @@ from rowers.models import (
|
||||
VideoAnalysis, ShareKey,
|
||||
StandardCollection, CourseStandard,
|
||||
VirtualRaceFollower, TombStone, InstantPlan,
|
||||
PlannedSessionStep,InStrokeAnalysis, ForceCurveAnalysis, SyncRecord
|
||||
PlannedSessionStep,InStrokeAnalysis, ForceCurveAnalysis, SyncRecord,
|
||||
UserMessage,
|
||||
)
|
||||
from rowers.models import ( RowerPowerForm, RowerHRZonesForm, SimpleRowerPowerForm,
|
||||
RowerForm, RowerCPForm, GraphImage, AdvancedWorkoutForm,
|
||||
@@ -1289,11 +1290,24 @@ def sendmail(request):
|
||||
subject = 'Rowsandall Contact Form:'+form.cleaned_data['subject']
|
||||
message = form.cleaned_data['message']
|
||||
fullemail = firstname + " " + lastname + " " + "<" + email + ">"
|
||||
try:
|
||||
send_mail(subject, message, fullemail, ['info@rowsandall.com'])
|
||||
except:
|
||||
d = {
|
||||
'firstname': firstname,
|
||||
'lastname': lastname,
|
||||
'email': email,
|
||||
'subject': subject,
|
||||
'message': message,
|
||||
}
|
||||
res = send_template_email('Rowsandall <info@rowsandall.com>',
|
||||
['Rowsandall <info@rowsandall.com>'],
|
||||
subject,
|
||||
'contactformemail.html',
|
||||
d
|
||||
)
|
||||
if res:
|
||||
messages.info(request,"Your contact form was submitted")
|
||||
else:
|
||||
messages.error(
|
||||
request, "Something went wrong trying to send the email")
|
||||
request, "Something went wrong trying to send the form")
|
||||
return HttpResponseRedirect('/rowers/email/thankyou/')
|
||||
else:
|
||||
if not success:
|
||||
|
||||
@@ -248,6 +248,50 @@ def start_plantrial_view(request):
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@login_required()
|
||||
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
|
||||
def user_messages(request,userid=0):
|
||||
r = getrequestrowercoachee(request, userid=userid, notpermanent=True)
|
||||
usermessages = UserMessage.objects.filter(receiver=r).order_by('-datetime')
|
||||
return render(request,'user_messages.html',
|
||||
{'usermessages':usermessages,
|
||||
'rower':r})
|
||||
|
||||
@login_required()
|
||||
def user_message_delete(request,id=0):
|
||||
try:
|
||||
msg = UserMessage.objects.get(id=id)
|
||||
except UserMessage.DoesNotExist:
|
||||
pass
|
||||
|
||||
if msg.receiver == request.user.rower:
|
||||
msg.delete()
|
||||
messages.info(request,'Deleted message {id}'.format(id=id))
|
||||
else:
|
||||
messages.error('You are not allowed to delete this message')
|
||||
|
||||
url = reverse('user_messages')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
@login_required()
|
||||
def user_message_markread(request,id=0):
|
||||
try:
|
||||
msg = UserMessage.objects.get(id=id)
|
||||
except UserMessage.DoesNotExist:
|
||||
pass
|
||||
|
||||
if msg.receiver == request.user.rower:
|
||||
msg.isread = True
|
||||
msg.save()
|
||||
messages.info(request,'Marked message {id} read'.format(id=id))
|
||||
else:
|
||||
messages.error('You are not allowed to change this message')
|
||||
|
||||
url = reverse('user_messages')
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
|
||||
# Page where user can manage his favorite charts
|
||||
@login_required()
|
||||
|
||||
@@ -166,6 +166,14 @@
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% if user.rower|usermessages %}
|
||||
<li>
|
||||
<a class="" href="{% url 'user_messages' %}" title="Messages" id="id-messages">
|
||||
<i class="fas fa-envelope fa-fw"></i>
|
||||
({{ user.rower|usermessages }})
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a class="" href="{% url 'logout' %}?next=/login/" title="Sign Out" id="id-logout">
|
||||
<i class="fas fa-sign-out-alt "></i>
|
||||
|
||||
Reference in New Issue
Block a user