diff --git a/src/memberdb/approve.py b/src/memberdb/approve.py
index 55c232642498b01c9dca5bc1ee5b7637fa41c621..59fe2bc7e46c0d685cd2ecc30309b7474ebbc09f 100644
--- a/src/memberdb/approve.py
+++ b/src/memberdb/approve.py
@@ -8,110 +8,102 @@ from django.urls import reverse
 from django.utils import timezone
 from django import forms
 
-from memberdb.models import Member, Membership, get_membership_type
+from memberdb.models import Member, Membership, get_membership_choices
 from memberdb.forms import MyModelForm
 from memberdb.views import MyUpdateView
 
-"""
-inline admin change list action buttons
-see https://medium.com/@hakibenita/how-to-add-custom-action-buttons-to-django-admin-8d266f5b0d41
-and have a look at .admin.MembershipAdmin
-"""
+
 class MembershipApprovalForm(MyModelForm):
-    payment_confirm = forms.BooleanField(label='Confirm payment', required=False)
-
-    class Meta:
-        model = Membership
-        fields = ['membership_type', 'payment_method']
-        widgets = {
-            'membership_type': forms.RadioSelect(),
-            'payment_method': forms.RadioSelect(),
-        }
-
-    """
-    Called to validate the data on the form.
-    here we fill out some fields automatically (ie. approver, date paid / approved, etc.)
-    TODO: deal with account activation/creation, etc.
-    """
-    def clean(self):
-        # get the cleaned data from the form API and do something with it
-        data = super().clean()
-        now = timezone.now()
-        #breakpoint()
-        # find a Member matching our current username
-        approver = Member.objects.filter(username__exact=self.request.user.username).first()
-        if (approver == None):
-            self.add_error(None, 'Cannot set approver: no Member record with username %s' % self.request.user.username)
-        data['approver'] = approver
-        data['approved'] = True
-        data['date_approved'] = now
-
-        if (data['payment_confirm'] == True):
-            if (data['payment_method'] == ''):
-                self.add_error('payment_method', 'Please select a payment method')
-            data['date_paid'] = now
-        else:
-            data['date_paid'] = None
-        
-        # make sure "no payment" is recorded for Life Members.
-        # XXX this might not actually be the case, since some life members may want to also be financial members (ie. for constitutional voting rights)
-        #     and so this is probably more annoying than helpful
-        if (data['membership_type'] == ''):
-            if (data['payment_method'] != ''):
-                self.add_error('payment_method', 'Life members shall not pay membership fees!')
-            data['payment_method'] = ''
-
-        return data
-
-    """
-    do the stuff, approve the things
-    """
-    def save(self, commit=True):
-        ms = super().save(commit=False)
-
-        # copy attributes not specified in fields
-        ms.approver = self.cleaned_data['approver']
-        ms.approved = self.cleaned_data['approved']
-        ms.date_approved = self.cleaned_data['date_approved']
-        ms.date_paid = self.cleaned_data['date_paid']
-
-        # do something
-        if (commit):
-            ms.save()
-        return ms
+	payment_confirm = forms.BooleanField(
+		label = 'Confirm payment',
+		required = False
+	)
+
+	class Meta:
+		model = Membership
+		fields = ['membership_type', 'payment_method']
+		widgets = {
+			'membership_type': forms.RadioSelect(),
+			'payment_method': forms.RadioSelect(),
+		}
+
+	def __init__(self, *args, **kwargs):
+		super().__init__(*args, **kwargs)
+
+		# override the model membership_type field so we display all the options with prices
+		self.fields['membership_type'].choices = get_membership_choices()
+
+	def clean(self):
+		"""
+		Called to validate the data on the form.
+		here we fill out some fields automatically (ie. approver, date paid / approved, etc.)
+		TODO: deal with account activation/creation, etc.
+		"""
+		data = super().clean()
+		now = timezone.now()
+		approver = self.request.member
+		if (approver == None):
+			self.add_error(None, 'Cannot set approver: no Member record associated with current session. (username %s)' % self.request.user.username)
+
+		data['approver'] = approver
+		data['approved'] = True
+		data['date_approved'] = now
+
+		if (data['payment_confirm'] == True):
+			if (data['payment_method'] == ''):
+				self.add_error('payment_method', 'Please select a payment method')
+			data['date_paid'] = now
+		else:
+			data['date_paid'] = None
+
+		return data
+
+	def save(self, commit=True):
+		""" save the data into a Membership object """
+		ms = super().save(commit=False)
+
+		# copy attributes not specified in editable form fields
+		ms.approver = self.cleaned_data['approver']
+		ms.approved = self.cleaned_data['approved']
+		ms.date_approved = self.cleaned_data['date_approved']
+		ms.date_paid = self.cleaned_data['date_paid']
+
+		if (commit):
+			ms.save()
+		return ms
 
 class MembershipApprovalAdminView(MyUpdateView):
-    template_name = 'admin/memberdb/membership_approve.html'
-    form_class = MembershipApprovalForm
-    model = Membership
-    pk_url_kwarg = 'object_id'
-    # override with the instance of ModelAdmin
-    admin = None
-
-    def get_context_data(self, **kwargs):
-        ms = self.get_object()
-        context = super().get_context_data(**kwargs)
-        context.update(self.admin.admin_site.each_context(self.request))
-        context.update({
-            'opts': self.admin.model._meta,
-            'ms': ms,
-            'member': ms.member,
-            'show_member_summary': True,
-        })
-        return context
-
-    """
-    called when the approval form is submitted and valid data (according to the form's field types and defined validators) is given
-    """
-    def form_valid(self, form):
-        ms = form.save()
-        
-        self.admin.message_user(self.request, 'Approve success')
-        url = reverse(
-            'admin:memberdb_membership_changelist',
-            args=[],
-            current_app=self.admin.admin_site.name,
-        )
-        return HttpResponseRedirect(url)
+	template_name = 'admin/memberdb/membership_approve.html'
+	form_class = MembershipApprovalForm
+	model = Membership
+	pk_url_kwarg = 'object_id'
+	# override with the instance of ModelAdmin
+	admin = None
+
+	def get_context_data(self, **kwargs):
+		ms = self.get_object()
+		context = super().get_context_data(**kwargs)
+		context.update(self.admin.admin_site.each_context(self.request))
+		context.update({
+			'opts': self.admin.model._meta,
+			'ms': ms,
+			'member': ms.member,
+			'show_member_summary': True,
+		})
+		return context
+
+	"""
+	called when the approval form is submitted and valid data (according to the form's field types and defined validators) is given
+	"""
+	def form_valid(self, form):
+		ms = form.save()
+
+		self.admin.message_user(self.request, 'Approve success')
+		url = reverse(
+			'admin:memberdb_membership_changelist',
+			args=[],
+			current_app=self.admin.admin_site.name,
+		)
+		return HttpResponseRedirect(url)
 
 
diff --git a/src/memberdb/models.py b/src/memberdb/models.py
index 622049c3cad0e02566b7bc1f2239b68b681805e5..f4bcea338a17f96b31e7bdc918a39d4d28bab5b5 100644
--- a/src/memberdb/models.py
+++ b/src/memberdb/models.py
@@ -100,13 +100,18 @@ def get_membership_type(member):
 	return best
 
 def make_pending_membership(member):
-    # check if this member already has a pending membership
-    ms = Membership.objects.filter(member=member, approved__exact=False).first()
-    if (ms is None):
-        ms = Membership(member=member, approved=False)
-    ms.date_submitted = timezone.now()
-    ms.membership_type = get_membership_type(member)
-    return ms
+	""" creates or updates and returns a pending membership for the given member """
+	latest = member.get_last_renewal()
+	if latest is None or latest.date_submitted.year != timezone.now().year:
+		# create a Membership if none exists already for this year
+		latest = Membership(member=member)
+		latest.membership_type = get_membership_type(member)
+
+	# otherwise update the existing membership and mark as pending
+	latest.approved = False
+	latest.date_submitted = timezone.now()
+
+	return latest
 
 def make_token():
 	return get_random_string(128)
@@ -162,8 +167,6 @@ 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=True, blank=True, unique=False, validators=[RegexValidator(regex='^[a-z0-9._-]*$')])
@@ -182,6 +185,10 @@ class Member (IncAssocMember):
 
 	has_account		= models.BooleanField ('Has AD account', null=False, editable=False, default=False)
 
+	def get_last_renewal(self):
+		""" returns the most recently submitted Membership object """
+		return self.memberships.order_by('-date_submitted').first()
+
 	# account info
 	def get_uid(self):
 		result, uid = subprocess.getstatusoutput(["id", "-u", self.username])
@@ -210,7 +217,7 @@ class Membership (models.Model):
 	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')
-	date_submitted  = models.DateTimeField ('Date signed up')
+	date_submitted  = models.DateTimeField ('Date signed up', default=timezone.now)
 	date_paid       = models.DateTimeField ('Date of payment', blank=True, null=True)
 	date_approved   = models.DateTimeField ('Date approved', blank=True, null=True)
 
@@ -220,6 +227,9 @@ class Membership (models.Model):
 	def get_dispense_item(self):
 		return MEMBERSHIP_TYPES[self.membership_type]['dispense']
 
+	def get_pretty_type(self):
+		return MEMBERSHIP_TYPES[self.membership_type]['desc']
+
 	class Meta:
 		verbose_name = "Membership renewal record"
 		ordering = ['approved', '-date_submitted']
diff --git a/src/memberdb/register.py b/src/memberdb/register.py
index 18cac9beab5597c475ffb9b331767f70a2341eb9..06a78b2b638371b8ebc66ea5f7829a366364246b 100644
--- a/src/memberdb/register.py
+++ b/src/memberdb/register.py
@@ -7,6 +7,7 @@ from django.shortcuts import render
 from django.urls import reverse
 from django.contrib.auth.mixins import LoginRequiredMixin
 from django.utils.safestring import mark_safe
+from django.utils import timezone
 from django.contrib import messages
 from django import forms
 
@@ -44,7 +45,7 @@ class RegisterRenewForm(MyModelForm):
 			if (self['email_address'].value() != self['confirm_email'].value()):
 				self.add_error('email_address', 'Email addresses must match.')
 			if (self['email_address'].value().lower().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();
@@ -57,8 +58,8 @@ class RegisterRenewForm(MyModelForm):
 		# 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)
+		ms.membership_type = self.cleaned_data['membership_type']
 
 		if (commit):
 			ms.save();
@@ -112,13 +113,18 @@ 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 get_context_data(self, **kwargs):
+		""" update view context with current renewal year """
+		context = super().get_context_data(**kwargs)
+		context['year'] = timezone.now().year
+		return context
+
 	def form_valid(self, form):
+		"""
+		called when valid form data has been POSTed
+		invalid form data simply redisplays the form with validation errors
+		"""
 		# save the member data and get the Member instance
 		m, ms = form.save()
 		messages.success(self.request, 'Your registration has been submitted.')
@@ -139,29 +145,37 @@ def thanks_view(request, member, ms):
 	}
 	return render(request, 'thanks.html', context)
 
-class RenewView(LoginRequiredMixin, MyUpdateView):
+class RenewView(LoginRequiredMixin, RegisterView):
 	template_name = 'renew.html'
 	form_class = RenewForm
 	model = Member
 
 	def get_object(self):
+		""" try to get a pending renewal for this year (to edit & resubmit) otherwise create a new one """
 		u = self.request.user
+		m = self.request.member
 
-		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
+		if m is None:
+			# this member is not in the DB yet - make a new Member object and prefill some data
+			m = Member(username=u.username)
+			m.first_name = u.first_name
+			m.last_name = u.last_name
+			m.email_address = u.email
+			m.login_token = None # renewing members won't need this, make sure it is null for security
+		return m
 
 	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,
-		})
+		last_renewal = self.object.get_last_renewal()
+
+		# renew.html says whether a record exists in the DB or not
+		context['is_new'] = self.request.member is None
+
+		# let the template check if user has already renewed this year so it displays a warning
+		if last_renewal is not None:
+			context['last_renewal'] = last_renewal.date_submitted.year
+			context['memberships'] = [last_renewal]
+
 		return context
 
 	def form_valid(self, form):
diff --git a/src/memberdb/views.py b/src/memberdb/views.py
index 32ce1762d16d3600082f85af132c44d7dedc60a3..334daf71bb53d08422bed4c0ebb09e381370e8b8 100644
--- a/src/memberdb/views.py
+++ b/src/memberdb/views.py
@@ -108,24 +108,13 @@ class MemberHomeView(MemberAccessMixin, MyUpdateView):
     def get_object(self):
         return self.request.member
 
-    def get_membership_context(self, ms):
-        """ gets the per-membership-record context data """
-        return {
-            'id': ms.id,
-            'type': MEMBERSHIP_TYPES[ms.membership_type]['desc'],
-            'submitted': ms.date_submitted.strftime('%Y-%m-%d %H:%M'),
-            'paid': ms.date_paid.strftime('%Y-%m-%d %H:%M') if ms.date_paid is not None else None,
-            'approved': ms.date_approved.strftime('%Y-%m-%d %H:%M') if ms.approved else None,
-            'is_approved': ms.approved,
-        }
-
     def get_context_data(self):
         d = super().get_context_data()
         m = self.get_object()
 
         if m is not None:
             # get a list of all the membership records associated with this member
-            ms_list = [ self.get_membership_context(ms) for ms in m.memberships.all() ]
+            ms_list = m.memberships.all()
             d.update({
                 'memberships': ms_list,
             })
diff --git a/src/squarepay/dispense.py.orig b/src/squarepay/dispense.py.orig
deleted file mode 100644
index fbce946c21781aeb4ab90fd60527ed758afbf57e..0000000000000000000000000000000000000000
--- a/src/squarepay/dispense.py.orig
+++ /dev/null
@@ -1,102 +0,0 @@
-"""
-this file contains utilities for wrapping the opendispense2 CLI utility `dispense`
-It is essentially a hack to avoid having to write an actual dispense client here.
-"""
-
-import subprocess
-from subprocess import CalledProcessError, TimeoutExpired
-from django.conf import settings
-
-from .payments import log
-
-DISPENSE_BIN = getattr(settings, 'DISPENSE_BIN', None)
-
-if DISPENSE_BIN is None:
-	log.warning("DISPENSE_BIN is not defined! Lookups for prices will fallback to weird numbers (for testing)!")
-
-def run_dispense(*args):
-<<<<<<< HEAD
-	if DISPENSE_BIN is None:
-		return None
-	
-	cmd = [DISPENSE_BIN] + args
-	log.info("run_dispense: " + cmd)
-	try:
-		# get a string containing the output of the program
-		res = subprocess.check_output(cmd, timeout=4, universal_newlines=True)
-	except CalledProcessError as e:
-		log.warning("dispense returned error code %d, output: '%s'" % (e.returncode, e.output))
-		return None
-	except TimeoutExpired as e:
-		log.error(e)
-		return None
-	return res
-
-def get_item_price(itemid):
-	""" gets the price of the given dispense item in cents """
-	if (itemid is None or itemid == ""):
-		return None
-	if DISPENSE_BIN is None:
-		return 2223
-
-	out = run_dispense('iteminfo', itemid)
-	if out is None:
-		return None
-	
-	s = out.split() # get something like ['pseudo:7', '25.00', 'membership', '(non-student', 'and', 'non-guild)']
-	if (s[0] != itemid):
-		log.warning("get_item_price: got result for incorrect item: %s" + s)
-		return None
-	else:
-		# return the price as a number of cents
-		return int(float(s[0]) * 100)
-
-def set_dispense_flag(user, flag, reason):
-	if DISPENSE_BIN is None:
-		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":
-		# user was not updated
-		log.warning("set_dispense_flag: user was not updated with error: " + out)
-		return False;
-	return True;
-=======
-    if DISPENSE_BIN is None:
-        return None
-    
-    cmd = (DISPENSE_BIN, ) + args
-    log.info("run_dispense: " + str(cmd))
-    try:
-        # get a string containing the output of the program
-        res = subprocess.check_output(cmd, timeout=4, universal_newlines=True)
-    except CalledProcessError as e:
-        log.warning("dispense returned error code %d, output: '%s'" % (e.returncode, e.output))
-        return None
-    except TimeoutExpired as e:
-        log.error(e)
-        return None
-    return res
-
-def get_item_price(itemid):
-    """ gets the price of the given dispense item in cents """
-    if (itemid is None or itemid == ""):
-        return None
-    if DISPENSE_BIN is None:
-        return 2223
-
-    out = run_dispense('iteminfo', itemid)
-    if out is None:
-        return None
-    
-    s = out.split() # get something like ['pseudo:7', '25.00', 'membership', '(non-student', 'and', 'non-guild)']
-    if (s[0] != itemid):
-        log.warning("get_item_price: got result for incorrect item: %s" + s)
-        return None
-    else:
-        # return the price as a number of cents
-        return int(float(s[1]) * 100)
->>>>>>> origin/frekk-testing
diff --git a/src/templates/home.html b/src/templates/home.html
index 4961defc30abaa76fb9db16f39810d17687aff52..e3f690a69a9be572bcd98190d461ef707bd448c9 100644
--- a/src/templates/home.html
+++ b/src/templates/home.html
@@ -31,45 +31,7 @@
     <h4>Membership renewals on record</h4>
     <!-- stuff from the membership record itself -->
     {% if memberships %}
-    <table class="membership-details">
-        <tr class="{% cycle 'row1' 'row2' as rcl %}">
-            <th>Membership type</th>
-            <th>Submitted</th>
-            <th>Paid</th>
-            <th>Approved</th>
-            <th>Actions</th>
-        </tr>
-        {% for ms in memberships %}
-        <tr class="{% cycle rcl %}">
-            <td>
-                {{ ms.type }}
-            </td>
-            <td>
-                {{ ms.submitted }}
-            </td>
-            <td>
-                {% if ms.paid %}
-                <img src="{% static 'admin/img/icon-yes.svg' %}" alt="yes">&nbsp; {{ ms.paid }}
-                {% else %}
-                <img src="{% static 'admin/img/icon-no.svg' %}" alt="no">&nbsp; Not paid yet
-                {% endif %}
-            </td>
-            <td>
-                {% if ms.approved and ms.is_approved %}
-                <img src="{% static 'admin/img/icon-yes.svg' %}" alt="yes">&nbsp; {{ ms.approved }}
-                {% elif ms.approved and not ms.is_approved %}
-                <img src="{% static 'admin/img/icon-no.svg' %}" alt="no">&nbsp; Rejected: {{ ms.approved }}
-                {% else %}
-                <img src="{% static 'admin/img/icon-unknown.svg' %}" alt="?">&nbsp; Not approved yet
-                {% endif %}
-            </td>
-            <td>
-                <!-- membership actions -->
-                {% if not ms.paid %}<a class="button" href="{% url 'squarepay:pay_membership' ms.id %}">Pay now</a>{% endif %}
-            </td>
-        </tr>
-        {% endfor %}
-    </table>
+	{% include 'membership_list.html' %}
     {% else %}
     No membership renewal records for your account exist yet. Please <a href="{% url 'memberdb:renew' %}">renew your membership</a> to get started.
     {% endif %}
diff --git a/src/templates/membership_list.html b/src/templates/membership_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..c5e4b1020a3fbc6b05c0b82d6ced82ba5bbaf85a
--- /dev/null
+++ b/src/templates/membership_list.html
@@ -0,0 +1,44 @@
+{% load static %}
+<table class="membership-details">
+	<tr class="{% cycle 'row1' 'row2' as rcl %}">
+		<th>Membership type</th>
+		<th>Submitted / Updated</th>
+		<th>Paid</th>
+		<th>Approved</th>
+		<th>Actions</th>
+	</tr>
+	{% for ms in memberships %}
+	<tr class="{% cycle rcl %}">
+		<td>
+			{{ ms.get_pretty_type }}
+		</td>
+		<td>
+			{{ ms.date_submitted |date:"Y-m-d H:i" }}
+		</td>
+		<td>
+			{% if ms.date_paid %}
+			<img src="{% static 'admin/img/icon-yes.svg' %}" alt="yes">&nbsp; {{ ms.date_paid |date:"Y-m-d H:i" }}
+			{% else %}
+			<img src="{% static 'admin/img/icon-no.svg' %}" alt="no">&nbsp; Not paid yet
+			{% endif %}
+		</td>
+		<td>
+			{% if ms.date_approved and ms.approved %}
+			<img src="{% static 'admin/img/icon-yes.svg' %}" alt="yes">&nbsp; {{ ms.date_approved |date:"Y-m-d H:i" }}
+			{% elif ms.date_approved and not ms.approved %}
+			<img src="{% static 'admin/img/icon-no.svg' %}" alt="no">&nbsp; Rejected: {{ ms.date_approved |date:"Y-m-d H:i" }}
+			{% else %}
+			<img src="{% static 'admin/img/icon-unknown.svg' %}" alt="?">&nbsp; Not approved yet
+			{% endif %}
+		</td>
+		<td>
+			<!-- membership actions -->
+			{% if not ms.date_paid %}
+				<a class="button" href="{% url 'squarepay:pay_membership' ms.id %}">Pay now</a>
+			{% else %}
+				-
+			{% endif %}
+		</td>
+	</tr>
+	{% endfor %}
+</table>
diff --git a/src/templates/register.html b/src/templates/register.html
index 3a318e05a5e26ca892eb50546d32a96d12ca574a..f557da3dc806761d357efbba1361846c08b210c2 100644
--- a/src/templates/register.html
+++ b/src/templates/register.html
@@ -2,7 +2,7 @@
 
 {% block title %}UCC Registration{% endblock %}
 
-{% block content_title %}<h1>Register as a new member</h1>{% endblock %}
+{% block content_title %}<h1>Register as a new member for {{ year }}</h1>{% endblock %}
 
 {% block tips %}
     Enter your details, and press "Register" when you are done.<br>
diff --git a/src/templates/renew.html b/src/templates/renew.html
index 6fc5842482d01bc541e47ac0d510c059ac79c268..3a67a1f072b80ac4d418589fcab5e5323332807a 100644
--- a/src/templates/renew.html
+++ b/src/templates/renew.html
@@ -1,7 +1,7 @@
 {% extends "base_form.html" %}
 {% block title %}UCC Membership Renewal{% endblock %}
 {% block content_title %}
-    <h1>Renew your membership</h1>
+	<h1>Renew your membership for {{ year }}</h1>
 {% endblock %}
 
 {% block tips %}
@@ -9,17 +9,26 @@
 <b>Your account exists already but no membership information has yet been recorded in this system.</b>
 <br>Please update/correct the details below as necessary. Some have probably been filled for you.
 {% else %}
-Please confirm that the details below have not changed since your last registration or membership renewal.
+	{% if last_renewal == year %}
+		You have already submitted a membership renewal for {{ year }}. You may update your details below if they have changed, although any changes you make will need to be approved.
+	{% else %}
+		Please confirm that the details below have not changed since your last registration or membership renewal.
+	{% endif %}
 {% endif %}
 {% endblock %}
 
 {% block extra_preform %}
+{% if memberships and last_renewal == year %}
+<div class="form-row">
+	{% include 'membership_list.html' %}
+</div>
+{% endif %}
 <div class="form-row readonly">
-    <label for="id_username">Username:</label>
-    <span class="text" id="id_username">{{ request.user.username }}</span>
+	<label for="id_username">Username:</label>
+	<span class="text" id="id_username">{{ request.user.username }}</span>
 </div>
 {% endblock %}
 
 {% block action_url %}{% url 'memberdb:renew' %}{% endblock %}
 
-{% block action_text %}Renew{% endblock %}
+{% block action_text %}{% if last_renewal == year %}Save &amp; Update{% else %}Renew{% endif %}{% endblock %}