diff --git a/apiserver/apiserver/api/models.py b/apiserver/apiserver/api/models.py
index de4d4eb..6b08b4b 100644
--- a/apiserver/apiserver/api/models.py
+++ b/apiserver/apiserver/api/models.py
@@ -7,6 +7,7 @@ from . import old_models
class Member(models.Model):
user = models.OneToOneField(User, blank=True, null=True, on_delete=models.SET_NULL)
+ old_email = models.CharField(max_length=254, blank=True, null=True)
photo_large = models.CharField(max_length=64, blank=True, null=True)
photo_medium = models.CharField(max_length=64, blank=True, null=True)
photo_small = models.CharField(max_length=64, blank=True, null=True)
diff --git a/apiserver/apiserver/api/serializers.py b/apiserver/apiserver/api/serializers.py
index 735577d..2a6106c 100644
--- a/apiserver/apiserver/api/serializers.py
+++ b/apiserver/apiserver/api/serializers.py
@@ -55,7 +55,7 @@ class OtherMemberSerializer(serializers.ModelSerializer):
class UserEmailField(serializers.ModelField):
def to_representation(self, obj):
- return obj.user.email
+ return getattr(obj.user, 'email', obj.old_email)
def to_internal_value(self, data):
return serializers.EmailField().run_validation(data)
@@ -88,8 +88,9 @@ class MemberSerializer(serializers.ModelSerializer):
]
def update(self, instance, validated_data):
- instance.user.email = validated_data.get('email', instance.user.email)
- instance.user.save()
+ if instance.user:
+ instance.user.email = validated_data.get('email', instance.user.email)
+ instance.user.save()
photo = validated_data.get('photo', None)
if photo:
@@ -107,6 +108,7 @@ class AdminMemberSerializer(MemberSerializer):
fields = '__all__'
read_only_fields = [
'id',
+ 'status',
'photo_large',
'photo_medium',
'photo_small',
@@ -127,7 +129,7 @@ class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
- fields = ['id', 'username', 'member', 'transactions', 'cards', 'training']
+ fields = ['id', 'username', 'member', 'transactions', 'cards', 'training', 'is_staff']
depth = 1
diff --git a/apiserver/import_old_portal.py b/apiserver/import_old_portal.py
index ff6b917..28b54c7 100755
--- a/apiserver/import_old_portal.py
+++ b/apiserver/import_old_portal.py
@@ -7,6 +7,7 @@ from apiserver.api.serializers import process_image
MEMBER_FIELDS = [
'id',
+ # email -> old_email
'first_name',
'last_name',
'preferred_name',
@@ -91,6 +92,7 @@ for o in old:
if o.city and o.province:
new['city'] = '{}, {}'.format(o.city, o.province)
+ new['old_email'] = o.email
new['is_minor'] = o.minor
small, medium, large = None, None, None
diff --git a/webclient/src/Account.js b/webclient/src/Account.js
index 01552f9..e069aad 100644
--- a/webclient/src/Account.js
+++ b/webclient/src/Account.js
@@ -65,11 +65,7 @@ function ChangePasswordForm(props) {
export function AccountForm(props) {
const member = props.user.member;
- const [input, setInput] = useState({
- ...member,
- birthdate: member.birthdate || '',
- set_details: true
- });
+ const [input, setInput] = useState({ ...member, set_details: true });
const [error, setError] = useState({});
const [loading, setLoading] = useState(false);
const history = useHistory();
@@ -97,7 +93,7 @@ export function AccountForm(props) {
const makeProps = (name) => ({
name: name,
onChange: handleChange,
- value: input[name],
+ value: input[name] || '',
error: error[name],
});
@@ -111,14 +107,14 @@ export function AccountForm(props) {
{...makeProps('first_name')}
/>
Are you under 18 years old?
@@ -159,7 +155,7 @@ export function AccountForm(props) {
{...makeProps('birthdate')}
/>}
{input.is_minor && }
diff --git a/webclient/src/Admin.js b/webclient/src/Admin.js
new file mode 100644
index 0000000..32cd943
--- /dev/null
+++ b/webclient/src/Admin.js
@@ -0,0 +1,206 @@
+import React, { useState, useEffect } from 'react';
+import { BrowserRouter as Router, Switch, Route, Link, useParams, useHistory } from 'react-router-dom';
+import './light.css';
+import { Container, Checkbox, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react';
+import { BasicTable, staticUrl, requester } from './utils.js';
+
+export function AdminMemberForm(props) {
+ const [input, setInput] = useState(false);
+ const [error, setError] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [success, setSuccess] = useState(false);
+ const { id } = useParams();
+
+ const handleValues = (e, v) => setInput({ ...input, [v.name]: v.value });
+ const handleUpload = (e, v) => setInput({ ...input, [v.name]: e.target.files[0] });
+ const handleChange = (e) => handleValues(e, e.currentTarget);
+ const handleCheck = (e, v) => setInput({ ...input, [v.name]: v.checked });
+
+ useEffect(() => {
+ requester('/members/'+id+'/', 'GET', props.token)
+ .then(res => {
+ setInput(res);
+ })
+ .catch(err => {
+ console.log(err);
+ setError(true);
+ });
+ }, []);
+
+ const handleSubmit = (e) => {
+ setLoading(true);
+ setSuccess(false);
+ requester('/members/' + id + '/', 'PATCH', props.token, input)
+ .then(res => {
+ setLoading(false);
+ setSuccess(true);
+ setError(false);
+ })
+ .catch(err => {
+ setLoading(false);
+ console.log(err);
+ setError(err.data);
+ });
+ };
+
+ const makeProps = (name) => ({
+ name: name,
+ onChange: handleChange,
+ value: input[name] || '',
+ error: error[name],
+ });
+
+ return (
+
+ {!error ?
+ input ?
+
+
+
+
+
+
+
+
+
+
+
+ Is the member a director?
+
+
+
+
+ Is the member an instructor?
+
+
+
+ {success &&
Success!
}
+
+ Submit
+
+
+ :
+
Loading...
+ :
+
Error loading member
+ }
+
+ );
+};
+
+export function AdminMemberInfo(props) {
+ const [member, setMember] = useState(false);
+ const [error, setError] = useState(false);
+ const { id } = useParams();
+
+ useEffect(() => {
+ requester('/members/'+id+'/', 'GET', props.token)
+ .then(res => {
+ setMember(res);
+ })
+ .catch(err => {
+ console.log(err);
+ setError(true);
+ });
+ }, []);
+
+ return (
+
+ {!error ?
+ member ?
+
+
+
+
+
+
+ Name:
+ {member.first_name} {member.last_name}
+
+
+ Status:
+ {member.status}
+
+
+
+ Email:
+ {member.email}
+
+
+ Phone:
+ {member.phone}
+
+
+
+ Address:
+ {member.street_address}
+
+
+ City:
+ {member.city}
+
+
+ Postal:
+ {member.postal_code}
+
+
+
+ Minor:
+ {member.is_minor ? 'Yes' : 'No'}
+
+ {member.is_minor &&
+ Birthdate:
+ {member.birthdate}
+ }
+ {member.is_minor &&
+ Guardian:
+ {member.guardian_name}
+ }
+
+
+ Emergency Contact Name:
+ {member.emergency_contact_name}
+
+
+ Emergency Contact Phone:
+ {member.emergency_contact_phone}
+
+
+
+
+ :
+
Loading...
+ :
+
Error loading member
+ }
+
+ );
+};
diff --git a/webclient/src/App.js b/webclient/src/App.js
index 357fdd1..bc1cb27 100644
--- a/webclient/src/App.js
+++ b/webclient/src/App.js
@@ -30,7 +30,6 @@ function App() {
useEffect(() => {
requester('/user/', 'GET', token)
.then(res => {
- console.log(res);
setUserCache(res);
})
.catch(err => {
@@ -42,7 +41,6 @@ function App() {
function logout() {
setTokenCache('');
setUserCache(false);
- window.location = '/';
}
return (
@@ -163,7 +161,7 @@ function App() {
-
+
diff --git a/webclient/src/Classes.js b/webclient/src/Classes.js
index 450d487..2da3be1 100644
--- a/webclient/src/Classes.js
+++ b/webclient/src/Classes.js
@@ -56,7 +56,6 @@ export function Classes(props) {
useEffect(() => {
requester('/sessions/', 'GET', token)
.then(res => {
- console.log(res);
setClasses(res.results);
})
.catch(err => {
@@ -96,7 +95,6 @@ export function ClassDetail(props) {
useEffect(() => {
requester('/sessions/'+id+'/', 'GET', token)
.then(res => {
- console.log(res);
setClass(res);
})
.catch(err => {
diff --git a/webclient/src/Courses.js b/webclient/src/Courses.js
index 0fb0c33..8437d1f 100644
--- a/webclient/src/Courses.js
+++ b/webclient/src/Courses.js
@@ -13,7 +13,6 @@ export function Courses(props) {
useEffect(() => {
requester('/courses/', 'GET', token)
.then(res => {
- console.log(res);
setCourses(res.results);
})
.catch(err => {
@@ -64,7 +63,6 @@ export function CourseDetail(props) {
useEffect(() => {
requester('/courses/'+id+'/', 'GET', token)
.then(res => {
- console.log(res);
setCourse(res);
})
.catch(err => {
diff --git a/webclient/src/Home.js b/webclient/src/Home.js
index f903afe..ad4c07e 100644
--- a/webclient/src/Home.js
+++ b/webclient/src/Home.js
@@ -46,6 +46,11 @@ function MemberInfo(props) {
+ {!member.photo_medium &&
+ Please set a member photo!
+ Visit the account settings page to set one.
+ }
+
diff --git a/webclient/src/LoginSignup.js b/webclient/src/LoginSignup.js
index e2f96c5..22dc661 100644
--- a/webclient/src/LoginSignup.js
+++ b/webclient/src/LoginSignup.js
@@ -16,7 +16,6 @@ export function LoginForm(props) {
setLoading(true);
requester('/rest-auth/login/', 'POST', '', input)
.then(res => {
- console.log(res);
setError({});
props.setTokenCache(res.key);
})
@@ -71,7 +70,6 @@ export function SignupForm(props) {
input.username = genUsername();
requester('/registration/', 'POST', '', input)
.then(res => {
- console.log(res);
setError({});
props.setTokenCache(res.key);
})
diff --git a/webclient/src/Members.js b/webclient/src/Members.js
index 669f1f1..9a09ce1 100644
--- a/webclient/src/Members.js
+++ b/webclient/src/Members.js
@@ -3,8 +3,9 @@ import { BrowserRouter as Router, Switch, Route, Link, useParams } from 'react-r
import './light.css';
import { Button, Container, Divider, Dropdown, Form, Grid, Header, Icon, Image, Input, Item, Menu, Message, Segment, Table } from 'semantic-ui-react';
import moment from 'moment';
-import { BasicTable, staticUrl, requester } from './utils.js';
+import { isAdmin, BasicTable, staticUrl, requester } from './utils.js';
import { NotFound, PleaseLogin } from './Misc.js';
+import { AdminMemberInfo, AdminMemberForm } from './Admin.js';
export function Members(props) {
const [members, setMembers] = useState(false);
@@ -75,7 +76,7 @@ export function Members(props) {
export function MemberDetail(props) {
const [member, setMember] = useState(false);
const [error, setError] = useState(false);
- const { token } = props;
+ const { token, user } = props;
const { id } = useParams();
useEffect(() => {
@@ -96,20 +97,35 @@ export function MemberDetail(props) {
{member.preferred_name} {member.last_name}
-
-
-
-
-
- Status:
- {member.status || 'Unknown'}
-
-
- Joined:
- {member.current_start_date || 'Unknown'}
-
-
-
+
+
+
+
+
+
+ {isAdmin(user) ?
+
+ :
+
+
+
+ Status:
+ {member.status || 'Unknown'}
+
+
+ Joined:
+ {member.current_start_date || 'Unknown'}
+
+
+
+ }
+
+
+
+ {isAdmin(user) && }
+
+
+
:
Loading...
diff --git a/webclient/src/Misc.js b/webclient/src/Misc.js
index 316cf34..bffaa39 100644
--- a/webclient/src/Misc.js
+++ b/webclient/src/Misc.js
@@ -7,9 +7,11 @@ export function PleaseLogin() {
return (
- You must login before you can do that!
+ You must login before you can do that!
Visit our login page, then try again.
+
+
);
};
@@ -18,7 +20,7 @@ export function NotFound() {
return (
- The page you requested can't be found!
+ The page you requested can't be found!
Visit our home page if you are lost.
diff --git a/webclient/src/utils.js b/webclient/src/utils.js
index d9149a6..12fc40f 100644
--- a/webclient/src/utils.js
+++ b/webclient/src/utils.js
@@ -1,6 +1,8 @@
import React, { useState, useEffect } from 'react';
import { Table } from 'semantic-ui-react';
+export const isAdmin = (user) => user.is_staff || user.member.is_director;
+
export const siteUrl = window.location.protocol + '//' + window.location.hostname;
export const apiUrl = window.location.protocol + '//api.' + window.location.hostname;
export const staticUrl = window.location.protocol + '//static.' + window.location.hostname;
@@ -23,7 +25,7 @@ export const requester = (route, method, token, data) => {
} else if (['POST', 'PUT', 'PATCH'].includes(method)) {
const formData = new FormData();
Object.keys(data).forEach(key =>
- formData.append(key, data[key])
+ formData.append(key, data[key] === null ? '' : data[key])
);
options = {