31 | Available commands are:
32 | list Print a list of all packages defined on ${OS_REQUIREMENTS_FILENAME} file
33 | help Print this help
34 |
35 | Commands that require superuser permission:
36 | install Install packages defined on ${OS_REQUIREMENTS_FILENAME} file. Note: This
37 | does not upgrade the packages already installed for new versions, even if
38 | new version is available in the repository.
39 | upgrade Same that install, but upgrade the already installed packages, if new
40 | version is available.
41 | EOF
42 | }
43 |
44 | # Read the requirements.apt file, and remove comments and blank lines
45 | function list_packages(){
46 | grep -v "#" "${OS_REQUIREMENTS_FILENAME}" | grep -v "^$";
47 | }
48 |
49 | function install_packages()
50 | {
51 | list_packages | xargs apt-get --no-upgrade install -y;
52 | }
53 |
54 | function upgrade_packages()
55 | {
56 | list_packages | xargs apt-get install -y;
57 | }
58 |
59 | function install_or_upgrade()
60 | {
61 | P=${1}
62 | PARAN=${P:-"install"}
63 |
64 | if [[ $EUID -ne 0 ]]; then
65 | cat <<-EOF >&2
66 | You must run this script with root privilege
67 | Please do:
68 | sudo $WORK_DIR/${0##*/} $PARAN
69 | EOF
70 | exit 1
71 | else
72 |
73 | apt-get update
74 |
75 | # Install the basic compilation dependencies and other required libraries of this project
76 | if [ "$PARAN" == "install" ]; then
77 | install_packages;
78 | else
79 | upgrade_packages;
80 | fi
81 |
82 | # cleaning downloaded packages from apt-get cache
83 | apt-get clean
84 |
85 | exit 0
86 | fi
87 | }
88 |
89 | # Handle command argument
90 | case "$1" in
91 | install) install_or_upgrade;;
92 | upgrade) install_or_upgrade "upgrade";;
93 | list) list_packages;;
94 | help|"") usage_message;;
95 | *) wrong_command "$1";;
96 | esac
97 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/utility/install_python_dependencies.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | WORK_DIR="$(dirname "$0")"
4 | PROJECT_DIR="$(dirname "$WORK_DIR")"
5 |
6 | pip --version >/dev/null 2>&1 || {
7 | echo >&2 -e "\npip is required but it's not installed."
8 | echo >&2 -e "You can install it by running the following command:\n"
9 | echo >&2 "wget https://bootstrap.pypa.io/get-pip.py --output-document=get-pip.py; chmod +x get-pip.py; sudo -H python3 get-pip.py"
10 | echo >&2 -e "\n"
11 | echo >&2 -e "\nFor more information, see pip documentation: https://pip.pypa.io/en/latest/"
12 | exit 1;
13 | }
14 |
15 | virtualenv --version >/dev/null 2>&1 || {
16 | echo >&2 -e "\nvirtualenv is required but it's not installed."
17 | echo >&2 -e "You can install it by running the following command:\n"
18 | echo >&2 "sudo -H pip3 install virtualenv"
19 | echo >&2 -e "\n"
20 | echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/"
21 | exit 1;
22 | }
23 |
24 | if [ -z "$VIRTUAL_ENV" ]; then
25 | echo >&2 -e "\nYou need activate a virtualenv first"
26 | echo >&2 -e 'If you do not have a virtualenv created, run the following command to create and automatically activate a new virtualenv named "venv" on current folder:\n'
27 | echo >&2 -e "virtualenv venv --python=\`which python3\`"
28 | echo >&2 -e "\nTo leave/disable the currently active virtualenv, run the following command:\n"
29 | echo >&2 "deactivate"
30 | echo >&2 -e "\nTo activate the virtualenv again, run the following command:\n"
31 | echo >&2 "source venv/bin/activate"
32 | echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/"
33 | echo >&2 -e "\n"
34 | exit 1;
35 | else
36 | pip install -r $PROJECT_DIR/requirements/local.txt
37 | {%- if cookiecutter.use_heroku == "y" -%}
38 | pip install -r $PROJECT_DIR/requirements.txt
39 | {%- endif %}
40 | fi
41 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/utility/requirements-bionic.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Ubuntu Bionic 18.04
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg8-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | libgraphviz-dev
24 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/utility/requirements-bullseye.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Debian Bullseye 11.x
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg62-turbo-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | libgraphviz-dev
24 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/utility/requirements-buster.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Debian Jessie 10.x
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg62-turbo-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | libgraphviz-dev
24 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/utility/requirements-focal.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Ubuntu Focal 20.04
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg8-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | graphviz-dev
24 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/utility/requirements-jessie.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Debian Jessie 8.x
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg62-turbo-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | graphviz-dev
24 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/utility/requirements-stretch.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Debian Jessie 9.x
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg62-turbo-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | graphviz-dev
24 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/utility/requirements-trusty.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Ubuntu Trusty 14.04
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff4-dev
17 | libjpeg8-dev
18 | libfreetype6-dev
19 | liblcms1-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | graphviz-dev
24 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/utility/requirements-xenial.apt:
--------------------------------------------------------------------------------
1 | ##basic build dependencies of various Django apps for Ubuntu Xenial 16.04
2 | #build-essential metapackage install: make, gcc, g++,
3 | build-essential
4 | #required to translate
5 | gettext
6 | python3-dev
7 |
8 | ##shared dependencies of:
9 | ##Pillow, pylibmc
10 | zlib1g-dev
11 |
12 | ##Postgresql and psycopg2 dependencies
13 | libpq-dev
14 |
15 | ##Pillow dependencies
16 | libtiff5-dev
17 | libjpeg8-dev
18 | libfreetype6-dev
19 | liblcms2-dev
20 | libwebp-dev
21 |
22 | ##django-extensions
23 | graphviz-dev
24 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "{{ cookiecutter.version }}"
2 | __version_info__ = tuple(
3 | int(num) if num.isdigit() else num
4 | for num in __version__.replace("-", ".", 1).split(".")
5 | )
6 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from {{ cookiecutter.project_slug }}.users.models import User
4 | from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
5 |
6 |
7 | @pytest.fixture(autouse=True)
8 | def media_storage(settings, tmpdir):
9 | settings.MEDIA_ROOT = tmpdir.strpath
10 |
11 |
12 | @pytest.fixture
13 | def user(db) -> User:
14 | return UserFactory()
15 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | import django.contrib.sites.models
2 | from django.contrib.sites.models import _simple_domain_name_validator
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = []
9 |
10 | operations = [
11 | migrations.CreateModel(
12 | name="Site",
13 | fields=[
14 | (
15 | "id",
16 | models.AutoField(
17 | verbose_name="ID",
18 | serialize=False,
19 | auto_created=True,
20 | primary_key=True,
21 | ),
22 | ),
23 | (
24 | "domain",
25 | models.CharField(
26 | max_length=100,
27 | verbose_name="domain name",
28 | validators=[_simple_domain_name_validator],
29 | ),
30 | ),
31 | ("name", models.CharField(max_length=50, verbose_name="display name")),
32 | ],
33 | options={
34 | "ordering": ("domain",),
35 | "db_table": "django_site",
36 | "verbose_name": "site",
37 | "verbose_name_plural": "sites",
38 | },
39 | bases=(models.Model,),
40 | managers=[("objects", django.contrib.sites.models.SiteManager())],
41 | )
42 | ]
43 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py:
--------------------------------------------------------------------------------
1 | import django.contrib.sites.models
2 | from django.db import migrations, models
3 |
4 |
5 | class Migration(migrations.Migration):
6 |
7 | dependencies = [("sites", "0001_initial")]
8 |
9 | operations = [
10 | migrations.AlterField(
11 | model_name="site",
12 | name="domain",
13 | field=models.CharField(
14 | max_length=100,
15 | unique=True,
16 | validators=[django.contrib.sites.models._simple_domain_name_validator],
17 | verbose_name="domain name",
18 | ),
19 | )
20 | ]
21 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 | from django.conf import settings
7 | from django.db import migrations
8 |
9 |
10 | def _update_or_create_site_with_sequence(site_model, connection, domain, name):
11 | """Update or create the site with default ID and keep the DB sequence in sync."""
12 | site, created = site_model.objects.update_or_create(
13 | id=settings.SITE_ID,
14 | defaults={
15 | "domain": domain,
16 | "name": name,
17 | },
18 | )
19 | if created:
20 | # We provided the ID explicitly when creating the Site entry, therefore the DB
21 | # sequence to auto-generate them wasn't used and is now out of sync. If we
22 | # don't do anything, we'll get a unique constraint violation the next time a
23 | # site is created.
24 | # To avoid this, we need to manually update DB sequence and make sure it's
25 | # greater than the maximum value.
26 | max_id = site_model.objects.order_by('-id').first().id
27 | with connection.cursor() as cursor:
28 | {%- if cookiecutter.database_engine == 'postgresql' %}
29 | cursor.execute("SELECT last_value from django_site_id_seq")
30 | (current_id,) = cursor.fetchone()
31 | if current_id <= max_id:
32 | cursor.execute(
33 | "alter sequence django_site_id_seq restart with %s",
34 | [max_id + 1],
35 | )
36 | {%- elif cookiecutter.database_engine == 'mysql' %}
37 | cursor.execute("SELECT MAX(id) FROM django_site")
38 | (current_id,) = cursor.fetchone()
39 | if current_id <= max_id:
40 | cursor.execute(
41 | "ALTER TABLE django_site AUTO_INCREMENT=%s",
42 | [max_id + 1],
43 | )
44 | {%- endif %}
45 |
46 |
47 | def update_site_forward(apps, schema_editor):
48 | """Set site domain and name."""
49 | Site = apps.get_model("sites", "Site")
50 | _update_or_create_site_with_sequence(
51 | Site,
52 | schema_editor.connection,
53 | "{{cookiecutter.domain_name}}",
54 | "{{cookiecutter.project_name}}",
55 | )
56 |
57 |
58 | def update_site_backward(apps, schema_editor):
59 | """Revert site domain and name to default."""
60 | Site = apps.get_model("sites", "Site")
61 | _update_or_create_site_with_sequence(
62 | Site,
63 | schema_editor.connection,
64 | "example.com",
65 | "example.com",
66 | )
67 |
68 |
69 | class Migration(migrations.Migration):
70 |
71 | dependencies = [("sites", "0002_alter_domain_unique")]
72 |
73 | operations = [migrations.RunPython(update_site_forward, update_site_backward)]
74 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0004_alter_options_ordering_domain.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.7 on 2021-02-04 14:49
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("sites", "0003_set_site_domain_and_name"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterModelOptions(
14 | name="site",
15 | options={
16 | "ordering": ["domain"],
17 | "verbose_name": "site",
18 | "verbose_name_plural": "sites",
19 | },
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | To understand why this file is here, please read:
3 |
4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django
5 | """
6 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/css/project.css:
--------------------------------------------------------------------------------
1 | /* These styles are generated from project.scss. */
2 |
3 | .alert-debug {
4 | color: black;
5 | background-color: white;
6 | border-color: #d6e9c6;
7 | }
8 |
9 | .alert-error {
10 | color: #b94a48;
11 | background-color: #f2dede;
12 | border-color: #eed3d7;
13 | }
14 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/fonts/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mabdullahadeel/cookiecutter-django-mysql/1db7745e2e161a7effda3c3dafa74003b291de3f/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/fonts/.gitkeep
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/images/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mabdullahadeel/cookiecutter-django-mysql/1db7745e2e161a7effda3c3dafa74003b291de3f/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/images/favicons/favicon.ico
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js:
--------------------------------------------------------------------------------
1 | /* Project specific Javascript goes here. */
2 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/custom_bootstrap_vars.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mabdullahadeel/cookiecutter-django-mysql/1db7745e2e161a7effda3c3dafa74003b291de3f/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/custom_bootstrap_vars.scss
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss:
--------------------------------------------------------------------------------
1 | @import "custom_bootstrap_vars";
2 | @import "bootstrap";
3 |
4 |
5 | // project specific CSS goes here
6 |
7 | ////////////////////////////////
8 | //Variables//
9 | ////////////////////////////////
10 |
11 | // Alert colors
12 |
13 | $white: #fff;
14 | $mint-green: #d6e9c6;
15 | $black: #000;
16 | $pink: #f2dede;
17 | $dark-pink: #eed3d7;
18 | $red: #b94a48;
19 |
20 | ////////////////////////////////
21 | //Alerts//
22 | ////////////////////////////////
23 |
24 | // bootstrap alert CSS, translated to the django-standard levels of
25 | // debug, info, success, warning, error
26 |
27 | .alert-debug {
28 | background-color: $white;
29 | border-color: $mint-green;
30 | color: $black;
31 | }
32 |
33 | .alert-error {
34 | background-color: $pink;
35 | border-color: $dark-pink;
36 | color: $red;
37 | }
38 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "base.html" %}
2 |
3 | {% block title %}Forbidden (403){% endblock %}
4 |
5 | {% block content %}
6 | Forbidden (403)
7 |
8 | {% if exception %}{{ exception }}{% else %}You're not allowed to access this page.{% endif %}
9 | {% endblock content %}
10 | {%- endraw %}
11 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "base.html" %}
2 |
3 | {% block title %}Page not found{% endblock %}
4 |
5 | {% block content %}
6 | Page not found
7 |
8 | {% if exception %}{{ exception }}{% else %}This is not the page you were looking for.{% endif %}
9 | {% endblock content %}
10 | {%- endraw %}
11 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "base.html" %}
2 |
3 | {% block title %}Server Error{% endblock %}
4 |
5 | {% block content %}
6 | Ooops!!! 500
7 |
8 | Looks like something went wrong!
9 |
10 | We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.
11 | {% endblock content %}
12 | {%- endraw %}
13 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% translate "Account Inactive" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% translate "Account Inactive" %}
9 |
10 | {% translate "This account is inactive." %}
11 | {% endblock %}
12 | {%- endraw %}
13 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "base.html" %}
2 | {% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %}
3 |
4 | {% block content %}
5 |
6 |
7 | {% block inner %}{% endblock %}
8 |
9 |
10 | {% endblock %}
11 | {%- endraw %}
12 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html:
--------------------------------------------------------------------------------
1 | {% raw %}
2 | {% extends "account/base.html" %}
3 |
4 | {% load i18n %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}{% translate "Account" %}{% endblock %}
8 |
9 | {% block inner %}
10 | {% translate "E-mail Addresses" %}
11 |
12 | {% if user.emailaddress_set.all %}
13 | {% translate 'The following e-mail addresses are associated with your account:' %}
14 |
15 |
44 |
45 | {% else %}
46 | {% translate 'Warning:'%} {% translate "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}
47 |
48 | {% endif %}
49 |
50 |
51 | {% translate "Add E-mail Address" %}
52 |
53 |
58 |
59 | {% endblock %}
60 |
61 |
62 | {% block inline_javascript %}
63 | {{ block.super }}
64 |
78 | {% endblock %}
79 | {%- endraw %}
80 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account %}
5 |
6 | {% block head_title %}{% translate "Confirm E-mail Address" %}{% endblock %}
7 |
8 |
9 | {% block inner %}
10 | {% translate "Confirm E-mail Address" %}
11 |
12 | {% if confirmation %}
13 |
14 | {% user_display confirmation.email_address.user as user_display %}
15 |
16 | {% blocktranslate with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktranslate %}
17 |
18 |
22 |
23 | {% else %}
24 |
25 | {% url 'account_email' as email_url %}
26 |
27 | {% blocktranslate %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktranslate %}
28 |
29 | {% endif %}
30 |
31 | {% endblock %}
32 | {%- endraw %}
33 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account socialaccount %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}{% translate "Sign In" %}{% endblock %}
8 |
9 | {% block inner %}
10 |
11 | {% translate "Sign In" %}
12 |
13 | {% get_providers as socialaccount_providers %}
14 |
15 | {% if socialaccount_providers %}
16 |
17 | {% translate "Please sign in with one of your existing third party accounts:" %}
18 | {% if ACCOUNT_ALLOW_REGISTRATION %}
19 | {% blocktranslate trimmed %}
20 | Or, sign up
21 | for a {{ site_name }} account and sign in below:
22 | {% endblocktranslate %}
23 | {% endif %}
24 |
25 |
26 |
27 |
28 |
29 | {% include "socialaccount/snippets/provider_list.html" with process="login" %}
30 |
31 |
32 |
{% translate "or" %}
33 |
34 |
35 |
36 | {% include "socialaccount/snippets/login_extra.html" %}
37 |
38 | {% else %}
39 | {% if ACCOUNT_ALLOW_REGISTRATION %}
40 |
41 | {% blocktranslate trimmed %}
42 | If you have not created an account yet, then please
43 | sign up first.
44 | {% endblocktranslate %}
45 |
46 | {% endif %}
47 | {% endif %}
48 |
49 |
58 |
59 | {% endblock %}
60 | {%- endraw %}
61 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% translate "Sign Out" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% translate "Sign Out" %}
9 |
10 | {% translate 'Are you sure you want to sign out?' %}
11 |
12 |
19 | {% endblock %}
20 | {%- endraw %}
21 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 |
6 | {% block head_title %}{% translate "Change Password" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% translate "Change Password" %}
10 |
11 |
16 | {% endblock %}
17 | {%- endraw %}
18 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account %}
5 | {% load crispy_forms_tags %}
6 |
7 | {% block head_title %}{% translate "Password Reset" %}{% endblock %}
8 |
9 | {% block inner %}
10 |
11 | {% translate "Password Reset" %}
12 | {% if user.is_authenticated %}
13 | {% include "account/snippets/already_logged_in.html" %}
14 | {% endif %}
15 |
16 | {% translate "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}
17 |
18 |
23 |
24 | {% blocktranslate %}Please contact us if you have any trouble resetting your password.{% endblocktranslate %}
25 | {% endblock %}
26 | {%- endraw %}
27 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load account %}
5 |
6 | {% block head_title %}{% translate "Password Reset" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% translate "Password Reset" %}
10 |
11 | {% if user.is_authenticated %}
12 | {% include "account/snippets/already_logged_in.html" %}
13 | {% endif %}
14 |
15 | {% blocktranslate %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %}
16 | {% endblock %}
17 | {%- endraw %}
18 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 | {% block head_title %}{% translate "Change Password" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% if token_fail %}{% translate "Bad Token" %}{% else %}{% translate "Change Password" %}{% endif %}
9 |
10 | {% if token_fail %}
11 | {% url 'account_reset_password' as passwd_reset_url %}
12 | {% blocktranslate %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktranslate %}
13 | {% else %}
14 | {% if form %}
15 |
20 | {% else %}
21 | {% translate 'Your password is now changed.' %}
22 | {% endif %}
23 | {% endif %}
24 | {% endblock %}
25 | {%- endraw %}
26 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% block head_title %}{% translate "Change Password" %}{% endblock %}
5 |
6 | {% block inner %}
7 | {% translate "Change Password" %}
8 | {% translate 'Your password is now changed.' %}
9 | {% endblock %}
10 | {%- endraw %}
11 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 |
6 | {% block head_title %}{% translate "Set Password" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% translate "Set Password" %}
10 |
11 |
16 | {% endblock %}
17 | {%- endraw %}
18 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 | {% load crispy_forms_tags %}
5 |
6 | {% block head_title %}{% translate "Signup" %}{% endblock %}
7 |
8 | {% block inner %}
9 | {% translate "Sign Up" %}
10 |
11 | {% blocktranslate %}Already have an account? Then please sign in.{% endblocktranslate %}
12 |
13 |
21 |
22 | {% endblock %}
23 | {%- endraw %}
24 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% translate "Sign Up Closed" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% translate "Sign Up Closed" %}
9 |
10 | {% translate "We are sorry, but the sign up is currently closed." %}
11 | {% endblock %}
12 | {%- endraw %}
13 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% translate "Verify Your E-mail Address" %}
9 |
10 | {% blocktranslate %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %}
11 |
12 | {% endblock %}
13 | {%- endraw %}
14 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "account/base.html" %}
2 |
3 | {% load i18n %}
4 |
5 | {% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %}
6 |
7 | {% block inner %}
8 | {% translate "Verify Your E-mail Address" %}
9 |
10 | {% url 'account_email' as email_url %}
11 |
12 | {% blocktranslate %}This part of the site requires us to verify that
13 | you are who you claim to be. For this purpose, we require that you
14 | verify ownership of your e-mail address. {% endblocktranslate %}
15 |
16 | {% blocktranslate %}We have sent an e-mail to you for
17 | verification. Please click on the link inside this e-mail. Please
18 | contact us if you do not receive it within a few minutes.{% endblocktranslate %}
19 |
20 | {% blocktranslate %}Note: you can still change your e-mail address.{% endblocktranslate %}
21 | {% endblock %}
22 | {%- endraw %}
23 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "base.html" %}{% endraw %}
2 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "base.html" %}{% endraw %}
2 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "base.html" %}
2 | {% load static %}
3 |
4 | {% block title %}User: {{ object.username }}{% endblock %}
5 |
6 | {% block content %}
7 |
8 |
9 |
10 |
11 |
12 |
{{ object.username }}
13 | {% if object.name %}
14 |
{{ object.name }}
15 | {% endif %}
16 |
17 |
18 |
19 | {% if object == request.user %}
20 |
21 |
30 |
31 | {% endif %}
32 |
33 |
34 | {% endblock content %}
35 | {%- endraw %}
36 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html:
--------------------------------------------------------------------------------
1 | {% raw %}{% extends "base.html" %}
2 | {% load crispy_forms_tags %}
3 |
4 | {% block title %}{{ user.username }}{% endblock %}
5 |
6 | {% block content %}
7 | {{ user.username }}
8 |
17 | {% endblock %}
18 | {%- endraw %}
19 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mabdullahadeel/cookiecutter-django-mysql/1db7745e2e161a7effda3c3dafa74003b291de3f/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/__init__.py
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 | from allauth.account.adapter import DefaultAccountAdapter
4 | from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
5 | from django.conf import settings
6 | from django.http import HttpRequest
7 |
8 |
9 | class AccountAdapter(DefaultAccountAdapter):
10 | def is_open_for_signup(self, request: HttpRequest):
11 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
12 |
13 |
14 | class SocialAccountAdapter(DefaultSocialAccountAdapter):
15 | def is_open_for_signup(self, request: HttpRequest, sociallogin: Any):
16 | return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
17 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.contrib.auth import admin as auth_admin
3 | from django.contrib.auth import get_user_model
4 | from django.utils.translation import gettext_lazy as _
5 |
6 | from {{ cookiecutter.project_slug }}.users.forms import UserAdminChangeForm, UserAdminCreationForm
7 |
8 | User = get_user_model()
9 |
10 |
11 | @admin.register(User)
12 | class UserAdmin(auth_admin.UserAdmin):
13 |
14 | form = UserAdminChangeForm
15 | add_form = UserAdminCreationForm
16 | fieldsets = (
17 | (None, {"fields": ("username", "password")}),
18 | (_("Personal info"), {"fields": ("name", "email")}),
19 | (
20 | _("Permissions"),
21 | {
22 | "fields": (
23 | "is_active",
24 | "is_staff",
25 | "is_superuser",
26 | "groups",
27 | "user_permissions",
28 | ),
29 | },
30 | ),
31 | (_("Important dates"), {"fields": ("last_login", "date_joined")}),
32 | )
33 | list_display = ["username", "name", "is_superuser"]
34 | search_fields = ["name"]
35 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from rest_framework import serializers
3 |
4 | User = get_user_model()
5 |
6 |
7 | class UserSerializer(serializers.ModelSerializer):
8 | class Meta:
9 | model = User
10 | fields = ["username", "name", "url"]
11 |
12 | extra_kwargs = {
13 | "url": {"view_name": "api:user-detail", "lookup_field": "username"}
14 | }
15 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from rest_framework import status
3 | from rest_framework.decorators import action
4 | from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
5 | from rest_framework.response import Response
6 | from rest_framework.viewsets import GenericViewSet
7 |
8 | from .serializers import UserSerializer
9 |
10 | User = get_user_model()
11 |
12 |
13 | class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet):
14 | serializer_class = UserSerializer
15 | queryset = User.objects.all()
16 | lookup_field = "username"
17 |
18 | def get_queryset(self, *args, **kwargs):
19 | assert isinstance(self.request.user.id, int)
20 | return self.queryset.filter(id=self.request.user.id)
21 |
22 | @action(detail=False)
23 | def me(self, request):
24 | serializer = UserSerializer(request.user, context={"request": request})
25 | return Response(status=status.HTTP_200_OK, data=serializer.data)
26 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 | from django.utils.translation import gettext_lazy as _
3 |
4 |
5 | class UsersConfig(AppConfig):
6 | name = "{{ cookiecutter.project_slug }}.users"
7 | verbose_name = _("Users")
8 |
9 | def ready(self):
10 | try:
11 | import {{ cookiecutter.project_slug }}.users.signals # noqa F401
12 | except ImportError:
13 | pass
14 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/context_processors.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 |
4 | def allauth_settings(request):
5 | """Expose some settings from django-allauth in templates."""
6 | return {
7 | "ACCOUNT_ALLOW_REGISTRATION": settings.ACCOUNT_ALLOW_REGISTRATION,
8 | }
9 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py:
--------------------------------------------------------------------------------
1 | from allauth.account.forms import SignupForm
2 | from allauth.socialaccount.forms import SignupForm as SocialSignupForm
3 | from django.contrib.auth import forms as admin_forms
4 | from django.contrib.auth import get_user_model
5 | from django.utils.translation import gettext_lazy as _
6 |
7 | User = get_user_model()
8 |
9 |
10 | class UserAdminChangeForm(admin_forms.UserChangeForm):
11 | class Meta(admin_forms.UserChangeForm.Meta):
12 | model = User
13 |
14 |
15 | class UserAdminCreationForm(admin_forms.UserCreationForm):
16 | """
17 | Form for User Creation in the Admin Area.
18 | To change user signup, see UserSignupForm and UserSocialSignupForm.
19 | """
20 |
21 | class Meta(admin_forms.UserCreationForm.Meta):
22 | model = User
23 |
24 | error_messages = {
25 | "username": {"unique": _("This username has already been taken.")}
26 | }
27 |
28 |
29 | class UserSignupForm(SignupForm):
30 | """
31 | Form that will be rendered on a user sign up section/screen.
32 | Default fields will be added automatically.
33 | Check UserSocialSignupForm for accounts created from social.
34 | """
35 |
36 |
37 | class UserSocialSignupForm(SocialSignupForm):
38 | """
39 | Renders the form when user has signed up using social accounts.
40 | Default fields will be added automatically.
41 | See UserSignupForm otherwise.
42 | """
43 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mabdullahadeel/cookiecutter-django-mysql/1db7745e2e161a7effda3c3dafa74003b291de3f/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/__init__.py
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import AbstractUser
2 | from django.db.models import CharField
3 | from django.urls import reverse
4 | from django.utils.translation import gettext_lazy as _
5 |
6 |
7 | class User(AbstractUser):
8 | """
9 | Default custom user model for {{cookiecutter.project_name}}.
10 | If adding fields that need to be filled at user signup,
11 | check forms.SignupForm and forms.SocialSignupForms accordingly.
12 | """
13 |
14 | #: First and last name do not cover name patterns around the globe
15 | name = CharField(_("Name of User"), blank=True, max_length=255)
16 | first_name = None # type: ignore
17 | last_name = None # type: ignore
18 |
19 | def get_absolute_url(self):
20 | """Get url for user's detail view.
21 |
22 | Returns:
23 | str: URL for user detail.
24 |
25 | """
26 | return reverse("users:detail", kwargs={"username": self.username})
27 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tasks.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 |
3 | from config import celery_app
4 |
5 | User = get_user_model()
6 |
7 |
8 | @celery_app.task()
9 | def get_users_count():
10 | """A pointless Celery task to demonstrate usage."""
11 | return User.objects.count()
12 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mabdullahadeel/cookiecutter-django-mysql/1db7745e2e161a7effda3c3dafa74003b291de3f/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/__init__.py
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py:
--------------------------------------------------------------------------------
1 | from collections.abc import Sequence
2 | from typing import Any
3 |
4 | from django.contrib.auth import get_user_model
5 | from factory import Faker, post_generation
6 | from factory.django import DjangoModelFactory
7 |
8 |
9 | class UserFactory(DjangoModelFactory):
10 |
11 | username = Faker("user_name")
12 | email = Faker("email")
13 | name = Faker("name")
14 |
15 | @post_generation
16 | def password(self, create: bool, extracted: Sequence[Any], **kwargs):
17 | password = (
18 | extracted
19 | if extracted
20 | else Faker(
21 | "password",
22 | length=42,
23 | special_chars=True,
24 | digits=True,
25 | upper_case=True,
26 | lower_case=True,
27 | ).evaluate(None, None, extra={"locale": None})
28 | )
29 | self.set_password(password)
30 |
31 | class Meta:
32 | model = get_user_model()
33 | django_get_or_create = ["username"]
34 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py:
--------------------------------------------------------------------------------
1 | from django.urls import reverse
2 |
3 | from {{ cookiecutter.project_slug }}.users.models import User
4 |
5 |
6 | class TestUserAdmin:
7 | def test_changelist(self, admin_client):
8 | url = reverse("admin:users_user_changelist")
9 | response = admin_client.get(url)
10 | assert response.status_code == 200
11 |
12 | def test_search(self, admin_client):
13 | url = reverse("admin:users_user_changelist")
14 | response = admin_client.get(url, data={"q": "test"})
15 | assert response.status_code == 200
16 |
17 | def test_add(self, admin_client):
18 | url = reverse("admin:users_user_add")
19 | response = admin_client.get(url)
20 | assert response.status_code == 200
21 |
22 | response = admin_client.post(
23 | url,
24 | data={
25 | "username": "test",
26 | "password1": "My_R@ndom-P@ssw0rd",
27 | "password2": "My_R@ndom-P@ssw0rd",
28 | },
29 | )
30 | assert response.status_code == 302
31 | assert User.objects.filter(username="test").exists()
32 |
33 | def test_view_user(self, admin_client):
34 | user = User.objects.get(username="admin")
35 | url = reverse("admin:users_user_change", kwargs={"object_id": user.pk})
36 | response = admin_client.get(url)
37 | assert response.status_code == 200
38 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import resolve, reverse
2 |
3 | from {{ cookiecutter.project_slug }}.users.models import User
4 |
5 |
6 | def test_user_detail(user: User):
7 | assert (
8 | reverse("api:user-detail", kwargs={"username": user.username})
9 | == f"/api/users/{user.username}/"
10 | )
11 | assert resolve(f"/api/users/{user.username}/").view_name == "api:user-detail"
12 |
13 |
14 | def test_user_list():
15 | assert reverse("api:user-list") == "/api/users/"
16 | assert resolve("/api/users/").view_name == "api:user-list"
17 |
18 |
19 | def test_user_me():
20 | assert reverse("api:user-me") == "/api/users/me/"
21 | assert resolve("/api/users/me/").view_name == "api:user-me"
22 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py:
--------------------------------------------------------------------------------
1 | from django.test import RequestFactory
2 |
3 | from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet
4 | from {{ cookiecutter.project_slug }}.users.models import User
5 |
6 |
7 | class TestUserViewSet:
8 | def test_get_queryset(self, user: User, rf: RequestFactory):
9 | view = UserViewSet()
10 | request = rf.get("/fake-url/")
11 | request.user = user
12 |
13 | view.request = request
14 |
15 | assert user in view.get_queryset()
16 |
17 | def test_me(self, user: User, rf: RequestFactory):
18 | view = UserViewSet()
19 | request = rf.get("/fake-url/")
20 | request.user = user
21 |
22 | view.request = request
23 |
24 | response = view.me(request)
25 |
26 | assert response.data == {
27 | "username": user.username,
28 | "name": user.name,
29 | "url": f"http://testserver/api/users/{user.username}/",
30 | }
31 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py:
--------------------------------------------------------------------------------
1 | """
2 | Module for all Form Tests.
3 | """
4 | from django.utils.translation import gettext_lazy as _
5 |
6 | from {{ cookiecutter.project_slug }}.users.forms import UserAdminCreationForm
7 | from {{ cookiecutter.project_slug }}.users.models import User
8 |
9 |
10 | class TestUserAdminCreationForm:
11 | """
12 | Test class for all tests related to the UserAdminCreationForm
13 | """
14 |
15 | def test_username_validation_error_msg(self, user: User):
16 | """
17 | Tests UserAdminCreation Form's unique validator functions correctly by testing:
18 | 1) A new user with an existing username cannot be added.
19 | 2) Only 1 error is raised by the UserCreation Form
20 | 3) The desired error message is raised
21 | """
22 |
23 | # The user already exists,
24 | # hence cannot be created.
25 | form = UserAdminCreationForm(
26 | {
27 | "username": user.username,
28 | "password1": user.password,
29 | "password2": user.password,
30 | }
31 | )
32 |
33 | assert not form.is_valid()
34 | assert len(form.errors) == 1
35 | assert "username" in form.errors
36 | assert form.errors["username"][0] == _("This username has already been taken.")
37 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py:
--------------------------------------------------------------------------------
1 | from {{ cookiecutter.project_slug }}.users.models import User
2 |
3 |
4 | def test_user_get_absolute_url(user: User):
5 | assert user.get_absolute_url() == f"/users/{user.username}/"
6 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from django.urls import reverse
3 |
4 |
5 | def test_swagger_accessible_by_admin(admin_client):
6 | url = reverse("api-docs")
7 | response = admin_client.get(url)
8 | assert response.status_code == 200
9 |
10 |
11 | @pytest.mark.django_db
12 | def test_swagger_ui_not_accessible_by_normal_user(client):
13 | url = reverse("api-docs")
14 | response = client.get(url)
15 | assert response.status_code == 403
16 |
17 |
18 | def test_api_schema_generated_successfully(admin_client):
19 | url = reverse("api-schema")
20 | response = admin_client.get(url)
21 | assert response.status_code == 200
22 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from celery.result import EagerResult
3 |
4 | from {{ cookiecutter.project_slug }}.users.tasks import get_users_count
5 | from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
6 |
7 | pytestmark = pytest.mark.django_db
8 |
9 |
10 | def test_user_count(settings):
11 | """A basic test to execute the get_users_count Celery task."""
12 | UserFactory.create_batch(3)
13 | settings.CELERY_TASK_ALWAYS_EAGER = True
14 | task_result = get_users_count.delay()
15 | assert isinstance(task_result, EagerResult)
16 | assert task_result.result == 3
17 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import resolve, reverse
2 |
3 | from {{ cookiecutter.project_slug }}.users.models import User
4 |
5 |
6 | def test_detail(user: User):
7 | assert (
8 | reverse("users:detail", kwargs={"username": user.username})
9 | == f"/users/{user.username}/"
10 | )
11 | assert resolve(f"/users/{user.username}/").view_name == "users:detail"
12 |
13 |
14 | def test_update():
15 | assert reverse("users:update") == "/users/~update/"
16 | assert resolve("/users/~update/").view_name == "users:update"
17 |
18 |
19 | def test_redirect():
20 | assert reverse("users:redirect") == "/users/~redirect/"
21 | assert resolve("/users/~redirect/").view_name == "users:redirect"
22 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from {{ cookiecutter.project_slug }}.users.views import (
4 | user_detail_view,
5 | user_redirect_view,
6 | user_update_view,
7 | )
8 |
9 | app_name = "users"
10 | urlpatterns = [
11 | path("~redirect/", view=user_redirect_view, name="redirect"),
12 | path("~update/", view=user_update_view, name="update"),
13 | path("/", view=user_detail_view, name="detail"),
14 | ]
15 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from django.contrib.auth.mixins import LoginRequiredMixin
3 | from django.contrib.messages.views import SuccessMessageMixin
4 | from django.urls import reverse
5 | from django.utils.translation import gettext_lazy as _
6 | from django.views.generic import DetailView, RedirectView, UpdateView
7 |
8 | User = get_user_model()
9 |
10 |
11 | class UserDetailView(LoginRequiredMixin, DetailView):
12 |
13 | model = User
14 | slug_field = "username"
15 | slug_url_kwarg = "username"
16 |
17 |
18 | user_detail_view = UserDetailView.as_view()
19 |
20 |
21 | class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
22 |
23 | model = User
24 | fields = ["name"]
25 | success_message = _("Information successfully updated")
26 |
27 | def get_success_url(self):
28 | assert (
29 | self.request.user.is_authenticated
30 | ) # for mypy to know that the user is authenticated
31 | return self.request.user.get_absolute_url()
32 |
33 | def get_object(self):
34 | return self.request.user
35 |
36 |
37 | user_update_view = UserUpdateView.as_view()
38 |
39 |
40 | class UserRedirectView(LoginRequiredMixin, RedirectView):
41 |
42 | permanent = False
43 |
44 | def get_redirect_url(self):
45 | return reverse("users:detail", kwargs={"username": self.request.user.username})
46 |
47 |
48 | user_redirect_view = UserRedirectView.as_view()
49 |
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mabdullahadeel/cookiecutter-django-mysql/1db7745e2e161a7effda3c3dafa74003b291de3f/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/__init__.py
--------------------------------------------------------------------------------
/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py:
--------------------------------------------------------------------------------
1 | {% if cookiecutter.cloud_provider == 'AWS' -%}
2 | from storages.backends.s3boto3 import S3Boto3Storage
3 |
4 |
5 | class StaticRootS3Boto3Storage(S3Boto3Storage):
6 | location = "static"
7 | default_acl = "public-read"
8 |
9 |
10 | class MediaRootS3Boto3Storage(S3Boto3Storage):
11 | location = "media"
12 | file_overwrite = False
13 | {%- elif cookiecutter.cloud_provider == 'GCP' -%}
14 | from storages.backends.gcloud import GoogleCloudStorage
15 |
16 |
17 | class StaticRootGoogleCloudStorage(GoogleCloudStorage):
18 | location = "static"
19 | default_acl = "publicRead"
20 |
21 |
22 | class MediaRootGoogleCloudStorage(GoogleCloudStorage):
23 | location = "media"
24 | file_overwrite = False
25 | {%- endif %}
26 |
--------------------------------------------------------------------------------