├── .gitignore
├── README.md
├── app
├── README.md
├── css
│ └── project_name.css
├── images
│ ├── icon-1024.png
│ └── icon.svg
├── js
│ ├── data
│ │ └── .gitignore
│ └── project_name.js
└── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── conf
└── project_name.conf
├── db
├── manage.py
├── project_name
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings
│ │ ├── .gitignore
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── dev.py
│ │ └── prod.py
│ ├── urls.py
│ └── wsgi.py
└── project_name_survey
│ ├── __init__.py
│ ├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
│ ├── models.py
│ ├── rest.py
│ └── serializers.py
├── deploy.bat
├── deploy.sh
├── media
└── .gitignore
├── runserver.sh
├── version.txt
└── wq.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | venv
3 | htdocs
4 | node_modules
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Django project template for wq framework
2 | =========================================
3 |
4 | This is the recommended Django project template for projects utilizing the [wq framework]. It uses [wq.app] for the front end and [wq.db] as the backend component. This template is meant to be used together with [wq.create]. See wq's [Getting Started] docs for more information.
5 |
6 | When used with the `--with-npm` command for wq create, the app/ folder in this template will replaced with the contents of [wq-vite-template] (via [@wq/create]).
7 |
8 | ### Rationale
9 |
10 | This project template is also useful as an example of how to build a web app with [React] and a [Django REST Framework] backend. It differs from the default Django and vite templates in a few key ways:
11 |
12 | * Key front end files are kept in the `app/` folder, making it easier to customize the generated [installable PWA], and (optionally) to compile the front end with React Native or Expo for distribution on the app stores.
13 | * Because of this separation, the root of the Django project is in `db/` rather than at the top level of the project.
14 | * The root `ReactDOM.render()` call and Redux initialization are handled automatically by [@wq/react] and [@wq/store]. It is not necessary to explicitly define any React components, except to override the default [@wq/material] UI.
15 | * A default Apache2 WSGI configuration is included in `conf/`
16 |
17 | [wq framework]: http://wq.io/
18 | [wq.app]: https://wq.io/wq.app/
19 | [wq.db]: https://wq.io/wq.db/
20 | [wq.create]: https://wq.io/wq.create/
21 | [Getting Started]: https://wq.io/overview/setup
22 |
23 | [wq]: https://wq.io/wq
24 | [@wq/app]: https://wq.io/@wq/app
25 | [wq-vite-template]: https://github.com/wq/wq-vite-template
26 | [@wq/create]: https://wq.io/@wq/create
27 | [@wq/material]: https://wq.io/@wq/material
28 | [@wq/react]: https://wq.io/@wq/react
29 | [@wq/store]: https://wq.io/@wq/store
30 |
31 | [React]: https://reactjs.org/
32 | [Django REST Framework]: http://www.django-rest-framework.org
33 | [installable PWA]: https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Installable_PWAs
34 |
--------------------------------------------------------------------------------
/app/README.md:
--------------------------------------------------------------------------------
1 | This folder is only for use with `wq create --without-npm`. Projects using `wq create --with-npm` will use [@wq/cra-template] instead.
2 |
3 | [@wq/cra-template]: https://github.com/wq/wq.create/tree/master/packages/cra-template
4 |
--------------------------------------------------------------------------------
/app/css/project_name.css:
--------------------------------------------------------------------------------
1 | @import url("./wq.css");
2 |
--------------------------------------------------------------------------------
/app/images/icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wq/wq-django-template/014f98e86f9634318ab81221f5a51110aced1921/app/images/icon-1024.png
--------------------------------------------------------------------------------
/app/images/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
21 |
23 |
41 |
50 |
51 |
53 |
54 |
56 | image/svg+xml
57 |
59 |
60 |
61 |
62 |
63 |
68 |
78 | WQ
90 |
91 |
92 |
--------------------------------------------------------------------------------
/app/js/data/.gitignore:
--------------------------------------------------------------------------------
1 | *.*
2 | # preserve this file, so the directory is created
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/app/js/project_name.js:
--------------------------------------------------------------------------------
1 | import wq from './wq.js';
2 | import config from './data/config.js';
3 | // import config from '/config.js'; // Load directly from wq.db
4 |
5 |
6 | async function init() {
7 | await wq.init(config);
8 | await wq.prefetchAll();
9 | if (config.debug) {
10 | window.wq = wq;
11 | }
12 | }
13 |
14 | init();
15 |
16 | navigator.serviceWorker.register('/service-worker.js');
17 |
--------------------------------------------------------------------------------
/app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wq/wq-django-template/014f98e86f9634318ab81221f5a51110aced1921/app/public/favicon.ico
--------------------------------------------------------------------------------
/app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ title }}
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "{{ project_name }}",
3 | "name": "{{ title }}",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "static/app/images/icon-192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "static/app/images/icon-512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "fullscreen",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/conf/project_name.conf:
--------------------------------------------------------------------------------
1 | # Apache configuration for {{ project_name }}
2 |
3 | ServerName {{ domain }}
4 | DocumentRoot {{ project_directory }}/htdocs/
5 |
6 |
7 | AllowOverride all
8 | Options FollowSymLinks Multiviews
9 | Require all granted
10 |
11 |
12 |
13 | AllowOverride all
14 | Options FollowSymLinks Multiviews
15 | Require all granted
16 |
17 |
18 | Alias /static {{ project_directory }}/htdocs/static
19 | Alias /media {{ project_directory }}/media/
20 | Alias /service-worker.js {{ project_directory }}/htdocs/service-worker.js
21 | Alias /manifest.json {{ project_directory }}/htdocs/manifest.json
22 | Alias /robots.txt {{ project_directory }}/htdocs/robots.txt
23 | Alias /favicon.ico {{ project_directory }}/htdocs/favicon.ico
24 | Alias /icon-180.png {{ project_directory }}/htdocs/icon-180.png
25 | Alias /icon-192.png {{ project_directory }}/htdocs/icon-192.png
26 | Alias /icon-512.png {{ project_directory }}/htdocs/icon-512.png
27 | Alias /icon-1024.png {{ project_directory }}/htdocs/icon-1024.png
28 |
29 | # Uncomment the following line to use a static front page
30 | # AliasMatch ^/$ {{ project_directory }}/htdocs/index.html
31 |
32 | WSGIScriptAlias / {{ project_directory }}/db/{{ project_name }}/wsgi.py
33 | WSGIDaemonProcess {{ project_name }} display-name=%{GROUP} python-home={{project_directory}}/venv python-path={{project_directory}}/db
34 | WSGIProcessGroup {{ project_name }}
35 | WSGIApplicationGroup %{GLOBAL}
36 |
37 |
38 |
39 | Require all granted
40 |
41 |
42 |
43 | ErrorLog ${APACHE_LOG_DIR}/{{ project_name }}-error.log
44 | CustomLog ${APACHE_LOG_DIR}/{{ project_name }}-access.log combined
45 |
46 |
--------------------------------------------------------------------------------
/db/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings.dev")
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | "available on your PYTHONPATH environment variable? Did you "
16 | "forget to activate a virtual environment?"
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/db/project_name/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wq/wq-django-template/014f98e86f9634318ab81221f5a51110aced1921/db/project_name/__init__.py
--------------------------------------------------------------------------------
/db/project_name/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for {{ project_name }} project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{{ project_name }}.settings.prod')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/db/project_name/settings/.gitignore:
--------------------------------------------------------------------------------
1 | dev.py
2 | prod.py
3 |
--------------------------------------------------------------------------------
/db/project_name/settings/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wq/wq-django-template/014f98e86f9634318ab81221f5a51110aced1921/db/project_name/settings/__init__.py
--------------------------------------------------------------------------------
/db/project_name/settings/base.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for {{ project_name }} project.
3 |
4 | Based on the Django 3.2 template, with wq-specific modifications noted as such.
5 | Generated by 'wq create' {{ wq_create_version }}.
6 |
7 | For more information on this file, see
8 | https://docs.djangoproject.com/en/3.2/topics/settings/
9 |
10 | For the full list of settings and their values, see
11 | https://docs.djangoproject.com/en/3.2/ref/settings/
12 |
13 | For more information about wq.db's Django settings see
14 | https://wq.io/wq.db/settings
15 |
16 | """
17 |
18 | from pathlib import Path
19 |
20 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
21 | # wq: extra .parent.parent to account for db/ and settings/ folders
22 | BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent
23 |
24 | # wq: SECRET_KEY, DEBUG, and ALLOWED_HOSTS are defined in dev.py/prod.py
25 |
26 |
27 | # Application definition
28 |
29 | INSTALLED_APPS = [
30 | 'django.contrib.admin',
31 | 'django.contrib.auth',
32 | 'django.contrib.contenttypes',
33 | 'django.contrib.sessions',
34 | 'django.contrib.messages',
35 | 'django.contrib.staticfiles',
36 | {% if not with_gis %}# {% endif %}'django.contrib.gis',
37 | 'rest_framework',
38 |
39 | 'wq.db.rest',
40 | 'wq.db.rest.auth',
41 | 'wq.app',
42 | 'wq.build',
43 |
44 | # Project apps
45 | '{{ project_name }}_survey',
46 | ]
47 |
48 | MIDDLEWARE = [
49 | 'django.middleware.security.SecurityMiddleware',{% if with_gunicorn %}
50 | 'whitenoise.middleware.WhiteNoiseMiddleware',{% endif %}
51 | 'django.contrib.sessions.middleware.SessionMiddleware',
52 | 'django.middleware.common.CommonMiddleware',
53 | 'django.middleware.csrf.CsrfViewMiddleware',
54 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
55 | 'django.contrib.messages.middleware.MessageMiddleware',
56 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
57 | ]
58 |
59 | ROOT_URLCONF = '{{ project_name }}.urls'
60 |
61 | # wq: Leverage wq.db for Django REST Framework defaults
62 | REST_FRAMEWORK = {
63 |
64 | 'DEFAULT_RENDERER_CLASSES': (
65 | 'wq.db.rest.renderers.HTMLRenderer',
66 | 'wq.db.rest.renderers.JSONRenderer',
67 | 'wq.db.rest.renderers.GeoJSONRenderer',
68 | ),
69 |
70 | 'DEFAULT_PAGINATION_CLASS': 'wq.db.rest.pagination.Pagination',
71 | 'PAGE_SIZE': 50,
72 |
73 | 'DEFAULT_PERMISSION_CLASSES': (
74 | 'wq.db.rest.permissions.ModelPermissions',
75 | ),
76 |
77 | 'DEFAULT_FILTER_BACKENDS': (
78 | 'wq.db.rest.filters.FilterBackend',
79 | ),
80 |
81 | 'DEFAULT_CONTENT_NEGOTIATION_CLASS':
82 | 'wq.db.rest.negotiation.ContentNegotiation'
83 | }
84 |
85 | TEMPLATES = [
86 | {
87 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
88 | 'DIRS': [],
89 | 'APP_DIRS': True,
90 | 'OPTIONS': {
91 | 'context_processors': [
92 | 'django.template.context_processors.debug',
93 | 'django.template.context_processors.request',
94 | 'django.contrib.auth.context_processors.auth',
95 | 'django.contrib.messages.context_processors.messages',
96 | ],
97 | },
98 | },
99 | ]
100 |
101 | WSGI_APPLICATION = '{{ project_name }}.wsgi.application'
102 |
103 |
104 | # wq: DATABASES is defined in dev.py/prod.py
105 |
106 | # Password validation
107 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
108 |
109 | AUTH_PASSWORD_VALIDATORS = [
110 | {
111 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
112 | },
113 | {
114 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
115 | },
116 | {
117 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
118 | },
119 | {
120 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
121 | },
122 | ]
123 |
124 |
125 | # Internationalization
126 | # https://docs.djangoproject.com/en/3.2/topics/i18n/
127 |
128 | LANGUAGE_CODE = 'en-us'
129 |
130 | TIME_ZONE = 'UTC'
131 |
132 | USE_I18N = True
133 |
134 | USE_L10N = True
135 |
136 | USE_TZ = True
137 |
138 |
139 | # Static files (CSS, JavaScript, Images)
140 | # https://docs.djangoproject.com/en/3.2/howto/static-files/
141 |
142 | STATIC_URL = '/static/'
143 | STATICFILES_DIRS = [
144 | {% if with_npm %}BASE_DIR / 'db' / '{{ project_name }}' / 'static'{% else %}('app', BASE_DIR / 'app'){% endif %},
145 | ]
146 |
147 | # wq: Configure paths for default project layout
148 | PROJECT_NAME = '{{ title }}'
149 | STATIC_ROOT = BASE_DIR / 'htdocs' / 'static'{% if with_gunicorn %}
150 | WHITENOISE_ROOT = BASE_DIR / 'htdocs'{% endif %}
151 | MEDIA_ROOT = BASE_DIR / 'media'
152 | WQ_APP_TEMPLATE = BASE_DIR / 'htdocs' / 'index.html'
153 | VERSION_TXT = BASE_DIR / 'version.txt'
154 | MEDIA_URL = '/media/'
155 | WQ_CONFIG = {
156 | "logo": "/static/app/images/icon-192.png",
157 | "material": {
158 | "theme": {
159 | "primary": "#7500ae",
160 | "secondary": "#0088bd"
161 | }
162 | },
163 | "map": {
164 | "bounds": [[-180, -70], [180, 70]]
165 | }
166 | }
167 |
168 | # Default primary key field type
169 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
170 |
171 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
172 |
--------------------------------------------------------------------------------
/db/project_name/settings/dev.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import mimetypes
3 | from .base import *
4 |
5 |
6 | # SECURITY WARNING: keep the secret key used in production secret!
7 | SECRET_KEY = "{{ secret_key }}"
8 |
9 | # SECURITY WARNING: don't run with debug turned on in production!
10 | DEBUG = True
11 |
12 | ALLOWED_HOSTS = ["localhost"]{% if with_npm %}
13 | CSRF_TRUSTED_ORIGINS = ["http://localhost:5173"]
14 | {% else %}
15 | # wq: Determine if we are running off django's testing server
16 | DEBUG_WITH_RUNSERVER = "manage.py" in sys.argv[0]
17 |
18 | if DEBUG_WITH_RUNSERVER:
19 | WQ_CONFIG_FILE = BASE_DIR / "app" / "js" / "data" / "config.js"{% endif %}
20 |
21 | # Database
22 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases
23 | DATABASES = {
24 | "default": {
25 | "ENGINE": {% if with_gis %}"django.contrib.gis.db.backends.postgis"{% else %}"django.db.backends.postgresql"{% endif %},
26 | "NAME": "{{ project_name }}",
27 | "USER": "postgres",
28 | "PASSWORD": "",
29 | "HOST": "localhost",
30 | "PORT": "",
31 | },
32 | # To use sqlite:
33 | # "default": {
34 | # "ENGINE": {% if with_gis %}"django.contrib.gis.db.backends.spatialite"{% else %}"django.db.backends.sqlite3"{% endif %},
35 | # "NAME": BASE_DIR / "conf" / "{{ project_name }}.sqlite3",
36 | # },
37 | }
38 |
39 | try:
40 | # Try to create dev database in container
41 | import psycopg2
42 |
43 | conn = psycopg2.connect(
44 | "host={HOST} user={USER}".format(**DATABASES["default"])
45 | )
46 | conn.set_session(autocommit=True)
47 | conn.cursor().execute(
48 | "CREATE DATABASE {NAME}".format(**DATABASES["default"])
49 | )
50 | except Exception:
51 | pass
52 |
--------------------------------------------------------------------------------
/db/project_name/settings/prod.py:
--------------------------------------------------------------------------------
1 | from .base import *
2 |
3 | {% if with_gunicorn %}
4 | import dj_database_url
5 | import os
6 |
7 |
8 | # SECURITY WARNING: keep the secret key used in production secret!
9 | SECRET_KEY = os.getenv("SECRET_KEY")
10 |
11 | # SECURITY WARNING: don't run with debug turned on in production!
12 | DEBUG = True if os.getenv("DEBUG") else False
13 |
14 | ALLOWED_HOSTS = [os.getenv("WEBSITE_HOSTNAME") or "localhost"]
15 | CSRF_TRUSTED_ORIGINS = [f"https://{host}" for host in ALLOWED_HOSTS]
16 |
17 |
18 | # Database
19 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases
20 | DATABASES = {"default": dj_database_url.config()}
21 |
22 | STORAGES = {
23 | "default": {
24 | # e.g. "storages.backends.s3.S3Storage"
25 | "BACKEND": "django.core.files.storage.FileSystemStorage",
26 | },
27 | "staticfiles": {
28 | "BACKEND": "whitenoise.storage.CompressedStaticFilesStorage",
29 | },
30 | }
31 | {% else %}
32 | # SECURITY WARNING: keep the secret key used in production secret!
33 | SECRET_KEY = "{{ secret_key }}"
34 |
35 | # SECURITY WARNING: don't run with debug turned on in production!
36 | DEBUG = False
37 |
38 | # wq: Determine if we are running off django's testing server
39 | DEBUG_WITH_RUNSERVER = False
40 |
41 | ALLOWED_HOSTS = ["{{ domain }}"]
42 |
43 |
44 | # Database
45 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases
46 |
47 | DATABASES = {
48 | "default": {
49 | {% if with_gis %}"ENGINE": "django.contrib.gis.db.backends.postgis",
50 | {% else %}"ENGINE": "django.db.backends.postgresql",
51 | # To enable GeoDjango:
52 | # "ENGINE": "django.contrib.gis.db.backends.postgis",
53 | {% endif %}"NAME": "{{ project_name }}",
54 | "USER": "{{ project_name }}",
55 | "PASSWORD": "",
56 | "HOST": "",
57 | "PORT": "",
58 | }
59 | }{% endif %}
60 |
--------------------------------------------------------------------------------
/db/project_name/urls.py:
--------------------------------------------------------------------------------
1 | """{{ project_name }} URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/3.2/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | import os
17 | from django.contrib import admin
18 | from django.urls import path{% if not with_npm %}
19 | from django.conf import settings
20 | from django.conf.urls.static import static{% endif %}
21 | from wq.db import rest
22 |
23 |
24 | urlpatterns = [
25 | path('admin/', admin.site.urls),
26 | path('', rest.router.urls),
27 | ]
28 | {% if not with_npm %}
29 | if settings.DEBUG_WITH_RUNSERVER:
30 |
31 | # To use django-media-thumbnailer
32 | # urlpatterns.append(url('^media/', include('dmt.urls')))
33 |
34 | urlpatterns += static('/media/', document_root=settings.MEDIA_ROOT)
35 |
36 | # after building...
37 | urlpatterns += static(
38 | '/', document_root=os.path.join(settings.BASE_DIR, 'htdocs')
39 | ){% endif %}
40 |
--------------------------------------------------------------------------------
/db/project_name/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for {{ project_name }} project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{{ project_name }}.settings.prod')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/db/project_name_survey/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wq/wq-django-template/014f98e86f9634318ab81221f5a51110aced1921/db/project_name_survey/__init__.py
--------------------------------------------------------------------------------
/db/project_name_survey/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings{% if with_gis %}
2 | import django.contrib.gis.db.models.fields{% endif %}
3 | from django.db import migrations, models
4 | import django.db.models.deletion
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='Category',
18 | fields=[
19 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('name', models.CharField(max_length=255)),
21 | ('description', models.TextField(blank=True, null=True)),
22 | ],
23 | options={
24 | 'verbose_name_plural': 'categories',
25 | },
26 | ),
27 | migrations.CreateModel(
28 | name='Observation',
29 | fields=[
30 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
31 | ('date', models.DateField(blank=True, help_text='The date when the observation was taken', null=True, verbose_name='Date')),{% if with_gis %}
32 | ('geometry', django.contrib.gis.db.models.fields.PointField(help_text='The location of the observation', srid=4326, verbose_name='Location')),{% endif %}
33 | ('photo', models.ImageField(blank=True, help_text='Photo of the observation', null=True, upload_to='observations', verbose_name='Photo')),
34 | ('notes', models.TextField(blank=True, help_text='Field observations and notes', null=True, verbose_name='Notes')),
35 | ('category', models.ForeignKey(blank=True, help_text='Observation type', null=True, on_delete=django.db.models.deletion.PROTECT, to='{{ project_name }}_survey.category', verbose_name='Category')),
36 | ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='submitted by')),
37 | ],
38 | options={
39 | 'verbose_name_plural': 'observations',
40 | 'ordering': ['-date'],
41 | },
42 | ),
43 | ]
44 |
--------------------------------------------------------------------------------
/db/project_name_survey/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wq/wq-django-template/014f98e86f9634318ab81221f5a51110aced1921/db/project_name_survey/migrations/__init__.py
--------------------------------------------------------------------------------
/db/project_name_survey/models.py:
--------------------------------------------------------------------------------
1 | {% if with_gis %}from django.contrib.gis.db import models
2 | {% else %}from django.db import models
3 | {% endif %}from wq.db.rest.models import LabelModel
4 | from django.conf import settings
5 |
6 |
7 | """
8 | Example survey app for {{ project_name }}. The models below are examples, and
9 | are not required for wq to work. Feel free to modify, rewrite, or delete and
10 | replace this folder with a completely new Django app.
11 |
12 | https://wq.io/guides/describe-your-data-model
13 | """
14 |
15 |
16 | class Category(LabelModel):
17 | name = models.CharField(max_length=255)
18 | description = models.TextField(null=True, blank=True)
19 |
20 | wq_label_template = "{% templatetag openvariable %}name{% templatetag closevariable %}"
21 |
22 | class Meta:
23 | verbose_name_plural = "categories"
24 |
25 |
26 | class Observation(LabelModel):
27 | user = models.ForeignKey(
28 | settings.AUTH_USER_MODEL,
29 | on_delete=models.PROTECT,
30 | null=True,
31 | blank=True,
32 | verbose_name="submitted by",
33 | )
34 | date = models.DateField(
35 | null=True,
36 | blank=True,
37 | verbose_name="Date",
38 | help_text="The date when the observation was taken",
39 | )
40 | category = models.ForeignKey(
41 | Category,
42 | on_delete=models.PROTECT,
43 | null=True,
44 | blank=True,
45 | verbose_name="Category",
46 | help_text="Observation type",
47 | ){% if with_gis %}
48 | geometry = models.PointField(
49 | srid=4326,
50 | verbose_name="Location",
51 | help_text="The location of the observation",
52 | ){% endif %}
53 | photo = models.ImageField(
54 | upload_to="observations",
55 | null=True,
56 | blank=True,
57 | verbose_name="Photo",
58 | help_text="Photo of the observation",
59 | )
60 | notes = models.TextField(
61 | null=True,
62 | blank=True,
63 | verbose_name="Notes",
64 | help_text="Field observations and notes",
65 | )
66 |
67 | wq_label_template = "{% templatetag openvariable %}date{% templatetag closevariable %}"
68 |
69 | class Meta:
70 | verbose_name_plural = "observations"
71 | ordering = ['-date']
72 |
--------------------------------------------------------------------------------
/db/project_name_survey/rest.py:
--------------------------------------------------------------------------------
1 | from wq.db import rest
2 | from .models import Category, Observation
3 | from .serializers import ObservationSerializer
4 |
5 |
6 | rest.router.register_model(
7 | Category,
8 | icon="config",
9 | description="Manage available categories",
10 | section="Admin",
11 | order=100,
12 | show_in_index="can_change",
13 | fields="__all__",
14 | cache="all",
15 | background_sync=False,
16 | )
17 |
18 | rest.router.register_model(
19 | Observation,
20 | icon="list",
21 | description="View and submit photos{% if with_gis %} on map{% endif %}",
22 | section="Contributions",
23 | order=1,
24 | serializer=ObservationSerializer,
25 | cache="first_page",
26 | background_sync=True,{% if with_gis %}
27 | map=[{
28 | "mode": "list",
29 | "autoLayers": True,
30 | "layers": [],
31 | }, {
32 | "mode": "detail",
33 | "autoLayers": True,
34 | "layers": [],
35 | }, {
36 | "mode": "edit",
37 | "layers": [],
38 | }],
39 | {% endif %}
40 | ){% if with_gis %}
41 |
42 | rest.router.add_page(
43 | "index",
44 | dict(
45 | url="",
46 | icon="directions",
47 | verbose_name="Map",
48 | description="Project overview map",
49 | section="Contributions",
50 | order=0,
51 | map={
52 | "mapId": "map",
53 | "layers": [
54 | {
55 | "name": "Observations",
56 | "type": "geojson",
57 | "url": "/observations.geojson",
58 | "popup": "observation",
59 | }
60 | ]
61 | },
62 | ),
63 | ){% endif %}
64 |
--------------------------------------------------------------------------------
/db/project_name_survey/serializers.py:
--------------------------------------------------------------------------------
1 | from wq.db.rest.serializers import ModelSerializer
2 | from rest_framework import serializers
3 | from .models import Observation
4 |
5 |
6 | class ObservationSerializer(ModelSerializer):
7 | user = serializers.HiddenField(
8 | default=serializers.CurrentUserDefault()
9 | )
10 |
11 | class Meta:
12 | fields = "__all__"
13 | model = Observation
14 | wq_field_config = {
15 | "notes": {"multiline": True},
16 | }
17 |
--------------------------------------------------------------------------------
/deploy.bat:
--------------------------------------------------------------------------------
1 | python db/manage.py deploy %1
2 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | db/manage.py deploy $1
4 | touch db/{{project_name}}/wsgi.py
5 |
--------------------------------------------------------------------------------
/media/.gitignore:
--------------------------------------------------------------------------------
1 | *.*
2 | # preserve this file, so the directory is created
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/runserver.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 | . venv/bin/activate
5 | echo "Starting Django..."
6 | db/manage.py runserver &
7 | {% if with_npm %}
8 | sleep 10;
9 | cd app
10 | echo "Starting NPM..."
11 | npm start
12 | {% endif %}
13 |
--------------------------------------------------------------------------------
/version.txt:
--------------------------------------------------------------------------------
1 | 0.0.0
--------------------------------------------------------------------------------
/wq.yml:
--------------------------------------------------------------------------------
1 | # Update version.txt & JS module
2 | setversion:
3 | filename: version.txt {% if with_npm %}
4 | package: app/package.json{% endif %}
5 |
6 | # Generate config.js
7 | dump_config:
8 | format: esm{% if with_npm %}
9 | filename: db/{{ project_name }}/static/app/js/data/config.js{% else %}
10 | filename: app/js/data/config.js{% endif %}
11 |
12 | # Generate web icons
13 | icons:
14 | source: {% if with_npm %}db/{{ project_name }}/static/{% endif %}app/images/icon-1024.png
15 | filename: {% if with_npm %}db/{{ project_name }}/static/{% endif %}app/images/icon-{size}.png
16 | size: web
17 |
18 | # Move public files to root
19 | movefiles:
20 | source: htdocs/static/app/public/*.*
21 | dest: htdocs/
22 |
23 | # Generate Service Worker
24 | serviceworker:
25 | output: ./htdocs/service-worker.js
26 | timeout: 400
27 | cache:
28 | - /
29 | - /manifest.json
30 | - /favicon.ico
31 | - /icon-*.png
32 | - /static/app/js/*.js
33 | - /static/app/js/data/*.js
34 | - /static/app/css/*.css
35 | exclude:
36 | - wq.dev.js
37 |
--------------------------------------------------------------------------------