import logging
logger = logging . getLogger ( __name__ )
from django . contrib . auth . models import User , Group
from django . shortcuts import get_object_or_404
from django . db . models import Max , F , Count , Q , Sum
from django . utils . timezone import now
from rest_framework import serializers
from rest_framework . exceptions import ValidationError
from rest_framework . validators import UniqueValidator
from rest_auth . registration . serializers import RegisterSerializer
from rest_auth . serializers import PasswordChangeSerializer , PasswordResetSerializer , PasswordResetConfirmSerializer , LoginSerializer
from rest_auth . serializers import UserDetailsSerializer
import re
import datetime , time , calendar
from . import models , fields , utils , utils_ldap , utils_auth , utils_stats
from . . import settings , secrets
from . permissions import is_admin_director
class UsageSerializer ( serializers . ModelSerializer ) :
first_name = serializers . SerializerMethodField ( )
class Meta :
model = models . Usage
fields = ' __all__ '
def get_first_name ( self , obj ) :
return obj . user . member . preferred_name
class ProtocoinTransactionSerializer ( serializers . ModelSerializer ) :
class Meta :
model = models . Transaction
fields = [
' id ' ,
' date ' ,
' protocoin ' ,
' account_type ' ,
' category ' ,
]
class TransactionSerializer ( serializers . ModelSerializer ) :
# fields directly from old portal. replace with slugs we want
account_type = serializers . ChoiceField ( [
' Interac ' ,
' TD Chequing ' ,
' Dream Pmt ' ,
' PayPal ' ,
' Square Pmt ' ,
' Member ' ,
' Clearing ' ,
' Cash ' ,
' Protocoin ' ,
] )
category = serializers . ChoiceField ( [
' Membership ' ,
' OnAcct ' ,
' Snacks ' ,
' Donation ' ,
' Consumables ' ,
' Purchases ' ,
' Garage Sale ' ,
' Reimburse ' ,
' Other ' ,
' Exchange ' ,
] )
member_id = serializers . SerializerMethodField ( )
member_name = serializers . SerializerMethodField ( )
date = serializers . DateField ( )
report_type = serializers . ChoiceField ( [
' Unmatched Member ' ,
' Unmatched Purchase ' ,
' User Flagged ' ,
] , allow_null = True , required = False )
number_of_membership_months = serializers . IntegerField ( max_value = 36 , min_value = - 36 , default = 0 )
recorder = serializers . SerializerMethodField ( )
amount = serializers . DecimalField ( max_digits = 7 , decimal_places = 2 , default = 0 )
protocoin = serializers . DecimalField ( max_digits = 7 , decimal_places = 2 , default = 0 )
class Meta :
model = models . Transaction
fields = ' __all__ '
read_only_fields = [
' id ' ,
' user ' ,
' recorder ' ,
' paypal_txn_id ' ,
' paypal_txn_type ' ,
' paypal_payer_id ' ,
]
def validate_transaction ( self , validated_data ) :
if not self . initial_data . get ( ' member_id ' , None ) :
raise ValidationError ( dict ( member_id = ' This field is required. ' ) )
member = get_object_or_404 ( models . Member , id = self . initial_data [ ' member_id ' ] )
validated_data [ ' user ' ] = member . user
if validated_data [ ' account_type ' ] == ' Protocoin ' :
validated_data [ ' amount ' ] = 0
else :
validated_data [ ' protocoin ' ] = 0
if validated_data [ ' category ' ] != ' Membership ' :
validated_data [ ' number_of_membership_months ' ] = 0
if validated_data [ ' category ' ] == ' Membership ' and not validated_data [ ' number_of_membership_months ' ] :
raise ValidationError ( dict ( number_of_membership_months = ' This field is required. ' ) )
if validated_data [ ' account_type ' ] == ' Protocoin ' and validated_data [ ' category ' ] == ' Exchange ' :
raise ValidationError ( dict ( category = ' Can \' t purchase Protocoin with Protocoin. ' ) )
if validated_data [ ' category ' ] == ' Exchange ' :
if validated_data [ ' amount ' ] == 0 :
raise ValidationError ( dict ( category = ' Can \' t purchase 0 Protocoin. ' ) )
validated_data [ ' protocoin ' ] = validated_data [ ' amount ' ]
if validated_data [ ' account_type ' ] == ' Protocoin ' :
if validated_data [ ' protocoin ' ] == 0 :
raise ValidationError ( dict ( account_type = ' Can \' t have a 0.00 protocoin transaction. ' ) )
if validated_data [ ' account_type ' ] not in [ ' Clearing ' , ' Protocoin ' ] :
if validated_data [ ' amount ' ] == 0 :
raise ValidationError ( dict ( account_type = ' Can \' t have a $0.00 {} transaction. Do you want " Membership Adjustment " ? ' . format ( validated_data [ ' account_type ' ] ) ) )
if validated_data [ ' account_type ' ] in [ ' Interac ' , ' Dream Pmt ' , ' Square Pmt ' , ' PayPal ' ] :
if not validated_data . get ( ' reference_number ' , None ) :
raise ValidationError ( dict ( reference_number = ' This field is required. ' ) )
return validated_data
def create ( self , validated_data ) :
validated_data = self . validate_transaction ( validated_data )
if validated_data [ ' protocoin ' ] < 0 :
user = validated_data [ ' user ' ]
current_protocoin = user . transactions . aggregate ( Sum ( ' protocoin ' ) ) [ ' protocoin__sum ' ] or 0
new_protocoin = current_protocoin + validated_data [ ' protocoin ' ]
if new_protocoin < 0 :
raise ValidationError ( dict ( category = ' Insufficient funds. Member only has {} protocoin. ' . format ( current_protocoin ) ) )
if validated_data [ ' account_type ' ] == ' PayPal ' :
msg = ' Manual PayPal transaction added: \n ' + str ( validated_data )
utils . alert_tanner ( msg )
if validated_data [ ' account_type ' ] == ' Protocoin ' :
msg = ' Manual Protocoin transaction added: \n ' + str ( validated_data )
utils . alert_tanner ( msg )
return super ( ) . create ( validated_data )
def update ( self , instance , validated_data ) :
validated_data = self . validate_transaction ( validated_data )
if validated_data [ ' protocoin ' ] < 0 :
user = validated_data [ ' user ' ]
# when updating, we need to subtract out the transaction being edited
current_protocoin = ( user . transactions . aggregate ( Sum ( ' protocoin ' ) ) [ ' protocoin__sum ' ] or 0 ) - instance . protocoin
new_protocoin = current_protocoin + validated_data [ ' protocoin ' ]
if new_protocoin < 0 :
msg = ' Negative Protocoin transaction updated: \n ' + str ( validated_data )
utils . alert_tanner ( msg )
return super ( ) . update ( instance , validated_data )
def get_member_id ( self , obj ) :
if not obj . user : return None
return obj . user . member . id
def get_member_name ( self , obj ) :
if not obj . user : return ' Unknown '
member = obj . user . member
return member . preferred_name + ' ' + member . last_name
def get_recorder ( self , obj ) :
if obj . recorder :
return obj . recorder . username
else :
return None
# member viewing other members
# hide info for non-vetted members so someone sitting
# in our parking lot can't scrape all our info
class OtherMemberSerializer ( serializers . ModelSerializer ) :
pinball_score = serializers . IntegerField ( required = False )
last_name = serializers . SerializerMethodField ( )
storage = serializers . SerializerMethodField ( )
class Meta :
model = models . Member
fields = [
' id ' ,
' preferred_name ' ,
' last_name ' ,
' status ' ,
' current_start_date ' ,
' application_date ' ,
' photo_small ' ,
' public_bio ' ,
' pinball_score ' ,
' storage ' ,
]
def get_last_name ( self , obj ) :
if len ( obj . last_name ) :
return obj . last_name [ 0 ] + ' . '
else :
return ' '
def get_storage ( self , obj ) :
serializer = SimpleStorageSpaceSerializer ( data = obj . user . storage , many = True )
serializer . is_valid ( )
return serializer . data
# vetted member viewing other members
class VettedOtherMemberSerializer ( serializers . ModelSerializer ) :
pinball_score = serializers . IntegerField ( required = False )
storage = serializers . SerializerMethodField ( )
class Meta :
model = models . Member
fields = [
' id ' ,
' preferred_name ' ,
' last_name ' ,
' status ' ,
' current_start_date ' ,
' application_date ' ,
' photo_small ' ,
' photo_large ' ,
' public_bio ' ,
' pinball_score ' ,
' storage ' ,
]
def get_storage ( self , obj ) :
serializer = StorageSpaceSerializer ( data = obj . user . storage , many = True )
serializer . is_valid ( )
return serializer . data
# member viewing his own details
class MemberSerializer ( serializers . ModelSerializer ) :
photo = serializers . ImageField ( write_only = True , required = False )
crop = serializers . CharField ( write_only = True , required = False )
email = fields . UserEmailField ( serializers . EmailField )
phone = serializers . CharField ( )
protocoin = serializers . SerializerMethodField ( )
sponsorship = OtherMemberSerializer ( many = True , read_only = True )
sponsored_by = OtherMemberSerializer ( many = True , read_only = True )
total_protocoin = serializers . SerializerMethodField ( )
class Meta :
model = models . Member
fields = ' __all__ '
read_only_fields = [
' id ' ,
' is_director ' ,
' is_staff ' ,
' is_instructor ' ,
' first_name ' ,
' last_name ' ,
' status ' ,
' expire_date ' ,
' current_start_date ' ,
' application_date ' ,
' vetted_date ' ,
' paused_date ' ,
' monthly_fees ' ,
' photo_large ' ,
' photo_medium ' ,
' photo_small ' ,
' member_forms ' ,
' card_photo ' ,
' user ' ,
' old_email ' ,
' orientation_date ' ,
' lathe_cert_date ' ,
' mill_cert_date ' ,
' wood_cert_date ' ,
' wood2_cert_date ' ,
' tormach_cnc_cert_date ' ,
' precix_cnc_cert_date ' ,
' rabbit_cert_date ' ,
' trotec_cert_date ' ,
' is_allowed_entry ' ,
' mediawiki_username ' ,
' signup_helper ' ,
]
def get_protocoin ( self , obj ) :
transactions = obj . user . transactions
total = transactions . aggregate ( Sum ( ' protocoin ' ) ) [ ' protocoin__sum ' ] or 0
return total
def get_total_protocoin ( self , obj ) :
transactions = models . Transaction . objects
total = transactions . aggregate ( Sum ( ' protocoin ' ) ) [ ' protocoin__sum ' ] or 0
return total
def update ( self , instance , validated_data ) :
instance . user . email = validated_data . get ( ' email ' , instance . user . email )
instance . user . save ( )
photo = validated_data . get ( ' photo ' , None )
crop = validated_data . get ( ' crop ' , None )
if photo :
small , medium , large = utils . process_image_upload ( photo , crop )
instance . photo_small = small
instance . photo_medium = medium
instance . photo_large = large
helper_id = self . initial_data . get ( ' helper_id ' , None )
if helper_id :
signup_helper = get_object_or_404 ( models . Member , id = helper_id )
instance . signup_helper = signup_helper . user
is_student = self . initial_data . get ( ' is_student ' , False )
if is_student :
instance . monthly_fees = 35
if ' discourse_username ' in validated_data :
changed = validated_data [ ' discourse_username ' ] != instance . discourse_username
if changed and utils_auth . discourse_is_configured ( ) :
username = instance . discourse_username
new_username = validated_data [ ' discourse_username ' ]
logger . info ( ' Changing discourse_username from %s to %s ' , username , new_username )
if utils_auth . change_discourse_username ( username , new_username ) != 200 :
msg = ' Problem connecting to Discourse Auth server: change username. '
utils . alert_tanner ( msg )
logger . info ( msg )
raise ValidationError ( dict ( discourse_username = ' Invalid Discourse username. ' ) )
if validated_data . get ( ' allow_last_scanned ' , None ) == True :
changed = validated_data [ ' allow_last_scanned ' ] != instance . allow_last_scanned
ONE_WEEK = now ( ) - datetime . timedelta ( days = 7 )
if changed and models . HistoryChange . objects . filter (
field = ' allow_last_scanned ' ,
index__history_user__member__id = instance . id ,
index__owner_id = instance . id ,
index__history_date__gte = ONE_WEEK ,
) . count ( ) > = 6 :
msg = ' Member allow_last_scanned rate limit exceeded by: ' + instance . preferred_name + ' ' + instance . last_name
utils . alert_tanner ( msg )
logger . info ( msg )
raise ValidationError ( dict ( allow_last_scanned = ' You \' re doing that too often. ' ) )
return super ( ) . update ( instance , validated_data )
# admin viewing member details
class AdminMemberSerializer ( MemberSerializer ) :
phone = serializers . CharField ( required = False )
monthly_fees = serializers . ChoiceField ( [ 10 , 30 , 35 , 50 , 55 ] )
class Meta :
model = models . Member
fields = ' __all__ '
read_only_fields = [
' id ' ,
' status ' ,
' expire_date ' ,
' paused_date ' ,
' photo_large ' ,
' photo_medium ' ,
' photo_small ' ,
' member_forms ' ,
' card_photo ' ,
' user ' ,
' old_email ' ,
' is_director ' ,
' is_staff ' ,
' mediawiki_username ' ,
]
def update ( self , instance , validated_data ) :
if ' is_allowed_entry ' in validated_data :
changed = validated_data [ ' is_allowed_entry ' ] != instance . is_allowed_entry
if changed :
utils_stats . changed_card ( )
if ' precix_cnc_cert_date ' in validated_data :
changed = validated_data [ ' precix_cnc_cert_date ' ] != instance . precix_cnc_cert_date
if changed :
if validated_data [ ' precix_cnc_cert_date ' ] :
utils_ldap . add_to_group ( instance , ' CNC-Precix-Users ' )
else :
utils_ldap . remove_from_group ( instance , ' CNC-Precix-Users ' )
if ' rabbit_cert_date ' in validated_data :
changed = validated_data [ ' rabbit_cert_date ' ] != instance . rabbit_cert_date
if changed :
if validated_data [ ' rabbit_cert_date ' ] :
utils_ldap . add_to_group ( instance , ' Laser Users ' )
else :
utils_ldap . remove_from_group ( instance , ' Laser Users ' )
if ' trotec_cert_date ' in validated_data :
changed = validated_data [ ' trotec_cert_date ' ] != instance . trotec_cert_date
if changed :
if validated_data [ ' trotec_cert_date ' ] :
utils_ldap . add_to_group ( instance , ' Trotec Users ' )
else :
utils_ldap . remove_from_group ( instance , ' Trotec Users ' )
return super ( ) . update ( instance , validated_data )
# member viewing member list or search result
class SearchSerializer ( serializers . Serializer ) :
q = serializers . CharField ( write_only = True , max_length = 64 )
seq = serializers . IntegerField ( write_only = True )
member = serializers . SerializerMethodField ( )
def get_member ( self , obj ) :
serializer = OtherMemberSerializer ( obj )
return serializer . data
# vetted member viewing member list or search result
class VettedSearchSerializer ( serializers . Serializer ) :
q = serializers . CharField ( write_only = True , max_length = 64 )
seq = serializers . IntegerField ( write_only = True )
member = serializers . SerializerMethodField ( )
def get_member ( self , obj ) :
serializer = VettedOtherMemberSerializer ( obj )
return serializer . data
# instructor viewing search result
class InstructorSearchSerializer ( serializers . Serializer ) :
member = serializers . SerializerMethodField ( )
training = serializers . SerializerMethodField ( )
def get_member ( self , obj ) :
serializer = VettedOtherMemberSerializer ( obj )
return serializer . data
def get_training ( self , obj ) :
queryset = obj . user . training
serializer = UserTrainingSerializer ( data = queryset , many = True )
serializer . is_valid ( )
return serializer . data
# admin viewing search result
class AdminSearchSerializer ( serializers . Serializer ) :
cards = serializers . SerializerMethodField ( )
member = serializers . SerializerMethodField ( )
training = serializers . SerializerMethodField ( )
transactions = serializers . SerializerMethodField ( )
#usages = serializers.SerializerMethodField()
def get_member ( self , obj ) :
serializer = AdminMemberSerializer ( obj )
return serializer . data
def get_cards ( self , obj ) :
queryset = obj . user . cards
queryset = queryset . order_by ( ' -last_seen ' )
serializer = CardSerializer ( data = queryset , many = True )
serializer . is_valid ( )
return serializer . data
def get_training ( self , obj ) :
queryset = obj . user . training
serializer = UserTrainingSerializer ( data = queryset , many = True )
serializer . is_valid ( )
return serializer . data
def get_transactions ( self , obj ) :
queryset = obj . user . transactions
queryset = queryset . order_by ( ' -date ' , ' -id ' )
serializer = TransactionSerializer ( data = queryset , many = True )
serializer . is_valid ( )
return serializer . data
#def get_usages(self, obj):
# queryset = obj.user.usages.order_by('-start_time')
# serializer = UsageSerializer(data=queryset, many=True)
# serializer.is_valid()
# return serializer.data
class CardSerializer ( serializers . ModelSerializer ) :
card_number = serializers . CharField ( validators = [ UniqueValidator (
queryset = models . Card . objects . all ( ) ,
message = ' Card number already exists. '
) ] )
member_id = serializers . SerializerMethodField ( )
active_status = serializers . ChoiceField ( [
' card_active ' ,
' card_inactive ' ,
] )
class Meta :
model = models . Card
fields = ' __all__ '
read_only_fields = [
' id ' ,
' last_seen ' ,
' last_seen_at ' ,
' user ' ,
]
def create ( self , validated_data ) :
if not self . initial_data . get ( ' member_id ' , None ) :
raise ValidationError ( dict ( member_id = ' This field is required. ' ) )
member = get_object_or_404 ( models . Member , id = self . initial_data [ ' member_id ' ] )
validated_data [ ' user ' ] = member . user
if not member . vetted_date :
raise ValidationError ( dict ( non_field_errors = ' Member not vetted yet. ' ) )
return super ( ) . create ( validated_data )
def update ( self , instance , validated_data ) :
if not self . initial_data . get ( ' member_id ' , None ) :
raise ValidationError ( dict ( member_id = ' This field is required. ' ) )
member = get_object_or_404 ( models . Member , id = self . initial_data [ ' member_id ' ] )
validated_data [ ' user ' ] = member . user
return super ( ) . update ( instance , validated_data )
def get_member_id ( self , obj ) :
if not obj . user : return None
return obj . user . member . id
class SimpleStorageSpaceSerializer ( serializers . ModelSerializer ) :
class Meta :
model = models . StorageSpace
fields = ' __all__ '
class StorageSpaceSerializer ( serializers . ModelSerializer ) :
member_id = serializers . SerializerMethodField ( )
member_name = serializers . SerializerMethodField ( )
member_status = serializers . SerializerMethodField ( )
member_paused = serializers . SerializerMethodField ( )
class Meta :
model = models . StorageSpace
fields = ' __all__ '
read_only_fields = [
' id ' ,
' shelf_id ' ,
' location ' ,
' user ' ,
]
def update ( self , instance , validated_data ) :
member_id = self . initial_data . get ( ' member_id ' , None )
if member_id :
member = get_object_or_404 ( models . Member , id = member_id )
validated_data [ ' user ' ] = member . user
else :
validated_data [ ' user ' ] = None
return super ( ) . update ( instance , validated_data )
def get_member_id ( self , obj ) :
if not obj . user : return None
return obj . user . member . id
def get_member_name ( self , obj ) :
if not obj . user : return None
member = obj . user . member
return member . preferred_name + ' ' + member . last_name
def get_member_status ( self , obj ) :
if not obj . user : return None
return obj . user . member . status
def get_member_paused ( self , obj ) :
if not obj . user : return None
return obj . user . member . paused_date
class TrainingSerializer ( serializers . ModelSerializer ) :
attendance_status = serializers . ChoiceField ( [
' Waiting for payment ' ,
' Withdrawn ' ,
' Rescheduled ' ,
' Try-again ' ,
' No-show ' ,
' Attended ' ,
' Confirmed '
] )
session = serializers . PrimaryKeyRelatedField ( queryset = models . Session . objects . all ( ) )
student_name = serializers . SerializerMethodField ( )
student_email = serializers . SerializerMethodField ( )
student_id = serializers . SerializerMethodField ( )
class Meta :
model = models . Training
fields = ' __all__ '
read_only_fields = [ ' user ' , ' sign_up_date ' , ' paid_date ' ]
def get_student_name ( self , obj ) :
member = obj . user . member
return member . preferred_name + ' ' + member . last_name
def get_student_email ( self , obj ) :
return obj . user . email
def get_student_id ( self , obj ) :
return obj . user . member . id
def update ( self , instance , validated_data ) :
if validated_data [ ' attendance_status ' ] == ' Waiting for payment ' and instance . paid_date :
validated_data [ ' attendance_status ' ] = ' Confirmed '
return super ( ) . update ( instance , validated_data )
class StudentTrainingSerializer ( TrainingSerializer ) :
attendance_status = serializers . ChoiceField ( [ ' Waiting for payment ' , ' Withdrawn ' ] )
class CourseSerializer ( serializers . ModelSerializer ) :
num_interested = serializers . IntegerField ( read_only = True )
class Meta :
model = models . Course
fields = [ ' id ' , ' name ' , ' is_old ' , ' description ' , ' tags ' , ' num_interested ' ]
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 )
max_students = serializers . IntegerField ( min_value = 1 , max_value = 50 , allow_null = True )
cost = serializers . DecimalField ( max_digits = None , decimal_places = 2 , min_value = 0 , max_value = 200 )
class Meta :
model = models . Session
fields = ' __all__ '
read_only_fields = [ ' old_instructor ' ]
def get_student_count ( self , obj ) :
return len ( [ x for x in obj . students . all ( ) if x . attendance_status != ' Withdrawn ' ] )
def get_course_data ( self , obj ) :
return CourseSerializer ( obj . course ) . data
def get_instructor_name ( self , obj ) :
if obj . instructor and hasattr ( obj . instructor , ' member ' ) :
name = ' {} {} . ' . format ( obj . instructor . member . preferred_name , obj . instructor . member . last_name [ 0 ] )
else :
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
def create ( self , validated_data ) :
if validated_data [ ' datetime ' ] < now ( ) - datetime . timedelta ( days = 2 ) :
msg = ' Past class creation detected: \n ' + str ( validated_data )
utils . alert_tanner ( msg )
raise ValidationError ( dict ( non_field_errors = ' Class can \' t be in the past. ' ) )
return super ( ) . create ( validated_data )
def update ( self , instance , validated_data ) :
if not self . initial_data . get ( ' instructor_id ' , None ) :
raise ValidationError ( dict ( instructor_id = ' This field is required. ' ) )
if validated_data [ ' datetime ' ] < now ( ) - datetime . timedelta ( days = 2 ) :
msg = ' Past class modification detected: \n ' + str ( validated_data )
utils . alert_tanner ( msg )
raise ValidationError ( dict ( non_field_errors = ' Can \' t modify past class. ' ) )
member = get_object_or_404 ( models . Member , id = self . initial_data [ ' instructor_id ' ] )
if not ( is_admin_director ( member . user ) or member . is_instructor ) :
raise ValidationError ( dict ( instructor_id = ' Member is not an instructor. ' ) )
validated_data [ ' instructor ' ] = member . user
return super ( ) . update ( instance , validated_data )
class SessionListSerializer ( SessionSerializer ) :
students = None
class CourseDetailSerializer ( serializers . ModelSerializer ) :
sessions = SessionListSerializer ( many = True , read_only = True )
name = serializers . CharField ( max_length = 100 )
description = fields . HTMLField ( max_length = 6000 )
suggestion = serializers . SerializerMethodField ( )
class Meta :
model = models . Course
fields = ' __all__ '
def get_suggestion ( self , obj ) :
def iter_dates ( ) :
start_of_month = utils . today_alberta_tz ( ) . replace ( day = 1 )
for i in range ( 90 ) :
yield start_of_month + datetime . timedelta ( days = i )
def iter_matching_dates ( weekday , week_num = False ) :
week_num_counts = [ 0 ] * 13
for date in iter_dates ( ) :
if date . weekday ( ) == weekday :
week_num_counts [ date . month ] + = 1
if week_num and week_num_counts [ date . month ] != week_num :
continue
yield date
def next_date ( weekday , week_num = False , fake_start = False ) :
start = fake_start or utils . today_alberta_tz ( )
for date in iter_matching_dates ( weekday , week_num ) :
if date > start :
return date
raise
def course_is_usually_monthly ( course ) :
two_months_ago = utils . now_alberta_tz ( ) - datetime . timedelta ( days = 61 )
recent_sessions = obj . sessions . filter ( datetime__gte = two_months_ago )
if recent_sessions . count ( ) < 3 :
return True
else :
return False
prev_session = obj . sessions . order_by ( ' datetime ' ) . last ( )
if obj . id == 273 : # monthly clean 10:00 AM 3rd Saturday of each month
date = next_date ( calendar . SATURDAY , week_num = 3 )
time = datetime . time ( 10 , 0 )
dt = datetime . datetime . combine ( date , time )
dt = utils . TIMEZONE_CALGARY . localize ( dt )
cost = 0
max_students = None
elif obj . id == 317 :
# members' meeting 7:00 PM 3rd Thursday of odd months, Wednesday of even months
# but December's gets skipped
next_month = next_date ( calendar . WEDNESDAY , week_num = 3 ) . month
if next_month == 12 :
one_month_ahead = utils . today_alberta_tz ( ) + datetime . timedelta ( days = 31 )
date = next_date ( calendar . THURSDAY , week_num = 3 , fake_start = one_month_ahead )
elif next_month % 2 == 0 :
date = next_date ( calendar . WEDNESDAY , week_num = 3 )
else :
date = next_date ( calendar . THURSDAY , week_num = 3 )
time = datetime . time ( 19 , 0 )
dt = datetime . datetime . combine ( date , time )
dt = utils . TIMEZONE_CALGARY . localize ( dt )
cost = 0
max_students = None
elif prev_session :
dt = prev_session . datetime
if course_is_usually_monthly ( obj ) :
offset_weeks = 4
else :
offset_weeks = 1
dt = dt + datetime . timedelta ( weeks = offset_weeks )
five_days_from_now = utils . now_alberta_tz ( ) + datetime . timedelta ( days = 5 )
while dt < five_days_from_now :
dt = dt + datetime . timedelta ( weeks = offset_weeks )
cost = prev_session . cost
max_students = prev_session . max_students
else :
return None
return dict ( datetime = dt , cost = str ( cost ) , max_students = max_students )
class UserTrainingSerializer ( serializers . ModelSerializer ) :
session = SessionListSerializer ( )
class Meta :
model = models . Training
exclude = [ ' user ' ]
depth = 2
class InterestSerializer ( serializers . ModelSerializer ) :
course = serializers . PrimaryKeyRelatedField ( queryset = models . Course . objects . all ( ) )
class Meta :
model = models . Interest
fields = ' __all__ '
read_only_fields = [ ' user ' , ' satisfied_by ' ]
class UserSerializer ( serializers . ModelSerializer ) :
training = UserTrainingSerializer ( many = True )
member = MemberSerializer ( )
transactions = serializers . SerializerMethodField ( )
training = serializers . SerializerMethodField ( )
interests = InterestSerializer ( many = True )
door_code = serializers . SerializerMethodField ( )
wifi_pass = serializers . SerializerMethodField ( )
app_version = serializers . SerializerMethodField ( )
class Meta :
model = User
fields = [
' id ' ,
' username ' ,
' member ' ,
' transactions ' ,
' cards ' ,
' training ' ,
' is_staff ' ,
' door_code ' ,
' wifi_pass ' ,
' app_version ' ,
#'usages',
' interests ' ,
' storage ' ,
]
depth = 1
def get_transactions ( self , obj ) :
queryset = models . Transaction . objects . filter ( user = obj )
queryset = queryset . select_related ( ' user ' , ' user__member ' )
queryset = queryset . exclude ( category = ' Memberships:Fake Months ' )
queryset = queryset . order_by ( ' -id ' , ' -date ' )
serializer = TransactionSerializer ( data = queryset , many = True )
serializer . is_valid ( )
return serializer . data
def get_training ( self , obj ) :
queryset = obj . training
queryset = queryset . select_related (
' session ' ,
' session__course ' ,
' session__instructor ' ,
' session__instructor__member '
)
queryset = queryset . prefetch_related ( ' session__students ' )
queryset = queryset . order_by ( ' -id ' )
serializer = UserTrainingSerializer ( data = queryset , many = True )
serializer . is_valid ( )
return serializer . data
def get_door_code ( self , obj ) :
if not obj . member . paused_date and obj . cards . count ( ) :
return secrets . DOOR_CODE
else :
return None
def get_wifi_pass ( self , obj ) :
if not obj . member . paused_date :
return secrets . WIFI_PASS
else :
return None
def get_app_version ( self , obj ) :
return settings . APP_VERSION
class MyRegisterSerializer ( RegisterSerializer ) :
first_name = serializers . CharField ( max_length = 32 )
last_name = serializers . CharField ( max_length = 32 )
preferred_name = serializers . CharField ( max_length = 32 )
request_id = serializers . CharField ( required = False )
def validate_username ( self , username ) :
if re . search ( r ' [^a-z.] ' , username ) :
raise ValidationError ( ' Invalid characters. ' )
if ' .. ' in username :
raise ValidationError ( ' Can \' t have double periods. Remove spaces. ' )
if username . startswith ( ' . ' ) or username . endswith ( ' . ' ) :
raise ValidationError ( ' Can \' t start or end with periods. ' )
return super ( ) . validate_username ( username )
def custom_signup ( self , request , user ) :
data = request . data
if not utils . is_request_from_protospace ( request ) :
logger . info ( ' Request not from protospace ' )
user . delete ( )
raise ValidationError ( dict ( non_field_errors = ' Can only register from Protospace. ' ) )
if data [ ' request_id ' ] : utils_stats . set_progress ( data [ ' request_id ' ] , ' Registering... ' )
utils . register_user ( data , user )
class MyPasswordChangeSerializer ( PasswordChangeSerializer ) :
def save ( self ) :
request_id = self . request . data . get ( ' request_id ' , ' ' )
data = dict (
username = self . user . username ,
password1 = self . request . data [ ' new_password1 ' ] ,
)
if utils_ldap . is_configured ( ) :
if request_id : utils_stats . set_progress ( request_id , ' Changing LDAP password... ' )
if utils_ldap . set_password ( data ) != 200 :
msg = ' Problem connecting to LDAP server: set. '
utils . alert_tanner ( msg )
logger . info ( msg )
raise ValidationError ( dict ( non_field_errors = msg ) )
data = dict (
username = self . user . username ,
password = self . data [ ' new_password1 ' ] ,
email = self . user . email ,
first_name = self . user . member . preferred_name ,
)
data [ ' username ' ] = self . user . member . mediawiki_username or self . user . username
if utils_auth . wiki_is_configured ( ) :
if request_id : utils_stats . set_progress ( request_id , ' Changing Wiki password... ' )
if utils_auth . set_wiki_password ( data ) != 200 :
msg = ' Problem connecting to Wiki Auth server: set. '
utils . alert_tanner ( msg )
logger . info ( msg )
raise ValidationError ( dict ( non_field_errors = msg ) )
data [ ' username ' ] = self . user . member . discourse_username or self . user . username
if utils_auth . discourse_is_configured ( ) :
if request_id : utils_stats . set_progress ( request_id , ' Changing Discourse password... ' )
if utils_auth . set_discourse_password ( data ) != 200 :
msg = ' Problem connecting to Discourse Auth server: set. '
utils . alert_tanner ( msg )
logger . info ( msg )
raise ValidationError ( dict ( non_field_errors = msg ) )
if not self . user . member . discourse_username :
self . user . member . discourse_username = self . user . username
self . user . member . save ( )
if request_id : utils_stats . set_progress ( request_id , ' Changing Spaceport password... ' )
time . sleep ( 1 )
super ( ) . save ( )
class MyPasswordResetSerializer ( PasswordResetSerializer ) :
def validate_email ( self , email ) :
if not User . objects . filter ( email__iexact = email ) . exists ( ) :
logging . info ( ' Email not found: ' + email )
raise ValidationError ( ' Not found. ' )
return super ( ) . validate_email ( email )
def save ( self ) :
email = self . data [ ' email ' ]
member = User . objects . get ( email__iexact = email ) . member
logging . info ( ' Password reset requested for: {} - {} {} ( {} ) ' . format ( email , member . preferred_name , member . last_name , member . id ) )
super ( ) . save ( )
class MyPasswordResetConfirmSerializer ( PasswordResetConfirmSerializer ) :
def save ( self ) :
request_id = self . data [ ' token ' ] [ - 10 : ]
data = dict (
username = self . user . username ,
password1 = self . data [ ' new_password1 ' ] ,
)
if utils_ldap . is_configured ( ) :
if request_id : utils_stats . set_progress ( request_id , ' Changing LDAP password... ' )
if utils_ldap . set_password ( data ) != 200 :
msg = ' Problem connecting to LDAP server: set. '
utils . alert_tanner ( msg )
logger . info ( msg )
raise ValidationError ( dict ( non_field_errors = msg ) )
data = dict (
username = self . user . username ,
password = self . data [ ' new_password1 ' ] ,
email = self . user . email ,
first_name = self . user . member . preferred_name ,
)
data [ ' username ' ] = self . user . member . mediawiki_username or self . user . username
if utils_auth . wiki_is_configured ( ) :
if request_id : utils_stats . set_progress ( request_id , ' Changing Wiki password... ' )
if utils_auth . set_wiki_password ( data ) != 200 :
msg = ' Problem connecting to Wiki Auth server: set. '
utils . alert_tanner ( msg )
logger . info ( msg )
raise ValidationError ( dict ( non_field_errors = msg ) )
data [ ' username ' ] = self . user . member . discourse_username or self . user . username
if utils_auth . discourse_is_configured ( ) :
if request_id : utils_stats . set_progress ( request_id , ' Changing Discourse password... ' )
if utils_auth . set_discourse_password ( data ) != 200 :
msg = ' Problem connecting to Discourse Auth server: set. '
utils . alert_tanner ( msg )
logger . info ( msg )
raise ValidationError ( dict ( non_field_errors = msg ) )
if not self . user . member . discourse_username :
self . user . member . discourse_username = self . user . username
self . user . member . save ( )
member = self . user . member
logging . info ( ' Password reset completed for: {} {} ( {} ) ' . format ( member . preferred_name , member . last_name , member . id ) )
if request_id : utils_stats . set_progress ( request_id , ' Success! You can now log in as: ' + self . user . username )
time . sleep ( 1 )
super ( ) . save ( )
class MemberCountSerializer ( serializers . ModelSerializer ) :
class Meta :
model = models . StatsMemberCount
fields = ' __all__ '
class SignupCountSerializer ( serializers . ModelSerializer ) :
month = serializers . SerializerMethodField ( )
class Meta :
model = models . StatsSignupCount
fields = ' __all__ '
def get_month ( self , obj ) :
return str ( obj . month ) [ : 7 ]
class SpaceActivitySerializer ( serializers . ModelSerializer ) :
class Meta :
model = models . StatsSpaceActivity
fields = ' __all__ '
class HistoryChangeSerializer ( serializers . ModelSerializer ) :
class Meta :
model = models . HistoryChange
fields = [ ' field ' , ' old ' , ' new ' ]
class HistorySerializer ( serializers . ModelSerializer ) :
changes = HistoryChangeSerializer ( many = True )
history_user = serializers . StringRelatedField ( )
class Meta :
model = models . HistoryIndex
fields = ' __all__ '
class SpaceportAuthSerializer ( LoginSerializer ) :
def authenticate ( self , * * kwargs ) :
user = super ( ) . authenticate ( * * kwargs )
if user :
data = self . context [ ' request ' ] . data . copy ( )
data [ ' email ' ] = user . email
data [ ' first_name ' ] = user . member . preferred_name
data [ ' username ' ] = user . member . mediawiki_username or user . username
utils_auth . set_wiki_password ( data )
data [ ' username ' ] = user . member . discourse_username or user . username
utils_auth . set_discourse_password ( data )
if not user . member . paused_date :
utils_auth . add_discourse_group_members ( ' protospace_members ' , [ data [ ' username ' ] ] )
if not user . member . discourse_username :
user . member . discourse_username = user . username
user . member . save ( )
return user
class MyLoginSerializer ( LoginSerializer ) :
def authenticate ( self , * * kwargs ) :
username = kwargs . get ( ' username ' , ' ' )
if ' your ' in username and ' own ' in username and ' name ' in username :
raise ValidationError ( dict ( username = ' *server explodes* ' ) )
if ' ' in username :
raise ValidationError ( dict ( username = ' Username shouldn \' t have spaces. Try " first.last " or " first.middle.last " . ' ) )
if ' first.last ' in username :
raise ValidationError ( dict ( username = ' Don \' t literally try " first.last " , use your own name. ' ) )
if ' first.middle.last ' in username :
raise ValidationError ( dict ( username = ' Don \' t literally try " first.middle.last " , use your own name. ' ) )
if not User . objects . filter ( username = username ) . exists ( ) :
raise ValidationError ( dict ( username = ' Username not found. Try " first.last " or " first.middle.last " . ' ) )
try :
_ = User . objects . get ( username = username ) . member
except User . member . RelatedObjectDoesNotExist :
raise ValidationError ( dict ( username = ' Can \' t log in as superuser. Make an account below. ' ) )
user = super ( ) . authenticate ( * * kwargs )
if not user :
raise ValidationError ( dict ( password = ' Incorrect password. Check caps lock. ' ) )
return user