clone from upstream lithium project

This commit is contained in:
wsvincent 2018-02-15 12:53:04 -05:00 committed by badbl0cks
parent 6f5167c24f
commit f946e4933a
56 changed files with 754 additions and 503 deletions

15
.gitignore vendored Normal file
View file

@ -0,0 +1,15 @@
# OSX #
.DS_Store
# Byte-compiled / optimized / DLL files #
__pycache__/
*.py[cod]
*$py.class
# Django #
*.log
db.sqlite3
media
# Virtual environment
.venv

5
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,5 @@
# Contributing
Thank you for help improving DjangoX. All kinds of contributions are welcome. Please note that this starter project is *intentionally* basic: I don't plan to add environment variables, Docker, or other production-appropriate features as I feel they will overwhelm beginners. But I'm open to suggestions!
Please submit an Issue or even better a PR and I'll review :)

27
Dockerfile Normal file
View file

@ -0,0 +1,27 @@
# Pull base image
FROM python:3.12.2-slim-bookworm
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# Create and set work directory called `app`
RUN mkdir -p /code
WORKDIR /code
# Install dependencies
COPY requirements.txt /tmp/requirements.txt
RUN set -ex && \
pip install --upgrade pip && \
pip install -r /tmp/requirements.txt && \
rm -rf /root/.cache/
# Copy local project
COPY . /code/
# Expose port 8000
EXPOSE 8000
# Use gunicorn on port 8000
CMD ["gunicorn", "--bind", ":8000", "--workers", "2", "django_project.wsgi"]

View file

@ -1,6 +1,6 @@
djangox: Copyright (c) 2018 William Vincent
djangox: Copyright (c) 2020 William Vincent
django-allauth: Copyright (c) 2010 Raymond Penners and contributors
cookie-cutter-django: Copyright (c) 2013-2018 Daniel Greenfeld
cookie-cutter-django: Copyright (c) 2013-2020 Daniel Greenfeld
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@ -21,4 +21,4 @@ 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.
OTHER DEALINGS IN THE SOFTWARE.

16
Pipfile
View file

@ -1,16 +0,0 @@
[[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
View file

@ -1,128 +0,0 @@
{
"_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": {}
}

137
README.md
View file

@ -1,67 +1,122 @@
# DjangoX
# Lithium: A Django-Powered Boilerplate
Lithium is a batteries-included Django starter project with everything you need to start coding, including user authentication, static files, default styling, debugging, DRY forms, custom error pages, and more.
**DjangoX** - A framework for launching new Django projects quickly.
> This project was formerly known as _DjangoX_ but was renamed to _Lithium_ in November 2024.
Comes with a custom user model, social authentication, and email/password for sign up and log in.
https://github.com/wsvincent/djangox/assets/766418/a73ea730-a7b4-4e53-bf51-aa68f6816d6a
![Falconx](static/images/falconx.png)
## 👋 Free Newsletter
[Sign up for updates](https://buttondown.com/lithiumsaas) to the free and upcoming premium SaaS version!
## Features
## 🚀 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
- Django 5.1 & Python 3.12
- Installation via [Pip](https://pypi.org/project/pip/) or [Docker](https://www.docker.com/)
- User authentication--log in, sign up, password reset--via [django-allauth](https://github.com/pennersr/django-allauth)
- Static files configured with [Whitenoise](http://whitenoise.evans.io/en/stable/index.html)
- Styling with [Bootstrap v5](https://getbootstrap.com/)
- Debugging with [django-debug-toolbar](https://github.com/jazzband/django-debug-toolbar)
- DRY forms with [django-crispy-forms](https://github.com/django-crispy-forms/django-crispy-forms)
- Custom 404, 500, and 403 error pages
## First-time setup
## Table of Contents
* **[Installation](#installation)**
* [Pip](#pip)
* [Docker](#docker)
* [Next Steps](#next-steps)
* [Contributing](#contributing)
* [Support](#support)
* [License](#license)
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`
## 📖 Installation
Lithium can be installed via Pip or Docker. To start, clone the repo to your local computer and change into the proper directory.
$ python manage.py makemigrations users
```
$ git clone https://github.com/wsvincent/lithium.git
$ cd lithium
```
5. Build the database schema:
### Pip
You can use [pip](https://pypi.org/project/pip/) to create a fresh virtual environment on either Windows or macOS.
$ python manage.py migrate
```
# On Windows
$ python -m venv .venv
$ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
$ .venv\Scripts\Activate.ps1
(.venv) $
6. Create a superuser:
# On macOS
$ python -m venv .venv
$ source .venv/bin/activate
(.venv) $
```
$ python manage.py createsuperuser
Then install all packages hosted in `requirements.txt` and run `migrate` to configure the initial database. The command `createsuperuser` will create a new superuser account for accessing the admin. Execute the `runserver` commandt o start up the local server.
7. Confirm everything is working:
```
(.venv) $ pip install -r requirements.txt
(.venv) $ python manage.py migrate
(.venv) $ python manage.py createsuperuser
(.venv) $ python manage.py runserver
# Load the site at http://127.0.0.1:8000 or http://127.0.0.1:8000/admin for the admin
```
$ python manage.py runserver
### Docker
Load the site at [http://127.0.0.1:8000](http://127.0.0.1:8000).
To use Docker with PostgreSQL as the database update the `DATABASES` section of `django_project/settings.py` to reflect the following:
Click on links for "Sign up" or "Log in."
```python
# django_project/settings.py
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "postgres",
"USER": "postgres",
"PASSWORD": "postgres",
"HOST": "db", # set in docker-compose.yml
"PORT": 5432, # default postgres port
}
}
```
8. This is optional but I also recommend logging into admin and changing the default site:
The `INTERNAL_IPS` configuration in `django_project/settings.py` must be also be updated:
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.
```python
# config/settings.py
# django-debug-toolbar
import socket
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
INTERNAL_IPS = [ip[:-1] + "1" for ip in ips]
```
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.
And then proceed to build the Docker image, run the container, and execute the standard commands within Docker.
## Recommendations
```
$ docker compose up -d --build
$ docker compose exec web python manage.py migrate
$ docker compose exec web python manage.py createsuperuser
# Load the site at http://127.0.0.1:8000 or http://127.0.0.1:8000/admin for the admin
```
* 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)
## Next Steps
* Make the [admin more secure](https://opensource.com/article/18/1/10-tips-making-django-admin-more-secure)
- Add environment variables. There are multiple packages but I personally prefer [environs](https://pypi.org/project/environs/).
- Add [gunicorn](https://pypi.org/project/gunicorn/) as the production web server.
- Update the [EMAIL_BACKEND](https://docs.djangoproject.com/en/4.0/topics/email/#module-django.core.mail) and connect with a mail provider.
- Make the [admin more secure](https://opensource.com/article/18/1/10-tips-making-django-admin-more-secure).
- `django-allauth` supports [social authentication](https://django-allauth.readthedocs.io/en/latest/providers.html) if you need that.
## Adding Social Authentication
I cover all of these steps in tutorials and premium courses over at [LearnDjango.com](https://learndjango.com).
* [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)
## 🤝 Contributing
## Acknowledgments
Contributions, issues and feature requests are welcome! See [CONTRIBUTING.md](https://github.com/wsvincent/djangox/blob/master/CONTRIBUTING.md).
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`.
## ⭐️ Support
Give a ⭐️ if this project helped you!
## License
[The MIT License](LICENSE)

View file

@ -7,9 +7,24 @@ from .models import CustomUser
class CustomUserAdmin(UserAdmin):
model = CustomUser
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
list_display = [
"email",
"username",
]
# Explicitly define add_fieldsets to prevent unexpected fields
add_fieldsets = (
(
None,
{
"classes": ("wide",),
"fields": ("username", "email", "password1", "password2"),
},
),
)
admin.site.register(CustomUser, CustomUserAdmin)

6
accounts/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'accounts'

View file

@ -2,16 +2,14 @@ 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')
fields = ('email', 'username',)
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = CustomUser
fields = UserChangeForm.Meta.fields
fields = ('email', 'username',)

View file

@ -0,0 +1,117 @@
# Generated by Django 5.0.2 on 2024-02-19 12:10
import django.contrib.auth.models
import django.contrib.auth.validators
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='CustomUser',
fields=[
(
'id',
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name='ID'
),
),
('password', models.CharField(max_length=128, verbose_name='password')),
(
'last_login',
models.DateTimeField(blank=True, null=True, verbose_name='last login'),
),
(
'is_superuser',
models.BooleanField(
default=False,
help_text='Designates that this user has all permissions without explicitly assigning them.',
verbose_name='superuser status',
),
),
(
'username',
models.CharField(
error_messages={'unique': 'A user with that username already exists.'},
help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.',
max_length=150,
unique=True,
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
verbose_name='username',
),
),
(
'first_name',
models.CharField(blank=True, max_length=150, verbose_name='first name'),
),
(
'last_name',
models.CharField(blank=True, max_length=150, verbose_name='last name'),
),
(
'email',
models.EmailField(blank=True, max_length=254, verbose_name='email address'),
),
(
'is_staff',
models.BooleanField(
default=False,
help_text='Designates whether the user can log into this admin site.',
verbose_name='staff status',
),
),
(
'is_active',
models.BooleanField(
default=True,
help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.',
verbose_name='active',
),
),
(
'date_joined',
models.DateTimeField(
default=django.utils.timezone.now, verbose_name='date joined'
),
),
(
'groups',
models.ManyToManyField(
blank=True,
help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.',
related_name='user_set',
related_query_name='user',
to='auth.group',
verbose_name='groups',
),
),
(
'user_permissions',
models.ManyToManyField(
blank=True,
help_text='Specific permissions for this user.',
related_name='user_set',
related_query_name='user',
to='auth.permission',
verbose_name='user permissions',
),
),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

8
accounts/models.py Normal file
View file

@ -0,0 +1,8 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
pass
def __str__(self):
return self.email

3
accounts/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

7
django_project/asgi.py Normal file
View file

@ -0,0 +1,7 @@
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings")
application = get_asgi_application()

202
django_project/settings.py Normal file
View file

@ -0,0 +1,202 @@
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/dev/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 = "django-insecure-0peo@#x9jur3!h$ryje!$879xww8y1y66jx!%*#ymhg&jkozs2"
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"]
# Application definition
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"whitenoise.runserver_nostatic",
"django.contrib.staticfiles",
"django.contrib.sites",
# Third-party
"allauth",
"allauth.account",
"crispy_forms",
"crispy_bootstrap5",
"debug_toolbar",
# Local
"accounts",
"pages",
]
# https://docs.djangoproject.com/en/dev/ref/settings/#middleware
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", # WhiteNoise
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"debug_toolbar.middleware.DebugToolbarMiddleware", # Django Debug Toolbar
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"allauth.account.middleware.AccountMiddleware", # django-allauth
]
# https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
ROOT_URLCONF = "django_project.urls"
# https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
WSGI_APPLICATION = "django_project.wsgi.application"
# https://docs.djangoproject.com/en/dev/ref/settings/#templates
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [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",
],
},
},
]
# https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# For Docker/PostgreSQL usage uncomment this and comment the DATABASES config above
# DATABASES = {
# "default": {
# "ENGINE": "django.db.backends.postgresql",
# "NAME": "postgres",
# "USER": "postgres",
# "PASSWORD": "postgres",
# "HOST": "db", # set in docker-compose.yml
# "PORT": 5432, # default postgres port
# }
# }
# Password validation
# 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",
},
]
# Internationalization
# https://docs.djangoproject.com/en/dev/topics/i18n/
# https://docs.djangoproject.com/en/dev/ref/settings/#language-code
LANGUAGE_CODE = "en-us"
# https://docs.djangoproject.com/en/dev/ref/settings/#time-zone
TIME_ZONE = "UTC"
# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-USE_I18N
USE_I18N = True
# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True
# https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths
LOCALE_PATHS = [BASE_DIR / 'locale']
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
# https://docs.djangoproject.com/en/dev/ref/settings/#static-root
STATIC_ROOT = BASE_DIR / "staticfiles"
# https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = "/static/"
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
STATICFILES_DIRS = [BASE_DIR / "static"]
# https://whitenoise.readthedocs.io/en/latest/django.html
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}
# Default primary key field type
# https://docs.djangoproject.com/en/stable/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# django-crispy-forms
# https://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap5'
CRISPY_TEMPLATE_PACK = "bootstrap5"
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
# https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email
DEFAULT_FROM_EMAIL = "root@localhost"
# django-debug-toolbar
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html
# https://docs.djangoproject.com/en/dev/ref/settings/#internal-ips
INTERNAL_IPS = ["127.0.0.1"]
# https://docs.djangoproject.com/en/dev/topics/auth/customizing/#substituting-a-custom-user-model
AUTH_USER_MODEL = "accounts.CustomUser"
# django-allauth config
# https://docs.djangoproject.com/en/dev/ref/settings/#site-id
SITE_ID = 1
# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
LOGIN_REDIRECT_URL = "home"
# https://django-allauth.readthedocs.io/en/latest/views.html#logout-account-logout
ACCOUNT_LOGOUT_REDIRECT_URL = "home"
# https://django-allauth.readthedocs.io/en/latest/installation.html?highlight=backends
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
)
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_SESSION_REMEMBER = True
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = "email"
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_UNIQUE_EMAIL = True

16
django_project/urls.py Normal file
View file

@ -0,0 +1,16 @@
from django.conf import settings
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("allauth.urls")),
path("", include("pages.urls")),
]
if settings.DEBUG:
import debug_toolbar
urlpatterns = [
path("__debug__/", include(debug_toolbar.urls)),
] + urlpatterns

View file

@ -2,6 +2,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangox.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings")
application = get_wsgi_application()

View file

@ -1,145 +0,0 @@
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

View file

@ -1,13 +0,0 @@
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
]

19
docker-compose.yml Normal file
View file

@ -0,0 +1,19 @@
services:
web:
build: .
command: python /code/manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- 8000:8000
depends_on:
- db
db:
image: postgres:16
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- "POSTGRES_HOST_AUTH_METHOD=trust"
volumes:
postgres_data:

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -1,9 +1,12 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangox.settings")
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
@ -13,3 +16,7 @@ if __name__ == "__main__":
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()

View file

@ -1,6 +0,0 @@
- tut on custom/auth...
- add about page...
- add base.html with nav, update other templates
- add 404, 403, 500 pages
- customize password reset...

View file

@ -2,4 +2,4 @@ from django.apps import AppConfig
class PagesConfig(AppConfig):
name = 'pages'
name = "pages"

View file

@ -1,8 +1,8 @@
from django.urls import path
from . import views
from .views import HomePageView, AboutPageView
urlpatterns = [
path('', views.HomePageView.as_view(), name='home'),
path('about/', views.AboutPageView.as_view(), name='about'),
path("", HomePageView.as_view(), name="home"),
path("about/", AboutPageView.as_view(), name="about"),
]

View file

@ -2,8 +2,8 @@ from django.views.generic import TemplateView
class HomePageView(TemplateView):
template_name = 'pages/home.html'
template_name = "pages/home.html"
class AboutPageView(TemplateView):
template_name = 'pages/about.html'
template_name = "pages/about.html"

26
requirements.txt Normal file
View file

@ -0,0 +1,26 @@
asgiref==3.8.1
certifi==2022.12.7
cffi==1.15.1
charset-normalizer==3.0.1
crispy-bootstrap5==2024.10
cryptography==39.0.1
defusedxml==0.7.1
Django==5.1.2
django-allauth==65.0.2
django-crispy-forms==2.3
django-debug-toolbar==4.4.6
gunicorn==23.0.0
idna==3.4
oauthlib==3.2.2
packaging==23.1
psycopg==3.2.3
psycopg-binary==3.2.3
pycparser==2.21
PyJWT==2.6.0
python3-openid==3.2.0
requests==2.28.2
requests-oauthlib==1.3.1
sqlparse==0.4.3
typing_extensions==4.9.0
urllib3==1.26.14
whitenoise==6.7.0

33
static/css/base.css Normal file
View file

@ -0,0 +1,33 @@
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
body {
margin-bottom: 60px; /* Margin bottom by footer height */
}
.container {
max-width: 960px;
}
.pricing-header {
max-width: 700px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
height: 60px; /* Set the fixed height of the footer here */
line-height: 60px; /* Vertically center the text there */
background-color: #f5f5f5;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

BIN
static/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

0
static/js/base.js Normal file
View file

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends '_base.html' %}
{% block title %}Forbidden (403){% endblock title %}

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends '_base.html' %}
{% block title %}404 Page not found{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends '_base.html' %}
{% block title %}500 Server Error{% endblock %}

95
templates/_base.html Normal file
View file

@ -0,0 +1,95 @@
{% load static %}
<!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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/base.css' %}">
{% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'home' %}">DjangoX</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="{% url 'home' %}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'about' %}">About</a>
</li>
</ul>
{% if user.is_authenticated %}
<div class="mr-auto">
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
aria-expanded="false">
Settings
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="#">{{ user.email }}</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href="{% url 'account_change_password' %}">Change password</a></li>
<li><a class="dropdown-item" href="{% url 'account_logout' %}">Sign out</a></li>
</ul>
</li>
</ul>
</div>
{% else %}
<div class="mr-auto">
<form class="form d-flex">
<a href="{% url 'account_login' %}" class="btn btn-outline-secondary">Log in</a>
<a href="{% url 'account_signup' %}" class="btn btn-primary ms-2">Sign up</a>
</form>
</div>
{% endif %}
</div>
</div>
</nav>
<div class="container">
{% block content %}
<p>Default content...</p>
{% endblock content %}
</div>
<footer class="footer">
<div class="container">
<span class="text-muted">Footer...</span>
</div>
</footer>
{% block javascript %}
<!-- Bootstrap JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
<!-- Project JS -->
<script src="{% static 'js/base.js' %}"></script>
{% endblock javascript %}
</body>
</html>

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block title %}Log in{% endblock %}
@ -10,5 +10,4 @@
{{ form|crispy }}
<button class="btn btn-success" type="submit">Log in</button>
</form>
<a href="{% url 'account_reset_password' %}">Forgot Password?</a>
{% endblock content %}
{% endblock content %}

View file

@ -0,0 +1,18 @@
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block title %}Log out{% endblock %}
{% block content %}
<h1>Sign Out</h1>
<p>Are you sure you want to sign out?</p>
<form method="post" action="{% url 'account_logout' %}">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-danger" type="submit">Sign Out</button>
</form>
{% endblock content %}

View file

@ -1,13 +1,13 @@
{% extends 'base.html' %}
{% 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">
<form method="post" action="{% url 'account_change_password' %}">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-success" type="submit">Change Password</button>
</form>
{% endblock content %}
{% endblock content %}

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block title %}Password Reset{% endblock %}
@ -7,9 +7,7 @@
<h2>Forgot your password? </h2>
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
{% csrf_token %}
{{ form|crispy }}
{{ form | crispy }}
<button class="btn btn-primary" type="submit">Reset Password</button>
</form>
{% endblock content %}

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block title %}Password Reset Done{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block title %}Change Password{% endblock title %}

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block title %}Change Password Done{% endblock title %}

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block title %}Set Password{% endblock title %}
@ -6,9 +6,10 @@
{% block content %}
<form method="POST" action="" class="password_set">
{% csrf_token %}
{% bootstrap_form password_set_form %}
{{ form | crispy }}
<div class="form-actions">
<button class="btn btn-primary" type="submit" name="action" value="{% trans "Set Password" %}"/>Change Password</button>
<button class="btn btn-primary" type="submit" name="action" value="Set Password">Change
Password</button>
</div>
</form>
{% endblock content %}

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block title %}Sign up{% endblock %}

View file

@ -1,76 +0,0 @@
{% 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

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends '_base.html' %}
{% block title %}About page{% endblock %}

View file

@ -1,10 +1,11 @@
{% extends 'base.html' %}
{% 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"/>
<div class="pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
<img src="{% static 'images/logo.png' %}" class="img-fluid" alt="DjangoX logo"/>
<p class="lead">A Django starter project with batteries.</p>
</div>
{% endblock content %}

View file

@ -1,6 +0,0 @@
<h2>Login</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Login</button>
</form>

View file

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

View file

@ -1,9 +0,0 @@
from django.contrib.auth.models import AbstractUser, UserManager
class CustomUserManager(UserManager):
pass
class CustomUser(AbstractUser):
objects = CustomUserManager()

View file

@ -1,6 +0,0 @@
from django.urls import path
from . import views
urlpatterns = [
path('signup/', views.SignUp.as_view(), name='signup'),
]

View file

@ -1,10 +0,0 @@
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'