Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tsenovilla committed Feb 29, 2024
0 parents commit e3a9570
Show file tree
Hide file tree
Showing 76 changed files with 2,785 additions and 0 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/pre-commit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Pre-commit

on: [push, pull_request]

jobs:
pre-commit:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11"]

steps:
- name: Checkout source
uses: actions/checkout@v3

## Setup Python, dependencies and migrations, pre-commit needs them
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run migrations
run: |
python manage.py makemigrations
python manage.py migrate
- name: Check linter
run: |
black --check .
- name: Check coverage
run: |
if [ ! -e coverage/.coverage ] || [ ! -e coverage/coverage.svg ]; then
echo "Have you run pre-commit?"
exit 1
else
echo "coverage found"
fi
coverage run --data-file=coverage/.coverage-CI manage.py test
coverage report > coverage.txt
coverage report --data-file=coverage/.coverage-CI > coverage-CI.txt
diff coverage.txt coverage-CI.txt
coverage-badge -f -o coverage/coverage-CI.svg
diff coverage/coverage.svg coverage/coverage-CI.svg
echo "Pre-commit correct"
31 changes: 31 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Tests

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]

steps:
- name: Checkout source
uses: actions/checkout@v3

- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run migrations ## Migraciones para tener la BBDD como necesita la app.
run: |
python manage.py makemigrations
python manage.py migrate
- name: Run tests
run: python manage.py test
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
__pycache__
.env
*/migrations/*
!*/migrations/__init__.py
media
db.sqlite3
25 changes: 25 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
repos:
- repo: local
hooks:
- id: linter
name: linter
entry: |
black .
language: system
types: [python]

- id: run-code-coverage
name: Run Code Coverage
entry: |
coverage run manage.py test
language: system
pass_filenames: false
types: [python]

- id: create-coverage-badge
name: Create coverage badge
entry: |
coverage-badge -f -o coverage/coverage.svg
language: system
pass_filenames: false
types: [python]
7 changes: 7 additions & 0 deletions License.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Copyright © 2024 <Tomás Senovilla Polo>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
![CI](https://github.com/tsenovilla/django_extended_accounts/actions/workflows/tests.yaml/badge.svg)
![pre-commit](https://github.com/tsenovilla/django_extended_accounts/actions/workflows/pre-commit.yaml/badge.svg)
![Coverage](./coverage/coverage.svg)
![Black](https://img.shields.io/badge/code%20style-black-000000.svg)

# Description 📖📚

**django_extended_accounts** is a Django app designed to extend the Django's default authentication system. As this is a common task, this project aims to be a reusable template to help developers saving a lot of time.

This solution is based on getting rid of the Django's User model to use a custom model called AccountModel. This model is designed to only contain authentication-related information, while all personal data resides in another model called ProfileModel, which maintains a one-to-one relationship with AccountModel. The AccountModel provided is similar to the default User model in Django, with some differences:

- It does not include first and last names; these are stored in ProfileModel.
- Upon saving, it executes an additional query to save profile information in ProfileModel.
- Accounts are initially deactivated, requiring users to activate them via email after creation.

The decision behind this design is to keep the authentication model as simple as possible and avoid interference with other apps using this solution, allowing each app to specify its own user data requirements without conflicting assumptions. However, as this is just a template, you can change this design choice if you feel it's worthy of your project.

The ProfileModel contains initially the following fields:

- First name.
- Last name.
- Phone number.
- Profile Image

Since this is a template, other fields aren't included for simplicity, but as the app is fully customizable this model may be altered to fit your project's requirements.

The provided app deals with some usual concepts present in many website accounts' system, such as:

- Allows users to upload a profile image, which is automatically converted into WebP format for efficiency while also saving the original format. If the user updates/deletes the image or the user itself is deleted, the former is automatically removed from the server.

- Sends a confirmation email to the user once it creates its account. If the account is not confirmed in an arbitrary period of time, the account is removed from the ddbb. This is achieved by integrating Celery into the project as a daemon.

Feel free to add/remove any functionality needed by your project.

##### Important Considerations ⚠️ ❗️

- Reusable apps generally shouldn't implement a custom user model as specified in [Django docs](https://docs.djangoproject.com/en/5.0/topics/auth/customizing/#reusable-apps-and-auth-user-model). However, this app is an exception as it specifically deals with extending the user model.
- Changing the authentication model mid-project is non-trivial so this app should only be used in new projects. Check [Django docs](https://docs.djangoproject.com/en/5.0/topics/auth/customizing/#changing-to-a-custom-user-model-mid-project) for further information.
- Usually, you won't interact with the ProfileModel directly but through the AccountModel. AccountModel comes with two safe methods to which you can happily pass any argument related to ProfileModel.
These methods will automatically handle that data for you. In addition, these methods ensure the integrity of data if something goes wrong, as they rollback the database if saving to AccountModel or ProfileModel fails.
Using other methods such as ``save`` or ``create`` may cause undesired behavior, so use them only if you're perfectly fine with them.
The safe methods are:

- `create_user`: Use this method through the model manager to create a new account. Ex: ``Account.objects.create_user(username='johndoe', password='johndoe', email='[email protected]', phone_number=123456789)``

- `update`: Use this method directly on the model instance to update an account. Ex: ``account.update(username='johndoe', phone_number=123456789, first_name='John')``


## DRF Version 📱💡

If your project uses Django Rest Framework to construct an API, the [DRF version](https://github.com/tsenovilla/django_extended_accounts_api) may be more suitable for your needs.


## Usage 📌🖇️

This application serves as a template for extending the Django user model. It is meant to be customized, extended, or reduced according to project requirements. Since it is not published on PyPI, the code is open for editing as needed in your project. Therefore, to use it you can follow this general guidelines:

1. Copy the application into your Django project.
2. Add specific configurations detailed at the end of `django_extended_accounts/settings.py` to your project's settings.
3. Add URLs as specified in `django_extended_accounts/urls.py`.
4. If using Celery, add `django_extended_accounts/celery.py` to the project's main folder (where `settings.py` resides).

For the sake of simplicity, this project uses development configurations in some tasks such as image uploading or email sending. For production projects, configurations should be adapted.

## Note on Celery Integration 🤝

Refer to the Celery documentation ([Celery Documentation](https://docs.celeryq.dev/en/stable/userguide/configuration.html)) for comprehensive configuration details. The included configuration is minimal for simplicity.

If you feel that your project doesn't need Celery, you can happily remove it from the template following the next steps:

- Remove Celery from the project's requirements.
- Delete Celery configurations in `django_extended_accounts/settings.py`.
- Remove `django_extended_accounts/celery.py`, `extended_accounts/helpers/tasks.py`, and `extended_accounts/signals/post_save_account_model.py`.

## Contributing 📝

Any contribution is more than welcome! 😸🤝🦾

We use pre-commit to ensure code formatting with Black and to run code coverage, CI checks that this's been correctly done :). Please, adhere to these standards if you want to contribute.

Binary file added coverage/.coverage
Binary file not shown.
21 changes: 21 additions & 0 deletions coverage/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
16 changes: 16 additions & 0 deletions django_extended_accounts/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
ASGI config for django_extended_accounts project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_extended_accounts.settings")

application = get_asgi_application()
18 changes: 18 additions & 0 deletions django_extended_accounts/celery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import os
from celery import Celery

# Set the default configuration file
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_extended_accounts.settings")

celery_app = Celery(
"django_extended_accounts"
) ## Rename the app with your project's name :)

# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means that all celery-related configuration keys
# should have a prefix `CELERY_`.
celery_app.config_from_object("django.conf:settings", namespace="CELERY")

# Import celery task modules registered in all Django apps
celery_app.autodiscover_tasks()
110 changes: 110 additions & 0 deletions django_extended_accounts/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from django.urls import reverse_lazy
from pathlib import Path
import os
import sys

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = "django-insecure-5_z8k5snwu(im+iuha=@fo=p&bvp$#+_zilfdaozn2ugnm3g04"

DEBUG = True

ALLOWED_HOSTS = []


INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]

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 = "django_extended_accounts.urls"

TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"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 = "django_extended_accounts.wsgi.application"

DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}


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",
},
]

LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True


STATIC_URL = "static/"
STATIC_ROOT = os.path.join(BASE_DIR, STATIC_URL)
MEDIA_URL = "media/"
MEDIA_ROOT = os.path.join(BASE_DIR, MEDIA_URL)

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

## Settings related to the django_extended_account apps start here!
## Make sure to add them into your settings.py
## The settings are related to a dev environment, make sure to adapt accordingly with your needs

EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DEFAULT_FROM_EMAIL = "[email protected]"
TESTING = "test" in sys.argv
INTEGRATION_TEST_CELERY = False

## Celery settings.
## Here the configuration is minimal, refer to the official docs https://docs.celeryq.dev/en/stable/userguide/configuration.html to check out all the availables options. If you're not using Celery in your project, you can happily delete them.
CELERY_BROKER_URL = "pyamqp://"


## extended_accounts app

AUTH_USER_MODEL = "extended_accounts.AccountModel"
INSTALLED_APPS += ["extended_accounts"]
LOGIN_URL = reverse_lazy("extended_accounts:login")
LOGIN_REDIRECT_URL = reverse_lazy("extended_accounts:redirect_account")
LOGOUT_REDIRECT_URL = reverse_lazy("extended_accounts:login")
17 changes: 17 additions & 0 deletions django_extended_accounts/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.urls import path, include
from django.conf import settings

urlpatterns = []

## If you are using the extended_accounts app
urlpatterns += [
path(
"extended_accounts/",
include("extended_accounts.urls", namespace="extended_accounts"),
)
]
## Use this setting to correctly visualize your images in development django templates.
if settings.DEBUG:
from django.conf.urls.static import static

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Loading

0 comments on commit e3a9570

Please sign in to comment.