mirror of
https://github.com/fspc/workstand.git
synced 2025-02-23 01:13:22 -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:
|
before_install:
|
||||||
- docker build -t bcbc/workstand:production .
|
- docker build -t bcbc/workstand:production .
|
||||||
after_success:
|
after_success:
|
||||||
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD";
|
|
||||||
- export REPO=bcbc/workstand
|
after_success:
|
||||||
- export TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "production"; else echo $TRAVIS_BRANCH ; fi`
|
- if [ "$TRAVIS_BRANCH" == "master" ]; then
|
||||||
- docker build -f Dockerfile -t $REPO:$COMMIT .
|
docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD";
|
||||||
- docker tag $REPO:$COMMIT $REPO:$TAG
|
docker push bcbc/workstand:production;
|
||||||
- docker tag $REPO:$COMMIT $REPO:travis-$TRAVIS_BUILD_NUMBER
|
fi
|
||||||
- docker push $REPO
|
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
|
RUN mkdir requirements
|
||||||
ADD bikeshop_project /code
|
ADD bikeshop_project /code
|
||||||
ADD requirements/base.txt /code/requirements/base.txt
|
ADD requirements/base.txt /code/requirements/base.txt
|
||||||
|
ADD requirements/testing.txt /code/requirements/testing.txt
|
||||||
ADD requirements/production.txt /code/requirements/production.txt
|
ADD requirements/production.txt /code/requirements/production.txt
|
||||||
RUN pip install -r requirements/production.txt
|
RUN pip install -r requirements/production.txt
|
||||||
RUN npm cache clean
|
RUN npm cache clean
|
||||||
@ -16,5 +17,6 @@ RUN bower install --allow-root
|
|||||||
ADD ./bikeshop_project/package.json package.json
|
ADD ./bikeshop_project/package.json package.json
|
||||||
RUN npm install --unsafe-perm
|
RUN npm install --unsafe-perm
|
||||||
RUN npm run build-production
|
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
|
EXPOSE 8000
|
||||||
|
@ -4,6 +4,7 @@ WORKDIR /code
|
|||||||
ADD ./bikeshop_project/package.json package.json
|
ADD ./bikeshop_project/package.json package.json
|
||||||
RUN npm install
|
RUN npm install
|
||||||
RUN npm install -g bower
|
RUN npm install -g bower
|
||||||
|
ADD ./bikeshop_project/.bowerrc .bowerrc
|
||||||
ADD ./bikeshop_project/bower.json bower.json
|
ADD ./bikeshop_project/bower.json bower.json
|
||||||
RUN bower install --allow-root
|
RUN bower install --allow-root
|
||||||
EXPOSE 3000:3000
|
EXPOSE 3000:3000
|
||||||
|
@ -29,11 +29,14 @@ export default class SignIn extends React.Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
fetch('/members/signin/')
|
fetch('/members/signin/')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then((data) => {
|
.then((visits) => {
|
||||||
const visits = JSON.parse(data);
|
|
||||||
this.setState({ signedIn: visits.map(visit => ({
|
this.setState({ signedIn: visits.map(visit => ({
|
||||||
id: visit.member.id,
|
id: visit.member.id,
|
||||||
|
banned: visit.member.banned,
|
||||||
|
suspended: visit.member.suspended,
|
||||||
purpose: visit.purpose,
|
purpose: visit.purpose,
|
||||||
|
first_name: visit.member.first_name,
|
||||||
|
last_name: visit.member.last_name,
|
||||||
text: `${visit.member.first_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}>`,
|
value: `${visit.member.first_name} ${visit.member.last_name} <${visit.member.email}>`,
|
||||||
at: moment(visit.created_at),
|
at: moment(visit.created_at),
|
||||||
@ -68,14 +71,11 @@ export default class SignIn extends React.Component {
|
|||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((parsedData) => {
|
||||||
const signedIn = this.state.signedIn;
|
|
||||||
const parsedData = JSON.parse(data);
|
|
||||||
|
|
||||||
signedIn.push({ ...member, purpose, at: moment(parsedData.results.created_at) });
|
|
||||||
this.setState({
|
this.setState({
|
||||||
...this.state,
|
...this.state,
|
||||||
signedIn,
|
signedIn: [...this.state.signedIn,
|
||||||
|
{ ...parsedData.results, purpose, at: moment(parsedData.results.created_at) }],
|
||||||
signOn: { purpose: 'FIX', member: undefined },
|
signOn: { purpose: 'FIX', member: undefined },
|
||||||
searchText: '',
|
searchText: '',
|
||||||
members: [],
|
members: [],
|
||||||
|
@ -5,6 +5,33 @@ import React, { PropTypes } from 'react';
|
|||||||
import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table';
|
import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table';
|
||||||
import moment from 'moment';
|
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 {
|
export default class SignedInList extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
members: PropTypes.arrayOf(PropTypes.shape({
|
members: PropTypes.arrayOf(PropTypes.shape({
|
||||||
@ -40,14 +67,25 @@ export default class SignedInList extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const memberRows = this.sortMembers(this.props.members)
|
const memberRows = this.sortMembers(this.props.members)
|
||||||
.map(member => (
|
.map((member) => {
|
||||||
<TableRow selectable={false} key={member.id}>
|
let memberStatus = <span style={styles.good} className="good">good</span>;
|
||||||
<TableRowColumn>{member.text}</TableRowColumn>
|
|
||||||
<TableRowColumn>{member.purpose}</TableRowColumn>
|
if (member.banned) {
|
||||||
<TableRowColumn>{member.at.fromNow()}</TableRowColumn>
|
memberStatus = <span style={styles.banned} className="banned">banned</span>;
|
||||||
<TableRowColumn><a href={`/members/edit/${member.id}/`}>Profile</a></TableRowColumn>
|
} else if (member.suspended) {
|
||||||
</TableRow>
|
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 (
|
return (
|
||||||
<div className="mdl-cell mdl-cell--12-col">
|
<div className="mdl-cell mdl-cell--12-col">
|
||||||
@ -57,11 +95,12 @@ export default class SignedInList extends React.Component {
|
|||||||
</FloatingActionButton>
|
</FloatingActionButton>
|
||||||
<Table selectable={false}>
|
<Table selectable={false}>
|
||||||
<TableHeader adjustForCheckbox={false} displaySelectAll={false}>
|
<TableHeader adjustForCheckbox={false} displaySelectAll={false}>
|
||||||
<TableRow>
|
<TableRow key="blah">
|
||||||
<TableHeaderColumn>Name</TableHeaderColumn>
|
<TableHeaderColumn>Name</TableHeaderColumn>
|
||||||
<TableHeaderColumn>Purpose</TableHeaderColumn>
|
<TableHeaderColumn>Purpose</TableHeaderColumn>
|
||||||
<TableHeaderColumn>Signed-in At</TableHeaderColumn>
|
<TableHeaderColumn>Signed-in At</TableHeaderColumn>
|
||||||
<TableHeaderColumn/>
|
<TableHeaderColumn>Member Status</TableHeaderColumn>
|
||||||
|
<TableHeaderColumn />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody displayRowCheckbox={false}>
|
<TableBody displayRowCheckbox={false}>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import os
|
import sys
|
||||||
from .base import * # noqa
|
from .base import * # noqa
|
||||||
|
|
||||||
|
|
||||||
@ -47,3 +47,7 @@ WEBPACK_LOADER = {
|
|||||||
'IGNORE': ['.+\.hot-update.js', '.+\.map']
|
'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 django.utils import timezone
|
||||||
from registration.models import Member
|
from registration.models import Member
|
||||||
|
|
||||||
@ -12,7 +12,8 @@ class MemberForm(ModelForm):
|
|||||||
|
|
||||||
exclude = ('waiver',)
|
exclude = ('waiver',)
|
||||||
fields = ['email', 'email_consent', 'first_name', 'last_name', 'preferred_name', 'date_of_birth',
|
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 = {
|
widgets = {
|
||||||
'email': EmailInput(attrs={'class': 'mdl-textfield__input'}),
|
'email': EmailInput(attrs={'class': 'mdl-textfield__input'}),
|
||||||
'email_consent': CheckboxInput(attrs={'class': 'mdl-checkbox__input'}),
|
'email_consent': CheckboxInput(attrs={'class': 'mdl-checkbox__input'}),
|
||||||
@ -28,6 +29,9 @@ class MemberForm(ModelForm):
|
|||||||
'country': TextInput(attrs={'class': 'mdl-textfield__input'}),
|
'country': TextInput(attrs={'class': 'mdl-textfield__input'}),
|
||||||
'post_code': 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]'}),
|
'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 = {
|
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)
|
post_code = models.CharField(max_length=20, null=True, blank=False)
|
||||||
waiver = models.DateTimeField(null=True, blank=True)
|
waiver = models.DateTimeField(null=True, blank=True)
|
||||||
is_active = models.BooleanField(default=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):
|
def get_full_name(self):
|
||||||
# The user is identified by their email address
|
# The user is identified by their email address
|
||||||
|
@ -10,4 +10,4 @@ class MemberSerializer(ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Member
|
model = Member
|
||||||
fields = ('first_name', 'last_name', 'email', 'id')
|
fields = ('first_name', 'last_name', 'email', 'id', 'banned', 'suspended')
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% 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>
|
<script>
|
||||||
dateOfBirthInput = document.getElementById('{{ form.date_of_birth.id_for_label }}');
|
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>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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>
|
<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">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
@ -66,118 +39,197 @@
|
|||||||
<span class="error">{{ form.errors }}</span>
|
<span class="error">{{ form.errors }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.email.errors %}is-invalid{% endif %}">
|
<div class="mdl-grid">
|
||||||
{{ form.email }}
|
<div class="mdl-cell mdl-cell--12-col">
|
||||||
<label class="mdl-textfield__label" for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
|
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.email.errors %}is-invalid{% endif %}">
|
||||||
{% if form.email.errors %}
|
{{ form.email }}
|
||||||
<span class="mdl-textfield__error">{{ form.email.errors }}</span>
|
<label class="mdl-textfield__label" for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
|
||||||
{% else %}
|
{% if form.email.errors %}
|
||||||
<span class="mdl-textfield__error">Invalid email.</span>
|
<span class="mdl-textfield__error">{{ form.email.errors }}</span>
|
||||||
{% endif %}
|
{% 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>
|
||||||
<div>
|
|
||||||
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="{{ form.email_consent.id_for_label }}">
|
<div class="mdl-grid">
|
||||||
{{ form.email_consent }}
|
<div class="mdl-cell">
|
||||||
<span class="mdl-checkbox__label">{{ form.email_consent.label }}</span>
|
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.first_name.errors %}is-invalid{% endif %}">
|
||||||
</label>
|
{{ 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>
|
||||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.first_name.errors %}is-invalid{% endif %}">
|
|
||||||
{{ form.first_name }}
|
<div class="mdl-grid">
|
||||||
<label class="mdl-textfield__label" for="{{ form.first_name.id_for_label }}">{{ form.first_name.label }}</label>
|
<div class="mdl-cell">
|
||||||
{% if form.first_name.errors %}
|
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.preferred_name.errors %}is-invalid{% endif %}">
|
||||||
<span class="mdl-textfield__error">{{ form.first_name.errors }}</span>
|
{{ form.preferred_name }}
|
||||||
{% else %}
|
<label class="mdl-textfield__label" for="{{ form.preferred_name.id_for_label }}">{{ form.preferred_name.label }}</label>
|
||||||
<span class="mdl-textfield__error">Name too long.</span>
|
{% if form.preferred_name.errors %}
|
||||||
{% endif %}
|
<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>
|
||||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.last_name.errors %}is-invalid{% endif %}">
|
|
||||||
{{ form.last_name }}
|
<div class="mdl-grid">
|
||||||
<label class="mdl-textfield__label" for="{{ form.last_name.id_for_label }}">{{ form.last_name.label }}</label>
|
<div class="mdl-cell">
|
||||||
{% if form.last_name.errors %}
|
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.date_of_birth.errors %}is-invalid{% endif %}">
|
||||||
<span class="mdl-textfield__error">{{ form.last_name.errors }}</span>
|
{{ form.date_of_birth }}
|
||||||
{% else %}
|
<label class="mdl-textfield__label" for="{{ form.date_of_birth.id_for_label }}">{{ form.date_of_birth.label }} (e.g. 1991-08-25)</label>
|
||||||
<span class="mdl-textfield__error">Name too long.</span>
|
{% if form.date_of_birth.errors %}
|
||||||
{% endif %}
|
<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>
|
||||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.preferred_name.errors %}is-invalid{% endif %}">
|
|
||||||
{{ form.preferred_name }}
|
<div class="mdl-grid">
|
||||||
<label class="mdl-textfield__label" for="{{ form.preferred_name.id_for_label }}">{{ form.preferred_name.label }}</label>
|
<div class="mdl-cell">
|
||||||
{% if form.preferred_name.errors %}
|
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.post_code.errors %}is-invalid{% endif %}">
|
||||||
<span class="mdl-textfield__error">{{ form.preferred_name.errors }}</span>
|
{{ form.post_code }}
|
||||||
{% else %}
|
<label class="mdl-textfield__label" for="{{ form.post_code.id_for_label }}">{{ form.post_code.label }}</label>
|
||||||
<span class="mdl-textfield__error">Name too long.</span>
|
{% if form.post_code.errors %}
|
||||||
{% endif %}
|
<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>
|
||||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.date_of_birth.errors %}is-invalid{% endif %}">
|
<div class="mdl-grid">
|
||||||
{{ form.date_of_birth }}
|
<div class="mdl-cell">
|
||||||
<label class="mdl-textfield__label" for="{{ form.date_of_birth.id_for_label }}">{{ form.date_of_birth.label }} (e.g. 1991-08-25)</label>
|
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.city.errors %}is-invalid{% endif %}">
|
||||||
{% if form.date_of_birth.errors %}
|
{{ form.city }}
|
||||||
<span class="mdl-textfield__error">{{ form.date_of_birth.errors }}</span>
|
<label class="mdl-textfield__label" for="{{ form.city.id_for_label }}">{{ form.city.label }}</label>
|
||||||
{% else %}
|
{% if form.city.errors %}
|
||||||
<span class="mdl-textfield__error">Incorrect date.</span>
|
<span class="mdl-textfield__error">{{ form.city.errors }}</span>
|
||||||
{% endif %}
|
{% 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>
|
||||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.guardian_name.errors %}is-invalid{% endif %}">
|
<div class="mdl-grid">
|
||||||
{{ form.guardian_name }}
|
<div class="mdl-cell">
|
||||||
<label class="mdl-textfield__label" for="{{ form.guardian_name.id_for_label }}">{{ form.guardian_name.label }}</label>
|
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.country.errors %}is-invalid{% endif %}">
|
||||||
{% if form.guardian_name.errors %}
|
{{ form.country }}
|
||||||
<span class="mdl-textfield__error">{{ form.guardian_name.errors }}</span>
|
<label class="mdl-textfield__label" for="{{ form.country.id_for_label }}">{{ form.country.label }}</label>
|
||||||
{% else %}
|
{% if form.country.errors %}
|
||||||
<span class="mdl-textfield__error">Name too long.</span>
|
<span class="mdl-textfield__error">{{ form.country.errors }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.phone.errors %}is-invalid{% endif %}">
|
|
||||||
{{ form.phone }}
|
<div class="mdl-grid">
|
||||||
<label class="mdl-textfield__label" for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
|
<div class="mdl-cell mdl-cell--12-col">
|
||||||
{% if form.phone.errors %}
|
<h2>Member Info</h2>
|
||||||
<span class="mdl-textfield__error">{{ form.phone.errors }}</span>
|
<h3>Notes</h3>
|
||||||
{% else %}
|
</div>
|
||||||
<span class="mdl-textfield__error">Digits only.</span>
|
<div class="mdl-cell mdl-cell--12-col">
|
||||||
{% endif %}
|
<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>
|
||||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.post_code.errors %}is-invalid{% endif %}">
|
|
||||||
{{ form.post_code }}
|
<div class="mdl-grid">
|
||||||
<label class="mdl-textfield__label" for="{{ form.post_code.id_for_label }}">{{ form.post_code.label }}</label>
|
<div class="mdl-cell mdl-cell--3-col">
|
||||||
{% if form.post_code.errors %}
|
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" for="{{ form.suspended.id_for_label }}">
|
||||||
<span class="mdl-textfield__error">{{ form.post_code.errors }}</span>
|
{{ form.suspended }}
|
||||||
{% else %}
|
<span class="mdl-checkbox__label">{{ form.suspended.label }}</span>
|
||||||
<span class="mdl-textfield__error">Format: A0A 0A0</span>
|
</label>
|
||||||
{% endif %}
|
</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>
|
||||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label {% if form.street.errors %}is-invalid{% endif %}">
|
<div class="mdl-grid">
|
||||||
{{ form.street }}
|
<div class="mdl-cell">
|
||||||
<label class="mdl-textfield__label" for="{{ form.street.id_for_label }}">{{ form.street.label }}</label>
|
<button id="submit" type="submit"
|
||||||
{% if form.street.errors %}
|
class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored">
|
||||||
<span class="mdl-textfield__error">{{ form.street.errors }}</span>
|
Save</button>
|
||||||
{% endif %}
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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 %}
|
{% if form.instance.memberships %}
|
||||||
<table class="mdl-data-table mdl-js-data-table">
|
<table class="mdl-data-table mdl-js-data-table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -205,5 +257,7 @@
|
|||||||
{% endif %}
|
{% 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>
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -104,6 +104,10 @@ class TestMemberSignIn(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
self.assertIsInstance(response, JsonResponse)
|
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)
|
self.assertTrue(visit)
|
||||||
|
|
||||||
def test_post_no_member(self):
|
def test_post_no_member(self):
|
||||||
@ -137,3 +141,5 @@ class TestMemberSignIn(TestCase):
|
|||||||
data = json.loads(data_string)
|
data = json.loads(data_string)
|
||||||
|
|
||||||
self.assertTrue(len(data), 3)
|
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):
|
def post(self, request):
|
||||||
member = get_object_or_404(Member, id=request.POST.get('id'))
|
member = get_object_or_404(Member, id=request.POST.get('id'))
|
||||||
visit = signin_member(member, request.POST.get('purpose'))
|
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)
|
return JsonResponse(data=data, safe=False, status=201)
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
visits = get_signed_in_members().prefetch_related()
|
visits = get_signed_in_members().prefetch_related()
|
||||||
serializer = VisitSerializer(visits, many=True)
|
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')
|
@method_decorator(login_required, name='dispatch')
|
||||||
|
@ -10,6 +10,8 @@ services:
|
|||||||
- "62260:62260"
|
- "62260:62260"
|
||||||
volumes:
|
volumes:
|
||||||
- ./bikeshop_project:/code:rw
|
- ./bikeshop_project:/code:rw
|
||||||
|
volumes_from:
|
||||||
|
- webpack
|
||||||
redis:
|
redis:
|
||||||
restart: always
|
restart: always
|
||||||
db:
|
db:
|
||||||
@ -22,5 +24,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
restart: always
|
restart: always
|
||||||
volumes_from:
|
volumes:
|
||||||
- workstand
|
- /code/node_modules
|
||||||
|
- /code/vendor
|
||||||
|
- ./bikeshop_project:/code:rw
|
||||||
|
@ -23,7 +23,7 @@ services:
|
|||||||
image: bcbc/workstand:production
|
image: bcbc/workstand:production
|
||||||
env_file:
|
env_file:
|
||||||
- workstand.env
|
- 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:
|
environment:
|
||||||
- DJANGO_SETTINGS_MODULE=bikeshop.settings.production
|
- DJANGO_SETTINGS_MODULE=bikeshop.settings.production
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
-r base.txt
|
-r base.txt
|
||||||
|
-r testing.txt
|
||||||
gunicorn==19.4.5
|
gunicorn==19.4.5
|
||||||
|
Loading…
x
Reference in New Issue
Block a user