initial commit

This commit is contained in:
wsvincent 2018-02-15 12:28:05 -05:00
commit 6f5167c24f
45 changed files with 763 additions and 0 deletions

24
LICENSE Normal file
View 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
View 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
View 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
View 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.
![Falconx](static/images/falconx.png)
## 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
View file

145
djangox/settings.py Normal file
View 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
View 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
View 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
View 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
View 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
View file

3
pages/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
pages/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class PagesConfig(AppConfig):
name = 'pages'

View file

3
pages/models.py Normal file
View file

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
pages/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

8
pages/urls.py Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

BIN
static/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

8
templates/403_csrf.html Normal file
View 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
View 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
View 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 %}

View 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 %}

View file

@ -0,0 +1 @@
Password Reset E-mail

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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%}

View 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 %}

View 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 %}

View 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
View 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>

View 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
View 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 %}

View 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
View file

15
users/admin.py Normal file
View 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
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'users'

17
users/forms.py Normal file
View 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
View 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
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

6
users/urls.py Normal file
View 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
View 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'