diff --git a/bikeshop_project/assets/js/components/SignIn.jsx b/bikeshop_project/assets/js/components/SignIn.jsx
index 69fc7b4..3fef0c2 100644
--- a/bikeshop_project/assets/js/components/SignIn.jsx
+++ b/bikeshop_project/assets/js/components/SignIn.jsx
@@ -1,13 +1,16 @@
+import ContentAdd from 'material-ui/svg-icons/content/add';
import fetch from 'isomorphic-fetch';
+import FloatingActionButton from 'material-ui/FloatingActionButton';
import moment from 'moment';
import { polyFill } from 'es6-promise';
-import RaisedButton from 'material-ui/RaisedButton';
import React from 'react';
+import RaisedButton from 'material-ui/RaisedButton';
import Member from './Member';
import Purpose from './Purpose';
import SignedInList from './SignedInList';
+
export default class SignIn extends React.Component {
constructor(props) {
super(props);
@@ -132,6 +135,7 @@ export default class SignIn extends React.Component {
searchText={this.state.searchText}
/>
+
-
@@ -147,6 +150,9 @@ export default class SignIn extends React.Component {
+
+
+
);
diff --git a/bikeshop_project/assets/js/members/components/MemberTable/index.jsx b/bikeshop_project/assets/js/members/components/MemberTable/index.jsx
new file mode 100644
index 0000000..44eb40c
--- /dev/null
+++ b/bikeshop_project/assets/js/members/components/MemberTable/index.jsx
@@ -0,0 +1,155 @@
+import React from 'react';
+import { polyFill } from 'es6-promise';
+import fetch from 'isomorphic-fetch';
+import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table';
+import FlatButton from 'material-ui/FlatButton';
+import { Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarTitle } from 'material-ui/Toolbar';
+import TextField from 'material-ui/TextField';
+import RaisedButton from 'material-ui/RaisedButton';
+
+function checkStatus(response) {
+ if (response.status >= 200 && response.status < 300) {
+ return response;
+ }
+ const error = new Error(response.statusText);
+ error.response = response;
+ throw error;
+}
+
+function parseJSON(response) {
+ return response.json();
+}
+
+export default class MemberTable extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ members: [],
+ searchText: '',
+ filteredMembers: [],
+ error: undefined,
+ };
+
+ this.handleUpdate = this.handleUpdate.bind(this);
+ this.handleSearch = this.handleSearch.bind(this);
+ this.clearSearch = this.clearSearch.bind(this);
+ }
+
+ componentDidMount() {
+ fetch('/api/v1/members/')
+ .then(checkStatus)
+ .then(parseJSON)
+ .then((data) => {
+ this.setState({ members: data });
+ console.log('request succeeded with JSON response', data);
+ })
+ .catch((error) => {
+ console.log('request failed', error);
+ });
+ }
+
+ handleUpdate(event, value) {
+ this.setState({ ...this.state, searchText: value });
+ }
+
+ clearSearch() {
+ this.setState({
+ ...this.state,
+ searchText: '',
+ });
+ }
+
+ handleSearch() {
+ const value = this.state.searchText.trim();
+ const self = this;
+
+ fetch(`/members/search/${value}/`)
+ .then((response) => {
+ if (response.status === 200) {
+ return response.json();
+ }
+ throw new Error('Bad response from server');
+ })
+ .then((data) => {
+ if (data.results.length > 0) {
+ self.setState({
+ ...this.state,
+ error: '',
+ filteredMembers: this.state.members.filter((member) => {
+ const ids = data.results.map(m => m.id);
+ console.log(ids);
+
+ if (ids.indexOf(member.id) !== -1) {
+ return member;
+ }
+ }),
+ });
+ } else {
+ self.setState({ ...this.state, error: 'Member not found.' });
+ }
+ });
+ }
+
+ render() {
+ const memberRows = this.state.members.map(member => (
+
+ {member.first_name}
+ {member.last_name}
+ {member.email}
+
+
+ ));
+
+ const filteredMemberRows = this.state.filteredMembers.map(member => (
+
+ {member.first_name}
+ {member.last_name}
+ {member.email}
+
+
+ ));
+
+
+ return (
+
+
+
Members
+
+
+
+
+
+
+
+
+
+
+ First name
+ Last Name
+ Email
+
+
+
+
+ {filteredMemberRows.length ?
+ filteredMemberRows : undefined
+ }
+ {memberRows.length && !this.state.searchText ?
+ memberRows :
+
+ {'Members loading.'}
+
+ }
+
+
+
+
+ );
+ }
+}
diff --git a/bikeshop_project/assets/js/members/index.jsx b/bikeshop_project/assets/js/members/index.jsx
new file mode 100644
index 0000000..c67abc5
--- /dev/null
+++ b/bikeshop_project/assets/js/members/index.jsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import injectTapEventPlugin from 'react-tap-event-plugin';
+import getMuiTheme from 'material-ui/styles/getMuiTheme';
+import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
+import MemberTable from './components/MemberTable';
+
+// Needed for onTouchTap
+// http://stackoverflow.com/a/34015469/988941
+injectTapEventPlugin();
+
+
+const App = () => (
+
+
+
Members
+
+
+
+);
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/bikeshop_project/bikeshop/urls.py b/bikeshop_project/bikeshop/urls.py
index f45ed72..26264fe 100644
--- a/bikeshop_project/bikeshop/urls.py
+++ b/bikeshop_project/bikeshop/urls.py
@@ -18,16 +18,30 @@ from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.auth.views import login, logout_then_login
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
+from rest_framework import routers
+from rest_framework_jwt.views import obtain_jwt_token
+import registration
from core import urls as core_urls
from registration import urls as member_urls
+routeLists = [
+ registration.urls.apiRoutes,
+]
+
+router = routers.DefaultRouter()
+for routeList in routeLists:
+ for route in routeList:
+ router.register(route[0], route[1])
+
urlpatterns = [
url(r'^', include(core_urls)),
url(r'^login/', login, {'template_name': 'login.html'}, name='login'),
url(r'^logout/', logout_then_login, name='logout'),
url(r'^members/', include(member_urls)),
url(r'^admin/', admin.site.urls),
+ url(r'^api/v1/', include(router.urls)),
+ url(r'^api/v1/token-auth/', obtain_jwt_token),
]
if getattr(settings, 'DEBUG'):
diff --git a/bikeshop_project/core/templates/dashboard.html b/bikeshop_project/core/templates/dashboard.html
index 996eb1e..aa938bd 100644
--- a/bikeshop_project/core/templates/dashboard.html
+++ b/bikeshop_project/core/templates/dashboard.html
@@ -53,7 +53,9 @@
{% endblock %}
+
{% block content %}
- {% render_bundle 'main' %}
+ {% render_bundle 'signin' %}
{% endblock %}
+{% render_bundle 'webpack' %}
diff --git a/bikeshop_project/registration/serializers.py b/bikeshop_project/registration/serializers.py
new file mode 100644
index 0000000..f060691
--- /dev/null
+++ b/bikeshop_project/registration/serializers.py
@@ -0,0 +1,13 @@
+from rest_framework import serializers
+from rest_framework.serializers import ModelSerializer
+
+from .models import Member
+
+
+class MemberSerializer(ModelSerializer):
+ first_name = serializers.CharField(allow_blank=True, required=False)
+ last_name = serializers.CharField(allow_blank=True, required=False)
+
+ class Meta:
+ model = Member
+ fields = ('first_name', 'last_name', 'email', 'id')
\ No newline at end of file
diff --git a/bikeshop_project/registration/templates/members.html b/bikeshop_project/registration/templates/members.html
index 26c105a..5743a1e 100644
--- a/bikeshop_project/registration/templates/members.html
+++ b/bikeshop_project/registration/templates/members.html
@@ -1,22 +1,7 @@
{% extends 'dashboard.html' %}
+{% load render_bundle from webpack_loader %}
{% block content %}
-
-
- {% for member in members %}
- -
-
- person
- {{ member.full_name }}
-
- {{ member.email }}
-
-
-
- edit
-
-
- {% endfor %}
-
-
+
+ {% render_bundle 'members' %}
{% endblock %}
\ No newline at end of file
diff --git a/bikeshop_project/registration/urls.py b/bikeshop_project/registration/urls.py
index 181c90b..e861aa9 100644
--- a/bikeshop_project/registration/urls.py
+++ b/bikeshop_project/registration/urls.py
@@ -1,6 +1,11 @@
from django.conf.urls import url
-from .views import MemberFormView, MemberSearchView, MemberSignIn, Members
+from .views import MemberFormView, MemberSearchView, MemberSignIn, Members, MemberViewSet
+
+apiRoutes = (
+ (r'members', MemberViewSet),
+)
+
urlpatterns = [
url(r'^new/$', MemberFormView.as_view(), name='member_new'),
url(r'^search/(?P[\w@\.\+]+)/$', MemberSearchView.as_view(), name='member_search'),
diff --git a/bikeshop_project/registration/views.py b/bikeshop_project/registration/views.py
index 7630be5..a0cd442 100644
--- a/bikeshop_project/registration/views.py
+++ b/bikeshop_project/registration/views.py
@@ -9,12 +9,13 @@ from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView, View
from haystack.query import SearchQuerySet
-from rest_framework import serializers
+from rest_framework import viewsets
from rest_framework.renderers import JSONRenderer
from rest_framework.serializers import ModelSerializer
from core.models import Visit
from registration.utils import signin_member, get_signed_in_members
+from .serializers import MemberSerializer
from .forms import MemberForm
from .models import Member
@@ -64,15 +65,6 @@ class MemberSearchView(View):
return HttpResponse(data, content_type='application/json')
-class MemberSerializer(ModelSerializer):
- first_name = serializers.CharField(allow_blank=True, required=False)
- last_name = serializers.CharField(allow_blank=True, required=False)
-
- class Meta:
- model = Member
- fields = ('first_name', 'last_name', 'email', 'id')
-
-
class VisitSerializer(ModelSerializer):
member = MemberSerializer()
@@ -109,3 +101,8 @@ class Members(TemplateView):
def get(self, request):
members = Member.objects.all()
return self.render_to_response(dict(members=members))
+
+
+class MemberViewSet(viewsets.ModelViewSet):
+ queryset = Member.objects.all()
+ serializer_class = MemberSerializer
diff --git a/bikeshop_project/webpack.config.js b/bikeshop_project/webpack.config.js
index d5e5bca..b2594cb 100644
--- a/bikeshop_project/webpack.config.js
+++ b/bikeshop_project/webpack.config.js
@@ -1,48 +1,50 @@
-const path = require("path");
+const path = require('path');
const webpack = require('webpack');
const BundleTracker = require('webpack-bundle-tracker');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
-const autoprefixer = require('autoprefixer');
+const autoprefixer = require('autoprefixer');
module.exports = {
- context: __dirname,
- devtool: 'inline-source-map',
- entry: './assets/js/index', // entry point of our app. assets/js/index.js should require other js modules and dependencies it needs
+ context: __dirname,
+ devtool: 'inline-source-map',
+ entry: {
+ signin: './assets/js/index',
+ members: './assets/js/members/index',
+ },
+ output: {
+ path: path.resolve('./assets/bundles/'),
+ filename: '[name]-[hash].js',
+ },
- output: {
- path: path.resolve('./assets/bundles/'),
- filename: "[name]-[hash].js"
- },
+ plugins: [
+ new BundleTracker({ filename: './webpack-stats.json' }),
+ new ExtractTextPlugin('react-toolbox.css', { allChunks: true }),
+ new webpack.NoErrorsPlugin(),
+ ],
- plugins: [
- new BundleTracker({filename: './webpack-stats.json'}),
- new ExtractTextPlugin('react-toolbox.css', { allChunks: true }),
- new webpack.NoErrorsPlugin()
+ module: {
+ loaders: [
+ {
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader',
+ query: {
+ presets: ['es2015', 'stage-0', 'react'],
+ },
+ },
+ {
+ test: /(\.scss|\.css)$/,
+ loader: ExtractTextPlugin.extract('style', 'css?sourceMap&modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss!sass?sourceMap!toolbox'),
+ },
],
-
- module: {
- loaders: [
- {
- test: /\.jsx?$/,
- exclude: /node_modules/,
- loader: 'babel-loader',
- query: {
- presets: ['es2015', 'stage-0', 'react']
- }
- },
- {
- test: /(\.scss|\.css)$/,
- loader: ExtractTextPlugin.extract('style', 'css?sourceMap&modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss!sass?sourceMap!toolbox')
- }
- ]
- },
- resolve: {
- modulesDirectories: [
- 'node_modules',
- 'bower_components',
- path.resolve(__dirname, './node_modules')
- ],
- extensions: ['', '.js', '.jsx', '.scss']
- },
- postcss: [autoprefixer]
-}
\ No newline at end of file
+ },
+ resolve: {
+ modulesDirectories: [
+ 'node_modules',
+ 'bower_components',
+ path.resolve(__dirname, './node_modules'),
+ ],
+ extensions: ['', '.js', '.jsx', '.scss'],
+ },
+ postcss: [autoprefixer],
+};
diff --git a/bikeshop_project/webpack.dev.config.js b/bikeshop_project/webpack.dev.config.js
index 4826c0b..0f4ada6 100644
--- a/bikeshop_project/webpack.dev.config.js
+++ b/bikeshop_project/webpack.dev.config.js
@@ -6,11 +6,14 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin');
var config = require('./webpack.base.config.js')
// Use webpack dev server
-config.entry = [
- 'webpack-dev-server/client?http://webpack.docker:3000',
- 'webpack/hot/only-dev-server',
- './assets/js/index'
-]
+config.entry = {
+ webpack: [
+ 'webpack-dev-server/client?http://webpack.docker:3000',
+ 'webpack/hot/only-dev-server',
+ ],
+ signin: './assets/js/index',
+ members: './assets/js/members/index',
+}
// override django's STATIC_URL for webpack bundles
config.output.publicPath = 'http://webpack.docker:3000/assets/bundles/'
diff --git a/requirements/base.txt b/requirements/base.txt
index e871554..791a167 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -11,3 +11,4 @@ djangorestframework
django-webpack-loader
requests
PyYAML
+djangorestframework-jwt==1.9.0
\ No newline at end of file