diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 928cf83..b77f64b 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 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 47e9b6c..0000000 Binary files a/packages/cra-template/template/public/favicon.ico and /dev/null differ 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 c1c7d62..0000000 Binary files a/packages/cra-template/template/public/icon-1024.png and /dev/null differ 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/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..be96c1a 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,20 +30,24 @@ 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': 'test_project'/'USER': '$USER'/" db/test_project/settings/dev.py 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);')"; 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 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: