diff --git a/src/memberdb/register.py b/src/memberdb/register.py index e2f648d80c7b5aa7b3dd6cf25e22290497d6a536..60f1ba38e8820a922afe2098a1089eea8baca7e0 100644 --- a/src/memberdb/register.py +++ b/src/memberdb/register.py @@ -107,7 +107,6 @@ def thanks_view(request, member, ms): 'member': member, 'ms': ms, 'login_url': reverse('memberdb:login_member', kwargs={'username': member.username, 'member_token': member.login_token}), - 'payment_url': reverse('squarepay:pay_membership', kwargs={'pk': ms.id}), } return render(request, 'thanks.html', context) @@ -126,6 +125,7 @@ class RenewView(LoginRequiredMixin, MyUpdateView): 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): diff --git a/src/memberdb/views.py b/src/memberdb/views.py index b0abaa51692339999d16ffbe646576cd3a0f8934..a1c067039bec3e19244e17bb18f195a29961d801 100644 --- a/src/memberdb/views.py +++ b/src/memberdb/views.py @@ -10,7 +10,7 @@ from django.views.generic.edit import UpdateView from django.contrib.auth.mixins import AccessMixin from django.utils import timezone -from .models import Member, IncAssocMember, Membership +from .models import Member, IncAssocMember, Membership, MEMBERSHIP_TYPES, TokenConfirmation from .forms import MemberHomeForm class MemberMiddleware: @@ -48,10 +48,6 @@ class MemberMiddleware: request.member = Member.objects.get(id=request.session['member_id']) response = self.get_response(request) - - # Code to be executed for each request/response after - # the view is called. - return response class MemberAccessMixin(AccessMixin): @@ -66,7 +62,6 @@ Can update and create models. Also passes the request object to the form via its kwargs. """ class MyUpdateView(UpdateView): - can_create = True object = None def get_object(self): @@ -93,12 +88,27 @@ 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() - d.update({ - 'memberships': m.memberships.all() if m is not None else None, - }) + + 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() ] + d.update({ + 'memberships': ms_list, + }) return d def form_valid(self, form): @@ -111,15 +121,36 @@ 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 ('member_token' in kwargs and 'username' in kwargs) or request.user.is_authenticated: - raise Http404() + 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) + + try: + member = Member.objects.get( + login_token=kwargs['member_token'], + username=kwargs['username'], + created__gte=week_ago + ) + except Member.DoesNotExist: + raise Http404() - # look up the member using exact match for token and username, and registered < 7 days ago - week_ago = timezone.now() - timedelta(days=7) + request.session['member_id'] = member.id + return HttpResponseRedirect(reverse('memberdb:home')) - member = Member.objects.get(login_token=kwargs['member_token'], username=kwargs['username'], created__gte=week_ago) - if member is None: - raise Http404() +class EmailConfirmView(View): + """ process email confirmations """ - request.session['member_id'] = member.id + def get(self, request, **kwargs): + week_ago = timezone.now() - timedelta(days=7) + try: + c = TokenConfirmation.objects.get( + id = kwargs['pk'], + confirm_token = kwargs['token'], + created__gte = week_ago + ) + c.mark_confirmed() + except: + pass + messages.success(request, "Your email address has been confirmed.") return HttpResponseRedirect(reverse('memberdb:home')) + diff --git a/src/static/memberdb.css b/src/static/memberdb.css index 74752c0452c888d7de41dae8c9f5102bac1806e1..004be23a6dae2550e8c7fd41ee4fd8d666860070 100644 --- a/src/static/memberdb.css +++ b/src/static/memberdb.css @@ -130,3 +130,8 @@ nav { .navtab:last-child { border-right: 1.5px solid #555; } + +.member-details, .membership-details { + width: 100%; + text-align: left; +} diff --git a/src/static/shared.css b/src/static/shared.css index e1a893818bd35010dfb11b1be756e8c05a7ac7c9..dc9e6ceb24e1fcbb0c03e83f3fa8938e690ae982 100644 --- a/src/static/shared.css +++ b/src/static/shared.css @@ -126,6 +126,7 @@ ul.messagelist li.error { a.button { padding: 4px 5px; + display: inline-block; } .button:active, input[type=submit]:active, input[type=button]:active, @@ -161,3 +162,57 @@ input[type=button][disabled].default { font-size: 12px; color: #999; } + +/* TABLES */ + +table { + border-collapse: collapse; + border-color: #ccc; +} + +td, th { + font-size: 13px; + line-height: 16px; + border-bottom: 1px solid #eee; + vertical-align: top; + padding: 8px; + /*font-family: "Roboto", "Lucida Grande", Verdana, Arial, sans-serif;*/ +} + +th { + font-weight: 600; + text-align: left; +} + +thead th, +tfoot td { + color: #666; + padding: 5px 10px; + font-size: 11px; + background: #fff; + border: none; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; +} + +tfoot td { + border-bottom: none; + border-top: 1px solid #eee; +} + +thead th.required { + color: #000; +} + +tr.alt { + background: #f6f6f6; +} + +.row1 { + background: rgba(255, 255, 255, 0.7); +} + +.row2 { + background: rgba(230, 230, 230, 0.7); +} + diff --git a/src/templates/admin/memberdb/membership_actions.html b/src/templates/admin/memberdb/membership_actions.html index 51019cba9f78557b3aa788e411a7b4bff49a47e4..6073fe9412c12194dc85e258f034a478713d78a6 100644 --- a/src/templates/admin/memberdb/membership_actions.html +++ b/src/templates/admin/memberdb/membership_actions.html @@ -1,7 +1,7 @@ -{% load static %} +{% 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 Member</a> + <a class="button" href="{{ member_url }}">Edit</a> {% if not ms.approved %} <a class="button" href="{{ member_approve }}">Approve</a> {% endif %} -</div> \ No newline at end of file +</div> diff --git a/src/templates/admin/memberdb/membership_summary.html b/src/templates/admin/memberdb/membership_summary.html index 3f8de8d1a284481567c0b0015e66a034b5bada86..702b76935eb18c7fe979b0d2e2940b37b63a6ee4 100644 --- a/src/templates/admin/memberdb/membership_summary.html +++ b/src/templates/admin/memberdb/membership_summary.html @@ -1,39 +1,6 @@ -{% load static %} -{% block member-summary %} -<table class="ms-summary"> - <tr class="{% cycle 'row1' 'row2' as rcl %}"> - <th>Real name</th> - <td><b>{{ member.first_name }} {{ member.last_name }}</b></td> - </tr> - <tr class="{% cycle rcl %}"> - <th>Username & display name</th> - <td><b>{{ member.username }}</b> ({{ member.display_name }})</td> - </tr> - <tr class="{% cycle rcl %}"> - <th>Email</th> - <td> - {% if member.email_address %} - <a href="mailto:{{ member.email_address }}">{{ member.email_address }}</a> - {% else %} - <i>not provided</i> - {% endif %} - </td> - </tr> - <tr class="{% cycle rcl %}"> - <th>Phone number</th> - <td>{% if member.phone_number %}{{ member.phone_number }}{% else %}<i>not provided</i>{% endif %}</td> - </tr> - <tr class="{% cycle rcl %}"> - <th>Is student</th> - <td>{% if member.is_student %}<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>Is UWA Guild</th> - <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 / License no.</th> - <td>{% if member.id_number %}{{ member.id_number }}{% else %}<i>not provided</i>{% endif %}</td> - </tr> -</table> -{% endblock %} \ No newline at end of file +{# this template is rendered and displayed as the contents of the "membership info" column in the admin pages #} +{% extends "membership_summary.html" %} + +{% block header1 %}{% endblock %} + + diff --git a/src/templates/base_form.html b/src/templates/base_form.html index 76d12e8e7fcc45f68a07a051a613a85158f2a911..a979ae5f92ea3add22d12650b19a3d8173adfe06 100644 --- a/src/templates/base_form.html +++ b/src/templates/base_form.html @@ -37,6 +37,7 @@ {% endif %} </div> {% endfor %} + {% block postform %}{% endblock %} <div class="submit-row"> <input type="submit" class="default" value="{% block action_text %}Register{% endblock %}"> </div> diff --git a/src/templates/home.html b/src/templates/home.html index 5c86bb993b52e1b49c6c76ac589948d4c9041998..4961defc30abaa76fb9db16f39810d17687aff52 100644 --- a/src/templates/home.html +++ b/src/templates/home.html @@ -1,34 +1,92 @@ {% extends "base_form.html" %} +{% load static %} {% block title %}UCC Member Home{% endblock %} {% block content_title %} <h1>Member home</h1> - <h3>Welcome, {{ request.member.first_name }} {{ request.member.last_name }} ({{ request.member.username }})</h3> + {% if request.member %} + <h3>Welcome, {{ request.member.first_name }} {{ request.member.last_name }} (<code>{{ request.member.username }}</code>)</h3> + {% else %} + <h3>Welcome, {{ request.user.first_name }} {{ request.user.last_name }} (<code>{{ request.user.username }}</code>)</h3> + {% endif %} {% endblock %} {% block tips %} {% if not request.member %} -{% if request.user.is_authenticated %} -<b>You have no member record associated with this account.</b><br> -Please <a href="{% url 'memberdb:renew' %}">renew your membership</a> to get started. + {% if request.user.is_authenticated %} + <b>You have no member record associated with this account.</b><br> + Please <a href="{% url 'memberdb:renew' %}">renew your membership</a> to get started. + {% else %} + <b>Something went wrong and your membership details could not be retrieved.</b> Please try <a href="{% url 'memberdb:login' %}">logging in</a>. + {% endif %} {% else %} -<b>Something went wrong and your membership details could not be retrieved.</b> Please try <a href="{% url 'memberdb:login' %}">logging in</a>. -{% endif %} -{% else %} -You can see and modify some of your membership and account details below. + You can see and modify some of your membership and account details below. {% endif %} {% endblock %} {% block extra_preform %} -<div class="form-row readonly"> - <label for="id_username">Username:</label> - <span class="text" id="id_username">{{ object.username }}</span> +<div class="form-row"> + {% include 'membership_summary.html' %} +</div> +<div class="form-row"> + <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"> {{ ms.paid }} + {% else %} + <img src="{% static 'admin/img/icon-no.svg' %}" alt="no"> Not paid yet + {% endif %} + </td> + <td> + {% if ms.approved and ms.is_approved %} + <img src="{% static 'admin/img/icon-yes.svg' %}" alt="yes"> {{ ms.approved }} + {% elif ms.approved and not ms.is_approved %} + <img src="{% static 'admin/img/icon-no.svg' %}" alt="no"> Rejected: {{ ms.approved }} + {% else %} + <img src="{% static 'admin/img/icon-unknown.svg' %}" alt="?"> 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> + {% else %} + No membership renewal records for your account exist yet. Please <a href="{% url 'memberdb:renew' %}">renew your membership</a> to get started. + {% endif %} </div> +<br> {% endblock %} {% block form %} {% if object %} {{ block.super }} {% endif %} {% endblock %} +{% block postform %} +<div class="form-row"> +<p class="help">To update your other membership details, please talk to a door or committee member.</p> +</div> +{% endblock %} + {% block action_url %}{% url 'memberdb:home' %}{% endblock %} {% block action_text %}Update details{% endblock %} diff --git a/src/templates/membership_summary.html b/src/templates/membership_summary.html new file mode 100644 index 0000000000000000000000000000000000000000..aeffa35b3f30715546aab9fd453ebb67de50e41e --- /dev/null +++ b/src/templates/membership_summary.html @@ -0,0 +1,55 @@ +{# this template is used by the admin membership renewal page and member homepage #} +{% load static %} +{% block header1 %} +<h4>Current member details</h4> +{% endblock %} +{% block member_details %} +<!-- try to display only the details which aren't editable on member home page --> +<table class="member-details"> + {% block name %} + <tr class="{% cycle 'row1' 'row2' as rcl %}"> + <th>Real name</th> + <td><b>{{ member.first_name }} {{ member.last_name }}</b></td> + </tr> + <tr class="{% cycle rcl %}"> + <th>Username & display name</th> + <td><b>{{ member.username }}</b> ({{ member.display_name }})</td> + </tr> + {% endblock %} + + {% block email %} + <tr class="{% cycle rcl %}"> + <th>Email</th> + <td> + {% if member.email_address %} + <a href="mailto:{{ member.email_address }}">{{ member.email_address }}</a> + {% else %} + <i>not provided</i> + {% endif %} + </td> + </tr> + {% endblock %} + + {% block phone %} + <tr class="{% cycle rcl %}"> + <th>Phone number</th> + <td>{% if member.phone_number %}{{ member.phone_number }}{% else %}<i>not provided</i>{% endif %}</td> + </tr> + {% endblock %} + + {% block student %} + <tr class="{% cycle rcl %}"> + <th>Is student</th> + <td>{% if member.is_student %}<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>Is UWA Guild</th> + <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> + <td>{% if member.id_number %}{{ member.id_number }}{% else %}<i>not provided</i>{% endif %}</td> + </tr> + {% endblock %} +</table> +{% endblock %}