diff --git a/README.md b/README.md index e0880c5..a6b695b 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,9 @@ sudo apt install gdal-bin ``` ### PostGIS -We are using Postgres/PostGIS for the database backend. For convenience, we have included a Docker compose file that you may use to run a PostGIS container. With Docker installed on your local computer, run the following command from the project root directory in order to start up PostGIS: +We are using Postgres/PostGIS for the database backend. You can use a standard docker image like https://registry.hub.docker.com/r/postgis/postgis or you can build your own image using the [Docker compose file](docker-compose.yml): + +With Docker installed on your local computer, run the following command from the project root directory in order to start up PostGIS: ``` docker-compose up @@ -110,13 +112,13 @@ You can override PostGIS and pgAdmin configuration prior to running `docker-comp The next step is to run through some Jupyter Notebooks that will import OpenStreetMap data into PostGIS. First, [download some OpenStreetMap data](http://download.geofabrik.de/) in the \*.shp.zip format. Then create a the following directory: -''' +```bash mkdir -p notebooks/data/OSM -''' +``` and extract the downloaded archive into this location. -Refer to the README in the notebooks folder to run through the Notebooks. +Refer to the [README](notebooks/README.md) in the notebooks folder to run through the Notebooks. ## Environment If you wish to keep the project's python environment separate from your global environment, you should create a [virtual environment](https://docs.python.org/3/library/venv.html) @@ -129,8 +131,17 @@ source env/bin/activate ### Python dependencies Use [pip](https://pip.pypa.io/en/stable/installing/) to install the dependencies: + +* **Local development** + +``` +pip install -r requirements/local.txt +``` + +* **Production** + ``` -pip install -r requirements.txt +pip install -r requirements/production.txt ``` ## Running the server @@ -141,6 +152,18 @@ Move into the Project Folder: cd platform ``` +## Tunning settings + +Set the file `platform/.env` You can take a look [platform/env.template](platform/env.template) +``` +# Main Database: +DATABASE_URL=postgres://postgres:changeme@postgres:5432/suds + +# Open Street Maps Database +DATABASE_OSM_URL=postgres://postgres:changeme@postgres:5432/openstreetmap + +``` + ### Migrations Before you can run the project, you will need to set up the database by running the migrations: @@ -171,6 +194,8 @@ It may warn you if you use a password that is similar to your user name, or if i You can run the server with +* **Local development** + ``` python manage.py runserver ``` @@ -179,6 +204,17 @@ The server will now tell you that it's running on http://127.0.0.1:8000/ You can connect to the admin interface at http://127.0.0.1:8000/admin with your newly created superuser account. +Note that if you log in to the Django for the very first time, you will see a message that says "Verify Your E-mail Address". As django-yubin don't send any email until configure its [django command](https://django-yubin.readthedocs.io/en/latest/queue.html#command-extensions) you won't receive any verification email. You can verify the email via admin reading the email at http://127.0.0.1:8000/admin/django_yubin/message/mail/1/ and continuing the process or clicking on `Verified` checkbox at http://127.0.0.1:8000/admin/account/emailaddress/1/change/ + + +* **Production** + +``` +export DJANGO_SETTINGS_MODULE=core.settings.production +python manage.py runserver +``` + + ## Support * See [CONTRIBUTING.md](CONTRIBUTING.md) diff --git a/platform/core/asgi.py b/platform/core/asgi.py index 2bdce06..0c167b2 100644 --- a/platform/core/asgi.py +++ b/platform/core/asgi.py @@ -1,16 +1,40 @@ """ -ASGI config for sustainable_urban_design_space project. +ASGI config for Sustainable Urban design project. It exposes the ASGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ -""" +https://docs.djangoproject.com/en/dev/howto/deployment/asgi/ +""" import os +import sys +from pathlib import Path from django.core.asgi import get_asgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") +# This allows easy placement of apps within the interior +# main directory. +ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent +sys.path.append(str(ROOT_DIR / "main")) + +# If DJANGO_SETTINGS_MODULE is unset, default to the local settings +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + +# This application object is used by any ASGI server configured to use this file. +django_application = get_asgi_application() +# Apply ASGI middleware here. +# from helloworld.asgi import HelloWorldApplication +# application = HelloWorldApplication(application) + +# Import websocket application here, so apps from django_application are loaded first +from core.websocket import websocket_application # noqa isort:skip + -application = get_asgi_application() +async def application(scope, receive, send): + if scope["type"] == "http": + await django_application(scope, receive, send) + elif scope["type"] == "websocket": + await websocket_application(scope, receive, send) + else: + raise NotImplementedError(f"Unknown scope type {scope['type']}") diff --git a/platform/core/settings.py b/platform/core/settings.py deleted file mode 100644 index 5b27f16..0000000 --- a/platform/core/settings.py +++ /dev/null @@ -1,182 +0,0 @@ -""" -Django settings for sustainable_urban_design_space project. - -Generated by 'django-admin startproject' using Django 3.0.5. - -For more information on this file, see -https://docs.djangoproject.com/en/3.0/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/3.0/ref/settings/ -""" - -import os - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - -# Custom User Model -AUTH_USER_MODEL = "users.User" - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "-o=7gdw2jx$f-eo+63bqq3kbdy(#%vba2y8cj$u2l4=!b2xznz" - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = ["*"] - - -# Application definition - -DJANGO_APPS = [ - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.gis", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", - "django.contrib.sites", -] - -DJANGO_PACKAGE_APPS = [ - "allauth", - "allauth.account", - "allauth.socialaccount", - "allauth.socialaccount.providers.github", - "corsheaders", - "crispy_forms", - "mptt", - "taggit", -] - -PROJECT_APPS = [ - "places.apps.PlacesConfig", - "front_page", - "maps", - "openstreetmap", - "patterns", - "projects", - "resources", - "users", -] - -INSTALLED_APPS = DJANGO_APPS + DJANGO_PACKAGE_APPS + PROJECT_APPS - -MIDDLEWARE = [ - "corsheaders.middleware.CorsMiddleware", - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.middleware.locale.LocaleMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", -] - -ROOT_URLCONF = "core.urls" - -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [os.path.join(BASE_DIR, "templates")], - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - ], - }, - }, -] - -WSGI_APPLICATION = "core.wsgi.application" - - -# Database -# https://docs.djangoproject.com/en/3.0/ref/settings/#databases - -DATABASES = { - "default": { - "ENGINE": "django.contrib.gis.db.backends.postgis", - "NAME": "suds", - "USER": "postgres", - "PASSWORD": "changeme", - "HOST": "127.0.0.1", - "PORT": "5432", - }, - "openstreetmap": { - "ENGINE": "django.db.backends.postgresql", - "NAME": "openstreetmap", - "USER": "postgres", - "PASSWORD": "changeme", - "HOST": "127.0.0.1", - "PORT": "5432", - }, -} - - -# Password validation -# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", - }, - {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",}, - {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",}, - {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",}, -] - -AUTHENTICATION_BACKENDS = ( - "django.contrib.auth.backends.ModelBackend", - "allauth.account.auth_backends.AuthenticationBackend", -) - -# DO NOT TOUCH THIS -SITE_ID = 1 - - -# Internationalization -# https://docs.djangoproject.com/en/3.0/topics/i18n/ - -LOGIN_REDIRECT_URL = "/" - -LANGUAGE_CODE = "en" - -TIME_ZONE = "UTC" - -# Translations -USE_I18N = True - -USE_L10N = True - -LANGUAGES = ( - ("en", u"English"), - ("zh-hans", u"简体中文"), - ("de", u"German"), -) - -USE_TZ = True - -# Email Stuff For Django-AllAuth -EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.0/howto/static-files/ - -STATIC_URL = "/static/" -STATIC_ROOT = os.path.join(BASE_DIR, "static") - -MEDIA_ROOT = os.path.join(BASE_DIR, "media") -MEDIA_URL = "/media/" - -CORS_ORIGIN_ALLOW_ALL = True diff --git a/platform/core/settings/__init__.py b/platform/core/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/platform/core/settings/base.py b/platform/core/settings/base.py new file mode 100644 index 0000000..b2b2a15 --- /dev/null +++ b/platform/core/settings/base.py @@ -0,0 +1,334 @@ +""" +Django settings for sustainable_urban_design_space project. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" + +from pathlib import Path + +import environ + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent +ROOT_DIR = BASE_DIR.parent + +env = environ.Env() + +READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=True) +if READ_DOT_ENV_FILE: + # OS environment variables take precedence over variables from .env + env_file = Path(ROOT_DIR, ".env").resolve(strict=True) + print("Loading : {}".format(env_file)) + env.read_env(env_file) + print("The .env file has been loaded. See base.py for more information") + +# GENERAL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#debug +DEBUG = env.bool("DJANGO_DEBUG", False) + +# Local time zone. Choices are +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# though not all of them may be available with every OS. +# In Windows, this must be set to your system time zone. +TIME_ZONE = env("TIME_ZONE", default="UTC") + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz +USE_TZ = env.bool("USE_TZ", default=True) + +# https://docs.djangoproject.com/en/dev/ref/settings/#language-code +LANGUAGE_CODE = env("LANGUAGE_CODE", default="en") +LANGUAGES = ( + ("en", "English"), + ("zh-hans", "简体中文"), + ("de", "German"), +) + +# https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths +LOCALE_PATHS = [str(ROOT_DIR / "locale")] + +# Internationalization +# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n +USE_I18N = env.bool("USE_I18N", default=True) + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n +USE_L10N = env.bool("USE_L10N", default=True) +USE_THOUSAND_SEPARATOR = env.bool("USE_THOUSAND_SEPARATOR", default=True) + +# DATABASES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#databases +DATABASES = {"default": env.db("DATABASE_URL", default="postgres:///main")} +DATABASES["default"]["ATOMIC_REQUESTS"] = True +DATABASES["default"]["ENGINE"] = "django.contrib.gis.db.backends.postgis" + +if env("DATABASE_OSM_URL", default=""): + DATABASES["openstreetmap"] = env.db("DATABASE_OSM_URL") + DATABASES["openstreetmap"]["ATOMIC_REQUESTS"] = True + DATABASES["openstreetmap"]["ENGINE"] = "django.contrib.gis.db.backends.postgis" + +# URLS +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf +ROOT_URLCONF = "core.urls" + +# https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application +WSGI_APPLICATION = "core.wsgi.application" + +# Application definition +DJANGO_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.gis", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.sites", +] + +DJANGO_PACKAGE_APPS = [ + "allauth", + "allauth.account", + "allauth.socialaccount", + "allauth.socialaccount.providers.github", + "corsheaders", + "crispy_forms", + "mptt", + "taggit", +] + +PROJECT_APPS = [ + "places.apps.PlacesConfig", + "front_page", + "maps", + "openstreetmap", + "patterns", + "projects", + "resources", + "users", +] +# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps +INSTALLED_APPS = DJANGO_APPS + DJANGO_PACKAGE_APPS + PROJECT_APPS + + +# MIGRATIONS +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#migration-modules +# MIGRATION_MODULES = {"sites": "core.contrib.sites.migrations"} + + +# AUTHENTICATION +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends +AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", + "allauth.account.auth_backends.AuthenticationBackend", +] + +# Custom User Model +# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model +AUTH_USER_MODEL = "users.User" + +# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url +LOGIN_REDIRECT_URL = "my_profile_view" + +# https://docs.djangoproject.com/en/dev/ref/settings/#login-url +LOGIN_URL = "account_login" + +# PASSWORDS +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers +PASSWORD_HASHERS = [ + # https://docs.djangoproject.com/en/dev/topics/auth/passwords/#using-argon2-with-django + "django.contrib.auth.hashers.Argon2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", + "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", +] +# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators +AUTH_PASSWORD_VALIDATORS = [ + {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, + {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, +] + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ + +# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = env("DJANGO_SECRET_KEY", default="-o=7gdw2jx$f-eo+63bqq3kbdy(#%vba2y8cj$u2l4=!b2xznz") + +# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts +ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["localhost"]) + + +# MIDDLEWARE +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#middleware +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "corsheaders.middleware.CorsMiddleware", + # "whitenoise.middleware.WhiteNoiseMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.common.BrokenLinkEmailsMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +# STATIC +# ------------------------------------------------------------------------------ + +# https://docs.djangoproject.com/en/dev/ref/settings/#static-root +STATIC_ROOT = env("STATIC_ROOT", default=str(ROOT_DIR / "staticfiles")) + +# https://docs.djangoproject.com/en/dev/ref/settings/#static-url +STATIC_URL = env("STATIC_URL", default="/static/") + +# https://docs.djangoproject.com/en/dev/ref/settings/#staticfiles-dirs +STATICFILES_DIRS = env.list("STATICFILES_DIRS", default=[]) + +# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders +STATICFILES_FINDERS = env.list("STATICFILES_FINDERS", default=[ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", +]) + + +# MEDIA +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#media-root +MEDIA_ROOT = env("MEDIA_ROOT", default=str(BASE_DIR / "media")) + +# https://docs.djangoproject.com/en/dev/ref/settings/#media-url +MEDIA_URL = env("MEDIA_URL", default="/media/") + +# TEMPLATES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#templates +TEMPLATES = [ + { + # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND + "BACKEND": "django.template.backends.django.DjangoTemplates", + # https://docs.djangoproject.com/en/dev/ref/settings/#dirs + "DIRS": [str(ROOT_DIR / "templates")], + "APP_DIRS": True, + "OPTIONS": { + "debug": DEBUG, + # "loaders": ["django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader"], + # https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + # Stuff + "users.context_processors.allauth_settings", + ], + }, + } +] + + +# https://docs.djangoproject.com/en/dev/ref/settings/#form-renderer +# FORM_RENDERER = env("FORM_RENDERER", default="django.forms.renderers.TemplatesSetting") + +# http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs +# CRISPY_TEMPLATE_PACK = env("CRISPY_TEMPLATE_PACK", default="bootstrap5") +# CRISPY_ALLOWED_TEMPLATE_PACKS = env("CRISPY_ALLOWED_TEMPLATE_PACKS", default="bootstrap5") + +# FIXTURES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs +FIXTURE_DIRS = (str(BASE_DIR / "fixtures"),) + +# https://docs.djangoproject.com/en/dev/ref/settings/#site-id +SITE_ID = env("SITE_ID", default=1) + +# SECURITY +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly +SESSION_COOKIE_HTTPONLY = True +# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly +CSRF_COOKIE_HTTPONLY = True +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter +SECURE_BROWSER_XSS_FILTER = True +# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options +X_FRAME_OPTIONS = "DENY" + + +# EMAIL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend +EMAIL_BACKEND = env("DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend") + +# https://docs.djangoproject.com/en/dev/ref/settings/#email-timeout +EMAIL_TIMEOUT = env("EMAIL_TIMEOUT", default=5) + +# ADMIN +# ------------------------------------------------------------------------------ +# Django Admin URL. +ADMIN_URL = env("ADMIN_URL", default="admin/") +# https://docs.djangoproject.com/en/dev/ref/settings/#admins +ADMINS = env.list("ADMINS", default=[("""Brylie Oxley""", "info@sustainableurbandesign.space")]) +# https://docs.djangoproject.com/en/dev/ref/settings/#managers +MANAGERS = ADMINS + + +# LOGGING +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#logging +# See https://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s " + "%(process)d %(thread)d %(message)s" + } + }, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + } + }, + "root": {"level": "INFO", "handlers": ["console"]}, +} + + +# django-allauth +# ------------------------------------------------------------------------------ +ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True) +# https://django-allauth.readthedocs.io/en/latest/configuration.html +ACCOUNT_AUTHENTICATION_METHOD = "username" +# https://django-allauth.readthedocs.io/en/latest/configuration.html +ACCOUNT_EMAIL_REQUIRED = True +# https://django-allauth.readthedocs.io/en/latest/configuration.html +ACCOUNT_EMAIL_VERIFICATION = "mandatory" +# https://django-allauth.readthedocs.io/en/latest/configuration.html +ACCOUNT_ADAPTER = "users.adapters.AccountAdapter" +# https://django-allauth.readthedocs.io/en/latest/forms.html +ACCOUNT_FORMS = {"signup": "users.forms.UserSignupForm"} +# https://django-allauth.readthedocs.io/en/latest/configuration.html +SOCIALACCOUNT_ADAPTER = "users.adapters.SocialAccountAdapter" +# https://django-allauth.readthedocs.io/en/latest/forms.html +SOCIALACCOUNT_FORMS = {"signup": "users.forms.UserSocialSignupForm"} diff --git a/platform/core/settings/local.py b/platform/core/settings/local.py new file mode 100644 index 0000000..855ce43 --- /dev/null +++ b/platform/core/settings/local.py @@ -0,0 +1,68 @@ +from .base import * # noqa +from .base import env + +# GENERAL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#debug +DEBUG = True +# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +SECRET_KEY = env( + "DJANGO_SECRET_KEY", + default="9evUZxCSB1fvuHJ9inWGgbgHcCmEM34uxibbSICSWASaOpIpURSQgdxdFnFr7H8z", +) +# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts +ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] + +# CACHES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#caches +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "", + } +} + +# TEMPLATES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#templates +TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG # noqa F405 + + +# EMAIL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend +INSTALLED_APPS += ["django_yubin"] # noqa F405 +EMAIL_BACKEND = env("DJANGO_EMAIL_BACKEND", default="django_yubin.smtp_queue.EmailBackend") + +# +# ToDo: There are some warnings about the following line: +# (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField' +# To avoid them, you can uncomment following line: +# DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + + +# WhiteNoise +# ------------------------------------------------------------------------------ +# http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development +# INSTALLED_APPS = ["whitenoise.runserver_nostatic",] + INSTALLED_APPS # noqa F405 + + +# django-debug-toolbar +# ------------------------------------------------------------------------------ +# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites +INSTALLED_APPS += ["debug_toolbar"] # noqa F405 +# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware +MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa F405 +# https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config +DEBUG_TOOLBAR_CONFIG = { + "DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"], + "SHOW_TEMPLATE_CONTEXT": True, +} +# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips +INTERNAL_IPS = ["127.0.0.1", "10.0.2.2"] + +# django-extensions +# ------------------------------------------------------------------------------ +# https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration +INSTALLED_APPS += ["django_extensions"] # noqa F405 diff --git a/platform/core/settings/production.py b/platform/core/settings/production.py new file mode 100644 index 0000000..8ad066e --- /dev/null +++ b/platform/core/settings/production.py @@ -0,0 +1,178 @@ +import logging + +import sentry_sdk +# from sentry_sdk.integrations.celery import CeleryIntegration +from sentry_sdk.integrations.django import DjangoIntegration +from sentry_sdk.integrations.logging import LoggingIntegration +from sentry_sdk.integrations.redis import RedisIntegration + +from .base import * # noqa +from .base import env + +# GENERAL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +# SECRET_KEY = env("DJANGO_SECRET_KEY") +# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts +ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["sustainableurbandesign.space"]) + + +# DATABASES +# ------------------------------------------------------------------------------ +DATABASES["default"] = env.db("DATABASE_URL") # noqa F405 +DATABASES["default"]["ATOMIC_REQUESTS"] = True # noqa F405 +DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa F405 + + +# CACHES +# ------------------------------------------------------------------------------ +if env("REDIS_URL", default=None) is not None: + CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": env("REDIS_URL", None), + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + # Mimicing memcache behavior. + # https://github.com/jazzband/django-redis#memcached-exceptions-behavior + "IGNORE_EXCEPTIONS": True, + }, + } + } + +# SECURITY +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect +SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True) +# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure +SESSION_COOKIE_SECURE = True +# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure +CSRF_COOKIE_SECURE = True +# https://docs.djangoproject.com/en/dev/topics/security/#ssl-https +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds +# TODO: set this to 60 seconds first and then to 518400 once you prove the former works +SECURE_HSTS_SECONDS = 60 +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains +SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool( + "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True +) +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload +SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True) +# https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff +SECURE_CONTENT_TYPE_NOSNIFF = env.bool( + "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True +) + +# STATIC +# ------------------------ + + +# MEDIA +# ------------------------------------------------------------------------------ + +# EMAIL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email +DEFAULT_FROM_EMAIL = env( + "DJANGO_DEFAULT_FROM_EMAIL", + default="Sustainable Urban design ", +) +# https://docs.djangoproject.com/en/dev/ref/settings/#server-email +SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL) + +# https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix +EMAIL_SUBJECT_PREFIX = env( + "DJANGO_EMAIL_SUBJECT_PREFIX", + default="[Sustainable Urban design]", +) + +# ADMIN +# ------------------------------------------------------------------------------ +# Django Admin URL regex. +# ADMIN_URL = env("DJANGO_ADMIN_URL") + +# django-compressor +# ------------------------------------------------------------------------------ +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_ENABLED +COMPRESS_ENABLED = env.bool("COMPRESS_ENABLED", default=True) +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE +COMPRESS_STORAGE = "compressor.storage.GzipCompressorFileStorage" +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_URL +COMPRESS_URL = STATIC_URL # noqa F405 +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_OFFLINE +COMPRESS_OFFLINE = True # Offline compression is required when using Whitenoise +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_FILTERS +COMPRESS_FILTERS = { + "css": [ + "compressor.filters.css_default.CssAbsoluteFilter", + "compressor.filters.cssmin.rCSSMinFilter", + ], + "js": ["compressor.filters.jsmin.JSMinFilter"], +} + +# LOGGING +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#logging +# See https://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. + +LOGGING = { + "version": 1, + "disable_existing_loggers": True, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s " + "%(process)d %(thread)d %(message)s" + } + }, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + } + }, + "root": {"level": "INFO", "handlers": ["console"]}, + "loggers": { + "django.db.backends": { + "level": "ERROR", + "handlers": ["console"], + "propagate": False, + }, + # Errors logged by the SDK itself + "sentry_sdk": {"level": "ERROR", "handlers": ["console"], "propagate": False}, + "django.security.DisallowedHost": { + "level": "ERROR", + "handlers": ["console"], + "propagate": False, + }, + }, +} + +# Sentry +# ------------------------------------------------------------------------------ +SENTRY_DSN = env("SENTRY_DSN", default=None) +if SENTRY_DSN: + SENTRY_LOG_LEVEL = env.int("DJANGO_SENTRY_LOG_LEVEL", logging.INFO) + + sentry_logging = LoggingIntegration( + level=SENTRY_LOG_LEVEL, # Capture info and above as breadcrumbs + event_level=logging.ERROR, # Send errors as events + ) + integrations = [ + sentry_logging, + DjangoIntegration(), + # CeleryIntegration(), + RedisIntegration(), + ] + sentry_sdk.init( + dsn=SENTRY_DSN, + integrations=integrations, + environment=env("SENTRY_ENVIRONMENT", default="production"), + traces_sample_rate=env.float("SENTRY_TRACES_SAMPLE_RATE", default=0.0), + ) + +# Your stuff... +# ------------------------------------------------------------------------------ diff --git a/platform/core/settings/test.py b/platform/core/settings/test.py new file mode 100644 index 0000000..a6ecb96 --- /dev/null +++ b/platform/core/settings/test.py @@ -0,0 +1,29 @@ +""" +With these settings, tests run faster. +""" + +from .base import * # noqa +from .base import env + +# GENERAL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +SECRET_KEY = env( + "DJANGO_SECRET_KEY", + default="OLp90r8zVxEKpE40fi5voeKY7MvIxXvrTny9JDrMzB4uIzsVIc28nJ6lrDQmrU1j", +) +# https://docs.djangoproject.com/en/dev/ref/settings/#test-runner +TEST_RUNNER = "django.test.runner.DiscoverRunner" + +# PASSWORDS +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers +PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] + +# EMAIL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend +EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" + +# Your stuff... +# ------------------------------------------------------------------------------ diff --git a/platform/core/urls.py b/platform/core/urls.py index 475ae34..d092940 100644 --- a/platform/core/urls.py +++ b/platform/core/urls.py @@ -1,7 +1,7 @@ """sustainable_urban_design_space URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/3.0/topics/http/urls/ + https://docs.djangoproject.com/en/3.2/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views @@ -16,19 +16,25 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin +from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.urls import include, path, re_path - +from django.views import defaults as default_views from front_page.views import FrontPageView -from places import urls as places_urls from openstreetmap import urls as openstreetmap_urls from patterns import urls as patterns_urls -from users import urls as users_urls +from places import urls as places_urls from projects import urls as projects_urls +from users import urls as users_urls urlpatterns = [ - path("admin/", admin.site.urls), + # Django Admin, use {% url 'admin:index' %} + path(settings.ADMIN_URL, admin.site.urls), + + # User management path("accounts/", include("allauth.urls")), + path("i18n/", include("django.conf.urls.i18n")), + path("places/", include(places_urls)), path("openstreetmap/", include(openstreetmap_urls)), path("patterns/", include(patterns_urls)), @@ -36,3 +42,34 @@ path("projects/", include(projects_urls)), path("", FrontPageView.as_view(), name="front_page"), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + + +if settings.DEBUG: + # Static file serving when using Gunicorn + Uvicorn for local web socket development + urlpatterns += staticfiles_urlpatterns() + +if settings.DEBUG: + # This allows the error pages to be debugged during development, just visit + # these url in browser to see how these error pages look like. + urlpatterns += [ + path( + "400/", + default_views.bad_request, + kwargs={"exception": Exception("Bad Request!")}, + ), + path( + "403/", + default_views.permission_denied, + kwargs={"exception": Exception("Permission Denied")}, + ), + path( + "404/", + default_views.page_not_found, + kwargs={"exception": Exception("Page not Found")}, + ), + path("500/", default_views.server_error), + ] + if "debug_toolbar" in settings.INSTALLED_APPS: + import debug_toolbar + + urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns diff --git a/platform/core/websocket.py b/platform/core/websocket.py new file mode 100644 index 0000000..81adfbc --- /dev/null +++ b/platform/core/websocket.py @@ -0,0 +1,13 @@ +async def websocket_application(scope, receive, send): + while True: + event = await receive() + + if event["type"] == "websocket.connect": + await send({"type": "websocket.accept"}) + + if event["type"] == "websocket.disconnect": + break + + if event["type"] == "websocket.receive": + if event["text"] == "ping": + await send({"type": "websocket.send", "text": "pong!"}) diff --git a/platform/core/wsgi.py b/platform/core/wsgi.py index c8f259a..4459705 100644 --- a/platform/core/wsgi.py +++ b/platform/core/wsgi.py @@ -1,16 +1,38 @@ """ -WSGI config for sustainable_urban_design_space project. +WSGI config for Sustainable Urban design project. -It exposes the WSGI callable as a module-level variable named ``application``. +This module contains the WSGI application used by Django's development server +and any production WSGI deployments. It should expose a module-level variable +named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover +this application via the ``WSGI_APPLICATION`` setting. -For more information on this file, see -https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ -""" +Usually you will have the standard Django WSGI application here, but it also +might make sense to replace the whole Django WSGI application with a custom one +that later delegates to the Django one. For example, you could introduce WSGI +middleware here, or combine a Django application with an application of another +framework. +""" import os +import sys +from pathlib import Path from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") +# This allows easy placement of apps within the interior +# main directory. +ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent +sys.path.append(str(ROOT_DIR / "platform")) +# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks +# if running multiple sites in the same mod_wsgi process. To fix this, use +# mod_wsgi daemon mode with each site in its own daemon process, or use +# os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production" +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings.production") +# This application object is used by any WSGI server configured to use this +# file. This includes Django's development server, if the WSGI_APPLICATION +# setting points here. application = get_wsgi_application() +# Apply WSGI middleware here. +# from helloworld.wsgi import HelloWorldApplication +# application = HelloWorldApplication(application) diff --git a/platform/env.template b/platform/env.template new file mode 100644 index 0000000..6ce6ac6 --- /dev/null +++ b/platform/env.template @@ -0,0 +1,9 @@ +###################################### +# Edit this file and copy it as .env +###################################### + +# Main Database: +DATABASE_URL=postgres://:@:/ + +# Open Street Maps Database +DATABASE_OSM_URL=postgres://:@:/ diff --git a/platform/manage.py b/platform/manage.py index 24fa0e9..34653fa 100755 --- a/platform/manage.py +++ b/platform/manage.py @@ -2,10 +2,12 @@ """Django's command-line utility for administrative tasks.""" import os import sys +from pathlib import Path def main(): - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings.local") + try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -14,6 +16,12 @@ def main(): "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc + + # This allows easy placement of apps within the interior + # main directory. + # current_path = Path(__file__).parent.resolve() + # sys.path.append(str(current_path / "platform")) + execute_from_command_line(sys.argv) diff --git a/platform/maps/templates/maps/openlayers.html b/platform/maps/templates/maps/openlayers.html index ceda945..ec359c5 100644 --- a/platform/maps/templates/maps/openlayers.html +++ b/platform/maps/templates/maps/openlayers.html @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/platform/openstreetmap/queries.py b/platform/openstreetmap/queries.py index a99c805..d0e1710 100644 --- a/platform/openstreetmap/queries.py +++ b/platform/openstreetmap/queries.py @@ -1,7 +1,7 @@ def create_osm_to_geojson_query( table=None, - fclass=None, - limit=10, + fclass="", + limit="", xmin=23.5, ymin=61.45, xmax=23.8, @@ -16,6 +16,13 @@ def create_osm_to_geojson_query( # TODO: determine how best to allow WHERE clause # to filter based on fclass and possibly geometry. + + if limit: + limit = f"LIMIT {limit}" + + if fclass: + fclass = f"""AND "fclass" = '{fclass}'""" + return f""" SELECT jsonb_build_object( 'type', 'FeatureCollection', @@ -43,8 +50,8 @@ def create_osm_to_geojson_query( { xmax }, { ymax }, { epsg } ) ~ "geometry" - AND "fclass" = 'supermarket' - --limit { limit } + { fclass } + { limit } ) inputs ) features; """ diff --git a/platform/openstreetmap/urls.py b/platform/openstreetmap/urls.py index d8b7d1f..3d4dd23 100644 --- a/platform/openstreetmap/urls.py +++ b/platform/openstreetmap/urls.py @@ -3,5 +3,5 @@ from . import views urlpatterns = [ - path("data", views.get_osm_data), + path("data", views.get_osm_data, name="get_osm_data"), ] diff --git a/platform/openstreetmap/views.py b/platform/openstreetmap/views.py index e4c3497..84f7a3d 100644 --- a/platform/openstreetmap/views.py +++ b/platform/openstreetmap/views.py @@ -1,10 +1,9 @@ import json +import psycopg2 from django.conf import settings from django.http import HttpResponse -import psycopg2 - from .queries import create_osm_to_geojson_query @@ -21,15 +20,20 @@ def get_osm_data(request): password=osm_db["PASSWORD"], host=osm_db["HOST"], ) - - cursor = connection.cursor() - - osm_query = create_osm_to_geojson_query(table="osm_points_of_interest") - - cursor.execute(osm_query) - + + cursor = connection.cursor() + kw = { + "table": "osm_points_of_interest", + "epsg": 4326, + "limit": 200, + } + try: + kw["xmin"], kw["ymin"], kw["xmax"], kw["ymax"] = request.GET["ext"].split(",") + except (KeyError, ValueError): + pass + + osm_query = create_osm_to_geojson_query(**kw) + cursor.execute(osm_query) result = cursor.fetchone() - response = json.dumps(result[0], indent=2) - return HttpResponse(response) diff --git a/platform/patterns/apps.py b/platform/patterns/apps.py index 52dc118..60fd9f8 100644 --- a/platform/patterns/apps.py +++ b/platform/patterns/apps.py @@ -3,3 +3,4 @@ class PatternsConfig(AppConfig): name = "patterns" + default_auto_field = "django.db.models.BigAutoField" diff --git a/platform/patterns/migrations/0005_alter_urbandesignpattern_id.py b/platform/patterns/migrations/0005_alter_urbandesignpattern_id.py new file mode 100644 index 0000000..e20df87 --- /dev/null +++ b/platform/patterns/migrations/0005_alter_urbandesignpattern_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.11 on 2022-01-21 06:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('patterns', '0004_auto_20201011_0805'), + ] + + operations = [ + migrations.AlterField( + model_name='urbandesignpattern', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/platform/places/apps.py b/platform/places/apps.py index eacb3c3..df037a8 100644 --- a/platform/places/apps.py +++ b/platform/places/apps.py @@ -3,3 +3,4 @@ class PlacesConfig(AppConfig): name = 'places' + default_auto_field = "django.db.models.BigAutoField" diff --git a/platform/places/migrations/0002_alter_place_id.py b/platform/places/migrations/0002_alter_place_id.py new file mode 100644 index 0000000..3f2753b --- /dev/null +++ b/platform/places/migrations/0002_alter_place_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.11 on 2022-01-21 06:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('places', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='place', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/platform/projects/apps.py b/platform/projects/apps.py index 20f1a5a..d9f550b 100644 --- a/platform/projects/apps.py +++ b/platform/projects/apps.py @@ -3,3 +3,4 @@ class ProjectsConfig(AppConfig): name = "projects" + default_auto_field = "django.db.models.BigAutoField" diff --git a/platform/projects/migrations/0003_alter_project_id.py b/platform/projects/migrations/0003_alter_project_id.py new file mode 100644 index 0000000..ad61cc0 --- /dev/null +++ b/platform/projects/migrations/0003_alter_project_id.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.11 on 2022-01-21 06:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0002_project_place'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/platform/projects/templates/projects/edit_project_goals.html b/platform/projects/templates/projects/edit_project_goals.html index 68f7fdd..e6c5f07 100644 --- a/platform/projects/templates/projects/edit_project_goals.html +++ b/platform/projects/templates/projects/edit_project_goals.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load i18n %} {% block content %}
@@ -32,33 +33,80 @@ crossorigin="anonymous"> \ No newline at end of file + \ No newline at end of file diff --git a/platform/projects/templates/projects/project_goals_map.html b/platform/projects/templates/projects/project_goals_map.html index 61b6d8b..43d703d 100644 --- a/platform/projects/templates/projects/project_goals_map.html +++ b/platform/projects/templates/projects/project_goals_map.html @@ -1,4 +1,6 @@ - + diff --git a/platform/projects/templates/projects/vue_js.html b/platform/projects/templates/projects/vue_js.html index cfce88d..06e36b8 100644 --- a/platform/projects/templates/projects/vue_js.html +++ b/platform/projects/templates/projects/vue_js.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/platform/projects/views.py b/platform/projects/views.py index f9e7938..b23a96d 100644 --- a/platform/projects/views.py +++ b/platform/projects/views.py @@ -30,8 +30,6 @@ class ProjectUpdateView(UpdateView): form_class = ProjectForm -class EditProjectGoalsView(View): +class EditProjectGoalsView(DetailView): template_name = "projects/edit_project_goals.html" - - def get(self, request, *args, **kwargs): - return render(request, self.template_name) + model = Project diff --git a/platform/resources/apps.py b/platform/resources/apps.py index cbf046b..847b495 100644 --- a/platform/resources/apps.py +++ b/platform/resources/apps.py @@ -3,3 +3,4 @@ class ResourcesConfig(AppConfig): name = "resources" + default_auto_field = "django.db.models.BigAutoField" diff --git a/platform/resources/migrations/0002_auto_20220121_0614.py b/platform/resources/migrations/0002_auto_20220121_0614.py new file mode 100644 index 0000000..5ea0142 --- /dev/null +++ b/platform/resources/migrations/0002_auto_20220121_0614.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.11 on 2022-01-21 06:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='book', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='datasource', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/platform/templates/account/base.html b/platform/templates/account/base.html new file mode 100644 index 0000000..8e1f260 --- /dev/null +++ b/platform/templates/account/base.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %} + +{% block content %} +
+
+ {% block inner %}{% endblock %} +
+
+{% endblock %} diff --git a/platform/templates/account/login.html b/platform/templates/account/login.html new file mode 100644 index 0000000..c6ce699 --- /dev/null +++ b/platform/templates/account/login.html @@ -0,0 +1,96 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% load account socialaccount %} +{% load crispy_forms_tags %} + +{% block head_title %}{% trans "Sign In" %}{% endblock %} + +{% block inner %} + +

{% trans "Sign In" %}

+ +{% get_providers as socialaccount_providers %} + +{% if socialaccount_providers %} +

{% blocktrans with site.name as site_name %}Please sign in with one +of your existing third party accounts. Or, sign up +for a {{ site_name }} account and sign in below:{% endblocktrans %}

+ +
+ +
    + {% include "socialaccount/snippets/provider_list.html" with process="login" %} +
+ + + +
+ +{% include "socialaccount/snippets/login_extra.html" %} + +{% else %} +

{% blocktrans %}If you have not created an account yet, then please +sign up first.{% endblocktrans %}

+{% endif %} + + + +{% endblock %} + +{% block js_extra %} + +{% endblock %} diff --git a/platform/users/adapters.py b/platform/users/adapters.py new file mode 100644 index 0000000..0d206fa --- /dev/null +++ b/platform/users/adapters.py @@ -0,0 +1,16 @@ +from typing import Any + +from allauth.account.adapter import DefaultAccountAdapter +from allauth.socialaccount.adapter import DefaultSocialAccountAdapter +from django.conf import settings +from django.http import HttpRequest + + +class AccountAdapter(DefaultAccountAdapter): + def is_open_for_signup(self, request: HttpRequest): + return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) + + +class SocialAccountAdapter(DefaultSocialAccountAdapter): + def is_open_for_signup(self, request: HttpRequest, sociallogin: Any): + return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) diff --git a/platform/users/apps.py b/platform/users/apps.py index 5976c91..b6dcb74 100644 --- a/platform/users/apps.py +++ b/platform/users/apps.py @@ -3,6 +3,7 @@ class UsersConfig(AppConfig): name = "users" + default_auto_field = "django.db.models.BigAutoField" def ready(self): import users.signals # noqa diff --git a/platform/users/context_processors.py b/platform/users/context_processors.py new file mode 100644 index 0000000..e2633ae --- /dev/null +++ b/platform/users/context_processors.py @@ -0,0 +1,8 @@ +from django.conf import settings + + +def allauth_settings(request): + """Expose some settings from django-allauth in templates.""" + return { + "ACCOUNT_ALLOW_REGISTRATION": settings.ACCOUNT_ALLOW_REGISTRATION, + } diff --git a/platform/users/forms.py b/platform/users/forms.py index dfced65..7b53494 100644 --- a/platform/users/forms.py +++ b/platform/users/forms.py @@ -1,9 +1,51 @@ +from allauth.account.forms import SignupForm +from allauth.socialaccount.forms import SignupForm as SocialSignupForm from django import forms +from django.contrib.auth import forms as admin_forms +from django.contrib.auth import get_user_model +from django.utils.translation import gettext_lazy as _ from .models import UserProfile +User = get_user_model() + class UserProfileForm(forms.ModelForm): class Meta: model = UserProfile fields = ("family_name", "given_name", "job_title") + + +class UserAdminChangeForm(admin_forms.UserChangeForm): + class Meta(admin_forms.UserChangeForm.Meta): + model = User + + +class UserAdminCreationForm(admin_forms.UserCreationForm): + """ + Form for User Creation in the Admin Area. + To change user signup, see UserSignupForm and UserSocialSignupForm. + """ + + class Meta(admin_forms.UserCreationForm.Meta): + model = User + + error_messages = { + "username": {"unique": _("This username has already been taken.")} + } + + +class UserSignupForm(SignupForm): + """ + Form that will be rendered on a user sign up section/screen. + Default fields will be added automatically. + Check UserSocialSignupForm for accounts created from social. + """ + + +class UserSocialSignupForm(SocialSignupForm): + """ + Renders the form when user has signed up using social accounts. + Default fields will be added automatically. + See UserSignupForm otherwise. + """ diff --git a/platform/users/migrations/0003_auto_20220121_0614.py b/platform/users/migrations/0003_auto_20220121_0614.py new file mode 100644 index 0000000..2a0afd3 --- /dev/null +++ b/platform/users/migrations/0003_auto_20220121_0614.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.11 on 2022-01-21 06:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_userprofile'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='userprofile', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 55849e5..0000000 --- a/requirements.txt +++ /dev/null @@ -1,45 +0,0 @@ -appdirs==1.4.4 -asgiref==3.2.7 -astroid==2.4.0 -attrs==19.3.0 -black==20.8b1 -certifi==2020.4.5.1 -chardet==3.0.4 -click==7.1.2 -defusedxml==0.6.0 -dj-database-url==0.5.0 -Django==3.0.7 -django-allauth==0.41.0 -django-cors-headers==3.4.0 -django-crispy-forms==1.9.2 -django-js-asset==1.2.2 -django-mptt==0.11.0 -django-taggit==1.3.0 -djangorestframework==3.12.1 -et-xmlfile==1.0.1 -idna==2.9 -isort==4.3.21 -jdcal==1.4.1 -lazy-object-proxy==1.4.3 -mccabe==0.6.1 -mypy-extensions==0.4.3 -oauthlib==3.1.0 -openpyxl==3.0.5 -pathspec==0.8.0 -Pillow==7.2.0 -psycopg2-binary==2.8.5 -pylint==2.5.0 -python3-openid==3.1.0 -pytz==2020.1 -regex==2020.5.7 -requests==2.23.0 -requests-oauthlib==1.3.0 -six==1.14.0 -sqlparse==0.3.1 -toml==0.10.1 -typed-ast==1.4.1 -typing-extensions==3.7.4.3 -urllib3==1.25.9 -wrapt==1.12.1 -xlrd==1.2.0 -xlwt==1.3.0 diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 0000000..58a457a --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,19 @@ +# Django +# ------------------------------------------------------------------------------ +django==3.2.11 # pyup: < 4.0 # https://www.djangoproject.com/ +django-allauth==0.47.0 # https://github.com/pennersr/django-allauth +django-crispy-forms==1.13.0 # https://github.com/django-crispy-forms/django-crispy-forms +django-mptt # ==0.11.0 +django-taggit # ==1.3.0 +django-environ==0.8.1 # https://github.com/joke2k/django-environ +django[argon2] # https://docs.djangoproject.com/en/dev/topics/auth/passwords/#using-argon2-with-django + +# Django REST Framework +djangorestframework==3.13.1 # https://github.com/encode/django-rest-framework +django-cors-headers==3.11.0 # https://github.com/adamchainz/django-cors-headers +# DRF-spectacular for api documentation +# drf-spectacular==0.21.1 + +psycopg2==2.9.3 # https://github.com/psycopg/psycopg2 + +Pillow==9.0.0 # https://github.com/python-pillow/Pillow diff --git a/requirements/local.txt b/requirements/local.txt new file mode 100644 index 0000000..62ba965 --- /dev/null +++ b/requirements/local.txt @@ -0,0 +1,40 @@ +-r base.txt + +ipdb==0.13.9 # https://github.com/gotcha/ipdb + +# Email +django-yubin==1.7.1 # https://github.com/APSL/django-yubin + + +# Testing +# ------------------------------------------------------------------------------ +mypy==0.931 # https://github.com/python/mypy +django-stubs==1.9.0 # https://github.com/typeddjango/django-stubs +pytest==6.2.5 # https://github.com/pytest-dev/pytest +pytest-sugar==0.9.4 # https://github.com/Frozenball/pytest-sugar +djangorestframework-stubs==1.4.0 # https://github.com/typeddjango/djangorestframework-stubs + +# Documentation +# ------------------------------------------------------------------------------ +sphinx==4.4.0 # https://github.com/sphinx-doc/sphinx +sphinx-autobuild==2021.3.14 # https://github.com/GaretJax/sphinx-autobuild + +# Code quality +# ------------------------------------------------------------------------------ +flake8==4.0.1 # https://github.com/PyCQA/flake8 +flake8-isort==4.1.1 # https://github.com/gforcada/flake8-isort +coverage==6.2 # https://github.com/nedbat/coveragepy +black==21.12b0 # https://github.com/psf/black +pylint-django==2.5.0 # https://github.com/PyCQA/pylint-django +pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery +pre-commit==2.17.0 # https://github.com/pre-commit/pre-commit +bandit==1.7.1 # https://github.com/PyCQA/bandit + +# Django +# ------------------------------------------------------------------------------ +factory-boy==3.2.1 # https://github.com/FactoryBoy/factory_boy + +django-debug-toolbar==3.2.4 # https://github.com/jazzband/django-debug-toolbar +django-extensions==3.1.5 # https://github.com/django-extensions/django-extensions +django-coverage-plugin==2.0.2 # https://github.com/nedbat/django_coverage_plugin +pytest-django==4.5.2 # https://github.com/pytest-dev/pytest-django diff --git a/requirements/production.txt b/requirements/production.txt new file mode 100644 index 0000000..c29a528 --- /dev/null +++ b/requirements/production.txt @@ -0,0 +1,7 @@ +# PRECAUTION: avoid production dependencies that aren't in development + +-r base.txt + +gunicorn==20.1.0 # https://github.com/benoitc/gunicorn +psycopg2==2.9.3 # https://github.com/psycopg/psycopg2 +sentry-sdk==1.5.2 # https://github.com/getsentry/sentry-python