You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1087 lines
40 KiB

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()
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
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