diff --git a/apiserver/apiserver/api/utils.py b/apiserver/apiserver/api/utils.py index 9235609..87ee843 100644 --- a/apiserver/apiserver/api/utils.py +++ b/apiserver/apiserver/api/utils.py @@ -192,6 +192,42 @@ def process_image_upload(upload, crop): return small, medium, large +GARDEN_MEDIUM_SIZE = 500 + +def process_garden_image(upload): + try: + pic = Image.open(upload) + except OSError: + raise serializers.ValidationError(dict(non_field_errors='Invalid image file.')) + + logging.debug('Detected format: %s', pic.format) + + if pic.format == 'PNG': + ext = '.png' + elif pic.format == 'JPEG': + ext = '.jpg' + else: + raise serializers.ValidationError(dict(non_field_errors='Image must be a jpg or png.')) + + pic = ImageOps.exif_transpose(pic) + + draw = ImageDraw.Draw(pic) + + timestamp = now_alberta_tz().strftime('%a %b %-d, %Y %-I:%M %p') + + font = ImageFont.truetype('DejaVuSans.ttf', 60) + draw.text((10, 10), timestamp, (0,0,0), font=font) + + large = 'garden-large' + ext + pic.save(STATIC_FOLDER + large) + + medium = 'garden-medium' + ext + pic.thumbnail([GARDEN_MEDIUM_SIZE, GARDEN_MEDIUM_SIZE], Image.ANTIALIAS) + pic.save(STATIC_FOLDER + medium) + + return medium, large + + CARD_TEMPLATE_FILE = 'misc/member_card_template.jpg' CARD_PHOTO_SIZE = 425 CARD_PHOTO_MARGIN_TOP = 75 diff --git a/apiserver/apiserver/api/views.py b/apiserver/apiserver/api/views.py index 200037f..ba1f42e 100644 --- a/apiserver/apiserver/api/views.py +++ b/apiserver/apiserver/api/views.py @@ -857,7 +857,6 @@ class StatsViewSet(viewsets.ViewSet, List): month_total=month_total, )) - @action(detail=False, methods=['post']) def autoscan(self, request): if 'autoscan' not in request.data: @@ -866,6 +865,18 @@ class StatsViewSet(viewsets.ViewSet, List): cache.set('autoscan', request.data['autoscan']) return Response(200) + @action(detail=False, methods=['post']) + def garden(self, request): + if 'photo' not in request.data: + raise exceptions.ValidationError(dict(photo='This field is required.')) + + photo = request.data['photo'] + medium, large = utils.process_garden_image(photo) + + logging.debug('Wrote garden images to %s and %s', medium, large) + + return Response(200) + class MemberCountViewSet(Base, List): pagination_class = None diff --git a/webclient/src/App.js b/webclient/src/App.js index 38ab13e..61befa9 100644 --- a/webclient/src/App.js +++ b/webclient/src/App.js @@ -27,6 +27,7 @@ import { Subscribe } from './PayPal.js'; import { PasswordReset, ConfirmReset } from './PasswordReset.js'; import { NotFound, PleaseLogin } from './Misc.js'; import { Debug } from './Debug.js'; +import { Garden } from './Garden.js'; import { Footer } from './Footer.js'; const APP_VERSION = 3; // TODO: automate this @@ -263,6 +264,10 @@ function App() { + + + + {user && user.member.set_details ? diff --git a/webclient/src/Garden.js b/webclient/src/Garden.js new file mode 100644 index 0000000..fa48799 --- /dev/null +++ b/webclient/src/Garden.js @@ -0,0 +1,15 @@ +import React, { useState, useEffect, useReducer, useContext } from 'react'; +import { BrowserRouter as Router, Switch, Route, Link, useParams, useHistory } from 'react-router-dom'; +import { Button, Container, Checkbox, Dimmer, Divider, Dropdown, Form, Grid, Header, Icon, Image, Menu, Message, Segment, Table } from 'semantic-ui-react'; +import { apiUrl, statusColor, BasicTable, staticUrl, requester } from './utils.js'; +import { NotFound } from './Misc.js'; + +export function Garden(props) { + return ( + +
Protogarden
+ + +
+ ); +}; diff --git a/webclient/src/Home.js b/webclient/src/Home.js index 515f5c5..c627d89 100644 --- a/webclient/src/Home.js +++ b/webclient/src/Home.js @@ -351,6 +351,12 @@ export function Home(props) { +

Protogarden:

+ + + + + } diff --git a/webclient/src/Sign.js b/webclient/src/Sign.js index f4f51f5..0242977 100644 --- a/webclient/src/Sign.js +++ b/webclient/src/Sign.js @@ -36,16 +36,18 @@ export function SignForm(props) {

Send a message to the sign:

- + + - - Submit - + + Submit + + {success &&
Success!
} );