spaceport/apiserver/apiserver/api/utils.py

338 lines
9.8 KiB
Python

import datetime
import io
import requests
from rest_framework.exceptions import ValidationError
from dateutil import relativedelta
from uuid import uuid4
from PIL import Image
from bleach.sanitizer import Cleaner
from PyPDF2 import PdfFileWriter, PdfFileReader
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from django.db.models import Sum
from django.core.cache import cache
from . import models, serializers, utils_ldap
try:
from . import old_models
except ImportError:
print('Running without old portal data...')
old_models = None
STATIC_FOLDER = 'data/static/'
def num_months_spanned(d1, d2):
'''
Return number of month thresholds two dates span.
Order of arguments is same as subtraction
ie. Feb 2, Jan 29 returns 1
'''
return (d1.year - d2.year) * 12 + d1.month - d2.month
def num_months_difference(d1, d2):
'''
Return number of whole months between two dates.
Order of arguments is same as subtraction
ie. Feb 2, Jan 29 returns 0
'''
r = relativedelta.relativedelta(d1, d2)
return r.months + 12 * r.years
def calc_member_status(expire_date, fake_date=None):
'''
Return: status, if we should pause them
'''
today = fake_date or datetime.date.today()
difference = num_months_difference(expire_date, today)
#if today + datetime.timedelta(days=29) < expire_date:
if difference >= 1:
return 'Prepaid', False
elif difference <= -3:
return 'Overdue', True
elif difference <= -1:
return 'Overdue', False
elif today < expire_date:
return 'Current', False
elif today >= expire_date:
return 'Due', False
else:
raise()
def add_months(date, num_months):
return date + relativedelta.relativedelta(months=num_months)
def fake_missing_membership_months(member):
'''
Add fake months on importing the member so the length of their membership
resolves to their imported expiry date
'''
start_date = member.current_start_date
expire_date = member.expire_date
missing_months = num_months_spanned(expire_date, start_date)
user = member.user if member.user else None
tx = False
for i in range(missing_months):
memo = '{} / {} month membership dues accounting old portal import, {} to {} - hidden'.format(
str(i+1), str(missing_months), start_date, expire_date
)
tx = models.Transaction.objects.create(
amount=0,
user=user,
memo=memo,
member_id=member.id,
reference_number='',
info_source='System',
payment_method='N/A',
category='Memberships:Fake Months',
account_type='Clearing',
number_of_membership_months=1,
date=add_months(start_date, i),
)
return tx, missing_months
def tally_membership_months(member, fake_date=None):
'''
Sum together member's dues and calculate their new expire date and status
Doesn't work if member is paused.
'''
if member.paused_date: return False
start_date = member.current_start_date
if not start_date: return False
txs = models.Transaction.objects.filter(
member_id=member.id,
date__gte=start_date,
)
total_months_agg = txs.aggregate(Sum('number_of_membership_months'))
total_months = total_months_agg['number_of_membership_months__sum'] or 0
expire_date = add_months(start_date, total_months)
status, former = calc_member_status(expire_date, fake_date)
member.expire_date = expire_date
member.status = status
if former:
member.paused_date = expire_date
member.save()
return True
def gen_search_strings():
'''
Generate a cache dict of names to member ids for rapid string matching
'''
search_strings = {}
for m in models.Member.objects.all():
string = '{} {}'.format(
m.preferred_name,
m.last_name,
).lower()
search_strings[string] = m.id
cache.set('search_strings', search_strings)
LARGE_SIZE = 1080
MEDIUM_SIZE = 220
SMALL_SIZE = 110
def process_image_upload(upload):
'''
Save an image upload in small, medium, large sizes and return filenames
'''
try:
pic = Image.open(upload)
except OSError:
raise serializers.ValidationError('Invalid image file.')
if pic.format == 'PNG':
ext = '.png'
elif pic.format == 'JPEG':
ext = '.jpg'
else:
raise serializers.ValidationError('Image must be a jpg or png.')
large = str(uuid4()) + ext
pic.thumbnail([LARGE_SIZE, LARGE_SIZE], Image.ANTIALIAS)
pic.save(STATIC_FOLDER + large)
medium = str(uuid4()) + ext
pic.thumbnail([MEDIUM_SIZE, MEDIUM_SIZE], Image.ANTIALIAS)
pic.save(STATIC_FOLDER + medium)
small = str(uuid4()) + ext
pic.thumbnail([SMALL_SIZE, SMALL_SIZE], Image.ANTIALIAS)
pic.save(STATIC_FOLDER + small)
return small, medium, large
ALLOWED_TAGS = [
'h3',
'p',
'br',
'strong',
'em',
'u',
'code',
'ol',
'li',
'ul',
'a',
]
clean = Cleaner(tags=ALLOWED_TAGS).clean
def is_request_from_protospace(request):
whitelist = ['24.66.110.96', '205.233.15.76', '205.233.15.69']
# set (not appended) directly by nginx so we can trust it
real_ip = request.META.get('HTTP_X_REAL_IP', False)
return real_ip in whitelist
def link_old_member(data, user):
'''
If a member claims they have an account on the old protospace portal,
go through and link their objects to their new user using the member_id
found with their email as a hint
Since this runs AFTER registration, we need to delete the user on any
failures or else the username will be taken when they try again
'''
if not old_models:
raise ValidationError(dict(email='Unable to link, old DB wasn\'t imported.'))
old_members = old_models.Members.objects.using('old_portal')
try:
old_member = old_members.get(email=data['email'])
except old_models.Members.DoesNotExist:
raise ValidationError(dict(email='Unable to find email in old portal.'))
member = models.Member.objects.get(id=old_member.id)
if member.user:
raise ValidationError(dict(email='Old member already claimed.'))
if utils_ldap.is_configured():
result = utils_ldap.find_user(user.username)
if result == 200:
pass
elif result == 404:
raise ValidationError(dict(username='Unable to find username in old portal.'))
else:
raise ValidationError(dict(non_field_errors='Problem connecting to LDAP server: find.'))
if utils_ldap.set_password(data) != 200:
raise ValidationError(dict(non_field_errors='Problem connecting to LDAP server: set.'))
member.user = user
member.first_name = data['first_name']
member.last_name = data['last_name']
member.preferred_name = data['first_name']
member.save()
transactions = models.Transaction.objects.filter(member_id=member.id)
for t in transactions:
t.user = user
t.save()
cards = models.Card.objects.filter(member_id=member.id)
for c in cards:
c.user = user
c.save()
training = models.Training.objects.filter(member_id=member.id)
for t in training:
t.user = user
t.save()
def create_new_member(data, user):
if old_models:
old_members = old_models.Members.objects.using('old_portal')
if old_members.filter(email=data['email']).exists():
raise ValidationError(dict(email='Account was found in old portal.'))
if utils_ldap.is_configured():
result = utils_ldap.find_user(user.username)
if result == 200:
raise ValidationError(dict(username='Username was found in old portal.'))
elif result == 404:
pass
else:
raise ValidationError(dict(non_field_errors='Problem connecting to LDAP server.'))
if utils_ldap.create_user(data) != 200:
raise ValidationError(dict(non_field_errors='Problem connecting to LDAP server: create.'))
models.Member.objects.create(
user=user,
first_name=data['first_name'],
last_name=data['last_name'],
preferred_name=data['first_name'],
)
def register_user(data, user):
try:
if data['existing_member'] == 'true':
link_old_member(data, user)
else:
create_new_member(data, user)
except:
user.delete()
raise
BLANK_FORM = 'misc/blank_member_form.pdf'
def gen_member_forms(member):
serializer = serializers.MemberSerializer(member)
data = serializer.data
packet = io.BytesIO()
can = canvas.Canvas(packet, pagesize=letter)
can.drawRightString(580, 770, '{} {} ({})'.format(
data['first_name'],
data['last_name'],
data['id'],
))
can.drawString(34, 683, data['first_name'])
can.drawString(218, 683, data['last_name'])
can.drawString(403, 683, data['preferred_name'])
can.drawString(34, 654, data['street_address'])
can.drawString(275, 654, data['city'])
can.drawString(459, 654, data['postal_code'])
can.drawString(34, 626, data['email'])
can.drawString(332, 626, data['phone'])
can.drawString(34, 570, data['emergency_contact_name'])
can.drawString(332, 570, data['emergency_contact_phone'])
can.save()
packet.seek(0)
new_pdf = PdfFileReader(packet)
existing_pdf = PdfFileReader(open(BLANK_FORM, 'rb'))
output = PdfFileWriter()
page = existing_pdf.getPage(0)
page.mergePage(new_pdf.getPage(0))
output.addPage(page)
page = existing_pdf.getPage(1)
output.addPage(page)
page = existing_pdf.getPage(2)
output.addPage(page)
file_name = str(uuid4()) + '.pdf'
outputStream = open(STATIC_FOLDER + file_name, 'wb')
output.write(outputStream)
member.member_forms = file_name
member.save()