diff --git a/gms/memberdb/approve.py b/gms/memberdb/approve.py index c80632fb9a460c25989133bfaa8f7d22a0704e63..36bf5171a66f8390213faa565cab00946a14916b 100644 --- a/gms/memberdb/approve.py +++ b/gms/memberdb/approve.py @@ -8,25 +8,10 @@ from django.urls import reverse from django.utils import timezone from django import forms -from memberdb.models import Member, Membership, MEMBERSHIP_TYPES_ +from memberdb.models import Member, Membership, get_membership_type from memberdb.forms import MyModelForm from memberdb.views import MyUpdateView -def get_membership_type(member): - best = None - is_fresh = member.memberships.all().count() == 0 - for t in MEMBERSHIP_TYPES_: - if (t['must_be_fresh'] == is_fresh and t['is_student'] == member.is_student and t['is_guild'] == member.is_guild): - best = t - break - elif (t['is_student'] == member.is_student and t['is_guild'] == member.is_guild): - best = t - break - if (best is None): - return MEMBERSHIP_TYPES_[1]['dispense'] - else: - return best['dispense'] - def make_pending_membership(member): # check if this member already has a pending membership ms = Membership.objects.filter(member=member, approved__exact=False).first() diff --git a/gms/memberdb/models.py b/gms/memberdb/models.py index b6d1e32c4cfcd51a5b4ad3293de607d47f079dbf..7551994bca3f9a686080b4c825cc8c3ea885dbf4 100644 --- a/gms/memberdb/models.py +++ b/gms/memberdb/models.py @@ -2,61 +2,96 @@ from django.db import models from django.db.models import F from django.core.validators import RegexValidator +from squarepay.dispense import get_item_price + """ dictionary of membership types & descriptions, should be updated if these are changed in dispense. """ -MEMBERSHIP_TYPES_ = [ - { +MEMBERSHIP_TYPES = { + 'oday': { 'dispense':'pseudo:11', - 'desc':'O\' Day Special', + 'desc':'O\' Day Special - first time members only', 'is_guild':True, 'is_student':True, 'must_be_fresh':True, }, - { + 'student_and_guild': { 'dispense':'pseudo:10', 'desc':'Student and UWA Guild member', 'is_guild':True, 'is_student':True, 'must_be_fresh':False, }, - { + 'student_only': { 'dispense':'pseudo:9', 'desc':'Student and not UWA Guild member', 'is_guild':False, 'is_student':True, 'must_be_fresh':False, }, - { + 'guild_only': { 'dispense':'pseudo:8', 'desc':'Non-Student and UWA Guild member', 'is_guild':True, 'is_student':False, 'must_be_fresh':False, }, - { + 'non_student': { 'dispense':'pseudo:7', 'desc':'Non-Student and not UWA Guild member', 'is_guild':False, 'is_student':False, 'must_be_fresh':False, }, - { + 'lifer': { 'dispense':'', 'desc':'Life member', 'is_guild':False, 'is_student':False, 'must_be_fresh':False, } -] +} + +def get_membership_choices(is_renew=None, get_prices=True): + """ + turn MEMBERSHIP_TYPES into a list of choices used by Django + also dynamically fetch the prices from dispense (if possible) + """ + choices = [] + for key, val in MEMBERSHIP_TYPES.items(): + if (val['must_be_fresh'] and is_renew == True): + # if you have an account already, you don't qualify for the fresher special + continue + if (val['dispense'] == '' and is_renew == False): + # free memberships can only apply to life members, and they will have an existing membership somewhere + # so this option is only displayed on the renewal form + continue + else: + price = get_item_price(val['dispense']) + if (get_prices and price is not None): + desc = "%s ($%1.2f)" % (val['desc'], price / 100.0) + choices += [(key, desc)] + else: + # don't display the price + choices += [(key, val['desc'])] -def get_membership_types(): - l = [] - for t in MEMBERSHIP_TYPES_: - l += [(t['dispense'], t['desc'])] - return l + return choices -MEMBERSHIP_TYPES = get_membership_types() +def get_membership_type(member): + best = 'non_student' + is_fresh = member.memberships.all().count() == 0 + for i, t in MEMBERSHIP_TYPES.items(): + if (t['must_be_fresh'] == is_fresh and t['is_student'] == member.is_student and t['is_guild'] == member.is_guild): + best = i + break + elif (t['is_student'] == member.is_student and t['is_guild'] == member.is_guild): + best = i + break + return best + + +def make_token(): + return get_random_string(128) PAYMENT_METHODS = [ ('dispense', 'Existing dispense credit'), @@ -84,7 +119,6 @@ class IncAssocMember (models.Model): class Meta: verbose_name = "Incorporations Act member data" verbose_name_plural = verbose_name - default_permissions = ['view'] """ Member table: only latest information, one record per member @@ -95,10 +129,11 @@ class Member (IncAssocMember): display_name = models.CharField ('Display name', max_length=200) username = models.SlugField ('Username', max_length=32, null=False, blank=False, unique=True, validators=[RegexValidator(regex='^[a-z0-9._-]+$')]) phone_number = models.CharField ('Phone number', max_length=20, blank=False, validators=[RegexValidator(regex='^\+?[0-9() -]+$')]) - is_student = models.BooleanField ('Student at UWA', default=True, blank=True) + is_student = models.BooleanField ('Student', default=True, blank=True, help_text="Tick this box if you are a current student at a secondary or tertiary institution in WA") is_guild = models.BooleanField ('UWA Guild member', default=True, blank=True) - id_number = models.CharField ('Student number or Drivers License', max_length=50 , blank=False) + id_number = models.CharField ('Student email or Drivers License', max_length=255, blank=False, help_text="Student emails should end with '.edu.au' and drivers licences should be in the format '[WA]DL 1234567'") member_updated = models.DateTimeField ('Internal UCC info last updated', auto_now=True) + login_token = models.CharField ('Temporary access key', max_length=128, null=True, editable=False, default=make_token) def __str__ (self): if (self.display_name != "%s %s" % (self.first_name, self.last_name)): @@ -115,7 +150,7 @@ Membership table: store information related to individual (successful/accepted) """ class Membership (models.Model): member = models.ForeignKey (Member, on_delete=models.CASCADE, related_name='memberships') - membership_type = models.CharField ('Membership type', max_length=10, blank=True, null=False, choices=MEMBERSHIP_TYPES) + membership_type = models.CharField ('Membership type', max_length=20, blank=True, null=False, choices=get_membership_choices(get_prices=False)) payment_method = models.CharField ('Payment method', max_length=10, blank=True, null=True, choices=PAYMENT_METHODS, default=None) approved = models.BooleanField ('Membership approved', default=False) approver = models.ForeignKey (Member, on_delete=models.SET_NULL, null=True, blank=True, related_name='approved_memberships') @@ -126,6 +161,9 @@ class Membership (models.Model): def __str__ (self): return "Member [%s] (%s) renewed membership on %s" % (self.member.username, self.member.display_name, self.date_submitted.strftime("%Y-%m-%d")) + def get_dispense_item(self): + return MEMBERSHIP_TYPES[self.membership_type]['dispense'] + class Meta: verbose_name = "Membership renewal record" - ordering = ['approved', '-date_submitted'] \ No newline at end of file + ordering = ['approved', '-date_submitted'] diff --git a/gms/memberdb/register.py b/gms/memberdb/register.py index 045250dd744dde592e1c4169d6beff234dd63e18..ed604eb70ad41d57c5012c04d862459e86db57f0 100644 --- a/gms/memberdb/register.py +++ b/gms/memberdb/register.py @@ -10,7 +10,10 @@ from django.utils.safestring import mark_safe from django.contrib import messages from django import forms -from .models import Member, Membership +from squarepay.models import MembershipPayment +from squarepay.dispense import get_item_price + +from .models import Member, Membership, get_membership_choices, MEMBERSHIP_TYPES from .forms import MyModelForm from .views import MyUpdateView from .approve import make_pending_membership @@ -26,7 +29,7 @@ class RegisterForm(MyModelForm): "You agree to abide by the UCC Constitution, rulings of the UCC Committee, UCC and " "UWA’s Network Usage Guidelines and that you will be subscribed to the UCC Mailing List. <br>" 'Policies can be found <a href="https://www.ucc.asn.au/infobase/policies.ucc">here</a>.')) - #membership_type = forms.ChoiceField(label='Select your membership type', required=True, choices=MEMBERSHIP_TYPES) + membership_type = forms.ChoiceField(label='Select your membership type', required=True, choices=get_membership_choices(is_renew=False)) class Meta: model = Member @@ -56,13 +59,17 @@ class RegisterForm(MyModelForm): # now create a corresponding Membership (marked as pending / not accepted, mostly default values) ms = make_pending_membership(m) + + # make a card payment thing as well + if (commit): ms.save(); return m, ms class RenewForm(RegisterForm): confirm_email = None - + membership_type = forms.ChoiceField(label='Select your membership type', required=True, choices=get_membership_choices(is_renew=True)) + class Meta(RegisterForm.Meta): fields = ['first_name', 'last_name', 'phone_number', 'is_student', 'is_guild', 'id_number', 'email_address'] @@ -91,7 +98,7 @@ class RegisterView(MyUpdateView): # save the member data and get the Member instance m, ms = form.save() #messages.success(self.request, 'Your registration has been submitted.') - return HttpResponseRedirect(reverse("memberdb:thanks")) + return class RenewView(LoginRequiredMixin, MyUpdateView): template_name = 'renew.html' @@ -121,3 +128,19 @@ class RenewView(LoginRequiredMixin, MyUpdateView): m, ms = form.save() messages.success(self.request, 'Your membership renewal has been submitted.') return HttpResponseRedirect(reverse("memberdb:home")) + +def create_member_payment(membership, commit=True): + """ creates a MembershipPayment object for the given membership """ + # get the amount from dispense + price = get_item_price(membership.membership_type) + if (price is None or price == 0): + return None + desc = MEMBERSHIP_TYPES[membership.membership_type]['desc'] + payment = MembershipPayment(description=desc, amount=price, membership=membership) + + if (commit): + payment.save() + return payment + +def thanks_page(request, membership): + pass