From 66f8b75be6e35fcdc571f950be03007fd757a21d Mon Sep 17 00:00:00 2001 From: "S. Andrew Sheppard" Date: Wed, 27 Mar 2024 13:53:13 -0400 Subject: [PATCH] update dependencies - replace @wq/cra-template with @wq/create - update tests for wq.db 2.1 - test with Python 3.12 and Django 5.0 --- .github/workflows/test.yml | 16 +- .gitmodules | 3 + packages/cra-template/README.md | 17 -- packages/cra-template/package.json | 29 --- packages/cra-template/template.json | 16 -- packages/cra-template/template/gitignore | 23 -- .../cra-template/template/public/favicon.ico | Bin 9326 -> 0 bytes .../template/public/icon-1024.png | Bin 36261 -> 0 bytes .../cra-template/template/public/icon.svg | 91 ------- .../cra-template/template/public/index.html | 43 ---- .../template/public/manifest.json | 25 -- .../cra-template/template/public/robots.txt | 2 - .../cra-template/template/src/data/config.js | 107 --------- packages/cra-template/template/src/index.js | 30 --- .../template/src/serviceWorker.js | 136 ----------- packages/create/README.md | 16 ++ packages/create/index.js | 22 ++ packages/create/package-lock.json | 15 ++ packages/create/package.json | 19 ++ packages/create/template | 1 + packages/rollup-plugin/package.json | 2 +- packages/rollup-plugin/src/index.js | 4 +- packages/rollup-plugin/src/modules.js | 7 + packages/rollup-plugin/update_modules.sh | 2 +- pyproject.toml | 10 +- tests/expected-postgis/config0.json | 211 +++++++++++++++++ tests/expected-postgis/config1.json | 53 +++++ tests/expected-postgis/config2.json | 185 +++++++++++++++ tests/expected-postgis/config3.json | 222 ++++++++++++++++++ tests/expected-postgis/locations1.json | 10 + tests/expected-postgis/locations2.geojson | 29 +++ tests/expected-postgis/observations1.json | 19 ++ tests/expected/config0.json | 122 ++++++---- tests/expected/config1.json | 8 +- tests/expected/config2.json | 45 +++- tests/expected/config3.json | 119 ++++++---- tests/test-deploy.sh | 34 +-- wq/create/django_project | 2 +- wq/create/projects.py | 83 +++++-- 39 files changed, 1108 insertions(+), 670 deletions(-) delete mode 100644 packages/cra-template/README.md delete mode 100644 packages/cra-template/package.json delete mode 100644 packages/cra-template/template.json delete mode 100644 packages/cra-template/template/gitignore delete mode 100755 packages/cra-template/template/public/favicon.ico delete mode 100644 packages/cra-template/template/public/icon-1024.png delete mode 100644 packages/cra-template/template/public/icon.svg delete mode 100644 packages/cra-template/template/public/index.html delete mode 100644 packages/cra-template/template/public/manifest.json delete mode 100644 packages/cra-template/template/public/robots.txt delete mode 100644 packages/cra-template/template/src/data/config.js delete mode 100644 packages/cra-template/template/src/index.js delete mode 100644 packages/cra-template/template/src/serviceWorker.js create mode 100644 packages/create/README.md create mode 100644 packages/create/index.js create mode 100644 packages/create/package-lock.json create mode 100644 packages/create/package.json create mode 160000 packages/create/template create mode 100644 tests/expected-postgis/config0.json create mode 100644 tests/expected-postgis/config1.json create mode 100644 tests/expected-postgis/config2.json create mode 100644 tests/expected-postgis/config3.json create mode 100644 tests/expected-postgis/locations1.json create mode 100644 tests/expected-postgis/locations2.geojson create mode 100644 tests/expected-postgis/observations1.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 928cf83..896472f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,9 +11,9 @@ jobs: strategy: matrix: variant: [spatialite, postgis, npm] - python-version: ["3.11"] - django-version: [4.2.2] - drf-version: [3.14.0] + python-version: ["3.12"] + django-version: [5.0.3] + drf-version: [3.15.1] steps: - uses: actions/checkout@v2 with: @@ -25,15 +25,15 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip setuptools python -m pip install build python -m pip install flake8 pytest wheel python -m pip install django==${{ matrix.django-version }} python -m pip install djangorestframework==${{ matrix.drf-version }} python -m pip install xlsconv==2.0.0 - python -m pip install wq.build==2.0.0 - python -m pip install wq.app==2.0.0 - python -m pip install wq.db==2.0.0 + python -m pip install wq.build==2.1.0 + python -m pip install wq.app==2.1.0 + python -m pip install wq.db==2.1.0 - name: Test build run: | python -m build @@ -81,7 +81,7 @@ jobs: python-version: ["3.11"] node-version: [18] package: - - cra-template + - create - expo-template - rollup-plugin steps: diff --git a/.gitmodules b/.gitmodules index 2c6e360..2248757 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "django_project"] path = wq/create/django_project url = https://github.com/wq/wq-django-template.git +[submodule "packages/create/template"] + path = packages/create/template + url = https://github.com/wq/wq-vite-template.git diff --git a/packages/cra-template/README.md b/packages/cra-template/README.md deleted file mode 100644 index 1b569ba..0000000 --- a/packages/cra-template/README.md +++ /dev/null @@ -1,17 +0,0 @@ -[![@wq/cra-template][logo]][docs] - -This is the [Create React App][create-react-app] template for projects utilizing the [wq framework]. It uses [@wq/app], [@wq/material], and [@wq/map-gl] to generate a configuration-driven interface for collecting and managing geospatial field data. This template is generally meant to be used together with [wq.create]. See wq's [Getting Started] docs for more information. - -### [Documentation][docs] - -[logo]: https://wq.io/images/@wq/cra-template.svg -[docs]: https://wq.io/@wq/cra-template - -[wq framework]: https://wq.io/ -[@wq/app]: https://wq.io/@wq/app -[@wq/material]: https://wq.io/@wq/material -[@wq/map-gl]: https://wq.io/@wq/map-gl -[wq.create]: https://wq.io/wq.create/ -[Getting Started]: https://wq.io/overview/setup - -[create-react-app]: https://facebook.github.io/create-react-app/docs/getting-started diff --git a/packages/cra-template/package.json b/packages/cra-template/package.json deleted file mode 100644 index 3c12956..0000000 --- a/packages/cra-template/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@wq/cra-template", - "version": "2.0.0", - "description": "Create React App template for use with @wq/app", - "main": "template.json", - "repository": { - "type": "git", - "url": "https://github.com/wq/wq.create.git", - "directory": "packages/cra-template" - }, - "keywords": [ - "wq", - "app", - "create-react-app", - "template", - "offline", - "data-collection" - ], - "author": "S. Andrew Sheppard", - "license": "MIT", - "bugs": { - "url": "https://github.com/wq/wq.create/issues" - }, - "homepage": "https://wq.io/@wq/cra-template", - "files": [ - "template", - "template.json" - ] -} diff --git a/packages/cra-template/template.json b/packages/cra-template/template.json deleted file mode 100644 index c51e548..0000000 --- a/packages/cra-template/template.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "package": { - "proxy": "http://localhost:8000", - "dependencies": { - "@wq/app": "^2.0.0", - "@wq/material": "^2.0.0", - "@wq/material-web": "^2.0.0", - "@wq/map-gl": "^2.0.0", - "@wq/map-gl-web": "^2.0.0", - "maplibre-gl": "^3.1.0" - }, - "eslintConfig": { - "extends": ["react-app"] - } - } -} diff --git a/packages/cra-template/template/gitignore b/packages/cra-template/template/gitignore deleted file mode 100644 index 4d29575..0000000 --- a/packages/cra-template/template/gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/packages/cra-template/template/public/favicon.ico b/packages/cra-template/template/public/favicon.ico deleted file mode 100755 index 47e9b6ca9f062e10415487752ede9ee3a60e58ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9326 zcmeHNXG~Q|6#gFdQKU%~lzv47K@b#pz*CUk8CO{mTtLCLvZ9D(T?E^rUVAtALt~7+ z8>7)^tTC}AMvXO=#Kab3?>jT^-UrG<1$TewzDe%PIdjhU?#y{}&;1S$KmkM8*no~o zloxcgR`y)R;9|Hyqz@R~cFnI7_j2blxRaI4}t*yoM>C;hHSBLrY=VRf*g;=?A zCF<+zv1-*StXZ=L>({TxrcIl0b~UFl0l>p?Fyy;y$!QZq0xfU&VLOifK;VPOGFOG{W=ThrWb13NoAI5;@K(a{l3 zPEK%kc805~E8N`N;O*@VA0HpkjlMFue0)65<0&aA zJcsAz=AuuZK0J3978YXQz=0?#Dndy~3D4t0h77@&F=J3!S;_PHxN+k!e*Ab$m@ok~ zH8niHPnj|WQ>RYFtXZ=liX!IDor^__7GcGT6+&9Xf==hY#b}v12%K;snqC=g*(V#fukl{rYv>zI_{a?%cuMyLWN# z-aXvEe;*GYKE&h4kNG>{*|TSO{`@&!ym*1vuV3rm4_`WDdfQJL0TWfPW}3=`ZCOQ{ zZ7OE()vTHH`eE&=E^Jqytv^@ZX;Kd)F{QztjSf_Y9rFl98>M+XlN#)imZpUCR)S83 zA&HRgNd3g_-O)6igajF=2{G+PEu@{#CwB8_kM6{!XZy}18_CQbYTekLg`6Jl1f-ii zNkIm)hnqFFM_7>z<5XLcNEZOUv_OZ(c5h!ojJgVhC}N2tyECx}ZfrM?BE-`$h7h+- zBnf^AaZWbON#0+sb0#4%t~4ak{)D7?S+@;fX04qoIyxDz!^Ee42>aKbkFPyn>>2rS z8{;WzdR5cwYdl4vP=Ik2LqkIt85wcTVq#*#d5co1gqfKc%+1X?f3dQ%f~~DB=PWKR zE}XBpySsyuNO*a9X}OE9uP^5Ew4#SOVie=nVFf$%A(H{tyLM<>D#w2^78U{y;@LE0LFP3-zhFG z=6t8Lv=n7!WnjE#=+L1UHf$J%4S`?~Vl^-0M04iM;hbpRym?r#U;*bwix)4(k|j&9bm>woTeb|#moMi$Y3_&Zmwa zKh8PT$&)8}eSP}$X`DTKR?D$2T)4n_*5%8WdA)u0>Q!93c8&9{8#iw7`uoYH96y6jCZlR{MD;hoPWJ}^9FCFXOcAhA)4bE7 zre=R^lUom3DYdxukkPPdF_r_gD^Rx%ua*~(*gAZo9;bP12V}MRtxK6xwaSd*gR5vug&;ag@=5^hF7Cq6`8XpHBSLLPn5Q$>ghr5Vd{nmnfyA5DMy2;m-OR5F)*bL5XIQYdV{h<8L!SaFX8 z&?;pT7i${ARsr;uKS0O+X96P~P|G|zV6rj#a9>(Z&y+#*w-{N}fv4LSqtK6ez*O`@ zAosOER~aK#0sSRK2VRACVgbUa!|&)UX2OiQ(&bAbWttIU*g%^OyaVFJzxn$l3EARU z;7{r|l5UCvc;^H^Nok6&14>2F4)io5(4h?v57V3C?a_F4`w!7h~5~-Bn5J4-x-!n;Vl)Yk;EqWLg{RD#}F8ECtvz4 ziefpCB4K72qN5}>U4Imux$ReYOJ7nN52F|i^yQXm_6kgH7H^N9+%}ayZJAu631b2i zIsol+iu0mrDcEonK41R~U=T-%)>SnQT# diff --git a/packages/cra-template/template/public/icon-1024.png b/packages/cra-template/template/public/icon-1024.png deleted file mode 100644 index c1c7d62c4918b8010222dff8e8fca65eada85e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36261 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

%S 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

L|n6S#Be;ge#X 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 O02=9=>EI{jaVw8*aj diff --git a/packages/cra-template/template/public/icon.svg b/packages/cra-template/template/public/icon.svg deleted file mode 100644 index fc96acb..0000000 --- a/packages/cra-template/template/public/icon.svg +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - WQ - - diff --git a/packages/cra-template/template/public/index.html b/packages/cra-template/template/public/index.html deleted file mode 100644 index 0494e69..0000000 --- a/packages/cra-template/template/public/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - {{ title }} - - - -
- - - diff --git a/packages/cra-template/template/public/manifest.json b/packages/cra-template/template/public/manifest.json deleted file mode 100644 index ab0ec13..0000000 --- a/packages/cra-template/template/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "{{ project_name }}", - "name": "{{ title }}", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "icon-192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "icon-512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "fullscreen", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/packages/cra-template/template/public/robots.txt b/packages/cra-template/template/public/robots.txt deleted file mode 100644 index 01b0f9a..0000000 --- a/packages/cra-template/template/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * diff --git a/packages/cra-template/template/src/data/config.js b/packages/cra-template/template/src/data/config.js deleted file mode 100644 index edbaf7f..0000000 --- a/packages/cra-template/template/src/data/config.js +++ /dev/null @@ -1,107 +0,0 @@ -const config = { - store: { - service: "https://example.com/", - }, - material: { - theme: { - primary: "#7500ae", - secondary: "#0088bd", - }, - }, - map: { - bounds: [ - [-180, -70], - [180, 70], - ], - }, - pages: { - index: { - verbose_name: "Home", - url: "", - show_in_index: false, - }, - login: { - url: "login", - }, - logout: { - url: "logout", - }, - observation: { - cache: "first_page", - background_sync: true, - name: "observation", - url: "observations", - list: true, - form: [ - { - name: "date", - label: "Date", - hint: "The date when the observation was taken", - type: "date", - }, - { - name: "category", - label: "Category", - hint: "Observation type", - type: "select one", - "wq:ForeignKey": "category", - "wq:related_name": "observation_set", - }, - { - name: "geometry", - label: "Location", - bind: { - required: true, - }, - hint: "The location of the observation", - type: "geopoint", - }, - { - name: "photo", - label: "Photo", - hint: "Photo of the observation", - type: "image", - }, - { - name: "notes", - label: "Notes", - hint: "Field observations and notes", - type: "text", - multiline: true, - }, - ], - verbose_name: "observation", - verbose_name_plural: "observations", - ordering: ["-date"], - label_template: "{{date}}", - }, - category: { - cache: "all", - background_sync: false, - name: "category", - url: "categories", - list: true, - form: [ - { - name: "name", - label: "Name", - bind: { - required: true, - }, - "wq:length": 255, - type: "string", - }, - { - name: "description", - label: "Description", - type: "text", - }, - ], - verbose_name: "category", - verbose_name_plural: "categories", - label_template: "{{name}}", - }, - }, -}; - -export default config; diff --git a/packages/cra-template/template/src/index.js b/packages/cra-template/template/src/index.js deleted file mode 100644 index fba5d71..0000000 --- a/packages/cra-template/template/src/index.js +++ /dev/null @@ -1,30 +0,0 @@ -import app from "@wq/app"; -import material from "@wq/material"; -import mapgl from "@wq/map-gl"; -import maplibre from "maplibre-gl"; -import config from "./data/config"; -import * as serviceWorker from "./serviceWorker"; - -import "maplibre-gl/dist/maplibre-gl.css"; -import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css"; - -mapgl.setEngine(maplibre); - -app.use([material, mapgl]); - -async function init() { - // const response = await fetch('/config.json'), - // config = await response.json(); // Load directly from wq.db - await app.init(config); - await app.prefetchAll(); - if (config.debug) { - window.wq = app; - } -} - -init(); - -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); diff --git a/packages/cra-template/template/src/serviceWorker.js b/packages/cra-template/template/src/serviceWorker.js deleted file mode 100644 index a683e53..0000000 --- a/packages/cra-template/template/src/serviceWorker.js +++ /dev/null @@ -1,136 +0,0 @@ -// This optional code is used to register a service worker. -// register() is not called by default. - -// This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) -// will only see deployed updates on subsequent visits to a page, after all the -// existing tabs open on the page have been closed, since previously cached -// resources are updated in the background. - -// To learn more about the benefits of this model and instructions on how to -// opt-in, read https://bit.ly/CRA-PWA - -const isLocalhost = Boolean( - window.location.hostname === "localhost" || - // [::1] is the IPv6 localhost address. - window.location.hostname === "[::1]" || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) -); - -export function register(config) { - if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } - - window.addEventListener("load", () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); - - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - "This web app is being served cache-first by a service " + - "worker. To learn more, visit https://bit.ly/CRA-PWA" - ); - }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } -} - -function registerValidSW(swUrl, config) { - navigator.serviceWorker - .register(swUrl) - .then((registration) => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === "installed") { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - "New content is available and will be used when all " + - "tabs for this page are closed. See https://bit.ly/CRA-PWA." - ); - - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log("Content is cached for offline use."); - - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch((error) => { - console.error("Error during service worker registration:", error); - }); -} - -function checkValidServiceWorker(swUrl, config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then((response) => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get("content-type"); - if ( - response.status === 404 || - (contentType != null && - contentType.indexOf("javascript") === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then((registration) => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log( - "No internet connection found. App is running in offline mode." - ); - }); -} - -export function unregister() { - if ("serviceWorker" in navigator) { - navigator.serviceWorker.ready.then((registration) => { - registration.unregister(); - }); - } -} diff --git a/packages/create/README.md b/packages/create/README.md new file mode 100644 index 0000000..ff4b9b7 --- /dev/null +++ b/packages/create/README.md @@ -0,0 +1,16 @@ +[![@wq/create][logo]][docs] + +This command leverages [wq-vite-template] to generate [Vite] projects utilizing the [wq framework]. The template uses [@wq/app], [@wq/material], and [@wq/map-gl] to generate a configuration-driven interface for collecting and managing geospatial field data. This template is generally meant to be used together with [wq.create]. See wq's [Getting Started] docs for more information. + +### [Documentation][docs] + +[logo]: https://wq.io/images/@wq/create.svg +[docs]: https://wq.io/@wq/create +[wq framework]: https://wq.io/ +[@wq/app]: https://wq.io/@wq/app +[@wq/material]: https://wq.io/@wq/material +[@wq/map-gl]: https://wq.io/@wq/map-gl +[wq.create]: https://wq.io/wq.create/ +[Getting Started]: https://wq.io/overview/setup +[wq-vite-template]: https://github.com/wq/wq-vite-template +[Vite]: https://vitejs.dev/ diff --git a/packages/create/index.js b/packages/create/index.js new file mode 100644 index 0000000..f046706 --- /dev/null +++ b/packages/create/index.js @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +const fs = require("fs"), + path = require("path"), + template = path.join(path.dirname(__filename), "template"), + project = process.argv[2] || "project"; + +try { + fs.cpSync(template, project, { + recursive: true, + force: false, + errorOnExist: true, + }); + const pkgFile = path.join(project, "package.json"); + pkg = JSON.parse(fs.readFileSync(pkgFile)); + pkg.name = project; + fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 4) + "\n"); + fs.rmSync(path.join(project, "package-lock.json")); + console.log(`@wq/create: Created ${project} from wq-vite-template`); +} catch (e) { + console.log(e.message || "@wq/create: Error creating project"); +} diff --git a/packages/create/package-lock.json b/packages/create/package-lock.json new file mode 100644 index 0000000..cd2d036 --- /dev/null +++ b/packages/create/package-lock.json @@ -0,0 +1,15 @@ +{ + "name": "@wq/create", + "version": "2.1.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@wq/create", + "version": "2.1.1", + "bin": { + "wq-create": "index.js" + } + } + } +} diff --git a/packages/create/package.json b/packages/create/package.json new file mode 100644 index 0000000..88e5d05 --- /dev/null +++ b/packages/create/package.json @@ -0,0 +1,19 @@ +{ + "name": "@wq/create", + "version": "2.1.1", + "bin": { + "wq-create": "./index.js" + }, + "files": [ + "template" + ], + "repository": { + "type": "git", + "url": "https://github.com/wq/wq.create.git", + "directory": "packages/create" + }, + "bugs": { + "url": "https://github.com/wq/wq.create/issues" + }, + "homepage": "https://wq.io/@wq/create" +} diff --git a/packages/create/template b/packages/create/template new file mode 160000 index 0000000..fe9db42 --- /dev/null +++ b/packages/create/template @@ -0,0 +1 @@ +Subproject commit fe9db42b7e82c604da8b80b9cd09b23b429a75ce diff --git a/packages/rollup-plugin/package.json b/packages/rollup-plugin/package.json index c2ca51d..51111bc 100644 --- a/packages/rollup-plugin/package.json +++ b/packages/rollup-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wq/rollup-plugin", - "version": "2.0.0", + "version": "2.1.0", "description": "Build custom wq plugins that integrate with wq.js", "type": "module", "main": "src/index.js", diff --git a/packages/rollup-plugin/src/index.js b/packages/rollup-plugin/src/index.js index 9203632..f663e2a 100644 --- a/packages/rollup-plugin/src/index.js +++ b/packages/rollup-plugin/src/index.js @@ -1,10 +1,10 @@ import modules from "./modules.js"; const prefix = "\0wq-bundle:", - defaultConfig = {urlBase: "."}; + defaultConfig = { urlBase: "." }; export default function wq(config) { - const { urlBase } = {...defaultConfig,...config}; + const { urlBase } = { ...defaultConfig, ...config }; return { name: "@wq/rollup-plugin", resolveId(id) { diff --git a/packages/rollup-plugin/src/modules.js b/packages/rollup-plugin/src/modules.js index 41f21e3..0d642aa 100644 --- a/packages/rollup-plugin/src/modules.js +++ b/packages/rollup-plugin/src/modules.js @@ -543,6 +543,7 @@ const modules = { "DefaultList", "FileLink", "ForeignKey", + "ForeignKeyLink", "Form", "FormError", "ImagePreview", @@ -551,11 +552,13 @@ const modules = { "Loading", "Login", "Logout", + "ManyToManyLink", "Message", "NavMenu", "NotFound", "OutboxList", "PropertyTable", + "RelatedLinks", "RouteContext", "Server", "autoFormData", @@ -689,6 +692,7 @@ const modules = { "AutoOverlay", "DefaultDetail", "DefaultList", + "DefaultPopup", "Geo", "GeoCode", "GeoCoords", @@ -706,6 +710,8 @@ const modules = { "computeBounds", "useBasemapComponents", "useFeatureCollection", + "useFeatureUrl", + "useFeatureValues", "useGeoJSON", "useGeoTools", "useGeolocation", @@ -713,6 +719,7 @@ const modules = { "useMapInstance", "useMapState", "useOverlayComponents", + "useStyleProp", ], hasDefault: true, }, diff --git a/packages/rollup-plugin/update_modules.sh b/packages/rollup-plugin/update_modules.sh index 4a74a93..d153c7b 100755 --- a/packages/rollup-plugin/update_modules.sh +++ b/packages/rollup-plugin/update_modules.sh @@ -1,5 +1,5 @@ #!/bin/bash -wget https://unpkg.com/wq@next -O wq.js +wget https://unpkg.com/wq@latest -O wq.js sed -i "s/^import[^;]*;//" wq.js node update_modules.js > src/modules.js npm run prettier diff --git a/pyproject.toml b/pyproject.toml index 7a640e2..911d633 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [ ] description = "Project scaffolding tools for creating a new application with the wq framework." readme = "README.md" -requires-python = ">=3.7" +requires-python = ">=3.8" license = {text = "MIT"} classifiers = [ "Development Status :: 5 - Production/Stable", @@ -18,16 +18,16 @@ classifiers = [ "Natural Language :: English", "Programming Language :: JavaScript", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Framework :: Django", - "Framework :: Django :: 3.2", "Framework :: Django :: 4.0", "Framework :: Django :: 4.1", "Framework :: Django :: 4.2", + "Framework :: Django :: 5.0", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Code Generators", "Topic :: Text Processing :: Markup :: HTML", @@ -35,8 +35,8 @@ classifiers = [ "Topic :: Software Development :: Pre-processors", ] dependencies = [ - "wq.build>=2.0.0", - "xlsconv>=2.0.0,<2.1.0", + "wq.build>=2.1.0", + "xlsconv>=2.0.0,<2.2.0", "psycopg2-binary", ] diff --git a/tests/expected-postgis/config0.json b/tests/expected-postgis/config0.json new file mode 100644 index 0000000..988c0b2 --- /dev/null +++ b/tests/expected-postgis/config0.json @@ -0,0 +1,211 @@ +{ + "pages": { + "login": { + "url": "login", + "name": "login" + }, + "logout": { + "url": "logout", + "name": "logout" + }, + "index": { + "url": "", + "icon": "directions", + "verbose_name": "Map", + "description": "Project overview map", + "section": "Contributions", + "order": 0, + "map": { + "mapId": "map", + "layers": [ + { + "name": "Observations", + "type": "geojson", + "url": "/observations.geojson", + "popup": "observation" + } + ] + }, + "name": "index" + }, + "category": { + "icon": "config", + "description": "Manage available categories", + "section": "Admin", + "order": 100, + "show_in_index": "can_change", + "cache": "all", + "background_sync": false, + "name": "category", + "url": "categories", + "list": true, + "form": [ + { + "name": "name", + "label": "Name", + "bind": { + "required": true + }, + "wq:length": 255, + "type": "string" + }, + { + "name": "description", + "label": "Description", + "type": "text" + } + ], + "verbose_name": "category", + "verbose_name_plural": "categories", + "label_template": "{{name}}" + }, + "observation": { + "icon": "list", + "description": "View and submit photos on map", + "section": "Contributions", + "order": 1, + "cache": "first_page", + "background_sync": true, + "map": [ + { + "mode": "list", + "layers": [ + { + "name": "Observations", + "type": "geojson", + "data": [ + "context_feature_collection", + "geometry" + ], + "popup": "observation", + "cluster": true + }, + { + "name": "All Observations", + "type": "vector-tile", + "layer": "observation", + "popup": "observation", + "identifyLayers": [ + "observation-circle" + ], + "active": false + } + ] + }, + { + "mode": "detail", + "layers": [ + { + "name": "Observation", + "type": "geojson", + "data": [ + "context_feature", + "geometry" + ], + "popup": "observation" + }, + { + "name": "All Observations", + "type": "vector-tile", + "layer": "observation", + "popup": "observation", + "identifyLayers": [ + "observation-circle" + ], + "active": false + } + ] + }, + { + "mode": "edit", + "layers": [] + } + ], + "name": "observation", + "url": "observations", + "list": true, + "form": [ + { + "name": "date", + "label": "Date", + "hint": "The date when the observation was taken", + "type": "date" + }, + { + "name": "category", + "label": "Category", + "hint": "Observation type", + "type": "select one", + "wq:ForeignKey": "category", + "wq:related_name": "observation_set" + }, + { + "name": "geometry", + "label": "Location", + "bind": { + "required": true + }, + "hint": "The location of the observation", + "type": "geopoint" + }, + { + "name": "photo", + "label": "Photo", + "hint": "Photo of the observation", + "type": "image" + }, + { + "name": "notes", + "label": "Notes", + "hint": "Field observations and notes", + "type": "text", + "multiline": true + } + ], + "verbose_name": "observation", + "verbose_name_plural": "observations", + "ordering": [ + "-date" + ], + "label_template": "{{date}}", + "geometry_fields": [ + { + "name": "geometry", + "label": "Location", + "type": "geopoint" + } + ] + } + }, + "site_title": "test Project", + "router": { + "base_url": "" + }, + "store": { + "service": "", + "defaults": { + "format": "json" + } + }, + "logo": "/static/app/images/icon-192.png", + "material": { + "theme": { + "primary": "#7500ae", + "secondary": "#0088bd" + } + }, + "map": { + "bounds": [ + [ + -180, + -70 + ], + [ + 180, + 70 + ] + ], + "tiles": "/tiles/{z}/{x}/{y}.pbf" + }, + "debug": true +} diff --git a/tests/expected-postgis/config1.json b/tests/expected-postgis/config1.json new file mode 100644 index 0000000..8d019c8 --- /dev/null +++ b/tests/expected-postgis/config1.json @@ -0,0 +1,53 @@ +{ + "pages": { + "login": { + "url": "login", + "name": "login" + }, + "logout": { + "url": "logout", + "name": "logout" + }, + "index": { + "url": "", + "name": "index", + "show_in_index": false, + "verbose_name": "test Project", + "map": { + "mapId": "map", + "layers": [] + } + } + }, + "site_title": "test Project", + "router": { + "base_url": "" + }, + "store": { + "service": "", + "defaults": { + "format": "json" + } + }, + "logo": "/static/app/images/icon-192.png", + "material": { + "theme": { + "primary": "#7500ae", + "secondary": "#0088bd" + } + }, + "map": { + "bounds": [ + [ + -180, + -70 + ], + [ + 180, + 70 + ] + ], + "tiles": "/tiles/{z}/{x}/{y}.pbf" + }, + "debug": true +} diff --git a/tests/expected-postgis/config2.json b/tests/expected-postgis/config2.json new file mode 100644 index 0000000..8e34922 --- /dev/null +++ b/tests/expected-postgis/config2.json @@ -0,0 +1,185 @@ +{ + "pages": { + "login": { + "url": "login", + "name": "login" + }, + "logout": { + "url": "logout", + "name": "logout" + }, + "location": { + "cache": "first_page", + "background_sync": true, + "map": [ + { + "mode": "list", + "layers": [ + { + "name": "Locations", + "type": "geojson", + "data": [ + "context_feature_collection", + "geometry" + ], + "popup": "location", + "cluster": true + }, + { + "name": "All Locations", + "type": "vector-tile", + "layer": "location", + "popup": "location", + "identifyLayers": [ + "location-circle" + ], + "active": false + } + ] + }, + { + "mode": "detail", + "layers": [ + { + "name": "Location", + "type": "geojson", + "data": [ + "context_feature", + "geometry" + ], + "popup": "location" + }, + { + "name": "All Locations", + "type": "vector-tile", + "layer": "location", + "popup": "location", + "identifyLayers": [ + "location-circle" + ], + "active": false + } + ] + }, + { + "mode": "edit", + "layers": [ + { + "name": "All Locations", + "type": "vector-tile", + "layer": "location", + "popup": "location", + "active": false + } + ] + } + ], + "name": "location", + "url": "locations", + "list": true, + "form": [ + { + "name": "name", + "label": "Site Name", + "bind": { + "required": true + }, + "hint": "The unique name of the site", + "type": "text" + }, + { + "name": "type", + "label": "Site Type", + "bind": { + "required": true + }, + "hint": "Site classification", + "choices": [ + { + "name": "water", + "label": "Water" + }, + { + "name": "air", + "label": "Air" + }, + { + "name": "land", + "label": "Land" + } + ], + "type": "select one" + }, + { + "name": "geometry", + "label": "Location", + "bind": { + "required": true + }, + "hint": "The location of the site", + "type": "geopoint" + } + ], + "verbose_name": "location", + "verbose_name_plural": "locations", + "geometry_fields": [ + { + "name": "geometry", + "label": "Location", + "type": "geopoint" + } + ] + }, + "index": { + "url": "", + "name": "index", + "show_in_index": false, + "verbose_name": "test Project", + "map": { + "mapId": "map", + "layers": [ + { + "name": "Locations", + "type": "vector-tile", + "layer": "location", + "popup": "location", + "identifyLayers": [ + "location-circle" + ] + } + ] + } + } + }, + "site_title": "test Project", + "router": { + "base_url": "" + }, + "store": { + "service": "", + "defaults": { + "format": "json" + } + }, + "logo": "/static/app/images/icon-192.png", + "material": { + "theme": { + "primary": "#7500ae", + "secondary": "#0088bd" + } + }, + "map": { + "bounds": [ + [ + -180, + -70 + ], + [ + 180, + 70 + ] + ], + "tiles": "/tiles/{z}/{x}/{y}.pbf" + }, + "debug": true +} diff --git a/tests/expected-postgis/config3.json b/tests/expected-postgis/config3.json new file mode 100644 index 0000000..a6f8419 --- /dev/null +++ b/tests/expected-postgis/config3.json @@ -0,0 +1,222 @@ +{ + "pages": { + "login": { + "url": "login", + "name": "login" + }, + "logout": { + "url": "logout", + "name": "logout" + }, + "observation": { + "cache": "first_page", + "background_sync": true, + "name": "observation", + "url": "observations", + "list": true, + "form": [ + { + "name": "location", + "label": "Site", + "hint": "The site where the observation was taken", + "type": "select one", + "wq:ForeignKey": "location", + "wq:related_name": "observation_set" + }, + { + "name": "date", + "label": "Date", + "hint": "The date when the observation was taken", + "type": "date" + }, + { + "name": "photo", + "label": "Photo", + "hint": "Photo of the site", + "type": "image" + }, + { + "name": "notes", + "label": "Notes", + "hint": "Field observations and notes", + "type": "text" + } + ], + "verbose_name": "observation", + "verbose_name_plural": "observations" + }, + "location": { + "cache": "first_page", + "background_sync": true, + "map": [ + { + "mode": "list", + "layers": [ + { + "name": "Locations", + "type": "geojson", + "data": [ + "context_feature_collection", + "geometry" + ], + "popup": "location", + "cluster": true + }, + { + "name": "All Locations", + "type": "vector-tile", + "layer": "location", + "popup": "location", + "identifyLayers": [ + "location-circle" + ], + "active": false + } + ] + }, + { + "mode": "detail", + "layers": [ + { + "name": "Location", + "type": "geojson", + "data": [ + "context_feature", + "geometry" + ], + "popup": "location" + }, + { + "name": "All Locations", + "type": "vector-tile", + "layer": "location", + "popup": "location", + "identifyLayers": [ + "location-circle" + ], + "active": false + } + ] + }, + { + "mode": "edit", + "layers": [ + { + "name": "All Locations", + "type": "vector-tile", + "layer": "location", + "popup": "location", + "active": false + } + ] + } + ], + "name": "location", + "url": "locations", + "list": true, + "form": [ + { + "name": "name", + "label": "Site Name", + "bind": { + "required": true + }, + "hint": "The unique name of the site", + "type": "text" + }, + { + "name": "type", + "label": "Site Type", + "bind": { + "required": true + }, + "hint": "Site classification", + "choices": [ + { + "name": "water", + "label": "Water" + }, + { + "name": "air", + "label": "Air" + }, + { + "name": "land", + "label": "Land" + } + ], + "type": "select one" + }, + { + "name": "geometry", + "label": "Location", + "bind": { + "required": true + }, + "hint": "The location of the site", + "type": "geopoint" + } + ], + "verbose_name": "location", + "verbose_name_plural": "locations", + "geometry_fields": [ + { + "name": "geometry", + "label": "Location", + "type": "geopoint" + } + ] + }, + "index": { + "url": "", + "name": "index", + "show_in_index": false, + "verbose_name": "test Project", + "map": { + "mapId": "map", + "layers": [ + { + "name": "Locations", + "type": "vector-tile", + "layer": "location", + "popup": "location", + "identifyLayers": [ + "location-circle" + ] + } + ] + } + } + }, + "site_title": "test Project", + "router": { + "base_url": "" + }, + "store": { + "service": "", + "defaults": { + "format": "json" + } + }, + "logo": "/static/app/images/icon-192.png", + "material": { + "theme": { + "primary": "#7500ae", + "secondary": "#0088bd" + } + }, + "map": { + "bounds": [ + [ + -180, + -70 + ], + [ + 180, + 70 + ] + ], + "tiles": "/tiles/{z}/{x}/{y}.pbf" + }, + "debug": true +} diff --git a/tests/expected-postgis/locations1.json b/tests/expected-postgis/locations1.json new file mode 100644 index 0000000..58be6c5 --- /dev/null +++ b/tests/expected-postgis/locations1.json @@ -0,0 +1,10 @@ +{ + "count": 0, + "next": null, + "previous": null, + "page": 1, + "pages": 1, + "per_page": 50, + "multiple": false, + "list": [] +} diff --git a/tests/expected-postgis/locations2.geojson b/tests/expected-postgis/locations2.geojson new file mode 100644 index 0000000..b42e9b8 --- /dev/null +++ b/tests/expected-postgis/locations2.geojson @@ -0,0 +1,29 @@ +{ + "count": 1, + "next": null, + "previous": null, + "page": 1, + "pages": 1, + "per_page": 50, + "multiple": false, + "type": "FeatureCollection", + "features": [ + { + "id": 1, + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + -93.28, + 44.98 + ] + }, + "properties": { + "name": "Site 1", + "type": "water", + "label": "Site 1", + "type_label": "Water" + } + } + ] +} diff --git a/tests/expected-postgis/observations1.json b/tests/expected-postgis/observations1.json new file mode 100644 index 0000000..d8704c2 --- /dev/null +++ b/tests/expected-postgis/observations1.json @@ -0,0 +1,19 @@ +{ + "count": 1, + "next": null, + "previous": null, + "page": 1, + "pages": 1, + "per_page": 50, + "multiple": false, + "list": [ + { + "id": 1, + "date": "2015-10-08", + "photo": "http://localhost:8000/media/observations/photo.jpg", + "notes": "A cold winter day in Minneapolis", + "location_id": 1, + "label": "Site 1 on 2015-10-08" + } + ] +} diff --git a/tests/expected/config0.json b/tests/expected/config0.json index 9e93762..3fc0b08 100644 --- a/tests/expected/config0.json +++ b/tests/expected/config0.json @@ -8,52 +8,62 @@ "url": "logout", "name": "logout" }, - "category": { - "icon": "config", - "description": "Manage available categories", - "section": "Admin", - "show_in_index": "can_change", - "cache": "all", - "background_sync": false, - "name": "category", - "url": "categories", - "list": true, - "form": [ - { - "name": "name", - "label": "Name", - "bind": { - "required": true - }, - "wq:length": 255, - "type": "string" - }, - { - "name": "description", - "label": "Description", - "type": "text" - } - ], - "verbose_name": "category", - "verbose_name_plural": "categories", - "label_template": "{{name}}" + "index": { + "url": "", + "icon": "directions", + "verbose_name": "Map", + "description": "Project overview map", + "section": "Contributions", + "order": 0, + "map": { + "mapId": "map", + "layers": [ + { + "name": "Observations", + "type": "geojson", + "url": "/observations.geojson", + "popup": "observation" + } + ] + }, + "name": "index" }, "observation": { "icon": "list", "description": "View and submit photos on map", "section": "Contributions", + "order": 1, "cache": "first_page", "background_sync": true, "map": [ { "mode": "list", - "autoLayers": true, - "layers": [] + "layers": [ + { + "name": "Observations", + "type": "geojson", + "data": [ + "context_feature_collection", + "geometry" + ], + "popup": "observation", + "cluster": true + } + ] }, { "mode": "detail", - "autoLayers": true, - "layers": [] + "layers": [ + { + "name": "Observation", + "type": "geojson", + "data": [ + "context_feature", + "geometry" + ], + "popup": "observation" + } + ] }, { "mode": "edit", @@ -106,17 +116,48 @@ "ordering": [ "-date" ], - "label_template": "{{date}}" + "label_template": "{{date}}", + "geometry_fields": [ + { + "name": "geometry", + "label": "Location", + "type": "geopoint" + } + ] }, - "index": { - "url": "", - "name": "index", - "show_in_index": false, - "verbose_name": "test Project" + "category": { + "icon": "config", + "description": "Manage available categories", + "section": "Admin", + "order": 100, + "show_in_index": "can_change", + "cache": "all", + "background_sync": false, + "name": "category", + "url": "categories", + "list": true, + "form": [ + { + "name": "name", + "label": "Name", + "bind": { + "required": true + }, + "wq:length": 255, + "type": "string" + }, + { + "name": "description", + "label": "Description", + "type": "text" + } + ], + "verbose_name": "category", + "verbose_name_plural": "categories", + "label_template": "{{name}}" } }, "site_title": "test Project", - "logo": "/icon-192.png", "router": { "base_url": "" }, @@ -126,6 +167,7 @@ "format": "json" } }, + "logo": "/static/app/images/icon-192.png", "material": { "theme": { "primary": "#7500ae", diff --git a/tests/expected/config1.json b/tests/expected/config1.json index ea2227a..838b210 100644 --- a/tests/expected/config1.json +++ b/tests/expected/config1.json @@ -12,11 +12,14 @@ "url": "", "name": "index", "show_in_index": false, - "verbose_name": "test Project" + "verbose_name": "test Project", + "map": { + "mapId": "map", + "layers": [] + } } }, "site_title": "test Project", - "logo": "/icon-192.png", "router": { "base_url": "" }, @@ -26,6 +29,7 @@ "format": "json" } }, + "logo": "/static/app/images/icon-192.png", "material": { "theme": { "primary": "#7500ae", diff --git a/tests/expected/config2.json b/tests/expected/config2.json index 771d22a..2e722e4 100644 --- a/tests/expected/config2.json +++ b/tests/expected/config2.json @@ -14,17 +14,35 @@ "map": [ { "mode": "list", - "autoLayers": true, - "layers": [] + "layers": [ + { + "name": "Locations", + "type": "geojson", + "data": [ + "context_feature_collection", + "geometry" + ], + "popup": "location", + "cluster": true + } + ] }, { "mode": "detail", - "autoLayers": true, - "layers": [] + "layers": [ + { + "name": "Location", + "type": "geojson", + "data": [ + "context_feature", + "geometry" + ], + "popup": "location" + } + ] }, { "mode": "edit", - "autoLayers": true, "layers": [] } ], @@ -75,17 +93,27 @@ } ], "verbose_name": "location", - "verbose_name_plural": "locations" + "verbose_name_plural": "locations", + "geometry_fields": [ + { + "name": "geometry", + "label": "Location", + "type": "geopoint" + } + ] }, "index": { "url": "", "name": "index", "show_in_index": false, - "verbose_name": "test Project" + "verbose_name": "test Project", + "map": { + "mapId": "map", + "layers": [] + } } }, "site_title": "test Project", - "logo": "/icon-192.png", "router": { "base_url": "" }, @@ -95,6 +123,7 @@ "format": "json" } }, + "logo": "/static/app/images/icon-192.png", "material": { "theme": { "primary": "#7500ae", diff --git a/tests/expected/config3.json b/tests/expected/config3.json index ec39432..d38f926 100644 --- a/tests/expected/config3.json +++ b/tests/expected/config3.json @@ -8,60 +8,41 @@ "url": "logout", "name": "logout" }, - "observation": { - "cache": "first_page", - "background_sync": true, - "name": "observation", - "url": "observations", - "list": true, - "form": [ - { - "name": "location", - "label": "Site", - "hint": "The site where the observation was taken", - "type": "select one", - "wq:ForeignKey": "location", - "wq:related_name": "observation_set" - }, - { - "name": "date", - "label": "Date", - "hint": "The date when the observation was taken", - "type": "date" - }, - { - "name": "photo", - "label": "Photo", - "hint": "Photo of the site", - "type": "image" - }, - { - "name": "notes", - "label": "Notes", - "hint": "Field observations and notes", - "type": "text" - } - ], - "verbose_name": "observation", - "verbose_name_plural": "observations" - }, "location": { "cache": "first_page", "background_sync": true, "map": [ { "mode": "list", - "autoLayers": true, - "layers": [] + "layers": [ + { + "name": "Locations", + "type": "geojson", + "data": [ + "context_feature_collection", + "geometry" + ], + "popup": "location", + "cluster": true + } + ] }, { "mode": "detail", - "autoLayers": true, - "layers": [] + "layers": [ + { + "name": "Location", + "type": "geojson", + "data": [ + "context_feature", + "geometry" + ], + "popup": "location" + } + ] }, { "mode": "edit", - "autoLayers": true, "layers": [] } ], @@ -112,17 +93,64 @@ } ], "verbose_name": "location", - "verbose_name_plural": "locations" + "verbose_name_plural": "locations", + "geometry_fields": [ + { + "name": "geometry", + "label": "Location", + "type": "geopoint" + } + ] + }, + "observation": { + "cache": "first_page", + "background_sync": true, + "name": "observation", + "url": "observations", + "list": true, + "form": [ + { + "name": "location", + "label": "Site", + "hint": "The site where the observation was taken", + "type": "select one", + "wq:ForeignKey": "location", + "wq:related_name": "observation_set" + }, + { + "name": "date", + "label": "Date", + "hint": "The date when the observation was taken", + "type": "date" + }, + { + "name": "photo", + "label": "Photo", + "hint": "Photo of the site", + "type": "image" + }, + { + "name": "notes", + "label": "Notes", + "hint": "Field observations and notes", + "type": "text" + } + ], + "verbose_name": "observation", + "verbose_name_plural": "observations" }, "index": { "url": "", "name": "index", "show_in_index": false, - "verbose_name": "test Project" + "verbose_name": "test Project", + "map": { + "mapId": "map", + "layers": [] + } } }, "site_title": "test Project", - "logo": "/icon-192.png", "router": { "base_url": "" }, @@ -132,6 +160,7 @@ "format": "json" } }, + "logo": "/static/app/images/icon-192.png", "material": { "theme": { "primary": "#7500ae", diff --git a/tests/test-deploy.sh b/tests/test-deploy.sh index afb1b77..f2e622e 100755 --- a/tests/test-deploy.sh +++ b/tests/test-deploy.sh @@ -7,7 +7,6 @@ rm -rf output mkdir output if [[ "$TEST_VARIANT" == "postgis" ]]; then - export DJANGO_SETTINGS_MODULE="test_project.settings.prod" GIS_FLAG="--with-gis" elif [[ "$TEST_VARIANT" == "spatialite" ]]; then GIS_FLAG="--with-gis" @@ -31,23 +30,30 @@ else NPM_FLAG="--without-npm" fi; -wq create test_project ./test_project -d test.wq.io -t "test Project" $NPM_FLAG $GIS_FLAG -cd test_project +GUNICORN_FLAG="--with-apache" -# Verify ./deploy.sh works -./deploy.sh 0.0.0 +wq create test_project ./test_project -d test.wq.io -t "test Project" $NPM_FLAG $GIS_FLAG $GUNICORN_FLAG +cd test_project # Load db and verify initial config if [[ "$TEST_VARIANT" == "postgis" ]]; then - sed -i "s/'USER': 'test_project'/'USER': '$USER'/" db/test_project/settings/prod.py - sed -i "s/ALLOWED_HOSTS.*/ALLOWED_HOSTS = ['localhost']/" db/test_project/settings/prod.py + sed -i "s/\"USER\": \"postgres\"/\"USER\": \"$USER\"/" db/test_project/settings/dev.py + sed -i "s/\"HOST\": \"localhost\"/\"HOST\": \"\"/" db/test_project/settings/dev.py + EXPECTED=expected-postgis else + sed -i "s/\# *\"/\"/" db/test_project/settings/dev.py + sed -i "s/\# \}/\}/" db/test_project/settings/dev.py # See https://code.djangoproject.com/ticket/32935 $MANAGE shell -c "import django;django.db.connection.cursor().execute('SELECT InitSpatialMetaData(1);')"; + EXPECTED=expected fi; + +# Verify ./deploy.sh works +./deploy.sh 0.0.0 + $MANAGE migrate $MANAGE dump_config > $OUTPUT/config0.json -$COMPARE expected/config0.json output/config0.json +$COMPARE $EXPECTED/config0.json output/config0.json # Remove example app and verify deploy still works rm -rf db/test_project_survey/ @@ -55,19 +61,19 @@ sed -i 's/"test_project_survey",//' db/test_project/settings/base.py ./deploy.sh 0.0.1 $MANAGE migrate $MANAGE dump_config > $OUTPUT/config1.json -$COMPARE expected/config1.json output/config1.json +$COMPARE $EXPECTED/config1.json output/config1.json # wq addform: Add a single form and verify changed config wq addform -f ../location.csv sed -i "s/class Meta:/def __str__(self):\n return self.name\n\n class Meta:/" db/location/models.py $MANAGE dump_config > $OUTPUT/config2.json -$COMPARE expected/config2.json output/config2.json +$COMPARE $EXPECTED/config2.json output/config2.json # wq addform: Add a second form that references the first wq addform -f ../observation.csv sed -i "s/class Meta:/def __str__(self):\n return '%s on %s' % (self.location, self.date)\n\n class Meta:/" db/observation/models.py $MANAGE dump_config > $OUTPUT/config3.json -$COMPARE expected/config3.json output/config3.json +$COMPARE $EXPECTED/config3.json output/config3.json # Enable anonymous submissions and start webserver sed -i "s/WSGI_APPLICATION/ANONYMOUS_PERMISSIONS = ['location.add_location', 'observation.add_observation']\n\nWSGI_APPLICATION/" db/test_project/settings/base.py @@ -75,14 +81,14 @@ $MANAGE runserver $PORT & sleep 5 # Submit a new site curl -sf http://localhost:$PORT/locations.json > $OUTPUT/locations1.json -$COMPARE expected/locations1.json output/locations1.json +$COMPARE $EXPECTED/locations1.json output/locations1.json curl -sf http://localhost:$PORT/locations.json -d name="Site 1" -d type="water" -d geometry='{"type": "Point", "coordinates": [-93.28, 44.98]}' > /dev/null curl -sf http://localhost:$PORT/locations.geojson > $OUTPUT/locations2.geojson -$COMPARE expected/locations2.geojson output/locations2.geojson +$COMPARE $EXPECTED/locations2.geojson output/locations2.geojson # Submit a new observation curl -sf http://localhost:$PORT/observations.json -F location_id=1 -F date="2015-10-08" -F notes="A cold winter day in Minneapolis" -F photo=@../photo.jpg > /dev/null curl -sf http://localhost:$PORT/observations.json > $OUTPUT/observations1.json sed -i "s/$PORT/8000/" $OUTPUT/observations1.json -$COMPARE expected/observations1.json output/observations1.json +$COMPARE $EXPECTED/observations1.json output/observations1.json diff ../photo.jpg media/observations/photo.jpg diff --git a/wq/create/django_project b/wq/create/django_project index f7c52eb..014f98e 160000 --- a/wq/create/django_project +++ b/wq/create/django_project @@ -1 +1 @@ -Subproject commit f7c52ebd7a9fabb1c5bea972dd6e9772c0da2b44 +Subproject commit 014f98e86f9634318ab81221f5a51110aced1921 diff --git a/wq/create/projects.py b/wq/create/projects.py index f925b44..28e4825 100644 --- a/wq/create/projects.py +++ b/wq/create/projects.py @@ -23,10 +23,10 @@ if os.name == "nt": - NPX_COMMAND = "npx.cmd" + NPM_COMMAND = "npm.cmd" DEPLOY_SCRIPT = "deploy.bat" else: - NPX_COMMAND = "npx" + NPM_COMMAND = "npm" DEPLOY_SCRIPT = "./deploy.sh" @@ -36,7 +36,8 @@ def add_arguments(self, parser): parser.add_argument("--domain", help="Web Domain") parser.add_argument("--title", help="Site Title") parser.add_argument("--with-gis", help="Enable GeoDjango") - parser.add_argument("--with-npm", help="Enable NPM") + parser.add_argument("--with-npm", help="Enable NPM)") + parser.add_argument("--with-gunicorn", help="Enable Gunicorn") parser.add_argument("--wq-create-version", help="wq create version") @@ -51,7 +52,12 @@ def add_arguments(self, parser): @click.option( "--with-npm/--without-npm", default=None, - help="Enable NPM (& Create React App)", + help="Enable NPM (Vite with @wq/rollup-plugin)", +) +@click.option( + "--with-gunicorn/--with-apache", + default=None, + help="Use Gunicorn + Whitenoise instead of Apache WSGI", ) def create( project_name, @@ -60,6 +66,7 @@ def create( title=None, with_gis=None, with_npm=None, + with_gunicorn=None, ): """ Start a new project with wq.app and wq.db. A new Django project will be @@ -74,10 +81,26 @@ def create( See https://wq.io/overview/setup for more tips on getting started with wq. """ - do_create(project_name, destination, domain, title, with_gis, with_npm) + do_create( + project_name, + destination, + domain, + title, + with_gis, + with_npm, + with_gunicorn, + ) -def do_create(project_name, destination, domain, title, with_gis, with_npm): +def do_create( + project_name, + destination, + domain, + title, + with_gis, + with_npm, + with_gunicorn, +): any_prompts = False if project_name is None: @@ -117,7 +140,14 @@ def do_create(project_name, destination, domain, title, with_gis, with_npm): if with_npm is None: any_prompts = True with_npm = click.confirm( - "Enable NPM / Create React App? (Requires Node.js)", + "Enable NPM / Vite + @wq/rollup-plugin? (Requires Node.js)", + default=False, + ) + + if with_gunicorn is None: + any_prompts = True + with_gunicorn = click.confirm( + "Enable Gunicorn + Whitenoise instead of Apache WSGI?", default=False, ) @@ -134,6 +164,7 @@ def do_create(project_name, destination, domain, title, with_gis, with_npm): wq_create_version=VERSION, with_gis=with_gis, with_npm=with_npm, + with_gunicorn=with_gunicorn, ) call_command(StartProjectCommand(), *args, **kwargs) @@ -143,43 +174,47 @@ def do_create(project_name, destination, domain, title, with_gis, with_npm): for dep in freeze.freeze(): print(dep, file=f) + os.remove(os.path.join(path, "app", "README.md")) if with_npm: - shutil.rmtree(os.path.join(path, "app")) + project_static_dir = os.path.join(path, "db", project_name, "static") + os.makedirs(project_static_dir, exist_ok=True) + shutil.move(os.path.join(path, "app"), project_static_dir) subprocess.check_call( - [ - NPX_COMMAND, - "create-react-app", - project_name, - "--template", - "@wq", - ], - cwd=path, + [NPM_COMMAND, "init", "@wq", project_name], cwd=path ) os.rename( os.path.join(path, project_name), os.path.join(path, "app"), ) - for filename in ("index.html", "manifest.json"): - filepath = os.path.join(path, "app", "public", filename) + for filename in ("index.html", "vite.config.js"): + filepath = os.path.join(path, "app", filename) with open(filepath) as f: content = f.read() - content = content.replace("{{ title }}", title) - content = content.replace("{{ project_name }}", project_name) + content = content.replace("Example Project", title) + content = content.replace("project", project_name) with open(filepath, "w") as f: f.write(content) - else: - os.remove(os.path.join(path, "app", "README.md")) + subprocess.check_call( + [NPM_COMMAND, "install"], cwd=os.path.join(path, "app") + ) + + if with_gunicorn: + os.remove(os.path.join(path, "conf", f"{project_name}.conf")) flags = [] if with_gis: flags.append("GIS") if with_npm: flags.append("NPM") + if with_gunicorn: + flags.append("Gunicorn") - if len(flags) == 3: - flag_summary = " with {0}, {1}, and {2} support".format(*flags) + if len(flags) > 2: + flag_summary = " with {first}, and {last} support".format( + first=", ".join(flags[:-1]), last=flags[-1] + ) elif len(flags) == 2: flag_summary = " with {0} and {1} support".format(*flags) elif len(flags) == 1: