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
|
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
|
Permission is hereby granted, free of charge, to any person
|
||||||
obtaining a copy of this software and associated documentation
|
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
|
- Django 5.1 & Python 3.12
|
||||||
* [Pipenv](https://github.com/pypa/pipenv) for virtualenvs
|
- Installation via [Pip](https://pypi.org/project/pip/) or [Docker](https://www.docker.com/)
|
||||||
* User registration via [django-allauth](https://github.com/pennersr/django-allauth)
|
- User authentication--log in, sign up, password reset--via [django-allauth](https://github.com/pennersr/django-allauth)
|
||||||
* Add social auth via Google, Facebook, etc
|
- Static files configured with [Whitenoise](http://whitenoise.evans.io/en/stable/index.html)
|
||||||
* [Bootstrap v4](https://getbootstrap.com/)
|
- Styling with [Bootstrap v5](https://getbootstrap.com/)
|
||||||
* Custom user model with email and no username
|
- 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/).
|
## 📖 Installation
|
||||||
2. Install packages with `pipenv install`
|
Lithium can be installed via Pip or Docker. To start, clone the repo to your local computer and change into the proper directory.
|
||||||
3. Activate a virtual environment with `pipenv shell`
|
|
||||||
4. Set up the initial migration for our custom user models in `users`
|
|
||||||
|
|
||||||
$ python manage.py makemigrations users
|
```
|
||||||
|
$ 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/)
|
## Next Steps
|
||||||
* Use [django-environ](https://github.com/joke2k/django-environ) for environment variables
|
|
||||||
* Update [EMAIL_BACKEND](https://docs.djangoproject.com/en/2.0/topics/email/#module-django.core.mail) to [configure an SMTP backend](https://djangoforbeginners.com/password-change-reset/)
|
|
||||||
* Add [django-debug-toolbar](https://github.com/jazzband/django-debug-toolbar) and [django-extensions](https://github.com/django-extensions/django-extensions)
|
|
||||||
|
|
||||||
* Make the [admin more secure](https://opensource.com/article/18/1/10-tips-making-django-admin-more-secure)
|
- 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)
|
## 🤝 Contributing
|
||||||
* [Configuring Facebook](http://www.sarahhagstrom.com/2013/09/the-missing-django-allauth-tutorial/#Create_and_configure_a_Facebook_app)
|
|
||||||
* [Configuring Github](https://wsvincent.com/django-allauth-tutorial/)
|
|
||||||
* `django-allauth` supports [many, many other providers in the official docs](https://django-allauth.readthedocs.io/en/latest/providers.html)
|
|
||||||
|
|
||||||
## Acknowledgments
|
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):
|
class CustomUserAdmin(UserAdmin):
|
||||||
model = CustomUser
|
|
||||||
add_form = CustomUserCreationForm
|
add_form = CustomUserCreationForm
|
||||||
form = CustomUserChangeForm
|
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)
|
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 django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||||
from .models import CustomUser
|
from .models import CustomUser
|
||||||
|
|
||||||
|
|
||||||
class CustomUserCreationForm(UserCreationForm):
|
class CustomUserCreationForm(UserCreationForm):
|
||||||
|
|
||||||
class Meta(UserCreationForm.Meta):
|
class Meta(UserCreationForm.Meta):
|
||||||
model = CustomUser
|
model = CustomUser
|
||||||
fields = ('username', 'email')
|
fields = ('email', 'username',)
|
||||||
|
|
||||||
|
|
||||||
class CustomUserChangeForm(UserChangeForm):
|
class CustomUserChangeForm(UserChangeForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomUser
|
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
|
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()
|
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
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
import os
|
import os
|
||||||
import sys
|
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:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
|
|
@ -13,3 +16,7 @@ if __name__ == "__main__":
|
||||||
"forget to activate a virtual environment?"
|
"forget to activate a virtual environment?"
|
||||||
) from exc
|
) from exc
|
||||||
execute_from_command_line(sys.argv)
|
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):
|
class PagesConfig(AppConfig):
|
||||||
name = 'pages'
|
name = "pages"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from .views import HomePageView, AboutPageView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.HomePageView.as_view(), name='home'),
|
path("", HomePageView.as_view(), name="home"),
|
||||||
path('about/', views.AboutPageView.as_view(), name='about'),
|
path("about/", AboutPageView.as_view(), name="about"),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
|
||||||
class HomePageView(TemplateView):
|
class HomePageView(TemplateView):
|
||||||
template_name = 'pages/home.html'
|
template_name = "pages/home.html"
|
||||||
|
|
||||||
|
|
||||||
class AboutPageView(TemplateView):
|
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 %}
|
{% block title %}Forbidden (403){% endblock title %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'base.html' %}
|
{% extends '_base.html' %}
|
||||||
|
|
||||||
{% block title %}404 Page not found{% endblock %}
|
{% block title %}404 Page not found{% endblock %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'base.html' %}
|
{% extends '_base.html' %}
|
||||||
|
|
||||||
{% block title %}500 Server Error{% endblock %}
|
{% 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 %}
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Log in{% endblock %}
|
{% block title %}Log in{% endblock %}
|
||||||
|
|
@ -10,5 +10,4 @@
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<button class="btn btn-success" type="submit">Log in</button>
|
<button class="btn btn-success" type="submit">Log in</button>
|
||||||
</form>
|
</form>
|
||||||
<a href="{% url 'account_reset_password' %}">Forgot Password?</a>
|
|
||||||
{% endblock content %}
|
{% 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 %}
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Change Password{% endblock %}
|
{% block title %}Change Password{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Change Password</h2>
|
<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 %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<button class="btn btn-success" type="submit">Change Password</button>
|
<button class="btn btn-success" type="submit">Change Password</button>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'base.html' %}
|
{% extends '_base.html' %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Password Reset{% endblock %}
|
{% block title %}Password Reset{% endblock %}
|
||||||
|
|
@ -7,9 +7,7 @@
|
||||||
<h2>Forgot your password? </h2>
|
<h2>Forgot your password? </h2>
|
||||||
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
|
<form method="POST" action="{% url 'account_reset_password' %}" class="password_reset">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form | crispy }}
|
||||||
<button class="btn btn-primary" type="submit">Reset Password</button>
|
<button class="btn btn-primary" type="submit">Reset Password</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'base.html' %}
|
{% extends '_base.html' %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Password Reset Done{% endblock %}
|
{% block title %}Password Reset Done{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'base.html' %}
|
{% extends '_base.html' %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Change Password{% endblock title %}
|
{% block title %}Change Password{% endblock title %}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'base.html' %}
|
{% extends '_base.html' %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Change Password Done{% endblock title %}
|
{% block title %}Change Password Done{% endblock title %}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'base.html' %}
|
{% extends '_base.html' %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Set Password{% endblock title %}
|
{% block title %}Set Password{% endblock title %}
|
||||||
|
|
@ -6,9 +6,10 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="POST" action="" class="password_set">
|
<form method="POST" action="" class="password_set">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_form password_set_form %}
|
{{ form | crispy }}
|
||||||
<div class="form-actions">
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'base.html' %}
|
{% extends '_base.html' %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Sign up{% endblock %}
|
{% 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 %}
|
{% block title %}About page{% endblock %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
{% extends 'base.html' %}
|
{% extends '_base.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}Home page{% endblock title %}
|
{% block title %}Home page{% endblock title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Home page</h1>
|
<div class="pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
|
||||||
<img src="{% static 'images/falconx.png' %}" class="img-fluid" alt="FalconX launch"/>
|
<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 %}
|
{% 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