Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export all survey tables to CSV #426

Merged
merged 7 commits into from
Jan 24, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/icp/apps/beekeepers/js/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand All @@ -179,6 +180,7 @@ export function signUp(form) {
dispatch(setAuthState({
username: '',
authError: error.response.data.errors[0],
isStaff: false,
userId: null,
userSurvey: null,
message: '',
Expand All @@ -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,
}));
Expand All @@ -208,6 +211,7 @@ export function login(form) {
dispatch(setAuthState({
username: '',
message: '',
isStaff: false,
authError: error.response.data.errors[0],
userId: null,
userSurvey: null,
Expand All @@ -222,6 +226,7 @@ export function logout() {
username: '',
authError: '',
message: '',
isStaff: false,
userId: null,
userSurvey: null,
}));
Expand All @@ -238,13 +243,15 @@ 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,
}));
}).catch((error) => {
dispatch(setAuthState({
message: '',
authError: error.response.data.errors[0],
isStaff: false,
username: '',
userSurvey: null,
userId: null,
Expand All @@ -258,6 +265,7 @@ export function createUserSurvey(form) {
auth: {
username,
userId,
isStaff,
},
} = getState();

Expand All @@ -268,6 +276,7 @@ export function createUserSurvey(form) {
message: '',
authError: '',
userId,
isStaff,
userSurvey: data.beekeeper_survey,
}));
dispatch(closeUserSurveyModal());
Expand All @@ -279,6 +288,7 @@ export function createUserSurvey(form) {
username,
message: '',
authError: errorMsg,
isStaff,
userId,
userSurvey: null,
}));
Expand Down
33 changes: 29 additions & 4 deletions src/icp/apps/beekeepers/js/src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
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 }) => {
const Header = ({ dispatch, username, isStaff }) => {
const exportDataButton = isStaff
? (
<li>
<a
rel="noopener noreferrer"
className="navbar__link"
href="/beekeepers/export/"
fungjj92 marked this conversation as resolved.
Show resolved Hide resolved
>
Export Survey Data
</a>
</li>
) : null;
const authButtons = username
? (
<li className="navbar__item navbar__item--user">
Expand All @@ -16,7 +32,14 @@ const Header = ({ dispatch, username }) => {
{username}
</button>
{/* Hidden Log Out button for screen readers */}
{/* Hidden buttons for screen readers */}
<a
rel="noopener noreferrer"
className="sr-only navbar__link"
href="/beekeepers/export/"
>
Export Survey Data
</a>
<button
type="button"
className="sr-only"
Expand All @@ -25,6 +48,7 @@ const Header = ({ dispatch, username }) => {
Log Out
</button>
<ul className="navbar__options">
{exportDataButton}
<li>
<button
type="button"
Expand Down Expand Up @@ -92,6 +116,7 @@ function mapStateToProps(state) {
Header.propTypes = {
dispatch: func.isRequired,
username: string.isRequired,
isStaff: bool.isRequired,
};

export default withRouter(connect(mapStateToProps)(Header));
1 change: 1 addition & 0 deletions src/icp/apps/beekeepers/js/src/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const initialAuthState = {
userId: null,
authError: '',
message: '',
isStaff: false,
userSurvey: null,
};

Expand Down
5 changes: 5 additions & 0 deletions src/icp/apps/beekeepers/sass/06_components/_navbar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
}
}

&__link {
color: $color-grey-2;
text-decoration: none;
}

&__button {
padding: 0;
color: $color-grey-2;
Expand Down
1 change: 1 addition & 0 deletions src/icp/apps/beekeepers/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<apiary_id>[0-9]+)/survey/$',
views.create_survey, name='survey-create'),
Expand Down
68 changes: 66 additions & 2 deletions src/icp/apps/beekeepers/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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']

fungjj92 marked this conversation as resolved.
Show resolved Hide resolved

@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()
rajadain marked this conversation as resolved.
Show resolved Hide resolved
cur.copy_expert(full_query, tempfile)
tempfile.seek(0)
zf.writestr(filename, tempfile.read())
zf.close()

resp = HttpResponse(
stream.getvalue(),
content_type='application/x-zip-compressed'
fungjj92 marked this conversation as resolved.
Show resolved Hide resolved
)
resp['Content-Disposition'] = 'attachment; filename={}.zip'.format(zip_dir)
connection.close()
return resp


@decorators.api_view(['POST'])
Expand Down
2 changes: 2 additions & 0 deletions src/icp/apps/user/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
}
Expand Down