mirror of
https://github.com/fspc/workstand.git
synced 2025-02-22 17:03:23 -05:00
Add member notes (#35)
* Linting. * Clean up. * Hope this makes installs better. * Rework edit member form with notes and banned and suspended. * Add slack notifications. * Display new member statuses. * Update docker run command migrate, collectstatic, and rebuild_index * Formatting and only push when master. * Don’t need this for an edit page. * More meaningful action. * Add testing requirements. * Run tests during build. * Use prod settings for testing. * Nail down the order of those tests.
This commit is contained in:
parent
960e107415
commit
49f37ed492
16
.travis.yml
16
.travis.yml
@ -4,10 +4,12 @@ services:
|
||||
before_install:
|
||||
- docker build -t bcbc/workstand:production .
|
||||
after_success:
|
||||
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD";
|
||||
- export REPO=bcbc/workstand
|
||||
- export TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "production"; else echo $TRAVIS_BRANCH ; fi`
|
||||
- docker build -f Dockerfile -t $REPO:$COMMIT .
|
||||
- docker tag $REPO:$COMMIT $REPO:$TAG
|
||||
- docker tag $REPO:$COMMIT $REPO:travis-$TRAVIS_BUILD_NUMBER
|
||||
- docker push $REPO
|
||||
|
||||
after_success:
|
||||
- if [ "$TRAVIS_BRANCH" == "master" ]; then
|
||||
docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD";
|
||||
docker push bcbc/workstand:production;
|
||||
fi
|
||||
notifications:
|
||||
slack:
|
||||
secure: n2tF/UW/3cnLgqxU0iq6M6mXoYitXgv4x8Iqv9uFuO6AXJktKADVbSeHVmeDVJu7vlyexytpN7BfJ2W0rbMB5Q+yef3TmWhc5P0MWRR3J+QwbLXdqBIP2tku69OK2aUEcxPvoJZLtDKdfNTt9vtQurGC9kbJ+MKmU0Sm+MFrrxrM4D7/74rynbhppMnwcwouVgzdfmJOt0gc0ySZlWqOacUSOVZxhpJ6aqABkFHWbIzO9LHXBwFLJU3oa8poFg51AYx2cuy7PjHfMtEt2i5bHXPR0NkVm4UMbtmkLvvmHLErS1tAHUgWnLAKAL8i7j8hsKWjBc/+0Pwz7t4j5M79+XcF78dS7VQOzmrP/TdyWfsN4rZeUvl+b+owZKLv1uLG6kQhRI332fzCwtK+VejtBtjdoC9nq1KTOIHa6y10aswqGgfsPL4+6UOke/LZsgb9fqfiB44TYkn0dgfh1J/3P+hNtLOsm1Q2IZQvDU97CqdavfAlye4uKC/WlnY3fXeISEMeE57ZAu8Tz4IKrAIbg7mt7qFTmNqxUIvzfWO5ZaMQZhncRfIMajAUigXr9XjRkewo9cxF2cuDwllTVeY5MTrq8hTqlr0g/N5njS72KkLAzaXKUmzyTpfndrGKH5K2XiSGlpgOZ6ao/FOKr3TmzNV5fiOsLEr9Y2+Q3bgknOE=
|
||||
|
@ -7,6 +7,7 @@ WORKDIR /code
|
||||
RUN mkdir requirements
|
||||
ADD bikeshop_project /code
|
||||
ADD requirements/base.txt /code/requirements/base.txt
|
||||
ADD requirements/testing.txt /code/requirements/testing.txt
|
||||
ADD requirements/production.txt /code/requirements/production.txt
|
||||
RUN pip install -r requirements/production.txt
|
||||
RUN npm cache clean
|
||||
@ -16,5 +17,6 @@ RUN bower install --allow-root
|
||||
ADD ./bikeshop_project/package.json package.json
|
||||
RUN npm install --unsafe-perm
|
||||
RUN npm run build-production
|
||||
RUN DJANGO_SETTINGS_MODULE=bikeshop.settings.production python manage.py collectstatic --no-input
|
||||
RUN DJANGO_SETTINGS_MODULE=bikeshop.settings.production python manage.py test
|
||||
CMD 'bash -c "PYTHONUNBUFFERED=TRUE python manage.py migrate --no-input && python manage.py collectstatic --no-input && python manage.py rebuild_index --noinput && gunicorn --log-file=- -b 0.0.0.0:8000 bikeshop.wsgi:application"'
|
||||
EXPOSE 8000
|
||||
|
@ -4,6 +4,7 @@ WORKDIR /code
|
||||
ADD ./bikeshop_project/package.json package.json
|
||||
RUN npm install
|
||||
RUN npm install -g bower
|
||||
ADD ./bikeshop_project/.bowerrc .bowerrc
|
||||
ADD ./bikeshop_project/bower.json bower.json
|
||||
RUN bower install --allow-root
|
||||
EXPOSE 3000:3000
|
||||
|
@ -29,11 +29,14 @@ export default class SignIn extends React.Component {
|
||||
componentDidMount() {
|
||||
fetch('/members/signin/')
|
||||
.then(response => response.json())
|
||||
.then((data) => {
|
||||
const visits = JSON.parse(data);
|
||||
.then((visits) => {
|
||||
this.setState({ signedIn: visits.map(visit => ({
|
||||
id: visit.member.id,
|
||||
banned: visit.member.banned,
|
||||
suspended: visit.member.suspended,
|
||||
purpose: visit.purpose,
|
||||
first_name: visit.member.first_name,
|
||||
last_name: visit.member.last_name,
|
||||
text: `${visit.member.first_name} ${visit.member.last_name}`,
|
||||
value: `${visit.member.first_name} ${visit.member.last_name} <${visit.member.email}>`,
|
||||
at: moment(visit.created_at),
|
||||
@ -68,14 +71,11 @@ export default class SignIn extends React.Component {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
const signedIn = this.state.signedIn;
|
||||
const parsedData = JSON.parse(data);
|
||||
|
||||
signedIn.push({ ...member, purpose, at: moment(parsedData.results.created_at) });
|
||||
.then((parsedData) => {
|
||||
this.setState({
|
||||
...this.state,
|
||||
signedIn,
|
||||
signedIn: [...this.state.signedIn,
|
||||
{ ...parsedData.results, purpose, at: moment(parsedData.results.created_at) }],
|
||||
signOn: { purpose: 'FIX', member: undefined },
|
||||
searchText: '',
|
||||
members: [],
|
||||
|
@ -5,6 +5,33 @@ import React, { PropTypes } from 'react';
|
||||
import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table';
|
||||
import moment from 'moment';
|
||||
|
||||
const styles = {
|
||||
suspended: {
|
||||
display: 'block',
|
||||
padding: '10px',
|
||||
background: 'yellow',
|
||||
borderRadius: '5px',
|
||||
textAlign: 'center',
|
||||
color: 'black',
|
||||
},
|
||||
good: {
|
||||
display: 'block',
|
||||
padding: '10px',
|
||||
background: 'green',
|
||||
borderRadius: '5px',
|
||||
textAlign: 'center',
|
||||
color: 'white',
|
||||
},
|
||||
banned: {
|
||||
display: 'block',
|
||||
padding: '10px',
|
||||
background: 'red',
|
||||
borderRadius: '5px',
|
||||
textAlign: 'center',
|
||||
color: 'white',
|
||||
},
|
||||
};
|
||||
|
||||
export default class SignedInList extends React.Component {
|
||||
static propTypes = {
|
||||
members: PropTypes.arrayOf(PropTypes.shape({
|
||||
@ -40,14 +67,25 @@ export default class SignedInList extends React.Component {
|
||||
|
||||
render() {
|
||||
const memberRows = this.sortMembers(this.props.members)
|
||||
.map(member => (
|
||||
<TableRow selectable={false} key={member.id}>
|
||||
<TableRowColumn>{member.text}</TableRowColumn>
|
||||
<TableRowColumn>{member.purpose}</TableRowColumn>
|
||||
<TableRowColumn>{member.at.fromNow()}</TableRowColumn>
|
||||
<TableRowColumn><a href={`/members/edit/${member.id}/`}>Profile</a></TableRowColumn>
|
||||
</TableRow>
|
||||
));
|
||||
.map((member) => {
|
||||
let memberStatus = <span style={styles.good} className="good">good</span>;
|
||||
|
||||
if (member.banned) {
|
||||
memberStatus = <span style={styles.banned} className="banned">banned</span>;
|
||||
} else if (member.suspended) {
|
||||
memberStatus = <span style={styles.suspended} className="suspended">suspended</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow selectable={false} key={member.id}>
|
||||
<TableRowColumn>{member.first_name} {member.last_name}</TableRowColumn>
|
||||
<TableRowColumn>{member.purpose}</TableRowColumn>
|
||||
<TableRowColumn>{member.at.fromNow()}</TableRowColumn>
|
||||
<TableRowColumn>{memberStatus}</TableRowColumn>
|
||||
<TableRowColumn><a href={`/members/edit/${member.id}/`}>Profile</a></TableRowColumn>
|
||||
</TableRow>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mdl-cell mdl-cell--12-col">
|
||||
@ -57,11 +95,12 @@ export default class SignedInList extends React.Component {
|
||||
</FloatingActionButton>
|
||||
<Table selectable={false}>
|
||||
<TableHeader adjustForCheckbox={false} displaySelectAll={false}>
|
||||
<TableRow>
|
||||
<TableRow key="blah">
|
||||
<TableHeaderColumn>Name</TableHeaderColumn>
|
||||
<TableHeaderColumn>Purpose</TableHeaderColumn>
|
||||
<TableHeaderColumn>Signed-in At</TableHeaderColumn>
|
||||
<TableHeaderColumn/>
|
||||
<TableHeaderColumn>Member Status</TableHeaderColumn>
|
||||
<TableHeaderColumn />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody displayRowCheckbox={false}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import os
|
||||
import sys
|
||||
from .base import * # noqa
|
||||
|
||||
|
||||
@ -47,3 +47,7 @@ WEBPACK_LOADER = {
|
||||
'IGNORE': ['.+\.hot-update.js', '.+\.map']
|
||||
}
|
||||
}
|
||||
|
||||
# Covers regular testing and django-coverage
|
||||
if 'test' in sys.argv or 'test_coverage' in sys.argv:
|
||||
DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3' # noqa
|
||||
|
@ -1,4 +1,4 @@
|
||||
from django.forms import ModelForm, EmailInput, TextInput, DateInput, CheckboxInput, BooleanField
|
||||
from django.forms import ModelForm, EmailInput, TextInput, DateInput, CheckboxInput, BooleanField, Textarea
|
||||
from django.utils import timezone
|
||||
from registration.models import Member
|
||||
|
||||
@ -12,7 +12,8 @@ class MemberForm(ModelForm):
|
||||
|
||||
exclude = ('waiver',)
|
||||
fields = ['email', 'email_consent', 'first_name', 'last_name', 'preferred_name', 'date_of_birth',
|
||||
'guardian_name', 'phone', 'street', 'city', 'province', 'country', 'post_code', 'waiver']
|
||||
'guardian_name', 'phone', 'street', 'city', 'province', 'country', 'post_code', 'waiver',
|
||||
'banned', 'suspended', 'notes']
|
||||
widgets = {
|
||||
'email': EmailInput(attrs={'class': 'mdl-textfield__input'}),
|
||||
'email_consent': CheckboxInput(attrs={'class': 'mdl-checkbox__input'}),
|
||||
@ -28,6 +29,9 @@ class MemberForm(ModelForm):
|
||||
'country': TextInput(attrs={'class': 'mdl-textfield__input'}),
|
||||
'post_code': TextInput(attrs={'class': 'mdl-textfield__input',
|
||||
'pattern': '[A-Za-z][0-9][A-Za-z] [0-9][A-Za-z][0-9]'}),
|
||||
'notes': Textarea(attrs={'class': 'mdl-textfield__input'}),
|
||||
'suspended': CheckboxInput(attrs={'class': 'mdl-checkbox__input'}),
|
||||
'banned': CheckboxInput(attrs={'class': 'mdl-checkbox__input'}),
|
||||
}
|
||||
|
||||
labels = {
|
||||
|
@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-02-15 03:08
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('registration', '0002_auto_20161130_0157'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='member',
|
||||
name='banned',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='member',
|
||||
name='notes',
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='member',
|
||||
name='suspended',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
@ -93,6 +93,9 @@ class Member(models.Model):
|
||||
post_code = models.CharField(max_length=20, null=True, blank=False)
|
||||
waiver = models.DateTimeField(null=True, blank=True)
|
||||
is_active = models.BooleanField(default=True)
|
||||
notes = models.TextField(null=True, blank=True)
|
||||
suspended = models.BooleanField(default=False)
|
||||
banned = models.BooleanField(default=False)
|
||||
|
||||
def get_full_name(self):
|
||||
# The user is identified by their email address
|
||||
|
@ -10,4 +10,4 @@ class MemberSerializer(ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Member
|
||||
fields = ('first_name', 'last_name', 'email', 'id')
|
||||
fields = ('first_name', 'last_name', 'email', 'id', 'banned', 'suspended')
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{% static 'vendor/moment/min/moment.min.js' %}"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js"></script>
|
||||
<script>
|
||||
dateOfBirthInput = document.getElementById('{{ form.date_of_birth.id_for_label }}');
|
||||
|
||||
@ -23,41 +23,14 @@
|
||||
}
|
||||
});
|
||||
|
||||
var waiverCheckBox = document.getElementById('{{ form.waiver_substitute.id_for_label }}');
|
||||
var submitButton = document.getElementById('submit');
|
||||
var requiredCheckboxes = function() {
|
||||
return waiverCheckBox.checked;
|
||||
};
|
||||
|
||||
waiverCheckBox.addEventListener('change', function() {
|
||||
if (requiredCheckboxes()) {
|
||||
submitButton.disabled = false;
|
||||
}
|
||||
else {
|
||||
submitButton.disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
// On page load check on the check boxes
|
||||
if (requiredCheckboxes()) {
|
||||
submitButton.disabled = false;
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="mdl-cell mdl-cell--8-col">
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell mdl-cell--12-col">
|
||||
<h1>{{ form.instance.first_name }} {{ form.instance.last_name }}</h1>
|
||||
<p>
|
||||
The Bridge City Bicycle Co-operative (herein referred to as The BCBC and The Community) is a nonprofit,
|
||||
community bicycle repair education and resource co-operative. We offer our members nonjudgmental repair
|
||||
space, tools and instruction during business hours (hours on website) by donation, and educational
|
||||
workshops. We also offer reconditioned/recycled low cost bikes and parts for sale.
|
||||
The BCBC is operated by volunteers; a medley of professionals, students, bike enthusiasts, activists,
|
||||
and other community members who share a love for cycling in Saskatoon. Membership is open to all
|
||||
individuals and costs $20 per year. A receipt will be issued to you once your membership fee has been paid.
|
||||
</p>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
@ -66,118 +39,197 @@
|
||||
<span class="error">{{ form.errors }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.email.errors %}is-invalid{% endif %}">
|
||||
{{ form.email }}
|
||||
<label class="mdl-textfield__label" for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
|
||||
{% if form.email.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.email.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Invalid email.</span>
|
||||
{% endif %}
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell mdl-cell--12-col">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.email.errors %}is-invalid{% endif %}">
|
||||
{{ form.email }}
|
||||
<label class="mdl-textfield__label" for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
|
||||
{% if form.email.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.email.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Invalid email.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="{{ form.email_consent.id_for_label }}">
|
||||
{{ form.email_consent }}
|
||||
<span class="mdl-checkbox__label">{{ form.email_consent.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="{{ form.email_consent.id_for_label }}">
|
||||
{{ form.email_consent }}
|
||||
<span class="mdl-checkbox__label">{{ form.email_consent.label }}</span>
|
||||
</label>
|
||||
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.first_name.errors %}is-invalid{% endif %}">
|
||||
{{ form.first_name }}
|
||||
<label class="mdl-textfield__label" for="{{ form.first_name.id_for_label }}">{{ form.first_name.label }}</label>
|
||||
{% if form.first_name.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.first_name.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Name too long.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-cell">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.last_name.errors %}is-invalid{% endif %}">
|
||||
{{ form.last_name }}
|
||||
<label class="mdl-textfield__label" for="{{ form.last_name.id_for_label }}">{{ form.last_name.label }}</label>
|
||||
{% if form.last_name.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.last_name.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Name too long.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.first_name.errors %}is-invalid{% endif %}">
|
||||
{{ form.first_name }}
|
||||
<label class="mdl-textfield__label" for="{{ form.first_name.id_for_label }}">{{ form.first_name.label }}</label>
|
||||
{% if form.first_name.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.first_name.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Name too long.</span>
|
||||
{% endif %}
|
||||
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.preferred_name.errors %}is-invalid{% endif %}">
|
||||
{{ form.preferred_name }}
|
||||
<label class="mdl-textfield__label" for="{{ form.preferred_name.id_for_label }}">{{ form.preferred_name.label }}</label>
|
||||
{% if form.preferred_name.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.preferred_name.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Name too long.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-cell">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.phone.errors %}is-invalid{% endif %}">
|
||||
{{ form.phone }}
|
||||
<label class="mdl-textfield__label" for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
|
||||
{% if form.phone.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.phone.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Digits only.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.last_name.errors %}is-invalid{% endif %}">
|
||||
{{ form.last_name }}
|
||||
<label class="mdl-textfield__label" for="{{ form.last_name.id_for_label }}">{{ form.last_name.label }}</label>
|
||||
{% if form.last_name.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.last_name.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Name too long.</span>
|
||||
{% endif %}
|
||||
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.date_of_birth.errors %}is-invalid{% endif %}">
|
||||
{{ form.date_of_birth }}
|
||||
<label class="mdl-textfield__label" for="{{ form.date_of_birth.id_for_label }}">{{ form.date_of_birth.label }} (e.g. 1991-08-25)</label>
|
||||
{% if form.date_of_birth.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.date_of_birth.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Incorrect date.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-cell">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.guardian_name.errors %}is-invalid{% endif %}">
|
||||
{{ form.guardian_name }}
|
||||
<label class="mdl-textfield__label" for="{{ form.guardian_name.id_for_label }}">{{ form.guardian_name.label }}</label>
|
||||
{% if form.guardian_name.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.guardian_name.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Name too long.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.preferred_name.errors %}is-invalid{% endif %}">
|
||||
{{ form.preferred_name }}
|
||||
<label class="mdl-textfield__label" for="{{ form.preferred_name.id_for_label }}">{{ form.preferred_name.label }}</label>
|
||||
{% if form.preferred_name.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.preferred_name.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Name too long.</span>
|
||||
{% endif %}
|
||||
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.post_code.errors %}is-invalid{% endif %}">
|
||||
{{ form.post_code }}
|
||||
<label class="mdl-textfield__label" for="{{ form.post_code.id_for_label }}">{{ form.post_code.label }}</label>
|
||||
{% if form.post_code.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.post_code.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Format: A0A 0A0</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-cell">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.street.errors %}is-invalid{% endif %}">
|
||||
{{ form.street }}
|
||||
<label class="mdl-textfield__label" for="{{ form.street.id_for_label }}">{{ form.street.label }}</label>
|
||||
{% if form.street.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.street.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.date_of_birth.errors %}is-invalid{% endif %}">
|
||||
{{ form.date_of_birth }}
|
||||
<label class="mdl-textfield__label" for="{{ form.date_of_birth.id_for_label }}">{{ form.date_of_birth.label }} (e.g. 1991-08-25)</label>
|
||||
{% if form.date_of_birth.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.date_of_birth.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Incorrect date.</span>
|
||||
{% endif %}
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.city.errors %}is-invalid{% endif %}">
|
||||
{{ form.city }}
|
||||
<label class="mdl-textfield__label" for="{{ form.city.id_for_label }}">{{ form.city.label }}</label>
|
||||
{% if form.city.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.city.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-cell">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.province.errors %}is-invalid{% endif %}">
|
||||
{{ form.province }}
|
||||
<label class="mdl-textfield__label" for="{{ form.province.id_for_label }}">{{ form.province.label }}</label>
|
||||
{% if form.province.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.province.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.guardian_name.errors %}is-invalid{% endif %}">
|
||||
{{ form.guardian_name }}
|
||||
<label class="mdl-textfield__label" for="{{ form.guardian_name.id_for_label }}">{{ form.guardian_name.label }}</label>
|
||||
{% if form.guardian_name.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.guardian_name.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Name too long.</span>
|
||||
{% endif %}
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.country.errors %}is-invalid{% endif %}">
|
||||
{{ form.country }}
|
||||
<label class="mdl-textfield__label" for="{{ form.country.id_for_label }}">{{ form.country.label }}</label>
|
||||
{% if form.country.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.country.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.phone.errors %}is-invalid{% endif %}">
|
||||
{{ form.phone }}
|
||||
<label class="mdl-textfield__label" for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
|
||||
{% if form.phone.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.phone.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Digits only.</span>
|
||||
{% endif %}
|
||||
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell mdl-cell--12-col">
|
||||
<h2>Member Info</h2>
|
||||
<h3>Notes</h3>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--12-col">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-cell--12-col">
|
||||
{{ form.notes }}
|
||||
<label class="mdl-textfield__label" for="{{ form.notes.id_for_label }}">{{ form.notes.label }}</label>
|
||||
{% if form.notes.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.notes.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.post_code.errors %}is-invalid{% endif %}">
|
||||
{{ form.post_code }}
|
||||
<label class="mdl-textfield__label" for="{{ form.post_code.id_for_label }}">{{ form.post_code.label }}</label>
|
||||
{% if form.post_code.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.post_code.errors }}</span>
|
||||
{% else %}
|
||||
<span class="mdl-textfield__error">Format: A0A 0A0</span>
|
||||
{% endif %}
|
||||
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell mdl-cell--3-col">
|
||||
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="{{ form.suspended.id_for_label }}">
|
||||
{{ form.suspended }}
|
||||
<span class="mdl-checkbox__label">{{ form.suspended.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--3-col">
|
||||
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="{{ form.banned.id_for_label }}">
|
||||
{{ form.banned }}
|
||||
<span class="mdl-checkbox__label">{{ form.banned.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.street.errors %}is-invalid{% endif %}">
|
||||
{{ form.street }}
|
||||
<label class="mdl-textfield__label" for="{{ form.street.id_for_label }}">{{ form.street.label }}</label>
|
||||
{% if form.street.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.street.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.city.errors %}is-invalid{% endif %}">
|
||||
{{ form.city }}
|
||||
<label class="mdl-textfield__label" for="{{ form.city.id_for_label }}">{{ form.city.label }}</label>
|
||||
{% if form.city.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.city.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.province.errors %}is-invalid{% endif %}">
|
||||
{{ form.province }}
|
||||
<label class="mdl-textfield__label" for="{{ form.province.id_for_label }}">{{ form.province.label }}</label>
|
||||
{% if form.province.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.province.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.country.errors %}is-invalid{% endif %}">
|
||||
{{ form.country }}
|
||||
<label class="mdl-textfield__label" for="{{ form.country.id_for_label }}">{{ form.country.label }}</label>
|
||||
{% if form.country.errors %}
|
||||
<span class="mdl-textfield__error">{{ form.country.errors }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<button id="submit" type="submit" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored">Submit</button>
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell">
|
||||
<button id="submit" type="submit"
|
||||
class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored">
|
||||
Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--8-col">
|
||||
</div>
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell mdl-cell--8-col">
|
||||
{% if form.instance.memberships %}
|
||||
<table class="mdl-data-table mdl-js-data-table">
|
||||
<thead>
|
||||
@ -205,5 +257,7 @@
|
||||
{% endif %}
|
||||
<a class="mdl-button mdl-js-button mdl-button--flat mdl-js-ripple-effect mdl-button--colored" href="{% url 'new_membership' member_id=member.id %}">Add membership</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
@ -104,6 +104,10 @@ class TestMemberSignIn(TestCase):
|
||||
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertIsInstance(response, JsonResponse)
|
||||
data = json.loads(response.content.decode())
|
||||
results = data['results']
|
||||
self.assertTrue('banned' in results)
|
||||
self.assertTrue('suspended' in results)
|
||||
self.assertTrue(visit)
|
||||
|
||||
def test_post_no_member(self):
|
||||
@ -137,3 +141,5 @@ class TestMemberSignIn(TestCase):
|
||||
data = json.loads(data_string)
|
||||
|
||||
self.assertTrue(len(data), 3)
|
||||
self.assertTrue('banned' in data[0]['member'])
|
||||
self.assertTrue('suspended' in data[0]['member'])
|
||||
|
@ -83,16 +83,17 @@ class MemberSignIn(View):
|
||||
def post(self, request):
|
||||
member = get_object_or_404(Member, id=request.POST.get('id'))
|
||||
visit = signin_member(member, request.POST.get('purpose'))
|
||||
data = json.dumps(dict(results=dict(id=member.id, created_at=visit.created_at.isoformat())))
|
||||
data = dict(results=dict(id=member.id, first_name=member.first_name, last_name=member.last_name,
|
||||
suspended=member.suspended, banned=member.banned,
|
||||
created_at=visit.created_at.isoformat()))
|
||||
|
||||
return JsonResponse(data=data, safe=False, status=201)
|
||||
|
||||
def get(self, request):
|
||||
visits = get_signed_in_members().prefetch_related()
|
||||
serializer = VisitSerializer(visits, many=True)
|
||||
results_json = JSONRenderer().render(serializer.data)
|
||||
|
||||
return JsonResponse(data=results_json.decode(), safe=False, status=200)
|
||||
return JsonResponse(data=serializer.data, safe=False, status=200)
|
||||
|
||||
|
||||
@method_decorator(login_required, name='dispatch')
|
||||
|
@ -10,6 +10,8 @@ services:
|
||||
- "62260:62260"
|
||||
volumes:
|
||||
- ./bikeshop_project:/code:rw
|
||||
volumes_from:
|
||||
- webpack
|
||||
redis:
|
||||
restart: always
|
||||
db:
|
||||
@ -22,5 +24,7 @@ services:
|
||||
ports:
|
||||
- "3000:3000"
|
||||
restart: always
|
||||
volumes_from:
|
||||
- workstand
|
||||
volumes:
|
||||
- /code/node_modules
|
||||
- /code/vendor
|
||||
- ./bikeshop_project:/code:rw
|
||||
|
@ -23,7 +23,7 @@ services:
|
||||
image: bcbc/workstand:production
|
||||
env_file:
|
||||
- workstand.env
|
||||
command: gunicorn --log-file=- -b 0.0.0.0:8000 bikeshop.wsgi:application
|
||||
command: 'bash -c "PYTHONUNBUFFERED=TRUE python manage.py migrate --no-input && python manage.py collectstatic --no-input && python manage.py rebuild_index --noinput && gunicorn --log-file=- -b 0.0.0.0:8000 bikeshop.wsgi:application"'
|
||||
environment:
|
||||
- DJANGO_SETTINGS_MODULE=bikeshop.settings.production
|
||||
volumes:
|
||||
|
@ -1,2 +1,3 @@
|
||||
-r base.txt
|
||||
-r testing.txt
|
||||
gunicorn==19.4.5
|
||||
|
Loading…
x
Reference in New Issue
Block a user