commit 134a3fa598da8c99a412cbee713af09178d6c96d Author: christian Date: Sun Oct 27 02:18:42 2019 +0100 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ffee9e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.sqlite3 +*.swp +*.pyc +*.swo +env/ +*/migrations/ diff --git a/bloomhunt/__init__.py b/bloomhunt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bloomhunt/settings.py b/bloomhunt/settings.py new file mode 100644 index 0000000..4b5603c --- /dev/null +++ b/bloomhunt/settings.py @@ -0,0 +1,126 @@ +""" +Django settings for bloomhunt project. + +Generated by 'django-admin startproject' using Django 1.11. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.11/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '8$19!k4+%bg^r0v#04+ksm2ny(4*dp!v8dxnbbz)sj275(_(^&' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'mobile', + 'map', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'bloomhunt.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'bloomhunt.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.11/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +STATIC_URL = '/static/' + +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "static"), +] diff --git a/bloomhunt/urls.py b/bloomhunt/urls.py new file mode 100644 index 0000000..113d477 --- /dev/null +++ b/bloomhunt/urls.py @@ -0,0 +1,24 @@ +"""bloomhunt URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.11/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url +from django.contrib import admin +from django.urls import include, path + +urlpatterns = [ + url(r'^admin/', admin.site.urls), + url(r'^mobile/', include('mobile.urls')), + url(r'^map/', include('map.urls')) +] diff --git a/bloomhunt/wsgi.py b/bloomhunt/wsgi.py new file mode 100644 index 0000000..e6bffcd --- /dev/null +++ b/bloomhunt/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for bloomhunt project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bloomhunt.settings") + +application = get_wsgi_application() diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..1bb3229 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bloomhunt.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/map/__init__.py b/map/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/map/admin.py b/map/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/map/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/map/apps.py b/map/apps.py new file mode 100644 index 0000000..fb66c9b --- /dev/null +++ b/map/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class MapConfig(AppConfig): + name = 'map' diff --git a/map/models.py b/map/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/map/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/map/static/map/index.css b/map/static/map/index.css new file mode 100644 index 0000000..1c1d335 --- /dev/null +++ b/map/static/map/index.css @@ -0,0 +1,43 @@ +#map1 { + height: 43% +} + +#map2 { + margin-top: 10px; + height: 43%; + margin-bottom: 7px; +} + +.slider { + -webkit-appearance: none; + width: 100%; + height: 15px; + border-radius: 5px; + background: #d3d3d3; + outline: none; + opacity: 0.7; + -webkit-transition: .2s; + transition: opacity .2s; +} + +.slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 25px; + height: 25px; + border-radius: 50%; + background: #4CAF50; + cursor: pointer; +} + +.slider::-moz-range-thumb { + width: 25px; + height: 25px; + border-radius: 50%; + background: #4CAF50; + cursor: pointer; +} + +.no-bloom-icon { + height: 30px; +} diff --git a/map/static/map/map.js b/map/static/map/map.js new file mode 100644 index 0000000..e93ea04 --- /dev/null +++ b/map/static/map/map.js @@ -0,0 +1,139 @@ +var p_date = document.getElementById("p_date"); + +var map1 = L.map('map1').setView([49.4093524, 8.6931736], 15); +var map2 = L.map('map2').setView([49.4093524, 8.6931736], 15); + +map1.sync(map2); +map2.sync(map1); + +var tile = "https://c.tile.openstreetmap.org/{z}/{x}/{y}.png " + +var layer1 = L.tileLayer(tile, { + attribution: '© OpenStreetMap contributors' +}).addTo(map1); + +var layer2 = L.tileLayer(tile, { + attribution: '© OpenStreetMap contributors' +}).addTo(map2); + +//var layer1 = L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', { +// attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox', +// maxZoom: 18, +// id: 'mapbox.streets', +// accessToken: 'pk.eyJ1IjoicG9zdGZsYXYiLCJhIjoiY2syN29xZHVpMnlzcDNtbXZkN2tlYWdhaSJ9.Th5tmsyOlPKoogGrOQrMNA' +//}); + +//var layer2 = L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', { +// attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox', +// maxZoom: 18, +// id: 'mapbox.hiking', +// accessToken: 'pk.eyJ1IjoicG9zdGZsYXYiLCJhIjoiY2syN29xZHVpMnlzcDNtbXZkN2tlYWdhaSJ9.Th5tmsyOlPKoogGrOQrMNA' +//}); + +layer1.addTo(map1); +layer2.addTo(map2); + +function mk_not_bloom_icon(label) { + return new L.DivIcon({ + className: 'no-bloom-icon', + html: '' + + '' + label + '', + iconAnchor: [15, 30] + }); +} + +function mk_bloom_icon(label) { + return new L.DivIcon({ + className: 'bloom-icon', + html: '' + + '' + label + '', + iconAnchor: [15, 30] + }); + +} + +//var not_bloom_icon = L.icon({ +// iconUrl: 'tree_not_blooming.png', +// +// iconSize: [40, 40], // size of the icon +// shadowSize: [0, 0], // size of the shadow +// iconAnchor: [20, 40], // point of the icon which will correspond to marker's location +// shadowAnchor: [4, 62], // the same for the shadow +// popupAnchor: [-3, -76] // point from which the popup should open relative to the iconAnchor +//}); + +//var bloom_icon = L.icon({ +// iconUrl: 'tree_blooming.png', +// +// iconSize: [40, 40], // size of the icon +// shadowSize: [0, 0], // size of the shadow +// iconAnchor: [20, 40], // point of the icon which will correspond to marker's location +// shadowAnchor: [4, 62], // the same for the shadow +// popupAnchor: [-3, -76] // point from which the popup should open relative to the iconAnchor +//}); + + +class Tree { + constructor(name, bloom_start, xcoord, ycoord) { + this.name = name; + this.bloom_start = bloom_start; + this.marker = L.marker([xcoord, ycoord], + {icon: mk_not_bloom_icon(this.name)}); + this.blooming = false; + } + update(dayno) { + if ((dayno < this.bloom_start || dayno >= this.bloom_start + 14) + && this.blooming) { + this.marker.setIcon(mk_not_bloom_icon(this.name)); + this.blooming = false; + } else if (dayno >= this.bloom_start + && dayno < this.bloom_start + 14 + && !this.blooming) { + this.marker.setIcon(mk_bloom_icon(this.name)); + this.blooming = true; + } + } +} + +var tree = new Tree("Fritz", 55, 49.4093524, 8.6931736); +var tree2 = new Tree("Hans", 80, 45.02, 8.6931736); +var tree3 = new Tree("Hans", 80, 49.02, 8.69317); + +var a_tree = new Tree("Fritz", 120, 49.4093524, 8.6931736); +var a_tree2 = new Tree("Hans", 100, 45.02, 8.6931736); +var a_tree3 = new Tree("Hans", 100, 49.02, 8.69317); + +function updateMap(value) { + tree.update(value); + tree2.update(value); + tree3.update(value); + a_tree.update(value); + a_tree2.update(value); + a_tree3.update(value); + p_date.innerHTML = "Date: " + dateFromDay(value); +} + +function dateFromDay(dayno) { + var date = new Date(2019, 0); // initialize a date in `year-01-01` + var date2 = new Date(date.setDate(dayno)); + return date2.getMonth() + 1 + "-" + date2.getDate(); +} + +var cities1 = L.layerGroup([tree.marker, tree2.marker, tree3.marker]); +var cities2 = L.layerGroup([a_tree.marker, a_tree2.marker, a_tree3.marker]); +cities1.addTo(map1); +cities2.addTo(map2); + +//var control1 = L.control.layers({'2019': cities1, '2020': cities1}, null, {collapsed: false}); +//var control2 = L.control.layers({'2019': cities2, '2020': cities2}, null, {collapsed: false}); + +//control1.addTo(map1); +//control2.addTo(map2); + +//map1.on('baselayerchange', function(val) { +// console.log(val); +//}); +// +//map2.on('baselayerchange', function(val) { +// console.log("map2", val); +//}); diff --git a/map/static/map/mapsync.js b/map/static/map/mapsync.js new file mode 100644 index 0000000..a4795da --- /dev/null +++ b/map/static/map/mapsync.js @@ -0,0 +1,269 @@ +/* + * Extends L.Map to synchronize the interaction on one map to one or more other maps. + */ + +(function () { + var NO_ANIMATION = { + animate: false, + reset: true, + disableViewprereset: true + }; + + L.Sync = function () {}; + /* + * Helper function to compute the offset easily. + * + * The arguments are relative positions with respect to reference and target maps of + * the point to sync. If you provide ratioRef=[0, 1], ratioTarget=[1, 0] will sync the + * bottom left corner of the reference map with the top right corner of the target map. + * The values can be less than 0 or greater than 1. It will sync points out of the map. + */ + L.Sync.offsetHelper = function (ratioRef, ratioTarget) { + var or = L.Util.isArray(ratioRef) ? ratioRef : [0.5, 0.5]; + var ot = L.Util.isArray(ratioTarget) ? ratioTarget : [0.5, 0.5]; + return function (center, zoom, refMap, targetMap) { + var rs = refMap.getSize(); + var ts = targetMap.getSize(); + var pt = refMap.project(center, zoom) + .subtract([(0.5 - or[0]) * rs.x, (0.5 - or[1]) * rs.y]) + .add([(0.5 - ot[0]) * ts.x, (0.5 - ot[1]) * ts.y]); + return refMap.unproject(pt, zoom); + }; + }; + + + L.Map.include({ + sync: function (map, options) { + this._initSync(); + options = L.extend({ + noInitialSync: false, + syncCursor: false, + syncCursorMarkerOptions: { + radius: 10, + fillOpacity: 0.3, + color: '#da291c', + fillColor: '#fff' + }, + offsetFn: function (center, zoom, refMap, targetMap) { + // no transformation at all + return center; + } + }, options); + + // prevent double-syncing the map: + if (this._syncMaps.indexOf(map) === -1) { + this._syncMaps.push(map); + this._syncOffsetFns[L.Util.stamp(map)] = options.offsetFn; + } + + if (!options.noInitialSync) { + map.setView( + options.offsetFn(this.getCenter(), this.getZoom(), this, map), + this.getZoom(), NO_ANIMATION); + } + if (options.syncCursor) { + if (typeof map.cursor === 'undefined') { + map.cursor = L.circleMarker([0, 0], options.syncCursorMarkerOptions).addTo(map); + } + + this._cursors.push(map.cursor); + + this.on('mousemove', this._cursorSyncMove, this); + this.on('mouseout', this._cursorSyncOut, this); + } + + // on these events, we should reset the view on every synced map + // dragstart is due to inertia + this.on('resize zoomend', this._selfSetView); + this.on('moveend', this._syncOnMoveend); + this.on('dragend', this._syncOnDragend); + return this; + }, + + + // unsync maps from each other + unsync: function (map) { + var self = this; + + if (this._cursors) { + this._cursors.forEach(function (cursor, indx, _cursors) { + if (cursor === map.cursor) { + _cursors.splice(indx, 1); + } + }); + } + + // TODO: hide cursor in stead of moving to 0, 0 + if (map.cursor) { + map.cursor.setLatLng([0, 0]); + } + + if (this._syncMaps) { + this._syncMaps.forEach(function (synced, id) { + if (map === synced) { + delete self._syncOffsetFns[L.Util.stamp(map)]; + self._syncMaps.splice(id, 1); + } + }); + } + + if (!this._syncMaps || this._syncMaps.length == 0) { + // no more synced maps, so these events are not needed. + this.off('resize zoomend', this._selfSetView); + this.off('moveend', this._syncOnMoveend); + this.off('dragend', this._syncOnDragend); + } + + return this; + }, + + // Checks if the map is synced with anything or a specifyc map + isSynced: function (otherMap) { + var has = (this.hasOwnProperty('_syncMaps') && Object.keys(this._syncMaps).length > 0); + if (has && otherMap) { + // Look for this specific map + has = false; + this._syncMaps.forEach(function (synced) { + if (otherMap == synced) { has = true; } + }); + } + return has; + }, + + + // Callbacks for events... + _cursorSyncMove: function (e) { + this._cursors.forEach(function (cursor) { + cursor.setLatLng(e.latlng); + }); + }, + + _cursorSyncOut: function (e) { + this._cursors.forEach(function (cursor) { + // TODO: hide cursor in stead of moving to 0, 0 + cursor.setLatLng([0, 0]); + }); + }, + + _selfSetView: function (e) { + // reset the map, and let setView synchronize the others. + this.setView(this.getCenter(), this.getZoom(), NO_ANIMATION); + }, + + _syncOnMoveend: function (e) { + if (this._syncDragend) { + // This is 'the moveend' after the dragend. + // Without inertia, it will be right after, + // but when inertia is on, we need this to detect that. + this._syncDragend = false; // before calling setView! + this._selfSetView(e); + this._syncMaps.forEach(function (toSync) { + toSync.fire('moveend'); + }); + } + }, + + _syncOnDragend: function (e) { + // It is ugly to have state, but we need it in case of inertia. + this._syncDragend = true; + }, + + + // overload methods on originalMap to replay interactions on _syncMaps; + _initSync: function () { + if (this._syncMaps) { + return; + } + var originalMap = this; + + this._syncMaps = []; + this._cursors = []; + this._syncOffsetFns = {}; + + L.extend(originalMap, { + setView: function (center, zoom, options, sync) { + // Use this sandwich to disable and enable viewprereset + // around setView call + function sandwich (obj, fn) { + var viewpreresets = []; + var doit = options && options.disableViewprereset && obj && obj._events; + if (doit) { + // The event viewpreresets does an invalidateAll, + // that reloads all the tiles. + // That causes an annoying flicker. + viewpreresets = obj._events.viewprereset; + obj._events.viewprereset = []; + } + var ret = fn(obj); + if (doit) { + // restore viewpreresets event to its previous values + obj._events.viewprereset = viewpreresets; + } + return ret; + } + + // Looks better if the other maps 'follow' the active one, + // so call this before _syncMaps + var ret = sandwich(this, function (obj) { + return L.Map.prototype.setView.call(obj, center, zoom, options); + }); + + if (!sync) { + originalMap._syncMaps.forEach(function (toSync) { + sandwich(toSync, function (obj) { + return toSync.setView( + originalMap._syncOffsetFns[L.Util.stamp(toSync)](center, zoom, originalMap, toSync), + zoom, options, true); + }); + }); + } + + return ret; + }, + + panBy: function (offset, options, sync) { + if (!sync) { + originalMap._syncMaps.forEach(function (toSync) { + toSync.panBy(offset, options, true); + }); + } + return L.Map.prototype.panBy.call(this, offset, options); + }, + + _onResize: function (event, sync) { + if (!sync) { + originalMap._syncMaps.forEach(function (toSync) { + toSync._onResize(event, true); + }); + } + return L.Map.prototype._onResize.call(this, event); + }, + + _stop: function (sync) { + L.Map.prototype._stop.call(this); + if (!sync) { + originalMap._syncMaps.forEach(function (toSync) { + toSync._stop(true); + }); + } + } + }); + + originalMap.dragging._draggable._updatePosition = function () { + L.Draggable.prototype._updatePosition.call(this); + var self = this; + originalMap._syncMaps.forEach(function (toSync) { + L.DomUtil.setPosition(toSync.dragging._draggable._element, self._newPos); + toSync.eachLayer(function (layer) { + if (layer._google !== undefined) { + var offsetFn = originalMap._syncOffsetFns[L.Util.stamp(toSync)]; + var center = offsetFn(originalMap.getCenter(), originalMap.getZoom(), originalMap, toSync); + layer._google.setCenter(center); + } + }); + toSync.fire('move'); + }); + }; + } + }); +})(); diff --git a/map/static/map/tree_blooming.png b/map/static/map/tree_blooming.png new file mode 100644 index 0000000..3317c20 Binary files /dev/null and b/map/static/map/tree_blooming.png differ diff --git a/map/static/map/tree_not_blooming.png b/map/static/map/tree_not_blooming.png new file mode 100644 index 0000000..0040970 Binary files /dev/null and b/map/static/map/tree_not_blooming.png differ diff --git a/map/templates/map/index.html b/map/templates/map/index.html new file mode 100644 index 0000000..4f1142b --- /dev/null +++ b/map/templates/map/index.html @@ -0,0 +1,177 @@ +{% load static %} + + + + + Bloommap + + + + + + + + + + +
+
+ +
2019
+
2029
+ + +
+ +
+

+ + + + + + diff --git a/map/tests.py b/map/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/map/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/map/urls.py b/map/urls.py new file mode 100644 index 0000000..2fcca0d --- /dev/null +++ b/map/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path('', views.index, name='index') +] diff --git a/map/views.py b/map/views.py new file mode 100644 index 0000000..0b7b740 --- /dev/null +++ b/map/views.py @@ -0,0 +1,15 @@ +from django.http import HttpResponse +from django.shortcuts import render +import json +import os + + +def index(request): + if not os.path.isfile("/tmp/data.json"): + data = {"trees": str({ "trees": []}) } + else: + with open("/tmp/data.json", "r") as f: + loaded = json.load(f) + s = json.dumps(loaded); + data = {"trees": s} + return render(request, "map/index.html", context=data); diff --git a/mobile/__init__.py b/mobile/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mobile/admin.py b/mobile/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/mobile/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/mobile/apps.py b/mobile/apps.py new file mode 100644 index 0000000..13970a5 --- /dev/null +++ b/mobile/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class MobileConfig(AppConfig): + name = 'mobile' diff --git a/mobile/models.py b/mobile/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/mobile/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/mobile/templates/mobile/base.html b/mobile/templates/mobile/base.html new file mode 100644 index 0000000..bb70a85 --- /dev/null +++ b/mobile/templates/mobile/base.html @@ -0,0 +1,18 @@ + + + + + + + + + + +
+ {% block content %} + {% endblock %} +
+ + + + diff --git a/mobile/templates/mobile/index.html b/mobile/templates/mobile/index.html new file mode 100644 index 0000000..283002d --- /dev/null +++ b/mobile/templates/mobile/index.html @@ -0,0 +1,10 @@ +{% extends "mobile/base.html" %} + +{% block content %} + +

Bloomhunt

+ +

Hunt blooming trees in your area.

+ +Scan a blooming tree +{% endblock %} diff --git a/mobile/templates/mobile/scan.html b/mobile/templates/mobile/scan.html new file mode 100644 index 0000000..ab9f9f2 --- /dev/null +++ b/mobile/templates/mobile/scan.html @@ -0,0 +1,19 @@ +{% extends "mobile/base.html" %} + +{% block content %} +

Scan a blooming tree!

+ +

We will help you identifying the tree you are hunting.

+ +

{{ question.text }}

+ +{% for option in question.options %} + +
+ {% csrf_token %} + + +
+{% endfor %} + +{% endblock %} diff --git a/mobile/templates/mobile/scanned.html b/mobile/templates/mobile/scanned.html new file mode 100644 index 0000000..954522c --- /dev/null +++ b/mobile/templates/mobile/scanned.html @@ -0,0 +1,28 @@ +{% extends "mobile/base.html" %} + +{% block content %} + +

Congratulions, you are the first to report that this {{ tree.name }} +tree is blooming.

+ +

You may give it a name now

+ +
+ {% csrf_token %} + + + + + +
+ + + +{% endblock %} diff --git a/mobile/templates/mobile/thankyou.html b/mobile/templates/mobile/thankyou.html new file mode 100644 index 0000000..0f69b97 --- /dev/null +++ b/mobile/templates/mobile/thankyou.html @@ -0,0 +1,12 @@ +{% extends "mobile/base.html" %} + +{% block content %} + +

You are now one of the glorious Bloom Hunters!

+ +

Does that feel great? It should! You really achieved something in +your life!

+ +Back to hunting + +{% endblock %} diff --git a/mobile/tests.py b/mobile/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/mobile/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/mobile/urls.py b/mobile/urls.py new file mode 100644 index 0000000..f43f719 --- /dev/null +++ b/mobile/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path('addtree/', views.addtree, name='addtree'), + path('scan/', views.scan , name='scan'), + path('', views.index, name='index') +] diff --git a/mobile/views.py b/mobile/views.py new file mode 100644 index 0000000..f88ca85 --- /dev/null +++ b/mobile/views.py @@ -0,0 +1,88 @@ +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render + +from datetime import datetime +import json +import os + + +def index(request): + return render(request, 'mobile/index.html') + + +def scan(request): + question = q1 + if request.POST: + data = request.POST + print("data") + if data["question"]: + print("response to question", data["question"]) + for q in questions: + if not q.key == data["question"]: + continue + for opt in q.options: + if not opt.text == data["chosen_option"]: + continue + if opt.question: + question = opt.question + break + else: + return scanned(request, opt.result) + # first question + context = {'question': question} + return render(request, 'mobile/scan.html', context) + + +def scanned(request, tree): + context = {'tree': {'name': tree}} + return render(request, 'mobile/scanned.html', context) + + +def addtree(request): + if not request.POST: + return HttpResponseRedirect("/mobile/scan") + name = request.POST["name"] + tree = request.POST["name"] + longitude = request.POST["longitude"] + latitude = request.POST["latitude"] + bloom_start = datetime.now().timetuple().tm_yday + if not os.path.isfile('/tmp/data.json'): + data = {'trees': []} + else: + with open('/tmp/data.json', 'r') as f: + data = json.load(f) + + with open('/tmp/data.json', 'w') as f: + data["trees"].append( + {'name': name, 'species': tree, + 'longitude': longitude, 'latitude': latitude, + 'bloom_start': bloom_start } + ) + json.dump(data, f, indent=4) + return render(request, "mobile/thankyou.html") + + +class Option: + def __init__(self, text, question=None, result=None): + self.text = text + self.question = question + self.result = result + + +class Question: + def __init__(self, key, text, options): + self.key = key + self.text = text + self.options = options + + +q3 = Question("alive", "Is your tree alive?", + [Option("yes", result="Poor Apple Tree"), + Option("no", result="Dead Apple Tree")]) +q2 = Question("red", "Is your tree red?", + [Option("yes", result="Red Apple Tree"), + Option("no", result="Green Apple Tree")]) +q1 = Question("leaves", "Does your tree have leaves?", + [Option("yes", question=q2), Option("no", question=q3)]) + +questions = [q1, q2, q3] diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..0040970 Binary files /dev/null and b/static/favicon.ico differ