initial commit
This commit is contained in:
commit
6f5167c24f
45 changed files with 763 additions and 0 deletions
24
LICENSE
Normal file
24
LICENSE
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
djangox: Copyright (c) 2018 William Vincent
|
||||||
|
django-allauth: Copyright (c) 2010 Raymond Penners and contributors
|
||||||
|
cookie-cutter-django: Copyright (c) 2013-2018 Daniel Greenfeld
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
16
Pipfile
Normal file
16
Pipfile
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[[source]]
|
||||||
|
|
||||||
|
url = "https://pypi.python.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
|
||||||
|
django = "*"
|
||||||
|
django-allauth = "*"
|
||||||
|
django-crispy-forms = "*"
|
||||||
|
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
128
Pipfile.lock
generated
Normal file
128
Pipfile.lock
generated
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "6d92f3e54c0f137198dc7ff38d1703a2c25f77a1870919b2cd22333a8fe35d51"
|
||||||
|
},
|
||||||
|
"host-environment-markers": {
|
||||||
|
"implementation_name": "cpython",
|
||||||
|
"implementation_version": "3.6.4",
|
||||||
|
"os_name": "posix",
|
||||||
|
"platform_machine": "x86_64",
|
||||||
|
"platform_python_implementation": "CPython",
|
||||||
|
"platform_release": "17.4.0",
|
||||||
|
"platform_system": "Darwin",
|
||||||
|
"platform_version": "Darwin Kernel Version 17.4.0: Sun Dec 17 09:19:54 PST 2017; root:xnu-4570.41.2~1/RELEASE_X86_64",
|
||||||
|
"python_full_version": "3.6.4",
|
||||||
|
"python_version": "3.6",
|
||||||
|
"sys_platform": "darwin"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.python.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296",
|
||||||
|
"sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d"
|
||||||
|
],
|
||||||
|
"version": "==2018.1.18"
|
||||||
|
},
|
||||||
|
"chardet": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
|
||||||
|
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"
|
||||||
|
],
|
||||||
|
"version": "==3.0.4"
|
||||||
|
},
|
||||||
|
"defusedxml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:702a91ade2968a82beb0db1e0766a6a273f33d4616a6ce8cde475d8e09853b20",
|
||||||
|
"sha256:24d7f2f94f7f3cb6061acb215685e5125fbcdc40a857eff9de22518820b0a4f4"
|
||||||
|
],
|
||||||
|
"version": "==0.5.0"
|
||||||
|
},
|
||||||
|
"django": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7c8ff92285406fb349e765e9ade685eec7271d6f5c3f918e495a74768b765c99",
|
||||||
|
"sha256:dc3b61d054f1bced64628c62025d480f655303aea9f408e5996c339a543b45f0"
|
||||||
|
],
|
||||||
|
"version": "==2.0.2"
|
||||||
|
},
|
||||||
|
"django-allauth": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7b31526cccd1c46f9f09acf0703068e8a9669337d29eb065f7e8143c2d897339"
|
||||||
|
],
|
||||||
|
"version": "==0.35.0"
|
||||||
|
},
|
||||||
|
"django-crispy-forms": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:d37fe72eb550b41ba651c06293fb861d5a9e6e3ada23304718858cd6250d258d",
|
||||||
|
"sha256:b29a9a671194e3a482891f319f69da81e30ae81c075f6e37adb14a83ba2c409b"
|
||||||
|
],
|
||||||
|
"version": "==1.7.0"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4",
|
||||||
|
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f"
|
||||||
|
],
|
||||||
|
"version": "==2.6"
|
||||||
|
},
|
||||||
|
"oauthlib": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:ce57b501e906ff4f614e71c36a3ab9eacbb96d35c24d1970d2539bbc3ec70ce1"
|
||||||
|
],
|
||||||
|
"version": "==2.0.6"
|
||||||
|
},
|
||||||
|
"python3-openid": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0086da6b6ef3161cfe50fb1ee5cceaf2cda1700019fda03c2c5c440ca6abe4fa",
|
||||||
|
"sha256:628d365d687e12da12d02c6691170f4451db28d6d68d050007e4a40065868502"
|
||||||
|
],
|
||||||
|
"version": "==3.1.0"
|
||||||
|
},
|
||||||
|
"pytz": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe",
|
||||||
|
"sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda",
|
||||||
|
"sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9",
|
||||||
|
"sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f",
|
||||||
|
"sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd",
|
||||||
|
"sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5",
|
||||||
|
"sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d",
|
||||||
|
"sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef",
|
||||||
|
"sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0"
|
||||||
|
],
|
||||||
|
"version": "==2018.3"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
|
||||||
|
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
|
||||||
|
],
|
||||||
|
"version": "==2.18.4"
|
||||||
|
},
|
||||||
|
"requests-oauthlib": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:50a8ae2ce8273e384895972b56193c7409601a66d4975774c60c2aed869639ca",
|
||||||
|
"sha256:883ac416757eada6d3d07054ec7092ac21c7f35cb1d2cf82faf205637081f468"
|
||||||
|
],
|
||||||
|
"version": "==0.8.0"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
||||||
|
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
||||||
|
],
|
||||||
|
"version": "==1.22"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
||||||
67
README.md
Normal file
67
README.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# DjangoX
|
||||||
|
|
||||||
|
**DjangoX** - A framework for launching new Django projects quickly.
|
||||||
|
|
||||||
|
Comes with a custom user model, social authentication, and email/password for sign up and log in.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Django 2.0 and Python 3.6
|
||||||
|
* [Pipenv](https://github.com/pypa/pipenv) for virtualenvs
|
||||||
|
* User registration via [django-allauth](https://github.com/pennersr/django-allauth)
|
||||||
|
* Add social auth via Google, Facebook, etc
|
||||||
|
* [Bootstrap v4](https://getbootstrap.com/)
|
||||||
|
* Custom user model with email and no username
|
||||||
|
|
||||||
|
## First-time setup
|
||||||
|
|
||||||
|
1. Make sure Python 3.6x and Pipenv are already installed. [See here for help](https://djangoforbeginners.com/initial-setup/).
|
||||||
|
2. Install packages with `pipenv install`
|
||||||
|
3. Activate a virtual environment with `pipenv shell`
|
||||||
|
4. Set up the initial migration for our custom user models in `users`
|
||||||
|
|
||||||
|
$ python manage.py makemigrations users
|
||||||
|
|
||||||
|
5. Build the database schema:
|
||||||
|
|
||||||
|
$ python manage.py migrate
|
||||||
|
|
||||||
|
6. Create a superuser:
|
||||||
|
|
||||||
|
$ python manage.py createsuperuser
|
||||||
|
|
||||||
|
7. Confirm everything is working:
|
||||||
|
|
||||||
|
$ python manage.py runserver
|
||||||
|
|
||||||
|
Load the site at [http://127.0.0.1:8000](http://127.0.0.1:8000).
|
||||||
|
|
||||||
|
Click on links for "Sign up" or "Log in."
|
||||||
|
|
||||||
|
8. This is optional but I also recommend logging into admin and changing the default site:
|
||||||
|
|
||||||
|
Go to [http://127.0.0.1:8000/admin]([http://127.0.0.1:8000/admin]). You may need to logout and then login with your superuser account.
|
||||||
|
|
||||||
|
Navigate to [http://127.0.0.1:8000/admin/sites/site/](http://127.0.0.1:8000/admin/sites/site/) and change the default "example.com" to "127.0.0.1" and the name to "<YOUR_PROJECT_NAME>" for local development.
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
* Use [PostgreSQL locally via Docker](https://wsvincent.com/django-docker-postgresql/)
|
||||||
|
* Use [django-environ](https://github.com/joke2k/django-environ) for environment variables
|
||||||
|
* Update [EMAIL_BACKEND](https://docs.djangoproject.com/en/2.0/topics/email/#module-django.core.mail) to [configure an SMTP backend](https://djangoforbeginners.com/password-change-reset/)
|
||||||
|
* Add [django-debug-toolbar](https://github.com/jazzband/django-debug-toolbar) and [django-extensions](https://github.com/django-extensions/django-extensions)
|
||||||
|
|
||||||
|
* Make the [admin more secure](https://opensource.com/article/18/1/10-tips-making-django-admin-more-secure)
|
||||||
|
|
||||||
|
## Adding Social Authentication
|
||||||
|
|
||||||
|
* [Configuring Google](https://wsvincent.com/django-allauth-tutorial-custom-user-model/#google-credentials)
|
||||||
|
* [Configuring Facebook](http://www.sarahhagstrom.com/2013/09/the-missing-django-allauth-tutorial/#Create_and_configure_a_Facebook_app)
|
||||||
|
* [Configuring Github](https://wsvincent.com/django-allauth-tutorial/)
|
||||||
|
* `django-allauth` supports [many, many other providers in the official docs](https://django-allauth.readthedocs.io/en/latest/providers.html)
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
This project is heavily inspired by [cookiecutter-django](https://github.com/pydanny/cookiecutter-django). It's my own preferred template for starting new projects built out of a personal desire to actually understand all the config magic in `cookiecutter-django`.
|
||||||
0
djangox/__init__.py
Normal file
0
djangox/__init__.py
Normal file
145
djangox/settings.py
Normal file
145
djangox/settings.py
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
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__)))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '4(j*7q=-dm@4&d8hb)-ivy#b(&_3ew19ujzo#h_hq-39!6-5d+'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.sites',
|
||||||
|
|
||||||
|
'allauth',
|
||||||
|
'allauth.account',
|
||||||
|
'allauth.socialaccount',
|
||||||
|
# 'allauth.socialaccount.providers.google',
|
||||||
|
'crispy_forms',
|
||||||
|
|
||||||
|
'users',
|
||||||
|
'pages',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'djangox.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 = 'djangox.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/2.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',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/2.0/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
|
||||||
|
|
||||||
|
# Bootstrap Crispy-Forms config
|
||||||
|
CRISPY_TEMPLATE_PACK = 'bootstrap4'
|
||||||
|
|
||||||
|
# Authentication settings
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
"allauth.account.auth_backends.AuthenticationBackend",
|
||||||
|
)
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = 'users.CustomUser'
|
||||||
|
SITE_ID = 1
|
||||||
|
LOGIN_REDIRECT_URL = 'home'
|
||||||
|
ACCOUNT_LOGOUT_REDIRECT_URL = 'home'
|
||||||
|
ACCOUNT_AUTHENTICATION_METHOD = 'email'
|
||||||
|
ACCOUNT_USERNAME_REQUIRED = False
|
||||||
|
ACCOUNT_EMAIL_REQUIRED = True
|
||||||
|
ACCOUNT_USERNAME_MIN_LENGTH = 3
|
||||||
|
ACCOUNT_EMAIL_VERIFICATION = 'optional' # set this to 'mandatory'?
|
||||||
|
ACCOUNT_UNIQUE_EMAIL = True
|
||||||
|
# ACCOUNT_USER_MODEL_EMAIL_FIELD = 'email'
|
||||||
|
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
|
||||||
|
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
|
||||||
|
ACCOUNT_SESSION_REMEMBER = True
|
||||||
13
djangox/urls.py
Normal file
13
djangox/urls.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', include('pages.urls')),
|
||||||
|
|
||||||
|
# Django Admin
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
|
||||||
|
# User management
|
||||||
|
path('users/', include('users.urls')),
|
||||||
|
path('accounts/', include('allauth.urls')), # new
|
||||||
|
]
|
||||||
7
djangox/wsgi.py
Normal file
7
djangox/wsgi.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangox.settings")
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
15
manage.py
Executable file
15
manage.py
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangox.settings")
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
6
notes.md
Normal file
6
notes.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
- tut on custom/auth...
|
||||||
|
|
||||||
|
- add about page...
|
||||||
|
- add base.html with nav, update other templates
|
||||||
|
- add 404, 403, 500 pages
|
||||||
|
- customize password reset...
|
||||||
0
pages/__init__.py
Normal file
0
pages/__init__.py
Normal file
3
pages/admin.py
Normal file
3
pages/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
5
pages/apps.py
Normal file
5
pages/apps.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PagesConfig(AppConfig):
|
||||||
|
name = 'pages'
|
||||||
0
pages/migrations/__init__.py
Normal file
0
pages/migrations/__init__.py
Normal file
3
pages/models.py
Normal file
3
pages/models.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
3
pages/tests.py
Normal file
3
pages/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
8
pages/urls.py
Normal file
8
pages/urls.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.HomePageView.as_view(), name='home'),
|
||||||
|
path('about/', views.AboutPageView.as_view(), name='about'),
|
||||||
|
]
|
||||||
9
pages/views.py
Normal file
9
pages/views.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
|
||||||
|
class HomePageView(TemplateView):
|
||||||
|
template_name = 'pages/home.html'
|
||||||
|
|
||||||
|
|
||||||
|
class AboutPageView(TemplateView):
|
||||||
|
template_name = 'pages/about.html'
|
||||||
BIN
static/images/falconx.png
Normal file
BIN
static/images/falconx.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
BIN
static/images/favicon.ico
Normal file
BIN
static/images/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 484 B |
8
templates/403_csrf.html
Normal file
8
templates/403_csrf.html
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Forbidden (403){% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Forbidden (403)</h1>
|
||||||
|
<p>CSRF verification failed. Request aborted.</p>
|
||||||
|
{% endblock content %}
|
||||||
7
templates/404.html
Normal file
7
templates/404.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}404 Page not found{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Page not found</h1>
|
||||||
|
{% endblock content %}
|
||||||
8
templates/500.html
Normal file
8
templates/500.html
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}500 Server Error{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>500 Server Error</h1>
|
||||||
|
<p>Looks like something went wrong!</p>
|
||||||
|
{% endblock content %}
|
||||||
14
templates/account/email/password_reset_key_message.txt
Normal file
14
templates/account/email/password_reset_key_message.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Hello from {{ site_name }}!
|
||||||
|
|
||||||
|
We've received a request to reset your password. If you didn't make this request, you can safely ignore this email. Otherwise, click the button below to reset your password.{% endblocktrans %}
|
||||||
|
|
||||||
|
{{ password_reset_url }}
|
||||||
|
|
||||||
|
{% if username %}{% blocktrans %}In case you forgot, your username is {{ username }}.{% endblocktrans %}
|
||||||
|
|
||||||
|
{% endif %}{% blocktrans with site_name=current_site.name site_domain=current_site.domain %}Thank you for using {{ site_name }}!
|
||||||
|
{{ site_domain }}{% endblocktrans %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1
templates/account/email/password_reset_key_subject.txt
Normal file
1
templates/account/email/password_reset_key_subject.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Password Reset E-mail
|
||||||
14
templates/account/login.html
Normal file
14
templates/account/login.html
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block title %}Log in{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Log in</h2>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
<button class="btn btn-success" type="submit">Log in</button>
|
||||||
|
</form>
|
||||||
|
<a href="{% url 'account_reset_password' %}">Forgot Password?</a>
|
||||||
|
{% endblock content %}
|
||||||
13
templates/account/password_change.html
Normal file
13
templates/account/password_change.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block title %}Change Password{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Change Password</h2>
|
||||||
|
<form method="post" action="{% url 'account_change_password' %}" class="password_change">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
<button class="btn btn-success" type="submit">Change Password</button>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
15
templates/account/password_reset.html
Normal file
15
templates/account/password_reset.html
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block title %}Password Reset{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Forgot your password? </h2>
|
||||||
|
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
<button class="btn btn-primary" type="submit">Reset Password</button>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
|
||||||
9
templates/account/password_reset_done.html
Normal file
9
templates/account/password_reset_done.html
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block title %}Password Reset Done{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Password Reset</h1>
|
||||||
|
<p>We have sent you an e-mail. Please contact us if you do not receive it in a few minutes.</p>
|
||||||
|
{% endblock content %}
|
||||||
22
templates/account/password_reset_from_key.html
Normal file
22
templates/account/password_reset_from_key.html
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block title %}Change Password{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{% if token_fail %}Bad Token{% else %}Change Password{% endif %}</h1>
|
||||||
|
|
||||||
|
{% if token_fail %}
|
||||||
|
<p>The password reset link was invalid. Perhaps it has already been used? Please request a <a href="{% url 'account_reset_password' %}">new password reset</a>.</p>
|
||||||
|
{% else %}
|
||||||
|
{% if form %}
|
||||||
|
<form method="POST" action=".">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
<button class="btn btn-primary" type="submit">Change Password</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<p>Your password is now changed.</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock content%}
|
||||||
9
templates/account/password_reset_from_key_done.html
Normal file
9
templates/account/password_reset_from_key_done.html
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block title %}Change Password Done{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Password Change Done</h1>
|
||||||
|
<p>Your password has been changed.</p>
|
||||||
|
{% endblock content %}
|
||||||
14
templates/account/password_set.html
Normal file
14
templates/account/password_set.html
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block title %}Set Password{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="POST" action="" class="password_set">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form password_set_form %}
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" type="submit" name="action" value="{% trans "Set Password" %}"/>Change Password</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
13
templates/account/signup.html
Normal file
13
templates/account/signup.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block title %}Sign up{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Sign up</h2>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
<button class="btn btn-success" type="submit">Sign up</button>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
76
templates/base.html
Normal file
76
templates/base.html
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
{% load static %}
|
||||||
|
{% load socialaccount %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
|
||||||
|
<title>{% block title %}DjangoX{% endblock title %}</title>
|
||||||
|
<meta name="description" content="A framework for launching new Django projects quickly.">
|
||||||
|
<meta name="author" content="">
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="{% static 'images/favicon.ico' %}">
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{% static 'css/base.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
|
||||||
|
<a class="navbar-brand" href="{% url 'home' %}">DjangoX</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<ul class="navbar-nav ml-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="userMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
{{ user.username }}
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="userMenu">
|
||||||
|
<a class="dropdown-item" href="{% url 'account_change_password' %}">Change password</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item" href="{% url 'account_logout' %}">Log out</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<form class="form-inline ml-auto">
|
||||||
|
<a href="{% url 'account_login' %}" class="btn btn-outline-secondary">Log in</a>
|
||||||
|
<a href="{% url 'account_signup' %}" class="btn btn-primary ml-2">Sign up</a>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}
|
||||||
|
<p>Default content...</p>
|
||||||
|
{% endblock content %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
<!-- Bootstrap JavaScript -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<!-- 3rd party JavaScript -->
|
||||||
|
|
||||||
|
<!-- Project JS -->
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock javascript %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
templates/pages/about.html
Normal file
7
templates/pages/about.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}About page{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>About page</h1>
|
||||||
|
{% endblock content %}
|
||||||
10
templates/pages/home.html
Normal file
10
templates/pages/home.html
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Home page{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Home page</h1>
|
||||||
|
<img src="{% static 'images/falconx.png' %}" class="img-fluid" alt="FalconX launch"/>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
6
templates/registration/login.html
Normal file
6
templates/registration/login.html
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<h2>Login</h2>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
0
users/__init__.py
Normal file
0
users/__init__.py
Normal file
15
users/admin.py
Normal file
15
users/admin.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
|
||||||
|
from .forms import CustomUserCreationForm, CustomUserChangeForm
|
||||||
|
from .models import CustomUser
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserAdmin(UserAdmin):
|
||||||
|
model = CustomUser
|
||||||
|
add_form = CustomUserCreationForm
|
||||||
|
form = CustomUserChangeForm
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(CustomUser, CustomUserAdmin)
|
||||||
5
users/apps.py
Normal file
5
users/apps.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class UsersConfig(AppConfig):
|
||||||
|
name = 'users'
|
||||||
17
users/forms.py
Normal file
17
users/forms.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||||
|
from .models import CustomUser
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserCreationForm(UserCreationForm):
|
||||||
|
|
||||||
|
class Meta(UserCreationForm.Meta):
|
||||||
|
model = CustomUser
|
||||||
|
fields = ('username', 'email')
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserChangeForm(UserChangeForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CustomUser
|
||||||
|
fields = UserChangeForm.Meta.fields
|
||||||
9
users/models.py
Normal file
9
users/models.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.contrib.auth.models import AbstractUser, UserManager
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserManager(UserManager):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUser(AbstractUser):
|
||||||
|
objects = CustomUserManager()
|
||||||
3
users/tests.py
Normal file
3
users/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
6
users/urls.py
Normal file
6
users/urls.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('signup/', views.SignUp.as_view(), name='signup'),
|
||||||
|
]
|
||||||
10
users/views.py
Normal file
10
users/views.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.views import generic
|
||||||
|
|
||||||
|
from .forms import CustomUserCreationForm
|
||||||
|
|
||||||
|
|
||||||
|
class SignUp(generic.CreateView):
|
||||||
|
form_class = CustomUserCreationForm
|
||||||
|
success_url = reverse_lazy('login')
|
||||||
|
template_name = 'account/signup.html'
|
||||||
Loading…
Add table
Add a link
Reference in a new issue