Merge branch 'master' into usage_tracking

This commit is contained in:
2022-02-02 21:21:22 +00:00
19 changed files with 822 additions and 234 deletions

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
<style type="text/css">p.MsoNormal,p.MsoNoSpacing{margin:0}</style>
</head>
<body>
<div>Hi [name],<br></div>
<div><br></div>
<div>You just signed up to Spaceport with the username: [username]<br></div>
<div><br></div>
<div>To manage your Protospace membership go to:<br></div>
<div><a href="https://my.protospace.ca">https://my.protospace.ca</a><br></div>
<div><br></div>
<div>You have automatically been added to our forum Spacebar at:<br></div>
<div><a href="https://forum.protospace.ca">https://forum.protospace.ca</a><br></div>
<div><br></div>
<div>Please introduce yourself here:<br></div>
<div><a href="https://forum.protospace.ca/c/chattymcchatface/new-user-introductions/31">https://forum.protospace.ca/c/chattymcchatface/new-user-introductions/31</a><br></div>
<div><br></div>
<div>If you have any questions, you will get the fastest response there.<br></div>
<div><br></div>
<div>Your next goal is to become vetted after:<br></div>
<div>- paying your member dues<br></div>
<div>- being a member for four weeks<br></div>
<div>- attending a New Member Orientation<br></div>
<div>- finding two members to sponsor (vouch for) you<br></div>
<div><br></div>
<div>You can meet members Tuesday evenings during our open house.<br></div>
<div><br></div>
<div>Mark [date] on your calendar as the day you can get vetted.<br></div>
<div><br></div>
<div>Sign up for a New Member Orientation here:<br></div>
<div><a href="https://my.protospace.ca/classes">https://my.protospace.ca/classes</a><br></div>
<div><br></div>
<div>Good luck,<br></div>
<div>Spaceport<br></div>
<div><br></div>
</body>
</html>

View File

@@ -0,0 +1,30 @@
Hi [name],
You just signed up to Spaceport with the username: [username]
To manage your Protospace membership go to:
https://my.protospace.ca
You have automatically been added to our forum Spacebar at:
https://forum.protospace.ca
Please introduce yourself here:
https://forum.protospace.ca/c/chattymcchatface/new-user-introductions/31
If you have any questions, you will get the fastest response there.
Your next goal is to become vetted after:
- paying your member dues
- being a member for four weeks
- attending a New Member Orientation
- finding two members to sponsor (vouch for) you
You can meet members Tuesday evenings during our open house.
Mark [date] on your calendar as the day you can get vetted.
Sign up for a New Member Orientation here:
https://my.protospace.ca/classes
Good luck,
Spaceport

View File

@@ -117,6 +117,7 @@ class Course(models.Model):
name = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
is_old = models.BooleanField(default=False)
tags = models.CharField(max_length=128, blank=True)
history = HistoricalRecords()

View File

@@ -449,12 +449,13 @@ class StudentTrainingSerializer(TrainingSerializer):
class CourseSerializer(serializers.ModelSerializer):
class Meta:
model = models.Course
fields = ['id', 'name', 'is_old', 'description']
fields = ['id', 'name', 'is_old', 'description', 'tags']
class SessionSerializer(serializers.ModelSerializer):
student_count = serializers.SerializerMethodField()
course_data = serializers.SerializerMethodField()
instructor_name = serializers.SerializerMethodField()
instructor_id = serializers.SerializerMethodField()
datetime = serializers.DateTimeField()
course = serializers.PrimaryKeyRelatedField(queryset=models.Course.objects.all())
students = TrainingSerializer(many=True, read_only=True)
@@ -479,6 +480,12 @@ class SessionSerializer(serializers.ModelSerializer):
name = 'Unknown'
return obj.old_instructor or name
def get_instructor_id(self, obj):
if obj.instructor and hasattr(obj.instructor, 'member'):
return obj.instructor.member.id
else:
return None
class SessionListSerializer(SessionSerializer):
students = None

View File

@@ -22,6 +22,8 @@ class LoggingThrottle(throttling.BaseThrottle):
pass
elif path.startswith('/stats/'):
return True
elif path == '/sessions/' and user == None:
return True
if request.data:
data = request.data.dict()

View File

@@ -21,7 +21,7 @@ from django.db.models import Sum
from django.core.cache import cache
from django.utils.timezone import now, pytz
from . import models, serializers, utils_ldap, utils_stats, utils_auth, utils
from . import models, serializers, utils_ldap, utils_stats, utils_auth, utils, utils_email
from .. import settings
STATIC_FOLDER = 'data/static/'
@@ -374,6 +374,14 @@ def register_user(data, user):
utils.alert_tanner(msg)
logger.info(msg)
if data['request_id']: utils_stats.set_progress(data['request_id'], 'Sending welcome email...')
try:
utils_email.send_welcome_email(user.member)
except BaseException as e: # TODO: remove, just for testing
msg = 'Problem sending welcome email: ' + str(e)
logger.exception(msg)
alert_tanner(msg)
if data['request_id']: utils_stats.set_progress(data['request_id'], 'Done!')
time.sleep(1)

View File

@@ -0,0 +1,46 @@
import logging
logger = logging.getLogger(__name__)
import os
import smtplib
from datetime import datetime, timedelta
from django.core.mail import send_mail
from . import utils
from .. import settings
EMAIL_DIR = os.path.join(settings.BASE_DIR, 'apiserver/api/emails/')
def send_welcome_email(member):
vetting_date = member.application_date + timedelta(days=28)
def replace_fields(text):
return text.replace(
'[name]', member.first_name,
).replace(
'[username]', member.user.username,
).replace(
'[date]', vetting_date.strftime('%A, %B %d'),
)
with open(EMAIL_DIR + 'welcome.txt', 'r') as f:
email_text = replace_fields(f.read())
with open(EMAIL_DIR + 'welcome.html', 'r') as f:
email_html = replace_fields(f.read())
try:
send_mail(
subject='Welcome to Protospace!',
message=email_text,
from_email=None, # defaults to DEFAULT_FROM_EMAIL
recipient_list=[member.user.email, 'portal@tannercollin.com'],
html_message=email_html,
)
logger.info('Sent welcome email:\n' + email_text)
except smtplib.SMTPException as e:
msg = 'Problem sending welcome email to ' + member.user.email + ': ' + str(e)
utils.alert_tanner(msg)
logger.exception(msg)

View File

@@ -353,7 +353,7 @@ def process_paypal_ipn(data):
defaults=dict(user=user),
)
if custom_json.get('category', False) in ['Snacks', 'OnAcct', 'Donation']:
if custom_json.get('category', False) in ['Snacks', 'OnAcct', 'Donation', 'Consumables']:
logger.info('IPN - Category matched')
update_ipn(ipn, 'Accepted, category')
return create_category_tx(data, member, custom_json)

View File

@@ -4,7 +4,7 @@ logger = logging.getLogger(__name__)
from django.contrib.auth.models import User, Group
from django.shortcuts import get_object_or_404, redirect
from django.db import transaction
from django.db.models import Max, F, Count
from django.db.models import Max, F, Count, Q
from django.db.utils import OperationalError
from django.http import HttpResponse, Http404, FileResponse
from django.core.files.base import File
@@ -230,7 +230,7 @@ class CardViewSet(Base, Create, Retrieve, Update, Destroy):
class CourseViewSet(Base, List, Retrieve, Create, Update):
permission_classes = [AllowMetadata | IsAuthenticated, IsAdminOrReadOnly | IsInstructorOrReadOnly]
permission_classes = [AllowMetadata | IsAuthenticatedOrReadOnly, IsAdminOrReadOnly | IsInstructorOrReadOnly]
queryset = models.Course.objects.annotate(date=Max('sessions__datetime')).order_by('-date')
def get_serializer_class(self):
@@ -241,11 +241,27 @@ class CourseViewSet(Base, List, Retrieve, Create, Update):
class SessionViewSet(Base, List, Retrieve, Create, Update):
permission_classes = [AllowMetadata | IsAuthenticated, IsAdminOrReadOnly | IsInstructorOrReadOnly]
permission_classes = [AllowMetadata | IsAuthenticatedOrReadOnly, IsAdminOrReadOnly | IsInstructorOrReadOnly]
def get_queryset(self):
if self.action == 'list':
return models.Session.objects.order_by('-datetime')[:50]
week_ago = now() - datetime.timedelta(days=7)
year_ago = now() - datetime.timedelta(days=365)
return models.Session.objects.annotate(
course_count=Count(
'course__sessions',
filter=Q(
course__sessions__datetime__gte=year_ago,
),
),
).filter(
datetime__gte=week_ago,
).order_by(
'-course_count',
'-course_id',
'datetime',
)
else:
return models.Session.objects.all()
@@ -323,7 +339,7 @@ class TrainingViewSet(Base, Retrieve, Create, Update):
if data.get('member_id', None):
if not (is_admin_director(user) or session.instructor == user):
raise exceptions.ValidationError('Not allowed to register others')
raise exceptions.ValidationError(dict(non_field_errors='Not allowed to register others'))
member = get_object_or_404(models.Member, id=data['member_id'])
user = member.user
@@ -338,9 +354,9 @@ class TrainingViewSet(Base, Retrieve, Create, Update):
else:
training = models.Training.objects.filter(user=user, session=session)
if training.exists():
raise exceptions.ValidationError('Already registered')
raise exceptions.ValidationError(dict(non_field_errors='Already registered'))
if user == session.instructor:
raise exceptions.ValidationError('You are teaching this session')
raise exceptions.ValidationError(dict(non_field_errors='You are teaching this session'))
if status == 'Waiting for payment' and session.cost == 0:
status = 'Confirmed'
serializer.save(user=user, attendance_status=status)
@@ -736,12 +752,23 @@ class PasteView(views.APIView):
permission_classes = [IsAuthenticatedOrReadOnly]
def get(self, request):
return Response(dict(paste=cache.get('paste', '')))
if request.user.id == 9:
key = 'special_paste'
logging.info('Using special paste for a special someone.')
else:
key = 'paste'
return Response(dict(paste=cache.get(key, '')))
def post(self, request):
if 'paste' in request.data:
cache.set('paste', request.data['paste'][:20000])
return Response(dict(paste=cache.get('paste', '')))
if request.user.id == 9:
key = 'special_paste'
logging.info('Using special paste for a special someone.')
else:
key = 'paste'
cache.set(key, request.data['paste'][:20000])
return Response(dict(paste=cache.get(key, '')))
else:
raise exceptions.ValidationError(dict(paste='This field is required.'))

View File

@@ -282,7 +282,7 @@ DEFAULT_FROM_EMAIL = 'Protospace Portal <portal@mg.protospace.ca>'
if DEBUG: logger.info('Debug mode ON')
logger.info('Test logging for each thread')
APP_VERSION = 1 # TODO: automate this
APP_VERSION = 2 # TODO: automate this
#import logging_tree
#logging_tree.printout()