diff --git a/src/icp/apps/beekeepers/js/src/actions.js b/src/icp/apps/beekeepers/js/src/actions.js
index addfc86e5..d0395cd63 100644
--- a/src/icp/apps/beekeepers/js/src/actions.js
+++ b/src/icp/apps/beekeepers/js/src/actions.js
@@ -168,6 +168,7 @@ export function signUp(form) {
dispatch(setAuthState({
username: '',
authError: '',
+ isStaff: false,
userId: null,
userSurvey: null,
message: 'Please click the validation link in your email and then log in.',
@@ -179,6 +180,7 @@ export function signUp(form) {
dispatch(setAuthState({
username: '',
authError: error.response.data.errors[0],
+ isStaff: false,
userId: null,
userSurvey: null,
message: '',
@@ -199,6 +201,7 @@ export function login(form) {
username: data.username || '',
authError: '',
message: '',
+ isStaff: data.is_staff,
userId: data.id || null,
userSurvey: data.beekeeper_survey,
}));
@@ -208,6 +211,7 @@ export function login(form) {
dispatch(setAuthState({
username: '',
message: '',
+ isStaff: false,
authError: error.response.data.errors[0],
userId: null,
userSurvey: null,
@@ -222,6 +226,7 @@ export function logout() {
username: '',
authError: '',
message: '',
+ isStaff: false,
userId: null,
userSurvey: null,
}));
@@ -238,6 +243,7 @@ export function sendAuthLink(form, endpoint) {
username: '',
userId: null,
message: 'Check your email to reset your password or activate your account',
+ isStaff: false,
authError: '',
userSurvey: null,
}));
@@ -245,6 +251,7 @@ export function sendAuthLink(form, endpoint) {
dispatch(setAuthState({
message: '',
authError: error.response.data.errors[0],
+ isStaff: false,
username: '',
userSurvey: null,
userId: null,
@@ -258,6 +265,7 @@ export function createUserSurvey(form) {
auth: {
username,
userId,
+ isStaff,
},
} = getState();
@@ -268,6 +276,7 @@ export function createUserSurvey(form) {
message: '',
authError: '',
userId,
+ isStaff,
userSurvey: data.beekeeper_survey,
}));
dispatch(closeUserSurveyModal());
@@ -279,6 +288,7 @@ export function createUserSurvey(form) {
username,
message: '',
authError: errorMsg,
+ isStaff,
userId,
userSurvey: null,
}));
diff --git a/src/icp/apps/beekeepers/js/src/components/Header.jsx b/src/icp/apps/beekeepers/js/src/components/Header.jsx
index 4e1e639f4..c92274f02 100644
--- a/src/icp/apps/beekeepers/js/src/components/Header.jsx
+++ b/src/icp/apps/beekeepers/js/src/components/Header.jsx
@@ -1,11 +1,45 @@
import React from 'react';
import { connect } from 'react-redux';
import { NavLink, withRouter } from 'react-router-dom';
-import { func, string } from 'prop-types';
+import { func, string, bool } from 'prop-types';
-import { openParticipateModal, openLoginModal, logout } from '../actions';
+import {
+ openParticipateModal,
+ openLoginModal,
+ logout,
+} from '../actions';
+
+const Header = ({ dispatch, username, isStaff }) => {
+ const makeExportDataButton = srOnly => (
+
+ Export Survey Data
+
+ );
+
+ const exportDataButton = isStaff
+ ? (
+
+ {makeExportDataButton()}
+
+ ) : null;
+
+ const exportDataButtonForScreenReader = isStaff
+ ? makeExportDataButton(true) : null;
+
+ const makeLogOutButton = srOnly => (
+
+ );
-const Header = ({ dispatch, username }) => {
const authButtons = username
? (
@@ -16,23 +50,13 @@ const Header = ({ dispatch, username }) => {
{username}
▾
- {/* Hidden Log Out button for screen readers */}
-
+ {/* Hidden buttons for screen readers */}
+ {exportDataButtonForScreenReader}
+ {makeLogOutButton(true)}
+ {exportDataButton}
-
-
+ {makeLogOutButton()}
@@ -59,6 +83,7 @@ const Header = ({ dispatch, username }) => {
>
);
+
return (
@@ -92,6 +117,7 @@ function mapStateToProps(state) {
Header.propTypes = {
dispatch: func.isRequired,
username: string.isRequired,
+ isStaff: bool.isRequired,
};
export default withRouter(connect(mapStateToProps)(Header));
diff --git a/src/icp/apps/beekeepers/js/src/reducers.js b/src/icp/apps/beekeepers/js/src/reducers.js
index 11692e4b8..68fd81a06 100644
--- a/src/icp/apps/beekeepers/js/src/reducers.js
+++ b/src/icp/apps/beekeepers/js/src/reducers.js
@@ -89,6 +89,7 @@ const initialAuthState = {
userId: null,
authError: '',
message: '',
+ isStaff: false,
userSurvey: null,
};
diff --git a/src/icp/apps/beekeepers/sass/06_components/_navbar.scss b/src/icp/apps/beekeepers/sass/06_components/_navbar.scss
index 963ba067e..fad9b5d4f 100644
--- a/src/icp/apps/beekeepers/sass/06_components/_navbar.scss
+++ b/src/icp/apps/beekeepers/sass/06_components/_navbar.scss
@@ -47,6 +47,11 @@
}
}
+ &__link {
+ color: $color-grey-2;
+ text-decoration: none;
+ }
+
&__button {
padding: 0;
color: $color-grey-2;
diff --git a/src/icp/apps/beekeepers/urls.py b/src/icp/apps/beekeepers/urls.py
index ea0493f32..41ed6be98 100644
--- a/src/icp/apps/beekeepers/urls.py
+++ b/src/icp/apps/beekeepers/urls.py
@@ -16,6 +16,7 @@
urlpatterns = patterns(
'',
+ url(r'export/$', views.export_survey_tables, name='export'),
url(r'fetch/$', views.fetch_data, name='fetch_data'),
url(r'^apiary/(?P
[0-9]+)/survey/$',
views.create_survey, name='survey-create'),
diff --git a/src/icp/apps/beekeepers/views.py b/src/icp/apps/beekeepers/views.py
index 02e3c674c..ad24f6be8 100644
--- a/src/icp/apps/beekeepers/views.py
+++ b/src/icp/apps/beekeepers/views.py
@@ -2,14 +2,19 @@
from __future__ import division
import os
+import psycopg2
+from cStringIO import StringIO
+from zipfile import ZipFile
+from tempfile import SpooledTemporaryFile
-from django.http import Http404
+from django.conf import settings
+from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.timezone import now
from rest_framework import decorators, viewsets
from rest_framework.response import Response
-from rest_framework.permissions import AllowAny, IsAuthenticated
+from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser
from rest_framework.status import HTTP_204_NO_CONTENT
from models import Apiary, Survey, UserSurvey
@@ -22,6 +27,65 @@
DATA_BUCKET = os.environ['AWS_BEEKEEPERS_DATA_BUCKET']
+PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
+DB = settings.DATABASES['default']
+
+
+@decorators.api_view(['GET'])
+@decorators.permission_classes((IsAdminUser, ))
+def export_survey_tables(request):
+ """Export a zip file of CSVs of all types of beekeepers surveys."""
+
+ # prepare queries to the database
+ db_options = 'dbname={} user={} host={} password={}'.format(
+ DB['NAME'], DB['USER'], DB['HOST'], DB['PASSWORD']
+ )
+ connection = psycopg2.connect(db_options)
+ cur = connection.cursor()
+ tables = dict(
+ novembersurvey=None,
+ aprilsurvey=None,
+ monthlysurvey=None,
+ usersurvey="""
+ SELECT auth_user.username, auth_user.email,
+ beekeepers_usersurvey.*
+ FROM beekeepers_usersurvey
+ INNER JOIN auth_user ON beekeepers_usersurvey.user_id=auth_user.id
+ """,
+ survey="""
+ SELECT beekeepers_survey.*, beekeepers_apiary.lat,
+ beekeepers_apiary.lng
+ FROM beekeepers_survey
+ INNER JOIN beekeepers_apiary
+ ON beekeepers_survey.apiary_id=beekeepers_apiary.id
+ """,
+ )
+
+ # the zipped CSVs are written in memory
+ date_stamp = now().strftime('%Y-%m-%d_%H-%M-%S')
+ zip_dir = 'beekeepers_exports_{}'.format(date_stamp)
+ stream = StringIO()
+
+ with ZipFile(stream, 'w') as zf:
+ for table, query in tables.iteritems():
+ if query is None:
+ query = 'SELECT * FROM beekeepers_{}'.format(table)
+
+ filename = '{}/{}_{}.csv'.format(zip_dir, table, date_stamp)
+ full_query = 'COPY ({0}) TO STDOUT WITH CSV HEADER'.format(query)
+ tempfile = SpooledTemporaryFile()
+ cur.copy_expert(full_query, tempfile)
+ tempfile.seek(0)
+ zf.writestr(filename, tempfile.read())
+ zf.close()
+
+ resp = HttpResponse(
+ stream.getvalue(),
+ content_type='application/zip'
+ )
+ resp['Content-Disposition'] = 'attachment; filename={}.zip'.format(zip_dir)
+ connection.close()
+ return resp
@decorators.api_view(['POST'])
diff --git a/src/icp/apps/user/views.py b/src/icp/apps/user/views.py
index a8866bdf0..baf4cd719 100644
--- a/src/icp/apps/user/views.py
+++ b/src/icp/apps/user/views.py
@@ -48,6 +48,7 @@ def login(request):
'username': user.username,
'guest': False,
'id': user.id,
+ 'is_staff': user.is_staff,
'beekeeper_survey': user_survey
}
else:
@@ -79,6 +80,7 @@ def login(request):
'result': 'success',
'username': user.username,
'guest': False,
+ 'is_staff': user.is_staff,
'id': user.id,
'beekeeper_survey': user_survey
}