Browse Source

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.
feature/python-error-tracking
Drew Larson 7 years ago
committed by GitHub
parent
commit
49f37ed492
  1. 16
      .travis.yml
  2. 4
      Dockerfile
  3. 1
      Dockerfile-webpack
  4. 16
      bikeshop_project/assets/js/components/SignIn.jsx
  5. 59
      bikeshop_project/assets/js/components/SignedInList.jsx
  6. 6
      bikeshop_project/bikeshop/settings/production.py
  7. 8
      bikeshop_project/registration/forms.py
  8. 30
      bikeshop_project/registration/migrations/0003_auto_20170215_0308.py
  9. 3
      bikeshop_project/registration/models.py
  10. 2
      bikeshop_project/registration/serializers.py
  11. 316
      bikeshop_project/registration/templates/edit_member_form.html
  12. 6
      bikeshop_project/registration/tests/test_views.py
  13. 7
      bikeshop_project/registration/views.py
  14. 8
      docker-compose.dev.yml
  15. 2
      docker-compose.prod.yml
  16. 1
      requirements/production.txt

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=

4
Dockerfile

@ -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

1
Dockerfile-webpack

@ -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

16
bikeshop_project/assets/js/components/SignIn.jsx

@ -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: [],

59
bikeshop_project/assets/js/components/SignedInList.jsx

@ -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}>

6
bikeshop_project/bikeshop/settings/production.py

@ -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

8
bikeshop_project/registration/forms.py

@ -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 = {

30
bikeshop_project/registration/migrations/0003_auto_20170215_0308.py

@ -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),
),
]

3
bikeshop_project/registration/models.py

@ -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

2
bikeshop_project/registration/serializers.py

@ -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')

316
bikeshop_project/registration/templates/edit_member_form.html

@ -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>
<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 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 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 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 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 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.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.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.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.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">
<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.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">
<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.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 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.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 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.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 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.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 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>
<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 %}

6
bikeshop_project/registration/tests/test_views.py

@ -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'])

7
bikeshop_project/registration/views.py

@ -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')

8
docker-compose.dev.yml

@ -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

2
docker-compose.prod.yml

@ -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
requirements/production.txt

@ -1,2 +1,3 @@
-r base.txt
-r testing.txt
gunicorn==19.4.5

Loading…
Cancel
Save