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.
 
 
 
 

754 lines
26 KiB

import logging
logger = logging.getLogger(__name__)
from django.contrib.auth.models import User, Group
from django.shortcuts import get_object_or_404
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 time
from . import models, fields, utils, utils_ldap, utils_auth, utils_stats
from .. import settings, secrets
#class UsageSerializer(serializers.ModelSerializer):
# class Meta:
# model = models.UsageTrack
# fields = '__all__'
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',
])
info_source = serializers.ChoiceField([
'Web',
'DB Edit',
'System',
'Receipt or Stmt',
'Quicken Import',
'PayPal IPN',
'Auto',
'Nexus DB Bulk',
'IPN Trigger',
'Intranet Receipt',
'Automatic',
'Manual',
])
category = serializers.ChoiceField([
'Membership',
'OnAcct',
'Snacks',
'Donation',
'Consumables',
'Purchases',
'Garage Sale',
'Reimburse',
'Other',
])
member_id = serializers.IntegerField()
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)
recorder = serializers.SerializerMethodField()
class Meta:
model = models.Transaction
fields = '__all__'
read_only_fields = [
'id',
'last_seen_at',
'user',
'recorder',
'paypal_txn_id',
'paypal_payer_id',
]
def create(self, validated_data):
member = get_object_or_404(models.Member, id=validated_data['member_id'])
if member.user:
validated_data['user'] = member.user
return super().create(validated_data)
def update(self, instance, validated_data):
member = get_object_or_404(models.Member, id=validated_data['member_id'])
validated_data['user'] = member.user
return super().update(instance, validated_data)
def get_member_name(self, obj):
if not obj.member_id: return 'Unknown'
if obj.user:
member = obj.user.member
else:
member = models.Member.objects.get(id=obj.member_id)
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):
last_name = serializers.SerializerMethodField()
class Meta:
model = models.Member
fields = [
'id',
'preferred_name',
'last_name',
'status',
'current_start_date',
'application_date',
'photo_small',
'public_bio',
]
def get_last_name(self, obj):
if len(obj.last_name):
return obj.last_name[0] + '.'
else:
return ''
# vetted member viewing other members
class VettedOtherMemberSerializer(serializers.ModelSerializer):
class Meta:
model = models.Member
fields = [
'id',
'preferred_name',
'last_name',
'status',
'current_start_date',
'application_date',
'photo_small',
'photo_large',
'public_bio',
]
# 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()
street_address = serializers.CharField(required=False)
city = serializers.CharField(required=False)
postal_code = serializers.CharField(required=False)
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',
]
def update(self, instance, validated_data):
if instance.user:
instance.user.email = validated_data.get('email', instance.user.email)
instance.user.save()
else:
instance.old_email = validated_data.get('email', instance.old_email)
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
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.'))
return super().update(instance, validated_data)
# admin viewing member details
class AdminMemberSerializer(MemberSerializer):
phone = serializers.CharField(required=False)
street_address = serializers.CharField(required=False)
city = serializers.CharField(required=False)
postal_code = 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',
]
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):
if obj.user:
queryset = obj.user.training
else:
queryset = models.Training.objects.filter(member_id=obj.id)
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):
if obj.user:
queryset = obj.user.cards
else:
queryset = models.Card.objects.filter(member_id=obj.id)
queryset = queryset.order_by('-last_seen_at')
serializer = CardSerializer(data=queryset, many=True)
serializer.is_valid()
return serializer.data
def get_training(self, obj):
if obj.user:
queryset = obj.user.training
else:
queryset = models.Training.objects.filter(member_id=obj.id)
serializer = UserTrainingSerializer(data=queryset, many=True)
serializer.is_valid()
return serializer.data
def get_transactions(self, obj):
if obj.user:
queryset = obj.user.transactions
else:
queryset = models.Transaction.objects.filter(member_id=obj.id)
queryset = queryset.order_by('-id', '-date')
serializer = TransactionSerializer(data=queryset, many=True)
serializer.is_valid()
return serializer.data
#def get_usages(self, obj):
# if obj.user:
# queryset = obj.user.usages.order_by('-start_time')
# else:
# queryset = []
# 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.IntegerField()
active_status = serializers.ChoiceField([
'card_blocked',
'card_inactive',
'card_member_blocked',
'card_active'
])
class Meta:
model = models.Card
fields = '__all__'
read_only_fields = [
'id',
'last_seen_at',
'user',
]
def create(self, validated_data):
member = get_object_or_404(models.Member, id=validated_data['member_id'])
if member.user:
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)
class TrainingSerializer(serializers.ModelSerializer):
attendance_status = serializers.ChoiceField([
'Waiting for payment',
'Withdrawn',
'Rescheduled',
'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):
if obj.user:
member = obj.user.member
else:
member = models.Member.objects.get(id=obj.member_id)
return member.preferred_name + ' ' + member.last_name
def get_student_email(self, obj):
if obj.user:
return obj.user.email
else:
member = models.Member.objects.get(id=obj.member_id)
return member.old_email
def get_student_id(self, obj):
if obj.user:
return obj.user.member.id
else:
return obj.member_id
class StudentTrainingSerializer(TrainingSerializer):
attendance_status = serializers.ChoiceField(['Waiting for payment', 'Withdrawn'])
class CourseSerializer(serializers.ModelSerializer):
class Meta:
model = models.Course
fields = ['id', 'name', 'is_old', 'description']
class SessionSerializer(serializers.ModelSerializer):
student_count = serializers.SerializerMethodField()
course_data = serializers.SerializerMethodField()
instructor_name = 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', '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
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)
class Meta:
model = models.Course
fields = '__all__'
class UserTrainingSerializer(serializers.ModelSerializer):
session = SessionListSerializer()
class Meta:
model = models.Training
exclude = ['user']
depth = 2
class UserSerializer(serializers.ModelSerializer):
training = UserTrainingSerializer(many=True)
member = MemberSerializer()
transactions = serializers.SerializerMethodField()
door_code = serializers.SerializerMethodField()
wifi_pass = serializers.SerializerMethodField()
class Meta:
model = User
fields = [
'id',
'username',
'member',
'transactions',
'cards',
'training',
'is_staff',
'door_code',
'wifi_pass',
#'usages',
]
depth = 1
def get_transactions(self, obj):
queryset = models.Transaction.objects.filter(user=obj)
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_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
class MyRegisterSerializer(RegisterSerializer):
first_name = serializers.CharField(max_length=32)
last_name = serializers.CharField(max_length=32)
existing_member = serializers.ChoiceField(['true', 'false'])
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.')
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 secrets.REGISTRATION_BYPASS:
bypass_code = data.get('bypass_code', None)
allow_bypass = secrets.REGISTRATION_BYPASS == bypass_code
else:
allow_bypass = False
if not allow_bypass and 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.first_name,
)
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.first_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.first_name,
)
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.first_name, member.last_name, member.id))
if request_id: utils_stats.set_progress(request_id, 'Changing Spaceport password...')
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.first_name
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