Add progress to class creation, fix interest emails

If there's 20 people interested in a course, class creation could take
quite a while so show progress. Only send emails to active members.
Change "Interest +" wording to "interested" in emails.
This commit is contained in:
Tanner Collin 2022-09-05 22:01:46 +00:00
parent 2d76aaf87d
commit 4f121d0541
6 changed files with 46 additions and 9 deletions

View File

@ -12,7 +12,7 @@
<div>You can find the class on its course page here:<br></div> <div>You can find the class on its course page here:<br></div>
<div><a href="[link]">[link]</a><br></div> <div><a href="[link]">[link]</a><br></div>
<div><br></div> <div><br></div>
<div>Your "interest" in this course is now removed and you won't receive any more notifications about its classes until you press the "Interest +" button again.<br></div> <div>Your "interest" in this course is now removed and you won't receive any more notifications about its classes until you press the "interested" button again.<br></div>
<div><br></div> <div><br></div>
<div>Spaceport<br></div> <div>Spaceport<br></div>
</body> </body>

View File

@ -6,6 +6,6 @@ You can find the class on its course page here:
[link] [link]
Your "interest" in this course is now removed and you won't receive any more Your "interest" in this course is now removed and you won't receive any more
notifications about its classes until you press the "Interest +" button again. notifications about its classes until you press the "interested" button again.
Spaceport Spaceport

View File

@ -3,6 +3,7 @@ logger = logging.getLogger(__name__)
import os import os
import smtplib import smtplib
import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.core.mail import send_mail, EmailMultiAlternatives from django.core.mail import send_mail, EmailMultiAlternatives
@ -92,4 +93,7 @@ def send_interest_email(interest):
html_message=email_html, html_message=email_html,
) )
if not settings.EMAIL_HOST:
time.sleep(0.5) # simulate slowly sending emails when logging to console
logger.info('Sent interest email:\n' + email_text) logger.info('Sent interest email:\n' + email_text)

View File

@ -195,8 +195,13 @@ def calc_card_scans():
def get_progress(request_id): def get_progress(request_id):
return cache.get('request-progress-' + request_id, []) return cache.get('request-progress-' + request_id, [])
def set_progress(request_id, data): def set_progress(request_id, data, replace=False):
logger.info('Progress - ID: %s | Status: %s', request_id, data) logger.info('Progress - ID: %s | Status: %s', request_id, data)
progress = get_progress(request_id) progress = get_progress(request_id)
if replace and len(progress):
progress[-1] = data
else:
progress.append(data) progress.append(data)
cache.set('request-progress-' + request_id, progress) cache.set('request-progress-' + request_id, progress)

View File

@ -279,6 +279,7 @@ class SessionViewSet(Base, List, Retrieve, Create, Update):
return serializers.SessionSerializer return serializers.SessionSerializer
def perform_create(self, serializer): def perform_create(self, serializer):
data = self.request.data
session = serializer.save(instructor=self.request.user) session = serializer.save(instructor=self.request.user)
# ensure session datetime is at least 1 day in the future # ensure session datetime is at least 1 day in the future
@ -287,15 +288,22 @@ class SessionViewSet(Base, List, Retrieve, Create, Update):
logging.info('Session is in the past or too soon, not sending interest emails.') logging.info('Session is in the past or too soon, not sending interest emails.')
return return
interests = models.Interest.objects.filter(course=session.course, satisfied_by__isnull=True) interests = models.Interest.objects.filter(
course=session.course,
satisfied_by__isnull=True,
user__member__paused_date__isnull=True
)
for num, interest in enumerate(interests):
msg = 'Sending email {} / {}...'.format(num+1, len(interests))
if data['request_id']: utils_stats.set_progress(data['request_id'], msg, replace=True)
for interest in interests:
try: try:
utils_email.send_interest_email(interest) utils_email.send_interest_email(interest)
except BaseException as e: except BaseException as e:
msg = 'Problem sending interest email: ' + str(e) msg = 'Problem sending interest email: ' + str(e)
logger.exception(msg) logger.exception(msg)
alert_tanner(msg) utils.alert_tanner(msg)
num_satisfied = interests.update(satisfied_by=session) num_satisfied = interests.update(satisfied_by=session)
logging.info('Satisfied %s interests.', num_satisfied) logging.info('Satisfied %s interests.', num_satisfied)

View File

@ -6,7 +6,7 @@ import 'react-datetime/css/react-datetime.css';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import './light.css'; import './light.css';
import { Button, Checkbox, Form, Grid, Header, Icon, Label, Message, Table } from 'semantic-ui-react'; import { Button, Checkbox, Form, Grid, Header, Icon, Label, Message, Table } from 'semantic-ui-react';
import { requester } from './utils.js'; import { requester, randomString } from './utils.js';
import { MembersDropdown } from './Members.js'; import { MembersDropdown } from './Members.js';
class AttendanceSheet extends React.Component { class AttendanceSheet extends React.Component {
@ -378,6 +378,7 @@ export function InstructorClassList(props) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [input, setInput] = useState({ max_students: null }); const [input, setInput] = useState({ max_students: null });
const [error, setError] = useState(false); const [error, setError] = useState(false);
const [progress, setProgress] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false); const [success, setSuccess] = useState(false);
const [classes, setClasses] = useState([]); const [classes, setClasses] = useState([]);
@ -387,9 +388,23 @@ export function InstructorClassList(props) {
if (loading) return; if (loading) return;
setLoading(true); setLoading(true);
setSuccess(false); setSuccess(false);
const data = { ...input, course: course.id };
const request_id = randomString();
const getStatus = () => {
requester('/stats/progress/?request_id='+request_id, 'GET')
.then(res => {
setProgress(res);
})
.catch(err => {
console.log(err);
});
};
const interval = setInterval(getStatus, 500);
const data = { ...input, course: course.id, request_id: request_id };
requester('/sessions/', 'POST', token, data) requester('/sessions/', 'POST', token, data)
.then(res => { .then(res => {
clearInterval(interval);
setSuccess(res.id); setSuccess(res.id);
setInput({ max_students: null }); setInput({ max_students: null });
setLoading(false); setLoading(false);
@ -398,6 +413,7 @@ export function InstructorClassList(props) {
setCourse({ ...course, sessions: [ res, ...course.sessions ] }); setCourse({ ...course, sessions: [ res, ...course.sessions ] });
}) })
.catch(err => { .catch(err => {
clearInterval(interval);
setLoading(false); setLoading(false);
console.log(err); console.log(err);
setError(err.data); setError(err.data);
@ -445,6 +461,10 @@ export function InstructorClassList(props) {
<InstructorClassEditor input={input} setInput={setInput} error={error} token={token} /> <InstructorClassEditor input={input} setInput={setInput} error={error} token={token} />
<p>
{progress.map(x => <>{x}<br /></>)}
</p>
<Form.Button loading={loading} error={error.non_field_errors}> <Form.Button loading={loading} error={error.non_field_errors}>
Submit Submit
</Form.Button> </Form.Button>