mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2024-11-30 21:44:06 +03:00
Merge commit '4b6e2b503a4fdc9739574c78b7f953c18def855a'
This commit is contained in:
commit
7510e009e6
|
@ -87,7 +87,9 @@ Listed in alphabetical order.
|
|||
David Díaz `@ddiazpinto`_ @DavidDiazPinto
|
||||
Davur Clementsen `@dsclementsen`_ @davur
|
||||
Delio Castillo `@jangeador`_ @jangeador
|
||||
Denis Orehovsky `@apirobot`_
|
||||
Dónal Adams `@epileptic-fish`_
|
||||
Diane Chen `@purplediane`_ @purplediane88
|
||||
Dong Huynh `@trungdong`_
|
||||
Emanuel Calso `@bloodpet`_ @bloodpet
|
||||
Eraldo Energy `@eraldo`_
|
||||
|
@ -98,11 +100,13 @@ Listed in alphabetical order.
|
|||
Garry Polley `@garrypolley`_
|
||||
Hamish Durkin `@durkode`_
|
||||
Harry Percival `@hjwp`_
|
||||
Hendrik Schneider `@hendrikschneider`_
|
||||
Henrique G. G. Pereira `@ikkebr`_
|
||||
Ian Lee `@IanLee1521`_
|
||||
Jan Van Bruggen `@jvanbrug`_
|
||||
Jens Nilsson `@phiberjenz`_
|
||||
Jimmy Gitonga `@afrowave`_ @afrowave
|
||||
John Cass `@jcass77`_ @cass_john
|
||||
Julien Almarcha `@sladinji`_
|
||||
Julio Castillo `@juliocc`_
|
||||
Kaido Kert `@kaidokert`_
|
||||
|
@ -121,6 +125,7 @@ Listed in alphabetical order.
|
|||
Malik Sulaimanov `@flyudvik`_ @flyudvik
|
||||
Martin Blech
|
||||
Martin Saizar `@msaizar`_
|
||||
Mateusz Ostaszewski `@mostaszewski`_
|
||||
Mathijs Hoogland `@MathijsHoogland`_
|
||||
Matt Braymer-Hayes `@mattayes`_ @mattayes
|
||||
Matt Linares
|
||||
|
@ -161,6 +166,7 @@ Listed in alphabetical order.
|
|||
Will Farley `@goldhand`_ @g01dhand
|
||||
William Archinal `@archinal`_
|
||||
Yaroslav Halchenko
|
||||
Denis Bobrov `@delneg`_
|
||||
========================== ============================ ==============
|
||||
|
||||
.. _@a7p: https://github.com/a7p
|
||||
|
@ -172,6 +178,7 @@ Listed in alphabetical order.
|
|||
.. _@amjith: https://github.com/amjith
|
||||
.. _@andor-pierdelacabeza: https://github.com/andor-pierdelacabeza
|
||||
.. _@antoniablair: https://github.com/antoniablair
|
||||
.. _@apirobot: https://github.com/apirobot
|
||||
.. _@archinal: https://github.com/archinal
|
||||
.. _@areski: https://github.com/areski
|
||||
.. _@arruda: https://github.com/arruda
|
||||
|
@ -206,6 +213,7 @@ Listed in alphabetical order.
|
|||
.. _@goldhand: https://github.com/goldhand
|
||||
.. _@hackebrot: https://github.com/hackebrot
|
||||
.. _@hairychris: https://github.com/hairychris
|
||||
.. _@hendrikschneider https://github.com/hendrikschneider
|
||||
.. _@hjwp: https://github.com/hjwp
|
||||
.. _@IanLee1521: https://github.com/IanLee1521
|
||||
.. _@ikkebr: https://github.com/ikkebr
|
||||
|
@ -223,6 +231,7 @@ Listed in alphabetical order.
|
|||
.. _@MathijsHoogland: https://github.com/MathijsHoogland
|
||||
.. _@mattayes: https://github.com/mattayes
|
||||
.. _@menzenski: https://github.com/menzenski
|
||||
.. _@mostaszewski: https://github.com/mostaszewski
|
||||
.. _@mfwarren: https://github.com/mfwarren
|
||||
.. _@mimischi: https://github.com/mimischi
|
||||
.. _@mjsisley: https://github.com/mjsisley
|
||||
|
@ -263,7 +272,8 @@ Listed in alphabetical order.
|
|||
.. _@brentpayne: https://github.com/brentpayne
|
||||
.. _@afrowave: https://github.com/afrowave
|
||||
.. _@pchiquet: https://github.com/pchiquet
|
||||
|
||||
.. _@delneg: https://github.com/delneg
|
||||
.. _@purplediane: https://github.com/purplediane
|
||||
Special Thanks
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ Features
|
|||
* For Django 2.0
|
||||
* Works with Python 3.6
|
||||
* Renders Django projects with 100% starting test coverage
|
||||
* Twitter Bootstrap_ v4.0.0 (`maintained Foundation fork`_ also available)
|
||||
* Twitter Bootstrap_ v4.1.1 (`maintained Foundation fork`_ also available)
|
||||
* 12-Factor_ based settings via django-environ_
|
||||
* Secure by default. We believe in SSL.
|
||||
* Optimized development and production settings
|
||||
|
@ -65,7 +65,7 @@ Optional Integrations
|
|||
*These features can be enabled during initial project setup.*
|
||||
|
||||
* Serve static files from Amazon S3 or Whitenoise_
|
||||
* Configuration for Celery_
|
||||
* Configuration for Celery_ and Flower_ (the latter in Docker setup only)
|
||||
* Integration with MailHog_ for local email testing
|
||||
* Integration with Sentry_ for error logging
|
||||
|
||||
|
@ -78,6 +78,7 @@ Optional Integrations
|
|||
.. _Mailgun: http://www.mailgun.com/
|
||||
.. _Whitenoise: https://whitenoise.readthedocs.io/
|
||||
.. _Celery: http://www.celeryproject.org/
|
||||
.. _Flower: https://github.com/mher/flower
|
||||
.. _Anymail: https://github.com/anymail/django-anymail
|
||||
.. _MailHog: https://github.com/mailhog/MailHog
|
||||
.. _Sentry: https://sentry.io/welcome/
|
||||
|
|
|
@ -21,17 +21,27 @@ Run these commands to deploy the project to Heroku:
|
|||
heroku addons:create sentry:f1
|
||||
|
||||
heroku config:set PYTHONHASHSEED=random
|
||||
|
||||
heroku config:set WEB_CONCURRENCY=4
|
||||
|
||||
heroku config:set DJANGO_DEBUG=False
|
||||
heroku config:set DJANGO_SETTINGS_MODULE=config.settings.production
|
||||
heroku config:set DJANGO_SECRET_KEY="$(openssl rand -base64 64)"
|
||||
|
||||
# Generating a 32 character-long random string without any of the visually similiar characters "IOl01":
|
||||
heroku config:set DJANGO_ADMIN_URL="$(openssl rand -base64 4096 | tr -dc 'A-HJ-NP-Za-km-z2-9' | head -c 32)/"
|
||||
heroku config:set DJANGO_ALLOWED_HOSTS= # Set this to your Heroku app url, e.g. 'bionic-beaver-28392.herokuapp.com'
|
||||
|
||||
heroku config:set DJANGO_AWS_ACCESS_KEY_ID= # Assign with AWS_ACCESS_KEY_ID
|
||||
heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY= # Assign with AWS_SECRET_ACCESS_KEY
|
||||
heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME= # Assign with AWS_STORAGE_BUCKET_NAME
|
||||
# Set this to your Heroku app url, e.g. 'bionic-beaver-28392.herokuapp.com'
|
||||
heroku config:set DJANGO_ALLOWED_HOSTS=
|
||||
|
||||
# Assign with AWS_ACCESS_KEY_ID
|
||||
heroku config:set DJANGO_AWS_ACCESS_KEY_ID=
|
||||
|
||||
# Assign with AWS_SECRET_ACCESS_KEY
|
||||
heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY=
|
||||
|
||||
# Assign with AWS_STORAGE_BUCKET_NAME
|
||||
heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME=
|
||||
|
||||
git push heroku master
|
||||
|
||||
|
|
|
@ -21,10 +21,13 @@ Before you begin, check out the ``production.yml`` file in the root of this proj
|
|||
* ``redis``: Redis instance for caching;
|
||||
* ``caddy``: Caddy web server with HTTPS on by default.
|
||||
|
||||
Provided you have opted for Celery (via setting ``use_celery`` to ``y``) there are two more services:
|
||||
Provided you have opted for Celery (via setting ``use_celery`` to ``y``) there are three more services:
|
||||
|
||||
* ``celeryworker`` running a Celery worker process;
|
||||
* ``celerybeat`` running a Celery beat process.
|
||||
* ``celerybeat`` running a Celery beat process;
|
||||
* ``flower`` running Flower_ (for more info, check out :ref:`CeleryFlower` instructions for local environment).
|
||||
|
||||
.. _`Flower`: https://github.com/mher/flower
|
||||
|
||||
|
||||
Configuring the Stack
|
||||
|
@ -70,7 +73,7 @@ You can read more about this here at `Automatic HTTPS`_ in the Caddy docs.
|
|||
(Optional) Postgres Data Volume Modifications
|
||||
---------------------------------------------
|
||||
|
||||
Postgres is saving its database files to the ``postgres_data`` volume by default. Change that if you want something else and make sure to make backups since this is not done automatically.
|
||||
Postgres is saving its database files to the ``production_postgres_data`` volume by default. Change that if you want something else and make sure to make backups since this is not done automatically.
|
||||
|
||||
|
||||
Building & Running Production Stack
|
||||
|
|
|
@ -91,8 +91,8 @@ This is the excerpt from your project's ``local.yml``: ::
|
|||
context: .
|
||||
dockerfile: ./compose/production/postgres/Dockerfile
|
||||
volumes:
|
||||
- postgres_data_local:/var/lib/postgresql/data
|
||||
- postgres_backup_local:/backups
|
||||
- local_postgres_data:/var/lib/postgresql/data
|
||||
- local_postgres_data_backups:/backups
|
||||
env_file:
|
||||
- ./.envs/.local/.postgres
|
||||
|
||||
|
@ -170,3 +170,20 @@ When developing locally you can go with MailHog_ for email testing provided ``us
|
|||
#. open up ``http://127.0.0.1:8025``.
|
||||
|
||||
.. _Mailhog: https://github.com/mailhog/MailHog/
|
||||
|
||||
|
||||
.. _`CeleryFlower`:
|
||||
|
||||
Celery Flower
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
`Flower`_ is a "real-time monitor and web admin for Celery distributed task queue".
|
||||
|
||||
Prerequisites:
|
||||
|
||||
* ``use_docker`` was set to ``y`` on project initialization;
|
||||
* ``use_celery`` was set to ``y`` on project initialization.
|
||||
|
||||
By default, it's enabled both in local and production environments (``local.yml`` and ``production.yml`` Docker Compose configs, respectively) through a ``flower`` service. For added security, ``flower`` requires its clients to provide authentication credentials specified as the corresponding environments' ``.envs/.local/.django`` and ``.envs/.production/.django`` ``CELERY_FLOWER_USER`` and ``CELERY_FLOWER_PASSWORD`` environment variables. Check out ``localhost:5555`` and see for yourself.
|
||||
|
||||
.. _`Flower`: https://github.com/mher/flower
|
||||
|
|
|
@ -94,6 +94,7 @@ keep_local_envs_in_vcs:
|
|||
Indicates whether the project's ``.envs/.local/`` should be kept in VCS
|
||||
(comes in handy when working in teams where local environment reproducibility
|
||||
is strongly encouraged).
|
||||
Note: .env(s) are only utilized when Docker Compose and/or Heroku support is enabled.
|
||||
|
||||
debug:
|
||||
Indicates whether the project should be configured for debugging.
|
||||
|
|
|
@ -32,7 +32,10 @@ DEBUG_VALUE = "debug"
|
|||
|
||||
|
||||
def remove_open_source_files():
|
||||
file_names = ["CONTRIBUTORS.txt"]
|
||||
file_names = [
|
||||
"CONTRIBUTORS.txt",
|
||||
"LICENSE",
|
||||
]
|
||||
for file_name in file_names:
|
||||
os.remove(file_name)
|
||||
|
||||
|
@ -61,6 +64,10 @@ def remove_docker_files():
|
|||
os.remove(file_name)
|
||||
|
||||
|
||||
def remove_utility_files():
|
||||
shutil.rmtree("utility")
|
||||
|
||||
|
||||
def remove_heroku_files():
|
||||
file_names = ["Procfile", "runtime.txt", "requirements.txt"]
|
||||
for file_name in file_names:
|
||||
|
@ -162,8 +169,12 @@ def set_django_admin_url(file_path):
|
|||
return django_admin_url
|
||||
|
||||
|
||||
def generate_random_user():
|
||||
return generate_random_string(length=32, using_ascii_letters=True)
|
||||
|
||||
|
||||
def generate_postgres_user(debug=False):
|
||||
return DEBUG_VALUE if debug else generate_random_string(length=32, using_ascii_letters=True)
|
||||
return DEBUG_VALUE if debug else generate_random_user()
|
||||
|
||||
|
||||
def set_postgres_user(file_path, value):
|
||||
|
@ -187,25 +198,56 @@ def set_postgres_password(file_path, value=None):
|
|||
return postgres_password
|
||||
|
||||
|
||||
def set_celery_flower_user(file_path, value):
|
||||
celery_flower_user = set_flag(
|
||||
file_path,
|
||||
"!!!SET CELERY_FLOWER_USER!!!",
|
||||
value=value,
|
||||
)
|
||||
return celery_flower_user
|
||||
|
||||
|
||||
def set_celery_flower_password(file_path, value=None):
|
||||
celery_flower_password = set_flag(
|
||||
file_path,
|
||||
"!!!SET CELERY_FLOWER_PASSWORD!!!",
|
||||
value=value,
|
||||
length=64,
|
||||
using_digits=True,
|
||||
using_ascii_letters=True,
|
||||
)
|
||||
return celery_flower_password
|
||||
|
||||
|
||||
def append_to_gitignore_file(s):
|
||||
with open(".gitignore", "a") as gitignore_file:
|
||||
gitignore_file.write(s)
|
||||
gitignore_file.write(os.linesep)
|
||||
|
||||
|
||||
def set_flags_in_envs(postgres_user, debug=False):
|
||||
local_postgres_envs_path = os.path.join(".envs", ".local", ".postgres")
|
||||
set_postgres_user(local_postgres_envs_path, value=postgres_user)
|
||||
set_postgres_password(local_postgres_envs_path, value=DEBUG_VALUE if debug else None)
|
||||
|
||||
def set_flags_in_envs(
|
||||
postgres_user,
|
||||
celery_flower_user,
|
||||
debug=False,
|
||||
):
|
||||
local_django_envs_path = os.path.join(".envs", ".local", ".django")
|
||||
production_django_envs_path = os.path.join(".envs", ".production", ".django")
|
||||
local_postgres_envs_path = os.path.join(".envs", ".local", ".postgres")
|
||||
production_postgres_envs_path = os.path.join(".envs", ".production", ".postgres")
|
||||
|
||||
set_django_secret_key(production_django_envs_path)
|
||||
set_django_admin_url(production_django_envs_path)
|
||||
|
||||
production_postgres_envs_path = os.path.join(".envs", ".production", ".postgres")
|
||||
set_postgres_user(local_postgres_envs_path, value=postgres_user)
|
||||
set_postgres_password(local_postgres_envs_path, value=DEBUG_VALUE if debug else None)
|
||||
set_postgres_user(production_postgres_envs_path, value=postgres_user)
|
||||
set_postgres_password(production_postgres_envs_path, value=DEBUG_VALUE if debug else None)
|
||||
|
||||
set_celery_flower_user(local_django_envs_path, value=celery_flower_user)
|
||||
set_celery_flower_password(local_django_envs_path, value=DEBUG_VALUE if debug else None)
|
||||
set_celery_flower_user(production_django_envs_path, value=celery_flower_user)
|
||||
set_celery_flower_password(production_django_envs_path, value=DEBUG_VALUE if debug else None)
|
||||
|
||||
|
||||
def set_flags_in_settings_files():
|
||||
set_django_secret_key(os.path.join("config", "settings", "local.py"))
|
||||
|
@ -223,8 +265,13 @@ def remove_celery_compose_dirs():
|
|||
|
||||
|
||||
def main():
|
||||
postgres_user = generate_postgres_user(debug="{{ cookiecutter.debug }}".lower() == "y")
|
||||
set_flags_in_envs(postgres_user, debug="{{ cookiecutter.debug }}".lower() == "y")
|
||||
debug = "{{ cookiecutter.debug }}".lower() == "y"
|
||||
|
||||
set_flags_in_envs(
|
||||
DEBUG_VALUE if debug else generate_random_user(),
|
||||
DEBUG_VALUE if debug else generate_random_user(),
|
||||
debug=debug,
|
||||
)
|
||||
set_flags_in_settings_files()
|
||||
|
||||
if "{{ cookiecutter.open_source_license }}" == "Not open source":
|
||||
|
@ -235,7 +282,9 @@ def main():
|
|||
if "{{ cookiecutter.use_pycharm }}".lower() == "n":
|
||||
remove_pycharm_files()
|
||||
|
||||
if "{{ cookiecutter.use_docker }}".lower() == "n":
|
||||
if "{{ cookiecutter.use_docker }}".lower() == "y":
|
||||
remove_utility_files()
|
||||
else:
|
||||
remove_docker_files()
|
||||
|
||||
if "{{ cookiecutter.use_heroku }}".lower() == "n":
|
||||
|
|
|
@ -8,6 +8,6 @@ flake8==3.5.0
|
|||
|
||||
# Testing
|
||||
# ------------------------------------------------------------------------------
|
||||
tox==3.0.0
|
||||
pytest==3.6.1
|
||||
tox==3.2.1
|
||||
pytest==3.7.3
|
||||
pytest-cookies==0.3.0
|
||||
|
|
|
@ -14,8 +14,11 @@ cd .cache/docker
|
|||
cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y
|
||||
cd my_awesome_project
|
||||
|
||||
# run the project's type checks
|
||||
docker-compose -f local.yml run django mypy my_awesome_project
|
||||
|
||||
# run the project's tests
|
||||
docker-compose -f local.yml run django python manage.py test
|
||||
docker-compose -f local.yml run django pytest
|
||||
|
||||
# return non-zero status code if there are migrations that have not been created
|
||||
docker-compose -f local.yml run django python manage.py makemigrations --dry-run --check || { echo "ERROR: there were changes in the models, but migration listed above have not been created and are not saved in version control"; exit 1; }
|
||||
|
|
|
@ -5,3 +5,11 @@ USE_DOCKER=yes
|
|||
# Redis
|
||||
# ------------------------------------------------------------------------------
|
||||
REDIS_URL=redis://redis:6379/0
|
||||
{% if cookiecutter.use_celery == 'y' %}
|
||||
# Celery
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Flower
|
||||
CELERY_FLOWER_USER=!!!SET CELERY_FLOWER_USER!!!
|
||||
CELERY_FLOWER_PASSWORD=!!!SET CELERY_FLOWER_PASSWORD!!!
|
||||
{% endif %}
|
||||
|
|
|
@ -43,3 +43,11 @@ SENTRY_DSN=
|
|||
# Redis
|
||||
# ------------------------------------------------------------------------------
|
||||
REDIS_URL=redis://redis:6379/0
|
||||
{% if cookiecutter.use_celery == 'y' %}
|
||||
# Celery
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Flower
|
||||
CELERY_FLOWER_USER=!!!SET CELERY_FLOWER_USER!!!
|
||||
CELERY_FLOWER_PASSWORD=!!!SET CELERY_FLOWER_PASSWORD!!!
|
||||
{% endif %}
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests - all" type="DjangoTestsConfigurationType" factoryName="Django tests" singleton="true">
|
||||
<configuration default="false" name="pytest: ." type="tests" factoryName="py.test" singleton="true">
|
||||
<module name="{{ cookiecutter.project_slug }}" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.test" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||
<PathMappingSettings>
|
||||
<option name="pathMappings">
|
||||
<list>
|
||||
|
@ -20,11 +16,10 @@
|
|||
</list>
|
||||
</option>
|
||||
</PathMappingSettings>
|
||||
<option name="TARGET" value="." />
|
||||
<option name="SETTINGS_FILE" value="" />
|
||||
<option name="CUSTOM_SETTINGS" value="false" />
|
||||
<option name="USE_OPTIONS" value="false" />
|
||||
<option name="OPTIONS" value="" />
|
||||
<option name="_new_keywords" value="""" />
|
||||
<option name="_new_additionalArguments" value="""" />
|
||||
<option name="_new_target" value=""."" />
|
||||
<option name="_new_targetType" value=""PATH"" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,18 +1,14 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests - module: users" type="DjangoTestsConfigurationType" factoryName="Django tests" singleton="true">
|
||||
<configuration default="false" name="pytest: users" type="tests" factoryName="py.test" singleton="true">
|
||||
<module name="{{ cookiecutter.project_slug }}" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.test" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||
<PathMappingSettings>
|
||||
<option name="pathMappings">
|
||||
<list>
|
||||
|
@ -20,11 +16,10 @@
|
|||
</list>
|
||||
</option>
|
||||
</PathMappingSettings>
|
||||
<option name="TARGET" value="{{ cookiecutter.project_slug }}.users" />
|
||||
<option name="SETTINGS_FILE" value="" />
|
||||
<option name="CUSTOM_SETTINGS" value="false" />
|
||||
<option name="USE_OPTIONS" value="false" />
|
||||
<option name="OPTIONS" value="" />
|
||||
<option name="_new_keywords" value="""" />
|
||||
<option name="_new_additionalArguments" value="""" />
|
||||
<option name="_new_target" value=""./{{ cookiecutter.project_slug }}/users/"" />
|
||||
<option name="_new_targetType" value=""PATH"" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,30 +0,0 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests - class: TestUser" type="DjangoTestsConfigurationType" factoryName="Django tests" singleton="true">
|
||||
<module name="{{ cookiecutter.project_slug }}" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.test" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||
<PathMappingSettings>
|
||||
<option name="pathMappings">
|
||||
<list>
|
||||
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
|
||||
</list>
|
||||
</option>
|
||||
</PathMappingSettings>
|
||||
<option name="TARGET" value="{{ cookiecutter.project_slug }}.users.tests.test_models.TestUser" />
|
||||
<option name="SETTINGS_FILE" value="" />
|
||||
<option name="CUSTOM_SETTINGS" value="false" />
|
||||
<option name="USE_OPTIONS" value="false" />
|
||||
<option name="OPTIONS" value="" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,30 +0,0 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests - file: test_models" type="DjangoTestsConfigurationType" factoryName="Django tests" singleton="true">
|
||||
<module name="{{ cookiecutter.project_slug }}" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.test" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||
<PathMappingSettings>
|
||||
<option name="pathMappings">
|
||||
<list>
|
||||
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
|
||||
</list>
|
||||
</option>
|
||||
</PathMappingSettings>
|
||||
<option name="TARGET" value="{{ cookiecutter.project_slug }}.users.tests.test_models" />
|
||||
<option name="SETTINGS_FILE" value="" />
|
||||
<option name="CUSTOM_SETTINGS" value="false" />
|
||||
<option name="USE_OPTIONS" value="false" />
|
||||
<option name="OPTIONS" value="" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -1,30 +0,0 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests - specific: test_get_absolute_url" type="DjangoTestsConfigurationType" factoryName="Django tests" singleton="true">
|
||||
<module name="{{ cookiecutter.project_slug }}" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.test" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||
<PathMappingSettings>
|
||||
<option name="pathMappings">
|
||||
<list>
|
||||
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
|
||||
</list>
|
||||
</option>
|
||||
</PathMappingSettings>
|
||||
<option name="TARGET" value="{{ cookiecutter.project_slug }}.users.tests.test_models.TestUser.test_get_absolute_url" />
|
||||
<option name="SETTINGS_FILE" value="" />
|
||||
<option name="CUSTOM_SETTINGS" value="false" />
|
||||
<option name="USE_OPTIONS" value="false" />
|
||||
<option name="OPTIONS" value="" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -32,12 +32,21 @@ Setting Up Your Users
|
|||
|
||||
For convenience, you can keep your normal user logged in on Chrome and your superuser logged in on Firefox (or similar), so that you can see how the site behaves for both kinds of users.
|
||||
|
||||
Type checks
|
||||
^^^^^^^^^^^
|
||||
|
||||
Running type checks with mypy:
|
||||
|
||||
::
|
||||
|
||||
$ mypy {{cookiecutter.project_slug}}
|
||||
|
||||
Test coverage
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
To run the tests, check your test coverage, and generate an HTML coverage report::
|
||||
|
||||
$ coverage run manage.py test
|
||||
$ coverage run -m pytest
|
||||
$ coverage html
|
||||
$ open htmlcov/index.html
|
||||
|
||||
|
@ -46,7 +55,7 @@ Running tests with py.test
|
|||
|
||||
::
|
||||
|
||||
$ py.test
|
||||
$ pytest
|
||||
|
||||
Live reloading and Sass CSS compilation
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -138,7 +147,7 @@ Custom Bootstrap Compilation
|
|||
^^^^^^
|
||||
|
||||
The generated CSS is set up with automatic Bootstrap recompilation with variables of your choice.
|
||||
Bootstrap v4 is installed using npm and customised by tweaking your variables in ``static/sass/custom_bootstrap_vars``.
|
||||
Bootstrap v4.1.1 is installed using npm and customised by tweaking your variables in ``static/sass/custom_bootstrap_vars``.
|
||||
|
||||
You can find a list of available variables `in the bootstrap source`_, or get explanations on them in the `Bootstrap docs`_.
|
||||
|
||||
|
@ -147,6 +156,6 @@ Bootstrap's javascript as well as its dependencies is concatenated into a single
|
|||
{% endif %}
|
||||
|
||||
.. _in the bootstrap source: https://github.com/twbs/bootstrap/blob/v4-dev/scss/_variables.scss
|
||||
.. _Bootstrap docs: https://getbootstrap.com/docs/4.0/getting-started/theming/
|
||||
.. _Bootstrap docs: https://getbootstrap.com/docs/4.1/getting-started/theming/
|
||||
|
||||
{% endif %}
|
||||
|
|
|
@ -34,6 +34,10 @@ RUN chmod +x /start-celeryworker
|
|||
COPY ./compose/local/django/celery/beat/start /start-celerybeat
|
||||
RUN sed -i 's/\r//' /start-celerybeat
|
||||
RUN chmod +x /start-celerybeat
|
||||
|
||||
COPY ./compose/local/django/celery/flower/start /start-flower
|
||||
RUN sed -i 's/\r//' /start-flower
|
||||
RUN chmod +x /start-flower
|
||||
{% endif %}
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
|
||||
|
||||
celery flower \
|
||||
--app={{cookiecutter.project_slug}}.taskapp \
|
||||
--broker="${CELERY_BROKER_URL}" \
|
||||
--basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}"
|
|
@ -38,6 +38,10 @@ COPY ./compose/production/django/celery/beat/start /start-celerybeat
|
|||
RUN sed -i 's/\r//' /start-celerybeat
|
||||
RUN chmod +x /start-celerybeat
|
||||
RUN chown django /start-celerybeat
|
||||
|
||||
COPY ./compose/production/django/celery/flower/start /start-flower
|
||||
RUN sed -i 's/\r//' /start-flower
|
||||
RUN chmod +x /start-flower
|
||||
{% endif %}
|
||||
COPY . /app
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
|
||||
|
||||
celery flower \
|
||||
--app={{cookiecutter.project_slug}}.taskapp \
|
||||
--broker="${CELERY_BROKER_URL}" \
|
||||
--basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}"
|
|
@ -229,19 +229,25 @@ MANAGERS = ADMINS
|
|||
# Celery
|
||||
# ------------------------------------------------------------------------------
|
||||
INSTALLED_APPS += ['{{cookiecutter.project_slug}}.taskapp.celery.CeleryAppConfig']
|
||||
if USE_TZ:
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-timezone
|
||||
CELERY_TIMEZONE = TIME_ZONE
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-broker_url
|
||||
CELERY_BROKER_URL = env('CELERY_BROKER_URL', default='django://')
|
||||
CELERY_BROKER_URL = env('CELERY_BROKER_URL')
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-result_backend
|
||||
if CELERY_BROKER_URL == 'django://':
|
||||
CELERY_RESULT_BACKEND = 'redis://'
|
||||
else:
|
||||
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
|
||||
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-accept_content
|
||||
CELERY_ACCEPT_CONTENT = ['json']
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_serializer
|
||||
CELERY_TASK_SERIALIZER = 'json'
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-result_serializer
|
||||
CELERY_RESULT_SERIALIZER = 'json'
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-time-limit
|
||||
# TODO: set to whatever value is adequate in your circumstances
|
||||
CELERYD_TASK_TIME_LIMIT = 5 * 60
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-soft-time-limit
|
||||
# TODO: set to whatever value is adequate in your circumstances
|
||||
CELERYD_TASK_SOFT_TIME_LIMIT = 60
|
||||
|
||||
{%- endif %}
|
||||
# django-allauth
|
||||
|
|
|
@ -76,8 +76,10 @@ INSTALLED_APPS += ['django_extensions'] # noqa F405
|
|||
|
||||
# Celery
|
||||
# ------------------------------------------------------------------------------
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_always_eager
|
||||
CELERY_ALWAYS_EAGER = True
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-always-eager
|
||||
CELERY_TASK_ALWAYS_EAGER = True
|
||||
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-eager-propagates
|
||||
CELERY_TASK_EAGER_PROPAGATES = True
|
||||
|
||||
{%- endif %}
|
||||
# Your stuff...
|
||||
|
|
|
@ -73,8 +73,6 @@ AWS_SECRET_ACCESS_KEY = env('DJANGO_AWS_SECRET_ACCESS_KEY')
|
|||
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
|
||||
AWS_STORAGE_BUCKET_NAME = env('DJANGO_AWS_STORAGE_BUCKET_NAME')
|
||||
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
|
||||
AWS_AUTO_CREATE_BUCKET = True
|
||||
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
|
||||
AWS_QUERYSTRING_AUTH = False
|
||||
# DO NOT change these unless you know what you're doing.
|
||||
_AWS_EXPIRY = 60 * 60 * 24 * 7
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
version: '2'
|
||||
version: '3'
|
||||
|
||||
volumes:
|
||||
postgres_data_local: {}
|
||||
postgres_backup_local: {}
|
||||
local_postgres_data: {}
|
||||
local_postgres_data_backups: {}
|
||||
|
||||
services:
|
||||
django:{% if cookiecutter.use_celery == 'y' %} &django{% endif %}
|
||||
|
@ -30,8 +30,8 @@ services:
|
|||
dockerfile: ./compose/production/postgres/Dockerfile
|
||||
image: {{ cookiecutter.project_slug }}_production_postgres
|
||||
volumes:
|
||||
- postgres_data_local:/var/lib/postgresql/data
|
||||
- postgres_backup_local:/backups
|
||||
- local_postgres_data:/var/lib/postgresql/data
|
||||
- local_postgres_data_backups:/backups
|
||||
env_file:
|
||||
- ./.envs/.local/.postgres
|
||||
{%- if cookiecutter.use_mailhog == 'y' %}
|
||||
|
@ -71,4 +71,11 @@ services:
|
|||
ports: []
|
||||
command: /start-celerybeat
|
||||
|
||||
flower:
|
||||
<<: *django
|
||||
image: {{ cookiecutter.project_slug }}_local_flower
|
||||
ports:
|
||||
- "5555:5555"
|
||||
command: /start-flower
|
||||
|
||||
{%- endif %}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"devDependencies": {
|
||||
{% if cookiecutter.js_task_runner == 'Gulp' %}
|
||||
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
||||
"bootstrap": "^4.0.0",
|
||||
"bootstrap": "4.1.1",
|
||||
{% endif %}
|
||||
"browser-sync": "^2.14.0",
|
||||
"del": "^2.2.2",
|
||||
|
@ -23,8 +23,8 @@
|
|||
"gulp-uglify": "^3.0.0",
|
||||
"gulp-util": "^3.0.7",
|
||||
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
||||
"jquery": "^3.2.1-slim",
|
||||
"popper.js": "^1.12.3",
|
||||
"jquery": "3.3.1-slim",
|
||||
"popper.js": "1.14.3",
|
||||
{% endif %}
|
||||
"run-sequence": "^2.1.1"
|
||||
{% endif %}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
version: '2'
|
||||
version: '3'
|
||||
|
||||
volumes:
|
||||
postgres_data: {}
|
||||
postgres_backup: {}
|
||||
caddy: {}
|
||||
production_postgres_data: {}
|
||||
production_postgres_data_backups: {}
|
||||
production_caddy: {}
|
||||
|
||||
services:
|
||||
django:{% if cookiecutter.use_celery == 'y' %} &django{% endif %}
|
||||
|
@ -25,8 +25,8 @@ services:
|
|||
dockerfile: ./compose/production/postgres/Dockerfile
|
||||
image: {{ cookiecutter.project_slug }}_production_postgres
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- postgres_backup:/backups
|
||||
- production_postgres_data:/var/lib/postgresql/data
|
||||
- production_postgres_data_backups:/backups
|
||||
env_file:
|
||||
- ./.envs/.production/.postgres
|
||||
|
||||
|
@ -38,7 +38,7 @@ services:
|
|||
depends_on:
|
||||
- django
|
||||
volumes:
|
||||
- caddy:/root/.caddy
|
||||
- production_caddy:/root/.caddy
|
||||
env_file:
|
||||
- ./.envs/.production/.caddy
|
||||
ports:
|
||||
|
@ -59,4 +59,11 @@ services:
|
|||
image: {{ cookiecutter.project_slug }}_production_celerybeat
|
||||
command: /start-celerybeat
|
||||
|
||||
flower:
|
||||
<<: *django
|
||||
image: {{ cookiecutter.project_slug }}_production_flower
|
||||
ports:
|
||||
- "5555:5555"
|
||||
command: /start-flower
|
||||
|
||||
{%- endif %}
|
||||
|
|
|
@ -1,24 +1,27 @@
|
|||
pytz==2018.4 # https://github.com/stub42/pytz
|
||||
pytz==2018.5 # https://github.com/stub42/pytz
|
||||
python-slugify==1.2.5 # https://github.com/un33k/python-slugify
|
||||
Pillow==5.1.0 # https://github.com/python-pillow/Pillow
|
||||
Pillow==5.2.0 # https://github.com/python-pillow/Pillow
|
||||
{%- if cookiecutter.use_compressor == "y" %}
|
||||
rcssmin==1.0.6{% if cookiecutter.windows == 'y' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin
|
||||
{%- endif %}
|
||||
argon2-cffi==18.1.0 # https://github.com/hynek/argon2_cffi
|
||||
argon2-cffi==18.3.0 # https://github.com/hynek/argon2_cffi
|
||||
{%- if cookiecutter.use_whitenoise == 'y' %}
|
||||
whitenoise==3.3.1 # https://github.com/evansd/whitenoise
|
||||
whitenoise==4.0 # https://github.com/evansd/whitenoise
|
||||
{%- endif %}
|
||||
redis>=2.10.5 # https://github.com/antirez/redis
|
||||
{%- if cookiecutter.use_celery == "y" %}
|
||||
celery==3.1.26.post2 # pyup: <4.0 # https://github.com/celery/celery
|
||||
celery==4.2.1 # pyup: <5.0 # https://github.com/celery/celery
|
||||
{%- if cookiecutter.use_docker == 'y' %}
|
||||
flower==0.9.2 # https://github.com/mher/flower
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
|
||||
# Django
|
||||
# ------------------------------------------------------------------------------
|
||||
django==2.0.6 # pyup: < 2.1 # https://www.djangoproject.com/
|
||||
django-environ==0.4.4 # https://github.com/joke2k/django-environ
|
||||
django==2.0.8 # pyup: < 2.1 # https://www.djangoproject.com/
|
||||
django-environ==0.4.5 # https://github.com/joke2k/django-environ
|
||||
django-model-utils==3.1.2 # https://github.com/jazzband/django-model-utils
|
||||
django-allauth==0.36.0 # https://github.com/pennersr/django-allauth
|
||||
django-allauth==0.37.1 # https://github.com/pennersr/django-allauth
|
||||
django-crispy-forms==1.7.2 # https://github.com/django-crispy-forms/django-crispy-forms
|
||||
{%- if cookiecutter.use_compressor == "y" %}
|
||||
django-compressor==2.2 # https://github.com/django-compressor/django-compressor
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Werkzeug==0.14.1 # https://github.com/pallets/werkzeug
|
||||
ipdb==0.11 # https://github.com/gotcha/ipdb
|
||||
Sphinx==1.7.5 # https://github.com/sphinx-doc/sphinx
|
||||
Sphinx==1.7.8 # https://github.com/sphinx-doc/sphinx
|
||||
{%- if cookiecutter.use_docker == 'y' %}
|
||||
psycopg2==2.7.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
||||
{%- else %}
|
||||
|
@ -11,7 +11,8 @@ psycopg2-binary==2.7.5 # https://github.com/psycopg/psycopg2
|
|||
|
||||
# Testing
|
||||
# ------------------------------------------------------------------------------
|
||||
pytest==3.6.1 # https://github.com/pytest-dev/pytest
|
||||
mypy==0.620 # https://github.com/python/mypy
|
||||
pytest==3.7.3 # https://github.com/pytest-dev/pytest
|
||||
pytest-sugar==0.9.1 # https://github.com/Frozenball/pytest-sugar
|
||||
|
||||
# Code quality
|
||||
|
@ -22,9 +23,8 @@ coverage==4.5.1 # https://github.com/nedbat/coveragepy
|
|||
# Django
|
||||
# ------------------------------------------------------------------------------
|
||||
factory-boy==2.11.1 # https://github.com/FactoryBoy/factory_boy
|
||||
django-test-plus==1.1.0 # https://github.com/revsys/django-test-plus
|
||||
|
||||
django-debug-toolbar==1.9.1 # https://github.com/jazzband/django-debug-toolbar
|
||||
django-extensions==2.0.7 # https://github.com/django-extensions/django-extensions
|
||||
django-extensions==2.1.2 # https://github.com/django-extensions/django-extensions
|
||||
django-coverage-plugin==1.5.0 # https://github.com/nedbat/django_coverage_plugin
|
||||
pytest-django==3.3.0 # https://github.com/pytest-dev/pytest-django
|
||||
pytest-django==3.4.2 # https://github.com/pytest-dev/pytest-django
|
||||
|
|
|
@ -14,4 +14,4 @@ raven==6.9.0 # https://github.com/getsentry/raven-python
|
|||
# Django
|
||||
# ------------------------------------------------------------------------------
|
||||
django-storages[boto3]==1.6.6 # https://github.com/jschneier/django-storages
|
||||
django-anymail[mailgun]==3.0 # https://github.com/anymail/django-anymail
|
||||
django-anymail[mailgun]==4.1 # https://github.com/anymail/django-anymail
|
||||
|
|
|
@ -1 +1 @@
|
|||
python-3.6.5
|
||||
python-3.6.6
|
||||
|
|
|
@ -5,3 +5,17 @@ exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules
|
|||
[pycodestyle]
|
||||
max-line-length = 120
|
||||
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules
|
||||
|
||||
[mypy]
|
||||
python_version = 3.6
|
||||
check_untyped_defs = True
|
||||
ignore_errors = False
|
||||
ignore_missing_imports = True
|
||||
strict_optional = True
|
||||
warn_unused_ignores = True
|
||||
warn_redundant_casts = True
|
||||
warn_unused_configs = True
|
||||
|
||||
[mypy-*.migrations.*]
|
||||
# Django migrations should not produce any errors:
|
||||
ignore_errors = True
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import pytest
|
||||
from django.conf import settings
|
||||
from django.test import RequestFactory
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def media_storage(settings, tmpdir):
|
||||
settings.MEDIA_ROOT = tmpdir.strpath
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user() -> settings.AUTH_USER_MODEL:
|
||||
return UserFactory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def request_factory() -> RequestFactory:
|
||||
return RequestFactory()
|
|
@ -1,21 +1 @@
|
|||
/* Project specific Javascript goes here. */
|
||||
|
||||
/*
|
||||
Formatting hack to get around crispy-forms unfortunate hardcoding
|
||||
in helpers.FormHelper:
|
||||
|
||||
if template_pack == 'bootstrap4':
|
||||
grid_colum_matcher = re.compile('\w*col-(xs|sm|md|lg|xl)-\d+\w*')
|
||||
using_grid_layout = (grid_colum_matcher.match(self.label_class) or
|
||||
grid_colum_matcher.match(self.field_class))
|
||||
if using_grid_layout:
|
||||
items['using_grid_layout'] = True
|
||||
|
||||
Issues with the above approach:
|
||||
|
||||
1. Fragile: Assumes Bootstrap 4's API doesn't change (it does)
|
||||
2. Unforgiving: Doesn't allow for any variation in template design
|
||||
3. Really Unforgiving: No way to override this behavior
|
||||
4. Undocumented: No mention in the documentation, or it's too hard for me to find
|
||||
*/
|
||||
$('.form-group').removeClass('row');
|
||||
|
|
|
@ -20,7 +20,9 @@ class CeleryAppConfig(AppConfig):
|
|||
def ready(self):
|
||||
# Using a string here means the worker will not have to
|
||||
# pickle the object when using Windows.
|
||||
app.config_from_object('django.conf:settings')
|
||||
# - namespace='CELERY' means all celery-related configuration keys
|
||||
# should have a `CELERY_` prefix.
|
||||
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||
installed_apps = [app_config.name for app_config in apps.get_app_configs()]
|
||||
app.autodiscover_tasks(lambda: installed_apps, force=True)
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
{% block css %}
|
||||
{% endraw %}{% if cookiecutter.custom_bootstrap_compilation == "n" %}{% raw %}
|
||||
<!-- Latest compiled and minified Bootstrap 4 beta CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
|
||||
<!-- Latest compiled and minified Bootstrap 4.1.1 CSS -->
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
|
||||
{% endraw %}{% endif %}{% raw %}
|
||||
|
||||
<!-- Your stuff: Third-party CSS libraries go here -->
|
||||
|
@ -102,10 +102,10 @@
|
|||
<script src="{% static 'js/vendors.js' %}"></script>
|
||||
{% endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% endcompress %}{% endraw %}{% endif %}{% raw %}
|
||||
{% endraw %}{% else %}{% raw %}
|
||||
<!-- Required by Bootstrap v4 beta -->
|
||||
<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.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
|
||||
<!-- Required by Bootstrap v4.1.1 -->
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Your stuff: Third-party javascript libraries go here -->
|
||||
{% endraw %}{% endif %}{% raw %}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
from django.conf import settings
|
||||
from typing import Any
|
||||
|
||||
from allauth.account.adapter import DefaultAccountAdapter
|
||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||||
from django.conf import settings
|
||||
from django.http import HttpRequest
|
||||
|
||||
|
||||
class AccountAdapter(DefaultAccountAdapter):
|
||||
|
||||
def is_open_for_signup(self, request):
|
||||
def is_open_for_signup(self, request: HttpRequest):
|
||||
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
|
||||
|
||||
|
||||
class SocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||
|
||||
def is_open_for_signup(self, request, sociallogin):
|
||||
def is_open_for_signup(self, request: HttpRequest, sociallogin: Any):
|
||||
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
|
||||
|
|
|
@ -1,39 +1,17 @@
|
|||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
|
||||
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
||||
from .models import User
|
||||
from django.contrib.auth import admin as auth_admin
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.forms import UserChangeForm, UserCreationForm
|
||||
|
||||
class MyUserChangeForm(UserChangeForm):
|
||||
|
||||
class Meta(UserChangeForm.Meta):
|
||||
model = User
|
||||
|
||||
|
||||
class MyUserCreationForm(UserCreationForm):
|
||||
|
||||
error_message = UserCreationForm.error_messages.update(
|
||||
{"duplicate_username": "This username has already been taken."}
|
||||
)
|
||||
|
||||
class Meta(UserCreationForm.Meta):
|
||||
model = User
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data["username"]
|
||||
try:
|
||||
User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
return username
|
||||
|
||||
raise forms.ValidationError(self.error_messages["duplicate_username"])
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class MyUserAdmin(AuthUserAdmin):
|
||||
form = MyUserChangeForm
|
||||
add_form = MyUserCreationForm
|
||||
fieldsets = (("User Profile", {"fields": ("name",)}),) + AuthUserAdmin.fieldsets
|
||||
list_display = ("username", "name", "is_superuser")
|
||||
class UserAdmin(auth_admin.UserAdmin):
|
||||
|
||||
form = UserChangeForm
|
||||
add_form = UserCreationForm
|
||||
fieldsets = (("User", {"fields": ("name",)}),) + auth_admin.UserAdmin.fieldsets
|
||||
list_display = ["username", "name", "is_superuser"]
|
||||
search_fields = ["name"]
|
||||
|
|
|
@ -2,14 +2,11 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class UsersAppConfig(AppConfig):
|
||||
name = "{{cookiecutter.project_slug}}.users"
|
||||
|
||||
name = "{{ cookiecutter.project_slug }}.users"
|
||||
verbose_name = "Users"
|
||||
|
||||
def ready(self):
|
||||
"""Override this to put in:
|
||||
Users system checks
|
||||
Users signal registration
|
||||
"""
|
||||
try:
|
||||
import users.signals # noqa F401
|
||||
except ImportError:
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
from django.contrib.auth import get_user_model, forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class UserChangeForm(forms.UserChangeForm):
|
||||
|
||||
class Meta(forms.UserChangeForm.Meta):
|
||||
model = User
|
||||
|
||||
|
||||
class UserCreationForm(forms.UserCreationForm):
|
||||
|
||||
error_message = forms.UserCreationForm.error_messages.update(
|
||||
{"duplicate_username": _("This username has already been taken.")}
|
||||
)
|
||||
|
||||
class Meta(forms.UserCreationForm.Meta):
|
||||
model = User
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data["username"]
|
||||
|
||||
try:
|
||||
User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
return username
|
||||
|
||||
raise ValidationError(self.error_messages["duplicate_username"])
|
|
@ -1,5 +1,5 @@
|
|||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
from django.db.models import CharField
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
@ -8,10 +8,7 @@ class User(AbstractUser):
|
|||
|
||||
# First Name and Last Name do not cover name patterns
|
||||
# around the globe.
|
||||
name = models.CharField(_("Name of User"), blank=True, max_length=255)
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
name = CharField(_("Name of User"), blank=True, max_length=255)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("users:detail", kwargs={"username": self.username})
|
||||
|
|
|
@ -1,11 +1,29 @@
|
|||
import factory
|
||||
from typing import Any, Sequence
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from factory import DjangoModelFactory, Faker, post_generation
|
||||
|
||||
|
||||
class UserFactory(factory.django.DjangoModelFactory):
|
||||
username = factory.Sequence(lambda n: f"user-{n}")
|
||||
email = factory.Sequence(lambda n: f"user-{n}@example.com")
|
||||
password = factory.PostGenerationMethodCall("set_password", "password")
|
||||
class UserFactory(DjangoModelFactory):
|
||||
|
||||
username = Faker("user_name")
|
||||
email = Faker("email")
|
||||
name = Faker("name")
|
||||
|
||||
@post_generation
|
||||
def password(self, create: bool, extracted: Sequence[Any], **kwargs):
|
||||
password = Faker(
|
||||
"password",
|
||||
length=42,
|
||||
special_chars=True,
|
||||
digits=True,
|
||||
upper_case=True,
|
||||
lower_case=True,
|
||||
).generate(
|
||||
extra_kwargs={}
|
||||
)
|
||||
self.set_password(password)
|
||||
|
||||
class Meta:
|
||||
model = "users.User"
|
||||
django_get_or_create = ("username",)
|
||||
model = get_user_model()
|
||||
django_get_or_create = ["username"]
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
from test_plus.test import TestCase
|
||||
|
||||
from ..admin import MyUserCreationForm
|
||||
|
||||
|
||||
class TestMyUserCreationForm(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = self.make_user("notalamode", "notalamodespassword")
|
||||
|
||||
def test_clean_username_success(self):
|
||||
# Instantiate the form with a new username
|
||||
form = MyUserCreationForm(
|
||||
{
|
||||
"username": "alamode",
|
||||
"password1": "7jefB#f@Cc7YJB]2v",
|
||||
"password2": "7jefB#f@Cc7YJB]2v",
|
||||
}
|
||||
)
|
||||
# Run is_valid() to trigger the validation
|
||||
valid = form.is_valid()
|
||||
self.assertTrue(valid)
|
||||
|
||||
# Run the actual clean_username method
|
||||
username = form.clean_username()
|
||||
self.assertEqual("alamode", username)
|
||||
|
||||
def test_clean_username_false(self):
|
||||
# Instantiate the form with the same username as self.user
|
||||
form = MyUserCreationForm(
|
||||
{
|
||||
"username": self.user.username,
|
||||
"password1": "notalamodespassword",
|
||||
"password2": "notalamodespassword",
|
||||
}
|
||||
)
|
||||
# Run is_valid() to trigger the validation, which is going to fail
|
||||
# because the username is already taken
|
||||
valid = form.is_valid()
|
||||
self.assertFalse(valid)
|
||||
|
||||
# The form.errors dict should contain a single error called 'username'
|
||||
self.assertTrue(len(form.errors) == 1)
|
||||
self.assertTrue("username" in form.errors)
|
|
@ -0,0 +1,41 @@
|
|||
import pytest
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.forms import UserCreationForm
|
||||
from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
class TestUserCreationForm:
|
||||
|
||||
def test_clean_username(self):
|
||||
# A user with proto_user params does not exist yet.
|
||||
proto_user = UserFactory.build()
|
||||
|
||||
form = UserCreationForm(
|
||||
{
|
||||
"username": proto_user.username,
|
||||
"password1": proto_user._password,
|
||||
"password2": proto_user._password,
|
||||
}
|
||||
)
|
||||
|
||||
assert form.is_valid()
|
||||
assert form.clean_username() == proto_user.username
|
||||
|
||||
# Creating a user.
|
||||
form.save()
|
||||
|
||||
# The user with proto_user params already exists,
|
||||
# hence cannot be created.
|
||||
form = UserCreationForm(
|
||||
{
|
||||
"username": proto_user.username,
|
||||
"password1": proto_user._password,
|
||||
"password2": proto_user._password,
|
||||
}
|
||||
)
|
||||
|
||||
assert not form.is_valid()
|
||||
assert len(form.errors) == 1
|
||||
assert "username" in form.errors
|
|
@ -1,16 +1,8 @@
|
|||
from test_plus.test import TestCase
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
class TestUser(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = self.make_user()
|
||||
|
||||
def test__str__(self):
|
||||
self.assertEqual(
|
||||
self.user.__str__(),
|
||||
"testuser", # This is the default username for self.make_user()
|
||||
)
|
||||
|
||||
def test_get_absolute_url(self):
|
||||
self.assertEqual(self.user.get_absolute_url(), "/users/testuser/")
|
||||
def test_user_get_absolute_url(user: settings.AUTH_USER_MODEL):
|
||||
assert user.get_absolute_url() == f"/users/{user.username}/"
|
||||
|
|
|
@ -1,44 +1,28 @@
|
|||
import pytest
|
||||
from django.conf import settings
|
||||
from django.urls import reverse, resolve
|
||||
|
||||
from test_plus.test import TestCase
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
class TestUserURLs(TestCase):
|
||||
"""Test URL patterns for users app."""
|
||||
|
||||
def setUp(self):
|
||||
self.user = self.make_user()
|
||||
|
||||
def test_list_reverse(self):
|
||||
"""users:list should reverse to /users/."""
|
||||
self.assertEqual(reverse("users:list"), "/users/")
|
||||
|
||||
def test_list_resolve(self):
|
||||
"""/users/ should resolve to users:list."""
|
||||
self.assertEqual(resolve("/users/").view_name, "users:list")
|
||||
|
||||
def test_redirect_reverse(self):
|
||||
"""users:redirect should reverse to /users/~redirect/."""
|
||||
self.assertEqual(reverse("users:redirect"), "/users/~redirect/")
|
||||
|
||||
def test_redirect_resolve(self):
|
||||
"""/users/~redirect/ should resolve to users:redirect."""
|
||||
self.assertEqual(resolve("/users/~redirect/").view_name, "users:redirect")
|
||||
|
||||
def test_detail_reverse(self):
|
||||
"""users:detail should reverse to /users/testuser/."""
|
||||
self.assertEqual(
|
||||
reverse("users:detail", kwargs={"username": "testuser"}), "/users/testuser/"
|
||||
def test_detail(user: settings.AUTH_USER_MODEL):
|
||||
assert (
|
||||
reverse("users:detail", kwargs={"username": user.username})
|
||||
== f"/users/{user.username}/"
|
||||
)
|
||||
assert resolve(f"/users/{user.username}/").view_name == "users:detail"
|
||||
|
||||
def test_detail_resolve(self):
|
||||
"""/users/testuser/ should resolve to users:detail."""
|
||||
self.assertEqual(resolve("/users/testuser/").view_name, "users:detail")
|
||||
|
||||
def test_update_reverse(self):
|
||||
"""users:update should reverse to /users/~update/."""
|
||||
self.assertEqual(reverse("users:update"), "/users/~update/")
|
||||
def test_list():
|
||||
assert reverse("users:list") == "/users/"
|
||||
assert resolve("/users/").view_name == "users:list"
|
||||
|
||||
def test_update_resolve(self):
|
||||
"""/users/~update/ should resolve to users:update."""
|
||||
self.assertEqual(resolve("/users/~update/").view_name, "users:update")
|
||||
|
||||
def test_update():
|
||||
assert reverse("users:update") == "/users/~update/"
|
||||
assert resolve("/users/~update/").view_name == "users:update"
|
||||
|
||||
|
||||
def test_redirect():
|
||||
assert reverse("users:redirect") == "/users/~redirect/"
|
||||
assert resolve("/users/~redirect/").view_name == "users:redirect"
|
||||
|
|
|
@ -1,52 +1,53 @@
|
|||
import pytest
|
||||
from django.conf import settings
|
||||
from django.test import RequestFactory
|
||||
|
||||
from test_plus.test import TestCase
|
||||
from {{ cookiecutter.project_slug }}.users.views import UserRedirectView, UserUpdateView
|
||||
|
||||
from ..views import UserRedirectView, UserUpdateView
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
class BaseUserTestCase(TestCase):
|
||||
class TestUserUpdateView:
|
||||
"""
|
||||
TODO:
|
||||
extracting view initialization code as class-scoped fixture
|
||||
would be great if only pytest-django supported non-function-scoped
|
||||
fixture db access -- this is a work-in-progress for now:
|
||||
https://github.com/pytest-dev/pytest-django/pull/258
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.user = self.make_user()
|
||||
self.factory = RequestFactory()
|
||||
def test_get_success_url(
|
||||
self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory
|
||||
):
|
||||
view = UserUpdateView()
|
||||
request = request_factory.get("/fake-url/")
|
||||
request.user = user
|
||||
|
||||
|
||||
class TestUserRedirectView(BaseUserTestCase):
|
||||
|
||||
def test_get_redirect_url(self):
|
||||
# Instantiate the view directly. Never do this outside a test!
|
||||
view = UserRedirectView()
|
||||
# Generate a fake request
|
||||
request = self.factory.get("/fake-url")
|
||||
# Attach the user to the request
|
||||
request.user = self.user
|
||||
# Attach the request to the view
|
||||
view.request = request
|
||||
# Expect: '/users/testuser/', as that is the default username for
|
||||
# self.make_user()
|
||||
self.assertEqual(view.get_redirect_url(), "/users/testuser/")
|
||||
|
||||
assert view.get_success_url() == f"/users/{user.username}/"
|
||||
|
||||
def test_get_object(
|
||||
self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory
|
||||
):
|
||||
view = UserUpdateView()
|
||||
request = request_factory.get("/fake-url/")
|
||||
request.user = user
|
||||
|
||||
view.request = request
|
||||
|
||||
assert view.get_object() == user
|
||||
|
||||
|
||||
class TestUserUpdateView(BaseUserTestCase):
|
||||
class TestUserRedirectView:
|
||||
|
||||
def setUp(self):
|
||||
# call BaseUserTestCase.setUp()
|
||||
super(TestUserUpdateView, self).setUp()
|
||||
# Instantiate the view directly. Never do this outside a test!
|
||||
self.view = UserUpdateView()
|
||||
# Generate a fake request
|
||||
request = self.factory.get("/fake-url")
|
||||
# Attach the user to the request
|
||||
request.user = self.user
|
||||
# Attach the request to the view
|
||||
self.view.request = request
|
||||
def test_get_redirect_url(
|
||||
self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory
|
||||
):
|
||||
view = UserRedirectView()
|
||||
request = request_factory.get("/fake-url")
|
||||
request.user = user
|
||||
|
||||
def test_get_success_url(self):
|
||||
# Expect: '/users/testuser/', as that is the default username for
|
||||
# self.make_user()
|
||||
self.assertEqual(self.view.get_success_url(), "/users/testuser/")
|
||||
view.request = request
|
||||
|
||||
def test_get_object(self):
|
||||
# Expect: self.user, as that is the request's user object
|
||||
self.assertEqual(self.view.get_object(), self.user)
|
||||
assert view.get_redirect_url() == f"/users/{user.username}/"
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
from {{ cookiecutter.project_slug }}.users.views import (
|
||||
user_list_view,
|
||||
user_redirect_view,
|
||||
user_update_view,
|
||||
user_detail_view,
|
||||
)
|
||||
|
||||
app_name = "users"
|
||||
urlpatterns = [
|
||||
path("", view=views.UserListView.as_view(), name="list"),
|
||||
path("~redirect/", view=views.UserRedirectView.as_view(), name="redirect"),
|
||||
path("~update/", view=views.UserUpdateView.as_view(), name="update"),
|
||||
path(
|
||||
"<str:username>/",
|
||||
view=views.UserDetailView.as_view(),
|
||||
name="detail",
|
||||
),
|
||||
path("", view=user_list_view, name="list"),
|
||||
path("~redirect/", view=user_redirect_view, name="redirect"),
|
||||
path("~update/", view=user_update_view, name="update"),
|
||||
path("<str:username>/", view=user_detail_view, name="detail"),
|
||||
]
|
||||
|
|
|
@ -1,43 +1,52 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse
|
||||
from django.views.generic import DetailView, ListView, RedirectView, UpdateView
|
||||
|
||||
from .models import User
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class UserDetailView(LoginRequiredMixin, DetailView):
|
||||
|
||||
model = User
|
||||
# These next two lines tell the view to index lookups by username
|
||||
slug_field = "username"
|
||||
slug_url_kwarg = "username"
|
||||
|
||||
|
||||
user_detail_view = UserDetailView.as_view()
|
||||
|
||||
|
||||
class UserListView(LoginRequiredMixin, ListView):
|
||||
|
||||
model = User
|
||||
slug_field = "username"
|
||||
slug_url_kwarg = "username"
|
||||
|
||||
|
||||
user_list_view = UserListView.as_view()
|
||||
|
||||
|
||||
class UserUpdateView(LoginRequiredMixin, UpdateView):
|
||||
|
||||
model = User
|
||||
fields = ["name"]
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("users:detail", kwargs={"username": self.request.user.username})
|
||||
|
||||
def get_object(self):
|
||||
return User.objects.get(username=self.request.user.username)
|
||||
|
||||
|
||||
user_update_view = UserUpdateView.as_view()
|
||||
|
||||
|
||||
class UserRedirectView(LoginRequiredMixin, RedirectView):
|
||||
|
||||
permanent = False
|
||||
|
||||
def get_redirect_url(self):
|
||||
return reverse("users:detail", kwargs={"username": self.request.user.username})
|
||||
|
||||
|
||||
class UserUpdateView(LoginRequiredMixin, UpdateView):
|
||||
|
||||
fields = ["name"]
|
||||
|
||||
# we already imported User in the view code above, remember?
|
||||
model = User
|
||||
|
||||
# send the user back to their own page after a successful update
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("users:detail", kwargs={"username": self.request.user.username})
|
||||
|
||||
def get_object(self):
|
||||
# Only get the User record for the user making the request
|
||||
return User.objects.get(username=self.request.user.username)
|
||||
|
||||
|
||||
class UserListView(LoginRequiredMixin, ListView):
|
||||
model = User
|
||||
# These next two lines tell the view to index lookups by username
|
||||
slug_field = "username"
|
||||
slug_url_kwarg = "username"
|
||||
user_redirect_view = UserRedirectView.as_view()
|
||||
|
|
Loading…
Reference in New Issue
Block a user