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(