diff --git a/src/memberdb/admin.py b/src/memberdb/admin.py index 5b2910531c8dbe8a8d9b87598d52c65887d13ce8..690c4bea85bb5d0c4697bcb4c672d6033bd300f9 100644 --- a/src/memberdb/admin.py +++ b/src/memberdb/admin.py @@ -78,9 +78,9 @@ class MembershipInline(admin.TabularInline): fk_name = 'member' class MemberAdmin(admin.ModelAdmin): - list_display = ['first_name', 'last_name', 'display_name', 'username'] - list_filter = ['is_guild', 'is_student', MembershipRenewalFilter] - readonly_fields = ['member_updated', 'updated', 'created'] + list_display = ['first_name', 'last_name', 'display_name', 'username', 'has_account'] + list_filter = ['is_guild', 'is_student', MembershipRenewalFilter, 'has_account'] + readonly_fields = ['id', 'has_account', 'email_confirm', 'studnt_confirm', 'guild_confirm', 'member_updated', 'updated', 'created', ] search_fields = list_display actions = [download_as_csv] inlines = [MembershipInline] @@ -112,7 +112,7 @@ class MembershipAdmin(admin.ModelAdmin): """ list_display = ['membership_info', 'membership_type', 'payment_method', 'approved', 'date_submitted', 'member_actions'] list_display_links = None - list_filter = ['approved', 'payment_method', 'membership_type', 'member__is_student', 'member__is_guild'] + list_filter = ['approved', 'payment_method', 'membership_type', 'member__is_student', 'member__is_guild', 'member__has_account'] readonly_fields = ['date_submitted'] radio_fields = {'payment_method': admin.VERTICAL, 'membership_type': admin.VERTICAL} actions = [refresh_dispense_payment] diff --git a/src/memberdb/models.py b/src/memberdb/models.py index eb6b28096dab64875bb54a38daeeecc0eba57acd..e0642d7578b9e481d121a40e91fed3e7b42bde85 100644 --- a/src/memberdb/models.py +++ b/src/memberdb/models.py @@ -185,6 +185,7 @@ 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) + # stuff which is updated via admin commands has_account = models.BooleanField ('Has AD account', null=False, editable=False, default=False) def get_last_renewal(self): @@ -193,12 +194,14 @@ class Member (IncAssocMember): # account info def get_uid(self): + if self.username is None or self.username == '': + return None 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)): name = "%s (%s %s)" % (self.display_name, self.first_name, self.last_name) diff --git a/src/memberdb/register.py b/src/memberdb/register.py index 99ba5d15d136a699eaa5b079593c0d0bf5b3cf06..eaf4148b5870d792e9fb57f6c7f4a8c26719dd7a 100644 --- a/src/memberdb/register.py +++ b/src/memberdb/register.py @@ -22,6 +22,7 @@ from squarepay.dispense import get_item_price from .models import Member, Membership, get_membership_choices, make_pending_membership from .forms import MyModelForm from .views import MyUpdateView +from .account_backend import validate_username """ First step: enter an email address and some details (to fill at least a Member model) to create a pending membership. @@ -39,11 +40,6 @@ class RegisterRenewForm(MyModelForm): class Meta: model = Member fields = ['first_name', 'last_name', 'phone_number', 'is_student', 'is_guild', 'id_number', 'id_desc', 'email_address'] - error_messages = { - 'username': { - 'invalid': 'Please pick a username with only lowercase letters and numbers' - } - } def clean(self): try: @@ -60,7 +56,6 @@ class RegisterRenewForm(MyModelForm): m = super().save(commit=False) if (m.display_name == ""): m.display_name = "%s %s" % (m.first_name, m.last_name); - m.has_account = m.get_uid() != None # must save otherwise membership creation will fail m.save() @@ -76,20 +71,28 @@ 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" + help_text="This will be the username you use to access club systems. You may leave this blank to choose a username later", + error_messages={ + 'invalid': 'Please pick a username with only lowercase letters and numbers' + }, + validators=[validate_username] ) class Meta(): model = Member fields = ['first_name', 'last_name', 'username', 'phone_number', 'is_student', 'is_guild', 'id_number', 'id_desc', 'email_address'] + def clean_username(self): + """ store empty string usernames as NULL in the database to avoid violating unique constraint """ + u = self.cleaned_data['username'] + return None if u == '' else u - def clean(self): + 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.') + self.add_error('email_address', 'Contact address cannot be an UCC address.') except: pass super().clean(); diff --git a/src/memberdb/views.py b/src/memberdb/views.py index 19e40304f5648e01fa55512b1409ca8d0db3ebcb..84baa16e3010da5f4752e67301f3d1d0b873f2f3 100644 --- a/src/memberdb/views.py +++ b/src/memberdb/views.py @@ -8,6 +8,7 @@ from django.contrib import messages from django.views.generic.base import View from django.views.generic.edit import UpdateView from django.contrib.auth.mixins import AccessMixin +from django.contrib.auth import logout from django.utils import timezone from formtools.wizard.views import SessionWizardView @@ -32,12 +33,21 @@ class MemberMiddleware: if request.user.is_authenticated: # get the username only when a user is logged in # note that request.user will still exist even when the user isn't logged in - request.member = Member.objects.filter(username__exact=request.user.username).first() + + try: + request.member = Member.objects.get(username=request.user.username) + except Member.MultipleObjectsReturned: + messages.warning(request, "You are logged in, but someone has registered with your username more than once! Please ask an admin to fix this.") + except Member.DoesNotExist: + # the homepage will tell people to register properly, no need to do anything + pass if request.member is not None: # clean the member's auth token because they now have a working login - request.member.token = None - request.member.save() + if request.member.login_token is not None: + request.member.login_token = None + request.member.has_account = True + request.member.save() if request.user.ldap_user is not None: # copy the LDAP groups so templates can access them @@ -136,18 +146,17 @@ class MemberHomeView(MemberAccessMixin, MyUpdateView): class MemberTokenView(View): """ allow a user to login using a unique (secure) member token """ def get(self, request, **kwargs): - if not request.user.is_authenticated: - # look up the member using exact match for token and username, and registered < 7 days ago - week_ago = timezone.now() - timedelta(days=7) + # look up the member using exact match for token and username, and registered < 32 days ago + week_ago = timezone.now() - timedelta(days=32) - try: - member = Member.objects.get( - login_token=kwargs['member_token'], - id=kwargs['id'], - created__gte=week_ago - ) - except Member.DoesNotExist: - raise Http404() + try: + member = Member.objects.get( + login_token=kwargs['member_token'], + id=kwargs['id'], + created__gte=week_ago + ) + except Member.DoesNotExist: + raise Http404() request.session['member_id'] = member.id return HttpResponseRedirect(reverse('memberdb:home')) @@ -156,6 +165,9 @@ class EmailConfirmView(View): """ process email confirmations """ def get(self, request, **kwargs): + if request.user.is_authenticated: + logout(request) + week_ago = timezone.now() - timedelta(days=7) try: c = TokenConfirmation.objects.get(