├── .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 | --------------------------------------------------------------------------------