clone from upstream lithium project
This commit is contained in:
parent
6f5167c24f
commit
f946e4933a
56 changed files with 754 additions and 503 deletions
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal 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
5
CONTRIBUTING.md
Normal 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
27
Dockerfile
Normal 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"]
|
||||
4
LICENSE
4
LICENSE
|
|
@ -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
|
||||
|
|
|
|||
16
Pipfile
16
Pipfile
|
|
@ -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
128
Pipfile.lock
generated
|
|
@ -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
137
README.md
|
|
@ -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
|
||||
|
||||

|
||||
## 👋 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)
|
||||
|
|
|
|||
|
|
@ -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
6
accounts/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'accounts'
|
||||
|
|
@ -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',)
|
||||
117
accounts/migrations/0001_initial.py
Normal file
117
accounts/migrations/0001_initial.py
Normal 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
8
accounts/models.py
Normal 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
3
accounts/views.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
0
django_project/__init__.py
Normal file
0
django_project/__init__.py
Normal file
7
django_project/asgi.py
Normal file
7
django_project/asgi.py
Normal 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
202
django_project/settings.py
Normal 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
16
django_project/urls.py
Normal 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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
19
docker-compose.yml
Normal 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
BIN
logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
11
manage.py
11
manage.py
|
|
@ -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()
|
||||
|
|
|
|||
6
notes.md
6
notes.md
|
|
@ -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...
|
||||
|
|
@ -2,4 +2,4 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class PagesConfig(AppConfig):
|
||||
name = 'pages'
|
||||
name = "pages"
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
26
requirements.txt
Normal 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
33
static/css/base.css
Normal 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
BIN
static/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
0
static/js/base.js
Normal file
0
static/js/base.js
Normal file
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html' %}
|
||||
{% extends '_base.html' %}
|
||||
|
||||
{% block title %}Forbidden (403){% endblock title %}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html' %}
|
||||
{% extends '_base.html' %}
|
||||
|
||||
{% block title %}404 Page not found{% endblock %}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html' %}
|
||||
{% extends '_base.html' %}
|
||||
|
||||
{% block title %}500 Server Error{% endblock %}
|
||||
|
||||
|
|
|
|||
95
templates/_base.html
Normal file
95
templates/_base.html
Normal 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>
|
||||
|
|
@ -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 %}
|
||||
18
templates/account/logout.html
Normal file
18
templates/account/logout.html
Normal 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 %}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
{% 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>
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html' %}
|
||||
{% extends '_base.html' %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}Password Reset Done{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html' %}
|
||||
{% extends '_base.html' %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}Change Password{% endblock title %}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html' %}
|
||||
{% extends '_base.html' %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}Change Password Done{% endblock title %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html' %}
|
||||
{% extends '_base.html' %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block title %}Sign up{% endblock %}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'base.html' %}
|
||||
{% extends '_base.html' %}
|
||||
|
||||
{% block title %}About page{% endblock %}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
<h2>Login</h2>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UsersConfig(AppConfig):
|
||||
name = 'users'
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
from django.contrib.auth.models import AbstractUser, UserManager
|
||||
|
||||
|
||||
class CustomUserManager(UserManager):
|
||||
pass
|
||||
|
||||
|
||||
class CustomUser(AbstractUser):
|
||||
objects = CustomUserManager()
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('signup/', views.SignUp.as_view(), name='signup'),
|
||||
]
|
||||
|
|
@ -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'
|
||||
Loading…
Add table
Add a link
Reference in a new issue