Commit 528c2d2a authored by Zack Wong's avatar Zack Wong
Browse files

Many form improvements

parent 5859c521
......@@ -45,8 +45,7 @@ class AccountForm(MyModelForm):
pass
super().clean();
def save(self):
return
class EmailForm(MyModelForm):
forward = forms.BooleanField(required=False)
......@@ -64,7 +63,7 @@ class EmailForm(MyModelForm):
if self['forward'].value() == True:
try:
if (len(self['email_address'].value()) == 0):
self.add_error('email_address', 'Email field cannot be left blankL.')
self.add_error('email_address', 'Email field cannot be left blank.')
if (self['email_address'].value().split('@')[1] in ["ucc.asn.au", "ucc.gu.uwa.edu.au"]):
self.add_error('email_address', 'Forwarding address cannot be the same as your account address.')
except:
......@@ -121,12 +120,22 @@ class AccountView(MyWizardView):
def done(self, form_list, form_dict, **kwargs):
# create the user and save their username if successfull
if create_ad_user(self.get_cleaned_data_for_step('0'), self.object):
form_list[0].save()
messages.success(self.request, 'Your membership renewal has been submitted.')
# create the user and save their username if successfull
try:
if create_ad_user(self.get_cleaned_data_for_step('0'), self.object):
form_dict['0'].save()
make_home(self.get_cleaned_data_for_step('1'), self.object)
make_dispense_account(self.object.username, self.get_cleaned_data_for_step('2')['pin'])
subscribe_to_list(self.object)
except Exception as e:
messages.error(self.request,'Account creation failed for %s', self.object)
messages.error(self.request, e)
raise #DEBUG
else:
messages.success(self.request, 'An account has been successfully created for %s.' % self.object)
return HttpResponseRedirect(reverse("admin:memberdb_membership_changelist"))
#return accountProgressView(self.request, m)
......
......@@ -31,7 +31,7 @@ ldap_user_dn = getattr(settings, 'LDAP_USER_SEARCH_DN')
ldap_base_dn = getattr(settings, 'LDAP_BASE_DN')
ldap_bind_dn = getattr(settings, 'LDAP_BIND_DN')
ldap_bind_secret = getattr(settings, 'LDAP_BIND_SECRET')
make_home_cmd = "sudo python3 root_actions.py"
make_home_cmd = ["sudo", "python3", "root_actions.py"]
make_mail_cmd = 'ssh -i %s [email protected] "/usr/local/mailman/bin/add_members" -r- ucc-announce <<< %[email protected]'
make_mail_key = './mooneye.key'
......@@ -206,7 +206,8 @@ def create_ad_user(form_data, member):
result = get_maxuid()
except:
log.error("LDAP: cannot find base uid")
return False
raise
maxuid = int(result.msSFU30MaxUidNumber.value)
......@@ -236,7 +237,7 @@ def create_ad_user(form_data, member):
# sanity check: make sure the uid is free
if subprocess.call(["id", newuid], stderr=subprocess.DEVNULL) == 0:
log.error("LDAP: uid already taken")
return False
raise ValueError
# create the new user struct
objclass = ['top','posixAccount','person','organizationalPerson','user']
......@@ -261,24 +262,31 @@ def create_ad_user(form_data, member):
result = ld.add(dn, objclass, attrs)
if not result:
log.error("LDAP: user add failed")
return False
raise LDAPOperationsErrorResult
# set maxuid
result = ld.modify(maxuid_dn, {'msSFU30MaxUidNumber': [(MODIFY_REPLACE, newuid)]})
if not result:
log.warning("LDAP: user created but msSFU30MaxUidNumber not update")
log.warning("LDAP: user created but msSFU30MaxUidNumber not updated")
ld.unbind();
return True;
def make_home(member,formdata):
def make_home(formdata, member):
user = member.username
mail = formdata['email_address'] if formdata['forward'] else ""
return subprocess.call(make_home_cmd, user, mail)
result = subprocess.call(make_home_cmd + [user, mail])
if result == 0:
return True
else:
raise CalledProcessError
def subscribe_to_list(member):
return os.system(make_mail_cmd % (make_mail_key, member.username))
result = os.system(make_mail_cmd % (make_mail_key, member.username))
if result == 0:
return True
else:
raise CalledProcessError
def set_pin(member, pin):
return
......
......@@ -23,4 +23,4 @@ class MyForm(forms.Form):
class MemberHomeForm(MyModelForm):
class Meta:
model = Member
fields = ['display_name', 'email_address', 'phone_number']
fields = [ 'email_address', 'phone_number']
......@@ -5,6 +5,7 @@ from django.core.management.utils import get_random_string
from django.urls import reverse
from squarepay.dispense import get_item_price
import subprocess
import ldap
......@@ -110,6 +111,13 @@ PAYMENT_METHODS = [
('', 'No payment')
]
ID_TYPES = [
('student', 'Student ID'),
('drivers', 'Drivers licence'),
('passport', 'Passport'),
('Other', 'Other ID'),
]
ACCOUNT_STATUS = [
'enabled',
'disabled',
......@@ -124,15 +132,15 @@ class IncAssocMember (models.Model):
Note: these data should only be changed administratively or with suitable validation since it must be up to date & accurate.
"""
first_name = models.CharField ('First name', max_length=200)
last_name = models.CharField ('Surname', max_length=200)
email_address = models.EmailField ('Email address', blank=False)
first_name = models.CharField ('Given Name', max_length=200)
last_name = models.CharField ('Other Names', max_length=200)
email_address = models.EmailField ('Contact email', blank=False)
updated = models.DateTimeField ('IncA. info last updated', auto_now=True)
created = models.DateTimeField ('When created', auto_now_add=True)
def __str__ (self):
return "%s %s <%s>" % (self.first_name, self.last_name, self.email_address)
class Meta:
verbose_name = "Incorporations Act member data"
verbose_name_plural = verbose_name
......@@ -145,13 +153,16 @@ class Member (IncAssocMember):
Note: Privacy laws are a thing, unless people allow it then we cannot provide this info to members.
"""
# data to be entered by user and validated (mostly) manually
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._-]+$')])
username = models.SlugField ('Username', max_length=32, null=True, blank=True, 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', 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 email or Drivers License', max_length=255, blank=False, help_text="Student emails should end with '@student.*.edu.au' and drivers licences should be in the format '<AU state> 1234567'")
id_number = models.CharField ('Student number or other ID', max_length=255, blank=False, help_text="")
id_desc = models.CharField ('Form of ID provided', max_length=255, blank=False, help_text="Please describe the type of identification provided", choices=ID_TYPES, default="student")
# data used internally by the system, not to be touched, seen or heard (except when it is)
member_updated = models.DateTimeField ('Internal UCC info last updated', auto_now=True)
......@@ -160,9 +171,15 @@ class Member (IncAssocMember):
studnt_confirm = models.BooleanField ('Student status confirmed', null=False, editable=False, default=False)
guild_confirm = models.BooleanField ('Guild status confirmed', null=False, editable=False, default=False)
has_account = models.BooleanField ('Has AD account', null=False, editable=False, default=False)
# account info
def get_account_status(self):
return;
def get_uid(self):
result, uid = subprocess.getstatusoutput(["id", "-u", self.username])
if (result == 0):
return uid;
else:
return None;
def __str__ (self):
if (self.display_name != "%s %s" % (self.first_name, self.last_name)):
......
......@@ -23,119 +23,147 @@ First step: enter an email address and some details (to fill at least a Member m
see https://docs.djangoproject.com/en/2.1/ref/models/fields/#error-messages
and https://docs.djangoproject.com/en/2.1/ref/forms/fields/#error-messages
"""
class RegisterForm(MyModelForm):
confirm_email = forms.EmailField(label='Confirm your email address', required=False)
agree_tnc = forms.BooleanField(label='I agree to the terms & conditions', required=True, help_text=mark_safe(
"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>"
'<b>Policies can be found <a href="https://www.ucc.asn.au/infobase/policies.ucc">here</a>.</b>'))
membership_type = forms.ChoiceField(label='Select your membership type', required=True, choices=get_membership_choices(is_renew=False))
class Meta:
model = Member
fields = ['first_name', 'last_name', 'username', 'phone_number', 'is_student', 'is_guild', 'id_number', 'email_address']
error_messages = {
'username': {
'unique': 'This username is already taken, please pick another one.',
'invalid': 'Please pick a username with only lowercase letters and numbers'
}
}
def clean(self):
try:
if (self['email_address'].value() != self['confirm_email'].value()):
self.add_error('email_address', 'Email addresses must match.')
except:
pass
super().clean();
def save(self, commit=True):
# get the Member model instance (ie. a record in the Members table) based on submitted form data
m = super().save(commit=False)
if (m.display_name == ""):
m.display_name = "%s %s" % (m.first_name, m.last_name);
# must save otherwise membership creation will fail
m.save()
# now create a corresponding Membership (marked as pending / not accepted, mostly default values)
ms = make_pending_membership(m)
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']
def save(self, commit=True):
m, ms = super().save(commit=False)
m.username = self.request.user.username
if (commit):
m.save()
ms.save()
return m, ms
class RegisterRenewForm(MyModelForm):
confirm_email = forms.EmailField(label='Confirm your email address', required=False)
agree_tnc = forms.BooleanField(label='I agree to the terms & conditions', required=True, help_text=mark_safe(
"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>"
'<b>Policies can be found <a href="https://www.ucc.asn.au/infobase/policies.ucc">here</a>.</b>'))
membership_type = forms.ChoiceField(label='Select your membership type', required=True, choices=get_membership_choices(is_renew=False))
class Meta:
model = Member
fields = ['first_name', 'last_name', 'phone_number', 'is_student', 'is_guild', 'id_number', 'id_desc', 'email_address']
error_messages = {
'username': {
'unique': 'This username is already taken, please pick another one.',
'invalid': 'Please pick a username with only lowercase letters and numbers'
}
}
def clean(self):
try:
if (self['email_address'].value() != self['confirm_email'].value()):
self.add_error('email_address', 'Email addresses must match.')
if (self['email_address'].value().split('@')[1] in ["ucc.asn.au", "ucc.gu.uwa.edu.au"]):
self.add_error('email_address', 'Contact address cannot be an UCC address.')
except:
pass
super().clean();
def save(self, commit=True):
# get the Member model instance (ie. a record in the Members table) based on submitted form data
m = super().save(commit=False)
if (m.display_name == ""):
m.display_name = "%s %s" % (m.first_name, m.last_name);
# must save otherwise membership creation will fail
m.save()
# now create a corresponding Membership (marked as pending / not accepted, mostly default values)
ms = make_pending_membership(m)
if (commit):
ms.save();
return m, ms
class RegisterForm(RegisterRenewForm):
username = forms.CharField(
label='Preferred Username (optional)',
required=False,
help_text="This will be the username you use to access club systems. You may leave this blank to choose a username later"
)
class Meta():
model = Member
fields = ['first_name', 'last_name', 'username', 'phone_number', 'is_student', 'is_guild', 'id_number', 'id_desc', 'email_address']
def clean(self):
try:
if (self['email_address'].value() != self['confirm_email'].value()):
self.add_error('email_address', 'Email addresses must match.')
if (self['email_address'].value().split('@')[1] in ["ucc.asn.au", "ucc.gu.uwa.edu.au"]):
self.add_error('email_address', 'Contact address cannot be an UCC address.')
except:
pass
super().clean();
class RenewForm(RegisterRenewForm):
confirm_email = None
membership_type = forms.ChoiceField(label='Select your membership type', required=True, choices=get_membership_choices(is_renew=True))
class Meta:
model = Member
fields = ['first_name', 'last_name', 'phone_number', 'is_student', 'is_guild', 'id_number', 'id_desc', 'email_address']
exclude = ['username']
def save(self, commit=True):
m, ms = super().save(commit=False)
m.username = self.request.user.username
m.has_account = m.get_uid() != None
if (commit):
m.save()
ms.save()
return m, ms
"""
simple FormView which displays registration form and handles template rendering & form submission
"""
class RegisterView(MyUpdateView):
template_name = 'register.html'
form_class = RegisterForm
model = Member
can_create = False
"""
called when valid form data has been POSTed
invalid form data simply redisplays the form with validation errors
"""
def form_valid(self, form):
# save the member data and get the Member instance
m, ms = form.save()
messages.success(self.request, 'Your registration has been submitted.')
# don't set the member session info - user can click on the link
#self.request.session['member_id'] = m.id
return thanks_view(self.request, m, ms)
template_name = 'register.html'
form_class = RegisterForm
model = Member
can_create = False
"""
called when valid form data has been POSTed
invalid form data simply redisplays the form with validation errors
"""
def form_valid(self, form):
# save the member data and get the Member instance
m, ms = form.save()
messages.success(self.request, 'Your registration has been submitted.')
# don't set the member session info - user can click on the link
#self.request.session['member_id'] = m.id
return thanks_view(self.request, m, ms)
def thanks_view(request, member, ms):
""" display a thankyou page after registration is completed """
context = {
'member': member,
'ms': ms,
'login_url': reverse('memberdb:login_member', kwargs={'username': member.username, 'member_token': member.login_token}),
}
return render(request, 'thanks.html', context)
""" display a thankyou page after registration is completed """
context = {
'member': member,
'ms': ms,
'login_url': reverse('memberdb:login_member', kwargs={'username': member.username, 'member_token': member.login_token}),
}
return render(request, 'thanks.html', context)
class RenewView(LoginRequiredMixin, MyUpdateView):
template_name = 'renew.html'
form_class = RenewForm
model = Member
def get_object(self):
u = self.request.user
obj = Member.objects.filter(username__exact=u.username).first()
if (obj is None):
# make a new Member object and prefill some data
obj = Member(username=u.username)
obj.first_name = u.first_name
obj.last_name = u.last_name
obj.email_address = u.email
obj.login_token = None # renewing members won't need this
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'is_new': Member.objects.filter(username__exact=self.request.user.username).count() == 0,
})
return context
def form_valid(self, form):
m, ms = form.save()
messages.success(self.request, 'Your membership renewal has been submitted.')
return HttpResponseRedirect(reverse("memberdb:home"))
template_name = 'renew.html'
form_class = RenewForm
model = Member
def get_object(self):
u = self.request.user
obj = Member.objects.filter(username__exact=u.username).first()
if (obj is None):
# make a new Member object and prefill some data
obj = Member(username=u.username)
obj.first_name = u.first_name
obj.last_name = u.last_name
obj.email_address = u.email
obj.login_token = None # renewing members won't need this
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'is_new': Member.objects.filter(username__exact=self.request.user.username).count() == 0,
})
return context
def form_valid(self, form):
m, ms = form.save()
messages.success(self.request, 'Your membership renewal has been submitted.')
return HttpResponseRedirect(reverse("memberdb:home"))
......@@ -2,22 +2,31 @@ import sys
import os
import shutil
import subprocess
## WARNING ##
# this script runs with elevated permissions #
### ###### ## ## ######## ## ## ## ## ######
## ## ## ## ## ## ## ## ## ### ## ## ##
## ## ## ## ## ## ## ## #### ## ##
## ## ## ######### ## ## ## ## ## ## ## ####
######### ## ## ## ## ## ## ## #### ## ##
## ## ## ## ## ## ## ## ## ## ### ## ##
## ## ###### ## ## ## ####### ## ## ######
# this script runs with elevated permissions #
# be very careful with what you do #
def main():
os.umask(0o077)
if len(sys.argv) != 2:
return 1
user = sys.argv[0]
mail = sys.argv[1]
if len(sys.argv) != 3:
exit(1)
user = sys.argv[1]
mail = sys.argv[2]
# abort if user does not exist
if subprocess.call(["id", user], stderr=subprocess.DEVNULL) != 0:
return 1
exit(1)
homes = {
('/home/ucc/%s' % user, '/home/wheel/bin/skel/ucc'),
......@@ -34,7 +43,7 @@ def main():
os.system('chmod a+x %s' % home)
os.system('chmod a+rX %s/public-html' % home)
except:
return 1
exit(1)
# write .forward
try:
......@@ -46,10 +55,12 @@ def main():
shutil.chown(forward,user,"gumby")
os.chmod(forward, 0o644)
except:
return 1
exit(1)
if __name__ == "__main__":
main()
exit(0)
......
......@@ -55,7 +55,6 @@ def set_dispense_flag(user, flag, reason):
log.warning("DISPENSE_BIN is not defined, user will not be disabled")
return False
cmd = [DISPENSE_BIN] + args
out = run_dispense('user', 'type', user, flag, reason)
s = out.split()
if s[2] != "updated":
......@@ -63,3 +62,23 @@ def set_dispense_flag(user, flag, reason):
log.warning("set_dispense_flag: user was not updated with error: " + out)
return False;
return True;
def make_dispense_account(user, pin):
if DISPENSE_BIN is None:
log.warning("DISPENSE_BIN is not defined")
return False
cmdargs = [
("user","add", user),
("acct",user,"+500","treasurer: new user"),
("-u", user, "pinset", pin)
]
for args in cmdargs:
cmd = [DISPENSE_BIN] + args
out = run_dispense('user', 'type', user, flag, reason)
if out == None:
raise CalledProcessError
return True;
......@@ -44,8 +44,8 @@
</fieldset>
<div class="submit-row">
{% if wizard.steps.prev %}
<button name="wizard_goto_step" class="button" type="submit" value="{{ wizard.steps.prev }}"> prev </button>
{% endif %}
<button name="wizard_goto_step" class="button" type="submit" value="{{ wizard.steps.prev }}"> prev </button>
{% endif %}
{% if wizard.steps.next %}
<input type="submit" value="{% trans "next" %}"/>
......
{% load static %}{# this template is used to generate the member action buttons, see memberdb/admin.py #}
<div class="member-actions">
<a class="button" href="{{ member_url }}">Edit</a>&nbsp;
{% if not ms.approved %}
<a class="button" href="{{ member_approve }}">Approve</a>&nbsp;
{% endif %}
<a class="button" href="{{ create_account }}">Create Account</a>&nbsp;
<a class="button" href="{{ member_url }}">Edit</a>&nbsp;
{% if not ms.approved %}
<a class="button" href="{{ member_approve }}">Approve</a>&nbsp;
{% endif %}
{% if not member.has_account %}
<a class="button" href="{{ create_account }}">Create Account</a>&nbsp;
{% endif %}
</div>
......@@ -19,7 +19,7 @@
{% block email %}
<tr class="{% cycle rcl %}">
<th>Email</th>
<th>Contact email</th>
<td>
{% if member.email_address %}
<a href="mailto:{{ member.email_address }}">{{ member.email_address }}</a>
......@@ -47,9 +47,13 @@
<td>{% if member.is_guild %}<img src="{% static 'admin/img/icon-yes.svg' %}" alt="yes">{% else %}<img src="{% static 'admin/img/icon-no.svg' %}" alt="no">{% endif %}</td>
</tr>
<tr class="{% cycle rcl %}">
<th>Student email / Drivers license</th>
<th>Student number or Other ID</th>
<td>{% if member.id_number %}{{ member.id_number }}{% else %}<i>not provided</i>{% endif %}</td>
</tr>
<tr class="{% cycle rcl %}">
<th>ID type</th>
<td>{% if member.id_desc %}{{ member.id_desc }}{% else %}<i>not provided</i>{% endif %}</td>
</tr>
{% endblock %}
</table>
{% endblock %}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment