diff --git a/.editorconfig b/.editorconfig index 585e2abce0..6a9a5c45df 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,7 +12,7 @@ trim_trailing_whitespace = true indent_style = space indent_size = 4 -[*.{html,css,scss,json,yml}] +[*.{html,css,scss,json,yml,xml}] indent_style = space indent_size = 2 @@ -21,3 +21,7 @@ trim_trailing_whitespace = false [Makefile] indent_style = tab + +[nginx.conf] +indent_style = space +indent_size = 2 diff --git a/.github/CONTRIBUTORS-template.md b/.github/CONTRIBUTORS-template.md new file mode 100644 index 0000000000..d8ba28c630 --- /dev/null +++ b/.github/CONTRIBUTORS-template.md @@ -0,0 +1,56 @@ +# Contributors + +## Core Developers + +These contributors have commit flags for the repository, and are able to +accept and merge pull requests. + + + + + + + + {%- for contributor in core_contributors %} + + + + + + {%- endfor %} +
NameGithubTwitter
{{ contributor.name }} + {{ contributor.github_login }} + {{ contributor.twitter_username }}
+ +*Audrey is also the creator of Cookiecutter. Audrey and Daniel are on +the Cookiecutter core team.* + +## Other Contributors + +Listed in alphabetical order. + + + + + + + + {%- for contributor in other_contributors %} + + + + + + {%- endfor %} +
NameGithubTwitter
{{ contributor.name }} + {{ contributor.github_login }} + {{ contributor.twitter_username }}
+ +### Special Thanks + +The following haven't provided code directly, but have provided +guidance and advice. + +- Jannis Leidel +- Nate Aune +- Barry Morrison diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..53a4866717 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [pydanny, browniebroke] +patreon: feldroy +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ["https://www.patreon.com/browniebroke"] diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000000..e984493d87 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,58 @@ +--- +name: Bug Report +about: Report a bug +labels: bug +--- + +## What happened? + +## What should've happened instead? + +## Additional details + + + +* Host system configuration: + * Version of cookiecutter CLI (get it with `cookiecutter --version`): + * OS name and version: + + On Linux, run + ```bash + lsb_release -a 2> /dev/null || cat /etc/redhat-release 2> /dev/null || cat /etc/*-release 2> /dev/null || cat /etc/issue 2> /dev/null + ``` + + On MacOs, run + ```bash + sw_vers + ``` + + On Windows, via CMD, run + ``` + systeminfo | findstr /B /C:"OS Name" /C:"OS Version" + ``` + + + ```bash + # Insert here the OS name and version + + ``` + + * Python version, run `python3 -V`: + * Docker version (if using Docker), run `docker --version`: + * docker-compose version (if using Docker), run `docker-compose --version`: + * ... +* Options selected and/or [replay file](https://cookiecutter.readthedocs.io/en/latest/advanced/replay.html): + On Linux and MacOS: `cat ${HOME}/.cookiecutter_replay/cookiecutter-django.json` + (Please, take care to remove sensitive information) + ```json + # Insert here the replay file content + ``` + +Logs: +
+
+$ cookiecutter https://github.com/cookiecutter/cookiecutter-django
+project_name [Project Name]: ...
+
+
+
diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000000..ab719770c3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,13 @@ +--- +name: New Feature Proposal +about: Propose a new feature +labels: enhancement +--- + +## Description + +What are you proposing? How should it be implemented? + +## Rationale + +Why should this feature be implemented? diff --git a/.github/ISSUE_TEMPLATE/paid-support.md b/.github/ISSUE_TEMPLATE/paid-support.md new file mode 100644 index 0000000000..9f997a8c87 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/paid-support.md @@ -0,0 +1,12 @@ +--- +name: Paid Support Request +about: Ask Core Team members to help you out +--- + +Provided your question goes beyond [regular support](https://github.com/cookiecutter/cookiecutter-django/issues/new?template=question.md), and/or the task at hand is of timely/high priority nature use the below information to reach out for contributors directly. + +* Daniel Roy Greenfeld, Project Lead ([GitHub](https://github.com/pydanny), [Patreon](https://www.patreon.com/danielroygreenfeld)): expertise in Django and AWS ELB. + +* Nikita Shupeyko, Core Developer ([GitHub](https://github.com/webyneter)): expertise in Python/Django, hands-on DevOps and frontend experience. + +* Bruno Alla, Core Developer ([GitHub](https://github.com/sponsors/browniebroke)). diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000000..61f839cf6d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,11 @@ +--- +name: Question +about: Please ask your question on StackOverflow, Discord or GitHub Discussions. +labels: question +--- + +First, make sure to examine [the docs](https://cookiecutter-django.readthedocs.io/en/latest/). If that doesn't help, we recommend one of these 3 main channels: + +- If your issue is related to Django + something else but was generated with cookiecutter-django, the best is to post a question on [StackOverflow](https://stackoverflow.com/questions/tagged/cookiecutter-django) tagged with `cookiecutter-django`, you would get more visibility from other communities as well. +- Join us on [Discord](https://discord.gg/uFXweDQc5a) and ask around. +- Start [a discussion](https://github.com/cookiecutter/cookiecutter-django/discussions) on our project's GitHub. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..10b66e266d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ + + + +## Description + + + +Checklist: + +- [ ] I've made sure that tests are updated accordingly (especially if adding or updating a template option) +- [ ] I've updated the documentation or confirm that my change doesn't require any updates + +## Rationale + + diff --git a/.github/changelog-template.md b/.github/changelog-template.md new file mode 100644 index 0000000000..50aab38e16 --- /dev/null +++ b/.github/changelog-template.md @@ -0,0 +1,8 @@ +{%- for change_type, pulls in grouped_pulls.items() %} +{%- if pulls %} +### {{ change_type }} +{%- for pull_request in pulls %} +- {{ pull_request.title }} ([#{{ pull_request.number }}]({{ pull_request.html_url }})) +{%- endfor -%} +{% endif -%} +{% endfor -%} diff --git a/.github/contributors.json b/.github/contributors.json new file mode 100644 index 0000000000..190b81d386 --- /dev/null +++ b/.github/contributors.json @@ -0,0 +1,1306 @@ +[ + { + "name": "Daniel Roy Greenfeld", + "github_login": "pydanny", + "twitter_username": "pydanny", + "is_core": true + }, + { + "name": "Audrey Roy Greenfeld", + "github_login": "audreyr", + "twitter_username": "audreyr", + "is_core": true + }, + { + "name": "Fábio C. Barrionuevo da Luz", + "github_login": "luzfcb", + "twitter_username": "luzfcb", + "is_core": true + }, + { + "name": "Saurabh Kumar", + "github_login": "theskumar", + "twitter_username": "_theskumar", + "is_core": true + }, + { + "name": "Jannis Gebauer", + "github_login": "jayfk", + "twitter_username": "", + "is_core": true + }, + { + "name": "Burhan Khalid", + "github_login": "burhan", + "twitter_username": "burhan", + "is_core": true + }, + { + "name": "Shupeyko Nikita", + "github_login": "webyneter", + "twitter_username": "webyneter", + "is_core": true + }, + { + "name": "Bruno Alla", + "github_login": "browniebroke", + "twitter_username": "_BrunoAlla", + "is_core": true + }, + { + "name": "Wan Liuyang", + "github_login": "sfdye", + "twitter_username": "sfdye", + "is_core": true + }, + { + "name": "18", + "github_login": "dezoito", + "twitter_username": "" + }, + { + "name": "2O4", + "github_login": "2O4", + "twitter_username": "" + }, + { + "name": "a7p", + "github_login": "a7p", + "twitter_username": "" + }, + { + "name": "Aadith PM", + "github_login": "aadithpm", + "twitter_username": "" + }, + { + "name": "Aaron Eikenberry", + "github_login": "aeikenberry", + "twitter_username": "" + }, + { + "name": "Adam Bogdał", + "github_login": "bogdal", + "twitter_username": "" + }, + { + "name": "Adam Dobrawy", + "github_login": "ad-m", + "twitter_username": "" + }, + { + "name": "Adam Steele", + "github_login": "adammsteele", + "twitter_username": "" + }, + { + "name": "Agam Dua", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Agustín Scaramuzza", + "github_login": "scaramagus", + "twitter_username": "scaramagus" + }, + { + "name": "Alberto Sanchez", + "github_login": "alb3rto", + "twitter_username": "" + }, + { + "name": "Alex Tsai", + "github_login": "caffodian", + "twitter_username": "" + }, + { + "name": "Alvaro [Andor]", + "github_login": "andor-pierdelacabeza", + "twitter_username": "" + }, + { + "name": "Amjith Ramanujam", + "github_login": "amjith", + "twitter_username": "" + }, + { + "name": "Andreas Meistad", + "github_login": "ameistad", + "twitter_username": "" + }, + { + "name": "Andres Gonzalez", + "github_login": "andresgz", + "twitter_username": "" + }, + { + "name": "Andrew Mikhnevich", + "github_login": "zcho", + "twitter_username": "" + }, + { + "name": "Andrew Chen Wang", + "github_login": "Andrew-Chen-Wang", + "twitter_username": "" + }, + { + "name": "Andy Rose", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Anna Callahan", + "github_login": "jazztpt", + "twitter_username": "" + }, + { + "name": "Anna Sidwell", + "github_login": "takkaria", + "twitter_username": "" + }, + { + "name": "Antonia Blair", + "github_login": "antoniablair", + "twitter_username": "antoniablairart" + }, + { + "name": "Anuj Bansal", + "github_login": "ahhda", + "twitter_username": "" + }, + { + "name": "Arcuri Davide", + "github_login": "dadokkio", + "twitter_username": "" + }, + { + "name": "Areski Belaid", + "github_login": "areski", + "twitter_username": "" + }, + { + "name": "AsheKR", + "github_login": "ashekr", + "twitter_username": "" + }, + { + "name": "Ashley Camba", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Barclay Gauld", + "github_login": "yunti", + "twitter_username": "" + }, + { + "name": "Bartek", + "github_login": "btknu", + "twitter_username": "" + }, + { + "name": "Ben Lopatin", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Ben Warren", + "github_login": "bwarren2", + "twitter_username": "" + }, + { + "name": "Benjamin Abel", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Bert de Miranda", + "github_login": "bertdemiranda", + "twitter_username": "" + }, + { + "name": "Bo Lopker", + "github_login": "blopker", + "twitter_username": "" + }, + { + "name": "Bo Peng", + "github_login": "BoPeng", + "twitter_username": "" + }, + { + "name": "Bouke Haarsma", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Brent Payne", + "github_login": "brentpayne", + "twitter_username": "brentpayne" + }, + { + "name": "Bruce Olivier", + "github_login": "bolivierjr", + "twitter_username": "" + }, + { + "name": "Caio Ariede", + "github_login": "caioariede", + "twitter_username": "caioariede" + }, + { + "name": "Carl Johnson", + "github_login": "carlmjohnson", + "twitter_username": "carlmjohnson" + }, + { + "name": "Catherine Devlin", + "github_login": "catherinedevlin", + "twitter_username": "" + }, + { + "name": "Cédric Gaspoz", + "github_login": "cgaspoz", + "twitter_username": "" + }, + { + "name": "Charlie Smith", + "github_login": "chuckus", + "twitter_username": "" + }, + { + "name": "Chris Curvey", + "github_login": "ccurvey", + "twitter_username": "" + }, + { + "name": "Chris Franklin", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Chris Franklin", + "github_login": "hairychris", + "twitter_username": "" + }, + { + "name": "Chris Pappalardo", + "github_login": "ChrisPappalardo", + "twitter_username": "" + }, + { + "name": "Christopher Clarke", + "github_login": "chrisdev", + "twitter_username": "" + }, + { + "name": "Cole Mackenzie", + "github_login": "cmackenzie1", + "twitter_username": "" + }, + { + "name": "Cole Maclean", + "github_login": "cole", + "twitter_username": "cole" + }, + { + "name": "Collederas", + "github_login": "Collederas", + "twitter_username": "" + }, + { + "name": "Craig Margieson", + "github_login": "cmargieson", + "twitter_username": "" + }, + { + "name": "Cristian Vargas", + "github_login": "cdvv7788", + "twitter_username": "" + }, + { + "name": "Cullen Rhodes", + "github_login": "c-rhodes", + "twitter_username": "" + }, + { + "name": "Curtis St Pierre", + "github_login": "curtisstpierre", + "twitter_username": "cstpierre1388" + }, + { + "name": "Dan Shultz", + "github_login": "shultz", + "twitter_username": "" + }, + { + "name": "Dani Hodovic", + "github_login": "danihodovic", + "twitter_username": "" + }, + { + "name": "Daniel Hepper", + "github_login": "dhepper", + "twitter_username": "danielhepper" + }, + { + "name": "Daniel Hillier", + "github_login": "danifus", + "twitter_username": "" + }, + { + "name": "Daniel Sears", + "github_login": "highpost", + "twitter_username": "highpost" + }, + { + "name": "Daniele Tricoli", + "github_login": "eriol", + "twitter_username": "" + }, + { + "name": "David Díaz", + "github_login": "ddiazpinto", + "twitter_username": "DavidDiazPinto" + }, + { + "name": "Davit Tovmasyan", + "github_login": "davitovmasyan", + "twitter_username": "" + }, + { + "name": "Davur Clementsen", + "github_login": "dsclementsen", + "twitter_username": "davur" + }, + { + "name": "Delio Castillo", + "github_login": "jangeador", + "twitter_username": "jangeador" + }, + { + "name": "Demetris Stavrou", + "github_login": "demestav", + "twitter_username": "" + }, + { + "name": "Denis Bobrov", + "github_login": "delneg", + "twitter_username": "" + }, + { + "name": "Denis Orehovsky", + "github_login": "apirobot", + "twitter_username": "" + }, + { + "name": "Denis Savran", + "github_login": "blaxpy", + "twitter_username": "" + }, + { + "name": "Diane Chen", + "github_login": "purplediane", + "twitter_username": "purplediane88" + }, + { + "name": "Dónal Adams", + "github_login": "epileptic-fish", + "twitter_username": "" + }, + { + "name": "Dong Huynh", + "github_login": "trungdong", + "twitter_username": "" + }, + { + "name": "Duda Nogueira", + "github_login": "dudanogueira", + "twitter_username": "dudanogueira" + }, + { + "name": "Emanuel Calso", + "github_login": "bloodpet", + "twitter_username": "bloodpet" + }, + { + "name": "Eraldo Energy", + "github_login": "eraldo", + "twitter_username": "" + }, + { + "name": "Eric Groom", + "github_login": "ericgroom", + "twitter_username": "" + }, + { + "name": "Ernesto Cedeno", + "github_login": "codnee", + "twitter_username": "" + }, + { + "name": "Eyad Al Sibai", + "github_login": "eyadsibai", + "twitter_username": "" + }, + { + "name": "Felipe Arruda", + "github_login": "arruda", + "twitter_username": "" + }, + { + "name": "Florian Idelberger", + "github_login": "step21", + "twitter_username": "windrush" + }, + { + "name": "Gabriel Mejia", + "github_login": "elgartoinf", + "twitter_username": "elgartoinf" + }, + { + "name": "Garry Cairns", + "github_login": "garry-cairns", + "twitter_username": "" + }, + { + "name": "Garry Polley", + "github_login": "garrypolley", + "twitter_username": "" + }, + { + "name": "Gilbishkosma", + "github_login": "Gilbishkosma", + "twitter_username": "" + }, + { + "name": "Glenn Wiskur", + "github_login": "gwiskur", + "twitter_username": "" + }, + { + "name": "Guilherme Guy", + "github_login": "guilherme1guy", + "twitter_username": "" + }, + { + "name": "Hamish Durkin", + "github_login": "durkode", + "twitter_username": "" + }, + { + "name": "Hana Quadara", + "github_login": "hanaquadara", + "twitter_username": "" + }, + { + "name": "Hannah Lazarus", + "github_login": "hanhanhan", + "twitter_username": "" + }, + { + "name": "Harry Moreno", + "github_login": "morenoh149", + "twitter_username": "morenoh149" + }, + { + "name": "Harry Percival", + "github_login": "hjwp", + "twitter_username": "" + }, + { + "name": "Hendrik Schneider", + "github_login": "hendrikschneider", + "twitter_username": "" + }, + { + "name": "Henrique G. G. Pereira", + "github_login": "ikkebr", + "twitter_username": "" + }, + { + "name": "Howie Zhao", + "github_login": "howiezhao", + "twitter_username": "" + }, + { + "name": "Ian Lee", + "github_login": "IanLee1521", + "twitter_username": "" + }, + { + "name": "Irfan Ahmad", + "github_login": "erfaan", + "twitter_username": "erfaan" + }, + { + "name": "Isaac12x", + "github_login": "Isaac12x", + "twitter_username": "" + }, + { + "name": "Ivan Khomutov", + "github_login": "ikhomutov", + "twitter_username": "" + }, + { + "name": "James Williams", + "github_login": "jameswilliams1", + "twitter_username": "" + }, + { + "name": "Jan Van Bruggen", + "github_login": "jvanbrug", + "twitter_username": "" + }, + { + "name": "Jelmer Draaijer", + "github_login": "foarsitter", + "twitter_username": "" + }, + { + "name": "Jerome Caisip", + "github_login": "jeromecaisip", + "twitter_username": "" + }, + { + "name": "Jens Nilsson", + "github_login": "phiberjenz", + "twitter_username": "" + }, + { + "name": "Jerome Leclanche", + "github_login": "jleclanche", + "twitter_username": "Adys" + }, + { + "name": "Jimmy Gitonga", + "github_login": "Afrowave", + "twitter_username": "afrowave" + }, + { + "name": "John Cass", + "github_login": "jcass77", + "twitter_username": "cass_john" + }, + { + "name": "Jonathan Thompson", + "github_login": "nojanath", + "twitter_username": "" + }, + { + "name": "Jules Cheron", + "github_login": "jules-ch", + "twitter_username": "" + }, + { + "name": "Julien Almarcha", + "github_login": "sladinji", + "twitter_username": "" + }, + { + "name": "Julio Castillo", + "github_login": "juliocc", + "twitter_username": "" + }, + { + "name": "Kaido Kert", + "github_login": "kaidokert", + "twitter_username": "" + }, + { + "name": "kappataumu", + "github_login": "kappataumu", + "twitter_username": "kappataumu" + }, + { + "name": "Kaveh", + "github_login": "ka7eh", + "twitter_username": "" + }, + { + "name": "Keith Bailey", + "github_login": "keithjeb", + "twitter_username": "" + }, + { + "name": "Keith Webber", + "github_login": "townie", + "twitter_username": "" + }, + { + "name": "Kevin A. Stone", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Kevin Ndung'u", + "github_login": "kevgathuku", + "twitter_username": "" + }, + { + "name": "Keyvan Mosharraf", + "github_login": "keyvanm", + "twitter_username": "" + }, + { + "name": "Krzysztof Szumny", + "github_login": "noisy", + "twitter_username": "" + }, + { + "name": "Krzysztof Żuraw", + "github_login": "krzysztofzuraw", + "twitter_username": "" + }, + { + "name": "Leo won", + "github_login": "leollon", + "twitter_username": "" + }, + { + "name": "Leo Zhou", + "github_login": "glasslion", + "twitter_username": "" + }, + { + "name": "Leon Kim", + "github_login": "PilhwanKim", + "twitter_username": "" + }, + { + "name": "Leonardo Jimenez", + "github_login": "xpostudio4", + "twitter_username": "" + }, + { + "name": "Lin Xianyi", + "github_login": "iynaix", + "twitter_username": "" + }, + { + "name": "Luis Nell", + "github_login": "originell", + "twitter_username": "" + }, + { + "name": "Lukas Klein", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Lyla Fischer", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Malik Sulaimanov", + "github_login": "flyudvik", + "twitter_username": "flyudvik" + }, + { + "name": "Martin Blech", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Martin Saizar", + "github_login": "msaizar", + "twitter_username": "" + }, + { + "name": "Mateusz Ostaszewski", + "github_login": "mostaszewski", + "twitter_username": "" + }, + { + "name": "Mathijs Hoogland", + "github_login": "MathijsHoogland", + "twitter_username": "" + }, + { + "name": "Matt Braymer-Hayes", + "github_login": "mattayes", + "twitter_username": "mattayes" + }, + { + "name": "Matt Knapper", + "github_login": "mknapper1", + "twitter_username": "" + }, + { + "name": "Matt Linares", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Matt Menzenski", + "github_login": "menzenski", + "twitter_username": "" + }, + { + "name": "Matt Warren", + "github_login": "mfwarren", + "twitter_username": "" + }, + { + "name": "Matthew Sisley", + "github_login": "mjsisley", + "twitter_username": "" + }, + { + "name": "Matthias Sieber", + "github_login": "manonthemat", + "twitter_username": "MatzeOne" + }, + { + "name": "Meghan Heintz", + "github_login": "dot2dotseurat", + "twitter_username": "" + }, + { + "name": "Mesut Yılmaz", + "github_login": "myilmaz", + "twitter_username": "" + }, + { + "name": "Michael Gecht", + "github_login": "mimischi", + "twitter_username": "_mischi" + }, + { + "name": "Michael Samoylov", + "github_login": "msamoylov", + "twitter_username": "" + }, + { + "name": "Min ho Kim", + "github_login": "minho42", + "twitter_username": "" + }, + { + "name": "mozillazg", + "github_login": "mozillazg", + "twitter_username": "" + }, + { + "name": "Nico Stefani", + "github_login": "nicolas471", + "twitter_username": "moby_dick91" + }, + { + "name": "Oleg Russkin", + "github_login": "rolep", + "twitter_username": "" + }, + { + "name": "Pablo", + "github_login": "oubiga", + "twitter_username": "" + }, + { + "name": "Parbhat Puri", + "github_login": "parbhat", + "twitter_username": "" + }, + { + "name": "Pawan Chaurasia", + "github_login": "rjsnh1522", + "twitter_username": "" + }, + { + "name": "Peter Bittner", + "github_login": "bittner", + "twitter_username": "" + }, + { + "name": "Peter Coles", + "github_login": "mrcoles", + "twitter_username": "" + }, + { + "name": "Philipp Matthies", + "github_login": "canonnervio", + "twitter_username": "" + }, + { + "name": "Pierre Chiquet", + "github_login": "pchiquet", + "twitter_username": "" + }, + { + "name": "Raony Guimarães Corrêa", + "github_login": "raonyguimaraes", + "twitter_username": "" + }, + { + "name": "Raphael Pierzina", + "github_login": "hackebrot", + "twitter_username": "" + }, + { + "name": "Reggie Riser", + "github_login": "reggieriser", + "twitter_username": "" + }, + { + "name": "René Muhl", + "github_login": "rm--", + "twitter_username": "" + }, + { + "name": "Richard Hajdu", + "github_login": "Tusky", + "twitter_username": "" + }, + { + "name": "Roman Afanaskin", + "github_login": "siauPatrick", + "twitter_username": "" + }, + { + "name": "Roman Osipenko", + "github_login": "romanosipenko", + "twitter_username": "" + }, + { + "name": "Russell Davies", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Sam Collins", + "github_login": "MightySCollins", + "twitter_username": "" + }, + { + "name": "Sascha", + "github_login": "saschalalala", + "twitter_username": "saschalalala" + }, + { + "name": "Sławek Ehlert", + "github_login": "slafs", + "twitter_username": "" + }, + { + "name": "Sorasful", + "github_login": "sorasful", + "twitter_username": "" + }, + { + "name": "Srinivas Nyayapati", + "github_login": "shireenrao", + "twitter_username": "" + }, + { + "name": "stepmr", + "github_login": "stepmr", + "twitter_username": "" + }, + { + "name": "Steve Steiner", + "github_login": "ssteinerX", + "twitter_username": "" + }, + { + "name": "Sudarshan Wadkar", + "github_login": "wadkar", + "twitter_username": "" + }, + { + "name": "Sule Marshall", + "github_login": "suledev", + "twitter_username": "" + }, + { + "name": "Tano Abeleyra", + "github_login": "tanoabeleyra", + "twitter_username": "" + }, + { + "name": "Taylor Baldwin", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Théo Segonds", + "github_login": "show0k", + "twitter_username": "" + }, + { + "name": "Tim Claessens", + "github_login": "timclaessens", + "twitter_username": "" + }, + { + "name": "Tim Freund", + "github_login": "timfreund", + "twitter_username": "" + }, + { + "name": "Tom Atkins", + "github_login": "knitatoms", + "twitter_username": "" + }, + { + "name": "Tom Offermann", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Travis McNeill", + "github_login": "Travistock", + "twitter_username": "tavistock_esq" + }, + { + "name": "Tubo Shi", + "github_login": "Tubo", + "twitter_username": "" + }, + { + "name": "Umair Ashraf", + "github_login": "umrashrf", + "twitter_username": "fabumair" + }, + { + "name": "Vadim Iskuchekov", + "github_login": "Egregors", + "twitter_username": "egregors" + }, + { + "name": "Vicente G. Reyes", + "github_login": "reyesvicente", + "twitter_username": "highcenburg" + }, + { + "name": "Vitaly Babiy", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Vivian Guillen", + "github_login": "viviangb", + "twitter_username": "" + }, + { + "name": "Vlad Doster", + "github_login": "vladdoster", + "twitter_username": "" + }, + { + "name": "Will Farley", + "github_login": "goldhand", + "twitter_username": "g01dhand" + }, + { + "name": "William Archinal", + "github_login": "archinal", + "twitter_username": "" + }, + { + "name": "Xaver Y.R. Chen", + "github_login": "yrchen", + "twitter_username": "yrchen" + }, + { + "name": "Yaroslav Halchenko", + "github_login": "", + "twitter_username": "" + }, + { + "name": "Yuchen Xie", + "github_login": "mapx", + "twitter_username": "" + }, + { + "name": "enchance", + "github_login": "enchance", + "twitter_username": "" + }, + { + "name": "Jan Fabry", + "github_login": "janfabry", + "twitter_username": "" + }, + { + "name": "Corey Garvey", + "github_login": "coreygarvey", + "twitter_username": "" + }, + { + "name": "Arnav Choudhury", + "github_login": "arnav13081994", + "twitter_username": "" + }, + { + "name": "Wes Turner", + "github_login": "westurner", + "twitter_username": "westurner" + }, + { + "name": "Jakub Musko", + "github_login": "umgelurgel", + "twitter_username": "" + }, + { + "name": "Fabian Affolter", + "github_login": "fabaff", + "twitter_username": "fabaff" + }, + { + "name": "Simon Rey", + "github_login": "eqqe", + "twitter_username": "" + }, + { + "name": "Yotam Tal", + "github_login": "yotamtal", + "twitter_username": "" + }, + { + "name": "John", + "github_login": "thorrak", + "twitter_username": "" + }, + { + "name": "vascop", + "github_login": "vascop", + "twitter_username": "" + }, + { + "name": "PJ Hoberman", + "github_login": "pjhoberman", + "twitter_username": "" + }, + { + "name": "lcd1232", + "github_login": "lcd1232", + "twitter_username": "" + }, + { + "name": "Tames McTigue", + "github_login": "Tamerz", + "twitter_username": "" + }, + { + "name": "Ray Besiga", + "github_login": "raybesiga", + "twitter_username": "raybesiga" + }, + { + "name": "Grant McLean", + "github_login": "grantm", + "twitter_username": "grantmnz" + }, + { + "name": "Kuo Chao Cheng", + "github_login": "wwwtony5488", + "twitter_username": "" + }, + { + "name": "LECbg", + "github_login": "LECbg", + "twitter_username": "" + }, + { + "name": "Haseeb ur Rehman", + "github_login": "professorhaseeb", + "twitter_username": "professorhaseeb" + }, + { + "name": "Abdul Qoyyuum", + "github_login": "Qoyyuum", + "twitter_username": "Qoyyuum" + }, + { + "name": "mfosterw", + "github_login": "mfosterw", + "twitter_username": "" + }, + { + "name": "Keith Callenberg", + "github_login": "keithcallenberg", + "twitter_username": "" + }, + { + "name": "Mike97M", + "github_login": "Mike97M", + "twitter_username": "" + }, + { + "name": "Charlie Macfarlane Brodie", + "github_login": "tannart", + "twitter_username": "" + }, + { + "name": "Floyd Hightower", + "github_login": "fhightower", + "twitter_username": "" + }, + { + "name": "Manjit Pardeshi", + "github_login": "Manjit2003", + "twitter_username": "" + }, + { + "name": "Meraj ", + "github_login": "ichbinmeraj", + "twitter_username": "merajsafari" + }, + { + "name": "dalrrard", + "github_login": "dalrrard", + "twitter_username": "" + }, + { + "name": "Liam Brenner", + "github_login": "SableWalnut", + "twitter_username": "" + }, + { + "name": "Noah H", + "github_login": "nthall", + "twitter_username": "" + }, + { + "name": "Diego Montes", + "github_login": "d57montes", + "twitter_username": "" + }, + { + "name": "Chao Yang Wu", + "github_login": "goatwu1993", + "twitter_username": "" + }, + { + "name": "mpoli", + "github_login": "mpoli", + "twitter_username": "" + }, + { + "name": "Zach Borboa", + "github_login": "zachborboa", + "twitter_username": "" + }, + { + "name": "Timm Simpkins", + "github_login": "PoDuck", + "twitter_username": "" + }, + { + "name": "Douglas", + "github_login": "douglascdev", + "twitter_username": "" + }, + { + "name": "Will Gordon", + "github_login": "wgordon17", + "twitter_username": "" + }, + { + "name": "Bogdan Mateescu", + "github_login": "mateesville93", + "twitter_username": "" + }, + { + "name": "Fuzzwah", + "github_login": "Fuzzwah", + "twitter_username": "" + }, + { + "name": "Thibault J.", + "github_login": "thibault", + "twitter_username": "thibault" + }, + { + "name": "Pedro Campos", + "github_login": "pcampos119104", + "twitter_username": "" + }, + { + "name": "Vikas Yadav", + "github_login": "vik-y", + "twitter_username": "" + }, + { + "name": "Abdullah Adeel", + "github_login": "mabdullahadeel", + "twitter_username": "abdadeel_" + }, + { + "name": "Jorge Valdez", + "github_login": "jorgeavaldez", + "twitter_username": "" + }, + { + "name": "Ryan Fitch", + "github_login": "ryfi", + "twitter_username": "" + }, + { + "name": "ghazi-git", + "github_login": "ghazi-git", + "twitter_username": "" + }, + { + "name": "Cebrail Yılmaz", + "github_login": "b1sar", + "twitter_username": "" + }, + { + "name": "Artur Barseghyan", + "github_login": "barseghyanartur", + "twitter_username": "" + }, + { + "name": "innicoder", + "github_login": "innicoder", + "twitter_username": "" + }, + { + "name": "Naveen", + "github_login": "naveensrinivasan", + "twitter_username": "snaveen" + }, + { + "name": "Nikita Sobolev", + "github_login": "sobolevn", + "twitter_username": "" + }, + { + "name": "Sebastian Reyes Espinosa", + "github_login": "sebastian-code", + "twitter_username": "sebastianreyese" + }, + { + "name": "jugglinmike", + "github_login": "jugglinmike", + "twitter_username": "" + }, + { + "name": "monosans", + "github_login": "monosans", + "twitter_username": "" + }, + { + "name": "Marcio Mazza", + "github_login": "marciomazza", + "twitter_username": "marciomazza" + }, + { + "name": "Brandon Rumiser", + "github_login": "brumiser1550", + "twitter_username": "" + }, + { + "name": "krati yadav", + "github_login": "krati5", + "twitter_username": "" + }, + { + "name": "Abe Hanoka", + "github_login": "abe-101", + "twitter_username": "abe__101" + } +] \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..8922cd4502 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +# Config for Dependabot updates. See Documentation here: +# https://docs.github.com/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates + +version: 2 +updates: + # Update GitHub actions in workflows + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + labels: + - "update" + + # Update npm packages + - package-ecosystem: "npm" + directory: "{{cookiecutter.project_slug}}/" + schedule: + interval: "daily" + labels: + - "update" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..d466f40329 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,117 @@ +name: CI + +on: + push: + pull_request: + +concurrency: + group: ${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: pip + - name: Run pre-commit + uses: pre-commit/action@v3.0.0 + + tests: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macOS-latest + + name: "Run tests" + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: pip + - name: Install dependencies + run: pip install -r requirements.txt + - name: Run tests + run: pytest tests + + docker: + strategy: + fail-fast: false + matrix: + script: + - name: Basic + args: "" + - name: Extended + args: "use_celery=y use_drf=y frontend_pipeline=Gulp" + + name: "${{ matrix.script.name }} Docker" + runs-on: ubuntu-latest + env: + DOCKER_BUILDKIT: 1 + COMPOSE_DOCKER_CLI_BUILD: 1 + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: pip + - name: Install dependencies + run: pip install -r requirements.txt + - name: Docker ${{ matrix.script.name }} + run: sh tests/test_docker.sh ${{ matrix.script.args }} + + bare: + strategy: + fail-fast: false + matrix: + script: + - name: With Celery + args: "use_celery=y frontend_pipeline='Django Compressor'" + - name: With Gulp + args: "frontend_pipeline='Gulp'" + + name: "${{ matrix.script.name }} Bare metal" + runs-on: ubuntu-latest + services: + redis: + image: redis:5.0 + ports: + - 6379:6379 + postgres: + image: postgres:12 + ports: + - 5432:5432 + env: + POSTGRES_PASSWORD: postgres + + env: + CELERY_BROKER_URL: "redis://localhost:6379/0" + # postgres://user:password@host:port/database + DATABASE_URL: "postgres://postgres:postgres@localhost:5432/postgres" + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: pip + cache-dependency-path: | + requirements.txt + {{cookiecutter.project_slug}}/requirements/base.txt + {{cookiecutter.project_slug}}/requirements/local.txt + - name: Install dependencies + run: pip install -r requirements.txt + - uses: actions/setup-node@v3 + with: + node-version: "16" + - name: Bare Metal ${{ matrix.script.name }} + run: sh tests/test_bare.sh ${{ matrix.script.args }} diff --git a/.github/workflows/django-issue-checker.yml b/.github/workflows/django-issue-checker.yml new file mode 100644 index 0000000000..fd250604f6 --- /dev/null +++ b/.github/workflows/django-issue-checker.yml @@ -0,0 +1,30 @@ +# Creates a new issue for Major/Minor Django updates that keeps track +# of all dependencies that need to be updated/merged in order for the +# latest Django version to also be merged. +name: Django Issue Checker + +on: + schedule: + - cron: "28 5 * * *" + # Manual trigger + workflow_dispatch: + +jobs: + issue-checker: + # Disables this workflow from running in a repository that is not part of the indicated organization/user + if: github.repository_owner == 'cookiecutter' + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Create Django Major Issue + run: python scripts/create_django_issue.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml new file mode 100644 index 0000000000..a6e074137e --- /dev/null +++ b/.github/workflows/issue-manager.yml @@ -0,0 +1,40 @@ +# Automatically close issues or pull requests that have a label, after a custom delay, if no one replies. +# https://github.com/tiangolo/issue-manager +name: Issue Manager + +on: + schedule: + - cron: "12 0 * * *" + issue_comment: + types: + - created + issues: + types: + - labeled + pull_request_target: + types: + - labeled + workflow_dispatch: + +jobs: + issue-manager: + # Disables this workflow from running in a repository that is not part of the indicated organization/user + if: github.repository_owner == 'cookiecutter' + + runs-on: ubuntu-latest + steps: + - uses: tiangolo/issue-manager@0.4.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + config: > + { + "answered": { + "message": "Assuming the question was answered, this will be automatically closed now." + }, + "solved": { + "message": "Assuming the original issue was solved, it will be automatically closed now." + }, + "waiting": { + "message": "Automatically closing after waiting for additional info. To re-open, please provide the additional information requested." + } + } diff --git a/.github/workflows/pre-commit-autoupdate.yml b/.github/workflows/pre-commit-autoupdate.yml new file mode 100644 index 0000000000..1708e8b822 --- /dev/null +++ b/.github/workflows/pre-commit-autoupdate.yml @@ -0,0 +1,47 @@ +# Run pre-commit autoupdate every day at midnight +# and create a pull request if any changes + +name: Pre-commit auto-update + +on: + schedule: + - cron: "15 2 * * *" + workflow_dispatch: # to trigger manually + +permissions: + contents: read + +jobs: + auto-update: + # Disables this workflow from running in a repository that is not part of the indicated organization/user + if: github.repository_owner == 'cookiecutter' + permissions: + contents: write # for peter-evans/create-pull-request to create branch + pull-requests: write # for peter-evans/create-pull-request to create a PR + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Install pre-commit + run: pip install pre-commit + + - name: Autoupdate template + run: pre-commit autoupdate + + - name: Autoupdate generated projects + working-directory: "{{cookiecutter.project_slug}}" + run: pre-commit autoupdate + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: update/pre-commit-autoupdate + title: Auto-update pre-commit hooks + commit-message: Auto-update pre-commit hooks + body: Update versions of tools in pre-commit configs to latest version + labels: update diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml new file mode 100644 index 0000000000..27113a89f7 --- /dev/null +++ b/.github/workflows/update-changelog.yml @@ -0,0 +1,34 @@ +name: Update Changelog + +on: + # Every day at 2am + schedule: + - cron: "0 2 * * *" + # Manual trigger + workflow_dispatch: + +jobs: + release: + # Disables this workflow from running in a repository that is not part of the indicated organization/user + if: github.repository_owner == 'cookiecutter' + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Set git details + run: | + git config --global user.name "github-actions" + git config --global user.email "action@github.com" + - name: Update list + run: python scripts/update_changelog.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml new file mode 100644 index 0000000000..ee0c547e1f --- /dev/null +++ b/.github/workflows/update-contributors.yml @@ -0,0 +1,37 @@ +name: Update Contributors + +on: + push: + branches: + - master + +permissions: + contents: read + +jobs: + build: + # Disables this workflow from running in a repository that is not part of the indicated organization/user + if: github.repository_owner == 'cookiecutter' + permissions: + contents: write # for stefanzweifel/git-auto-commit-action to push code in repo + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Update list + run: python scripts/update_contributors.py + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v4.15.3 + with: + commit_message: Update Contributors + file_pattern: CONTRIBUTORS.md .github/contributors.json diff --git a/.gitignore b/.gitignore index 818cccd5a0..f753fec911 100644 --- a/.gitignore +++ b/.gitignore @@ -1,43 +1,217 @@ -### OSX ### -.DS_Store -.AppleDouble -.LSOverride +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage.* +.cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# Environments +.env +.venv +env/ +venv/ +ENV/ + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + + +### Linux template +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* -### SublimeText ### -# cache files for sublime text + +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + + +### Windows template +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### SublimeText template +# Cache files for Sublime Text *.tmlanguage.cache *.tmPreferences.cache *.stTheme.cache -# workspace files are user-specific +# Workspace files are user-specific *.sublime-workspace -# project files should be checked into the repository, unless a significant -# proportion of contributors will probably not be using SublimeText +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text # *.sublime-project -# sftp configuration file +# SFTP configuration file sftp-config.json -# Generated files -*.log -*.pot -*.pyc -.idea -_build +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache -# Project Specific Stuff -local_settings.py -project_slug -my_test_project/* +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings -# Generated when running py.test for the Cookiecutter Django generation tests -.cache/ -# Generated when running celery beat -celerybeat-schedule.db +### macOS template +# General +*.DS_Store +.AppleDouble +.LSOverride -# Unit test / coverage reports -.coverage -.tox -.cache +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### Vim template +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +# Auto-generated tag files +tags + + +# Even though the project might be opened and edited +# in any of the JetBrains IDEs, it makes no sence whatsoever +# to 'run' anything within it since any particular cookiecutter +# is declarative by nature. +.idea/ + +.pytest_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..e31055bb13 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +exclude: "{{cookiecutter.project_slug}}" +default_stages: [commit] + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + - id: check-yaml + + - repo: https://github.com/asottile/pyupgrade + rev: v3.2.2 + hooks: + - id: pyupgrade + args: [--py310-plus] + exclude: hooks/ + + - repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black + + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + +ci: + autoupdate_schedule: weekly + skip: [] + submodules: false diff --git a/.pyup.yml b/.pyup.yml new file mode 100644 index 0000000000..e5d4752e44 --- /dev/null +++ b/.pyup.yml @@ -0,0 +1,21 @@ +# configure updates globally +# default: all +# allowed: all, insecure, False +update: all + +# configure dependency pinning globally +# default: True +# allowed: True, False +pin: True + +# add a label to pull requests, default is not set +# requires private repo permissions, even on public repos +# default: empty +label_prs: update + +requirements: + - "requirements.txt" + - "docs/requirements.txt" + - "{{cookiecutter.project_slug}}/requirements/base.txt" + - "{{cookiecutter.project_slug}}/requirements/local.txt" + - "{{cookiecutter.project_slug}}/requirements/production.txt" diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000000..beb30d845f --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,15 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Version of Python and requirements required to build the docs +python: + version: "3.8" + install: + - requirements: docs/requirements.txt diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b548b92353..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Config file for automatic testing at travis-ci.org - -sudo: required - -services: - - docker - -language: python - -python: 3.5 - -env: - - TOX_ENV=py27 - - TOX_ENV=py34 - - TOX_ENV=py35 - -before_install: - - sudo apt-get update - - sudo apt-get install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" docker-engine=1.10.1-0~trusty - - sudo rm /usr/local/bin/docker-compose - - curl -L https://github.com/docker/compose/releases/download/1.6.0/docker-compose-`uname -s`-`uname -m` > docker-compose - - chmod +x docker-compose - - sudo mv docker-compose /usr/local/bin - -script: - - tox -e $TOX_ENV - - sh tests/test_docker.sh - -install: - - pip install tox - -notifications: - email: - on_success: change - on_failure: always diff --git a/CHANGELOG.md b/CHANGELOG.md index 76ba8fba54..bc6394eac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,2282 @@ # Change Log All enhancements and patches to Cookiecutter Django will be documented in this file. -This project adheres to [Semantic Versioning](http://semver.org/). + + + +## 2022.11.07 + +### Updated +- Update watchfiles to 0.18.1 ([#3938](https://github.com/cookiecutter/cookiecutter-django/pull/3938)) + +## 2022.11.06 + +### Changed +- Store extended Celery task attributes in backend ([#3855](https://github.com/cookiecutter/cookiecutter-django/pull/3855)) +- add os requirements for Ubuntu 22.04 (Jammy) ([#3930](https://github.com/cookiecutter/cookiecutter-django/pull/3930)) +### Updated +- Update pytest-sugar to 0.9.6 ([#3937](https://github.com/cookiecutter/cookiecutter-django/pull/3937)) +- Update pygithub to 1.57 ([#3936](https://github.com/cookiecutter/cookiecutter-django/pull/3936)) +- Update sphinx-rtd-theme to 1.1.1 ([#3935](https://github.com/cookiecutter/cookiecutter-django/pull/3935)) + +## 2022.11.02 + +### Changed +- fix typo in CONTRIBUTING.md ([#3932](https://github.com/cookiecutter/cookiecutter-django/pull/3932)) +### Updated +- Update crispy-bootstrap5 to 0.7 ([#3886](https://github.com/cookiecutter/cookiecutter-django/pull/3886)) +- Update django-coverage-plugin to 2.0.4 ([#3927](https://github.com/cookiecutter/cookiecutter-django/pull/3927)) +- Update pytz to 2022.6 ([#3928](https://github.com/cookiecutter/cookiecutter-django/pull/3928)) +- Update sphinx-rtd-theme to 1.1.0 ([#3929](https://github.com/cookiecutter/cookiecutter-django/pull/3929)) +- Update pillow to 9.3.0 ([#3922](https://github.com/cookiecutter/cookiecutter-django/pull/3922)) + +## 2022.10.30 + +### Updated +- Auto-update pre-commit hooks ([#3924](https://github.com/cookiecutter/cookiecutter-django/pull/3924)) + +## 2022.10.28 + +### Updated +- Bump stefanzweifel/git-auto-commit-action from 4.15.2 to 4.15.3 ([#3921](https://github.com/cookiecutter/cookiecutter-django/pull/3921)) + +## 2022.10.26 + +### Updated +- Update uvicorn to 0.19.0 ([#3920](https://github.com/cookiecutter/cookiecutter-django/pull/3920)) +- Update pytest to 7.2.0 ([#3919](https://github.com/cookiecutter/cookiecutter-django/pull/3919)) +- Update tox to 3.27.0 ([#3917](https://github.com/cookiecutter/cookiecutter-django/pull/3917)) +- Update psycopg2 to 2.9.5 ([#3918](https://github.com/cookiecutter/cookiecutter-django/pull/3918)) + +## 2022.10.24 + +### Changed +- Upgrade Python version from 3.9 to 3.10 ([#3913](https://github.com/cookiecutter/cookiecutter-django/pull/3913)) +### Updated +- Update sentry-sdk to 1.10.1 ([#3911](https://github.com/cookiecutter/cookiecutter-django/pull/3911)) +- Bump stefanzweifel/git-auto-commit-action from 4.15.1 to 4.15.2 ([#3914](https://github.com/cookiecutter/cookiecutter-django/pull/3914)) + +## 2022.10.19 + +### Changed +- Set AWS_S3_MAX_MEMORY_SIZE ([#3810](https://github.com/cookiecutter/cookiecutter-django/pull/3810)) +- Upgrade to Django 4.0 ([#3848](https://github.com/cookiecutter/cookiecutter-django/pull/3848)) +### Updated +- Update pytz to 2022.5 ([#3906](https://github.com/cookiecutter/cookiecutter-django/pull/3906)) +- Update sphinx to 5.3.0 ([#3905](https://github.com/cookiecutter/cookiecutter-django/pull/3905)) +- Update django-celery-beat to 2.4.0 ([#3908](https://github.com/cookiecutter/cookiecutter-django/pull/3908)) +- Update watchfiles to 0.18.0 ([#3907](https://github.com/cookiecutter/cookiecutter-django/pull/3907)) + +## 2022.10.13 + +### Updated +- Update pygithub to 1.56 ([#3904](https://github.com/cookiecutter/cookiecutter-django/pull/3904)) + +## 2022.10.11 + +### Updated +- Auto-update pre-commit hooks ([#3899](https://github.com/cookiecutter/cookiecutter-django/pull/3899)) +- Update flake8-isort to 5.0.0 ([#3901](https://github.com/cookiecutter/cookiecutter-django/pull/3901)) +- Update gitpython to 3.1.29 ([#3902](https://github.com/cookiecutter/cookiecutter-django/pull/3902)) +- Update psycopg2 to 2.9.4 ([#3896](https://github.com/cookiecutter/cookiecutter-django/pull/3896)) +- Bump stefanzweifel/git-auto-commit-action from 4.15.0 to 4.15.1 ([#3903](https://github.com/cookiecutter/cookiecutter-django/pull/3903)) +- Update black to 22.10.0 ([#3898](https://github.com/cookiecutter/cookiecutter-django/pull/3898)) + +## 2022.10.04 + +### Updated +- Update django to 3.2.16 ([#3895](https://github.com/cookiecutter/cookiecutter-django/pull/3895)) +- Update mypy to 0.982 ([#3893](https://github.com/cookiecutter/cookiecutter-django/pull/3893)) +- Auto-update pre-commit hooks ([#3894](https://github.com/cookiecutter/cookiecutter-django/pull/3894)) + +## 2022.10.03 + +### Updated +- Update sentry-sdk to 1.9.10 ([#3892](https://github.com/cookiecutter/cookiecutter-django/pull/3892)) + +## 2022.10.02 + +### Updated +- Update pytz to 2022.4 ([#3891](https://github.com/cookiecutter/cookiecutter-django/pull/3891)) + +## 2022.09.30 + +### Updated +- Update coverage to 6.5.0 ([#3890](https://github.com/cookiecutter/cookiecutter-django/pull/3890)) +- Update mypy to 0.981 ([#3889](https://github.com/cookiecutter/cookiecutter-django/pull/3889)) +- Update sentry-sdk to 1.9.9 ([#3888](https://github.com/cookiecutter/cookiecutter-django/pull/3888)) +- Update sphinx to 5.2.3 ([#3887](https://github.com/cookiecutter/cookiecutter-django/pull/3887)) + +## 2022.09.29 + +### Changed +- Remove outdated & optional Sendgrid settings from production config ([#3885](https://github.com/cookiecutter/cookiecutter-django/pull/3885)) + +## 2022.09.27 + +### Updated +- Update sphinx to 5.2.2 ([#3884](https://github.com/cookiecutter/cookiecutter-django/pull/3884)) + +## 2022.09.26 + +### Updated +- Update drf-spectacular to 0.24.2 ([#3882](https://github.com/cookiecutter/cookiecutter-django/pull/3882)) +- Update djangorestframework to 3.14.0 ([#3881](https://github.com/cookiecutter/cookiecutter-django/pull/3881)) +- Update django-debug-toolbar to 3.7.0 ([#3878](https://github.com/cookiecutter/cookiecutter-django/pull/3878)) +- Auto-update pre-commit hooks ([#3877](https://github.com/cookiecutter/cookiecutter-django/pull/3877)) +- Bump stefanzweifel/git-auto-commit-action from 4.14.1 to 4.15.0 ([#3880](https://github.com/cookiecutter/cookiecutter-django/pull/3880)) +- Update sphinx to 5.2.1 ([#3879](https://github.com/cookiecutter/cookiecutter-django/pull/3879)) + +## 2022.09.24 + +### Fixed +- Remove `--no-deps` in pip wheels command of docs Dockerfile ([#3875](https://github.com/cookiecutter/cookiecutter-django/pull/3875)) + +## 2022.09.23 + +### Changed +- Reload uvicorn on html file change ([#3866](https://github.com/cookiecutter/cookiecutter-django/pull/3866)) +- Mailjet default api url does not work out of the box ([#3871](https://github.com/cookiecutter/cookiecutter-django/pull/3871)) +### Updated +- Auto-update pre-commit hooks ([#3872](https://github.com/cookiecutter/cookiecutter-django/pull/3872)) +- Update django-extensions to 3.2.1 ([#3867](https://github.com/cookiecutter/cookiecutter-django/pull/3867)) +- Update tox to 3.26.0 ([#3864](https://github.com/cookiecutter/cookiecutter-django/pull/3864)) +- Update drf-spectacular to 0.24.1 ([#3874](https://github.com/cookiecutter/cookiecutter-django/pull/3874)) + +## 2022.09.15 + +### Updated +- Update watchfiles to 0.17.0 ([#3869](https://github.com/cookiecutter/cookiecutter-django/pull/3869)) +- Update drf-spectacular to 0.24.0 ([#3870](https://github.com/cookiecutter/cookiecutter-django/pull/3870)) + +## 2022.09.05 + +### Updated +- Update sentry-sdk to 1.9.8 ([#3861](https://github.com/cookiecutter/cookiecutter-django/pull/3861)) + +## 2022.09.02 + +### Updated +- Update pytest to 7.1.3 ([#3860](https://github.com/cookiecutter/cookiecutter-django/pull/3860)) +- Update sentry-sdk to 1.9.7 ([#3859](https://github.com/cookiecutter/cookiecutter-django/pull/3859)) + +## 2022.09.01 + +### Changed +- Add article to README about how to use a hosted DB ([#3844](https://github.com/cookiecutter/cookiecutter-django/pull/3844)) +### Updated +- Update sentry-sdk to 1.9.6 ([#3856](https://github.com/cookiecutter/cookiecutter-django/pull/3856)) +- Auto-update pre-commit hooks ([#3858](https://github.com/cookiecutter/cookiecutter-django/pull/3858)) +- Update black to 22.8.0 ([#3857](https://github.com/cookiecutter/cookiecutter-django/pull/3857)) + +## 2022.08.26 + +### Changed +- Fix formatting in docs ([#3850](https://github.com/cookiecutter/cookiecutter-django/pull/3850)) + +## 2022.08.24 + +### Updated +- Update django-debug-toolbar to 3.6.0 ([#3847](https://github.com/cookiecutter/cookiecutter-django/pull/3847)) +- Update werkzeug to 2.2.2 ([#3846](https://github.com/cookiecutter/cookiecutter-django/pull/3846)) +- Update coverage to 6.4.4 ([#3842](https://github.com/cookiecutter/cookiecutter-django/pull/3842)) +- Update uvicorn to 0.18.3 ([#3845](https://github.com/cookiecutter/cookiecutter-django/pull/3845)) +- Update sentry-sdk to 1.9.5 ([#3841](https://github.com/cookiecutter/cookiecutter-django/pull/3841)) +- Update flower to 1.2.0 ([#3836](https://github.com/cookiecutter/cookiecutter-django/pull/3836)) +- Update django-storages to 1.13.1 ([#3833](https://github.com/cookiecutter/cookiecutter-django/pull/3833)) + +## 2022.08.15 + +### Updated +- Update coverage to 6.4.3 ([#3835](https://github.com/cookiecutter/cookiecutter-django/pull/3835)) +- Update pytz to 2022.2.1 ([#3840](https://github.com/cookiecutter/cookiecutter-django/pull/3840)) +- Update sentry-sdk to 1.9.4 ([#3838](https://github.com/cookiecutter/cookiecutter-django/pull/3838)) + +## 2022.08.09 + +### Updated +- Update sentry-sdk to 1.9.3 ([#3837](https://github.com/cookiecutter/cookiecutter-django/pull/3837)) + +## 2022.08.05 + +### Updated +- Update sentry-sdk to 1.9.2 ([#3832](https://github.com/cookiecutter/cookiecutter-django/pull/3832)) + +## 2022.08.04 + +### Updated +- Auto-update pre-commit hooks ([#3816](https://github.com/cookiecutter/cookiecutter-django/pull/3816)) +- Update flake8 to 5.0.4 ([#3829](https://github.com/cookiecutter/cookiecutter-django/pull/3829)) +- Update django-compressor to 4.1 ([#3823](https://github.com/cookiecutter/cookiecutter-django/pull/3823)) +- Update flake8-isort to 4.2.0 ([#3828](https://github.com/cookiecutter/cookiecutter-django/pull/3828)) + +## 2022.08.03 + +### Updated +- Update django to 3.2.15 ([#3822](https://github.com/cookiecutter/cookiecutter-django/pull/3822)) + +## 2022.07.29 + +### Updated +- Update sentry-sdk to 1.9.0 ([#3815](https://github.com/cookiecutter/cookiecutter-django/pull/3815)) + +## 2022.07.28 + +### Updated +- Update werkzeug to 2.2.1 ([#3814](https://github.com/cookiecutter/cookiecutter-django/pull/3814)) + +## 2022.07.27 + +### Updated +- Update werkzeug to 2.2.0 ([#3813](https://github.com/cookiecutter/cookiecutter-django/pull/3813)) +- Update sphinx to 5.1.1 ([#3811](https://github.com/cookiecutter/cookiecutter-django/pull/3811)) +- Update drf-spectacular to 0.23.1 ([#3812](https://github.com/cookiecutter/cookiecutter-django/pull/3812)) + +## 2022.07.26 + +### Changed +- Switch from `watchgod` to `watchfiles` ([#3791](https://github.com/cookiecutter/cookiecutter-django/pull/3791)) +- Change Django settings file used by pylint ([#3806](https://github.com/cookiecutter/cookiecutter-django/pull/3806)) +- Simplify database access in tests ([#3807](https://github.com/cookiecutter/cookiecutter-django/pull/3807)) +- Provide more context when wating for PostgreSQL takes too long ([#3782](https://github.com/cookiecutter/cookiecutter-django/pull/3782)) +### Updated +- Update django-compressor to 4.0 ([#3802](https://github.com/cookiecutter/cookiecutter-django/pull/3802)) +- Update flake8-isort to 4.1.2.post0 ([#3809](https://github.com/cookiecutter/cookiecutter-django/pull/3809)) +- Update sphinx to 5.1.0 ([#3808](https://github.com/cookiecutter/cookiecutter-django/pull/3808)) +- Update sh to 1.14.3 ([#3798](https://github.com/cookiecutter/cookiecutter-django/pull/3798)) +- Auto-update pre-commit hooks ([#3780](https://github.com/cookiecutter/cookiecutter-django/pull/3780)) + +## 2022.07.22 + +### Updated +- Update pytest-sugar to 0.9.5 ([#3800](https://github.com/cookiecutter/cookiecutter-django/pull/3800)) +- Update sphinx to 5.0.2 ([#3801](https://github.com/cookiecutter/cookiecutter-django/pull/3801)) +- Update pillow to 9.2.0 ([#3799](https://github.com/cookiecutter/cookiecutter-django/pull/3799)) +- Update werkzeug to 2.1.2 ([#3797](https://github.com/cookiecutter/cookiecutter-django/pull/3797)) + +## 2022.07.21 + +### Changed +- Set user to form instance in update user view test ([#3776](https://github.com/cookiecutter/cookiecutter-django/pull/3776)) +- Fix warning from django-coverage-plugin in tests ([#3790](https://github.com/cookiecutter/cookiecutter-django/pull/3790)) +- Always use `const` instead of `var` in `gulpfile.js` ([#3786](https://github.com/cookiecutter/cookiecutter-django/pull/3786)) +### Updated +- Update flower to 1.1.0 ([#3796](https://github.com/cookiecutter/cookiecutter-django/pull/3796)) +- Update coverage to 6.4.2 ([#3783](https://github.com/cookiecutter/cookiecutter-django/pull/3783)) +- Update mypy to 0.971 ([#3788](https://github.com/cookiecutter/cookiecutter-django/pull/3788)) +- Update sentry-sdk to 1.8.0 ([#3792](https://github.com/cookiecutter/cookiecutter-django/pull/3792)) +- Update pre-commit to 2.20.0 ([#3779](https://github.com/cookiecutter/cookiecutter-django/pull/3779)) +- Update django-extensions to 3.2.0 ([#3774](https://github.com/cookiecutter/cookiecutter-django/pull/3774)) +- Update tox to 3.25.1 ([#3767](https://github.com/cookiecutter/cookiecutter-django/pull/3767)) +- Update uvicorn to 0.18.2 ([#3762](https://github.com/cookiecutter/cookiecutter-django/pull/3762)) +- Update redis to 4.3.4 ([#3763](https://github.com/cookiecutter/cookiecutter-django/pull/3763)) +- Update requests to 2.28.1 ([#3766](https://github.com/cookiecutter/cookiecutter-django/pull/3766)) + +## 2022.07.10 + +### Changed +- Revert auto-update pre-commit hooks ([#3778](https://github.com/cookiecutter/cookiecutter-django/pull/3778)) +### Updated +- Auto-update pre-commit hooks ([#3775](https://github.com/cookiecutter/cookiecutter-django/pull/3775)) + +## 2022.07.06 + +### Updated +- Update django to 3.2.14 ([#3768](https://github.com/cookiecutter/cookiecutter-django/pull/3768)) + +## 2022.06.28 + +### Updated +- Auto-update pre-commit hooks ([#3765](https://github.com/cookiecutter/cookiecutter-django/pull/3765)) +- Update black to 22.6.0 ([#3764](https://github.com/cookiecutter/cookiecutter-django/pull/3764)) + +## 2022.06.23 + +### Updated +- Update django-debug-toolbar to 3.5.0 ([#3760](https://github.com/cookiecutter/cookiecutter-django/pull/3760)) + +## 2022.06.22 + +### Updated +- Update django-stubs to 1.12.0 ([#3757](https://github.com/cookiecutter/cookiecutter-django/pull/3757)) +- Update sentry-sdk to 1.6.0 ([#3756](https://github.com/cookiecutter/cookiecutter-django/pull/3756)) +- Update djangorestframework-stubs to 1.7.0 ([#3754](https://github.com/cookiecutter/cookiecutter-django/pull/3754)) + +## 2022.06.15 + +### Updated +- Update django-environ to 0.9.0 ([#3751](https://github.com/cookiecutter/cookiecutter-django/pull/3751)) + +## 2022.06.13 + +### Updated +- Update cookiecutter to 2.1.1 ([#3727](https://github.com/cookiecutter/cookiecutter-django/pull/3727)) + +## 2022.06.11 + +### Updated +- Update requests to 2.28.0 ([#3748](https://github.com/cookiecutter/cookiecutter-django/pull/3748)) + +## 2022.06.09 + +### Updated +- Bump actions/setup-python from 3 to 4 ([#3746](https://github.com/cookiecutter/cookiecutter-django/pull/3746)) + +## 2022.06.08 + +### Updated +- Auto-update pre-commit hooks ([#3744](https://github.com/cookiecutter/cookiecutter-django/pull/3744)) + +## 2022.06.07 + +### Updated +- Update django-allauth to 0.51.0 ([#3743](https://github.com/cookiecutter/cookiecutter-django/pull/3743)) +- Auto-update pre-commit hooks ([#3742](https://github.com/cookiecutter/cookiecutter-django/pull/3742)) + +## 2022.06.06 + +### Updated +- Bump pre-commit/action from 2.0.3 to 3.0.0 ([#3739](https://github.com/cookiecutter/cookiecutter-django/pull/3739)) + +## 2022.06.05 + +### Updated +- Update whitenoise to 6.2.0 ([#3737](https://github.com/cookiecutter/cookiecutter-django/pull/3737)) +- Update django-cors-headers to 3.13.0 ([#3738](https://github.com/cookiecutter/cookiecutter-django/pull/3738)) + +## 2022.06.04 + +### Updated +- Update django-cors-headers to 3.12.0 ([#3736](https://github.com/cookiecutter/cookiecutter-django/pull/3736)) +- Update djangorestframework-stubs to 1.6.0 ([#3718](https://github.com/cookiecutter/cookiecutter-django/pull/3718)) +- Update django-stubs to 1.11.0 ([#3734](https://github.com/cookiecutter/cookiecutter-django/pull/3734)) +- Update sphinx to 5.0.1 ([#3733](https://github.com/cookiecutter/cookiecutter-django/pull/3733)) +- Update sphinx to 5.0.0 ([#3724](https://github.com/cookiecutter/cookiecutter-django/pull/3724)) +- Update celery to 5.2.7 ([#3732](https://github.com/cookiecutter/cookiecutter-django/pull/3732)) +- Update django-celery-beat to 2.3.0 ([#3731](https://github.com/cookiecutter/cookiecutter-django/pull/3731)) + +## 2022.06.02 + +### Updated +- Update coverage to 6.4.1 ([#3729](https://github.com/cookiecutter/cookiecutter-django/pull/3729)) +- Update redis to 4.3.3 ([#3728](https://github.com/cookiecutter/cookiecutter-django/pull/3728)) + +## 2022.06.01 + +### Updated +- Update redis to 4.3.2 ([#3726](https://github.com/cookiecutter/cookiecutter-django/pull/3726)) + +## 2022.05.24 + +### Updated +- Update coverage to 6.4 ([#3716](https://github.com/cookiecutter/cookiecutter-django/pull/3716)) + +## 2022.05.18 + +### Updated +- Update pillow to 9.1.1 ([#3714](https://github.com/cookiecutter/cookiecutter-django/pull/3714)) + +## 2022.05.16 + +### Changed +- Update postgres versions ([#3712](https://github.com/cookiecutter/cookiecutter-django/pull/3712)) +### Updated +- Update django-anymail to 8.6 ([#3713](https://github.com/cookiecutter/cookiecutter-django/pull/3713)) + +## 2022.05.14 + +### Updated +- Update coverage to 6.3.3 ([#3709](https://github.com/cookiecutter/cookiecutter-django/pull/3709)) +- Update whitenoise to 6.1.0 ([#3707](https://github.com/cookiecutter/cookiecutter-django/pull/3707)) +- Update sentry-sdk to 1.5.12 ([#3706](https://github.com/cookiecutter/cookiecutter-django/pull/3706)) +- Update redis to 4.3.1 ([#3704](https://github.com/cookiecutter/cookiecutter-django/pull/3704)) + +## 2022.05.07 + +### Changed +- Add pyupgrade to pre-commit config ([#3702](https://github.com/cookiecutter/cookiecutter-django/pull/3702)) +- Set permissions for GitHub actions ([#3698](https://github.com/cookiecutter/cookiecutter-django/pull/3698)) +### Updated +- Update jinja2 to 3.1.2 ([#3700](https://github.com/cookiecutter/cookiecutter-django/pull/3700)) + +## 2022.05.06 + +### Updated +- Update pre-commit to 2.19.0 ([#3697](https://github.com/cookiecutter/cookiecutter-django/pull/3697)) + +## 2022.05.04 + +### Updated +- Update django-coverage-plugin to 2.0.3 ([#3695](https://github.com/cookiecutter/cookiecutter-django/pull/3695)) + +## 2022.05.03 + +### Updated +- Update django-debug-toolbar to 3.4.0 ([#3692](https://github.com/cookiecutter/cookiecutter-django/pull/3692)) +- Update sentry-sdk to 1.5.11 ([#3693](https://github.com/cookiecutter/cookiecutter-django/pull/3693)) + +## 2022.05.01 + +### Updated +- Update django-debug-toolbar to 3.3.0 ([#3690](https://github.com/cookiecutter/cookiecutter-django/pull/3690)) + +## 2022.04.28 + +### Changed +- Add the possibility to set a max django version on create_django_issue script ([#3680](https://github.com/cookiecutter/cookiecutter-django/pull/3680)) + +## 2022.04.27 + +### Updated +- Update mypy to 0.950 ([#3687](https://github.com/cookiecutter/cookiecutter-django/pull/3687)) +- Update python-slugify to 6.1.2 ([#3686](https://github.com/cookiecutter/cookiecutter-django/pull/3686)) +- Update drf-spectacular to 0.22.1 ([#3684](https://github.com/cookiecutter/cookiecutter-django/pull/3684)) + +## 2022.04.25 + +### Updated +- Update pytest to 7.1.2 ([#3683](https://github.com/cookiecutter/cookiecutter-django/pull/3683)) + +## 2022.04.19 + +### Updated +- Update tox to 3.25.0 ([#3675](https://github.com/cookiecutter/cookiecutter-django/pull/3675)) +- Update sentry-sdk to 1.5.10 ([#3679](https://github.com/cookiecutter/cookiecutter-django/pull/3679)) + +## 2022.04.13 + +### Updated +- Bump stefanzweifel/git-auto-commit-action from 4.14.0 to 4.14.1 ([#3677](https://github.com/cookiecutter/cookiecutter-django/pull/3677)) + +## 2022.04.11 + +### Updated +- Update django to 3.2.13 ([#3676](https://github.com/cookiecutter/cookiecutter-django/pull/3676)) + +## 2022.04.08 + +### Updated +- Auto-update pre-commit hooks ([#3673](https://github.com/cookiecutter/cookiecutter-django/pull/3673)) + +## 2022.04.05 + +### Updated +- Update celery to 5.2.6 ([#3671](https://github.com/cookiecutter/cookiecutter-django/pull/3671)) + +## 2022.04.04 + +### Updated +- Update redis to 4.2.2 ([#3670](https://github.com/cookiecutter/cookiecutter-django/pull/3670)) +- Update celery to 5.2.5 ([#3669](https://github.com/cookiecutter/cookiecutter-django/pull/3669)) +- Update pre-commit to 2.18.1 ([#3668](https://github.com/cookiecutter/cookiecutter-django/pull/3668)) +- Update pillow to 9.1.0 ([#3665](https://github.com/cookiecutter/cookiecutter-django/pull/3665)) + +## 2022.04.01 + +### Changed +- Update domain for Celery docs ([#3663](https://github.com/cookiecutter/cookiecutter-django/pull/3663)) +### Updated +- Update watchgod to 0.8.2 ([#3664](https://github.com/cookiecutter/cookiecutter-django/pull/3664)) +- Update redis to 4.2.1 ([#3660](https://github.com/cookiecutter/cookiecutter-django/pull/3660)) + +## 2022.03.28 + +### Changed +- Update `black` version to `22.3.0` ([#3657](https://github.com/cookiecutter/cookiecutter-django/pull/3657)) + +## 2022.03.27 + +### Updated +- Update sphinx to 4.5.0 ([#3654](https://github.com/cookiecutter/cookiecutter-django/pull/3654)) +- Update jinja2 to 3.1.1 ([#3652](https://github.com/cookiecutter/cookiecutter-django/pull/3652)) +- Update pylint-django to 2.5.3 ([#3650](https://github.com/cookiecutter/cookiecutter-django/pull/3650)) +- Update django-allauth to 0.50.0 ([#3649](https://github.com/cookiecutter/cookiecutter-django/pull/3649)) +- Update mypy to 0.942 ([#3648](https://github.com/cookiecutter/cookiecutter-django/pull/3648)) +- Update jinja2 to 3.1.0 ([#3647](https://github.com/cookiecutter/cookiecutter-django/pull/3647)) +- Update redis to 4.2.0 ([#3646](https://github.com/cookiecutter/cookiecutter-django/pull/3646)) +- Update watchgod to 0.8.1 ([#3643](https://github.com/cookiecutter/cookiecutter-django/pull/3643)) +- Bump stefanzweifel/git-auto-commit-action from 4.13.1 to 4.14.0 ([#3641](https://github.com/cookiecutter/cookiecutter-django/pull/3641)) +- Update drf-spectacular to 0.22.0 ([#3642](https://github.com/cookiecutter/cookiecutter-django/pull/3642)) +- Update pytz to 2022.1 ([#3639](https://github.com/cookiecutter/cookiecutter-django/pull/3639)) +- Update sentry-sdk to 1.5.8 ([#3638](https://github.com/cookiecutter/cookiecutter-django/pull/3638)) +- Update pytest to 7.1.1 ([#3637](https://github.com/cookiecutter/cookiecutter-django/pull/3637)) +- Update uvicorn to 0.17.6 ([#3627](https://github.com/cookiecutter/cookiecutter-django/pull/3627)) + +## 2022.03.23 + +### Updated +- Bump peter-evans/create-pull-request from 3.14.0 to 4 ([#3645](https://github.com/cookiecutter/cookiecutter-django/pull/3645)) + +## 2022.03.20 + +### Changed +- Unify compressor, gulp and custom bootstrap options ([#3535](https://github.com/cookiecutter/cookiecutter-django/pull/3535)) + +## 2022.03.14 + +### Fixed +- Fix broken link in README of generated projects ([#3634](https://github.com/cookiecutter/cookiecutter-django/pull/3634)) + +## 2022.03.13 + +### Changed +- Add DRF spectacular link in requirements ([#3630](https://github.com/cookiecutter/cookiecutter-django/pull/3630)) + +## 2022.03.09 + +### Changed +- Fix a few typos in the documentation ([#3625](https://github.com/cookiecutter/cookiecutter-django/pull/3625)) + +## 2022.03.08 + +### Updated +- Update sentry-sdk to 1.5.7 ([#3624](https://github.com/cookiecutter/cookiecutter-django/pull/3624)) + +## 2022.03.03 + +### Updated +- Upgrade actions/setup-python to v3 ([#3621](https://github.com/cookiecutter/cookiecutter-django/pull/3621)) + +## 2022.03.02 + +### Updated +- Bump actions/checkout from 2 to 3 ([#3619](https://github.com/cookiecutter/cookiecutter-django/pull/3619)) + +## 2022.03.01 + +### Updated +- Bump actions/setup-python from 2 to 3 ([#3617](https://github.com/cookiecutter/cookiecutter-django/pull/3617)) +- Bump peter-evans/create-pull-request from 3.13.0 to 3.14.0 ([#3618](https://github.com/cookiecutter/cookiecutter-django/pull/3618)) + +## 2022.02.28 + +### Updated +- Update python-slugify to 6.1.1 ([#3615](https://github.com/cookiecutter/cookiecutter-django/pull/3615)) +- Bump peter-evans/create-pull-request from 3.12.1 to 3.13.0 ([#3616](https://github.com/cookiecutter/cookiecutter-django/pull/3616)) + +## 2022.02.25 + +### Updated +- Bump actions/setup-node from 2 to 3 ([#3614](https://github.com/cookiecutter/cookiecutter-django/pull/3614)) + +## 2022.02.24 + +### Updated +- Update django-allauth to 0.49.0 ([#3613](https://github.com/cookiecutter/cookiecutter-django/pull/3613)) +- Update sentry-sdk to 1.5.6 ([#3611](https://github.com/cookiecutter/cookiecutter-django/pull/3611)) +- Update python-slugify to 6.1.0 ([#3612](https://github.com/cookiecutter/cookiecutter-django/pull/3612)) + +## 2022.02.21 + +### Changed +- Cancel previous CI runs on successive PR pushes with GitHub actions ([#3575](https://github.com/cookiecutter/cookiecutter-django/pull/3575)) +### Updated +- Update coverage to 6.3.2 ([#3610](https://github.com/cookiecutter/cookiecutter-django/pull/3610)) +- Update gitpython to 3.1.27 ([#3607](https://github.com/cookiecutter/cookiecutter-django/pull/3607)) +- Update pylint-django to 2.5.2 ([#3602](https://github.com/cookiecutter/cookiecutter-django/pull/3602)) +- Update python-slugify to 6.0.1 ([#3599](https://github.com/cookiecutter/cookiecutter-django/pull/3599)) +- Update uvicorn to 0.17.5 ([#3596](https://github.com/cookiecutter/cookiecutter-django/pull/3596)) +- Update redis to 4.1.4 ([#3595](https://github.com/cookiecutter/cookiecutter-django/pull/3595)) + +## 2022.02.20 + +### Changed +- Fix incorrect createdb instruction in documentation ([#3606](https://github.com/cookiecutter/cookiecutter-django/pull/3606)) + +## 2022.02.16 + +### Fixed +- Fix Swagger schema API endpoint & add a test for it ([#3592](https://github.com/cookiecutter/cookiecutter-django/pull/3592)) + +## 2022.02.15 + +### Changed +- Update the drf-spectacular local dev server url to use http instead of https ([#3591](https://github.com/cookiecutter/cookiecutter-django/pull/3591)) + +## 2022.02.13 + +### Changed +- Change docs port from 7000 to 9000 ([#3590](https://github.com/cookiecutter/cookiecutter-django/pull/3590)) + +## 2022.02.12 + +### Updated +- Update pytest to 7.0.1 ([#3588](https://github.com/cookiecutter/cookiecutter-django/pull/3588)) + +## 2022.02.11 + +### Updated +- Update sentry-sdk to 1.5.5 ([#3586](https://github.com/cookiecutter/cookiecutter-django/pull/3586)) + +## 2022.02.10 + +### Fixed +- Fix GitLab CI error caused by Docker Compose's `platform` option ([#3585](https://github.com/cookiecutter/cookiecutter-django/pull/3585)) +### Updated +- Update whitenoise to 6.0.0 ([#3583](https://github.com/cookiecutter/cookiecutter-django/pull/3583)) + +## 2022.02.08 + +### Fixed +- Fixed some typos in drf-spectacular description and comments ([#3579](https://github.com/cookiecutter/cookiecutter-django/pull/3579)) +### Updated +- Update redis to 4.1.3 ([#3577](https://github.com/cookiecutter/cookiecutter-django/pull/3577)) +- Update werkzeug to 2.0.3 ([#3576](https://github.com/cookiecutter/cookiecutter-django/pull/3576)) + +## 2022.02.07 + +### Changed +- Update black to 22.1.0 ([#3572](https://github.com/cookiecutter/cookiecutter-django/pull/3572)) +### Fixed +- Fix docker-compose config on Apple silicon ([#3562](https://github.com/cookiecutter/cookiecutter-django/pull/3562)) +### Updated +- Update uvicorn to 0.17.4 ([#3574](https://github.com/cookiecutter/cookiecutter-django/pull/3574)) +- Update django-allauth to 0.48.0 ([#3573](https://github.com/cookiecutter/cookiecutter-django/pull/3573)) +- Update pytest to 7.0.0 ([#3567](https://github.com/cookiecutter/cookiecutter-django/pull/3567)) +- Update coverage to 6.3.1 ([#3561](https://github.com/cookiecutter/cookiecutter-django/pull/3561)) +- Update pillow to 9.0.1 ([#3571](https://github.com/cookiecutter/cookiecutter-django/pull/3571)) +- Bump peter-evans/create-pull-request from 3.12.0 to 3.12.1 ([#3558](https://github.com/cookiecutter/cookiecutter-django/pull/3558)) +- Update drf-spectacular to 0.21.2 ([#3560](https://github.com/cookiecutter/cookiecutter-django/pull/3560)) +- Update django to 3.2.12 ([#3559](https://github.com/cookiecutter/cookiecutter-django/pull/3559)) + +## 2022.01.27 + +### Updated +- Update redis to 4.1.2 ([#3551](https://github.com/cookiecutter/cookiecutter-django/pull/3551)) + +## 2022.01.26 + +### Updated +- Update coverage to 6.3 ([#3550](https://github.com/cookiecutter/cookiecutter-django/pull/3550)) +- Update sentry-sdk to 1.5.4 ([#3549](https://github.com/cookiecutter/cookiecutter-django/pull/3549)) +- Update django-crispy-forms to 1.14.0 ([#3548](https://github.com/cookiecutter/cookiecutter-django/pull/3548)) +- Update uvicorn to 0.17.0.post1 ([#3547](https://github.com/cookiecutter/cookiecutter-django/pull/3547)) + +## 2022.01.21 + +### Changed +- mysql support link ([#3544](https://github.com/cookiecutter/cookiecutter-django/pull/3544)) +### Updated +- Update sentry-sdk to 1.5.3 ([#3543](https://github.com/cookiecutter/cookiecutter-django/pull/3543)) +- Update django-anymail to 8.5 ([#3542](https://github.com/cookiecutter/cookiecutter-django/pull/3542)) + +## 2022.01.19 + +### Changed +- Add swagger API documentation when DRF is enabled ([#3536](https://github.com/cookiecutter/cookiecutter-django/pull/3536)) +### Updated +- Update pre-commit to 2.17.0 ([#3541](https://github.com/cookiecutter/cookiecutter-django/pull/3541)) + +## 2022.01.17 + +### Changed +- Avoid docker image/volume collision by prefixing with project slug ([#3528](https://github.com/cookiecutter/cookiecutter-django/pull/3528)) +### Updated +- Update redis to 4.1.1 ([#3540](https://github.com/cookiecutter/cookiecutter-django/pull/3540)) +- Update sphinx to 4.4.0 ([#3537](https://github.com/cookiecutter/cookiecutter-django/pull/3537)) + +## 2022.01.14 + +### Updated +- Update uvicorn to 0.17.0 ([#3534](https://github.com/cookiecutter/cookiecutter-django/pull/3534)) +- Bump stefanzweifel/git-auto-commit-action from 4.13.0 to 4.13.1 ([#3532](https://github.com/cookiecutter/cookiecutter-django/pull/3532)) + +## 2022.01.13 + +### Changed +- Add UserSignupForm and UserSocialSignupForm ([#3515](https://github.com/cookiecutter/cookiecutter-django/pull/3515)) +### Fixed +- Fix high CPU usage when running `runserver_plus` in Docker ([#3531](https://github.com/cookiecutter/cookiecutter-django/pull/3531)) +- Fix out-of-sync sequence for Site ID ([#3511](https://github.com/cookiecutter/cookiecutter-django/pull/3511)) + +## 2022.01.11 + +### Updated +- Bump stefanzweifel/git-auto-commit-action from 4.12.0 to 4.13.0 ([#3527](https://github.com/cookiecutter/cookiecutter-django/pull/3527)) + +## 2022.01.10 + +### Updated +- Update django-cors-headers to 3.11.0 ([#3526](https://github.com/cookiecutter/cookiecutter-django/pull/3526)) +- Update sentry-sdk to 1.5.2 ([#3525](https://github.com/cookiecutter/cookiecutter-django/pull/3525)) +- Update gitpython to 3.1.26 ([#3524](https://github.com/cookiecutter/cookiecutter-django/pull/3524)) + +## 2022.01.09 + +### Changed +- Fix broken center align of image links in README ([#3522](https://github.com/cookiecutter/cookiecutter-django/pull/3522)) + +## 2022.01.07 + +### Fixed +- Fix cache dependency path for linter job in CI workflow ([#3520](https://github.com/cookiecutter/cookiecutter-django/pull/3520)) +- Fix `open` option for `initBrowserSync` when using Docker ([#3519](https://github.com/cookiecutter/cookiecutter-django/pull/3519)) +### Updated +- Update mypy to 0.931 ([#3521](https://github.com/cookiecutter/cookiecutter-django/pull/3521)) +- Update gitpython to 3.1.25 ([#3518](https://github.com/cookiecutter/cookiecutter-django/pull/3518)) + +## 2022.01.06 + +### Changed +- Update output example in README ([#3512](https://github.com/cookiecutter/cookiecutter-django/pull/3512)) + +## 2022.01.05 + +### Changed +- Update references to Bootstrap from v4 to v5 in README ([#3513](https://github.com/cookiecutter/cookiecutter-django/pull/3513)) +### Updated +- Update requests to 2.27.1 ([#3516](https://github.com/cookiecutter/cookiecutter-django/pull/3516)) + +## 2022.01.04 + +### Changed +- Double quote array expansions to avoid re-splitting elements ([#3514](https://github.com/cookiecutter/cookiecutter-django/pull/3514)) +### Updated +- Update django to 3.2.11 ([#3510](https://github.com/cookiecutter/cookiecutter-django/pull/3510)) + +## 2022.01.03 + +### Changed +- Convert top level RST files to Markdown ([#3489](https://github.com/cookiecutter/cookiecutter-django/pull/3489)) +### Updated +- Update requests to 2.27.0 ([#3509](https://github.com/cookiecutter/cookiecutter-django/pull/3509)) +- Update pillow to 9.0.0 ([#3508](https://github.com/cookiecutter/cookiecutter-django/pull/3508)) +- Update pylint-django to 2.5.0 ([#3505](https://github.com/cookiecutter/cookiecutter-django/pull/3505)) + +## 2021.12.29 + +### Fixed +- Add generated files to `.gitignore` when selecting Gulp ([#3500](https://github.com/cookiecutter/cookiecutter-django/pull/3500)) +### Updated +- Update psycopg2-binary to 2.9.3 ([#3504](https://github.com/cookiecutter/cookiecutter-django/pull/3504)) +- Update psycopg2 to 2.9.3 ([#3503](https://github.com/cookiecutter/cookiecutter-django/pull/3503)) +- Update celery to 5.2.3 ([#3502](https://github.com/cookiecutter/cookiecutter-django/pull/3502)) +- Update tox to 3.24.5 ([#3501](https://github.com/cookiecutter/cookiecutter-django/pull/3501)) + +## 2021.12.28 + +### Changed +- Build the HTML for the documentation as part of the CI ([#3498](https://github.com/cookiecutter/cookiecutter-django/pull/3498)) + +## 2021.12.27 + +### Changed +- Hides 'sign up' elements when ACCOUNT_ALLOW_REGISTRATION is disabled ([#1914](https://github.com/cookiecutter/cookiecutter-django/pull/1914)) + +## 2021.12.26 + +### Fixed +- Fix missing psycopg2 dependency in docs Docker image ([#3494](https://github.com/cookiecutter/cookiecutter-django/pull/3494)) +### Updated +- Update celery to 5.2.2 ([#3496](https://github.com/cookiecutter/cookiecutter-django/pull/3496)) +- Update redis to 4.1.0 ([#3495](https://github.com/cookiecutter/cookiecutter-django/pull/3495)) + +## 2021.12.25 + +### Changed +- Automatically add Django version label to issue ([#3492](https://github.com/cookiecutter/cookiecutter-django/pull/3492)) +### Updated +- Auto-update pre-commit hooks ([#3493](https://github.com/cookiecutter/cookiecutter-django/pull/3493)) + +## 2021.12.24 + +### Changed +- Simplify `TEMPLATES` settings with `APP_DIRS=True` ([#3488](https://github.com/cookiecutter/cookiecutter-django/pull/3488)) +- Fix docs not building ([#3491](https://github.com/cookiecutter/cookiecutter-django/pull/3491)) +- Remove pylint-django from VITAL_BUT_UNKNOWN ([#3490](https://github.com/cookiecutter/cookiecutter-django/pull/3490)) +- Making docs image 40% smaller and also making python version upgrades easier for multi-stage builds. ([#2836](https://github.com/cookiecutter/cookiecutter-django/pull/2836)) +- Added Django's current language to the lang attribute of the html tag ([#3174](https://github.com/cookiecutter/cookiecutter-django/pull/3174)) +### Updated +- Update uvicorn to 0.16.0 ([#3454](https://github.com/cookiecutter/cookiecutter-django/pull/3454)) + +## 2021.12.22 + +### Changed +- Use built-in pip caching from actions/setup-python in generated project ([#3481](https://github.com/cookiecutter/cookiecutter-django/pull/3481)) +- Speed up CI tests on macOS ([#3480](https://github.com/cookiecutter/cookiecutter-django/pull/3480)) +### Updated +- Update mypy to 0.930 ([#3487](https://github.com/cookiecutter/cookiecutter-django/pull/3487)) +- Update redis to 4.0.2 ([#3486](https://github.com/cookiecutter/cookiecutter-django/pull/3486)) +- Update django-redis to 5.2.0 ([#3485](https://github.com/cookiecutter/cookiecutter-django/pull/3485)) + +## 2021.12.20 + +### Changed +- Add a PyCharm run configuration for docker-compose ([#3462](https://github.com/cookiecutter/cookiecutter-django/pull/3462)) + +## 2021.12.19 + +### Updated +- Update mypy to 0.920 ([#3478](https://github.com/cookiecutter/cookiecutter-django/pull/3478)) +- Update django-compressor to 3.1 ([#3476](https://github.com/cookiecutter/cookiecutter-django/pull/3476)) +- Update sphinx to 4.3.2 ([#3477](https://github.com/cookiecutter/cookiecutter-django/pull/3477)) + +## 2021.12.17 + +### Fixed +- Fix BrowserSync config on non-Docker setup ([#3461](https://github.com/cookiecutter/cookiecutter-django/pull/3461)) + +## 2021.12.16 + +### Fixed +- Fix carriage return in `.gitignore` on Windows ([#3456](https://github.com/cookiecutter/cookiecutter-django/pull/3456)) +### Updated +- Update django-debug-toolbar to 3.2.4 ([#3473](https://github.com/cookiecutter/cookiecutter-django/pull/3473)) + +## 2021.12.15 + +### Updated +- Update djangorestframework to 3.13.1 ([#3472](https://github.com/cookiecutter/cookiecutter-django/pull/3472)) + +## 2021.12.14 + +### Changed +- Update rcssmin & django-compressor ([#3470](https://github.com/cookiecutter/cookiecutter-django/pull/3470)) +### Updated +- Update pytest-django to 4.5.2 ([#3471](https://github.com/cookiecutter/cookiecutter-django/pull/3471)) +- Bump peter-evans/create-pull-request from 3.11.0 to 3.12.0 ([#3469](https://github.com/cookiecutter/cookiecutter-django/pull/3469)) + +## 2021.12.13 + +### Updated +- Update djangorestframework to 3.13.0 ([#3468](https://github.com/cookiecutter/cookiecutter-django/pull/3468)) +- Update sentry-sdk to 1.5.1 ([#3467](https://github.com/cookiecutter/cookiecutter-django/pull/3467)) +- Update django-debug-toolbar to 3.2.3 ([#3466](https://github.com/cookiecutter/cookiecutter-django/pull/3466)) +- Update argon2-cffi to 21.3.0 ([#3464](https://github.com/cookiecutter/cookiecutter-django/pull/3464)) +- Update django-allauth to 0.47.0 ([#3459](https://github.com/cookiecutter/cookiecutter-django/pull/3459)) + +## 2021.12.09 + +### Updated +- Auto-update pre-commit hooks ([#3457](https://github.com/cookiecutter/cookiecutter-django/pull/3457)) + +## 2021.12.08 + +### Changed +- Reword introduction in documentation ([#3452](https://github.com/cookiecutter/cookiecutter-django/pull/3452)) +### Updated +- Update argon2-cffi to 21.2.0 ([#3453](https://github.com/cookiecutter/cookiecutter-django/pull/3453)) + +## 2021.12.07 + +### Changed +- Add docker, pip and npm to GitHub's Dependabot ([#3401](https://github.com/cookiecutter/cookiecutter-django/pull/3401)) +- Configure Dependabot for npm packages at the template level ([#3436](https://github.com/cookiecutter/cookiecutter-django/pull/3436)) +### Updated +- Update django to 3.2.10 ([#3451](https://github.com/cookiecutter/cookiecutter-django/pull/3451)) + +## 2021.12.06 + +### Updated +- Auto-update pre-commit hooks ([#3449](https://github.com/cookiecutter/cookiecutter-django/pull/3449)) +- Update black to 21.12b0 ([#3448](https://github.com/cookiecutter/cookiecutter-django/pull/3448)) +- Update django-cors-headers to 3.10.1 ([#3447](https://github.com/cookiecutter/cookiecutter-django/pull/3447)) + +## 2021.12.04 + +### Changed +- Removed mention of Foundation fork from readme ([#3445](https://github.com/cookiecutter/cookiecutter-django/pull/3445)) +### Updated +- Update pytest-django to 4.5.1 ([#3443](https://github.com/cookiecutter/cookiecutter-django/pull/3443)) + +## 2021.12.01 + +### Updated +- Update pre-commit to 2.16.0 ([#3442](https://github.com/cookiecutter/cookiecutter-django/pull/3442)) + +## 2021.11.30 + +### Updated +- Update django-redis to 5.1.0 ([#3440](https://github.com/cookiecutter/cookiecutter-django/pull/3440)) +- Update django-stubs to 1.9.0 ([#3439](https://github.com/cookiecutter/cookiecutter-django/pull/3439)) + +## 2021.11.29 + +### Fixed +- Fix pre-commit config ([#3435](https://github.com/cookiecutter/cookiecutter-django/pull/3435)) +### Updated +- Update sphinx to 4.3.1 ([#3438](https://github.com/cookiecutter/cookiecutter-django/pull/3438)) + +## 2021.11.27 + +### Updated +- Update coverage to 6.2 ([#3437](https://github.com/cookiecutter/cookiecutter-django/pull/3437)) + +## 2021.11.26 + +### Changed +- Setup pre-commit for the template files ([#3433](https://github.com/cookiecutter/cookiecutter-django/pull/3433)) + +## 2021.11.25 + +### Changed +- Add an assertion to fix mypy type error ([#3150](https://github.com/cookiecutter/cookiecutter-django/pull/3150)) +- Make `django` depend on `redis` in local Docker ([#3265](https://github.com/cookiecutter/cookiecutter-django/pull/3265)) + +## 2021.11.24 + +### Changed +- Cache Python dependencies on our CI ([#3434](https://github.com/cookiecutter/cookiecutter-django/pull/3434)) +- Small formatting fixes to Deploy to PythonAnywhere page ([#3432](https://github.com/cookiecutter/cookiecutter-django/pull/3432)) +### Updated +- Upgrade to Django 3.2 ([#3425](https://github.com/cookiecutter/cookiecutter-django/pull/3425)) + +## 2021.11.22 + +### Changed +- Removed unnecessary custom context processor exposing the DEBUG Template Context Variable ([#3042](https://github.com/cookiecutter/cookiecutter-django/pull/3042)) +- Clean up trailing whitespace ([#3430](https://github.com/cookiecutter/cookiecutter-django/pull/3430)) +### Updated +- Update redis to 4.0.2 ([#3431](https://github.com/cookiecutter/cookiecutter-django/pull/3431)) +- Bump Postgres to 13.5 12.9 11.14 10.19; add 14.1 ([#3428](https://github.com/cookiecutter/cookiecutter-django/pull/3428)) + +## 2021.11.20 + +### Fixed +- Update repos for pre-commit hooks ([#3424](https://github.com/cookiecutter/cookiecutter-django/pull/3424)) +### Updated +- Bump pre-commit/action to 2.0.3 ([#3426](https://github.com/cookiecutter/cookiecutter-django/pull/3426)) + +## 2021.11.19 + +### Updated +- Update celery to 5.2.1 ([#3423](https://github.com/cookiecutter/cookiecutter-django/pull/3423)) +- Auto-update pre-commit hooks ([#3420](https://github.com/cookiecutter/cookiecutter-django/pull/3420)) + +## 2021.11.18 + +### Changed +- Switch template to calendar versioning & automate releases ([#3415](https://github.com/cookiecutter/cookiecutter-django/pull/3415)) +### Updated +- Update black to 21.11b1 ([#3421](https://github.com/cookiecutter/cookiecutter-django/pull/3421)) +- Update redis to 4.0.1 ([#3416](https://github.com/cookiecutter/cookiecutter-django/pull/3416)) + +## [2021-11-17] +### Updated +- Update sentry-sdk to 1.5.0 ([#3417](https://github.com/cookiecutter/cookiecutter-django/pull/3417)) +- Update black to 21.11b0 ([#3414](https://github.com/cookiecutter/cookiecutter-django/pull/3414)) + +## [2021-11-16] +### Changed +- Upgrade JS dependencies and upgrade to node 16 ([#3400](https://github.com/cookiecutter/cookiecutter-django/pull/3400)) +### Fixed +- Fix ungraceful Celery workers shutdown in container ([#3405](https://github.com/cookiecutter/cookiecutter-django/pull/3405)) +### Updated +- Update psycopg2-binary to 2.9.2 ([#3411](https://github.com/cookiecutter/cookiecutter-django/pull/3411)) +- Update psycopg2 to 2.9.2 ([#3410](https://github.com/cookiecutter/cookiecutter-django/pull/3410)) +- Update redis to 4.0.0 ([#3406](https://github.com/cookiecutter/cookiecutter-django/pull/3406)) +- Update django-coverage-plugin to 2.0.2 ([#3409](https://github.com/cookiecutter/cookiecutter-django/pull/3409)) +- Update black to 21.10b0 ([#3408](https://github.com/cookiecutter/cookiecutter-django/pull/3408)) + +## [2021-11-15] +### Updated +- Update django-allauth to 0.46.0 ([#3407](https://github.com/cookiecutter/cookiecutter-django/pull/3407)) + +## [2021-11-13] +### Fixed +- Fix incorrect node version in `package.json` ([#3399](https://github.com/cookiecutter/cookiecutter-django/pull/3399)) + +## [2021-11-12] +### Changed +- Add Django major/minor release table maker in GitHub issues ([#3288](https://github.com/cookiecutter/cookiecutter-django/pull/3288)) +- Upgrade to Bootstrap 5 ([#3276](https://github.com/cookiecutter/cookiecutter-django/pull/3276)) +### Updated +- Update requests to 2.26.0 ([#3397](https://github.com/cookiecutter/cookiecutter-django/pull/3397)) +- Update crispy-bootstrap5 to 0.6 ([#3396](https://github.com/cookiecutter/cookiecutter-django/pull/3396)) + +## [2021-11-11] +### Changed +- Build all images on CI ([#3394](https://github.com/cookiecutter/cookiecutter-django/pull/3394)) +### Updated +- Update coverage to 6.1.2 ([#3393](https://github.com/cookiecutter/cookiecutter-django/pull/3393)) + +## [2021-11-10] +### Changed +- Update sphinx to 4.3.0 ([#3392](https://github.com/cookiecutter/cookiecutter-django/pull/3392)) +### Updated +- Auto-update pre-commit hooks ([#3389](https://github.com/cookiecutter/cookiecutter-django/pull/3389)) +- Update jinja2 to 3.0.3 ([#3388](https://github.com/cookiecutter/cookiecutter-django/pull/3388)) + +## [2021-11-09] +### Changed +- refactor: remove user API methods parameter ([#3385](https://github.com/cookiecutter/cookiecutter-django/pull/3385)) +- Get GitHub repo from environment ([#3387](https://github.com/cookiecutter/cookiecutter-django/pull/3387)) +### Updated +- Update celery to 5.2.0 ([#3384](https://github.com/cookiecutter/cookiecutter-django/pull/3384)) +- Update isort to 5.10.1 ([#3386](https://github.com/cookiecutter/cookiecutter-django/pull/3386)) + +## [2021-11-08] +### Changed +- Update docker and non-docker configs to Debian 11 (bullseye) ([#3372](https://github.com/cookiecutter/cookiecutter-django/pull/3372)) + +## [2021-11-07] +### Updated +- Update django-extensions to 3.1.5 ([#3383](https://github.com/cookiecutter/cookiecutter-django/pull/3383)) + +## [2021-11-04] +### Changed +- change path in docs Makefile to use APP variable ([#3379](https://github.com/cookiecutter/cookiecutter-django/pull/3379)) +### Fixed +- fix help in docs Makefile ([#3380](https://github.com/cookiecutter/cookiecutter-django/pull/3380)) +### Updated +- Bump peter-evans/create-pull-request from 3.10.1 to 3.11.0 ([#3382](https://github.com/cookiecutter/cookiecutter-django/pull/3382)) +- Auto-update pre-commit hooks ([#3381](https://github.com/cookiecutter/cookiecutter-django/pull/3381)) +- Update isort to 5.10.0 ([#3378](https://github.com/cookiecutter/cookiecutter-django/pull/3378)) + +## [2021-11-02] +### Updated +- Auto-update pre-commit hooks ([#3377](https://github.com/cookiecutter/cookiecutter-django/pull/3377)) + +## [2021-11-01] +### Updated +- Update django-storages to 1.12.3 ([#3374](https://github.com/cookiecutter/cookiecutter-django/pull/3374)) +- Update coverage to 6.1.1 ([#3376](https://github.com/cookiecutter/cookiecutter-django/pull/3376)) + +## [2021-10-28] +### Updated +- Update factory-boy to 3.2.1 ([#3373](https://github.com/cookiecutter/cookiecutter-django/pull/3373)) + +## [2021-10-26] +### Changed +- use Wayback Machine to fix dead link for postgres user setup ([#3363](https://github.com/cookiecutter/cookiecutter-django/pull/3363)) +- Fix pull request links to correct repo URL on CHANGELOG.md ([#3370](https://github.com/cookiecutter/cookiecutter-django/pull/3370)) +### Updated +- Update pyyaml to 6.0 ([#3362](https://github.com/cookiecutter/cookiecutter-django/pull/3362)) +- Update pillow to 8.4.0 ([#3364](https://github.com/cookiecutter/cookiecutter-django/pull/3364)) +- Update django-storages to 1.12.2 ([#3365](https://github.com/cookiecutter/cookiecutter-django/pull/3365)) +- Update django-environ to 0.8.1 ([#3368](https://github.com/cookiecutter/cookiecutter-django/pull/3368)) + +## [2021-10-22] +### Changed +- Move repo under cookiecutter organisation ([#3357](https://github.com/cookiecutter/cookiecutter-django/pull/3357)) + +## [2021-10-18] +### Updated +- Update django-environ to 0.8.0 ([#3367](https://github.com/cookiecutter/cookiecutter-django/pull/3367)) + +## [2021-10-14] +### Updated +- Update flake8 to 4.0.1 ([#3361](https://github.com/cookiecutter/cookiecutter-django/pull/3361)) +- Update flake8-isort to 4.1.1 ([#3360](https://github.com/cookiecutter/cookiecutter-django/pull/3360)) +- Update django-model-utils to 4.2.0 ([#3359](https://github.com/cookiecutter/cookiecutter-django/pull/3359)) + +## [2021-10-13] +### Changed +- Add drf stubs ([#3353](https://github.com/cookiecutter/cookiecutter-django/pull/3353)) + +## [2021-10-12] +### Updated +- Update coverage to 6.0.2 ([#3356](https://github.com/cookiecutter/cookiecutter-django/pull/3356)) + +## [2021-10-11] +### Updated +- Update werkzeug to 2.0.2 ([#3344](https://github.com/cookiecutter/cookiecutter-django/pull/3344)) +- Update coverage to 6.0.1 ([#3348](https://github.com/cookiecutter/cookiecutter-django/pull/3348)) +- Update django-coverage-plugin to 2.0.1 ([#3349](https://github.com/cookiecutter/cookiecutter-django/pull/3349)) +- Update django-cors-headers to 3.10.0 ([#3345](https://github.com/cookiecutter/cookiecutter-django/pull/3345)) +- Update jinja2 to 3.0.2 ([#3343](https://github.com/cookiecutter/cookiecutter-django/pull/3343)) +- Update django-storages to 1.12.1 ([#3355](https://github.com/cookiecutter/cookiecutter-django/pull/3355)) + +## [2021-10-03] +### Updated +- Update pytz to 2021.3 ([#3340](https://github.com/cookiecutter/cookiecutter-django/pull/3340)) + +## [2021-10-01] +### Changed +- Fix the wrong pre-commit hyperlink in Prerequisites section ([#3338](https://github.com/cookiecutter/cookiecutter-django/pull/3338)) + +## [2021-09-30] +### Updated +- Update sentry-sdk to 1.4.3 ([#3334](https://github.com/cookiecutter/cookiecutter-django/pull/3334)) + +## [2021-09-29] +### Updated +- Update django-cors-headers to 3.9.0 ([#3332](https://github.com/cookiecutter/cookiecutter-django/pull/3332)) + +## [2021-09-27] +### Updated +- Update sentry-sdk to 1.4.2 ([#3329](https://github.com/cookiecutter/cookiecutter-django/pull/3329)) + +## [2021-09-26] +### Updated +- Update django-crispy-forms to 1.13.0 ([#3327](https://github.com/cookiecutter/cookiecutter-django/pull/3327)) + +## [2021-09-24] +### Changed +- Add django-settings-module to .pylintrc ([#3326](https://github.com/cookiecutter/cookiecutter-django/pull/3326)) + +## [2021-09-23] +### Updated +- Update sentry-sdk to 1.4.1 ([#3325](https://github.com/cookiecutter/cookiecutter-django/pull/3325)) + +## [2021-09-22] +### Updated +- Update sentry-sdk to 1.4.0 ([#3324](https://github.com/cookiecutter/cookiecutter-django/pull/3324)) + +## [2021-09-16] +### Updated +- Update tox to 3.24.4 ([#3323](https://github.com/cookiecutter/cookiecutter-django/pull/3323)) + +## [2021-09-15] +### Updated +- Auto-update pre-commit hooks ([#3322](https://github.com/cookiecutter/cookiecutter-django/pull/3322)) + +## [2021-09-14] +### Updated +- Update black to 21.9b0 ([#3321](https://github.com/cookiecutter/cookiecutter-django/pull/3321)) + +## [2021-09-13] +### Updated +- Bump stefanzweifel/git-auto-commit-action from 4.11.0 to 4.12.0 ([#3320](https://github.com/cookiecutter/cookiecutter-django/pull/3320)) +- Update sphinx to 4.2.0 ([#3319](https://github.com/cookiecutter/cookiecutter-django/pull/3319)) + +## [2021-09-11] +### Changed +- Removing pycharm docs if app does not use pycharm ([#3139](https://github.com/cookiecutter/cookiecutter-django/pull/3139)) +### Updated +- Update django-environ to 0.7.0 ([#3317](https://github.com/cookiecutter/cookiecutter-django/pull/3317)) + +## [2021-09-06] +### Changed +- Update Celery to v5 ([#3280](https://github.com/cookiecutter/cookiecutter-django/pull/3280)) + +## [2021-09-05] +### Updated +- Update django-environ to 0.6.0 ([#3314](https://github.com/cookiecutter/cookiecutter-django/pull/3314)) + +## [2021-09-03] +### Changed +- Update available postgres versions ([#3297](https://github.com/cookiecutter/cookiecutter-django/pull/3297)) +### Updated +- Update pre-commit to 2.15.0 ([#3313](https://github.com/cookiecutter/cookiecutter-django/pull/3313)) +- Auto-update pre-commit hooks ([#3307](https://github.com/cookiecutter/cookiecutter-django/pull/3307)) +- Update pillow to 8.3.2 ([#3312](https://github.com/cookiecutter/cookiecutter-django/pull/3312)) +- Update django-environ to 0.5.0 ([#3311](https://github.com/cookiecutter/cookiecutter-django/pull/3311)) +- Update pytest to 6.2.5 ([#3310](https://github.com/cookiecutter/cookiecutter-django/pull/3310)) +- Update black to 21.8b0 ([#3308](https://github.com/cookiecutter/cookiecutter-django/pull/3308)) +- Update argon2-cffi to 21.1.0 ([#3306](https://github.com/cookiecutter/cookiecutter-django/pull/3306)) +- Bump peter-evans/create-pull-request from 3.10.0 to 3.10.1 ([#3303](https://github.com/cookiecutter/cookiecutter-django/pull/3303)) +- Update django-debug-toolbar to 3.2.2 ([#3296](https://github.com/cookiecutter/cookiecutter-django/pull/3296)) +- Update django-cors-headers to 3.8.0 ([#3295](https://github.com/cookiecutter/cookiecutter-django/pull/3295)) +- Update uvicorn to 0.15.0 ([#3294](https://github.com/cookiecutter/cookiecutter-django/pull/3294)) + +## [2021-08-27] +### Updated +- Update tox to 3.24.3 ([#3302](https://github.com/cookiecutter/cookiecutter-django/pull/3302)) + +## [2021-08-20] +### Changed +- Fix Jinja2 break line control on Procfile ([#3300](https://github.com/cookiecutter/cookiecutter-django/pull/3300)) + +## [2021-08-19] +### Changed +- Fix several minor typos ([#3301](https://github.com/cookiecutter/cookiecutter-django/pull/3301)) + +## [2021-08-13] +### Changed +- Upgrade to Redis 6 ([#3255](https://github.com/cookiecutter/cookiecutter-django/pull/3255)) +### Fixed +- Fix RTD build image to support Python 3.9 ([#3293](https://github.com/cookiecutter/cookiecutter-django/pull/3293)) + +## [2021-08-12] +### Changed +- Add documentation for automating backups ([#3268](https://github.com/cookiecutter/cookiecutter-django/pull/3268)) +- Add missing step to getting started locally in docs ([#3291](https://github.com/cookiecutter/cookiecutter-django/pull/3291)) +- Moved isort config from `.editorconfig` to `setup.cfg` ([#3290](https://github.com/cookiecutter/cookiecutter-django/pull/3290)) +- How to pre-commit in Docker Development ([#3287](https://github.com/cookiecutter/cookiecutter-django/pull/3287)) +### Updated +- Update sentry-sdk to 1.3.1 ([#3281](https://github.com/cookiecutter/cookiecutter-django/pull/3281)) +- Update tox to 3.24.1 ([#3285](https://github.com/cookiecutter/cookiecutter-django/pull/3285)) +- Update pre-commit to 2.14.0 ([#3289](https://github.com/cookiecutter/cookiecutter-django/pull/3289)) + +## [2021-07-30] +### Updated +- Auto-update pre-commit hooks ([#3283](https://github.com/cookiecutter/cookiecutter-django/pull/3283)) +- Update isort to 5.9.3 ([#3282](https://github.com/cookiecutter/cookiecutter-django/pull/3282)) + +## [2021-07-27] +### Changed +- Convert trans to translate in templates ([#3277](https://github.com/cookiecutter/cookiecutter-django/pull/3277)) +### Updated +- Update hiredis to 2.0.0 ([#3110](https://github.com/cookiecutter/cookiecutter-django/pull/3110)) +- Update mypy to 0.910 ([#3237](https://github.com/cookiecutter/cookiecutter-django/pull/3237)) +- Update whitenoise to 5.3.0 ([#3273](https://github.com/cookiecutter/cookiecutter-django/pull/3273)) +- Update tox to 3.24.0 ([#3269](https://github.com/cookiecutter/cookiecutter-django/pull/3269)) +- Update django-allauth to 0.45.0 ([#3267](https://github.com/cookiecutter/cookiecutter-django/pull/3267)) +- Update sentry-sdk to 1.3.0 ([#3262](https://github.com/cookiecutter/cookiecutter-django/pull/3262)) +- Update sphinx to 4.1.2 ([#3278](https://github.com/cookiecutter/cookiecutter-django/pull/3278)) +- Auto-update pre-commit hooks ([#3264](https://github.com/cookiecutter/cookiecutter-django/pull/3264)) +- Update isort to 5.9.2 ([#3279](https://github.com/cookiecutter/cookiecutter-django/pull/3279)) +- Update pillow to 8.3.1 ([#3259](https://github.com/cookiecutter/cookiecutter-django/pull/3259)) +- Update black to 21.7b0 ([#3272](https://github.com/cookiecutter/cookiecutter-django/pull/3272)) + +## [2021-07-12] +### Changed +- Define REMAP_SIGTERM=SIGQUIT on Profile of Celery on Heroku ([#3263](https://github.com/cookiecutter/cookiecutter-django/pull/3263)) + +## [2021-07-08] +### Updated +- Update django to 3.1.13 ([#3247](https://github.com/cookiecutter/cookiecutter-django/pull/3247)) + +## [2021-06-29] +### Changed +- Improve github bug report template ([#3243](https://github.com/cookiecutter/cookiecutter-django/pull/3243)) + +## [2021-06-28] +### Changed +- Revert "Fix Celery ports error on local Docker" ([#3242](https://github.com/cookiecutter/cookiecutter-django/pull/3242)) +### Fixed +- Fix Celery ports error on local Docker ([#3241](https://github.com/cookiecutter/cookiecutter-django/pull/3241)) + +## [2021-06-25] +### Changed +- Update `.gitignore` file for VSCode ([#3238](https://github.com/cookiecutter/cookiecutter-django/pull/3238)) +### Fixed +- Wrap jQuery call in `DOMContentLoaded` event listener on account email page ([#3239](https://github.com/cookiecutter/cookiecutter-django/pull/3239)) + +## [2021-06-22] +### Changed +- Update docs/howto.rst ([#3230](https://github.com/cookiecutter/cookiecutter-django/pull/3230)) +- Add support for PG 13. Drop PG 9. Update all minor versions ([#3154](https://github.com/cookiecutter/cookiecutter-django/pull/3154)) +### Updated +- Update isort to 5.9.1 ([#3236](https://github.com/cookiecutter/cookiecutter-django/pull/3236)) +- Auto-update pre-commit hooks ([#3235](https://github.com/cookiecutter/cookiecutter-django/pull/3235)) + +## [2021-06-21] +### Updated +- Update isort to 5.9.0 ([#3234](https://github.com/cookiecutter/cookiecutter-django/pull/3234)) +- Update django-anymail to 8.4 ([#3225](https://github.com/cookiecutter/cookiecutter-django/pull/3225)) +- Update django-redis to 5.0.0 ([#3205](https://github.com/cookiecutter/cookiecutter-django/pull/3205)) +- Update pylint-django to 2.4.4 ([#3233](https://github.com/cookiecutter/cookiecutter-django/pull/3233)) +- Auto-update pre-commit hooks ([#3220](https://github.com/cookiecutter/cookiecutter-django/pull/3220)) +- Bump peter-evans/create-pull-request from 3.9.2 to 3.10.0 ([#3197](https://github.com/cookiecutter/cookiecutter-django/pull/3197)) +- Update black to 21.6b0 ([#3232](https://github.com/cookiecutter/cookiecutter-django/pull/3232)) +- Update pytest to 6.2.4 ([#3231](https://github.com/cookiecutter/cookiecutter-django/pull/3231)) +- Update django-crispy-forms to 1.12.0 ([#3221](https://github.com/cookiecutter/cookiecutter-django/pull/3221)) +- Update mypy to 0.902 ([#3219](https://github.com/cookiecutter/cookiecutter-django/pull/3219)) +- Update django-coverage-plugin to 2.0.0 ([#3217](https://github.com/cookiecutter/cookiecutter-django/pull/3217)) +- Update ipdb to 0.13.9 ([#3210](https://github.com/cookiecutter/cookiecutter-django/pull/3210)) +- Update uvicorn to 0.14.0 ([#3207](https://github.com/cookiecutter/cookiecutter-django/pull/3207)) +- Update pytest-cookies to 0.6.1 ([#3196](https://github.com/cookiecutter/cookiecutter-django/pull/3196)) +- Update sphinx to 4.0.2 ([#3193](https://github.com/cookiecutter/cookiecutter-django/pull/3193)) +- Update jinja2 to 3.0.1 ([#3189](https://github.com/cookiecutter/cookiecutter-django/pull/3189)) + +## [2021-06-19] +### Updated +- Update psycopg2 to 2.9.1 ([#3227](https://github.com/cookiecutter/cookiecutter-django/pull/3227)) +- Update psycopg2-binary to 2.9.1 ([#3228](https://github.com/cookiecutter/cookiecutter-django/pull/3228)) + +## [2021-06-14] +### Changed +- Update black GitHub link in requirements ([#3222](https://github.com/cookiecutter/cookiecutter-django/pull/3222)) + +## [2021-06-09] +### Changed +- Fix link format in developing-locally.rst ([#3214](https://github.com/cookiecutter/cookiecutter-django/pull/3214)) +### Updated +- Update pre-commit to 2.13.0 ([#3195](https://github.com/cookiecutter/cookiecutter-django/pull/3195)) +- Update pytest-django to 4.4.0 ([#3212](https://github.com/cookiecutter/cookiecutter-django/pull/3212)) +- Update mypy to 0.901 ([#3215](https://github.com/cookiecutter/cookiecutter-django/pull/3215)) +- Auto-update pre-commit hooks ([#3206](https://github.com/cookiecutter/cookiecutter-django/pull/3206)) +- Update black to 21.5b2 ([#3204](https://github.com/cookiecutter/cookiecutter-django/pull/3204)) + +## [2021-06-06] +### Changed +- Updated .pre-commit-config.yaml to self-update its dependencies ([#3208](https://github.com/cookiecutter/cookiecutter-django/pull/3208)) + +## [2021-06-05] +### Changed +- Shorthand for the officially supported buildpack ([#3211](https://github.com/cookiecutter/cookiecutter-django/pull/3211)) + +## [2021-06-02] +### Updated +- Update django to 3.1.12 ([#3209](https://github.com/cookiecutter/cookiecutter-django/pull/3209)) + +## [2021-05-18] +### Changed +- Move ARG PYTHON_VERSION=3.9-slim-buster to the global scope ([#3188](https://github.com/cookiecutter/cookiecutter-django/pull/3188)) + +## [2021-05-17] +### Updated +- Bump tiangolo/issue-manager from 0.3.0 to 0.4.0 ([#3186](https://github.com/cookiecutter/cookiecutter-django/pull/3186)) +- Auto-update pre-commit hooks ([#3185](https://github.com/cookiecutter/cookiecutter-django/pull/3185)) + +## [2021-05-15] +### Changed +- Update watchgod to 0.7 ([#3177](https://github.com/cookiecutter/cookiecutter-django/pull/3177)) +### Updated +- Auto-update pre-commit hooks ([#3184](https://github.com/cookiecutter/cookiecutter-django/pull/3184)) +- Update black to 21.5b1 ([#3167](https://github.com/cookiecutter/cookiecutter-django/pull/3167)) +- Update flake8 to 3.9.2 ([#3164](https://github.com/cookiecutter/cookiecutter-django/pull/3164)) +- Update pytest-django to 4.3.0 ([#3182](https://github.com/cookiecutter/cookiecutter-django/pull/3182)) +- Auto-update pre-commit hooks ([#3157](https://github.com/cookiecutter/cookiecutter-django/pull/3157)) +- Update python-slugify to 5.0.2 ([#3161](https://github.com/cookiecutter/cookiecutter-django/pull/3161)) +- Bump stefanzweifel/git-auto-commit-action from 4.10.0 to 4.11.0 ([#3171](https://github.com/cookiecutter/cookiecutter-django/pull/3171)) +- Update sentry-sdk to 1.1.0 ([#3163](https://github.com/cookiecutter/cookiecutter-django/pull/3163)) +- Bump actions/setup-python from 2 to 2.2.2 ([#3173](https://github.com/cookiecutter/cookiecutter-django/pull/3173)) +- Update tox to 3.23.1 ([#3160](https://github.com/cookiecutter/cookiecutter-django/pull/3160)) +- Update pytest to 6.2.4 ([#3156](https://github.com/cookiecutter/cookiecutter-django/pull/3156)) +- Bump peter-evans/create-pull-request from 3.8.2 to 3.9.2 ([#3179](https://github.com/cookiecutter/cookiecutter-django/pull/3179)) +- Update sphinx to 4.0.1 ([#3169](https://github.com/cookiecutter/cookiecutter-django/pull/3169)) +- Update cookiecutter to 1.7.3 ([#3180](https://github.com/cookiecutter/cookiecutter-django/pull/3180)) +- Update django to 3.1.11 ([#3178](https://github.com/cookiecutter/cookiecutter-django/pull/3178)) + +## [2021-05-06] +### Updated +- Update django to 3.1.10 ([#3162](https://github.com/cookiecutter/cookiecutter-django/pull/3162)) + +## [2021-05-04] +### Updated +- Update django to 3.1.9 ([#3155](https://github.com/cookiecutter/cookiecutter-django/pull/3155)) + +## [2021-04-30] +### Fixed +- Fix linting error in production.py ([#3148](https://github.com/cookiecutter/cookiecutter-django/pull/3148)) + +## [2021-04-29] +### Updated +- Update black to 21.4b2 ([#3147](https://github.com/cookiecutter/cookiecutter-django/pull/3147)) +- Auto-update pre-commit hooks ([#3146](https://github.com/cookiecutter/cookiecutter-django/pull/3146)) + +## [2021-04-28] +### Changed +- Fix README link ([#3144](https://github.com/cookiecutter/cookiecutter-django/pull/3144)) +### Updated +- Auto-update pre-commit hooks ([#3145](https://github.com/cookiecutter/cookiecutter-django/pull/3145)) + +## [2021-04-27] +### Updated +- Update pygithub to 1.55 ([#3141](https://github.com/cookiecutter/cookiecutter-django/pull/3141)) +- Update black to 21.4b1 ([#3143](https://github.com/cookiecutter/cookiecutter-django/pull/3143)) + +## [2021-04-26] +### Updated +- Update black to 21.4b0 ([#3138](https://github.com/cookiecutter/cookiecutter-django/pull/3138)) +- Auto-update pre-commit hooks ([#3137](https://github.com/cookiecutter/cookiecutter-django/pull/3137)) + +## [2021-04-21] +### Updated +- Auto-update pre-commit hooks ([#3133](https://github.com/cookiecutter/cookiecutter-django/pull/3133)) +- Update django-extensions to 3.1.3 ([#3136](https://github.com/cookiecutter/cookiecutter-django/pull/3136)) +- Update django-compressor to 2.4.1 ([#3135](https://github.com/cookiecutter/cookiecutter-django/pull/3135)) +- Update pre-commit to 2.12.1 ([#3134](https://github.com/cookiecutter/cookiecutter-django/pull/3134)) +- Update flake8 to 3.9.1 ([#3131](https://github.com/cookiecutter/cookiecutter-django/pull/3131)) +- Update django-stubs to 1.8.0 ([#3127](https://github.com/cookiecutter/cookiecutter-django/pull/3127)) +- Update sphinx to 3.5.4 ([#3126](https://github.com/cookiecutter/cookiecutter-django/pull/3126)) + +## [2021-04-15] +### Updated +- Update django-debug-toolbar to 3.2.1 ([#3129](https://github.com/cookiecutter/cookiecutter-django/pull/3129)) + +## [2021-04-14] +### Updated +- Bump stefanzweifel/git-auto-commit-action from v4.9.2 to v4.10.0 ([#3128](https://github.com/cookiecutter/cookiecutter-django/pull/3128)) + +## [2021-04-11] +### Updated +- Update pytest-django to 4.2.0 ([#3125](https://github.com/cookiecutter/cookiecutter-django/pull/3125)) +- Update pylint-django to 2.4.3 ([#3122](https://github.com/cookiecutter/cookiecutter-django/pull/3122)) + +## [2021-04-09] +### Changed +- Update from Python 3.8 to Python 3.9 ([#3023](https://github.com/cookiecutter/cookiecutter-django/pull/3023)) + +## [2021-04-08] +### Changed +- Switch .dockerignore to explicit list ([#3121](https://github.com/cookiecutter/cookiecutter-django/pull/3121)) +- Change Docker image to multi-stage build for Django ([#2815](https://github.com/cookiecutter/cookiecutter-django/pull/2815)) +- Fix deprecated warning in middleware tests ([#3038](https://github.com/cookiecutter/cookiecutter-django/pull/3038)) +### Updated +- Update pre-commit to 2.12.0 ([#3120](https://github.com/cookiecutter/cookiecutter-django/pull/3120)) + +## [2021-04-07] +### Changed +- Update django to 3.1.8 ([#3117](https://github.com/cookiecutter/cookiecutter-django/pull/3117)) +### Fixed +- Fix linting via pre-commit on Github CI ([#3077](https://github.com/cookiecutter/cookiecutter-django/pull/3077)) +- Fix gitlab-ci using duplicate key name for image ([#3112](https://github.com/cookiecutter/cookiecutter-django/pull/3112)) +### Updated +- Update sentry-sdk to 1.0.0 ([#3080](https://github.com/cookiecutter/cookiecutter-django/pull/3080)) +- Update gunicorn to 20.1.0 ([#3108](https://github.com/cookiecutter/cookiecutter-django/pull/3108)) +- Update pre-commit to 2.12.0 ([#3118](https://github.com/cookiecutter/cookiecutter-django/pull/3118)) +- Update django-extensions to 3.1.2 ([#3116](https://github.com/cookiecutter/cookiecutter-django/pull/3116)) +- Update pillow to 8.2.0 ([#3113](https://github.com/cookiecutter/cookiecutter-django/pull/3113)) +- Update pytest to 6.2.3 ([#3115](https://github.com/cookiecutter/cookiecutter-django/pull/3115)) + +## [2021-03-26] +### Updated +- Update djangorestframework to 3.12.4 ([#3107](https://github.com/cookiecutter/cookiecutter-django/pull/3107)) + +## [2021-03-25] +### Updated +- Update djangorestframework to 3.12.3 ([#3105](https://github.com/cookiecutter/cookiecutter-django/pull/3105)) + +## [2021-03-22] +### Updated +- Update django-crispy-forms to 1.11.2 ([#3104](https://github.com/cookiecutter/cookiecutter-django/pull/3104)) +- Update sphinx to 3.5.3 ([#3103](https://github.com/cookiecutter/cookiecutter-django/pull/3103)) +- Update ipdb to 0.13.7 ([#3102](https://github.com/cookiecutter/cookiecutter-django/pull/3102)) +- Update sphinx-autobuild to 2021.3.14 ([#3101](https://github.com/cookiecutter/cookiecutter-django/pull/3101)) +- Update isort to 5.8.0 ([#3100](https://github.com/cookiecutter/cookiecutter-django/pull/3100)) +- Update pre-commit to 2.11.1 ([#3089](https://github.com/cookiecutter/cookiecutter-django/pull/3089)) +- Update flake8 to 3.9.0 ([#3096](https://github.com/cookiecutter/cookiecutter-django/pull/3096)) +- Update pillow to 8.1.2 ([#3084](https://github.com/cookiecutter/cookiecutter-django/pull/3084)) +- Auto-update pre-commit hooks ([#3095](https://github.com/cookiecutter/cookiecutter-django/pull/3095)) + +## [2021-03-05] +### Changed +- Updated test_urls.py and views.py to re-use User.get_absolute_url() ([#3070](https://github.com/cookiecutter/cookiecutter-django/pull/3070)) +### Updated +- Bump stefanzweifel/git-auto-commit-action from v4.9.1 to v4.9.2 ([#3082](https://github.com/cookiecutter/cookiecutter-django/pull/3082)) + +## [2021-03-03] +### Updated +- Update tox to 3.23.0 ([#3079](https://github.com/cookiecutter/cookiecutter-django/pull/3079)) +- Update ipdb to 0.13.5 ([#3078](https://github.com/cookiecutter/cookiecutter-django/pull/3078)) + +## [2021-03-02] +### Fixed +- Fixes for pytest job in Github CI workflow ([#3076](https://github.com/cookiecutter/cookiecutter-django/pull/3076)) +### Updated +- Update pillow to 8.1.1 ([#3075](https://github.com/cookiecutter/cookiecutter-django/pull/3075)) +- Update coverage to 5.5 ([#3074](https://github.com/cookiecutter/cookiecutter-django/pull/3074)) + +## [2021-02-24] +### Updated +- Bump stefanzweifel/git-auto-commit-action from v4.9.0 to v4.9.1 ([#3069](https://github.com/cookiecutter/cookiecutter-django/pull/3069)) + +## [2021-02-23] +### Changed +- Update to Django 3.1 ([#3043](https://github.com/cookiecutter/cookiecutter-django/pull/3043)) +- Lint with pre-commit on CI with Github actions ([#3066](https://github.com/cookiecutter/cookiecutter-django/pull/3066)) +- Use exception var in status code pages if available ([#2992](https://github.com/cookiecutter/cookiecutter-django/pull/2992)) + +## [2021-02-22] +### Changed +- refactor: remove default cache settings in test.py ([#3064](https://github.com/cookiecutter/cookiecutter-django/pull/3064)) +- Update django to 3.0.13 ([#3060](https://github.com/cookiecutter/cookiecutter-django/pull/3060)) +### Fixed +- Fix missing Django Debug toolbar with node container ([#2865](https://github.com/cookiecutter/cookiecutter-django/pull/2865)) +- Remove Email from User API ([#3055](https://github.com/cookiecutter/cookiecutter-django/pull/3055)) +### Updated +- Bump stefanzweifel/git-auto-commit-action from v4.8.0 to v4.9.0 ([#3065](https://github.com/cookiecutter/cookiecutter-django/pull/3065)) +- Update django-crispy-forms to 1.11.1 ([#3063](https://github.com/cookiecutter/cookiecutter-django/pull/3063)) +- Update uvicorn to 0.13.4 ([#3062](https://github.com/cookiecutter/cookiecutter-django/pull/3062)) +- Update mypy to 0.812 ([#3061](https://github.com/cookiecutter/cookiecutter-django/pull/3061)) +- Update sentry-sdk to 0.20.3 ([#3059](https://github.com/cookiecutter/cookiecutter-django/pull/3059)) +- Update tox to 3.22.0 ([#3057](https://github.com/cookiecutter/cookiecutter-django/pull/3057)) +- Update sphinx to 3.5.1 ([#3056](https://github.com/cookiecutter/cookiecutter-django/pull/3056)) + +## [2021-02-16] +### Updated +- Update sentry-sdk to 0.20.2 ([#3054](https://github.com/cookiecutter/cookiecutter-django/pull/3054)) +- Update sphinx to 3.5.0 ([#3053](https://github.com/cookiecutter/cookiecutter-django/pull/3053)) + +## [2021-02-13] +### Updated +- Update sentry-sdk to 0.20.1 ([#3052](https://github.com/cookiecutter/cookiecutter-django/pull/3052)) + +## [2021-02-12] +### Updated +- Update pre-commit to 2.10.1 ([#3045](https://github.com/cookiecutter/cookiecutter-django/pull/3045)) +- Update sentry-sdk to 0.20.0 ([#3051](https://github.com/cookiecutter/cookiecutter-django/pull/3051)) + +## [2021-02-10] +### Updated +- Bump peter-evans/create-pull-request from v3.8.1 to v3.8.2 ([#3049](https://github.com/cookiecutter/cookiecutter-django/pull/3049)) + +## [2021-02-08] +### Updated +- Update django-extensions to 3.1.1 ([#3047](https://github.com/cookiecutter/cookiecutter-django/pull/3047)) +- Bump peter-evans/create-pull-request from v3.8.0 to v3.8.1 ([#3046](https://github.com/cookiecutter/cookiecutter-django/pull/3046)) + +## [2021-02-06] +### Changed +- Removed Redundant test_case_sensitivity() and made test_not_authenticated() get the LOGIN_URL dynamically. ([#3041](https://github.com/cookiecutter/cookiecutter-django/pull/3041)) +- Refactored users.forms to make the code more readeable ([#3029](https://github.com/cookiecutter/cookiecutter-django/pull/3029)) +- Update django to 3.0.12 ([#3037](https://github.com/cookiecutter/cookiecutter-django/pull/3037)) +### Updated +- Update tox to 3.21.4 ([#3044](https://github.com/cookiecutter/cookiecutter-django/pull/3044)) + +## [2021-02-01] +### Updated +- Update pytz to 2021.1 ([#3035](https://github.com/cookiecutter/cookiecutter-django/pull/3035)) +- Update jinja2 to 2.11.3 ([#3033](https://github.com/cookiecutter/cookiecutter-django/pull/3033)) +- Bump peter-evans/create-pull-request from v3.7.0 to v3.8.0 ([#3034](https://github.com/cookiecutter/cookiecutter-django/pull/3034)) + +## [2021-01-31] +### Changed +- Adding local celery instructions to developing-locally ([#3031](https://github.com/cookiecutter/cookiecutter-django/pull/3031)) +### Updated +- Update django-crispy-forms to 1.11.0 ([#3032](https://github.com/cookiecutter/cookiecutter-django/pull/3032)) + +## [2021-01-28] +### Updated +- Update pre-commit to 2.10.0 ([#3028](https://github.com/cookiecutter/cookiecutter-django/pull/3028)) +- Update django-anymail to 8.2 ([#3027](https://github.com/cookiecutter/cookiecutter-django/pull/3027)) +- Update tox to 3.21.3 ([#3026](https://github.com/cookiecutter/cookiecutter-django/pull/3026)) + +## [2021-01-26] +### Changed +- Bump peter-evans/create-pull-request from v3.6.0 to v3.7.0 ([#3022](https://github.com/cookiecutter/cookiecutter-django/pull/3022)) +- Using SuccessMessageMixin to send success message to django template ([#3021](https://github.com/cookiecutter/cookiecutter-django/pull/3021)) +### Fixed +- Update admin to ignore *_name User attributes ([#3018](https://github.com/cookiecutter/cookiecutter-django/pull/3018)) +### Updated +- Update coverage to 5.4 ([#3024](https://github.com/cookiecutter/cookiecutter-django/pull/3024)) +- Update pytest to 6.2.2 ([#3020](https://github.com/cookiecutter/cookiecutter-django/pull/3020)) +- Update django-cors-headers to 3.7.0 ([#3019](https://github.com/cookiecutter/cookiecutter-django/pull/3019)) + +## [2021-01-24] +### Changed +- Use defer for script tags (Fix #2922) ([#2927](https://github.com/cookiecutter/cookiecutter-django/pull/2927)) +- Made Traefik conf much easier to understand and improved redirect res… ([#2838](https://github.com/cookiecutter/cookiecutter-django/pull/2838)) +- Sentry Redis integration enabled by default in production. ([#2989](https://github.com/cookiecutter/cookiecutter-django/pull/2989)) +- Add test for UserUpdateView.form_valid() ([#2949](https://github.com/cookiecutter/cookiecutter-django/pull/2949)) +### Fixed +- Omit first_name and last_name in User model ([#2998](https://github.com/cookiecutter/cookiecutter-django/pull/2998)) +### Updated +- Update django-celery-beat to 2.2.0 ([#3009](https://github.com/cookiecutter/cookiecutter-django/pull/3009)) +- Update pyyaml to 5.4.1 ([#3011](https://github.com/cookiecutter/cookiecutter-django/pull/3011)) +- Update mypy to 0.800 ([#3013](https://github.com/cookiecutter/cookiecutter-django/pull/3013)) +- Update factory-boy to 3.2.0 ([#2986](https://github.com/cookiecutter/cookiecutter-django/pull/2986)) +- Update tox to 3.21.2 ([#3010](https://github.com/cookiecutter/cookiecutter-django/pull/3010)) + +## [2021-01-22] +### Changed +- Use self.request.user instead of second query ([#3012](https://github.com/cookiecutter/cookiecutter-django/pull/3012)) + +## [2021-01-14] +### Updated +- Update tox to 3.21.1 ([#3006](https://github.com/cookiecutter/cookiecutter-django/pull/3006)) + +## [2021-01-10] +### Updated +- Update pylint-django to 2.4.2 ([#3003](https://github.com/cookiecutter/cookiecutter-django/pull/3003)) +- Update tox to 3.21.0 ([#3002](https://github.com/cookiecutter/cookiecutter-django/pull/3002)) + +## [2021-01-08] +### Changed +- Upgrade Travis to Focal ([#2999](https://github.com/cookiecutter/cookiecutter-django/pull/2999)) +### Updated +- Update pylint-django to 2.4.1 ([#3001](https://github.com/cookiecutter/cookiecutter-django/pull/3001)) +- Update sphinx to 3.4.3 ([#3000](https://github.com/cookiecutter/cookiecutter-django/pull/3000)) +- Update pylint-django to 2.4.0 ([#2996](https://github.com/cookiecutter/cookiecutter-django/pull/2996)) + +## [2021-01-04] +### Updated +- Update isort to 5.7.0 ([#2988](https://github.com/cookiecutter/cookiecutter-django/pull/2988)) +- Update uvicorn to 0.13.3 ([#2987](https://github.com/cookiecutter/cookiecutter-django/pull/2987)) +- Auto-update pre-commit hooks ([#2990](https://github.com/cookiecutter/cookiecutter-django/pull/2990)) +- Update sphinx to 3.4.2 ([#2995](https://github.com/cookiecutter/cookiecutter-django/pull/2995)) +- Update pillow to 8.1.0 ([#2993](https://github.com/cookiecutter/cookiecutter-django/pull/2993)) + +## [2020-12-29] +### Updated +- Update pygithub to 1.54.1 ([#2982](https://github.com/cookiecutter/cookiecutter-django/pull/2982)) +- Update django-storages to 1.11.1 ([#2981](https://github.com/cookiecutter/cookiecutter-django/pull/2981)) + +## [2020-12-26] +### Updated +- Update sphinx to 3.4.1 ([#2985](https://github.com/cookiecutter/cookiecutter-django/pull/2985)) +- Update pytz to 2020.5 ([#2984](https://github.com/cookiecutter/cookiecutter-django/pull/2984)) + +## [2020-12-23] +### Changed +- Bump peter-evans/create-pull-request from v3.5.2 to v3.6.0 ([#2980](https://github.com/cookiecutter/cookiecutter-django/pull/2980)) +### Updated +- Update flower to 0.9.7 ([#2979](https://github.com/cookiecutter/cookiecutter-django/pull/2979)) +- Update sphinx to 3.4.0 ([#2978](https://github.com/cookiecutter/cookiecutter-django/pull/2978)) +- Update coverage to 5.3.1 ([#2977](https://github.com/cookiecutter/cookiecutter-django/pull/2977)) +- Update uvicorn to 0.13.2 ([#2976](https://github.com/cookiecutter/cookiecutter-django/pull/2976)) + +## [2020-12-18] +### Changed +- Bump stefanzweifel/git-auto-commit-action from v4.7.2 to v4.8.0 ([#2972](https://github.com/cookiecutter/cookiecutter-django/pull/2972)) +### Updated +- Update django-storages to 1.11 ([#2973](https://github.com/cookiecutter/cookiecutter-django/pull/2973)) +- Update pytest to 6.2.1 ([#2971](https://github.com/cookiecutter/cookiecutter-django/pull/2971)) +- Auto-update pre-commit hooks ([#2970](https://github.com/cookiecutter/cookiecutter-django/pull/2970)) + +## [2020-12-14] +### Updated +- Update pytest to 6.2.0 ([#2968](https://github.com/cookiecutter/cookiecutter-django/pull/2968)) +- Update django-cors-headers to 3.6.0 ([#2967](https://github.com/cookiecutter/cookiecutter-django/pull/2967)) +- Update uvicorn to 0.13.1 ([#2966](https://github.com/cookiecutter/cookiecutter-django/pull/2966)) + +## [2020-12-10] +### Changed +- Hot-reload support to celery ([#2554](https://github.com/cookiecutter/cookiecutter-django/pull/2554)) +### Updated +- Update uvicorn to 0.13.0 ([#2962](https://github.com/cookiecutter/cookiecutter-django/pull/2962)) +- Update sentry-sdk to 0.19.5 ([#2965](https://github.com/cookiecutter/cookiecutter-django/pull/2965)) + +## [2020-12-09] +### Changed +- Bump peter-evans/create-pull-request from v3.5.1 to v3.5.2 ([#2964](https://github.com/cookiecutter/cookiecutter-django/pull/2964)) + +## [2020-12-08] +### Updated +- Update pre-commit to 2.9.3 ([#2961](https://github.com/cookiecutter/cookiecutter-django/pull/2961)) + +## [2020-12-04] +### Updated +- Update django-debug-toolbar to 3.2 ([#2959](https://github.com/cookiecutter/cookiecutter-django/pull/2959)) + +## [2020-12-02] +### Updated +- Update django-model-utils to 4.1.1 ([#2957](https://github.com/cookiecutter/cookiecutter-django/pull/2957)) +- Update pygithub to 1.54 ([#2958](https://github.com/cookiecutter/cookiecutter-django/pull/2958)) + +## [2020-11-26] +### Updated +- Update django-extensions to 3.1.0 ([#2947](https://github.com/cookiecutter/cookiecutter-django/pull/2947)) +- Update pre-commit to 2.9.2 ([#2948](https://github.com/cookiecutter/cookiecutter-django/pull/2948)) +- Update django-allauth to 0.44.0 ([#2945](https://github.com/cookiecutter/cookiecutter-django/pull/2945)) + +## [2020-11-25] +### Changed +- Bump peter-evans/create-pull-request from v3.5.0 to v3.5.1 ([#2944](https://github.com/cookiecutter/cookiecutter-django/pull/2944)) + +## [2020-11-23] +### Updated +- Update uvicorn to 0.12.3 ([#2943](https://github.com/cookiecutter/cookiecutter-django/pull/2943)) +- Update pre-commit to 2.9.0 ([#2942](https://github.com/cookiecutter/cookiecutter-django/pull/2942)) + +## [2020-11-21] +### Changed +- Fix after uvicorn 0.12.0 - Ship extra dependencies ([#2939](https://github.com/cookiecutter/cookiecutter-django/pull/2939)) + +## [2020-11-20] +### Updated +- Update sentry-sdk to 0.19.4 ([#2938](https://github.com/cookiecutter/cookiecutter-django/pull/2938)) + +## [2020-11-19] +### Updated +- Update django-crispy-forms to 1.10.0 ([#2937](https://github.com/cookiecutter/cookiecutter-django/pull/2937)) + +## [2020-11-17] +### Changed +- Bump peter-evans/create-pull-request from v2 to v3.5.0 ([#2936](https://github.com/cookiecutter/cookiecutter-django/pull/2936)) + +## [2020-11-15] +### Changed +- Fix formatting in docs ([#2935](https://github.com/cookiecutter/cookiecutter-django/pull/2935)) + +## [2020-11-13] +### Changed +- Upgrade factory-boy to 3.1.0 ([#2932](https://github.com/cookiecutter/cookiecutter-django/pull/2932)) +### Updated +- Update sentry-sdk to 0.19.3 ([#2933](https://github.com/cookiecutter/cookiecutter-django/pull/2933)) +- Update sphinx to 3.3.1 ([#2934](https://github.com/cookiecutter/cookiecutter-django/pull/2934)) + +## [2020-11-12] +### Changed +- Migrate CI to Github Actions ([#2931](https://github.com/cookiecutter/cookiecutter-django/pull/2931)) + +## [2020-11-06] +### Updated +- Update djangorestframework to 3.12.2 ([#2930](https://github.com/cookiecutter/cookiecutter-django/pull/2930)) + +## [2020-11-04] +### Changed +- Fix docs service and add RTD support ([#2920](https://github.com/cookiecutter/cookiecutter-django/pull/2920)) +- Bump stefanzweifel/git-auto-commit-action from v4.6.0 to v4.7.2 ([#2914](https://github.com/cookiecutter/cookiecutter-django/pull/2914)) +### Updated +- Auto-update pre-commit hooks ([#2908](https://github.com/cookiecutter/cookiecutter-django/pull/2908)) +- Update mypy to 0.790 ([#2886](https://github.com/cookiecutter/cookiecutter-django/pull/2886)) +- Update django-stubs to 1.7.0 ([#2916](https://github.com/cookiecutter/cookiecutter-django/pull/2916)) + +## [2020-11-03] +### Updated +- Update sentry-sdk to 0.19.2 ([#2926](https://github.com/cookiecutter/cookiecutter-django/pull/2926)) +- Update sphinx to 3.3.0 ([#2925](https://github.com/cookiecutter/cookiecutter-django/pull/2925)) +- Update django to 3.0.11 ([#2924](https://github.com/cookiecutter/cookiecutter-django/pull/2924)) +- Update pytz to 2020.4 ([#2923](https://github.com/cookiecutter/cookiecutter-django/pull/2923)) +- Update pre-commit to 2.8.2 ([#2919](https://github.com/cookiecutter/cookiecutter-django/pull/2919)) +- Update pytest to 6.1.2 ([#2917](https://github.com/cookiecutter/cookiecutter-django/pull/2917)) +- Update sh to 1.14.1 ([#2912](https://github.com/cookiecutter/cookiecutter-django/pull/2912)) +- Update pytest-django to 4.1.0 ([#2911](https://github.com/cookiecutter/cookiecutter-django/pull/2911)) +- Update pillow to 8.0.1 ([#2910](https://github.com/cookiecutter/cookiecutter-django/pull/2910)) +- Update django-celery-beat to 2.1.0 ([#2907](https://github.com/cookiecutter/cookiecutter-django/pull/2907)) +- Update uvicorn to 0.12.2 ([#2906](https://github.com/cookiecutter/cookiecutter-django/pull/2906)) + +## [2020-10-19] +### Updated +- Update sentry-sdk to 0.19.1 ([#2905](https://github.com/cookiecutter/cookiecutter-django/pull/2905)) + +## [2020-10-17] +### Updated +- Update django-allauth to 0.43.0 ([#2901](https://github.com/cookiecutter/cookiecutter-django/pull/2901)) +- Update pytest-django to 4.0.0 ([#2903](https://github.com/cookiecutter/cookiecutter-django/pull/2903)) + +## [2020-10-15] +### Updated +- Update pillow to 8.0.0 ([#2898](https://github.com/cookiecutter/cookiecutter-django/pull/2898)) + +## [2020-10-14] +### Updated +- Auto-update pre-commit hooks ([#2897](https://github.com/cookiecutter/cookiecutter-django/pull/2897)) +- Update sentry-sdk to 0.19.0 ([#2896](https://github.com/cookiecutter/cookiecutter-django/pull/2896)) + +## [2020-10-13] +### Updated +- Update isort to 5.6.4 ([#2895](https://github.com/cookiecutter/cookiecutter-django/pull/2895)) + +## [2020-10-12] +### Changed +- Bump stefanzweifel/git-auto-commit-action from v4.5.1 to v4.6.0 ([#2893](https://github.com/cookiecutter/cookiecutter-django/pull/2893)) +### Updated +- Auto-update pre-commit hooks ([#2892](https://github.com/cookiecutter/cookiecutter-django/pull/2892)) + +## [2020-10-11] +### Updated +- Auto-update pre-commit hooks ([#2890](https://github.com/cookiecutter/cookiecutter-django/pull/2890)) +- Update isort to 5.6.3 ([#2891](https://github.com/cookiecutter/cookiecutter-django/pull/2891)) +- Update django-anymail to 8.1 ([#2887](https://github.com/cookiecutter/cookiecutter-django/pull/2887)) +- Update tox to 3.20.1 ([#2885](https://github.com/cookiecutter/cookiecutter-django/pull/2885)) + +## [2020-10-09] +### Updated +- Auto-update pre-commit hooks ([#2884](https://github.com/cookiecutter/cookiecutter-django/pull/2884)) +- Update isort to 5.6.1 ([#2883](https://github.com/cookiecutter/cookiecutter-django/pull/2883)) + +## [2020-10-08] +### Changed +- Add dedicated websockets package ([#2881](https://github.com/cookiecutter/cookiecutter-django/pull/2881)) +### Updated +- Update isort to 5.6.0 ([#2882](https://github.com/cookiecutter/cookiecutter-django/pull/2882)) + +## [2020-10-04] +### Updated +- Update pytest to 6.1.1 ([#2880](https://github.com/cookiecutter/cookiecutter-django/pull/2880)) +- Update mypy and django-stubs ([#2874](https://github.com/cookiecutter/cookiecutter-django/pull/2874)) +- Auto-update pre-commit hooks ([#2876](https://github.com/cookiecutter/cookiecutter-django/pull/2876)) +- Update flake8 to 3.8.4 ([#2877](https://github.com/cookiecutter/cookiecutter-django/pull/2877)) + +## [2020-10-01] +### Changed +- Bump actions/setup-python from v2.1.2 to v2.1.3 ([#2869](https://github.com/cookiecutter/cookiecutter-django/pull/2869)) +### Updated +- Update ipdb to 0.13.4 ([#2873](https://github.com/cookiecutter/cookiecutter-django/pull/2873)) +- Auto-update pre-commit hooks ([#2867](https://github.com/cookiecutter/cookiecutter-django/pull/2867)) +- Update uvicorn to 0.12.1 ([#2866](https://github.com/cookiecutter/cookiecutter-django/pull/2866)) +- Update isort to 5.5.4 ([#2864](https://github.com/cookiecutter/cookiecutter-django/pull/2864)) +- Update sentry-sdk to 0.18.0 ([#2863](https://github.com/cookiecutter/cookiecutter-django/pull/2863)) +- Update djangorestframework to 3.12.1 ([#2862](https://github.com/cookiecutter/cookiecutter-django/pull/2862)) +- Update pytest to 6.1.0 ([#2859](https://github.com/cookiecutter/cookiecutter-django/pull/2859)) +- Update django-debug-toolbar to 3.1.1 ([#2855](https://github.com/cookiecutter/cookiecutter-django/pull/2855)) + +## [2020-09-23] +### Updated +- Update sentry-sdk to 0.17.7 ([#2847](https://github.com/cookiecutter/cookiecutter-django/pull/2847)) +- Update django-debug-toolbar to 3.1 ([#2846](https://github.com/cookiecutter/cookiecutter-django/pull/2846)) + +## [2020-09-21] +### Changed +- Adding GitHub-Action CI Option ([#2837](https://github.com/cookiecutter/cookiecutter-django/pull/2837)) +### Updated +- Update django-debug-toolbar to 3.0 ([#2842](https://github.com/cookiecutter/cookiecutter-django/pull/2842)) +- Auto-update pre-commit hooks ([#2843](https://github.com/cookiecutter/cookiecutter-django/pull/2843)) +- Update isort to 5.5.3 ([#2844](https://github.com/cookiecutter/cookiecutter-django/pull/2844)) + +## [2020-09-18] +### Updated +- Update django-extensions to 3.0.9 ([#2839](https://github.com/cookiecutter/cookiecutter-django/pull/2839)) + +## [2020-09-16] +### Updated +- Update sentry-sdk to 0.17.6 ([#2833](https://github.com/cookiecutter/cookiecutter-django/pull/2833)) +- Update pytest-django to 3.10.0 ([#2832](https://github.com/cookiecutter/cookiecutter-django/pull/2832)) + +## [2020-09-14] +### Fixed +- Downgrade Celery to 4.4.6 ([#2829](https://github.com/cookiecutter/cookiecutter-django/pull/2829)) +### Updated +- Update sentry-sdk to 0.17.5 ([#2828](https://github.com/cookiecutter/cookiecutter-django/pull/2828)) +- Update coverage to 5.3 ([#2826](https://github.com/cookiecutter/cookiecutter-django/pull/2826)) +- Update django-storages to 1.10.1 ([#2825](https://github.com/cookiecutter/cookiecutter-django/pull/2825)) + +## [2020-09-12] +### Updated +- Updating Traefik version from 2.0 to 2.2.11 ([#2814](https://github.com/cookiecutter/cookiecutter-django/pull/2814)) +- Update pytest to 6.0.2 ([#2819](https://github.com/cookiecutter/cookiecutter-django/pull/2819)) +- Update django-anymail to 8.0 ([#2818](https://github.com/cookiecutter/cookiecutter-django/pull/2818)) + +## [2020-09-11] +### Updated +- Auto-update pre-commit hooks ([#2809](https://github.com/cookiecutter/cookiecutter-django/pull/2809)) + +## [2020-09-10] +### Updated +- Update isort to 5.5.2 ([#2807](https://github.com/cookiecutter/cookiecutter-django/pull/2807)) +- Update sentry-sdk to 0.17.4 ([#2805](https://github.com/cookiecutter/cookiecutter-django/pull/2805)) + +## [2020-09-09] +### Changed +- Update actions/setup-python requirement to v2.1.2 ([#2804](https://github.com/cookiecutter/cookiecutter-django/pull/2804)) +- Clean up nested venv files from `.gitignore` ([#2800](https://github.com/cookiecutter/cookiecutter-django/pull/2800)) + +## [2020-09-08] +### Changed +- Traeffik and Django dockerfile changes ([#2801](https://github.com/cookiecutter/cookiecutter-django/pull/2801)) + +## [2020-09-07] +### Changed +- Add :z/:Z to mounted volumes in {local,production}.yml ([#2663](https://github.com/cookiecutter/cookiecutter-django/pull/2663)) +- Remove --no-binary option for psycopg2 ([#2798](https://github.com/cookiecutter/cookiecutter-django/pull/2798)) +- Updated Gitlab CI to use Python 3.8 instead of Python 3.7 ([#2794](https://github.com/cookiecutter/cookiecutter-django/pull/2794)) +### Fixed +- Fix options for sphinx-autobuild in docs Makefile ([#2799](https://github.com/cookiecutter/cookiecutter-django/pull/2799)) +### Updated +- Update psycopg2-binary to 2.8.6 ([#2797](https://github.com/cookiecutter/cookiecutter-django/pull/2797)) + +## [2020-09-05] +### Updated +- Auto-update pre-commit hooks ([#2793](https://github.com/cookiecutter/cookiecutter-django/pull/2793)) + +## [2020-09-04] +### Updated +- Update django-extensions to 3.0.8 ([#2792](https://github.com/cookiecutter/cookiecutter-django/pull/2792)) +- Update isort to 5.5.1 ([#2791](https://github.com/cookiecutter/cookiecutter-django/pull/2791)) +- Auto-update pre-commit hooks ([#2790](https://github.com/cookiecutter/cookiecutter-django/pull/2790)) +- Update isort to 5.5.0 ([#2789](https://github.com/cookiecutter/cookiecutter-django/pull/2789)) + +## [2020-09-02] +### Changed +- Add environment and traces_sample_rate keyword to sentry_sdk.init ([#2777](https://github.com/cookiecutter/cookiecutter-django/pull/2777)) +### Updated +- Update sentry-sdk to 0.17.3 ([#2788](https://github.com/cookiecutter/cookiecutter-django/pull/2788)) +- Update django-extensions to 3.0.7 ([#2787](https://github.com/cookiecutter/cookiecutter-django/pull/2787)) + +## [2020-09-01] +### Changed +- Exclude venv directory and update document link ([#2780](https://github.com/cookiecutter/cookiecutter-django/pull/2780)) +### Updated +- Update tox to 3.20.0 ([#2786](https://github.com/cookiecutter/cookiecutter-django/pull/2786)) +- Update django-storages to 1.10 ([#2781](https://github.com/cookiecutter/cookiecutter-django/pull/2781)) +- Update sentry-sdk to 0.17.2 ([#2784](https://github.com/cookiecutter/cookiecutter-django/pull/2784)) +- Update django to 3.0.10 ([#2785](https://github.com/cookiecutter/cookiecutter-django/pull/2785)) +- Update sphinx-autobuild to 2020.9.1 ([#2782](https://github.com/cookiecutter/cookiecutter-django/pull/2782)) +- Update django-extensions to 3.0.6 ([#2783](https://github.com/cookiecutter/cookiecutter-django/pull/2783)) + +## [2020-08-31] +### Updated +- Update sh to 1.14.0 ([#2779](https://github.com/cookiecutter/cookiecutter-django/pull/2779)) +- Update sentry-sdk to 0.17.1 ([#2778](https://github.com/cookiecutter/cookiecutter-django/pull/2778)) + +## [2020-04-13] +### Changed +- Updated to Python 3.8 (@codnee) +- Moved coverage config in setup.cfg (@danihodovic) + +## [2020-04-08] +### Fixed +- Internal IPs for debug toolbar (@dudanogueira) + +## [2020-04-04] +### Fixed +- Added compress command with Django compressor (@gwiskur) + +## [2020-03-23] +### Changed +- Updated project to Django 3.0 + +## [2020-03-17] +### Changed +- Handle paths using Pathlib (@jules-ch) + +### Fixed +- Pre-commit hook regex (@demestav) + +## [2020-03-16] +### Added +- Support for all Anymail providers (@Andrew-Chen-Wang) +### Fixed +- Django compressor setup (@jameswilliams1) + +## [2020-01-23] +### Changed +- Fix UserFactory to set the password if provided (@BoPeng) +- Update documentation files with latest Sphinx (@howiezhao) + +## [2020-01-12] +### Changed +- Fix mypy setup and added django-stubs (@danifus) +- Add Gitlab CI as option (@ikhomutov) + +## [2020-01-11] +### Changed +- Speed up & reduce size for production Django image (@maxp) +- Bumped runtime version for Heroku (@Isaac12x) +- Added Debian 10 (Buster) OS dependencies (@ddiazpinto) +- Update Traefik to v2 (@blaxpy) +- Switched Docker images from Alpine based to Debian based (@trungdong) + +## [2019-10-06] +### Changed +- Default Python version is now 3.7 (@nicolas471) + +## [2019-10-04] +### Fixed +- Fix static files handling on GCP (@caioariede) + +## [2019-10-03] +### Fixed +- Fix incompatible combination between Whitenoise and no cloud provider (@caioariede) + +## [2019-07-09] +### Fixed +- Always use test settings in pytest (@danihodovic) +- Remove gunicorn from `INSTALLED_APPS` (@danihodovic) +- Remove `EMAIL_HOST` and `EMAIL_PORT` with locmem backend (@danihodovic) + +### Added +- Add `EMAIL_TIMEOUT` (@danihodovic) + +## [2019-06-22] +### Fixed +- Remove redundant template debug setting (@danihodovic) + +## [2019-06-19] +### Fixed +- Fix removal carriage returns in docker scripts (@timclaessens) + +## [2019-06-15] +### Fixed +- Issue with Pycharm setup for running things in Docker compose (@foarsitter) + +## [2019-06-06] +### Changed +- Update generated Travis config (@browniebroke) + +## [2019-06-03] +### Added +- Installed `django-celery-beat` to keep scheduled tasks in DB (@keyvanm) + +## [2019-05-28] +### Changed +- Use GCP acronym rather than inconsistent GCE/GCS (@tanoabeleyra) + +## [2019-05-27] +### Changed +- Made cloud provider optional (@tanoabeleyra) +- Updated to Django 2.2.1 (@browniebroke) + +### Fixed +- Celery worker-related setting names (@browniebroke) + +## [2019-05-18] +### Removed +- Remove the user list view (@browniebroke) + +### Fixed +- Static storage default ACL (@browniebroke) + +## [2019-05-17] +### Fixed +- Added `LocaleMiddleware` to the list of middlewares (@tanoabeleyra) +- Added `LOCALE_PATH` to settings (@tanoabeleyra) + +## [2019-05-16] +### Changed +- Users app to have a translated verbose name (@tanoabeleyra) +- Logging configuration for local (@browniebroke) + +## [2019-05-08] +### Changed +- Upgraded to Django 2.1 (@browniebroke) + +## [2019-04-07] +### Added +- Support for Google Cloud Storage (@ahhda) + +## [2019-04-03] +### Added +- Command to backup Db to AWS S3 (@foarsitter) + +## [2019-03-25] +### Added +- Node image to run Gulp with Docker (@browniebroke) + +## [2019-03-19] +### Changed +- Replaced Caddy with Traefik (@demestav) + +## [2019-03-11] +### Changed +- Sentry integration from Raven to Sentry-SDK (@gfabricio) +- Made Redis config conditional on Celery locally (@demestav) + +## [2019-03-11] +### Added +- Automatic migrations on Heroku (@yunti) + +## [2019-03-06] +### Fixed +- Missing script tag in Travis config (@btknu) + +## [2019-03-02] +### Changed +- Celery eager setting in local setting with Docker (@keithjeb) + +## [2019-03-01] +### Updated +- All NPM dependencies (@takkaria) + +## [2018-11-13] +### Changed +- Security settings in Dev (@carlmjohnson) + +## [2018-11-20] +### Fixed +- Passing the CSRF header from the reverse proxy to Django server for DRF (@hpbruna) + +## [2018-11-12] +### Fixed +- Initialisation of Celery app (@glasslion) + +## [2018-10-24] +### Fixed +- Persisting of iPython history between sessions (@davitovmasyan) + +### Added +- Postgres 10.5 option (@jleclanche) + +## [2018-09-18] +### Added +- Included `mypy` in dependencies and run it in tests (@apirobot) + +## [2018-09-18] +### Fixed +- Avoid `$` in environment variables to workaround a bug from django-environ (@browniebroke) + +## [2018-09-16] +### Fixed +- Bug in ordering of Middleware for production config (@ChrisPappalardo) + +## [2018-09-12] +### Fixed +- URLs for Static and Media for S3 buckets in regions other than N. Virginia (@umrashrf) + +## [2018-09-09] +### Changed +- Name of static and media storage classes (@sfdye) + +## [2018-09-01] +### Changed +- Make static and media storage fully-fledged classes (@erfaan) + +## [2018-08-28] +### Fixed +- Running tests in docker test script (@apirobot) + +## [2018-07-23] +### Changed +- Test commands to use pytest (@jcass77) + +### Removed +- Some hacks leftovers from Bootstrap v4 beta in `project.js` (@hendrikschneider) + +## [2018-07-12] +### Changed +- Upgraded to Bootstrap 4.1.1 (@mostaszewski) + +## [2018-06-25] +### Added +- Flower integration with Docker (@webyneter) + +## [2018-06-25] +### Changed +- Rewrite user app test to use a pytest style (@webyneter) + +## [2018-06-21] +### Added +- Extend & update Celery config (@webyneter & @apirobot) + +## [2018-05-25] +### Fixed +- Build issues due to incompatibility between libressl & openssl (@SassanoM) + +## [2018-05-21] +### Changed +- Updated Caddy to 0.11 and pin its version (@webyneter) + +## [2018-05-14] +### Changed +- Replace `awesome-slugify` by `python-slugify` (@hongquan) +- Migrate to Django 2.0+ URL style (@saschalalala) + +## [2018-05-05] +### Fixed +- Postgres backup & restore commands (@webyneter) + +## [2018-04-10] +### Changed +- Simplify configuration (@danidee10) + +## [2018-04-08] +### Added +- Adopt Black code style (@pydanny) + +## [2018-03-27] +### Fixed +- Simplified extra Celery config generated when opted out (@webyneter) + +## [2018-03-21] +### Removed +- Remove Opbeat support (@sfdye) + +## [2018-03-16] +### Fixed +- Install `psycopg2-binary` when using Docker locally (@browniebroke) + +## [2018-03-14] +### Fixed +- Fixed and improved Postgres backup & restore scripts (@webyneter) + +## [2018-03-10] +### Changed +- Simplify Mailgun setting (@browniebroke) + +## [2018-03-06] +### Changed +- Convert string formatting to f-strings (@sfdye) + +## [2018-03-01] +### Changed +- Celery to use JSON serialization by default (@adammsteele) +- Use Docker version from Travis to run tests (@browniebroke) + +## [2018-02-16] +### Changed +- Upgraded to Django 2.0 (@epicwhale) + +## [2018-01-15] +### Changed +- Removed Elastic Beanstalk support (@pydanny) + +## [2017-12-28] +### Changed +- Upgraded to Django 1.11 (@pydanny) + +## [2017-10-08] +### Changed +- Elastic Beanstalk: Added --noinput to migrate command (@MightySCollins ) + +## [2017-10-07] +### Added +- Finished first pass at Elastic Beanstalk docs (@pydanny & @audreyr) +### Deleted +- Removed Heroku instant deploy button (@pydanny) + + +##[2016-09-29] +### Added +- Added default `AUTH_PASSWORD_VALIDATORS` configuration, generated by django 1.10 startproject. See [Password Validation docs](https://docs.djangoproject.com/en/1.10/topics/auth/passwords/#module-django.contrib.auth.password_validation") (@luzfcb) +- Rename `MIDDLEWARE_CLASSES` to `MIDDLEWARE` to enable support to [new style middleware](https://github.com/django/deps/blob/master/final/0005-improved-middleware.rst) introduced in Django 1.10 (@luzfcb) +- New setting `MAILGUN_SENDER_DOMAIN` to allow sending mail from any domain other than those registered with mailgun (@jangeador) +- add `urlpatterns` configuration to django-debug-toolbar, because the automatic configuration of `urlpatterns` was removed from django-debug-toolbar (@luzfcb) +- Added Temporary workaround on `requirements/local.txt` to fix django-debug-toolbar issue: https://github.com/cookiecutter/cookiecutter-django/issues/827 (@luzfcb) + +### Changed +- Upgrade to Django 1.10.1 (@luzfcb) +- Upgrade django-model-utils to 2.6, django-redis to 4.5.0, redis to 2.10.5, Sphinx to 1.4.6, pytest-django to 3.0.0, django-anymail to 0.5, raven to 5.27.1, whitenoise to 3.2.2 (@luzfcb) +- Upgrade to Bootstrap 4 Alpha 4, jQuery to 3.1.1, tether.js to 1.3.7 (@luzfcb) +- Update `manage.py` to use same code of `manage.py` from Django 1.10 (@luzfcb) +- Sync `sites` app migrations with django 1.10, and fix aditional migrations to `sites` and `user` app (@luzfcb) +d changed 'admin' url on `config/urls.py`, to stay the same as generated by django 1.10 (@luzfcb) +- Make test_docker.sh tests pass by passing new password auth rules (@ssteinerx) + +### Removed +- Removed django-autoslug because not support django 1.10 at this date (@luzfcb) + + +##[2016-09-10] +### Changed +- Use app registry instead of INSTALLED_APPS to discover celery tasks (@dhepper) +- PEP8 imports fix (@aleprovencio) + +### Removed +- Removed django-floppyforms (@pydanny) + +##[2016-09-08] +### Removed +- Webpack support, see #774 (@ssteinerx) + +##[2016-08-10] +## Added +- PostgreSQL versions are now selectable, instead of defaulting to 9.5; the minimum version is 9.2, which is supported by [Heroku](https://devcenter.heroku.com/articles/heroku-postgresql#version-support-and-legacy-infrastructure) and Django (@burhan) +- Fixed minor issue in the README.rst (@burhan) + +##[2016-08-03] +## Changed +- Upgrade to Bootstrap 4 Alpha 3 and its dependencies, including jQuery (@audreyr) + +##[2016-06-25] +## Changed +- use `https` instead `ssh` to clone [cookiecutter-webpack](https://github.com/hzdg/cookiecutter-webpack) if `Webpack` is selected as `JS Task Runner` - fix issue #647 (@luzfcb and @resakse) + +##[2016-06-24] +## Added +- Settings file for running tests faster (@audreyr) +- Add GPLv3 licence support (@cgaspoz) + +## Changed +- Makes the database backups compressed. restores compressed backups (@jangeador) +- Review and edit django-allauth templates (@kappataumu) + +##[2016-06-19] +## Added +- Webpack as an option (@goldhand) + +##[2016-06-17] +## Added +- django-compressor support (@andresgz) +- Debian Jessie OS Requirements (@ddiazpinto) + +##[2016-06-14] +### Changed +- Move Docker backups to their own section (@pydanny) + +##[2016-06-13] +### Changed +- Use latest redis image in Docker (@pydanny) +- Documentation cleanup and corrections (@audreyr) + +##[2016-06-12] +### Changed +- Documentation cleanup and corrections (@kappataumu) + +##[2016-06-11] +### Changed +- Enhancements to the developing locally docs (@antoniablair) + +##[2016-06-06] +### Changed +- Pin Bootstrap CSS and JS to v4.0.0-alpha.2, use minified versions + +##[2016-06-05] +### Added +- Configurable admin for users (@pydanny, @jayfk, @dezoito) + +##[2016-06-04] +### Added +- Let's Encrypt automation and instruction (@mjsisley and @chrisdev) + +##[2016-06-03] +### Added +- Documentation for debugging with Docker (@mjsisley) +- Apache 2 License option in `cookiecutter.json` (@dot2dotseurat) +- Removed unnecessary version check from `pre_gen_project.py` (@suledev) +- Add gulp alternative as a js task runner and fix navbar style issue (@viviangb and @xpostudio4) + +### Deleted +- AngularJS (@pydanny) +- django-secure (@xpostudio4) + +##[2016-06-02] +### Added +- Added better instructions for installing postgres on Mac OS X (@dot2dotseurat ) ##[2016-05-22] ### Added @@ -46,7 +2322,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### [2016-05-01] ### Changed -- Restored the Pycharm project configuration files, that was accidentally removed in [15f350f](https://github.com/pydanny/cookiecutter-django/commit/15f350f05e2b49b4bdff0bdaa2b2ff260606e0f6) (@luzfcb @Newton715) +- Restored the Pycharm project configuration files, that was accidentally removed in [15f350f](https://github.com/cookiecutter/cookiecutter-django/commit/15f350f05e2b49b4bdff0bdaa2b2ff260606e0f6) (@luzfcb @Newton715) ### [2016-04-30] ### Changed @@ -170,7 +2446,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [2016-02-15] ### Changed - In `users` app adapter, fix `is_open_for_signup` missing parameter (@oryx2) -- Fixes and improvements in Hitch tests , see [#485](https://github.com/pydanny/cookiecutter-django/pull/485) (@crdoconnor) +- Fixes and improvements in Hitch tests , see [#485](https://github.com/cookiecutter/cookiecutter-django/pull/485) (@crdoconnor) ## [2016-02-12] diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..8a5fc41580 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +## Code of Conduct + +Everyone who interacts in the Cookiecutter project's codebase, issue trackers, chat rooms, and mailing lists is expected to follow the [PyPA Code of Conduct](https://www.pypa.io/en/latest/code-of-conduct/). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..69bce6f2e4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# How to Contribute + +Always happy to get issues identified and pull requests! + +## Getting your pull request merged in + +1. Keep it small. The smaller the pull request, the more likely we are to accept. +2. Pull requests that fix a current issue get priority for review. + +## Testing + +### Installation + +Please install [tox](https://tox.readthedocs.io/en/latest/), which is a generic virtualenv management and test command line tool. + +[tox](https://tox.readthedocs.io/en/latest/) is available for download from [PyPI](https://pypi.python.org/pypi) via [pip](https://pypi.python.org/pypi/pip/): + + $ pip install tox + +It will automatically create a fresh virtual environment and install our test dependencies, +such as [pytest-cookies](https://pypi.python.org/pypi/pytest-cookies/) and [flake8](https://pypi.python.org/pypi/flake8/). + +### Run the Tests + +Tox uses pytest under the hood, hence it supports the same syntax for selecting tests. + +For further information please consult the [pytest usage docs](https://pytest.org/latest/usage.html#specifying-tests-selecting-tests). + +To run all tests using various versions of python in virtualenvs defined in tox.ini, just run tox.: + + $ tox + +It is possible to test with a specific version of python. To do this, the command +is: + + $ tox -e py310 + +This will run pytest with the python3.10 interpreter, for example. + +To run a particular test with tox for against your current Python version: + + $ tox -e py -- -k test_default_configuration diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 9acc83287e..0000000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,56 +0,0 @@ -How to Contribute -================= - -Always happy to get issues identified and pull requests! - -Getting your pull request merged in ------------------------------------- - -#. Keep it small. The smaller the pull request the more likely I'll pull it in. -#. Pull requests that fix a current issue get priority for review. -#. If you're not already in the `CONTRIBUTORS.rst` file, add yourself! - -Testing -------- - -Installation -~~~~~~~~~~~~ - -Please install `tox`_, which is a generic virtualenv management and test command line tool. - -`tox`_ is available for download from `PyPI`_ via `pip`_:: - - $ pip install tox - -It will automatically create a fresh virtual environment and install our test dependencies, -such as `pytest-cookies`_ and `flake8`_. - -Run the Tests -~~~~~~~~~~~~~ - -Tox uses py.test under the hood, hence it supports the same syntax for selecting tests. - -For further information please consult the `pytest usage docs`_. - -To run all tests using various versions of python in virtualenvs defined in tox.ini, just run tox.:: - - $ tox - -It is possible to tests with some versions of python, to do this the command -is:: - - $ tox -e py27,py34 - -Will run py.test with the python2.7, and python3.4 interpreters, for -example. - -To run a particular test with tox for against your current Python version:: - - $ tox -e py -- -k test_default_configuration - -.. _`pytest usage docs`: https://pytest.org/latest/usage.html#specifying-tests-selecting-tests -.. _`tox`: https://tox.readthedocs.io/en/latest/ -.. _`pip`: https://pypi.python.org/pypi/pip/ -.. _`pytest-cookies`: https://pypi.python.org/pypi/pytest-cookies/ -.. _`flake8`: https://pypi.python.org/pypi/flake8/ -.. _`PyPI`: https://pypi.python.org/pypi diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 0000000000..86ee9251cb --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,1851 @@ +# Contributors + +## Core Developers + +These contributors have commit flags for the repository, and are able to +accept and merge pull requests. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameGithubTwitter
Daniel Roy Greenfeld + pydanny + pydanny
Audrey Roy Greenfeld + audreyr + audreyr
Fábio C. Barrionuevo da Luz + luzfcb + luzfcb
Saurabh Kumar + theskumar + _theskumar
Jannis Gebauer + jayfk +
Burhan Khalid + burhan + burhan
Shupeyko Nikita + webyneter + webyneter
Bruno Alla + browniebroke + _BrunoAlla
Wan Liuyang + sfdye + sfdye
+ +*Audrey is also the creator of Cookiecutter. Audrey and Daniel are on +the Cookiecutter core team.* + +## Other Contributors + +Listed in alphabetical order. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameGithubTwitter
18 + dezoito +
2O4 + 2O4 +
a7p + a7p +
Aadith PM + aadithpm +
Aaron Eikenberry + aeikenberry +
Abdul Qoyyuum + Qoyyuum + Qoyyuum
Abdullah Adeel + mabdullahadeel + abdadeel_
Abe Hanoka + abe-101 + abe__101
Adam Bogdał + bogdal +
Adam Dobrawy + ad-m +
Adam Steele + adammsteele +
Agam Dua + +
Agustín Scaramuzza + scaramagus + scaramagus
Alberto Sanchez + alb3rto +
Alex Tsai + caffodian +
Alvaro [Andor] + andor-pierdelacabeza +
Amjith Ramanujam + amjith +
Andreas Meistad + ameistad +
Andres Gonzalez + andresgz +
Andrew Chen Wang + Andrew-Chen-Wang +
Andrew Mikhnevich + zcho +
Andy Rose + +
Anna Callahan + jazztpt +
Anna Sidwell + takkaria +
Antonia Blair + antoniablair + antoniablairart
Anuj Bansal + ahhda +
Arcuri Davide + dadokkio +
Areski Belaid + areski +
Arnav Choudhury + arnav13081994 +
Artur Barseghyan + barseghyanartur +
AsheKR + ashekr +
Ashley Camba + +
Barclay Gauld + yunti +
Bartek + btknu +
Ben Lopatin + +
Ben Warren + bwarren2 +
Benjamin Abel + +
Bert de Miranda + bertdemiranda +
Bo Lopker + blopker +
Bo Peng + BoPeng +
Bogdan Mateescu + mateesville93 +
Bouke Haarsma + +
Brandon Rumiser + brumiser1550 +
Brent Payne + brentpayne + brentpayne
Bruce Olivier + bolivierjr +
Caio Ariede + caioariede + caioariede
Carl Johnson + carlmjohnson + carlmjohnson
Catherine Devlin + catherinedevlin +
Cebrail Yılmaz + b1sar +
Chao Yang Wu + goatwu1993 +
Charlie Macfarlane Brodie + tannart +
Charlie Smith + chuckus +
Chris Curvey + ccurvey +
Chris Franklin + +
Chris Franklin + hairychris +
Chris Pappalardo + ChrisPappalardo +
Christopher Clarke + chrisdev +
Cole Mackenzie + cmackenzie1 +
Cole Maclean + cole + cole
Collederas + Collederas +
Corey Garvey + coreygarvey +
Craig Margieson + cmargieson +
Cristian Vargas + cdvv7788 +
Cullen Rhodes + c-rhodes +
Curtis St Pierre + curtisstpierre + cstpierre1388
Cédric Gaspoz + cgaspoz +
dalrrard + dalrrard +
Dan Shultz + shultz +
Dani Hodovic + danihodovic +
Daniel Hepper + dhepper + danielhepper
Daniel Hillier + danifus +
Daniel Sears + highpost + highpost
Daniele Tricoli + eriol +
David Díaz + ddiazpinto + DavidDiazPinto
Davit Tovmasyan + davitovmasyan +
Davur Clementsen + dsclementsen + davur
Delio Castillo + jangeador + jangeador
Demetris Stavrou + demestav +
Denis Bobrov + delneg +
Denis Orehovsky + apirobot +
Denis Savran + blaxpy +
Diane Chen + purplediane + purplediane88
Diego Montes + d57montes +
Dong Huynh + trungdong +
Douglas + douglascdev +
Duda Nogueira + dudanogueira + dudanogueira
Dónal Adams + epileptic-fish +
Emanuel Calso + bloodpet + bloodpet
enchance + enchance +
Eraldo Energy + eraldo +
Eric Groom + ericgroom +
Ernesto Cedeno + codnee +
Eyad Al Sibai + eyadsibai +
Fabian Affolter + fabaff + fabaff
Felipe Arruda + arruda +
Florian Idelberger + step21 + windrush
Floyd Hightower + fhightower +
Fuzzwah + Fuzzwah +
Gabriel Mejia + elgartoinf + elgartoinf
Garry Cairns + garry-cairns +
Garry Polley + garrypolley +
ghazi-git + ghazi-git +
Gilbishkosma + Gilbishkosma +
Glenn Wiskur + gwiskur +
Grant McLean + grantm + grantmnz
Guilherme Guy + guilherme1guy +
Hamish Durkin + durkode +
Hana Quadara + hanaquadara +
Hannah Lazarus + hanhanhan +
Harry Moreno + morenoh149 + morenoh149
Harry Percival + hjwp +
Haseeb ur Rehman + professorhaseeb + professorhaseeb
Hendrik Schneider + hendrikschneider +
Henrique G. G. Pereira + ikkebr +
Howie Zhao + howiezhao +
Ian Lee + IanLee1521 +
innicoder + innicoder +
Irfan Ahmad + erfaan + erfaan
Isaac12x + Isaac12x +
Ivan Khomutov + ikhomutov +
Jakub Musko + umgelurgel +
James Williams + jameswilliams1 +
Jan Fabry + janfabry +
Jan Van Bruggen + jvanbrug +
Jelmer Draaijer + foarsitter +
Jens Nilsson + phiberjenz +
Jerome Caisip + jeromecaisip +
Jerome Leclanche + jleclanche + Adys
Jimmy Gitonga + Afrowave + afrowave
John + thorrak +
John Cass + jcass77 + cass_john
Jonathan Thompson + nojanath +
Jorge Valdez + jorgeavaldez +
jugglinmike + jugglinmike +
Jules Cheron + jules-ch +
Julien Almarcha + sladinji +
Julio Castillo + juliocc +
Kaido Kert + kaidokert +
kappataumu + kappataumu + kappataumu
Kaveh + ka7eh +
Keith Bailey + keithjeb +
Keith Callenberg + keithcallenberg +
Keith Webber + townie +
Kevin A. Stone + +
Kevin Ndung'u + kevgathuku +
Keyvan Mosharraf + keyvanm +
krati yadav + krati5 +
Krzysztof Szumny + noisy +
Krzysztof Żuraw + krzysztofzuraw +
Kuo Chao Cheng + wwwtony5488 +
lcd1232 + lcd1232 +
LECbg + LECbg +
Leo won + leollon +
Leo Zhou + glasslion +
Leon Kim + PilhwanKim +
Leonardo Jimenez + xpostudio4 +
Liam Brenner + SableWalnut +
Lin Xianyi + iynaix +
Luis Nell + originell +
Lukas Klein + +
Lyla Fischer + +
Malik Sulaimanov + flyudvik + flyudvik
Manjit Pardeshi + Manjit2003 +
Marcio Mazza + marciomazza + marciomazza
Martin Blech + +
Martin Saizar + msaizar +
Mateusz Ostaszewski + mostaszewski +
Mathijs Hoogland + MathijsHoogland +
Matt Braymer-Hayes + mattayes + mattayes
Matt Knapper + mknapper1 +
Matt Linares + +
Matt Menzenski + menzenski +
Matt Warren + mfwarren +
Matthew Sisley + mjsisley +
Matthias Sieber + manonthemat + MatzeOne
Meghan Heintz + dot2dotseurat +
Meraj + ichbinmeraj + merajsafari
Mesut Yılmaz + myilmaz +
mfosterw + mfosterw +
Michael Gecht + mimischi + _mischi
Michael Samoylov + msamoylov +
Mike97M + Mike97M +
Min ho Kim + minho42 +
monosans + monosans +
mozillazg + mozillazg +
mpoli + mpoli +
Naveen + naveensrinivasan + snaveen
Nico Stefani + nicolas471 + moby_dick91
Nikita Sobolev + sobolevn +
Noah H + nthall +
Oleg Russkin + rolep +
Pablo + oubiga +
Parbhat Puri + parbhat +
Pawan Chaurasia + rjsnh1522 +
Pedro Campos + pcampos119104 +
Peter Bittner + bittner +
Peter Coles + mrcoles +
Philipp Matthies + canonnervio +
Pierre Chiquet + pchiquet +
PJ Hoberman + pjhoberman +
Raony Guimarães Corrêa + raonyguimaraes +
Raphael Pierzina + hackebrot +
Ray Besiga + raybesiga + raybesiga
Reggie Riser + reggieriser +
René Muhl + rm-- +
Richard Hajdu + Tusky +
Roman Afanaskin + siauPatrick +
Roman Osipenko + romanosipenko +
Russell Davies + +
Ryan Fitch + ryfi +
Sam Collins + MightySCollins +
Sascha + saschalalala + saschalalala
Sebastian Reyes Espinosa + sebastian-code + sebastianreyese
Simon Rey + eqqe +
Sorasful + sorasful +
Srinivas Nyayapati + shireenrao +
stepmr + stepmr +
Steve Steiner + ssteinerX +
Sudarshan Wadkar + wadkar +
Sule Marshall + suledev +
Sławek Ehlert + slafs +
Tames McTigue + Tamerz +
Tano Abeleyra + tanoabeleyra +
Taylor Baldwin + +
Thibault J. + thibault + thibault
Théo Segonds + show0k +
Tim Claessens + timclaessens +
Tim Freund + timfreund +
Timm Simpkins + PoDuck +
Tom Atkins + knitatoms +
Tom Offermann + +
Travis McNeill + Travistock + tavistock_esq
Tubo Shi + Tubo +
Umair Ashraf + umrashrf + fabumair
Vadim Iskuchekov + Egregors + egregors
vascop + vascop +
Vicente G. Reyes + reyesvicente + highcenburg
Vikas Yadav + vik-y +
Vitaly Babiy + +
Vivian Guillen + viviangb +
Vlad Doster + vladdoster +
Wes Turner + westurner + westurner
Will Farley + goldhand + g01dhand
Will Gordon + wgordon17 +
William Archinal + archinal +
Xaver Y.R. Chen + yrchen + yrchen
Yaroslav Halchenko + +
Yotam Tal + yotamtal +
Yuchen Xie + mapx +
Zach Borboa + zachborboa +
+ +### Special Thanks + +The following haven't provided code directly, but have provided +guidance and advice. + +- Jannis Leidel +- Nate Aune +- Barry Morrison \ No newline at end of file diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst deleted file mode 100644 index 2fda7c6341..0000000000 --- a/CONTRIBUTORS.rst +++ /dev/null @@ -1,183 +0,0 @@ -Contributors -============ - -Core Developers ----------------- - -These contributors have commit flags for the repository, -and are able to accept and merge pull requests. - -=========================== ============= =========== -Name Github Twitter -=========================== ============= =========== -Daniel Roy Greenfeld `@pydanny`_ @pydanny -Audrey Roy Greenfeld* `@audreyr`_ @audreyr -Fábio C. Barrionuevo da Luz `@luzfcb`_ @luzfcb -Saurabh Kumar `@theskumar`_ @_theskumar -Jannis Gebauer `@jayfk`_ -Burhan Khalid `@burhan`_ @burhan -=========================== ============= =========== - -*Audrey is also the creator of Cookiecutter. Audrey and -Daniel are on the Cookiecutter core team.* - -.. _@pydanny: https://github.com/pydanny -.. _@luzfcb: https://github.com/luzfcb -.. _@theskumar: https://github.com/theskumar -.. _@audreyr: https://github.com/audreyr -.. _@jayfk: https://github.com/jayfk - -Other Contributors -------------------- - -Listed in alphabetical order. - -========================== ============================ ============== - Name Github Twitter -========================== ============================ ============== - 18 `@dezoito`_ - a7p `@a7p`_ - Aaron Eikenberry `@aeikenberry`_ - Adam Bogdał `@bogdal`_ - Adam Dobrawy `@ad-m`_ - Agam Dua - Alberto Sanchez `@alb3rto`_ - Alex Tsai `@caffodian`_ - Alvaro [Andor] `@andor-pierdelacabeza`_ - Amjith Ramanujam `@amjith`_ - Andrew Mikhnevich `@zcho`_ - Andy Rose - Anna Callahan `@jazztpt`_ - Areski Belaid `@areski`_ - Ashley Camba - Barclay Gauld `@yunti`_ - Ben Lopatin - Benjamin Abel - Bo Lopker `@blopker`_ - Bouke Haarsma - Burhan Khalid `@burhan`_ @burhan - Catherine Devlin `@catherinedevlin`_ - Chris Curvey `@ccurvey`_ - Chris Franklin - Chris Franklin `@hairychris`_ - Chris Pappalardo `@ChrisPappalardo`_ - Collederas `@Collederas`_ - Cristian Vargas `@cdvv7788`_ - Cullen Rhodes `@c-rhodes`_ - Daniele Tricoli `@eriol`_ - David Díaz `@ddiazpinto`_ @DavidDiazPinto - Davur Clementsen `@dsclementsen`_ @davur - Dónal Adams `@epileptic-fish`_ - Dong Huynh `@trungdong`_ - Emanuel Calso `@bloodpet`_ @bloodpet - Eraldo Energy `@eraldo`_ - Eyad Al Sibai `@eyadsibai`_ - Felipe Arruda `@arruda`_ - Garry Cairns `@garry-cairns`_ - Garry Polley `@garrypolley`_ - Harry Percival `@hjwp`_ - Henrique G. G. Pereira `@ikkebr`_ - Ian Lee `@IanLee1521`_ - Jan Van Bruggen `@jvanbrug`_ - Jens Nilsson `@phiberjenz` - Julio Castillo `@juliocc`_ - Kaido Kert `@kaidokert`_ - Kaveh `@ka7eh`_ - Kevin A. Stone - Kevin Ndung'u `@kevgathuku`_ - Krzysztof Szumny `@noisy`_ - Krzysztof Żuraw `@krzysztofzuraw`_ - Lin Xianyi `@iynaix`_ - Luis Nell `@originell`_ - Lukas Klein - Lyla Fischer - Martin Blech - Mathijs Hoogland `@MathijsHoogland`_ - Matt Linares - Matt Menzenski `@menzenski`_ - Matt Warren `@mfwarren`_ - mozillazg `@mozillazg`_ - Pablo `@oubiga`_ - Raphael Pierzina `@hackebrot`_ - Raony Guimarães Corrêa `@raonyguimaraes`_ - Roman Afanaskin `@siauPatrick`_ - Roman Osipenko `@romanosipenko`_ - Russell Davies - stepmr `@stepmr`_ - Sławek Ehlert `@slafs`_ - Srinivas Nyayapati `@shireenrao`_ - Taylor Baldwin - Théo Segonds `@show0k`_ - Tom Atkins `@knitatoms`_ - Tom Offermann - Travis McNeill `@Travistock`_ @tavistock_esq - Vitaly Babiy - Yaroslav Halchenko -========================== ============================ ============== - -.. _@dezoito: https://github.com/dezoito -.. _@a7p: https://github.com/a7p -.. _@ad-m: https://github.com/ad-m -.. _@aeikenberry: https://github.com/aeikenberry -.. _@alb3rto: https://github.com/alb3rto -.. _@amjith: https://github.com/amjith -.. _@andor-pierdelacabeza: https://github.com/andor-pierdelacabeza -.. _@areski: https://github.com/areski -.. _@arruda: https://github.com/arruda -.. _@bloodpet: https://github.com/bloodpet -.. _@bogdal: https://github.com/bogdal -.. _@burhan: https://github.com/burhan -.. _@c-rhodes: https://github.com/c-rhodes -.. _@caffodian: https://github.com/caffodian -.. _@catherinedevlin: https://github.com/catherinedevlin -.. _@ccurvey: https://github.com/ccurvey -.. _@cdvv7788: https://github.com/cdvv7788 -.. _@ChrisPappalardo: https://github.com/ChrisPappalardo -.. _@Collederas: https://github.com/Collederas -.. _@ddiazpinto: https://github.com/ddiazpinto -.. _@dsclementsen: https://github.com/dsclementsen -.. _@epileptic-fish: https://gihub.com/epileptic-fish -.. _@eraldo: https://github.com/eraldo -.. _@eriol: https://github.com/eriol -.. _@eyadsibai: https://github.com/eyadsibai -.. _@garry-cairns: https://github.com/garry-cairns -.. _@garrypolley: https://github.com/garrypolley -.. _@hackebrot: https://github.com/hackebrot -.. _@hairychris: https://github.com/hairychris -.. _@hjwp: https://github.com/hjwp -.. _@IanLee1521: https://github.com/IanLee1521 -.. _@ikkebr: https://github.com/ikkebr -.. _@iynaix: https://github.com/iynaix -.. _@jazztpt: https://github.com/jazztpt -.. _@juliocc: https://github.com/juliocc -.. _@jvanbrug: https://github.com/jvanbrug -.. _@ka7eh: https://github.com/ka7eh -.. _@kaidokert: https://github.com/kaidokert -.. _@kevgathuku: https://github.com/kevgathuku -.. _@knitatoms: https://github.com/knitatoms -.. _@MathijsHoogland: https://github.com/MathijsHoogland -.. _@menzenski: https://github.com/menzenski -.. _@mfwarren: https://github.com/mfwarren -.. _@mozillazg: https://github.com/mozillazg -.. _@originell: https://github.com/originell -.. _@oubiga: https://github.com/oubiga -.. _@romanosipenko: https://github.com/romanosipenko -.. _@raonyguimaraes: https://github.com/raonyguimaraes -.. _@show0k: https://github.com/show0k -.. _@siauPatrick: https://github.com/siauPatrick -.. _@shireenrao: https://github.com/shireenrao -.. _@slafs: https://github.com/slafs -.. _@stepmr: https://github.com/stepmr -.. _@Travistock: https://github.com/Tavistock -.. _@trungdong: https://github.com/trungdong -.. _@yunti: https://github.com/yunti -.. _@zcho: https://github.com/zcho -.. _@noisy: https://github.com/noisy -Special Thanks -~~~~~~~~~~~~~~ - -The following haven't provided code directly, but have provided guidance and advice. - -* Jannis Leidel -* Nate Aune -* Barry Morrison diff --git a/LICENSE b/LICENSE index 9a5d2fc08a..a67e4da21a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2016, Daniel Greenfeld +Copyright (c) 2013-2020, Daniel Roy Greenfeld All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/README.md b/README.md new file mode 100644 index 0000000000..9f2af6d37e --- /dev/null +++ b/README.md @@ -0,0 +1,247 @@ +# Cookiecutter Django + +[![Build Status](https://img.shields.io/github/workflow/status/cookiecutter/cookiecutter-django/CI/master)](https://github.com/cookiecutter/cookiecutter-django/actions?query=workflow%3ACI) +[![Documentation Status](https://readthedocs.org/projects/cookiecutter-django/badge/?version=latest)](https://cookiecutter-django.readthedocs.io/en/latest/?badge=latest) +[![Updates](https://pyup.io/repos/github/cookiecutter/cookiecutter-django/shield.svg)](https://pyup.io/repos/github/cookiecutter/cookiecutter-django/) +[![Join our Discord](https://img.shields.io/badge/Discord-cookiecutter-5865F2?style=flat&logo=discord&logoColor=white)](https://discord.gg/uFXweDQc5a) +[![Code Helpers Badge](https://www.codetriage.com/cookiecutter/cookiecutter-django/badges/users.svg)](https://www.codetriage.com/cookiecutter/cookiecutter-django) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) + +Powered by [Cookiecutter](https://github.com/cookiecutter/cookiecutter), Cookiecutter Django is a framework for jumpstarting +production-ready Django projects quickly. + +- Documentation: +- See [Troubleshooting](https://cookiecutter-django.readthedocs.io/en/latest/troubleshooting.html) for common errors and obstacles +- If you have problems with Cookiecutter Django, please open [issues](https://github.com/cookiecutter/cookiecutter-django/issues/new) don't send + emails to the maintainers. + +## Features + +- For Django 4.0 +- Works with Python 3.10 +- Renders Django projects with 100% starting test coverage +- Twitter [Bootstrap](https://github.com/twbs/bootstrap) v5 +- [12-Factor](http://12factor.net/) based settings via [django-environ](https://github.com/joke2k/django-environ) +- Secure by default. We believe in SSL. +- Optimized development and production settings +- Registration via [django-allauth](https://github.com/pennersr/django-allauth) +- Comes with custom user model ready to go +- Optional basic ASGI setup for Websockets +- Optional custom static build using Gulp and livereload +- Send emails via [Anymail](https://github.com/anymail/django-anymail) (using [Mailgun](http://www.mailgun.com/) by default or Amazon SES if AWS is selected cloud provider, but switchable) +- Media storage using Amazon S3 or Google Cloud Storage +- Docker support using [docker-compose](https://github.com/docker/compose) for development and production (using [Traefik](https://traefik.io/) with [LetsEncrypt](https://letsencrypt.org/) support) +- [Procfile](https://devcenter.heroku.com/articles/procfile) for deploying to Heroku +- Instructions for deploying to [PythonAnywhere](https://www.pythonanywhere.com/) +- Run tests with unittest or pytest +- Customizable PostgreSQL version +- Default integration with [pre-commit](https://github.com/pre-commit/pre-commit) for identifying simple issues before submission to code review + +## Optional Integrations + +*These features can be enabled during initial project setup.* + +- Serve static files from Amazon S3, Google Cloud Storage or [Whitenoise](https://whitenoise.readthedocs.io/) +- Configuration for [Celery](https://docs.celeryq.dev) and [Flower](https://github.com/mher/flower) (the latter in Docker setup only) +- Integration with [MailHog](https://github.com/mailhog/MailHog) for local email testing +- Integration with [Sentry](https://sentry.io/welcome/) for error logging + +## Constraints + +- Only maintained 3rd party libraries are used. +- Uses PostgreSQL everywhere: 10.19 - 14.1 ([MySQL fork](https://github.com/mabdullahadeel/cookiecutter-django-mysql) also available). +- Environment variables for configuration (This won't work with Apache/mod_wsgi). + +## Support this Project! + +This project is run by volunteers. Please support them in their efforts to maintain and improve Cookiecutter Django: + +- Daniel Roy Greenfeld, Project Lead ([GitHub](https://github.com/pydanny), [Patreon](https://www.patreon.com/danielroygreenfeld)): expertise in Django and AWS ELB. +- Nikita Shupeyko, Core Developer ([GitHub](https://github.com/webyneter)): expertise in Python/Django, hands-on DevOps and frontend experience. + +Projects that provide financial support to the maintainers: + +------------------------------------------------------------------------ + +

+ +

+ +Two Scoops of Django 3.x is the best ice cream-themed Django reference in the universe! + +### PyUp + +

+ +

+ +PyUp brings you automated security and dependency updates used by Google and other organizations. Free for open source projects! + +## Usage + +Let's pretend you want to create a Django project called "redditclone". Rather than using `startproject` +and then editing the results to include your name, email, and various configuration issues that always get forgotten until the worst possible moment, get [cookiecutter](https://github.com/cookiecutter/cookiecutter) to do all the work. + +First, get Cookiecutter. Trust me, it's awesome: + + $ pip install "cookiecutter>=1.7.0" + +Now run it against this repo: + + $ cookiecutter https://github.com/cookiecutter/cookiecutter-django + +You'll be prompted for some values. Provide them, then a Django project will be created for you. + +**Warning**: After this point, change 'Daniel Greenfeld', 'pydanny', etc to your own information. + +Answer the prompts with your own desired [options](http://cookiecutter-django.readthedocs.io/en/latest/project-generation-options.html). For example: + + Cloning into 'cookiecutter-django'... + remote: Counting objects: 550, done. + remote: Compressing objects: 100% (310/310), done. + remote: Total 550 (delta 283), reused 479 (delta 222) + Receiving objects: 100% (550/550), 127.66 KiB | 58 KiB/s, done. + Resolving deltas: 100% (283/283), done. + project_name [My Awesome Project]: Reddit Clone + project_slug [reddit_clone]: reddit + description [Behold My Awesome Project!]: A reddit clone. + author_name [Daniel Roy Greenfeld]: Daniel Greenfeld + domain_name [example.com]: myreddit.com + email [daniel-greenfeld@example.com]: pydanny@gmail.com + version [0.1.0]: 0.0.1 + Select open_source_license: + 1 - MIT + 2 - BSD + 3 - GPLv3 + 4 - Apache Software License 2.0 + 5 - Not open source + Choose from 1, 2, 3, 4, 5 [1]: 1 + timezone [UTC]: America/Los_Angeles + windows [n]: n + use_pycharm [n]: y + use_docker [n]: n + Select postgresql_version: + 1 - 14 + 2 - 13 + 3 - 12 + 4 - 11 + 5 - 10 + Choose from 1, 2, 3, 4, 5 [1]: 1 + Select cloud_provider: + 1 - AWS + 2 - GCP + 3 - None + Choose from 1, 2, 3 [1]: 1 + Select mail_service: + 1 - Mailgun + 2 - Amazon SES + 3 - Mailjet + 4 - Mandrill + 5 - Postmark + 6 - Sendgrid + 7 - SendinBlue + 8 - SparkPost + 9 - Other SMTP + Choose from 1, 2, 3, 4, 5, 6, 7, 8, 9 [1]: 1 + use_async [n]: n + use_drf [n]: y + Select frontend_pipeline: + 1 - None + 2 - Django Compressor + 3 - Gulp + Choose from 1, 2, 3, 4 [1]: 1 + use_celery [n]: y + use_mailhog [n]: n + use_sentry [n]: y + use_whitenoise [n]: n + use_heroku [n]: y + Select ci_tool: + 1 - None + 2 - Travis + 3 - Gitlab + 4 - Github + Choose from 1, 2, 3, 4 [1]: 4 + keep_local_envs_in_vcs [y]: y + debug [n]: n + +Enter the project and take a look around: + + $ cd reddit/ + $ ls + +Create a git repo and push it there: + + $ git init + $ git add . + $ git commit -m "first awesome commit" + $ git remote add origin git@github.com:pydanny/redditclone.git + $ git push -u origin master + +Now take a look at your repo. Don't forget to carefully look at the generated README. Awesome, right? + +For local development, see the following: + +- [Developing locally](http://cookiecutter-django.readthedocs.io/en/latest/developing-locally.html) +- [Developing locally using docker](http://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html) + +## Community + +- Have questions? **Before you ask questions anywhere else**, please post your question on [Stack Overflow](http://stackoverflow.com/questions/tagged/cookiecutter-django) under the *cookiecutter-django* tag. We check there periodically for questions. +- If you think you found a bug or want to request a feature, please open an [issue](https://github.com/cookiecutter/cookiecutter-django/issues). +- For anything else, you can chat with us on [Discord](https://discord.gg/uFXweDQc5a). + +## For Readers of Two Scoops of Django + +You may notice that some elements of this project do not exactly match what we describe in chapter 3. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored. + +## For PyUp Users + +If you are using [PyUp](https://pyup.io) to keep your dependencies updated and secure, use the code *cookiecutter* during checkout to get 15% off every month. + +## "Your Stuff" + +Scattered throughout the Python and HTML of this project are places marked with "your stuff". This is where third-party libraries are to be integrated with your project. + +## For MySQL users +To get full MySQL support in addition to the default Postgresql, you can use this fork of the cookiecutter-django: +https://github.com/mabdullahadeel/cookiecutter-django-mysql + +## Releases + +Need a stable release? You can find them at + +## Not Exactly What You Want? + +This is what I want. *It might not be what you want.* Don't worry, you have options: + +### Fork This + +If you have differences in your preferred setup, I encourage you to fork this to create your own version. +Once you have your fork working, let me know and I'll add it to a '*Similar Cookiecutter Templates*' list here. +It's up to you whether to rename your fork. + +If you do rename your fork, I encourage you to submit it to the following places: + +- [cookiecutter](https://github.com/cookiecutter/cookiecutter) so it gets listed in the README as a template. +- The cookiecutter [grid](https://www.djangopackages.com/grids/g/cookiecutters/) on Django Packages. + +### Submit a Pull Request + +We accept pull requests if they're small, atomic, and make our own project development +experience better. + +## Articles + +- [Cookiecutter Django With Amazon RDS](https://haseeburrehman.com/posts/cookiecutter-django-with-amazon-rds/) - Apr, 2, 2021 +- [Using cookiecutter-django with Google Cloud Storage](https://ahhda.github.io/cloud/gce/django/2019/03/12/using-django-cookiecutter-cloud-storage.html) - Mar. 12, 2019 +- [cookiecutter-django with Nginx, Route 53 and ELB](https://msaizar.com/blog/cookiecutter-django-nginx-route-53-and-elb/) - Feb. 12, 2018 +- [cookiecutter-django and Amazon RDS](https://msaizar.com/blog/cookiecutter-django-and-amazon-rds/) - Feb. 7, 2018 +- [Using Cookiecutter to Jumpstart a Django Project on Windows with PyCharm](https://joshuahunter.com/posts/using-cookiecutter-to-jumpstart-a-django-project-on-windows-with-pycharm/) - May 19, 2017 +- [Exploring with Cookiecutter](http://www.snowboardingcoder.com/django/2016/12/03/exploring-with-cookiecutter/) - Dec. 3, 2016 +- [Introduction to Cookiecutter-Django](http://krzysztofzuraw.com/blog/2016/django-cookiecutter.html) - Feb. 19, 2016 +- [Django and GitLab - Running Continuous Integration and tests with your FREE account](http://dezoito.github.io/2016/05/11/django-gitlab-continuous-integration-phantomjs.html) - May. 11, 2016 +- [Development and Deployment of Cookiecutter-Django on Fedora](https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-on-fedora/) - Jan. 18, 2016 +- [Development and Deployment of Cookiecutter-Django via Docker](https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-via-docker/) - Dec. 29, 2015 +- [How to create a Django Application using Cookiecutter and Django 1.8](https://www.swapps.io/blog/how-to-create-a-django-application-using-cookiecutter-and-django-1-8/) - Sept. 12, 2015 + +Have a blog or online publication? Write about your cookiecutter-django tips and tricks, then send us a pull request with the link. diff --git a/README.rst b/README.rst deleted file mode 100644 index 14dcf20781..0000000000 --- a/README.rst +++ /dev/null @@ -1,244 +0,0 @@ -Cookiecutter Django -======================= - -.. image:: https://requires.io/github/pydanny/cookiecutter-django/requirements.svg?branch=master - :target: https://requires.io/github/pydanny/cookiecutter-django/requirements/?branch=master - :alt: Requirements Status - -.. image:: https://travis-ci.org/pydanny/cookiecutter-django.svg?branch=master - :target: https://travis-ci.org/pydanny/cookiecutter-django?branch=master - :alt: Build Status - -.. image:: https://badges.gitter.im/Join Chat.svg - :target: https://gitter.im/pydanny/cookiecutter-django?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge - -Powered by Cookiecutter_, Cookiecutter Django is a framework for jumpstarting production-ready Django projects quickly. - -.. _cookiecutter: https://github.com/audreyr/cookiecutter - -**Warning**: if you get the error "jinja2.exceptions.TemplateSyntaxError: Encountered unknown tag 'now'." , please upgrade your cookiecutter version to >= 1.4 (see issue # 528_ ) - -.. _528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373 - -Features ---------- - -* For Django 1.9 -* Renders Django projects with 100% starting test coverage -* Twitter Bootstrap_ v4.0.0 - alpha_ -* AngularJS_ -* 12-Factor_ based settings via django-environ_ -* Optimized development and production settings -* Registration via django-allauth_ -* Comes with custom user model ready to go. -* Grunt build for compass and livereload -* Send emails via Anymail_ (using Mailgun_ by default, but switchable) -* Media storage using Amazon S3 -* Docker support using docker-compose_ for development and production -* Procfile_ for deploying to Heroku -* Instructions for deploying to PythonAnywhere_ -* Works with Python 2.7.x or 3.5.x -* Run tests with unittest or py.test - - -Optional Integrations ---------------------- - -*These features can be enabled during initial project setup.* - -* Serve static files from Amazon S3 or Whitenoise_ -* Configuration for Celery_ -* Integration with MailHog_ for local email testing -* Integration with Sentry_ for error logging -* Integration with NewRelic_ for performance monitoring -* Integration with Opbeat_ for performance monitoring - -.. _alpha: http://blog.getbootstrap.com/2015/08/19/bootstrap-4-alpha/ -.. _Bootstrap: https://github.com/twbs/bootstrap -.. _AngularJS: https://github.com/angular/angular.js -.. _django-environ: https://github.com/joke2k/django-environ -.. _12-Factor: http://12factor.net/ -.. _django-allauth: https://github.com/pennersr/django-allauth -.. _django-avatar: https://github.com/jezdez/django-avatar/ -.. _Procfile: https://devcenter.heroku.com/articles/procfile -.. _Mailgun: https://mailgun.com/ -.. _Whitenoise: https://whitenoise.readthedocs.io/ -.. _Celery: http://www.celeryproject.org/ -.. _Anymail: https://github.com/anymail/django-anymail -.. _MailHog: https://github.com/mailhog/MailHog -.. _Sentry: https://getsentry.com -.. _NewRelic: https://newrelic.com -.. _docker-compose: https://www.github.com/docker/compose -.. _Opbeat: https://opbeat.com/ -.. _PythonAnywhere: https://www.pythonanywhere.com/ - - -Constraints ------------ - -* Only maintained 3rd party libraries are used. -* PostgreSQL everywhere (9.0+) -* Environment variables for configuration (This won't work with Apache/mod_wsgi). - - -Usage ------- - -Let's pretend you want to create a Django project called "redditclone". Rather than using `startproject` -and then editing the results to include your name, email, and various configuration issues that always get forgotten until the worst possible moment, get cookiecutter_ to do all the work. - -First, get Cookiecutter. Trust me, it's awesome:: - - $ pip install cookiecutter - -Now run it against this repo:: - - $ cookiecutter https://github.com/pydanny/cookiecutter-django - -You'll be prompted for some values. Provide them, then a Django project will be created for you. - -**Warning**: After this point, change 'Daniel Greenfeld', 'pydanny', etc to your own information. - -**Warning**: project_slug must be a valid Python module name or you will have issues on imports. - -Answer the prompts with your own desired options_. For example:: - - Cloning into 'cookiecutter-django'... - remote: Counting objects: 550, done. - remote: Compressing objects: 100% (310/310), done. - remote: Total 550 (delta 283), reused 479 (delta 222) - Receiving objects: 100% (550/550), 127.66 KiB | 58 KiB/s, done. - Resolving deltas: 100% (283/283), done. - project_name [project_name]: Reddit Clone - project_slug [Reddit_Clone]: reddit - author_name [Your Name]: Daniel Roy Greenfeld - email [Your email]: pydanny@gmail.com - description [A short description of the project.]: A reddit clone. - domain_name [example.com]: myreddit.com - version [0.1.0]: 0.0.1 - timezone [UTC]: America/Los_Angeles - now [2016/03/01]: 2016/03/05 - year [2016]: - use_whitenoise [y]: n - use_celery [n]: y - use_mailhog [n]: n - use_sentry [n]: y - use_newrelic [n]: y - use_opbeat [n]: y - use_pycharm [n]: y - windows [n]: n - use_python2 [n]: n - use_docker [y]: y - use_heroku [n]: n - use_grunt [n]: y - use_angular [n]: n - Select open_source_license: - 1 - MIT - 2 - BSD - 3 - Not open source - Choose from 1, 2, 3 [1]: 1 - -Enter the project and take a look around:: - - $ cd reddit/ - $ ls - -Create a git repo and push it there:: - - $ git init - $ git add . - $ git commit -m "first awesome commit" - $ git remote add origin git@github.com:pydanny/redditclone.git - $ git push -u origin master - -Now take a look at your repo. Don't forget to carefully look at the generated README. Awesome, right? - -For development, see the following for local development: - -* `Developing locally`_ -* `Developing locally using docker`_ - -.. _options: http://cookiecutter-django.readthedocs.io/en/latest/project-generation-options.html -.. _`Developing locally`: http://cookiecutter-django.readthedocs.io/en/latest/developing-locally.html -.. _`Developing locally using docker`: http://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html - -Community ------------ - -* Got questions? **Before you ask questions anywhere else**, please post your question on `Stack Overflow`_ under the *cookiecutter-django* tag. We check there periodically for questions. -* If you think you found a bug or want to request a feature, please open an issue_. -* For anything else, you can chat with us on `Gitter`_. - -.. _`Stack Overflow`: http://stackoverflow.com/questions/tagged/cookiecutter-django -.. _`issue`: https://github.com/pydanny/cookiecutter-django/issues -.. _`Gitter`: https://gitter.im/pydanny/cookiecutter-django?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge - -For Readers of Two Scoops of Django 1.8 --------------------------------------------- - -You may notice that some elements of this project do not exactly match what we describe in chapter 3. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored. - -"Your Stuff" -------------- - -Scattered throughout the Python and HTML of this project are places marked with "your stuff". This is where third-party libraries are to be integrated with your project. - -Releases --------- - -Want a stable release? You can find them at https://github.com/pydanny/cookiecutter-django/releases - - -Not Exactly What You Want? ---------------------------- - -This is what I want. *It might not be what you want.* Don't worry, you have options: - -Fork This -~~~~~~~~~~ - -If you have differences in your preferred setup, I encourage you to fork this to create your own version. -Once you have your fork working, let me know and I'll add it to a '*Similar Cookiecutter Templates*' list here. -It's up to you whether or not to rename your fork. - -If you do rename your fork, I encourage you to submit it to the following places: - -* cookiecutter_ so it gets listed in the README as a template. -* The cookiecutter grid_ on Django Packages. - -.. _cookiecutter: https://github.com/audreyr/cookiecutter -.. _grid: https://www.djangopackages.com/grids/g/cookiecutters/ - -Or Submit a Pull Request -~~~~~~~~~~~~~~~~~~~~~~~~~ - -We also accept pull requests on this, if they're small, atomic, and if they make our own project development -experience better. - -Articles ---------- - -* `Development and Deployment of Cookiecutter-Django on Fedora`_ - Jan. 18, 2016 -* `Development and Deployment of Cookiecutter-Django via Docker`_ - Dec. 29, 2015 -* `How to create a Django Application using Cookiecutter and Django 1.8`_ - Sept. 12, 2015 -* `Introduction to Cookiecutter-Django`_ - Feb. 19, 2016 -* `Django and GitLab - Running Continuous Integration and tests with your FREE account`_ - May. 11, 2016 - -Got a blog or online publication? Write about your cookiecutter-django tips and tricks, then send us a pull request with the link. - -.. _`Development and Deployment of Cookiecutter-Django via Docker`: https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-via-docker -.. _`Development and Deployment of Cookiecutter-Django on Fedora`: https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-on-fedora/ -.. _`How to create a Django Application using Cookiecutter and Django 1.8`: http://blog.swapps.co/how-to-create-a-django-application-using-cookiecutter-and-django-1-8/#.VxKfBpMrKRs -.. _`Introduction to Cookiecutter-Django`: http://krzysztofzuraw.com/blog/2016/django-cookiecutter.html -.. _`Django and GitLab - Running Continuous Integration and tests with your FREE account`: http://dezoito.github.io/2016/05/11/django-gitlab-continuous-integration-phantomjs.html - -Support This Project ---------------------------- - -This project is maintained by volunteers. Support their efforts by spreading the word about: - -.. image:: https://s3.amazonaws.com/tsacademy/images/tsa-logo-250x60-transparent-01.png - :name: Two Scoops Academy - :align: center - :alt: Two Scoops Academy - :target: http://www.twoscoops.academy/ diff --git a/cookiecutter.json b/cookiecutter.json index 8fe7010a2d..90d084b3ac 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -1,26 +1,63 @@ { - "project_name": "project_name", - "project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_') }}", - "author_name": "Your Name", - "email": "Your email", - "description": "A short description of the project.", - "domain_name": "example.com", - "version": "0.1.0", - "timezone": "UTC", - "now": "{% now 'local' %}", - "year": "{{ cookiecutter.now[:4] }}", - "use_whitenoise": "y", - "use_celery": "n", - "use_mailhog": "n", - "use_sentry": "n", - "use_newrelic": "n", - "use_opbeat": "n", - "use_pycharm": "n", - "windows": "n", - "use_python2": "n", - "use_docker": "y", - "use_heroku": "n", - "use_grunt": "n", - "use_angular": "n", - "open_source_license": ["MIT", "BSD", "Not open source"] + "project_name": "My Awesome Project", + "project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_')|replace('.', '_')|trim() }}", + "description": "Behold My Awesome Project!", + "author_name": "Daniel Roy Greenfeld", + "domain_name": "example.com", + "email": "{{ cookiecutter.author_name.lower()|replace(' ', '-') }}@example.com", + "version": "0.1.0", + "open_source_license": [ + "MIT", + "BSD", + "GPLv3", + "Apache Software License 2.0", + "Not open source" + ], + "timezone": "UTC", + "windows": "n", + "use_pycharm": "n", + "use_docker": "n", + "postgresql_version": [ + "14", + "13", + "12", + "11", + "10" + ], + "cloud_provider": [ + "AWS", + "GCP", + "None" + ], + "mail_service": [ + "Mailgun", + "Amazon SES", + "Mailjet", + "Mandrill", + "Postmark", + "Sendgrid", + "SendinBlue", + "SparkPost", + "Other SMTP" + ], + "use_async": "n", + "use_drf": "n", + "frontend_pipeline": [ + "None", + "Django Compressor", + "Gulp" + ], + "use_celery": "n", + "use_mailhog": "n", + "use_sentry": "n", + "use_whitenoise": "n", + "use_heroku": "n", + "ci_tool": [ + "None", + "Travis", + "Gitlab", + "Github" + ], + "keep_local_envs_in_vcs": "y", + "debug": "n" } diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/__init__.py b/docs/_static/.gitkeep similarity index 100% rename from {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/__init__.py rename to docs/_static/.gitkeep diff --git a/docs/conf.py b/docs/conf.py index 2fdeb69e64..b53e6a7e78 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # cookiecutter-django documentation build configuration file. # # This file is execfile()d with the current directory set to its containing dir. @@ -9,12 +7,7 @@ # # All configuration values have a default; values that are commented out # serve to show the default. - -from __future__ import unicode_literals - from datetime import datetime -import os -import sys now = datetime.now() @@ -33,29 +26,29 @@ extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'Cookiecutter Django' -copyright = '2013-{}, Daniel Roy Greenfeld'.format(now.year) +project = "Cookiecutter Django" +copyright = f"2013-{now.year}, Daniel Roy Greenfeld" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '{}.{}.{}'.format(*now.isocalendar()) +version = "{}.{}.{}".format(*now.isocalendar()) # The full version, including alpha/beta/rc tags. -release = '{}.{}.{}'.format(*now.isocalendar()) +release = "{}.{}.{}".format(*now.isocalendar()) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -69,7 +62,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None @@ -86,7 +79,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -96,7 +89,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -125,7 +118,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. @@ -169,7 +162,7 @@ # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'cookiecutter-djangodoc' +htmlhelp_basename = "cookiecutter-djangodoc" # -- Options for LaTeX output -------------------------------------------------- @@ -177,10 +170,8 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # 'preamble': '', } @@ -188,10 +179,13 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', - 'cookiecutter-django.tex', - 'cookiecutter-django Documentation', - 'cookiecutter-django', 'manual'), + ( + "index", + "cookiecutter-django.tex", + "cookiecutter-django Documentation", + "cookiecutter-django", + "manual", + ) ] # The name of an image file (relative to this directory) to place at the top of @@ -220,8 +214,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'Cookiecutter Django', 'Cookiecutter Django documentation', - ['Daniel Roy Greenfeld'], 1) + ( + "index", + "Cookiecutter Django", + "Cookiecutter Django documentation", + ["Daniel Roy Greenfeld"], + 1, + ) ] # If true, show URL addresses after external links. @@ -234,9 +233,16 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'Cookiecutter Django', 'Cookiecutter Django documentation', - 'Daniel Roy Greenfeld', 'Cookiecutter Django', - 'A Cookiecutter template for creating production-ready Django projects quickly.', 'Miscellaneous'), + ( + "index", + "Cookiecutter Django", + "Cookiecutter Django documentation", + "Daniel Roy Greenfeld", + "Cookiecutter Django", + "A Cookiecutter template for creating production-ready " + "Django projects quickly.", + "Miscellaneous", + ) ] # Documents to append as an appendix to all manuals. diff --git a/docs/deployment-on-heroku.rst b/docs/deployment-on-heroku.rst index 82bdbace4a..e239b65935 100644 --- a/docs/deployment-on-heroku.rst +++ b/docs/deployment-on-heroku.rst @@ -3,36 +3,126 @@ Deployment on Heroku .. index:: Heroku -You can either push the 'deploy' button in your generated README.rst or run these commands to deploy the project to Heroku: +Script +------ + +Run these commands to deploy the project to Heroku: .. code-block:: bash - heroku create --buildpack https://github.com/heroku/heroku-buildpack-python + heroku create --buildpack heroku/python heroku addons:create heroku-postgresql:hobby-dev + # On Windows use double quotes for the time zone, e.g. + # heroku pg:backups schedule --at "02:00 America/Los_Angeles" DATABASE_URL heroku pg:backups schedule --at '02:00 America/Los_Angeles' DATABASE_URL heroku pg:promote DATABASE_URL heroku addons:create heroku-redis:hobby-dev - heroku addons:create mailgun - heroku config:set DJANGO_ADMIN_URL=`openssl rand -base64 32` - heroku config:set DJANGO_SECRET_KEY=`openssl rand -base64 64` - heroku config:set DJANGO_SETTINGS_MODULE='config.settings.production' - heroku config:set DJANGO_ALLOWED_HOSTS='.herokuapp.com' + # Assuming you chose Mailgun as mail service (see below for others) + heroku addons:create mailgun:starter - heroku config:set DJANGO_AWS_ACCESS_KEY_ID=YOUR_AWS_ID_HERE - heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY_HERE - heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME=YOUR_AWS_S3_BUCKET_NAME_HERE + heroku config:set PYTHONHASHSEED=random - heroku config:set DJANGO_MAILGUN_SERVER_NAME=YOUR_MALGUN_SERVER - heroku config:set DJANGO_MAILGUN_API_KEY=YOUR_MAILGUN_API_KEY + heroku config:set WEB_CONCURRENCY=4 - heroku config:set PYTHONHASHSEED=random - heroku config:set DJANGO_ADMIN_URL=\^somelocation/ + 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 similar characters "IOl01": + heroku config:set DJANGO_ADMIN_URL="$(openssl rand -base64 4096 | tr -dc 'A-HJ-NP-Za-km-z2-9' | head -c 32)/" + + # 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 - heroku run python manage.py migrate - heroku run python manage.py check --deploy + heroku run python manage.py createsuperuser + + heroku run python manage.py check --deploy + heroku open + +Notes +----- + +Email Service ++++++++++++++ + +The script above assumes that you've chose Mailgun as email service. If you want to use another one, check the `documentation for django-anymail `_ to know which environment variables to set. Heroku provides other `add-ons for emails `_ (e.g. Sendgrid) which can be configured with a similar one line command. + +.. warning:: + + .. include:: mailgun.rst + +Heroku & Docker ++++++++++++++++ + +Although Heroku has some sort of `Docker support`_, it's not supported by cookiecutter-django. +We invite you to follow Heroku documentation about it. + +.. _Docker support: https://devcenter.heroku.com/articles/build-docker-images-heroku-yml + +Optional actions +---------------- + +Celery +++++++ + +Celery requires a few extra environment variables to be ready operational. Also, the worker is created, +it's in the ``Procfile``, but is turned off by default: + +.. code-block:: bash + + # Set the broker URL to Redis + heroku config:set CELERY_BROKER_URL=`heroku config:get REDIS_URL` + # Scale dyno to 1 instance + heroku ps:scale worker=1 + +Sentry +++++++ + +If you're opted for Sentry error tracking, you can either install it through the `Sentry add-on`_: + +.. code-block:: bash + + heroku addons:create sentry:f1 + + +Or add the DSN for your account, if you already have one: + +.. code-block:: bash + + heroku config:set SENTRY_DSN=https://xxxx@sentry.io/12345 + +.. _Sentry add-on: https://elements.heroku.com/addons/sentry + + +Gulp & Bootstrap compilation +++++++++++++++++++++++++++++ + +If you've opted for Gulp, you'll most likely need to setup +your app to use `multiple buildpacks`_: one for Python & one for Node.js: + +.. code-block:: bash + + heroku buildpacks:add --index 1 heroku/nodejs + +At time of writing, this should do the trick: during deployment, +the Heroku should run ``npm install`` and then ``npm build``, +which runs Gulp in cookiecutter-django. + +If things don't work, please refer to the Heroku docs. + +.. _multiple buildpacks: https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app \ No newline at end of file diff --git a/docs/deployment-on-pythonanywhere.rst b/docs/deployment-on-pythonanywhere.rst index 6aacba1543..7984d7b5e0 100644 --- a/docs/deployment-on-pythonanywhere.rst +++ b/docs/deployment-on-pythonanywhere.rst @@ -15,7 +15,7 @@ Full instructions follow, but here's a high-level view. 2. Set your config variables in the *postactivate* script -3. Run the *manage.py* ``migrate`` and ``collectstatic`` commands +3. Run the *manage.py* ``migrate`` and ``collectstatic`` commands. If you've opted for django-compressor, also run ``compress`` 4. Add an entry to the PythonAnywhere *Web tab* @@ -25,21 +25,19 @@ Full instructions follow, but here's a high-level view. Once you've been through this one-off config, future deployments are much simpler: just ``git pull`` and then hit the "Reload" button :) - Getting your code and dependencies installed on PythonAnywhere -------------------------------------------------------------- -Make sure your project is fully commited and pushed up to Bitbucket or Github or wherever it may be. Then, log into your PythonAnywhere account, open up a **Bash** console, clone your repo, and create a virtualenv: +Make sure your project is fully committed and pushed up to Bitbucket or Github or wherever it may be. Then, log into your PythonAnywhere account, open up a **Bash** console, clone your repo, and create a virtualenv: .. code-block:: bash git clone # you can also use hg cd my-project-name - mkvirtualenv --python=/usr/bin/python3.5 my-project-name # or python2.7, etc + mkvirtualenv --python=/usr/bin/python3.10 my-project-name pip install -r requirements/production.txt # may take a few minutes - Setting environment variables in the console -------------------------------------------- @@ -47,7 +45,7 @@ Generate a secret key for yourself, eg like this: .. code-block:: bash - python -c 'import random; print("".join(random.SystemRandom().choice("abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)") for _ in range(50)))' + python -c 'import random;import string; print("".join(random.SystemRandom().choice(string.digits + string.ascii_letters + string.punctuation) for _ in range(50)))' Make a note of it, since we'll need it here in the console and later on in the web app config tab. @@ -57,34 +55,36 @@ Set environment variables via the virtualenv "postactivate" script (this will se vi $VIRTUAL_ENV/bin/postactivate -**TIP:** *If you don't like vi, you can also edit this file via the PythonAnywhere "Files" menu; look in the ".virtualenvs" folder*. +.. note:: If you don't like vi, you can also edit this file via the PythonAnywhere "Files" menu; look in the ".virtualenvs" folder. Add these exports .. code-block:: bash + export WEB_CONCURRENCY=4 export DJANGO_SETTINGS_MODULE='config.settings.production' export DJANGO_SECRET_KEY='' export DJANGO_ALLOWED_HOSTS='' export DJANGO_ADMIN_URL='' - export DJANGO_MAILGUN_API_KEY='' - export DJANGO_MAILGUN_SERVER_NAME='' + export MAILGUN_API_KEY='' + export MAILGUN_DOMAIN='' export DJANGO_AWS_ACCESS_KEY_ID= export DJANGO_AWS_SECRET_ACCESS_KEY= export DJANGO_AWS_STORAGE_BUCKET_NAME= - export DATABASE_URL='' + export DATABASE_URL='' + export REDIS_URL='' -**NOTE:** *The AWS details are not required if you're using whitenoise or the built-in pythonanywhere static files service, but you do need to set them to blank, as above.* +.. note:: The AWS details are not required if you're using whitenoise or the built-in pythonanywhere static files service, but you do need to set them to blank, as above. -Database setup: ---------------- +Database setup +-------------- Go to the PythonAnywhere **Databases tab** and configure your database. -* For Postgres, setup your superuser password, then open a Postgres console and run a `CREATE DATABASE my-db-name`. You should probably also set up a specific role and permissions for your app, rather than using the superuser credentials. Make a note of the address and port of your postgres server. +* For Postgres, setup your superuser password, then open a Postgres console and run a ``CREATE DATABASE my-db-name``. You should probably also set up a specific role and permissions for your app, rather than using the superuser credentials. Make a note of the address and port of your postgres server. -* For MySQL, set the password and create a database. More info here: https://help.pythonanywhere.com/pages/UsingMySQL +* For MySQL, set the password and create a database. More info here: https://help.pythonanywhere.com/pages/UsingMySQL * You can also use sqlite if you like! Not recommended for anything beyond toy projects though. @@ -108,17 +108,26 @@ Now run the migration, and collectstatic: source $VIRTUAL_ENV/bin/postactivate python manage.py migrate python manage.py collectstatic + # if using django-compressor: + python manage.py compress # and, optionally python manage.py createsuperuser +Redis +----- + +PythonAnywhere does NOT `offer a built-in solution `_ for Redis, however the production setup from Cookiecutter Django uses Redis as cache and requires one. + +We recommend to signup to a separate service offering hosted Redis (e.g. `Redislab `_) and use the URL they provide. + Configure the PythonAnywhere Web Tab ------------------------------------ Go to the PythonAnywhere **Web tab**, hit **Add new web app**, and choose **Manual Config**, and then the version of Python you used for your virtualenv. -**NOTE:** *If you're using a custom domain (not on \*.pythonanywhere.com), then you'll need to set up a CNAME with your domain registrar.* +.. note:: If you're using a custom domain (not on \*.pythonanywhere.com), then you'll need to set up a CNAME with your domain registrar. When you're redirected back to the web app config screen, set the **path to your virtualenv**. If you used virtualenvwrapper as above, you can just enter its name. @@ -137,8 +146,8 @@ Click through to the **WSGI configuration file** link (near the top) and edit th os.environ['DJANGO_SECRET_KEY'] = '' os.environ['DJANGO_ALLOWED_HOSTS'] = '' os.environ['DJANGO_ADMIN_URL'] = '' - os.environ['DJANGO_MAILGUN_API_KEY'] = '' - os.environ['DJANGO_MAILGUN_SERVER_NAME'] = '' + os.environ['MAILGUN_API_KEY'] = '' + os.environ['MAILGUN_DOMAIN'] = '' os.environ['DJANGO_AWS_ACCESS_KEY_ID'] = '' os.environ['DJANGO_AWS_SECRET_ACCESS_KEY'] = '' os.environ['DJANGO_AWS_STORAGE_BUCKET_NAME'] = '' @@ -151,15 +160,14 @@ Click through to the **WSGI configuration file** link (near the top) and edit th Back on the Web tab, hit **Reload**, and your app should be live! -**NOTE:** *you may see security warnings until you set up your SSL certificates. If you -want to supress them temporarily, set DJANGO_SECURE_SSL_REDIRECT to blank. Follow -the instructions here to get SSL set up: https://help.pythonanywhere.com/pages/SSLOwnDomains/* +.. note:: You may see security warnings until you set up your SSL certificates. If you want to suppress them temporarily, set ``DJANGO_SECURE_SSL_REDIRECT`` to blank. Follow `these instructions `_ to get SSL set up. + Optional: static files ---------------------- -If you want to use the PythonAnywhere static files service instead of using whitenoise or S3, you'll find its configuration section on the Web tab. Essentially you'll need an entry to match your ``STATIC_URL`` and ``STATIC_ROOT`` settings. There's more info here: https://help.pythonanywhere.com/pages/DjangoStaticFiles +If you want to use the PythonAnywhere static files service instead of using whitenoise or S3, you'll find its configuration section on the Web tab. Essentially you'll need an entry to match your ``STATIC_URL`` and ``STATIC_ROOT`` settings. There's more info `in this article `_. Future deployments @@ -174,9 +182,9 @@ For subsequent deployments, the procedure is much simpler. In a Bash console: git pull python manage.py migrate python manage.py collectstatic + # if using django-compressor: + python manage.py compress And then go to the Web tab and hit **Reload** -**TIP:** *if you're really keen, you can set up git-push based deployments: https://blog.pythonanywhere.com/87/* - - +.. note:: If you're really keen, you can set up git-push based deployments: https://blog.pythonanywhere.com/87/ diff --git a/docs/deployment-with-docker.rst b/docs/deployment-with-docker.rst index de3abf7d46..fcce7e6f52 100644 --- a/docs/deployment-with-docker.rst +++ b/docs/deployment-with-docker.rst @@ -1,112 +1,163 @@ Deployment with Docker -================================================= +====================== -.. index:: Docker, deployment +.. index:: deployment, docker, docker-compose, compose -TODO: Review and revise -**Warning** +Prerequisites +------------- -Docker is evolving extremely fast, but it has still some rough edges here and there. Compose is currently (as of version 1.4) -not considered production ready. That means you won't be able to scale to multiple servers and you won't be able to run -zero downtime deployments out of the box. Consider all this as experimental until you understand all the implications -to run docker (with compose) on production. +* Docker 17.05+. +* Docker Compose 1.17+ -**Run your app with docker-compose** -Prerequisites: +Understanding the Docker Compose Setup +-------------------------------------- -* docker (at least 1.10) -* docker-compose (at least 1.6) +Before you begin, check out the ``production.yml`` file in the root of this project. Keep note of how it provides configuration for the following services: -Before you start, check out the `docker-compose.yml` file in the root of this project. This is where each component -of this application gets its configuration from. It consists of a `postgres` service that runs the database, `redis` -for caching, `nginx` as reverse proxy and last but not least the `django` application run by gunicorn. -{% if cookiecutter.use_celery == 'y' -%} -Since this application also runs Celery, there are two more services with a service called `celeryworker` that runs the -celery worker process and `celerybeat` that runs the celery beat process. -{% endif %} +* ``django``: your application running behind ``Gunicorn``; +* ``postgres``: PostgreSQL database with the application's relational data; +* ``redis``: Redis instance for caching; +* ``traefik``: Traefik reverse proxy with HTTPS on by default. +Provided you have opted for Celery (via setting ``use_celery`` to ``y``) there are three more services: -All of these services except `redis` rely on environment variables set by you. There is an `env.example` file in the -root directory of this project as a starting point. Add your own variables to the file and rename it to `.env`. This -file won't be tracked by git by default so you'll have to make sure to use some other mechanism to copy your secret if -you are relying solely on git. +* ``celeryworker`` running a Celery worker process; +* ``celerybeat`` running a Celery beat process; +* ``flower`` running Flower_. +The ``flower`` service is served by Traefik over HTTPS, through the port ``5555``. For more information about Flower and its login credentials, check out :ref:`CeleryFlower` instructions for local environment. -By default, the application is configured to listen on all interfaces on port 80. If you want to change that, open the -`docker-compose.yml` file and replace `0.0.0.0` with your own ip. If you are using `nginx-proxy`_ to run multiple -application stacks on one host, remove the port setting entirely and add `VIRTUAL_HOST={{cookiecutter.domain_name}}` to your env file. -This pass all incoming requests on `nginx-proxy`_ to the nginx service your application is using. +.. _`Flower`: https://github.com/mher/flower -.. _nginx-proxy: https://github.com/jwilder/nginx-proxy -Postgres is saving its database files to the `postgres_data` volume by default. Change that if you wan't -something else and make sure to make backups since this is not done automatically. +Configuring the Stack +--------------------- -To get started, pull your code from source control (don't forget the `.env` file) and change to your projects root -directory. +The majority of services above are configured through the use of environment variables. Just check out :ref:`envs` and you will know the drill. -You'll need to build the stack first. To do that, run:: +To obtain logs and information about crashes in a production setup, make sure that you have access to an external Sentry instance (e.g. by creating an account with `sentry.io`_), and set the ``SENTRY_DSN`` variable. Logs of level `logging.ERROR` are sent as Sentry events. Therefore, in order to send a Sentry event use: - docker-compose build +.. code-block:: python + + import logging + logging.error("This event is sent to Sentry", extra={"": ""}) + +The `extra` parameter allows you to send additional information about the context of this error. + + +You will probably also need to setup the Mail backend, for example by adding a `Mailgun`_ API key and a `Mailgun`_ sender domain, otherwise, the account creation view will crash and result in a 500 error when the backend attempts to send an email to the account owner. + +.. _sentry.io: https://sentry.io/welcome +.. _Mailgun: https://mailgun.com + + +.. warning:: + + .. include:: mailgun.rst + + +Optional: Use AWS IAM Role for EC2 instance +------------------------------------------- + +If you are deploying to AWS, you can use the IAM role to substitute AWS credentials, after which it's safe to remove the ``AWS_ACCESS_KEY_ID`` AND ``AWS_SECRET_ACCESS_KEY`` from ``.envs/.production/.django``. To do it, create an `IAM role`_ and `attach`_ it to the existing EC2 instance or create a new EC2 instance with that role. The role should assume, at minimum, the ``AmazonS3FullAccess`` permission. + +.. _IAM role: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html +.. _attach: https://aws.amazon.com/blogs/security/easily-replace-or-attach-an-iam-role-to-an-existing-ec2-instance-by-using-the-ec2-console/ + + +HTTPS is On by Default +---------------------- + +SSL (Secure Sockets Layer) is a standard security technology for establishing an encrypted link between a server and a client, typically in this case, a web server (website) and a browser. Not having HTTPS means that malicious network users can sniff authentication credentials between your website and end users' browser. + +It is always better to deploy a site behind HTTPS and will become crucial as the web services extend to the IoT (Internet of Things). For this reason, we have set up a number of security defaults to help make your website secure: + +* If you are not using a subdomain of the domain name set in the project, then remember to put your staging/production IP address in the ``DJANGO_ALLOWED_HOSTS`` environment variable (see :ref:`settings`) before you deploy your website. Failure to do this will mean you will not have access to your website through the HTTP protocol. + +* Access to the Django admin is set up by default to require HTTPS in production or once *live*. + +The Traefik reverse proxy used in the default configuration will get you a valid certificate from Lets Encrypt and update it automatically. All you need to do to enable this is to make sure that your DNS records are pointing to the server Traefik runs on. + +You can read more about this feature and how to configure it, at `Automatic HTTPS`_ in the Traefik docs. + +.. _Automatic HTTPS: https://docs.traefik.io/https/acme/ + + +(Optional) Postgres Data Volume Modifications +--------------------------------------------- + +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 +----------------------------------- + +You will need to build the stack first. To do that, run:: + + docker-compose -f production.yml build Once this is ready, you can run it with:: - docker-compose up + docker-compose -f production.yml up + +To run the stack and detach the containers, run:: + docker-compose -f production.yml up -d To run a migration, open up a second terminal and run:: - docker-compose run django python manage.py migrate + docker-compose -f production.yml run --rm django python manage.py migrate To create a superuser, run:: - docker-compose run django python manage.py createsuperuser - + docker-compose -f production.yml run --rm django python manage.py createsuperuser If you need a shell, run:: - docker-compose run django python manage.py shell + docker-compose -f production.yml run --rm django python manage.py shell -To get an output of all running containers. +To check the logs out, run:: -To check your logs, run:: - - docker-compose logs + docker-compose -f production.yml logs If you want to scale your application, run:: - docker-compose scale django=4 - docker-compose scale celeryworker=2 + docker-compose -f production.yml up --scale django=4 + docker-compose -f production.yml up --scale celeryworker=2 + +.. warning:: don't try to scale ``postgres``, ``celerybeat``, or ``traefik``. +To see how your containers are doing run:: -**Don't run the scale command on postgres or celerybeat** + docker-compose -f production.yml ps -Once you are ready with your initial setup, you wan't to make sure that your application is run by a process manager to + +Example: Supervisor +------------------- + +Once you are ready with your initial setup, you want to make sure that your application is run by a process manager to survive reboots and auto restarts in case of an error. You can use the process manager you are most familiar with. All -it needs to do is to run `docker-compose up` in your projects root directory. +it needs to do is to run ``docker-compose -f production.yml up`` in your projects root directory. -If you are using `supervisor`, you can use this file as a starting point:: +If you are using ``supervisor``, you can use this file as a starting point:: [program:{{cookiecutter.project_slug}}] - command=docker-compose up + command=docker-compose -f production.yml up directory=/path/to/{{cookiecutter.project_slug}} redirect_stderr=true autostart=true autorestart=true priority=10 - -Place it in `/etc/supervisor/conf.d/{{cookiecutter.project_slug}}.conf` and run:: +Move it to ``/etc/supervisor/conf.d/{{cookiecutter.project_slug}}.conf`` and run:: supervisorctl reread + supervisorctl update supervisorctl start {{cookiecutter.project_slug}} -To get the status, run:: +For status check, run:: supervisorctl status -If you have errors, you can always check your stack with `docker-compose`. Switch to your projects root directory and run:: - - docker-compose ps diff --git a/docs/developing-locally-docker.rst b/docs/developing-locally-docker.rst index 03e9221c51..4bb5d90323 100644 --- a/docs/developing-locally-docker.rst +++ b/docs/developing-locally-docker.rst @@ -1,143 +1,304 @@ -Getting Up and Running with Docker -================================== +Getting Up and Running Locally With Docker +========================================== .. index:: Docker The steps below will get you up and running with a local development environment. All of these commands assume you are in the root of your generated project. +.. note:: + + If you're new to Docker, please be aware that some resources are cached system-wide + and might reappear if you generate a project multiple times with the same name (e.g. + :ref:`this issue with Postgres `). + + Prerequisites ------------- -You'll need at least docker 1.10. +* Docker; if you don't have it yet, follow the `installation instructions`_; +* Docker Compose; refer to the official documentation for the `installation guide`_. +* Pre-commit; refer to the official documentation for the `pre-commit`_. -If you don't already have it installed, follow the instructions for your OS: +.. _`installation instructions`: https://docs.docker.com/install/#supported-platforms +.. _`installation guide`: https://docs.docker.com/compose/install/ +.. _`pre-commit`: https://pre-commit.com/#install - - On Mac OS X/Windows, you'll need `Docker Toolbox`_ - - On Linux, you'll need `docker-engine`_ -.. _`Docker Toolbox`: https://github.com/docker/toolbox/releases -.. _`docker-engine`: https://docs.docker.com/engine/installation/ +Build the Stack +--------------- -Create the Machine (Optional) ------------------------------ +This can take a while, especially the first time you run this particular command on your development system:: -On Linux you have native Docker, so you don't need to create a VM with -docker-machine to use it. + $ docker-compose -f local.yml build -However, on Mac/Windows/other systems without native Docker, you'll want to -start by creating a VM with docker-machine:: +Generally, if you want to emulate production environment use ``production.yml`` instead. And this is true for any other actions you might need to perform: whenever a switch is required, just do it! - $ docker-machine create --driver virtualbox dev1 +Before doing any git commit, `pre-commit`_ should be installed globally on your local machine, and then:: -**Note:** If you want to have more than one docker development environment, then -name them accordingly. Instead of 'dev1' you might have 'dev2', 'myproject', -'djangopackages', et al. + $ git init + $ pre-commit install -Get the IP Address ------------------- +Failing to do so will result with a bunch of CI and Linter errors that can be avoided with pre-commit. -Once your machine is up and running, run this:: - $ docker-machine ip dev1 - 123.456.789.012 +Run the Stack +------------- -This is also the IP address where the Django project will be served from. +This brings up both Django and PostgreSQL. The first time it is run it might take a while to get started, but subsequent runs will occur quickly. -Build the Stack ---------------- +Open a terminal at the project root and run the following for local development:: -This can take a while, especially the first time you run this particular command -on your development system:: + $ docker-compose -f local.yml up - $ docker-compose -f dev.yml build +You can also set the environment variable ``COMPOSE_FILE`` pointing to ``local.yml`` like this:: -If you want to build the production environment you don't have to pass an argument -f, it will automatically use docker-compose.yml. + $ export COMPOSE_FILE=local.yml -Boot the System ---------------- +And then run:: -This brings up both Django and PostgreSQL. + $ docker-compose up -The first time it is run it might take a while to get started, but subsequent -runs will occur quickly. +To run in a detached (background) mode, just:: -Open a terminal at the project root and run the following for local development:: + $ docker-compose up -d - $ docker-compose -f dev.yml up -You can also set the environment variable ``COMPOSE_FILE`` pointing to ``dev.yml`` like this:: +Execute Management Commands +--------------------------- - $ export COMPOSE_FILE=dev.yml +As with any shell command that we wish to run in our container, this is done using the ``docker-compose -f local.yml run --rm`` command: :: -And then run:: + $ docker-compose -f local.yml run --rm django python manage.py migrate + $ docker-compose -f local.yml run --rm django python manage.py createsuperuser - $ docker-compose up +Here, ``django`` is the target service we are executing the commands against. -Running management commands -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -As with any shell command that we wish to run in our container, this is done -using the ``docker-compose run`` command. +(Optionally) Designate your Docker Development Server IP +-------------------------------------------------------- -To migrate your app and to create a superuser, run:: +When ``DEBUG`` is set to ``True``, the host is validated against ``['localhost', '127.0.0.1', '[::1]']``. This is adequate when running a ``virtualenv``. For Docker, in the ``config.settings.local``, add your host development server IP to ``INTERNAL_IPS`` or ``ALLOWED_HOSTS`` if the variable exists. - $ docker-compose -f dev.yml run django python manage.py migrate - $ docker-compose -f dev.yml run django python manage.py createsuperuser -Here we specify the ``django`` container as the location to run our management commands. +.. _envs: -Production Mode -~~~~~~~~~~~~~~~ +Configuring the Environment +--------------------------- -Instead of using `dev.yml`, you would use `docker-compose.yml`. +This is the excerpt from your project's ``local.yml``: :: -Database Backups -~~~~~~~~~~~~~~~~ + # ... -The database has to be running to create/restore a backup. + postgres: + build: + context: . + dockerfile: ./compose/production/postgres/Dockerfile + volumes: + - local_postgres_data:/var/lib/postgresql/data + - local_postgres_data_backups:/backups + env_file: + - ./.envs/.local/.postgres -First, run the app with `docker-compose -f dev.yml up`. + # ... -To create a backup, run:: +The most important thing for us here now is ``env_file`` section enlisting ``./.envs/.local/.postgres``. Generally, the stack's behavior is governed by a number of environment variables (`env(s)`, for short) residing in ``envs/``, for instance, this is what we generate for you: :: - docker-compose -f dev.yml run postgres backup + .envs + ├── .local + │   ├── .django + │   └── .postgres + └── .production + ├── .django + └── .postgres +By convention, for any service ``sI`` in environment ``e`` (you know ``someenv`` is an environment when there is a ``someenv.yml`` file in the project root), given ``sI`` requires configuration, a ``.envs/.e/.sI`` `service configuration` file exists. -To list backups, run:: +Consider the aforementioned ``.envs/.local/.postgres``: :: - docker-compose -f dev.yml run postgres list-backups + # PostgreSQL + # ------------------------------------------------------------------------------ + POSTGRES_HOST=postgres + POSTGRES_DB= + POSTGRES_USER=XgOWtQtJecsAbaIyslwGvFvPawftNaqO + POSTGRES_PASSWORD=jSljDz4whHuwO3aJIgVBrqEml5Ycbghorep4uVJ4xjDYQu0LfuTZdctj7y0YcCLu +The three envs we are presented with here are ``POSTGRES_DB``, ``POSTGRES_USER``, and ``POSTGRES_PASSWORD`` (by the way, their values have also been generated for you). You might have figured out already where these definitions will end up; it's all the same with ``django`` service container envs. -To restore a backup, run:: +One final touch: should you ever need to merge ``.envs/.production/*`` in a single ``.env`` run the ``merge_production_dotenvs_in_dotenv.py``: :: - docker-compose -f dev.yml run postgres restore filename.sql + $ python merge_production_dotenvs_in_dotenv.py -To copy the files from the running Postgres container to the host system:: +The ``.env`` file will then be created, with all your production envs residing beside each other. - docker cp :/backups /host/path/target -Where is the ID of the Postgres container. To get it, run:: +Tips & Tricks +------------- - docker ps +Activate a Docker Machine +~~~~~~~~~~~~~~~~~~~~~~~~~ -Other Useful Tips ------------------ +This tells our computer that all future commands are specifically for the dev1 machine. Using the ``eval`` command we can switch machines as needed.:: -Make a machine the active unit -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + $ eval "$(docker-machine env dev1)" -This tells our computer that all future commands are specifically for the dev1 machine. -Using the ``eval`` command we can switch machines as needed. +Debugging +~~~~~~~~~ -:: +ipdb +""""" - $ eval "$(docker-machine env dev1)" +If you are using the following within your code to debug: :: + + import ipdb; ipdb.set_trace() + +Then you may need to run the following for it to work as desired: :: + + $ docker-compose -f local.yml run --rm --service-ports django + + +django-debug-toolbar +"""""""""""""""""""" + +In order for ``django-debug-toolbar`` to work designate your Docker Machine IP with ``INTERNAL_IPS`` in ``local.py``. + + +docker +"""""" + +The ``container_name`` from the yml file can be used to check on containers with docker commands, for example: :: + + $ docker logs _local_celeryworker + $ docker top _local_celeryworker + + +Notice that the ``container_name`` is generated dynamically using your project slug as a prefix + +Mailhog +~~~~~~~ + +When developing locally you can go with MailHog_ for email testing provided ``use_mailhog`` was set to ``y`` on setup. To proceed, + +#. make sure ``_local_mailhog`` container is up and running; + +#. open up ``http://127.0.0.1:8025``. + +.. _Mailhog: https://github.com/mailhog/MailHog/ + +.. _`CeleryTasks`: + +Celery tasks in local development +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +When not using docker Celery tasks are set to run in Eager mode, so that a full stack is not needed. When using docker the task scheduler will be used by default. + +If you need tasks to be executed on the main thread during development set ``CELERY_TASK_ALWAYS_EAGER = True`` in ``config/settings/local.py``. + +Possible uses could be for testing, or ease of profiling with DJDT. + +.. _`CeleryFlower`: -Detached Mode +Celery Flower ~~~~~~~~~~~~~ -If you want to run the stack in detached mode (in the background), use the ``-d`` argument: +`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 + +Developing locally with HTTPS +----------------------------- + +Increasingly it is becoming necessary to develop software in a secure environment in order that there are very few changes when deploying to production. Recently Facebook changed their policies for apps/sites that use Facebook login which requires the use of an HTTPS URL for the OAuth redirect URL. So if you want to use the ``users`` application with a OAuth provider such as Facebook, securing your communication to the local development environment will be necessary. + +In order to create a secure environment, we need to have a trusted SSL certificate installed in our Docker application. + +#. **Let's Encrypt** + + The official line from Let’s Encrypt is: + + [For local development section] ... The best option: Generate your own certificate, either self-signed or signed by a local root, and trust it in your operating system’s trust store. Then use that certificate in your local web server. See below for details. + + See `letsencrypt.org - certificates-for-localhost`_ + + .. _`letsencrypt.org - certificates-for-localhost`: https://letsencrypt.org/docs/certificates-for-localhost/ + +#. **mkcert: Valid Https Certificates For Localhost** + + `mkcert`_ is a simple by design tool that hides all the arcane knowledge required to generate valid TLS certificates. It works for any hostname or IP, including localhost. It supports macOS, Linux, and Windows, and Firefox, Chrome and Java. It even works on mobile devices with a couple manual steps. + + See https://blog.filippo.io/mkcert-valid-https-certificates-for-localhost/ + + .. _`mkcert`: https://github.com/FiloSottile/mkcert/blob/master/README.md#supported-root-stores + +After installing a trusted TLS certificate, configure your docker installation. We are going to configure an ``nginx`` reverse-proxy server. This makes sure that it does not interfere with our ``traefik`` configuration that is reserved for production environments. + +These are the places that you should configure to secure your local environment. + +certs +~~~~~ + +Take the certificates that you generated and place them in a folder called ``certs`` in the project's root folder. Assuming that you registered your local hostname as ``my-dev-env.local``, the certificates you will put in the folder should have the names ``my-dev-env.local.crt`` and ``my-dev-env.local.key``. + +local.yml +~~~~~~~~~ + +#. Add the ``nginx-proxy`` service. :: + + ... + + nginx-proxy: + image: jwilder/nginx-proxy:alpine + container_name: nginx-proxy + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./certs:/etc/nginx/certs + restart: always + depends_on: + - django + + ... + +#. Link the ``nginx-proxy`` to ``django`` through environment variables. + + ``django`` already has an ``.env`` file connected to it. Add the following variables. You should do this especially if you are working with a team and you want to keep your local environment details to yourself. + + :: + + # HTTPS + # ------------------------------------------------------------------------------ + VIRTUAL_HOST=my-dev-env.local + VIRTUAL_PORT=8000 + + The services run behind the reverse proxy. + +config/settings/local.py +~~~~~~~~~~~~~~~~~~~~~~~~ + +You should allow the new hostname. :: + + ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1", "my-dev-env.local"] + +Rebuild your ``docker`` application. :: + + $ docker-compose -f local.yml up -d --build + +Go to your browser and type in your URL bar ``https://my-dev-env.local`` + +See `https with nginx`_ for more information on this configuration. + + .. _`https with nginx`: https://codewithhugo.com/docker-compose-local-https/ + +.gitignore +~~~~~~~~~~ -:: +Add ``certs/*`` to the ``.gitignore`` file. This allows the folder to be included in the repo but its contents to be ignored. - $ docker-compose -f dev.yml up -d +*This configuration is for local development environments only. Do not use this for production since you might expose your local* ``rootCA-key.pem``. diff --git a/docs/developing-locally.rst b/docs/developing-locally.rst index 119856909a..c9d28ff733 100644 --- a/docs/developing-locally.rst +++ b/docs/developing-locally.rst @@ -3,87 +3,178 @@ Getting Up and Running Locally .. index:: pip, virtualenv, PostgreSQL -The steps below will get you up and running with a local development environment. We assume you have the following installed: -* pip -* virtualenv -* PostgreSQL +Setting Up Development Environment +---------------------------------- -First make sure to create and activate a virtualenv_, then open a terminal at the project root and install the os dependencies:: +Make sure to have the following on your host: - $ sudo ./install_os_dependencies.sh install +* Python 3.10 +* PostgreSQL_. +* Redis_, if using Celery +* Cookiecutter_ -Then install the requirements for your local development:: +First things first. +#. Create a virtualenv: :: + + $ python3.10 -m venv + +#. Activate the virtualenv you have just created: :: + + $ source /bin/activate + +#. Install cookiecutter-django: :: + + $ cookiecutter gh:cookiecutter/cookiecutter-django + +#. Install development requirements: :: + + $ cd $ pip install -r requirements/local.txt + $ git init # A git repo is required for pre-commit to install + $ pre-commit install + + .. note:: + + the `pre-commit` hook exists in the generated project as default. + For the details of `pre-commit`, follow the `pre-commit`_ site. + +#. Create a new PostgreSQL database using createdb_: :: -.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ + $ createdb --username=postgres + ``project_slug`` is what you have entered as the project_slug at the setup stage. -Then, create a PostgreSQL database with the following command, where `[project_slug]` is what value you entered for your project's `project_slug`:: + .. note:: - $ createdb [project_slug] + if this is the first time a database is created on your machine you might need an + `initial PostgreSQL set up`_ to allow local connections & set a password for + the ``postgres`` user. The `postgres documentation`_ explains the syntax of the config file + that you need to change. -`Cookiecutter Django` uses the excellent `django-environ`_ package with its ``DATABASE_URL`` environment variable to simplify database configuration in your Django settings. Now all you have to do is rename env.example to .env and then compose a definition for ``DATABASE_URL`` as shown below and add it to the .env file: -.. parsed-literal:: +#. Set the environment variables for your database(s): :: - $ export DATABASE_URL="postgres://**:**\ @127.0.0.1:\ **/**" + $ export DATABASE_URL=postgres://postgres:@127.0.0.1:5432/ + # Optional: set broker URL if using Celery + $ export CELERY_BROKER_URL=redis://localhost:6379/0 -.. _django-environ: http://django-environ.readthedocs.io + .. note:: -You can now run the usual Django ``migrate`` and ``runserver`` commands:: + Check out the :ref:`settings` page for a comprehensive list of the environments variables. + + .. seealso:: + + To help setting up your environment variables, you have a few options: + + * create an ``.env`` file in the root of your project and define all the variables you need in it. + Then you just need to have ``DJANGO_READ_DOT_ENV_FILE=True`` in your machine and all the variables + will be read. + * Use a local environment manager like `direnv`_ + +#. Apply migrations: :: $ python manage.py migrate - $ python manage.py runserver -**Setup your email backend** +#. If you're running synchronously, see the application being served through Django development server: :: + + $ python manage.py runserver 0.0.0.0:8000 + +or if you're running asynchronously: :: + + $ uvicorn config.asgi:application --host 0.0.0.0 --reload --reload-include '*.html' + +.. _PostgreSQL: https://www.postgresql.org/download/ +.. _Redis: https://redis.io/download +.. _CookieCutter: https://github.com/cookiecutter/cookiecutter +.. _createdb: https://www.postgresql.org/docs/current/static/app-createdb.html +.. _initial PostgreSQL set up: https://web.archive.org/web/20190303010033/http://suite.opengeo.org/docs/latest/dataadmin/pgGettingStarted/firstconnect.html +.. _postgres documentation: https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html +.. _pre-commit: https://pre-commit.com/ +.. _direnv: https://direnv.net/ + -django-allauth sends an email to verify users (and superusers) after signup and login (if they are still not verified). To send email you need to `configure your email backend`_ +Setup Email Backend +------------------- -.. _configure your email backend: http://docs.djangoproject.com/en/1.9/topics/email/#smtp-backend -{% if cookiecutter.use_docker == 'y' %} -In development you can (optionally) use MailHog_ for email testing. MailHog is added as docker-container. To use MailHog:: +MailHog +~~~~~~~ -1. Make sure, that ``mailhog`` docker container is up and running -2. Open your browser and go to ``http://127.0.0.1:8025`` +.. note:: In order for the project to support MailHog_ it must have been bootstrapped with ``use_mailhog`` set to ``y``. -.. _Mailhog: https://github.com/mailhog/MailHog/ -{% else %} -In development you can (optionally) use MailHog_ for email testing. MailHog is built with Go so there are no dependencies. To use MailHog:: +MailHog is used to receive emails during development, it is written in Go and has no external dependencies. -1. `Download the latest release`_ for your operating system -2. Rename the executable to ``mailhog`` and copy it to the root of your project directory -3. Make sure it is executable (e.g. ``chmod +x mailhog``) -4. Execute mailhog from the root of your project in a new terminal window (e.g. ``./mailhog``) -5. All emails generated from your django app can be seen on http://127.0.0.1:8025/ +For instance, one of the packages we depend upon, ``django-allauth`` sends verification emails to new users signing up as well as to the existing ones who have not yet verified themselves. -.. _Mailhog: https://github.com/mailhog/MailHog/ -.. _Download the latest release: https://github.com/mailhog/MailHog/releases -{% endif %} -Alternatively simply output emails to the console via: ``EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'`` +#. `Download the latest MailHog release`_ for your OS. -In production basic email configuration is setup to send emails with Mailgun_ +#. Rename the build to ``MailHog``. + +#. Copy the file to the project root. + +#. Make it executable: :: + + $ chmod +x MailHog + +#. Spin up another terminal window and start it there: :: + + ./MailHog + +#. Check out ``_ to see how it goes. + +Now you have your own mail server running locally, ready to receive whatever you send it. + +.. _`Download the latest MailHog release`: https://github.com/mailhog/MailHog + +Console +~~~~~~~ + +.. note:: If you have generated your project with ``use_mailhog`` set to ``n`` this will be a default setup. + +Alternatively, deliver emails over console via ``EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'``. + +In production, we have Mailgun_ configured to have your back! .. _Mailgun: https://www.mailgun.com/ -**Live reloading and Sass CSS compilation** -If you'd like to take advantage of live reloading and Sass / Compass CSS compilation you can do so with the included Grunt task. +Celery +------ + +If the project is configured to use Celery as a task scheduler then by default tasks are set to run on the main thread +when developing locally. If you have the appropriate setup on your local machine then set the following +in ``config/settings/local.py``:: + + CELERY_TASK_ALWAYS_EAGER = False + +To run Celery locally, make sure redis-server is installed (instructions are available at https://redis.io/topics/quickstart), run the server in one terminal with `redis-server`, and then start celery in another terminal with the following command:: + + celery -A config.celery_app worker --loglevel=info + + +Sass Compilation & Live Reloading +--------------------------------- + +If you've opted for Gulp as front-end pipeline, the project comes configured with `Sass`_ compilation and `live reloading`_. As you change you Sass/JS source files, the task runner will automatically rebuild the corresponding CSS and JS assets and reload them in your browser without refreshing the page. -Make sure that nodejs_ is installed. Then in the project root run:: +#. Make sure that `Node.js`_ v16 is installed on your machine. +#. In the project root, install the JS dependencies with:: $ npm install -.. _nodejs: http://nodejs.org/download/ +#. Now - with your virtualenv activated - start the application by running:: -Now you just need:: + $ npm run dev - $ grunt serve + The app will now run with live reloading enabled, applying front-end changes dynamically. -The base app will now run as it would with the usual ``manage.py runserver`` but with live reloading and Sass compilation enabled. +.. note:: The task will start 2 processes in parallel: the static assets build loop on one side, and the Django server on the other. You do NOT need to run Django as your would normally with ``manage.py runserver``. -To get live reloading to work you'll probably need to install an `appropriate browser extension`_ +.. _Node.js: http://nodejs.org/download/ +.. _Sass: https://sass-lang.com/ +.. _live reloading: https://browsersync.io -.. _appropriate browser extension: http://feedback.livereload.com/knowledgebase/articles/86242-how-do-i-install-and-use-the-browser-extensions- +Summary +------- -It's time to write the code!!! +Congratulations, you have made it! Keep on reading to unleash full potential of Cookiecutter Django. diff --git a/docs/docker-postgres-backups.rst b/docs/docker-postgres-backups.rst new file mode 100644 index 0000000000..875d737eb3 --- /dev/null +++ b/docs/docker-postgres-backups.rst @@ -0,0 +1,98 @@ +PostgreSQL Backups with Docker +============================== + +.. note:: For brevity it is assumed that you will be running the below commands against local environment, however, this is by no means mandatory so feel free to switch to ``production.yml`` when needed. + + +Prerequisites +------------- + +#. the project was generated with ``use_docker`` set to ``y``; +#. the stack is up and running: ``docker-compose -f local.yml up -d postgres``. + + +Creating a Backup +----------------- + +To create a backup, run:: + + $ docker-compose -f local.yml exec postgres backup + +Assuming your project's database is named ``my_project`` here is what you will see: :: + + Backing up the 'my_project' database... + SUCCESS: 'my_project' database backup 'backup_2018_03_13T09_05_07.sql.gz' has been created and placed in '/backups'. + +Keep in mind that ``/backups`` is the ``postgres`` container directory. + + +Viewing the Existing Backups +---------------------------- + +To list existing backups, :: + + $ docker-compose -f local.yml exec postgres backups + +These are the sample contents of ``/backups``: :: + + These are the backups you have got: + total 24K + -rw-r--r-- 1 root root 5.2K Mar 13 09:05 backup_2018_03_13T09_05_07.sql.gz + -rw-r--r-- 1 root root 5.2K Mar 12 21:13 backup_2018_03_12T21_13_03.sql.gz + -rw-r--r-- 1 root root 5.2K Mar 12 21:12 backup_2018_03_12T21_12_58.sql.gz + + +Copying Backups Locally +----------------------- + +If you want to copy backups from your ``postgres`` container locally, ``docker cp`` command_ will help you on that. + +For example, given ``9c5c3f055843`` is the container ID copying all the backups over to a local directory is as simple as :: + + $ docker cp 9c5c3f055843:/backups ./backups + +With a single backup file copied to ``.`` that would be :: + + $ docker cp 9c5c3f055843:/backups/backup_2018_03_13T09_05_07.sql.gz . + +You can also get the container ID using ``docker-compose -f local.yml ps -q postgres`` so if you want to automate your backups, you don't have to check the container ID manually every time. Here is the full command :: + + $ docker cp $(docker-compose -f local.yml ps -q postgres):/backups ./backups + +.. _`command`: https://docs.docker.com/engine/reference/commandline/cp/ + +Restoring from the Existing Backup +---------------------------------- + +To restore from one of the backups you have already got (take the ``backup_2018_03_13T09_05_07.sql.gz`` for example), :: + + $ docker-compose -f local.yml exec postgres restore backup_2018_03_13T09_05_07.sql.gz + +You will see something like :: + + Restoring the 'my_project' database from the '/backups/backup_2018_03_13T09_05_07.sql.gz' backup... + INFO: Dropping the database... + INFO: Creating a new database... + INFO: Applying the backup to the new database... + SET + SET + SET + SET + SET + set_config + ------------ + + (1 row) + + SET + # ... + ALTER TABLE + SUCCESS: The 'my_project' database has been restored from the '/backups/backup_2018_03_13T09_05_07.sql.gz' backup. + + +Backup to Amazon S3 +---------------------------------- +For uploading your backups to Amazon S3 you can use the aws cli container. There is an upload command for uploading the postgres /backups directory recursively and there is a download command for downloading a specific backup. The default S3 environment variables are used. :: + + $ docker-compose -f production.yml run --rm awscli upload + $ docker-compose -f production.yml run --rm awscli download backup_2018_03_13T09_05_07.sql.gz diff --git a/docs/document.rst b/docs/document.rst new file mode 100644 index 0000000000..974c66c69c --- /dev/null +++ b/docs/document.rst @@ -0,0 +1,44 @@ +.. _document: + +Document +========= + +This project uses Sphinx_ documentation generator. + +After you have set up to `develop locally`_, run the following command from the project directory to build and serve HTML documentation: :: + + $ make -C docs livehtml + +If you set up your project to `develop locally with docker`_, run the following command: :: + + $ docker-compose -f local.yml up docs + +Navigate to port 9000 on your host to see the documentation. This will be opened automatically at `localhost`_ for local, non-docker development. + +Note: using Docker for documentation sets up a temporary SQLite file by setting the environment variable ``DATABASE_URL=sqlite:///readthedocs.db`` in ``docs/conf.py`` to avoid a dependency on PostgreSQL. + +Generate API documentation +---------------------------- + +Edit the ``docs`` files and project application docstrings to create your documentation. + +Sphinx can automatically include class and function signatures and docstrings in generated documentation. +See the generated project documentation for more examples. + +Setting up ReadTheDocs +---------------------- + +To setup your documentation on `ReadTheDocs`_, you must + +1. Go to `ReadTheDocs`_ and login/create an account +2. Add your GitHub repository +3. Trigger a build + +Additionally, you can auto-build Pull Request previews, but `you must enable it`_. + +.. _localhost: http://localhost:9000/ +.. _Sphinx: https://www.sphinx-doc.org/en/master/index.html +.. _develop locally: ./developing-locally.html +.. _develop locally with docker: ./developing-locally-docker.html +.. _ReadTheDocs: https://readthedocs.org/ +.. _you must enable it: https://docs.readthedocs.io/en/latest/guides/autobuild-docs-for-pull-requests.html#autobuild-documentation-for-pull-requests diff --git a/docs/faq.rst b/docs/faq.rst index 854e9dddd3..52a99467c2 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -1,27 +1,27 @@ FAQ -==== +=== .. index:: FAQ, 12-Factor App Why is there a django.contrib.sites directory in Cookiecutter Django? --------------------------------------------------------------------- -It is there to add a migration so you don't have to manually change the ``sites.Site`` record from ``example.com`` to whatever your domain is. Instead, your ``{{cookiecutter.domain_name}}`` and {{cookiecutter.project_name}} value is placed by **Cookiecutter** in the domain and name fields respectively. +It is there to add a migration so you don't have to manually change the ``sites.Site`` record from ``example.com`` to whatever your domain is. Instead, your ``{{cookiecutter.domain_name}}`` and ``{{cookiecutter.project_name}}`` value is placed by **Cookiecutter** in the domain and name fields respectively. -See `0002_set_site_domain_and_name.py`_. +See `0003_set_site_domain_and_name.py`_. -.. _`0002_set_site_domain_and_name.py`: https://github.com/pydanny/cookiecutter-django/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/contrib/sites/migrations/0002_set_site_domain_and_name.py +.. _`0003_set_site_domain_and_name.py`: https://github.com/cookiecutter/cookiecutter-django/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/contrib/sites/migrations/0003_set_site_domain_and_name.py Why aren't you using just one configuration file (12-Factor App) ---------------------------------------------------------------------- TODO +.. TODO -Why doesn't this follow the layout from Two Scoops of Django 1.8? ----------------------------------------------------------------------- - -You may notice that some elements of this project do not exactly match what we describe in chapter 3 of `Two Scoops of Django`_. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored. +Why doesn't this follow the layout from Two Scoops of Django? +------------------------------------------------------------- +You may notice that some elements of this project do not exactly match what we describe in chapter 3 of `Two Scoops of Django 1.11`_. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored. -.. _`Two Scoops of Django`: http://twoscoopspress.com/products/two-scoops-of-django-1-8 +.. _Two Scoops of Django 1.11: https://www.feldroy.com/collections/django/products/two-scoops-of-django-1-11 diff --git a/docs/index.rst b/docs/index.rst index beafb44cf2..dae641d103 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,36 +1,37 @@ .. cookiecutter-django documentation master file. Welcome to Cookiecutter Django's documentation! -==================================================================== +=============================================== -A Cookiecutter_ template for Django. +Powered by Cookiecutter_, Cookiecutter Django is a project template for jumpstarting production-ready Django projects. The template offers a number of generation options, we invite you to check the :ref:`dedicated page ` to learn more about each of them. -.. _cookiecutter: https://github.com/audreyr/cookiecutter +.. _cookiecutter: https://github.com/cookiecutter/cookiecutter -.. note:: This is an in-progress documentation reorganization. Locations of files may change dramatically over the course of the next few days. See https://github.com/pydanny/cookiecutter-django/issues/335 - -Contents: +Contents +-------- .. toctree:: :maxdepth: 2 - project-generation-options developing-locally developing-locally-docker settings linters - live-reloading-and-sass-compilation + testing + document deployment-on-pythonanywhere deployment-on-heroku deployment-with-docker + docker-postgres-backups + websocket faq + troubleshooting Indices and tables -================== +------------------ * :ref:`genindex` * :ref:`search` -.. At some point it would be good to have a module index of the high level things -we are doing. Then we can * :ref:`modindex` back in. +.. At some point it would be good to have a module index of the high level things we are doing. Then we can * :ref:`modindex` back in. diff --git a/docs/linters.rst b/docs/linters.rst index c2f76f3522..a4f60cc8dc 100644 --- a/docs/linters.rst +++ b/docs/linters.rst @@ -5,39 +5,39 @@ Linters flake8 -------- +------ -To run flake8: +To run flake8: :: $ flake8 The config for flake8 is located in setup.cfg. It specifies: * Set max line length to 120 chars -* Exclude .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules +* Exclude ``.tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules`` pylint ------ -This is included in flake8's checks, but you can also run it separately to see a more detailed report: +To run pylint: :: $ pylint The config for pylint is located in .pylintrc. It specifies: -* Use the pylint_common and pylint_django plugins. If using Celery, also use pylint_celery. +* Use the pylint_django plugin. If using Celery, also use pylint_celery. * Set max line length to 120 chars * Disable linting messages for missing docstring and invalid name * max-parents=13 -pep8 ------ +pycodestyle +----------- -This is included in flake8's checks, but you can also run it separately to see a more detailed report: +This is included in flake8's checks, but you can also run it separately to see a more detailed report: :: - $ pep8 + $ pycodestyle -The config for pep8 is located in setup.cfg. It specifies: +The config for pycodestyle is located in setup.cfg. It specifies: * Set max line length to 120 chars -* Exclude .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules \ No newline at end of file +* Exclude ``.tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules`` diff --git a/docs/live-reloading-and-sass-compilation.rst b/docs/live-reloading-and-sass-compilation.rst deleted file mode 100644 index 5996bc327f..0000000000 --- a/docs/live-reloading-and-sass-compilation.rst +++ /dev/null @@ -1,24 +0,0 @@ -Live reloading and Sass CSS compilation -======================================= - -If you'd like to take advantage of live reloading and Sass / Compass CSS compilation you can do so with a little bit of prep work. - -Make sure that nodejs_ is installed. Then in the project root run:: - - $ npm install - -.. _nodejs: http://nodejs.org/download/ - -If you don't already have it, install `compass` (doesn't hurt if you run this command twice):: - - gem install compass - -Now you just need:: - - $ grunt serve - -The base app will now run as it would with the usual ``manage.py runserver`` but with live reloading and Sass compilation enabled. - -To get live reloading to work you'll probably need to install an `appropriate browser extension`_ - -.. _appropriate browser extension: http://feedback.livereload.com/knowledgebase/articles/86242-how-do-i-install-and-use-the-browser-extensions- diff --git a/docs/mailgun.rst b/docs/mailgun.rst new file mode 100644 index 0000000000..b5f49b625d --- /dev/null +++ b/docs/mailgun.rst @@ -0,0 +1,13 @@ +If your email server used to send email isn't configured properly (Mailgun by default), +attempting to send an email will cause an Internal Server Error. + +By default, ``django-allauth`` is setup to `have emails verifications mandatory`_, +which means it'll send a verification email when an unverified user tries to +log-in or when someone tries to sign-up. + +This may happen just after you've setup your Mailgun account, which is running in a +sandbox subdomain by default. Either add your email to the list of authorized recipients +or verify your domain. + + +.. _have emails verifications mandatory: https://django-allauth.readthedocs.io/en/latest/configuration.html?highlight=ACCOUNT_EMAIL_VERIFICATION diff --git a/docs/project-generation-options.rst b/docs/project-generation-options.rst index 9ffad1e5f3..2d8103cf2f 100644 --- a/docs/project-generation-options.rst +++ b/docs/project-generation-options.rst @@ -1,55 +1,179 @@ +.. _template-options: + Project Generation Options ========================== -project_name [project_name]: - Your human-readable project name, including any capitalization or spaces. +This page describes all the template options that will be prompted by the `cookiecutter CLI`_ prior to generating your project. + +.. _cookiecutter CLI: https://github.com/cookiecutter/cookiecutter + +project_name: + Your project's human-readable name, capitals and spaces allowed. -project_slug [project_name]: - The slug of your project, without dashes or spaces. Used to name your repo +project_slug: + Your project's slug without dashes or spaces. Used to name your repo and in other places where a Python-importable version of your project name is needed. -author_name [Your Name]: - You! This goes into places like the LICENSE file. +description: + Describes your project and gets used in places like ``README.rst`` and such. -email [Your email]: - Your email address. +author_name: + This is you! The value goes into places like ``LICENSE`` and such. -description [A short description of the project.] - Used in the generated README.rst and other places. +email: + The email address you want to identify yourself in the project. -domain_name [example.com] - Whatever domain name you plan to use for your project when it goes live. +domain_name: + The domain name you plan to use for your project once it goes live. + Note that it can be safely changed later on whenever you need to. -version [0.1.0] - The starting version number for your project. +version: + The version of the project at its inception. -timezone [UTC] - Used in the common settings file for the `TIME_ZONE` value. +open_source_license: + A software license for the project. The choices are: -use_whitenoise [y] - Whether to use WhiteNoise_ for static file serving. + 1. MIT_ + 2. BSD_ + 3. GPLv3_ + 4. `Apache Software License 2.0`_ + 5. Not open source -use_celery [n] - Whether to use Celery_. This gives you the ability to use distributed task - queues in your project. +timezone: + The value to be used for the ``TIME_ZONE`` setting of the project. -use_mailhog [n] - Whether to use MailHog_. MailHog is a tool that simulates email receiving - for development purposes. It runs a simple SMTP server which catches - any message sent to it. Messages are displayed in a web interface which runs at ``http://localhost:8025/`` You need to download the MailHog executable for your operating system, see the 'Developing Locally' docs for instructions. +windows: + Indicates whether the project should be configured for development on Windows. -use_sentry [n] - Whether to use Sentry_ to log errors from your project. +use_pycharm: + Indicates whether the project should be configured for development with PyCharm_. -windows [n] - Whether you'll be developing on Windows. +use_docker: + Indicates whether the project should be configured to use Docker_ and `Docker Compose`_. -use_python2 [n] - By default, the Python code generated will be for Python 3.x. But if you - answer `y` here, it will be legacy Python 2.7 code. +postgresql_version: + Select a PostgreSQL_ version to use. The choices are: + + 1. 14 + 2. 13 + 3. 12 + 4. 11 + 5. 10 + +cloud_provider: + Select a cloud provider for static & media files. The choices are: + + 1. AWS_ + 2. GCP_ + 3. None + + Note that if you choose no cloud provider, media files won't work. + +mail_service: + Select an email service that Django-Anymail provides + + 1. Mailgun_ + 2. `Amazon SES`_ + 3. Mailjet_ + 4. Mandrill_ + 5. Postmark_ + 6. SendGrid_ + 7. SendinBlue_ + 8. SparkPost_ + 9. `Other SMTP`_ + +use_async: + Indicates whether the project should use web sockets with Uvicorn + Gunicorn. + +use_drf: + Indicates whether the project should be configured to use `Django Rest Framework`_. + +frontend_pipeline: + Select a pipeline to compile and optimise frontend assets (JS, CSS, ...): + + 1. None + 2. `Django Compressor`_ + 3. `Gulp`_: support Bootstrap recompilation with real-time variables alteration. + +use_celery: + Indicates whether the project should be configured to use Celery_. + +use_mailhog: + Indicates whether the project should be configured to use MailHog_. + +use_sentry: + Indicates whether the project should be configured to use Sentry_. + +use_whitenoise: + Indicates whether the project should be configured to use WhiteNoise_. + +use_heroku: + Indicates whether the project should be configured so as to be deployable + to Heroku_. + +ci_tool: + Select a CI tool for running tests. The choices are: + + 1. None + 2. `Travis CI`_ + 3. `Gitlab CI`_ + 4. `Github Actions`_ + +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. + This option is relevant for Cookiecutter Django developers only. + + +.. _MIT: https://opensource.org/licenses/MIT +.. _BSD: https://opensource.org/licenses/BSD-3-Clause +.. _GPLv3: https://www.gnu.org/licenses/gpl.html +.. _Apache Software License 2.0: http://www.apache.org/licenses/LICENSE-2.0 + +.. _PyCharm: https://www.jetbrains.com/pycharm/ + +.. _Docker: https://github.com/docker/docker +.. _Docker Compose: https://docs.docker.com/compose/ + +.. _PostgreSQL: https://www.postgresql.org/docs/ + +.. _Gulp: https://github.com/gulpjs/gulp + +.. _AWS: https://aws.amazon.com/s3/ +.. _GCP: https://cloud.google.com/storage/ + +.. _Amazon SES: https://aws.amazon.com/ses/ +.. _Mailgun: https://www.mailgun.com +.. _Mailjet: https://www.mailjet.com +.. _Mandrill: http://mandrill.com +.. _Postmark: https://postmarkapp.com +.. _SendGrid: https://sendgrid.com +.. _SendinBlue: https://www.sendinblue.com +.. _SparkPost: https://www.sparkpost.com +.. _Other SMTP: https://anymail.readthedocs.io/en/stable/ + +.. _Django Rest Framework: https://github.com/encode/django-rest-framework/ + +.. _Django Compressor: https://github.com/django-compressor/django-compressor -.. _WhiteNoise: https://github.com/evansd/whitenoise .. _Celery: https://github.com/celery/celery + .. _MailHog: https://github.com/mailhog/MailHog + .. _Sentry: https://github.com/getsentry/sentry + +.. _WhiteNoise: https://github.com/evansd/whitenoise + +.. _Heroku: https://github.com/heroku/heroku-buildpack-python + +.. _Travis CI: https://travis-ci.org/ + +.. _GitLab CI: https://docs.gitlab.com/ee/ci/ + +.. _Github Actions: https://docs.github.com/en/actions diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000000..2cc8302a23 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,2 @@ +sphinx==5.3.0 +sphinx-rtd-theme==1.1.1 diff --git a/docs/settings.rst b/docs/settings.rst index 768ff61821..b8c6d448e4 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -1,19 +1,27 @@ +.. _settings: + Settings -========== +======== This project relies extensively on environment settings which **will not work with Apache/mod_wsgi setups**. It has been deployed successfully with both Gunicorn/Nginx and even uWSGI/Nginx. -For configuration purposes, the following table maps environment variables to their Django setting: +For configuration purposes, the following table maps environment variables to their Django setting and project settings: + + +======================================= =========================== ============================================== ====================================================================== +Environment Variable Django Setting Development Default Production Default +======================================= =========================== ============================================== ====================================================================== +DJANGO_READ_DOT_ENV_FILE READ_DOT_ENV_FILE False False +======================================= =========================== ============================================== ====================================================================== ======================================= =========================== ============================================== ====================================================================== Environment Variable Django Setting Development Default Production Default ======================================= =========================== ============================================== ====================================================================== -DJANGO_ADMIN_URL n/a r'^admin/' raises error -DJANGO_CACHES CACHES (default) locmem redis -DJANGO_DATABASES DATABASES (default) See code See code +DATABASE_URL DATABASES auto w/ Docker; postgres://project_slug w/o raises error +DJANGO_ADMIN_URL n/a 'admin/' raises error DJANGO_DEBUG DEBUG True False -DJANGO_SECRET_KEY SECRET_KEY CHANGEME!!! raises error +DJANGO_SECRET_KEY SECRET_KEY auto-generated raises error DJANGO_SECURE_BROWSER_XSS_FILTER SECURE_BROWSER_XSS_FILTER n/a True DJANGO_SECURE_SSL_REDIRECT SECURE_SSL_REDIRECT n/a True DJANGO_SECURE_CONTENT_TYPE_NOSNIFF SECURE_CONTENT_TYPE_NOSNIFF n/a True @@ -32,19 +40,37 @@ The following table lists settings and their defaults for third-party applicatio ======================================= =========================== ============================================== ====================================================================== Environment Variable Django Setting Development Default Production Default ======================================= =========================== ============================================== ====================================================================== +CELERY_BROKER_URL CELERY_BROKER_URL auto w/ Docker; raises error w/o raises error DJANGO_AWS_ACCESS_KEY_ID AWS_ACCESS_KEY_ID n/a raises error DJANGO_AWS_SECRET_ACCESS_KEY AWS_SECRET_ACCESS_KEY n/a raises error DJANGO_AWS_STORAGE_BUCKET_NAME AWS_STORAGE_BUCKET_NAME n/a raises error -DJANGO_SENTRY_DSN SENTRY_DSN n/a raises error -DJANGO_SENTRY_CLIENT SENTRY_CLIENT n/a raven.contrib.django.raven_compat.DjangoClient +DJANGO_AWS_S3_REGION_NAME AWS_S3_REGION_NAME n/a None +DJANGO_AWS_S3_CUSTOM_DOMAIN AWS_S3_CUSTOM_DOMAIN n/a None +DJANGO_AWS_S3_MAX_MEMORY_SIZE AWS_S3_MAX_MEMORY_SIZE n/a 100_000_000 +DJANGO_GCP_STORAGE_BUCKET_NAME GS_BUCKET_NAME n/a raises error +GOOGLE_APPLICATION_CREDENTIALS n/a n/a raises error +SENTRY_DSN SENTRY_DSN n/a raises error +SENTRY_ENVIRONMENT n/a n/a production +SENTRY_TRACES_SAMPLE_RATE n/a n/a 0.0 DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a logging.INFO -DJANGO_MAILGUN_API_KEY MAILGUN_ACCESS_KEY n/a raises error -DJANGO_MAILGUN_SERVER_NAME MAILGUN_SERVER_NAME n/a raises error -NEW_RELIC_APP_NAME NEW_RELIC_APP_NAME n/a raises error -NEW_RELIC_LICENSE_KEY NEW_RELIC_LICENSE_KEY n/a raises error -DJANGO_OPBEAT_APP_ID OPBEAT['APP_ID'] n/a raises error -DJANGO_OPBEAT_SECRET_TOKEN OPBEAT['SECRET_TOKEN'] n/a raises error -DJANGO_OPBEAT_ORGANIZATION_ID OPBEAT['ORGANIZATION_ID'] n/a raises error +MAILGUN_API_KEY MAILGUN_API_KEY n/a raises error +MAILGUN_DOMAIN MAILGUN_SENDER_DOMAIN n/a raises error +MAILGUN_API_URL n/a n/a "https://api.mailgun.net/v3" +MAILJET_API_KEY MAILJET_API_KEY n/a raises error +MAILJET_SECRET_KEY MAILJET_SECRET_KEY n/a raises error +MAILJET_API_URL n/a n/a "https://api.mailjet.com/v3" +MANDRILL_API_KEY MANDRILL_API_KEY n/a raises error +MANDRILL_API_URL n/a n/a "https://mandrillapp.com/api/1.0" +POSTMARK_SERVER_TOKEN POSTMARK_SERVER_TOKEN n/a raises error +POSTMARK_API_URL n/a n/a "https://api.postmarkapp.com/" +SENDGRID_API_KEY SENDGRID_API_KEY n/a raises error +SENDGRID_GENERATE_MESSAGE_ID True n/a raises error +SENDGRID_MERGE_FIELD_FORMAT None n/a raises error +SENDGRID_API_URL n/a n/a "https://api.sendgrid.com/v3/" +SENDINBLUE_API_KEY SENDINBLUE_API_KEY n/a raises error +SENDINBLUE_API_URL n/a n/a "https://api.sendinblue.com/v3/" +SPARKPOST_API_KEY SPARKPOST_API_KEY n/a raises error +SPARKPOST_API_URL n/a n/a "https://api.sparkpost.com/api/v1" ======================================= =========================== ============================================== ====================================================================== -------------------------- diff --git a/docs/testing.rst b/docs/testing.rst new file mode 100644 index 0000000000..dd6fcb48f2 --- /dev/null +++ b/docs/testing.rst @@ -0,0 +1,56 @@ +.. _testing: + +Testing +======== + +We encourage users to build application tests. As best practice, this should be done immediately after documentation of the application being built, before starting on any coding. + +Pytest +------ + +This project uses the Pytest_, a framework for easily building simple and scalable tests. +After you have set up to `develop locally`_, run the following commands to make sure the testing environment is ready: :: + + $ pytest + +You will get a readout of the `users` app that has already been set up with tests. If you do not want to run the `pytest` on the entire project, you can target a particular app by typing in its location: :: + + $ pytest + +If you set up your project to `develop locally with docker`_, run the following command: :: + + $ docker-compose -f local.yml run --rm django pytest + +Targeting particular apps for testing in ``docker`` follows a similar pattern as previously shown above. + +Coverage +-------- + +You should build your tests to provide the highest level of **code coverage**. You can run the ``pytest`` with code ``coverage`` by typing in the following command: :: + + $ docker-compose -f local.yml run --rm django coverage run -m pytest + +Once the tests are complete, in order to see the code coverage, run the following command: :: + + $ docker-compose -f local.yml run --rm django coverage report + +.. note:: + + At the root of the project folder, you will find the `pytest.ini` file. You can use this to customize_ the ``pytest`` to your liking. + + There is also the `.coveragerc`. This is the configuration file for the ``coverage`` tool. You can find out more about `configuring`_ ``coverage``. + +.. seealso:: + + For unit tests, run: :: + + $ python manage.py test + + Since this is a fresh install, and there are no tests built using the Python `unittest`_ library yet, you should get feedback that says there were no tests carried out. + +.. _Pytest: https://docs.pytest.org/en/latest/example/simple.html +.. _develop locally: ./developing-locally.html +.. _develop locally with docker: ./developing-locally-docker.html +.. _customize: https://docs.pytest.org/en/latest/customize.html +.. _unittest: https://docs.python.org/3/library/unittest.html#module-unittest +.. _configuring: https://coverage.readthedocs.io/en/v4.5.x/config.html diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst new file mode 100644 index 0000000000..ba8ab53e62 --- /dev/null +++ b/docs/troubleshooting.rst @@ -0,0 +1,51 @@ +Troubleshooting +===================================== + +This page contains some advice about errors and problems commonly encountered during the development of Cookiecutter Django applications. + +Server Error on sign-up/log-in +------------------------------ + +Make sure you have configured the mail backend (e.g. Mailgun) by adding the API key and sender domain + +.. include:: mailgun.rst + +.. _docker-postgres-auth-failed: + +Docker: Postgres authentication failed +-------------------------------------- + +Examples of logs:: + + postgres_1 | 2018-06-07 19:11:23.963 UTC [81] FATAL: password authentication failed for user "pydanny" + postgres_1 | 2018-06-07 19:11:23.963 UTC [81] DETAIL: Password does not match for user "pydanny". + postgres_1 | Connection matched pg_hba.conf line 95: "host all all all md5" + +If you recreate the project multiple times with the same name, Docker would preserve the volumes for the postgres container between projects. Here is what happens: + +#. You generate the project the first time. The .env postgres file is populated with the random password +#. You run the docker-compose and the containers are created. The postgres container creates the database based on the .env file credentials +#. You "regenerate" the project with the same name, so the postgres .env file is populated with a new random password +#. You run docker-compose. Since the names of the containers are the same, docker will try to start them (not create them from scratch i.e. it won't execute the Dockerfile to recreate the database). When this happens, it tries to start the database based on the new credentials which do not match the ones that the database was created with, and you get the error message above. + +To fix this, you can either: + +- Clear your project-related Docker cache with ``docker-compose -f local.yml down --volumes --rmi all``. +- Use the Docker volume sub-commands to find volumes (`ls`_) and remove them (`rm`_). +- Use the `prune`_ command to clear system-wide (use with care!). + +.. _ls: https://docs.docker.com/engine/reference/commandline/volume_ls/ +.. _rm: https://docs.docker.com/engine/reference/commandline/volume_rm/ +.. _prune: https://docs.docker.com/v17.09/engine/reference/commandline/system_prune/ + +Others +------ + +#. ``project_slug`` must be a valid Python module name or you will have issues on imports. + +#. ``jinja2.exceptions.TemplateSyntaxError: Encountered unknown tag 'now'.``: please upgrade your cookiecutter version to >= 1.4 (see `#528`_) + +#. New apps not getting created in project root: This is the expected behavior, because cookiecutter-django does not change the way that django startapp works, you'll have to fix this manually (see `#1725`_) + +.. _#528: https://github.com/cookiecutter/cookiecutter-django/issues/528#issuecomment-212650373 +.. _#1725: https://github.com/cookiecutter/cookiecutter-django/issues/1725#issuecomment-407493176 diff --git a/docs/websocket.rst b/docs/websocket.rst new file mode 100644 index 0000000000..9d2e3264a3 --- /dev/null +++ b/docs/websocket.rst @@ -0,0 +1,25 @@ +.. _websocket: + +========= +Websocket +========= + +You can enable web sockets if you select ``use_async`` option when creating a project. That indicates whether the project can use web sockets with Uvicorn + Gunicorn. + +Usage +----- + +JavaScript example: :: + + > ws = new WebSocket('ws://localhost:8000/') // or 'wss:///' in prod + WebSocket {url: "ws://localhost:8000/", readyState: 0, bufferedAmount: 0, onopen: null, onerror: null, …} + > ws.onmessage = event => console.log(event.data) + event => console.log(event.data) + > ws.send("ping") + undefined + pong! + + +If you don't use Traefik, you might have to configure your reverse proxy accordingly (example with Nginx_). + +.. _Nginx: https://www.nginx.com/blog/websocket-nginx/ diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index cffb052e85..9c3d946c1c 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -1,203 +1,423 @@ """ -Does the following: - -1. Generates and saves random secret key -2. Removes the taskapp if celery isn't going to be used -3. Removes the .idea directory if PyCharm isn't going to be used -4. Copy files from /docs/ to {{ cookiecutter.project_slug }}/docs/ - - TODO: this might have to be moved to a pre_gen_hook - -A portion of this code was adopted from Django's standard crypto functions and -utilities, specifically: - https://github.com/django/django/blob/master/django/utils/crypto.py +NOTE: + the below code is to be maintained Python 2.x-compatible + as the whole Cookiecutter Django project initialization + can potentially be run in Python 2.x environment + (at least so we presume in `pre_gen_project.py`). + +TODO: restrict Cookiecutter Django project initialization to + Python 3.x environments only """ from __future__ import print_function + import os import random import shutil +import string -# Get the root project directory -PROJECT_DIRECTORY = os.path.realpath(os.path.curdir) - -# Use the system PRNG if possible try: + # Inspired by + # https://github.com/django/django/blob/master/django/utils/crypto.py random = random.SystemRandom() using_sysrandom = True except NotImplementedError: using_sysrandom = False +TERMINATOR = "\x1b[0m" +WARNING = "\x1b[1;33m [WARNING]: " +INFO = "\x1b[1;33m [INFO]: " +HINT = "\x1b[3;33m" +SUCCESS = "\x1b[1;32m [SUCCESS]: " + +DEBUG_VALUE = "debug" + + +def remove_open_source_files(): + file_names = ["CONTRIBUTORS.txt", "LICENSE"] + for file_name in file_names: + os.remove(file_name) + + +def remove_gplv3_files(): + file_names = ["COPYING"] + for file_name in file_names: + os.remove(file_name) + + +def remove_pycharm_files(): + idea_dir_path = ".idea" + if os.path.exists(idea_dir_path): + shutil.rmtree(idea_dir_path) + + docs_dir_path = os.path.join("docs", "pycharm") + if os.path.exists(docs_dir_path): + shutil.rmtree(docs_dir_path) + + +def remove_docker_files(): + shutil.rmtree("compose") + + file_names = ["local.yml", "production.yml", ".dockerignore"] + for file_name in file_names: + os.remove(file_name) + if "{{ cookiecutter.use_pycharm }}".lower() == "y": + file_names = ["docker_compose_up_django.xml", "docker_compose_up_docs.xml"] + for file_name in file_names: + os.remove(os.path.join(".idea", "runConfigurations", 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: + if ( + file_name == "requirements.txt" + and "{{ cookiecutter.ci_tool }}".lower() == "travis" + ): + # don't remove the file if we are using travisci but not using heroku + continue + os.remove(file_name) + remove_heroku_build_hooks() + + +def remove_heroku_build_hooks(): + shutil.rmtree("bin") + + +def remove_gulp_files(): + file_names = ["gulpfile.js"] + for file_name in file_names: + os.remove(file_name) + remove_sass_files() + + +def remove_sass_files(): + shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "static", "sass")) + + +def remove_packagejson_file(): + file_names = ["package.json"] + for file_name in file_names: + os.remove(file_name) + + +def remove_celery_files(): + file_names = [ + os.path.join("config", "celery_app.py"), + os.path.join("{{ cookiecutter.project_slug }}", "users", "tasks.py"), + os.path.join( + "{{ cookiecutter.project_slug }}", "users", "tests", "test_tasks.py" + ), + ] + for file_name in file_names: + os.remove(file_name) + + +def remove_async_files(): + file_names = [ + os.path.join("config", "asgi.py"), + os.path.join("config", "websocket.py"), + ] + for file_name in file_names: + os.remove(file_name) + + +def remove_dottravisyml_file(): + os.remove(".travis.yml") + -def get_random_string( - length=50, - allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789!@#%^&*(-_=+)'): +def remove_dotgitlabciyml_file(): + os.remove(".gitlab-ci.yml") + + +def remove_dotgithub_folder(): + shutil.rmtree(".github") + + +def generate_random_string( + length, using_digits=False, using_ascii_letters=False, using_punctuation=False +): """ - Returns a securely generated random string. - The default length of 12 with the a-z, A-Z, 0-9 character set returns - a 71-bit value. log_2((26+26+10)^12) =~ 71 bits + Example: + opting out for 50 symbol-long, [a-z][A-Z][0-9] string + would yield log_2((26+26+50)^50) ~= 334 bit strength. """ - if using_sysrandom: - return ''.join(random.choice(allowed_chars) for i in range(length)) - print( - "Cookiecutter Django couldn't find a secure pseudo-random number generator on your system." - " Please change change your SECRET_KEY variables in conf/settings/local.py and env.example" - " manually." + if not using_sysrandom: + return None + + symbols = [] + if using_digits: + symbols += string.digits + if using_ascii_letters: + symbols += string.ascii_letters + if using_punctuation: + all_punctuation = set(string.punctuation) + # These symbols can cause issues in environment variables + unsuitable = {"'", '"', "\\", "$"} + suitable = all_punctuation.difference(unsuitable) + symbols += "".join(suitable) + return "".join([random.choice(symbols) for _ in range(length)]) + + +def set_flag(file_path, flag, value=None, formatted=None, *args, **kwargs): + if value is None: + random_string = generate_random_string(*args, **kwargs) + if random_string is None: + print( + "We couldn't find a secure pseudo-random number generator on your " + "system. Please, make sure to manually {} later.".format(flag) + ) + random_string = flag + if formatted is not None: + random_string = formatted.format(random_string) + value = random_string + + with open(file_path, "r+") as f: + file_contents = f.read().replace(flag, value) + f.seek(0) + f.write(file_contents) + f.truncate() + + return value + + +def set_django_secret_key(file_path): + django_secret_key = set_flag( + file_path, + "!!!SET DJANGO_SECRET_KEY!!!", + length=64, + using_digits=True, + using_ascii_letters=True, + ) + return django_secret_key + + +def set_django_admin_url(file_path): + django_admin_url = set_flag( + file_path, + "!!!SET DJANGO_ADMIN_URL!!!", + formatted="{}/", + length=32, + using_digits=True, + using_ascii_letters=True, ) - return "CHANGEME!!" + return django_admin_url + +def generate_random_user(): + return generate_random_string(length=32, using_ascii_letters=True) -def set_secret_key(setting_file_location): - # Open locals.py - with open(setting_file_location) as f: - file_ = f.read() - # Generate a SECRET_KEY that matches the Django standard - SECRET_KEY = get_random_string() +def generate_postgres_user(debug=False): + return DEBUG_VALUE if debug else generate_random_user() - # Replace "CHANGEME!!!" with SECRET_KEY - file_ = file_.replace('CHANGEME!!!', SECRET_KEY, 1) - # Write the results to the locals.py module - with open(setting_file_location, 'w') as f: - f.write(file_) +def set_postgres_user(file_path, value): + postgres_user = set_flag(file_path, "!!!SET POSTGRES_USER!!!", value=value) + return postgres_user -def make_secret_key(project_directory): - """Generates and saves random secret key""" - # Determine the local_setting_file_location - local_setting = os.path.join( - project_directory, - 'config/settings/local.py' +def set_postgres_password(file_path, value=None): + postgres_password = set_flag( + file_path, + "!!!SET POSTGRES_PASSWORD!!!", + value=value, + length=64, + using_digits=True, + using_ascii_letters=True, ) + return postgres_password - # local.py settings file - set_secret_key(local_setting) - env_file = os.path.join( - project_directory, - 'env.example' +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 - # env.example file - set_secret_key(env_file) + +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 remove_task_app(project_directory): - """Removes the taskapp if celery isn't going to be used""" - # Determine the local_setting_file_location - task_app_location = os.path.join( - PROJECT_DIRECTORY, - '{{ cookiecutter.project_slug }}/taskapp' +def append_to_gitignore_file(ignored_line): + with open(".gitignore", "a") as gitignore_file: + gitignore_file.write(ignored_line) + gitignore_file.write("\n") + + +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) + + 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 ) - shutil.rmtree(task_app_location) + 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 remove_pycharm_dir(project_directory): - """ - Removes directories related to PyCharm - if it isn't going to be used - """ - idea_dir_location = os.path.join(PROJECT_DIRECTORY, '.idea/') - if os.path.exists(idea_dir_location): - shutil.rmtree(idea_dir_location) - docs_dir_location = os.path.join(PROJECT_DIRECTORY, 'docs/pycharm/') - if os.path.exists(docs_dir_location): - shutil.rmtree(docs_dir_location) +def set_flags_in_settings_files(): + set_django_secret_key(os.path.join("config", "settings", "local.py")) + set_django_secret_key(os.path.join("config", "settings", "test.py")) -def remove_heroku_files(): - """ - Removes files needed for heroku if it isn't going to be used - """ - for filename in ["app.json", "Procfile", "requirements.txt", "runtime.txt"]: - os.remove(os.path.join( - PROJECT_DIRECTORY, filename - )) +def remove_envs_and_associated_files(): + shutil.rmtree(".envs") + os.remove("merge_production_dotenvs_in_dotenv.py") -def remove_docker_files(): - """ - Removes files needed for docker if it isn't going to be used - """ - for filename in ["dev.yml", "docker-compose.yml", ".dockerignore"]: - os.remove(os.path.join( - PROJECT_DIRECTORY, filename - )) +def remove_celery_compose_dirs(): + shutil.rmtree(os.path.join("compose", "local", "django", "celery")) + shutil.rmtree(os.path.join("compose", "production", "django", "celery")) - shutil.rmtree(os.path.join( - PROJECT_DIRECTORY, "compose" - )) +def remove_node_dockerfile(): + shutil.rmtree(os.path.join("compose", "local", "node")) -def remove_grunt_files(): - """ - Removes files needed for grunt if it isn't going to be used - """ - for filename in ["Gruntfile.js", "package.json"]: - os.remove(os.path.join( - PROJECT_DIRECTORY, filename - )) - -# IN PROGRESS -# def copy_doc_files(project_directory): -# cookiecutters_dir = DEFAULT_CONFIG['cookiecutters_dir'] -# cookiecutter_django_dir = os.path.join( -# cookiecutters_dir, -# 'cookiecutter-django', -# 'docs' -# ) -# target_dir = os.path.join( -# project_directory, -# 'docs' -# ) -# for name in os.listdir(cookiecutter_django_dir): -# if name.endswith('.rst') and not name.startswith('index'): -# src = os.path.join(cookiecutter_django_dir, name) -# dst = os.path.join(target_dir, name) -# shutil.copyfile(src, dst) - -# 1. Generates and saves random secret key -make_secret_key(PROJECT_DIRECTORY) - -# 2. Removes the taskapp if celery isn't going to be used -if '{{ cookiecutter.use_celery }}'.lower() == 'n': - remove_task_app(PROJECT_DIRECTORY) - -# 3. Removes the .idea directory if PyCharm isn't going to be used -if '{{ cookiecutter.use_pycharm }}'.lower() != 'y': - remove_pycharm_dir(PROJECT_DIRECTORY) - -# 4. Removes all heroku files if it isn't going to be used -if '{{ cookiecutter.use_heroku }}'.lower() != 'y': - remove_heroku_files() - -# 5. Removes all docker files if it isn't going to be used -if '{{ cookiecutter.use_docker }}'.lower() != 'y': - remove_docker_files() - -# 6. Removes all grunt files if it isn't going to be used -if '{{ cookiecutter.use_grunt }}'.lower() != 'y': - remove_grunt_files() - - -# 7. Display a warning if use_docker and use_grunt are selected. Grunt isn't supported by our -# docker config atm. -if '{{ cookiecutter.use_grunt }}'.lower() == 'y' and '{{ cookiecutter.use_docker }}'.lower() == 'y': - print( - "You selected to use docker and grunt. This is NOT supported out of the box for now. You " - "can continue to use the project like you normally would, but you will need to add a " - " grunt service to your docker configuration manually." - ) -# 7. Display a warning if use_docker and use_mailhog are selected. Mailhog isn't supported by our -# docker config atm. -if '{{ cookiecutter.use_mailhog }}'.lower() == 'y' and '{{ cookiecutter.use_docker }}'.lower() == 'y': - print( - "You selected to use docker and mailhog. This is NOT supported out of the box for now. You" - " can continue to use the project like you normally would, but you will need to add a " - " mailhog service to your docker configuration manually." +def remove_aws_dockerfile(): + shutil.rmtree(os.path.join("compose", "production", "aws")) + + +def remove_drf_starter_files(): + os.remove(os.path.join("config", "api_router.py")) + shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "users", "api")) + os.remove( + os.path.join( + "{{cookiecutter.project_slug}}", "users", "tests", "test_drf_urls.py" + ) + ) + os.remove( + os.path.join( + "{{cookiecutter.project_slug}}", "users", "tests", "test_drf_views.py" + ) ) + os.remove( + os.path.join( + "{{cookiecutter.project_slug}}", "users", "tests", "test_swagger.py" + ) + ) + -# 4. Copy files from /docs/ to {{ cookiecutter.project_slug }}/docs/ -# copy_doc_files(PROJECT_DIRECTORY) +def remove_storages_module(): + os.remove(os.path.join("{{cookiecutter.project_slug}}", "utils", "storages.py")) + + +def main(): + 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": + remove_open_source_files() + if "{{ cookiecutter.open_source_license}}" != "GPLv3": + remove_gplv3_files() + + if "{{ cookiecutter.use_pycharm }}".lower() == "n": + remove_pycharm_files() + + if "{{ cookiecutter.use_docker }}".lower() == "y": + remove_utility_files() + else: + remove_docker_files() + + if ( + "{{ cookiecutter.use_docker }}".lower() == "y" + and "{{ cookiecutter.cloud_provider}}" != "AWS" + ): + remove_aws_dockerfile() + + if "{{ cookiecutter.use_heroku }}".lower() == "n": + remove_heroku_files() + elif "{{ cookiecutter.frontend_pipeline }}" != "Django Compressor": + remove_heroku_build_hooks() + + if ( + "{{ cookiecutter.use_docker }}".lower() == "n" + and "{{ cookiecutter.use_heroku }}".lower() == "n" + ): + if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y": + print( + INFO + ".env(s) are only utilized when Docker Compose and/or " + "Heroku support is enabled so keeping them does not " + "make sense given your current setup." + TERMINATOR + ) + remove_envs_and_associated_files() + else: + append_to_gitignore_file(".env") + append_to_gitignore_file(".envs/*") + if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y": + append_to_gitignore_file("!.envs/.local/") + + if "{{ cookiecutter.frontend_pipeline }}" != "Gulp": + remove_gulp_files() + remove_packagejson_file() + if "{{ cookiecutter.use_docker }}".lower() == "y": + remove_node_dockerfile() + + if "{{ cookiecutter.cloud_provider}}" == "None": + print( + WARNING + "You chose not to use a cloud provider, " + "media files won't be served in production." + TERMINATOR + ) + remove_storages_module() + + if "{{ cookiecutter.use_celery }}".lower() == "n": + remove_celery_files() + if "{{ cookiecutter.use_docker }}".lower() == "y": + remove_celery_compose_dirs() + + if "{{ cookiecutter.ci_tool }}" != "Travis": + remove_dottravisyml_file() + + if "{{ cookiecutter.ci_tool }}" != "Gitlab": + remove_dotgitlabciyml_file() + + if "{{ cookiecutter.ci_tool }}" != "Github": + remove_dotgithub_folder() + + if "{{ cookiecutter.use_drf }}".lower() == "n": + remove_drf_starter_files() + + if "{{ cookiecutter.use_async }}".lower() == "n": + remove_async_files() + + print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR) + + +if __name__ == "__main__": + main() diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 3dfc190a25..2845f012cd 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -1,8 +1,85 @@ -import cookiecutter +""" +NOTE: + the below code is to be maintained Python 2.x-compatible + as the whole Cookiecutter Django project initialization + can potentially be run in Python 2.x environment. -project_slug = '{{ cookiecutter.project_slug }}' +TODO: restrict Cookiecutter Django project initialization + to Python 3.x environments only +""" +from __future__ import print_function -if hasattr(project_slug, 'isidentifier'): - assert project_slug.isidentifier(), 'Project slug should be valid Python identifier!' +import sys -assert cookiecutter.__version__ > '1.3.0', 'Please upgrade your Cookiecutter installation' +TERMINATOR = "\x1b[0m" +WARNING = "\x1b[1;33m [WARNING]: " +INFO = "\x1b[1;33m [INFO]: " +HINT = "\x1b[3;33m" +SUCCESS = "\x1b[1;32m [SUCCESS]: " + +project_slug = "{{ cookiecutter.project_slug }}" +if hasattr(project_slug, "isidentifier"): + assert ( + project_slug.isidentifier() + ), "'{}' project slug is not a valid Python identifier.".format(project_slug) + +assert ( + project_slug == project_slug.lower() +), "'{}' project slug should be all lowercase".format(project_slug) + +assert ( + "\\" not in "{{ cookiecutter.author_name }}" +), "Don't include backslashes in author name." + +if "{{ cookiecutter.use_docker }}".lower() == "n": + python_major_version = sys.version_info[0] + if python_major_version == 2: + print( + WARNING + "You're running cookiecutter under Python 2, but the generated " + "project requires Python 3.10+. Do you want to proceed (y/n)? " + TERMINATOR + ) + yes_options, no_options = frozenset(["y"]), frozenset(["n"]) + while True: + choice = raw_input().lower() # noqa: F821 + if choice in yes_options: + break + + elif choice in no_options: + print(INFO + "Generation process stopped as requested." + TERMINATOR) + sys.exit(1) + else: + print( + HINT + + "Please respond with {} or {}: ".format( + ", ".join( + ["'{}'".format(o) for o in yes_options if not o == ""] + ), + ", ".join( + ["'{}'".format(o) for o in no_options if not o == ""] + ), + ) + + TERMINATOR + ) + +if ( + "{{ cookiecutter.use_whitenoise }}".lower() == "n" + and "{{ cookiecutter.cloud_provider }}" == "None" +): + print( + "You should either use Whitenoise or select a " + "Cloud Provider to serve static files" + ) + sys.exit(1) + +if ( + "{{ cookiecutter.cloud_provider }}" == "GCP" + and "{{ cookiecutter.mail_service }}" == "Amazon SES" +) or ( + "{{ cookiecutter.cloud_provider }}" == "None" + and "{{ cookiecutter.mail_service }}" == "Amazon SES" +): + print( + "You should either use AWS or select a different " + "Mail Service for sending emails." + ) + sys.exit(1) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000000..52506f47de --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +addopts = -v --tb=short +norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/* diff --git a/requirements.txt b/requirements.txt index d63bed8736..ea96718760 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,26 @@ -cookiecutter==1.4.0 -flake8==2.5.4 -sh==1.11 -binaryornot==0.4.0 +cookiecutter==2.1.1 +sh==1.14.3; sys_platform != "win32" +binaryornot==0.4.4 + +# Code quality +# ------------------------------------------------------------------------------ +black==22.10.0 +isort==5.10.1 +flake8==5.0.4 +flake8-isort==5.0.0 +pre-commit==2.20.0 # Testing -pytest==2.9.1 -pep8==1.7.0 -pyflakes==1.2.3 -tox==2.3.1 -pytest-cookies==0.2.0 +# ------------------------------------------------------------------------------ +tox==3.27.0 +pytest==7.2.0 +pytest-cookies==0.6.1 +pytest-instafail==0.4.2 +pyyaml==6.0 + +# Scripting +# ------------------------------------------------------------------------------ +PyGithub==1.57 +gitpython==3.1.29 +jinja2==3.1.2 +requests==2.28.1 diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scripts/create_django_issue.py b/scripts/create_django_issue.py new file mode 100644 index 0000000000..5809f393d9 --- /dev/null +++ b/scripts/create_django_issue.py @@ -0,0 +1,320 @@ +""" +Creates an issue that generates a table for dependency checking whether +all packages support the latest Django version. "Latest" does not include +patches, only comparing major and minor version numbers. + +This script handles when there are multiple Django versions that need +to keep up to date. +""" +from __future__ import annotations + +import os +import re +import sys +from collections.abc import Iterable +from pathlib import Path +from typing import TYPE_CHECKING, Any, NamedTuple + +import requests +from github import Github + +if TYPE_CHECKING: + from github.Issue import Issue + +CURRENT_FILE = Path(__file__) +ROOT = CURRENT_FILE.parents[1] +REQUIREMENTS_DIR = ROOT / "{{cookiecutter.project_slug}}" / "requirements" +GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", None) +GITHUB_REPO = os.getenv("GITHUB_REPOSITORY", None) + + +class DjVersion(NamedTuple): + """ + Wrapper to parse, compare and render Django versions. + + Only keeps track on (major, minor) versions, excluding patches and pre-releases. + """ + + major: int + minor: int + + def __str__(self) -> str: + """To render as string.""" + return f"{self.major}.{self.minor}" + + @classmethod + def parse(cls, version_str: str) -> DjVersion: + """Parse interesting values from the version string.""" + major, minor, *_ = version_str.split(".") + return cls(major=int(major), minor=int(minor)) + + @classmethod + def parse_to_tuple(cls, version_str: str): + version = cls.parse(version_str=version_str) + return version.major, version.minor + + +def get_package_info(package: str) -> dict: + """Get package metadata using PyPI API.""" + # "django" converts to "Django" on redirect + r = requests.get(f"https://pypi.org/pypi/{package}/json", allow_redirects=True) + if not r.ok: + print(f"Couldn't find package: {package}") + sys.exit(1) + return r.json() + + +def get_django_versions() -> Iterable[DjVersion]: + """List all django versions.""" + django_package_info: dict[str, Any] = get_package_info("django") + releases = django_package_info["releases"].keys() + for release_str in releases: + if release_str.replace(".", "").isdigit(): + # Exclude pre-releases with non-numeric characters in version + yield DjVersion.parse(release_str) + + +def get_name_and_version(requirements_line: str) -> tuple[str, ...]: + """Get the name a version of a package from a line in the requirement file.""" + full_name, version = requirements_line.split(" ", 1)[0].split("==") + name_without_extras = full_name.split("[", 1)[0] + return name_without_extras, version + + +def get_all_latest_django_versions( + django_max_version: tuple[DjVersion] = None, +) -> tuple[DjVersion, list[DjVersion]]: + """ + Grabs all Django versions that are worthy of a GitHub issue. + Depends on Django versions having higher major version or minor version. + """ + _django_max_version = (99, 99) + if django_max_version: + _django_max_version = django_max_version + + print("Fetching all Django versions from PyPI") + base_txt = REQUIREMENTS_DIR / "base.txt" + with base_txt.open() as f: + for line in f.readlines(): + if "django==" in line.lower(): + break + else: + print(f"django not found in {base_txt}") # Huh...? + sys.exit(1) + + # Begin parsing and verification + _, current_version_str = get_name_and_version(line) + # Get a tuple of (major, minor) - ignoring patch version + current_minor_version = DjVersion.parse(current_version_str) + newer_versions: set[DjVersion] = set() + for django_version in get_django_versions(): + if current_minor_version < django_version <= _django_max_version: + newer_versions.add(django_version) + + return current_minor_version, sorted(newer_versions, reverse=True) + + +_TABLE_HEADER = """ + +## {file}.txt + +| Name | Version in Master | {dj_version} Compatible Version | OK | +| ---- | :---------------: | :-----------------------------: | :-: | +""" +VITAL_BUT_UNKNOWN = [ + "django-environ", # not updated often +] + + +class GitHubManager: + def __init__(self, base_dj_version: DjVersion, needed_dj_versions: list[DjVersion]): + self.github = Github(GITHUB_TOKEN) + self.repo = self.github.get_repo(GITHUB_REPO) + + self.base_dj_version = base_dj_version + self.needed_dj_versions = needed_dj_versions + # (major+minor) Version and description + self.existing_issues: dict[DjVersion, Issue] = {} + + # Load all requirements from our requirements files and preload their + # package information like a cache: + self.requirements_files = ["base", "local", "production"] + # Format: + # requirement file name: {package name: (master_version, package_info)} + self.requirements: dict[str, dict[str, tuple[str, dict]]] = { + x: {} for x in self.requirements_files + } + + def setup(self) -> None: + self.load_requirements() + self.load_existing_issues() + + def load_requirements(self): + print("Reading requirements") + for requirements_file in self.requirements_files: + with (REQUIREMENTS_DIR / f"{requirements_file}.txt").open() as f: + for line in f.readlines(): + if ( + "==" in line + and not line.startswith("{%") + and not line.startswith(" #") + and not line.startswith("#") + and not line.startswith(" ") + ): + name, version = get_name_and_version(line) + self.requirements[requirements_file][name] = ( + version, + get_package_info(name), + ) + + def load_existing_issues(self): + """Closes the issue if the base Django version is greater than needed""" + print("Load existing issues from GitHub") + qualifiers = { + "repo": GITHUB_REPO, + "author": "app/github-actions", + "state": "open", + "is": "issue", + "in": "title", + } + issues = list( + self.github.search_issues( + "[Django Update]", "created", "desc", **qualifiers + ) + ) + print(f"Found {len(issues)} issues matching search") + for issue in issues: + matches = re.match(r"\[Update Django] Django (\d+.\d+)$", issue.title) + if not matches: + continue + issue_version = DjVersion.parse(matches.group(1)) + if self.base_dj_version > issue_version: + issue.edit(state="closed") + print(f"Closed issue {issue.title} (ID: [{issue.id}]({issue.url}))") + else: + self.existing_issues[issue_version] = issue + + def get_compatibility( + self, package_name: str, package_info: dict, needed_dj_version: DjVersion + ): + """ + Verify compatibility via setup.py classifiers. If Django is not in the + classifiers, then default compatibility is n/a and OK is ✅. + + If it's a package that's vital but known to not be updated often, we give it + a ❓. If a package has ❓ or 🕒, then we allow manual update. Automatic updates + only include ❌ and ✅. + """ + # If issue previously existed, find package and skip any gtg, manually + # updated packages, or known releases that will happen but haven't yet + if issue := self.existing_issues.get(needed_dj_version): + if index := issue.body.find(package_name): + name, _current, prev_compat, ok = ( + s.strip() for s in issue.body[index:].split("|", 4)[:4] + ) + if ok in ("✅", "❓", "🕒"): + return prev_compat, ok + + if package_name in VITAL_BUT_UNKNOWN: + return "", "❓" + + # Check classifiers if it includes Django + supported_dj_versions: list[DjVersion] = [] + for classifier in package_info["info"]["classifiers"]: + # Usually in the form of "Framework :: Django :: 3.2" + tokens = classifier.split(" ") + if len(tokens) >= 5 and tokens[2].lower() == "django": + version = DjVersion.parse(tokens[4]) + if len(version) == 2: + supported_dj_versions.append(version) + + if supported_dj_versions: + if any(v >= needed_dj_version for v in supported_dj_versions): + return package_info["info"]["version"], "✅" + else: + return "", "❌" + + # Django classifier DNE; assume it isn't a Django lib + # Great exceptions include pylint-django, where we need to do this manually... + return "n/a", "✅" + + HOME_PAGE_URL_KEYS = [ + "home_page", + "project_url", + "docs_url", + "package_url", + "release_url", + "bugtrack_url", + ] + + def _get_md_home_page_url(self, package_info: dict): + urls = [ + package_info["info"].get(url_key) for url_key in self.HOME_PAGE_URL_KEYS + ] + try: + return f"[{{}}]({next(item for item in urls if item)})" + except StopIteration: + return "{}" + + def generate_markdown(self, needed_dj_version: DjVersion): + requirements = f"{needed_dj_version} requirements tables\n\n" + for _file in self.requirements_files: + requirements += _TABLE_HEADER.format_map( + {"file": _file, "dj_version": needed_dj_version} + ) + for package_name, (version, info) in self.requirements[_file].items(): + compat_version, icon = self.get_compatibility( + package_name, info, needed_dj_version + ) + requirements += ( + f"| {self._get_md_home_page_url(info).format(package_name)} " + f"| {version.strip()} " + f"| {compat_version.strip()} " + f"| {icon} " + f"|\n" + ) + + return requirements + + def create_or_edit_issue(self, needed_dj_version: DjVersion, description: str): + if issue := self.existing_issues.get(needed_dj_version): + print(f"Editing issue #{issue.number} for Django {needed_dj_version}") + issue.edit(body=description) + else: + print(f"Creating new issue for Django {needed_dj_version}") + issue = self.repo.create_issue( + f"[Update Django] Django {needed_dj_version}", description + ) + issue.add_to_labels(f"django{needed_dj_version}") + + def generate(self): + for version in self.needed_dj_versions: + print(f"Handling GitHub issue for Django {version}") + md_content = self.generate_markdown(version) + print(f"Generated markdown:\n\n{md_content}") + self.create_or_edit_issue(version, md_content) + + +def main(django_max_version=None) -> None: + # Check if there are any djs + current_dj, latest_djs = get_all_latest_django_versions( + django_max_version=django_max_version + ) + if not latest_djs: + sys.exit(0) + manager = GitHubManager(current_dj, latest_djs) + manager.setup() + manager.generate() + + +if __name__ == "__main__": + if GITHUB_REPO is None: + raise RuntimeError( + "No github repo, please set the environment variable GITHUB_REPOSITORY" + ) + max_version = None + last_arg = sys.argv[-1] + if CURRENT_FILE.name not in last_arg: + max_version = DjVersion.parse_to_tuple(version_str=last_arg) + + main(django_max_version=max_version) diff --git a/scripts/update_changelog.py b/scripts/update_changelog.py new file mode 100644 index 0000000000..b50d25066a --- /dev/null +++ b/scripts/update_changelog.py @@ -0,0 +1,158 @@ +import datetime as dt +import os +import re +from collections.abc import Iterable +from pathlib import Path + +import git +import github.PullRequest +import github.Repository +from github import Github +from jinja2 import Template + +CURRENT_FILE = Path(__file__) +ROOT = CURRENT_FILE.parents[1] +GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") +GITHUB_REPO = os.getenv("GITHUB_REPOSITORY") +GIT_BRANCH = os.getenv("GITHUB_REF_NAME") + + +def main() -> None: + """ + Script entry point. + """ + # Generate changelog for PRs merged yesterday + merged_date = dt.date.today() - dt.timedelta(days=1) + repo = Github(login_or_token=GITHUB_TOKEN).get_repo(GITHUB_REPO) + merged_pulls = list(iter_pulls(repo, merged_date)) + print(f"Merged pull requests: {merged_pulls}") + if not merged_pulls: + print("Nothing was merged, existing.") + return + + # Group pull requests by type of change + grouped_pulls = group_pulls_by_change_type(merged_pulls) + + # Generate portion of markdown + release_changes_summary = generate_md(grouped_pulls) + print(f"Summary of changes: {release_changes_summary}") + + # Update CHANGELOG.md file + release = f"{merged_date:%Y.%m.%d}" + changelog_path = ROOT / "CHANGELOG.md" + write_changelog(changelog_path, release, release_changes_summary) + print(f"Wrote {changelog_path}") + + # Update version + setup_py_path = ROOT / "setup.py" + update_version(setup_py_path, release) + print(f"Updated version in {setup_py_path}") + + # Commit changes, create tag and push + update_git_repo([changelog_path, setup_py_path], release) + + # Create GitHub release + github_release = repo.create_git_release( + tag=release, + name=release, + message=release_changes_summary, + ) + print(f"Created release on GitHub {github_release}") + + +def iter_pulls( + repo: github.Repository.Repository, + merged_date: dt.date, +) -> Iterable[github.PullRequest.PullRequest]: + """Fetch merged pull requests at the date we're interested in.""" + recent_pulls = repo.get_pulls( + state="closed", + sort="updated", + direction="desc", + ).get_page(0) + for pull in recent_pulls: + if pull.merged and pull.merged_at.date() == merged_date: + yield pull + + +def group_pulls_by_change_type( + pull_requests_list: list[github.PullRequest.PullRequest], +) -> dict[str, list[github.PullRequest.PullRequest]]: + """Group pull request by change type.""" + grouped_pulls = { + "Changed": [], + "Fixed": [], + "Updated": [], + } + for pull in pull_requests_list: + label_names = {label.name for label in pull.labels} + if "update" in label_names: + group_name = "Updated" + elif "bug" in label_names: + group_name = "Fixed" + else: + group_name = "Changed" + grouped_pulls[group_name].append(pull) + return grouped_pulls + + +def generate_md(grouped_pulls: dict[str, list[github.PullRequest.PullRequest]]) -> str: + """Generate markdown file from Jinja template.""" + changelog_template = ROOT / ".github" / "changelog-template.md" + template = Template(changelog_template.read_text(), autoescape=True) + return template.render(grouped_pulls=grouped_pulls) + + +def write_changelog(file_path: Path, release: str, content: str) -> None: + """Write Release details to the changelog file.""" + content = f"## {release}\n{content}" + old_content = file_path.read_text() + updated_content = old_content.replace( + "", + f"\n\n{content}", + ) + file_path.write_text(updated_content) + + +def update_version(file_path: Path, release: str) -> None: + """Update template version in setup.py.""" + old_content = file_path.read_text() + updated_content = re.sub( + r'\nversion = "\d+\.\d+\.\d+"\n', + f'\nversion = "{release}"\n', + old_content, + ) + file_path.write_text(updated_content) + + +def update_git_repo(paths: list[Path], release: str) -> None: + """Commit, tag changes in git repo and push to origin.""" + repo = git.Repo(ROOT) + for path in paths: + repo.git.add(path) + message = f"Release {release}" + + user = repo.git.config("--get", "user.name") + email = repo.git.config("--get", "user.email") + + repo.git.commit( + m=message, + author=f"{user} <{email}>", + ) + repo.git.tag("-a", release, m=message) + server = f"https://{GITHUB_TOKEN}@github.com/{GITHUB_REPO}.git" + print(f"Pushing changes to {GIT_BRANCH} branch of {GITHUB_REPO}") + repo.git.push(server, GIT_BRANCH) + repo.git.push("--tags", server, GIT_BRANCH) + + +if __name__ == "__main__": + if GITHUB_REPO is None: + raise RuntimeError( + "No github repo, please set the environment variable GITHUB_REPOSITORY" + ) + if GIT_BRANCH is None: + raise RuntimeError( + "No git branch set, please set the GITHUB_REF_NAME environment variable" + ) + main() diff --git a/scripts/update_contributors.py b/scripts/update_contributors.py new file mode 100644 index 0000000000..76ccf60ade --- /dev/null +++ b/scripts/update_contributors.py @@ -0,0 +1,112 @@ +import json +import os +from pathlib import Path + +from github import Github +from github.NamedUser import NamedUser +from jinja2 import Template + +CURRENT_FILE = Path(__file__) +ROOT = CURRENT_FILE.parents[1] +BOT_LOGINS = ["pyup-bot"] +GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", None) +GITHUB_REPO = os.getenv("GITHUB_REPOSITORY", None) + + +def main() -> None: + """ + Script entry point. + + 1. Fetch recent contributors from the Github API + 2. Add missing ones to the JSON file + 3. Generate Markdown from JSON file + """ + recent_authors = set(iter_recent_authors()) + + # Add missing users to the JSON file + contrib_file = ContributorsJSONFile() + for author in recent_authors: + print(f"Checking if {author.login} should be added") + if author.login not in contrib_file: + contrib_file.add_contributor(author) + print(f"Added {author.login} to contributors") + contrib_file.save() + + # Generate MD file from JSON file + write_md_file(contrib_file.content) + + +def iter_recent_authors(): + """ + Fetch users who opened recently merged pull requests. + + Use Github API to fetch recent authors rather than + git CLI to work with Github usernames. + """ + repo = Github(login_or_token=GITHUB_TOKEN, per_page=5).get_repo(GITHUB_REPO) + recent_pulls = repo.get_pulls( + state="closed", sort="updated", direction="desc" + ).get_page(0) + for pull in recent_pulls: + if ( + pull.merged + and pull.user.type == "User" + and pull.user.login not in BOT_LOGINS + ): + yield pull.user + + +class ContributorsJSONFile: + """Helper to interact with the JSON file.""" + + file_path = ROOT / ".github" / "contributors.json" + content = None + + def __init__(self) -> None: + """Read initial content.""" + self.content = json.loads(self.file_path.read_text()) + + def __contains__(self, github_login: str): + """Provide a nice API to do: `username in file`.""" + return any( + # Github usernames are case insensitive + github_login.lower() == contrib["github_login"].lower() + for contrib in self.content + ) + + def add_contributor(self, user: NamedUser): + """Append the contributor data we care about at the end.""" + contributor_data = { + "name": user.name or user.login, + "github_login": user.login, + "twitter_username": user.twitter_username or "", + } + self.content.append(contributor_data) + + def save(self): + """Write the file to disk with indentation.""" + text_content = json.dumps(self.content, indent=2, ensure_ascii=False) + self.file_path.write_text(text_content) + + +def write_md_file(contributors): + """Generate markdown file from Jinja template.""" + contributors_template = ROOT / ".github" / "CONTRIBUTORS-template.md" + template = Template(contributors_template.read_text(), autoescape=True) + core_contributors = [c for c in contributors if c.get("is_core", False)] + other_contributors = (c for c in contributors if not c.get("is_core", False)) + other_contributors = sorted(other_contributors, key=lambda c: c["name"].lower()) + content = template.render( + core_contributors=core_contributors, other_contributors=other_contributors + ) + + file_path = ROOT / "CONTRIBUTORS.md" + file_path.write_text(content) + + +if __name__ == "__main__": + if GITHUB_REPO is None: + raise RuntimeError( + "No github repo, please set the environment variable GITHUB_REPOSITORY" + ) + main() diff --git a/setup.cfg b/setup.cfg index c5b3019911..dd8f1ef3c1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,7 @@ -[pytest] -python_paths = . -norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/* +[flake8] +exclude = docs +max-line-length = 88 + +[isort] +profile = black +known_first_party = tests,scripts,hooks diff --git a/setup.py b/setup.py index 97444577f4..8100edc12c 100644 --- a/setup.py +++ b/setup.py @@ -1,55 +1,44 @@ #!/usr/bin/env python - -import os -import sys - try: from setuptools import setup except ImportError: from distutils.core import setup -# Our version ALWAYS matches the version of Django we support -# If Django has a new release, we branch, tag, then update this setting after the tag. -version = '1.9.6b' +# We use calendar versioning +version = "2022.11.07" -if sys.argv[-1] == 'tag': - os.system('git tag -a %s -m "version %s"' % (version, version)) - os.system('git push --tags') - sys.exit() - -with open('README.rst') as readme_file: +with open("README.rst") as readme_file: long_description = readme_file.read() setup( - name='cookiecutter-django', + name="cookiecutter-django", version=version, - description='A Cookiecutter template for creating production-ready Django projects quickly.', + description=( + "A Cookiecutter template for creating production-ready " + "Django projects quickly." + ), long_description=long_description, - author='Daniel Roy Greenfeld', - author_email='pydanny@gmail.com', - url='https://github.com/pydanny/cookiecutter-django', + author="Daniel Roy Greenfeld", + author_email="pydanny@gmail.com", + url="https://github.com/cookiecutter/cookiecutter-django", packages=[], - license='BSD', + license="BSD", zip_safe=False, classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: Console', - 'Framework :: Django :: 1.9', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Software Development', + "Development Status :: 4 - Beta", + "Environment :: Console", + "Framework :: Django :: 4.0", + "Intended Audience :: Developers", + "Natural Language :: English", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Software Development", ], keywords=( - 'cookiecutter, Python, projects, project templates, django, ' - 'skeleton, scaffolding, project directory, setup.py' + "cookiecutter, Python, projects, project templates, django, " + "skeleton, scaffolding, project directory, setup.py" ), ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_bare.sh b/tests/test_bare.sh new file mode 100755 index 0000000000..05da9328bb --- /dev/null +++ b/tests/test_bare.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# this is a very simple script that tests the docker configuration for cookiecutter-django +# it is meant to be run from the root directory of the repository, eg: +# sh tests/test_bare.sh + +set -o errexit +set -x + +# create a cache directory +mkdir -p .cache/bare +cd .cache/bare + +# create the project using the default settings in cookiecutter.json +cookiecutter ../../ --no-input --overwrite-if-exists use_docker=n "$@" +cd my_awesome_project + +# Install OS deps +sudo utility/install_os_dependencies.sh install + +# Install Python deps +pip install -r requirements/local.txt + +# Lint by running pre-commit on all files +# Needs a git repo to find the project root +git init +git add . +pre-commit run --show-diff-on-failure -a + +# run the project's tests +pytest + +# Make sure the check doesn't raise any warnings +python manage.py check --fail-level WARNING + +if [ -f "package.json" ] +then + npm install + if [ -f "gulpfile.js" ] + then + npm run build + fi +fi + +# Generate the HTML for the documentation +cd docs && make html diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py old mode 100644 new mode 100755 index 6815751a81..3a757fcb7b --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -1,32 +1,127 @@ -# -*- coding: utf-8 -*- - import os import re -import sh +import sys import pytest + +try: + import sh +except (ImportError, ModuleNotFoundError): + sh = None # sh doesn't support Windows +import yaml from binaryornot.check import is_binary +from cookiecutter.exceptions import FailedHookException -PATTERN = '{{(\s?cookiecutter)[.](.*?)}}' +PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}" RE_OBJ = re.compile(PATTERN) +if sys.platform.startswith("win"): + pytest.skip("sh doesn't support windows", allow_module_level=True) +elif sys.platform.startswith("darwin") and os.getenv("CI"): + pytest.skip("skipping slow macOS tests on CI", allow_module_level=True) + @pytest.fixture def context(): return { - 'project_name': 'My Test Project', - 'project_slug': 'my_test_project', - 'author_name': 'Test Author', - 'email': 'test@example.com', - 'description': 'A short description of the project.', - 'domain_name': 'example.com', - 'version': '0.1.0', - 'timezone': 'UTC', - 'now': '2015/01/13', - 'year': '2015' + "project_name": "My Test Project", + "project_slug": "my_test_project", + "author_name": "Test Author", + "email": "test@example.com", + "description": "A short description of the project.", + "domain_name": "example.com", + "version": "0.1.0", + "timezone": "UTC", } +SUPPORTED_COMBINATIONS = [ + {"open_source_license": "MIT"}, + {"open_source_license": "BSD"}, + {"open_source_license": "GPLv3"}, + {"open_source_license": "Apache Software License 2.0"}, + {"open_source_license": "Not open source"}, + {"windows": "y"}, + {"windows": "n"}, + {"use_pycharm": "y"}, + {"use_pycharm": "n"}, + {"use_docker": "y"}, + {"use_docker": "n"}, + {"postgresql_version": "14"}, + {"postgresql_version": "13"}, + {"postgresql_version": "12"}, + {"postgresql_version": "11"}, + {"postgresql_version": "10"}, + {"cloud_provider": "AWS", "use_whitenoise": "y"}, + {"cloud_provider": "AWS", "use_whitenoise": "n"}, + {"cloud_provider": "GCP", "use_whitenoise": "y"}, + {"cloud_provider": "GCP", "use_whitenoise": "n"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailgun"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailjet"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mandrill"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Postmark"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Sendgrid"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "SendinBlue"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "SparkPost"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Other SMTP"}, + # Note: cloud_provider=None AND use_whitenoise=n is not supported + {"cloud_provider": "AWS", "mail_service": "Mailgun"}, + {"cloud_provider": "AWS", "mail_service": "Amazon SES"}, + {"cloud_provider": "AWS", "mail_service": "Mailjet"}, + {"cloud_provider": "AWS", "mail_service": "Mandrill"}, + {"cloud_provider": "AWS", "mail_service": "Postmark"}, + {"cloud_provider": "AWS", "mail_service": "Sendgrid"}, + {"cloud_provider": "AWS", "mail_service": "SendinBlue"}, + {"cloud_provider": "AWS", "mail_service": "SparkPost"}, + {"cloud_provider": "AWS", "mail_service": "Other SMTP"}, + {"cloud_provider": "GCP", "mail_service": "Mailgun"}, + {"cloud_provider": "GCP", "mail_service": "Mailjet"}, + {"cloud_provider": "GCP", "mail_service": "Mandrill"}, + {"cloud_provider": "GCP", "mail_service": "Postmark"}, + {"cloud_provider": "GCP", "mail_service": "Sendgrid"}, + {"cloud_provider": "GCP", "mail_service": "SendinBlue"}, + {"cloud_provider": "GCP", "mail_service": "SparkPost"}, + {"cloud_provider": "GCP", "mail_service": "Other SMTP"}, + # Note: cloud_providers GCP and None with mail_service Amazon SES is not supported + {"use_async": "y"}, + {"use_async": "n"}, + {"use_drf": "y"}, + {"use_drf": "n"}, + {"frontend_pipeline": "None"}, + {"frontend_pipeline": "Django Compressor"}, + {"frontend_pipeline": "Gulp"}, + {"use_celery": "y"}, + {"use_celery": "n"}, + {"use_mailhog": "y"}, + {"use_mailhog": "n"}, + {"use_sentry": "y"}, + {"use_sentry": "n"}, + {"use_whitenoise": "y"}, + {"use_whitenoise": "n"}, + {"use_heroku": "y"}, + {"use_heroku": "n"}, + {"ci_tool": "None"}, + {"ci_tool": "Travis"}, + {"ci_tool": "Gitlab"}, + {"ci_tool": "Github"}, + {"keep_local_envs_in_vcs": "y"}, + {"keep_local_envs_in_vcs": "n"}, + {"debug": "y"}, + {"debug": "n"}, +] + +UNSUPPORTED_COMBINATIONS = [ + {"cloud_provider": "None", "use_whitenoise": "n"}, + {"cloud_provider": "GCP", "mail_service": "Amazon SES"}, + {"cloud_provider": "None", "mail_service": "Amazon SES"}, +] + + +def _fixture_id(ctx): + """Helper to get a user-friendly test name from the parametrized context.""" + return "-".join(f"{key}:{value}" for key, value in ctx.items()) + + def build_files_list(root_dir): """Build a list containing absolute paths to the generated files.""" return [ @@ -37,54 +132,182 @@ def build_files_list(root_dir): def check_paths(paths): - """Method to check all paths have correct substitutions, - used by other tests cases - """ + """Method to check all paths have correct substitutions.""" # Assert that no match is found in any of the files for path in paths: if is_binary(path): continue - for line in open(path, 'r'): + + for line in open(path): match = RE_OBJ.search(line) - msg = 'cookiecutter variable not replaced in {}' - assert match is None, msg.format(path) + assert match is None, f"cookiecutter variable not replaced in {path}" -def test_default_configuration(cookies, context): - result = cookies.bake(extra_context=context) +@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) +def test_project_generation(cookies, context, context_override): + """Test that project is generated and fully rendered.""" + + result = cookies.bake(extra_context={**context, **context_override}) assert result.exit_code == 0 assert result.exception is None - assert result.project.basename == context['project_slug'] - assert result.project.isdir() + assert result.project_path.name == context["project_slug"] + assert result.project_path.is_dir() - paths = build_files_list(str(result.project)) + paths = build_files_list(str(result.project_path)) assert paths check_paths(paths) -@pytest.fixture(params=['use_mailhog', 'use_celery', 'windows']) -def feature_context(request, context): - context.update({request.param: 'y'}) - return context +@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) +def test_flake8_passes(cookies, context_override): + """Generated project should pass flake8.""" + result = cookies.bake(extra_context=context_override) + try: + sh.flake8(_cwd=str(result.project_path)) + except sh.ErrorReturnCode as e: + pytest.fail(e.stdout.decode()) + + +@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id) +def test_black_passes(cookies, context_override): + """Generated project should pass black.""" + result = cookies.bake(extra_context=context_override) + + try: + sh.black( + "--check", + "--diff", + "--exclude", + "migrations", + ".", + _cwd=str(result.project_path), + ) + except sh.ErrorReturnCode as e: + pytest.fail(e.stdout.decode()) + + +@pytest.mark.parametrize( + ["use_docker", "expected_test_script"], + [ + ("n", "pytest"), + ("y", "docker-compose -f local.yml run django pytest"), + ], +) +def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_script): + context.update({"ci_tool": "Travis", "use_docker": use_docker}) + result = cookies.bake(extra_context=context) -def test_enabled_features(cookies, feature_context): - result = cookies.bake(extra_context=feature_context) assert result.exit_code == 0 assert result.exception is None - assert result.project.basename == feature_context['project_slug'] - assert result.project.isdir() + assert result.project_path.name == context["project_slug"] + assert result.project_path.is_dir() - paths = build_files_list(str(result.project)) - assert paths - check_paths(paths) + with open(f"{result.project_path}/.travis.yml") as travis_yml: + try: + yml = yaml.safe_load(travis_yml)["jobs"]["include"] + assert yml[0]["script"] == ["flake8"] + assert yml[1]["script"] == [expected_test_script] + except yaml.YAMLError as e: + pytest.fail(str(e)) -def test_flake8_compliance(cookies): - """generated project should pass flake8""" - result = cookies.bake() +@pytest.mark.parametrize( + ["use_docker", "expected_test_script"], + [ + ("n", "pytest"), + ("y", "docker-compose -f local.yml run django pytest"), + ], +) +def test_gitlab_invokes_flake8_and_pytest( + cookies, context, use_docker, expected_test_script +): + context.update({"ci_tool": "Gitlab", "use_docker": use_docker}) + result = cookies.bake(extra_context=context) - try: - sh.flake8(str(result.project)) - except sh.ErrorReturnCode as e: - pytest.fail(e) + assert result.exit_code == 0 + assert result.exception is None + assert result.project_path.name == context["project_slug"] + assert result.project_path.is_dir() + + with open(f"{result.project_path}/.gitlab-ci.yml") as gitlab_yml: + try: + gitlab_config = yaml.safe_load(gitlab_yml) + assert gitlab_config["flake8"]["script"] == ["flake8"] + assert gitlab_config["pytest"]["script"] == [expected_test_script] + except yaml.YAMLError as e: + pytest.fail(e) + + +@pytest.mark.parametrize( + ["use_docker", "expected_test_script"], + [ + ("n", "pytest"), + ("y", "docker-compose -f local.yml run django pytest"), + ], +) +def test_github_invokes_linter_and_pytest( + cookies, context, use_docker, expected_test_script +): + context.update({"ci_tool": "Github", "use_docker": use_docker}) + result = cookies.bake(extra_context=context) + + assert result.exit_code == 0 + assert result.exception is None + assert result.project_path.name == context["project_slug"] + assert result.project_path.is_dir() + + with open(f"{result.project_path}/.github/workflows/ci.yml") as github_yml: + try: + github_config = yaml.safe_load(github_yml) + linter_present = False + for action_step in github_config["jobs"]["linter"]["steps"]: + if action_step.get("uses", "NA").startswith("pre-commit"): + linter_present = True + assert linter_present + + expected_test_script_present = False + for action_step in github_config["jobs"]["pytest"]["steps"]: + if action_step.get("run") == expected_test_script: + expected_test_script_present = True + assert expected_test_script_present + except yaml.YAMLError as e: + pytest.fail(e) + + +@pytest.mark.parametrize("slug", ["project slug", "Project_Slug"]) +def test_invalid_slug(cookies, context, slug): + """Invalid slug should fail pre-generation hook.""" + context.update({"project_slug": slug}) + + result = cookies.bake(extra_context=context) + + assert result.exit_code != 0 + assert isinstance(result.exception, FailedHookException) + + +@pytest.mark.parametrize("invalid_context", UNSUPPORTED_COMBINATIONS) +def test_error_if_incompatible(cookies, context, invalid_context): + """It should not generate project an incompatible combination is selected.""" + context.update(invalid_context) + result = cookies.bake(extra_context=context) + + assert result.exit_code != 0 + assert isinstance(result.exception, FailedHookException) + + +@pytest.mark.parametrize( + ["use_pycharm", "pycharm_docs_exist"], + [ + ("n", False), + ("y", True), + ], +) +def test_pycharm_docs_removed(cookies, context, use_pycharm, pycharm_docs_exist): + """.""" + context.update({"use_pycharm": use_pycharm}) + result = cookies.bake(extra_context=context) + + with open(f"{result.project_path}/docs/index.rst") as f: + has_pycharm_docs = "pycharm/configuration" in f.read() + assert has_pycharm_docs is pycharm_docs_exist diff --git a/tests/test_docker.sh b/tests/test_docker.sh old mode 100644 new mode 100755 index 30a27dad35..b3663bd2cf --- a/tests/test_docker.sh +++ b/tests/test_docker.sh @@ -3,16 +3,41 @@ # it is meant to be run from the root directory of the repository, eg: # sh tests/test_docker.sh -# install test requirements -pip install -r requirements.txt +set -o errexit +set -x # create a cache directory mkdir -p .cache/docker cd .cache/docker # create the project using the default settings in cookiecutter.json -cookiecutter ../../ --no-input --overwrite-if-exists -cd project_name +cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y "$@" +cd my_awesome_project + +# Lint by running pre-commit on all files +# Needs a git repo to find the project root +# We don't have git inside Docker, so run it outside +git init +git add . +pre-commit run --show-diff-on-failure -a + +# make sure all images build +docker-compose -f local.yml build + +# 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 dev.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; } + +# Test support for translations +docker-compose -f local.yml run django python manage.py makemessages --all + +# Make sure the check doesn't raise any warnings +docker-compose -f local.yml run django python manage.py check --fail-level WARNING + +# Generate the HTML for the documentation +docker-compose -f local.yml run docs make html diff --git a/tests/test_hooks.py b/tests/test_hooks.py new file mode 100644 index 0000000000..7ca752722b --- /dev/null +++ b/tests/test_hooks.py @@ -0,0 +1,28 @@ +"""Unit tests for the hooks""" +import os +from pathlib import Path + +import pytest + +from hooks.post_gen_project import append_to_gitignore_file + + +@pytest.fixture() +def working_directory(tmp_path): + prev_cwd = Path.cwd() + os.chdir(tmp_path) + try: + yield tmp_path + finally: + os.chdir(prev_cwd) + + +def test_append_to_gitignore_file(working_directory): + gitignore_file = working_directory / ".gitignore" + gitignore_file.write_text("node_modules/\n") + append_to_gitignore_file(".envs/*") + linesep = os.linesep.encode() + assert ( + gitignore_file.read_bytes() == b"node_modules/" + linesep + b".envs/*" + linesep + ) + assert gitignore_file.read_text() == "node_modules/\n.envs/*\n" diff --git a/tox.ini b/tox.ini index deb1dd5037..0400e4f919 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,11 @@ [tox] skipsdist = true -envlist = py27,py34,py35 +envlist = py310,black-template [testenv] -passenv = LC_ALL, LANG, HOME -deps = - binaryornot - flake8 - pytest-cookies - sh -commands = py.test {posargs:tests} +deps = -rrequirements.txt +commands = pytest {posargs:./tests} + +[testenv:black-template] +deps = black +commands = black --check hooks tests setup.py docs scripts diff --git a/{{cookiecutter.project_slug}}/.coveragerc b/{{cookiecutter.project_slug}}/.coveragerc deleted file mode 100644 index 283a4b89c7..0000000000 --- a/{{cookiecutter.project_slug}}/.coveragerc +++ /dev/null @@ -1,5 +0,0 @@ -[run] -include = {{cookiecutter.project_slug}}/* -omit = *migrations*, *tests* -plugins = - django_coverage_plugin diff --git a/{{cookiecutter.project_slug}}/.dockerignore b/{{cookiecutter.project_slug}}/.dockerignore index e63c0c1844..5518e60af1 100644 --- a/{{cookiecutter.project_slug}}/.dockerignore +++ b/{{cookiecutter.project_slug}}/.dockerignore @@ -1,4 +1,10 @@ -.* -!.coveragerc -!.env -!.pylintrc +.editorconfig +.gitattributes +.github +.gitignore +.gitlab-ci.yml +.idea +.pre-commit-config.yaml +.readthedocs.yml +.travis.yml +venv diff --git a/{{cookiecutter.project_slug}}/.editorconfig b/{{cookiecutter.project_slug}}/.editorconfig index 9759b2ca6f..6a9a5c45df 100644 --- a/{{cookiecutter.project_slug}}/.editorconfig +++ b/{{cookiecutter.project_slug}}/.editorconfig @@ -12,13 +12,7 @@ trim_trailing_whitespace = true indent_style = space indent_size = 4 -[*.py] -line_length=120 -known_first_party={{ cookiecutter.project_slug }} -multi_line_output=3 -default_section=THIRDPARTY - -[*.{html,css,scss,json,yml}] +[*.{html,css,scss,json,yml,xml}] indent_style = space indent_size = 2 @@ -27,3 +21,7 @@ trim_trailing_whitespace = false [Makefile] indent_style = tab + +[nginx.conf] +indent_style = space +indent_size = 2 diff --git a/{{cookiecutter.project_slug}}/.envs/.local/.django b/{{cookiecutter.project_slug}}/.envs/.local/.django new file mode 100644 index 0000000000..ef581a1c09 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.envs/.local/.django @@ -0,0 +1,17 @@ +# General +# ------------------------------------------------------------------------------ +USE_DOCKER=yes +IPYTHONDIR=/app/.ipython + +{%- if cookiecutter.use_celery == 'y' %} +# Redis +# ------------------------------------------------------------------------------ +REDIS_URL=redis://redis:6379/0 + +# Celery +# ------------------------------------------------------------------------------ + +# Flower +CELERY_FLOWER_USER=!!!SET CELERY_FLOWER_USER!!! +CELERY_FLOWER_PASSWORD=!!!SET CELERY_FLOWER_PASSWORD!!! +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/.envs/.local/.postgres b/{{cookiecutter.project_slug}}/.envs/.local/.postgres new file mode 100644 index 0000000000..f190db8e68 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.envs/.local/.postgres @@ -0,0 +1,7 @@ +# PostgreSQL +# ------------------------------------------------------------------------------ +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 +POSTGRES_DB={{ cookiecutter.project_slug }} +POSTGRES_USER=!!!SET POSTGRES_USER!!! +POSTGRES_PASSWORD=!!!SET POSTGRES_PASSWORD!!! diff --git a/{{cookiecutter.project_slug}}/.envs/.production/.django b/{{cookiecutter.project_slug}}/.envs/.production/.django new file mode 100644 index 0000000000..e7e8461c96 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.envs/.production/.django @@ -0,0 +1,71 @@ +# General +# ------------------------------------------------------------------------------ +# DJANGO_READ_DOT_ENV_FILE=True +DJANGO_SETTINGS_MODULE=config.settings.production +DJANGO_SECRET_KEY=!!!SET DJANGO_SECRET_KEY!!! +DJANGO_ADMIN_URL=!!!SET DJANGO_ADMIN_URL!!! +DJANGO_ALLOWED_HOSTS=.{{ cookiecutter.domain_name }} + +# Security +# ------------------------------------------------------------------------------ +# TIP: better off using DNS, however, redirect is OK too +DJANGO_SECURE_SSL_REDIRECT=False + +# Email +# ------------------------------------------------------------------------------ +DJANGO_SERVER_EMAIL= +{% if cookiecutter.mail_service == 'Mailgun' %} +MAILGUN_API_KEY= +MAILGUN_DOMAIN= +{% elif cookiecutter.mail_service == 'Mailjet' %} +MAILJET_API_KEY= +MAILJET_SECRET_KEY= +{% elif cookiecutter.mail_service == 'Mandrill' %} +MANDRILL_API_KEY= +{% elif cookiecutter.mail_service == 'Postmark' %} +POSTMARK_SERVER_TOKEN= +{% elif cookiecutter.mail_service == 'Sendgrid' %} +SENDGRID_API_KEY= +SENDGRID_GENERATE_MESSAGE_ID=True +SENDGRID_MERGE_FIELD_FORMAT=None +{% elif cookiecutter.mail_service == 'SendinBlue' %} +SENDINBLUE_API_KEY= +{% elif cookiecutter.mail_service == 'SparkPost' %} +SPARKPOST_API_KEY= +{% endif %} +{% if cookiecutter.cloud_provider == 'AWS' %} +# AWS +# ------------------------------------------------------------------------------ +DJANGO_AWS_ACCESS_KEY_ID= +DJANGO_AWS_SECRET_ACCESS_KEY= +DJANGO_AWS_STORAGE_BUCKET_NAME= +{% elif cookiecutter.cloud_provider == 'GCP' %} +# GCP +# ------------------------------------------------------------------------------ +GOOGLE_APPLICATION_CREDENTIALS= +DJANGO_GCP_STORAGE_BUCKET_NAME= +{% endif %} +# django-allauth +# ------------------------------------------------------------------------------ +DJANGO_ACCOUNT_ALLOW_REGISTRATION=True + +# Gunicorn +# ------------------------------------------------------------------------------ +WEB_CONCURRENCY=4 +{% if cookiecutter.use_sentry == 'y' %} +# Sentry +# ------------------------------------------------------------------------------ +SENTRY_DSN= +{% endif %} + +# 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 %} diff --git a/{{cookiecutter.project_slug}}/.envs/.production/.postgres b/{{cookiecutter.project_slug}}/.envs/.production/.postgres new file mode 100644 index 0000000000..f190db8e68 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.envs/.production/.postgres @@ -0,0 +1,7 @@ +# PostgreSQL +# ------------------------------------------------------------------------------ +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 +POSTGRES_DB={{ cookiecutter.project_slug }} +POSTGRES_USER=!!!SET POSTGRES_USER!!! +POSTGRES_PASSWORD=!!!SET POSTGRES_PASSWORD!!! diff --git a/{{cookiecutter.project_slug}}/.github/dependabot.yml b/{{cookiecutter.project_slug}}/.github/dependabot.yml new file mode 100644 index 0000000000..420a63cdcc --- /dev/null +++ b/{{cookiecutter.project_slug}}/.github/dependabot.yml @@ -0,0 +1,95 @@ +# Config for Dependabot updates. See Documentation here: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Update GitHub actions in workflows + - package-ecosystem: "github-actions" + directory: "/" + # Check for updates to GitHub Actions every weekday + schedule: + interval: "daily" + +{%- if cookiecutter.use_docker == 'y' %} + + # Enable version updates for Docker + # We need to specify each Dockerfile in a separate entry because Dependabot doesn't + # support wildcards or recursively checking subdirectories. Check this issue for updates: + # https://github.com/dependabot/dependabot-core/issues/2178 + - package-ecosystem: "docker" + # Look for a `Dockerfile` in the `compose/local/django` directory + directory: "compose/local/django/" + # Check for updates to GitHub Actions every weekday + schedule: + interval: "daily" + + # Enable version updates for Docker + - package-ecosystem: "docker" + # Look for a `Dockerfile` in the `compose/local/docs` directory + directory: "compose/local/docs/" + # Check for updates to GitHub Actions every weekday + schedule: + interval: "daily" + + # Enable version updates for Docker + - package-ecosystem: "docker" + # Look for a `Dockerfile` in the `compose/local/node` directory + directory: "compose/local/node/" + # Check for updates to GitHub Actions every weekday + schedule: + interval: "daily" + + # Enable version updates for Docker + - package-ecosystem: "docker" + # Look for a `Dockerfile` in the `compose/production/aws` directory + directory: "compose/production/aws/" + # Check for updates to GitHub Actions every weekday + schedule: + interval: "daily" + + # Enable version updates for Docker + - package-ecosystem: "docker" + # Look for a `Dockerfile` in the `compose/production/django` directory + directory: "compose/production/django/" + # Check for updates to GitHub Actions every weekday + schedule: + interval: "daily" + + # Enable version updates for Docker + - package-ecosystem: "docker" + # Look for a `Dockerfile` in the `compose/production/postgres` directory + directory: "compose/production/postgres/" + # Check for updates to GitHub Actions every weekday + schedule: + interval: "daily" + + # Enable version updates for Docker + - package-ecosystem: "docker" + # Look for a `Dockerfile` in the `compose/production/traefik` directory + directory: "compose/production/traefik/" + # Check for updates to GitHub Actions every weekday + schedule: + interval: "daily" + +{%- endif %} + + # Enable version updates for Python/Pip - Production + - package-ecosystem: "pip" + # Look for a `requirements.txt` in the `root` directory + # also 'setup.cfg', 'runtime.txt' and 'requirements/*.txt' + directory: "/" + # Check for updates to GitHub Actions every weekday + schedule: + interval: "daily" + +{%- if cookiecutter.frontend_pipeline == 'Gulp' %} + + # Enable version updates for javascript/npm + - package-ecosystem: "npm" + # Look for a `packages.json' in the `root` directory + directory: "/" + # Check for updates to GitHub Actions every weekday + schedule: + interval: "daily" + +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml new file mode 100644 index 0000000000..0790187bd5 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml @@ -0,0 +1,103 @@ +name: CI + +# Enable Buildkit and let compose use it to speed up image building +env: + DOCKER_BUILDKIT: 1 + COMPOSE_DOCKER_CLI_BUILD: 1 + +on: + pull_request: + branches: [ "master", "main" ] + paths-ignore: [ "docs/**" ] + + push: + branches: [ "master", "main" ] + paths-ignore: [ "docs/**" ] + +concurrency: + group: {% raw %}${{ github.head_ref || github.run_id }}{% endraw %} + cancel-in-progress: true + +jobs: + linter: + runs-on: ubuntu-latest + steps: + + - name: Checkout Code Repository + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: "3.10" + cache: pip + cache-dependency-path: | + requirements/base.txt + requirements/local.txt + + - name: Run pre-commit + uses: pre-commit/action@v2.0.3 + + # With no caching at all the entire ci process takes 4m 30s to complete! + pytest: + runs-on: ubuntu-latest + {%- if cookiecutter.use_docker == 'n' %} + + services: + {%- if cookiecutter.use_celery == 'y' %} + redis: + image: redis:6 + ports: + - 6379:6379 + {%- endif %} + postgres: + image: postgres:12 + ports: + - 5432:5432 + env: + POSTGRES_PASSWORD: postgres + + env: + {%- if cookiecutter.use_celery == 'y' %} + CELERY_BROKER_URL: "redis://localhost:6379/0" + {%- endif %} + # postgres://user:password@host:port/database + DATABASE_URL: "postgres://postgres:postgres@localhost:5432/postgres" + {%- endif %} + + steps: + + - name: Checkout Code Repository + uses: actions/checkout@v3 + {%- if cookiecutter.use_docker == 'y' %} + + - name: Build the Stack + run: docker-compose -f local.yml build + + - name: Run DB Migrations + run: docker-compose -f local.yml run --rm django python manage.py migrate + + - name: Run Django Tests + run: docker-compose -f local.yml run django pytest + + - name: Tear down the Stack + run: docker-compose -f local.yml down + {%- else %} + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: "3.10" + cache: pip + cache-dependency-path: | + requirements/base.txt + requirements/local.txt + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements/local.txt + + - name: Test with pytest + run: pytest + {%- endif %} diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_slug}}/.gitignore index f2d14296fa..ede26ef722 100644 --- a/{{cookiecutter.project_slug}}/.gitignore +++ b/{{cookiecutter.project_slug}}/.gitignore @@ -1,76 +1,346 @@ -### OSX ### -.DS_Store -.AppleDouble -.LSOverride - -### SublimeText ### -# cache files for sublime text -*.tmlanguage.cache -*.tmPreferences.cache -*.stTheme.cache - -# workspace files are user-specific -*.sublime-workspace +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class -# project files should be checked into the repository, unless a significant -# proportion of contributors will probably not be using SublimeText -# *.sublime-project +# C extensions +*.so -# sftp configuration file -sftp-config.json +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg -# Basics -*.py[cod] -__pycache__ +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec -# Logs -*.log +# Installer logs pip-log.txt +pip-delete-this-directory.txt # Unit test / coverage reports +htmlcov/ +.tox/ .coverage -.tox +.coverage.* +.cache nosetests.xml -htmlcov +coverage.xml +*.cover +.hypothesis/ # Translations *.mo *.pot -# Pycharm -.idea/* -{% if cookiecutter.use_pycharm == 'y' %} +# Django stuff: +staticfiles/ + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +{% if cookiecutter.use_celery == 'y' -%} +# celery beat schedule file +celerybeat-schedule +{%- endif %} + +# Environments +.venv +venv/ +ENV/ + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + + +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + + +### Linux template +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + + +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + + +{% if cookiecutter.use_pycharm == 'y' -%} # Provided default Pycharm Run/Debug Configurations should be tracked by git # In case of local modifications made by Pycharm, use update-index command # for each changed file, like this: # git update-index --assume-unchanged .idea/{{cookiecutter.project_slug}}.iml -!.idea/runConfigurations/ -!.idea/{{cookiecutter.project_slug}}.iml -!.idea/vcs.xml -!.idea/webResources.xml +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties {% endif %} -# Vim -*~ -*.swp -*.swo +### Windows template +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db -# npm -node_modules/ +# Dump file +*.stackdump + +# Folder config file +Desktop.ini -# Compass -.sass-cache +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### macOS template +# General +*.DS_Store +.AppleDouble +.LSOverride -# virtual environments -.env +# Icon must end with two \r +Icon -# User-uploaded media +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### SublimeText template +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings + + +### Vim template +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist + +# Auto-generated tag files +tags + +### Project template +{%- if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' %} +MailHog +{%- endif %} {{ cookiecutter.project_slug }}/media/ -# Hitch directory -tests/.hitch -{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n'%} -# MailHog binary -mailhog -{% endif %} +.pytest_cache/ + +{%- if cookiecutter.use_docker == 'y' %} +.ipython/ +{%- endif %} + +{%- if cookiecutter.frontend_pipeline == 'Gulp' %} +project.css +project.min.css +vendors.js +*.min.js +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml new file mode 100644 index 0000000000..dbb65fb73d --- /dev/null +++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml @@ -0,0 +1,52 @@ +stages: + - lint + - test + +variables: + POSTGRES_USER: '{{ cookiecutter.project_slug }}' + POSTGRES_PASSWORD: '' + POSTGRES_DB: 'test_{{ cookiecutter.project_slug }}' + POSTGRES_HOST_AUTH_METHOD: trust + {% if cookiecutter.use_celery == 'y' -%} + CELERY_BROKER_URL: 'redis://redis:6379/0' + {%- endif %} + +flake8: + stage: lint + image: python:3.10-alpine + before_script: + - pip install -q flake8 + script: + - flake8 + +pytest: + stage: test + {% if cookiecutter.use_docker == 'y' -%} + image: docker/compose:1.29.2 + tags: + - docker + services: + - docker:dind + before_script: + - docker-compose -f local.yml build + # Ensure celerybeat does not crash due to non-existent tables + - docker-compose -f local.yml run --rm django python manage.py migrate + - docker-compose -f local.yml up -d + script: + - docker-compose -f local.yml run django pytest + {%- else -%} + image: python:3.10 + tags: + - python + services: + - postgres:{{ cookiecutter.postgresql_version }} + variables: + DATABASE_URL: pgsql://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB + + before_script: + - pip install -r requirements/local.txt + + script: + - pytest + {%- endif %} + diff --git a/{{cookiecutter.project_slug}}/.idea/misc.xml b/{{cookiecutter.project_slug}}/.idea/misc.xml new file mode 100644 index 0000000000..10af178fd4 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/{{cookiecutter.project_slug}}/.idea/modules.xml b/{{cookiecutter.project_slug}}/.idea/modules.xml new file mode 100644 index 0000000000..1418fcddfb --- /dev/null +++ b/{{cookiecutter.project_slug}}/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__runserver.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__runserver.xml deleted file mode 100644 index 573f4cd6d8..0000000000 --- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__runserver.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___all.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___all.xml deleted file mode 100644 index 7ede8bfb55..0000000000 --- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___all.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___class__TestUser.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___class__TestUser.xml deleted file mode 100644 index 235832f3fb..0000000000 --- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___class__TestUser.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___file__test_models.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___file__test_models.xml deleted file mode 100644 index 6b67f69ff3..0000000000 --- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___file__test_models.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___module__users.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___module__users.xml deleted file mode 100644 index d7232cf4e5..0000000000 --- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___module__users.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___specific__test_get_absolute_url.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___specific__test_get_absolute_url.xml deleted file mode 100644 index 224e0e3835..0000000000 --- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__tests___specific__test_get_absolute_url.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_django.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_django.xml new file mode 100644 index 0000000000..ad3b6a35a6 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_django.xml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_docs.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_docs.xml new file mode 100644 index 0000000000..0f77b28dfe --- /dev/null +++ b/{{cookiecutter.project_slug}}/.idea/runConfigurations/docker_compose_up_docs.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/merge_production_dotenvs_in_dotenv.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/merge_production_dotenvs_in_dotenv.xml new file mode 100644 index 0000000000..cf2c5dd89d --- /dev/null +++ b/{{cookiecutter.project_slug}}/.idea/runConfigurations/merge_production_dotenvs_in_dotenv.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__migrate.xml b/{{cookiecutter.project_slug}}/.idea/runConfigurations/migrate.xml similarity index 80% rename from {{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__migrate.xml rename to {{cookiecutter.project_slug}}/.idea/runConfigurations/migrate.xml index 18829b2f23..b457c5f55b 100644 --- a/{{cookiecutter.project_slug}}/.idea/runConfigurations/Docker__migrate.xml +++ b/{{cookiecutter.project_slug}}/.idea/runConfigurations/migrate.xml @@ -1,17 +1,17 @@ - + + diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml new file mode 100644 index 0000000000..433d97de90 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -0,0 +1,39 @@ +exclude: "^docs/|/migrations/" +default_stages: [commit] + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + + - repo: https://github.com/asottile/pyupgrade + rev: v3.2.2 + hooks: + - id: pyupgrade + args: [--py310-plus] + + - repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black + + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + args: ["--config=setup.cfg"] + additional_dependencies: [flake8-isort] + +# sets up .pre-commit-ci.yaml to ensure pre-commit dependencies stay up to date +ci: + autoupdate_schedule: weekly + skip: [] + submodules: false diff --git a/{{cookiecutter.project_slug}}/.pylintrc b/{{cookiecutter.project_slug}}/.pylintrc index 1a19928627..9d604334bb 100644 --- a/{{cookiecutter.project_slug}}/.pylintrc +++ b/{{cookiecutter.project_slug}}/.pylintrc @@ -1,6 +1,6 @@ [MASTER] -load-plugins=pylint_common, pylint_django{% if cookiecutter.use_celery == "y" %}, pylint_celery {% endif %} - +load-plugins=pylint_django{% if cookiecutter.use_celery == "y" %}, pylint_celery{% endif %} +django-settings-module=config.settings.local [FORMAT] max-line-length=120 diff --git a/{{cookiecutter.project_slug}}/.readthedocs.yml b/{{cookiecutter.project_slug}}/.readthedocs.yml new file mode 100644 index 0000000000..e943a5fa95 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.readthedocs.yml @@ -0,0 +1,12 @@ +version: 2 + +sphinx: + configuration: docs/conf.py + +build: + image: testing + +python: + version: 3.10 + install: + - requirements: requirements/local.txt diff --git a/{{cookiecutter.project_slug}}/.travis.yml b/{{cookiecutter.project_slug}}/.travis.yml index befee87834..326d78392b 100644 --- a/{{cookiecutter.project_slug}}/.travis.yml +++ b/{{cookiecutter.project_slug}}/.travis.yml @@ -1,15 +1,45 @@ -sudo: true -before_install: - - sudo apt-get update -qq - - sudo apt-get install -qq build-essential gettext python-dev zlib1g-dev libpq-dev xvfb - - sudo apt-get install -qq libtiff4-dev libjpeg8-dev libfreetype6-dev liblcms1-dev libwebp-dev - - sudo apt-get install -qq graphviz-dev python-setuptools python3-dev python-virtualenv python-pip - - sudo apt-get install -qq firefox automake libtool libreadline6 libreadline6-dev libreadline-dev - - sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm +dist: focal + language: python python: -{% if cookiecutter.use_python2 == 'n' -%} - - "3.5" -{% else %} - - "2.7" -{%- endif %} + - "3.10" + +services: + - {% if cookiecutter.use_docker == 'y' %}docker{% else %}postgresql{% endif %} +jobs: + include: + - name: "Linter" + before_script: + - pip install -q flake8 + script: + - "flake8" + + - name: "Django Test" + {%- if cookiecutter.use_docker == 'y' %} + before_script: + - docker-compose -v + - docker -v + - docker-compose -f local.yml build + # Ensure celerybeat does not crash due to non-existent tables + - docker-compose -f local.yml run --rm django python manage.py migrate + - docker-compose -f local.yml up -d + script: + - "docker-compose -f local.yml run django pytest" + after_failure: + - docker-compose -f local.yml logs + {%- else %} + before_install: + - sudo apt-get update -qq + - sudo apt-get install -qq build-essential gettext python-dev zlib1g-dev libpq-dev xvfb + - sudo apt-get install -qq libjpeg8-dev libfreetype6-dev libwebp-dev + - sudo apt-get install -qq graphviz-dev python-setuptools python3-dev python-virtualenv python-pip + - sudo apt-get install -qq firefox automake libtool libreadline6 libreadline6-dev libreadline-dev + - sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm + language: python + python: + - "3.10" + install: + - pip install -r requirements/local.txt + script: + - "pytest" + {%- endif %} diff --git a/{{cookiecutter.project_slug}}/CONTRIBUTORS.txt b/{{cookiecutter.project_slug}}/CONTRIBUTORS.txt index 25e9ec56f6..82a80bfc17 100644 --- a/{{cookiecutter.project_slug}}/CONTRIBUTORS.txt +++ b/{{cookiecutter.project_slug}}/CONTRIBUTORS.txt @@ -1 +1 @@ -{{cookiecutter.author_name}} +{{ cookiecutter.author_name }} diff --git a/{{cookiecutter.project_slug}}/COPYING b/{{cookiecutter.project_slug}}/COPYING new file mode 100644 index 0000000000..94a9ed024d --- /dev/null +++ b/{{cookiecutter.project_slug}}/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/{{cookiecutter.project_slug}}/Gruntfile.js b/{{cookiecutter.project_slug}}/Gruntfile.js deleted file mode 100644 index e5fbcceedb..0000000000 --- a/{{cookiecutter.project_slug}}/Gruntfile.js +++ /dev/null @@ -1,138 +0,0 @@ -module.exports = function (grunt) { - - var appConfig = grunt.file.readJSON('package.json'); - - // Load grunt tasks automatically - // see: https://github.com/sindresorhus/load-grunt-tasks - require('load-grunt-tasks')(grunt); - - // Time how long tasks take. Can help when optimizing build times - // see: https://npmjs.org/package/time-grunt - require('time-grunt')(grunt); - - var pathsConfig = function (appName) { - this.app = appName || appConfig.name; - - return { - app: this.app, - templates: this.app + '/templates', - css: this.app + '/static/css', - sass: this.app + '/static/sass', - fonts: this.app + '/static/fonts', - images: this.app + '/static/images', - js: this.app + '/static/js', - manageScript: 'manage.py', - } - }; - - grunt.initConfig({ - - paths: pathsConfig(), - pkg: appConfig, - - // see: https://github.com/gruntjs/grunt-contrib-watch - watch: { - gruntfile: { - files: ['Gruntfile.js'] - }, - sass: { - files: ['<%= paths.sass %>/**/*.{scss,sass}'], - tasks: ['sass:dev'], - options: { - atBegin: true - } - }, - livereload: { - files: [ - '<%= paths.js %>/**/*.js', - '<%= paths.sass %>/**/*.{scss,sass}', - '<%= paths.app %>/**/*.html' - ], - options: { - spawn: false, - livereload: true, - }, - }, - }, - - // see: https://github.com/sindresorhus/grunt-sass - sass: { - dev: { - options: { - outputStyle: 'nested', - sourceMap: false, - precision: 10 - }, - files: { - '<%= paths.css %>/project.css': '<%= paths.sass %>/project.scss' - }, - }, - dist: { - options: { - outputStyle: 'compressed', - sourceMap: false, - precision: 10 - }, - files: { - '<%= paths.css %>/project.css': '<%= paths.sass %>/project.scss' - }, - } - }, - - //see https://github.com/nDmitry/grunt-postcss - postcss: { - options: { - map: true, // inline sourcemaps - - processors: [ - require('pixrem')(), // add fallbacks for rem units - require('autoprefixer-core')({browsers: [ - 'Android 2.3', - 'Android >= 4', - 'Chrome >= 20', - 'Firefox >= 24', - 'Explorer >= 8', - 'iOS >= 6', - 'Opera >= 12', - 'Safari >= 6' - ]}), // add vendor prefixes - require('cssnano')() // minify the result - ] - }, - dist: { - src: '<%= paths.css %>/*.css' - } - }, - - // see: https://npmjs.org/package/grunt-bg-shell - bgShell: { - _defaults: { - bg: true - }, - runDjango: { - cmd: 'python <%= paths.manageScript %> runserver' - }, - {% if cookiecutter.use_mailhog == "y" and cookiecutter.use_docker == 'n' -%}runMailHog: { - cmd: './mailhog' - },{%- endif %} - } - }); - - grunt.registerTask('serve', [ - {% if cookiecutter.use_mailhog == "y" and cookiecutter.use_docker == 'n' -%} - 'bgShell:runMailHog', - {%- endif %} - 'bgShell:runDjango', - 'watch' - ]); - - grunt.registerTask('build', [ - 'sass:dist', - 'postcss' - ]); - - grunt.registerTask('default', [ - 'build' - ]); - -}; diff --git a/{{cookiecutter.project_slug}}/LICENSE b/{{cookiecutter.project_slug}}/LICENSE index e6d8e8103e..812fa0fa63 100644 --- a/{{cookiecutter.project_slug}}/LICENSE +++ b/{{cookiecutter.project_slug}}/LICENSE @@ -1,14 +1,14 @@ {% if cookiecutter.open_source_license == 'MIT' %} The MIT License (MIT) -Copyright (c) {{ cookiecutter.year }}, {{ cookiecutter.author_name }} +Copyright (c) {% now 'utc', '%Y' %}, {{ cookiecutter.author_name }} Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -{% elif cookiecutter.open_source_license == 'BSD' %} -Copyright (c) {{ cookiecutter.year }}, {{ cookiecutter.author_name }} +{%- elif cookiecutter.open_source_license == 'BSD' %} +Copyright (c) {% now 'utc', '%Y' %}, {{ cookiecutter.author_name }} All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -35,4 +35,211 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -{% endif %} +{%- elif cookiecutter.open_source_license == 'GPLv3' %} +Copyright (c) {% now 'utc', '%Y' %}, {{ cookiecutter.author_name }} + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +{%- elif cookiecutter.open_source_license == 'Apache Software License 2.0' %} + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright {% now 'utc', '%Y' %} {{ cookiecutter.author_name }} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/Procfile b/{{cookiecutter.project_slug}}/Procfile index a5476341a9..2f2fbe927d 100644 --- a/{{cookiecutter.project_slug}}/Procfile +++ b/{{cookiecutter.project_slug}}/Procfile @@ -1,4 +1,10 @@ +release: python manage.py migrate +{%- if cookiecutter.use_async == "y" %} +web: gunicorn config.asgi:application -k uvicorn.workers.UvicornWorker +{%- else %} web: gunicorn config.wsgi:application -{% if cookiecutter.use_celery == "y" -%} -worker: {% if cookiecutter.use_newrelic == "y" %}newrelic-admin run-program {% endif %}celery worker --app={{cookiecutter.project_slug}}.taskapp --loglevel=info +{%- endif %} +{%- if cookiecutter.use_celery == "y" %} +worker: REMAP_SIGTERM=SIGQUIT celery -A config.celery_app worker --loglevel=info +beat: REMAP_SIGTERM=SIGQUIT celery -A config.celery_app beat --loglevel=info {%- endif %} diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md new file mode 100644 index 0000000000..f7c29fb22e --- /dev/null +++ b/{{cookiecutter.project_slug}}/README.md @@ -0,0 +1,140 @@ +# {{cookiecutter.project_name}} + +{{ cookiecutter.description }} + +[![Built with Cookiecutter Django](https://img.shields.io/badge/built%20with-Cookiecutter%20Django-ff69b4.svg?logo=cookiecutter)](https://github.com/cookiecutter/cookiecutter-django/) +[![Black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) + +{%- if cookiecutter.open_source_license != "Not open source" %} + +License: {{cookiecutter.open_source_license}} +{%- endif %} + +## Settings + +Moved to [settings](http://cookiecutter-django.readthedocs.io/en/latest/settings.html). + +## Basic Commands + +### Setting Up Your Users + +- To create a **normal user account**, just go to Sign Up and fill out the form. Once you submit it, you'll see a "Verify Your E-mail Address" page. Go to your console to see a simulated email verification message. Copy the link into your browser. Now the user's email should be verified and ready to go. + +- To create a **superuser account**, use this command: + + $ python manage.py createsuperuser + +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 -m pytest + $ coverage html + $ open htmlcov/index.html + +#### Running tests with pytest + + $ pytest + +### Live reloading and Sass CSS compilation + +Moved to [Live reloading and SASS compilation](https://cookiecutter-django.readthedocs.io/en/latest/developing-locally.html#sass-compilation-live-reloading). + +{%- if cookiecutter.use_celery == "y" %} + +### Celery + +This app comes with Celery. + +To run a celery worker: + +``` bash +cd {{cookiecutter.project_slug}} +celery -A config.celery_app worker -l info +``` + +Please note: For Celery's import magic to work, it is important *where* the celery commands are run. If you are in the same folder with *manage.py*, you should be right. + +{%- endif %} +{%- if cookiecutter.use_mailhog == "y" %} + +### Email Server + +{%- if cookiecutter.use_docker == "y" %} + +In development, it is often nice to be able to see emails that are being sent from your application. For that reason local SMTP server [MailHog](https://github.com/mailhog/MailHog) with a web interface is available as docker container. + +Container mailhog will start automatically when you will run all docker containers. +Please check [cookiecutter-django Docker documentation](http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html) for more details how to start all containers. + +With MailHog running, to view messages that are sent by your application, open your browser and go to `http://127.0.0.1:8025` +{%- else %} + +In development, it is often nice to be able to see emails that are being sent from your application. If you choose to use [MailHog](https://github.com/mailhog/MailHog) when generating the project a local SMTP server with a web interface will be available. + +1. [Download the latest MailHog release](https://github.com/mailhog/MailHog/releases) for your OS. + +2. Rename the build to `MailHog`. + +3. Copy the file to the project root. + +4. Make it executable: + + $ chmod +x MailHog + +5. Spin up another terminal window and start it there: + + ./MailHog + +6. Check out to see how it goes. + +Now you have your own mail server running locally, ready to receive whatever you send it. + +{%- endif %} + +{%- endif %} +{%- if cookiecutter.use_sentry == "y" %} + +### Sentry + +Sentry is an error logging aggregator service. You can sign up for a free account at or download and host it yourself. +The system is set up with reasonable defaults, including 404 logging and integration with the WSGI application. + +You must set the DSN url in production. +{%- endif %} + +## Deployment + +The following details how to deploy this application. +{%- if cookiecutter.use_heroku.lower() == "y" %} + +### Heroku + +See detailed [cookiecutter-django Heroku documentation](http://cookiecutter-django.readthedocs.io/en/latest/deployment-on-heroku.html). + +{%- endif %} +{%- if cookiecutter.use_docker.lower() == "y" %} + +### Docker + +See detailed [cookiecutter-django Docker documentation](http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html). + +{%- endif %} +{%- if cookiecutter.frontend_pipeline == 'Gulp' %} +### Custom Bootstrap Compilation + +The generated CSS is set up with automatic Bootstrap recompilation with variables of your choice. +Bootstrap v5 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](https://github.com/twbs/bootstrap/blob/main/scss/_variables.scss), or get explanations on them in the [Bootstrap docs](https://getbootstrap.com/docs/5.1/customize/sass/). + +Bootstrap's javascript as well as its dependencies is concatenated into a single file: `static/js/vendors.js`. +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/README.rst b/{{cookiecutter.project_slug}}/README.rst deleted file mode 100644 index 38d923b666..0000000000 --- a/{{cookiecutter.project_slug}}/README.rst +++ /dev/null @@ -1,146 +0,0 @@ -{{cookiecutter.project_name}} -============================== - -{{cookiecutter.description}} - -.. image:: https://img.shields.io/badge/built%20with-Cookiecutter%20Django-ff69b4.svg - :target: https://github.com/pydanny/cookiecutter-django/ - :alt: Built with Cookiecutter Django - -{% if cookiecutter.open_source_license != "Not open source" %} -LICENSE: {{cookiecutter.open_source_license}} -{% endif %} - -Settings ------------- - -Moved to settings_. - -.. _settings: http://cookiecutter-django.readthedocs.io/en/latest/settings.html - -Basic Commands --------------- - -Setting Up Your Users -^^^^^^^^^^^^^^^^^^^^^ - -* To create a **normal user account**, just go to Sign Up and fill out the form. Once you submit it, you'll see a "Verify Your E-mail Address" page. Go to your console to see a simulated email verification message. Copy the link into your browser. Now the user's email should be verified and ready to go. - -* To create an **superuser account**, use this command:: - - $ python manage.py createsuperuser - -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. - -Test coverage -^^^^^^^^^^^^^ - -To run the tests, check your test coverage, and generate an HTML coverage report:: - - $ coverage run manage.py test - $ coverage html - $ open htmlcov/index.html - -Running tests with py.test -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:: - - $ py.test - -Live reloading and Sass CSS compilation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Moved to `Live reloading and SASS compilation`_. - -.. _`Live reloading and SASS compilation`: http://cookiecutter-django.readthedocs.io/en/latest/live-reloading-and-sass-compilation.html - -{% if cookiecutter.use_celery == "y" %} - -Celery -^^^^^^ - -This app comes with Celery. - -To run a celery worker: - -.. code-block:: bash - - cd {{cookiecutter.project_slug}} - celery -A {{cookiecutter.project_slug}}.taskapp worker -l info - -Please note: For Celery's import magic to work, it is important *where* the celery commands are run. If you are in the same folder with *manage.py*, you should be right. - -{% endif %} - -{% if cookiecutter.use_mailhog == "y" %} - -Email Server -^^^^^^^^^^^^ -{% if cookiecutter.use_docker == 'y' %} -In development, it is often nice to be able to see emails that are being sent from your application. For that reason local SMTP server `MailHog`_ with a web interface is available as docker container. - -.. _mailhog: https://github.com/mailhog/MailHog - -Container mailhog will start automatically when you will run all docker containers. -Please check `cookiecutter-django Docker documentation`_ for more details how to start all containers. - -With MailHog running, to view messages that are sent by your application, open your browser and go to ``http://127.0.0.1:8025`` -{% else %} -In development, it is often nice to be able to see emails that are being sent from your application. If you choose to use `MailHog`_ when generating the project a local SMTP server with a web interface will be available. - -.. _mailhog: https://github.com/mailhog/MailHog - -To start the service, make sure you have nodejs installed, and then type the following:: - - $ npm install - $ grunt serve - -(After the first run you only need to type ``grunt serve``) This will start an email server that listens on ``127.0.0.1:1025`` in addition to starting your Django project and a watch task for live reload. - -To view messages that are sent by your application, open your browser and go to ``http://127.0.0.1:8025`` - -The email server will exit when you exit the Grunt task on the CLI with Ctrl+C. -{% endif %} -{% endif %} - -{% if cookiecutter.use_sentry == "y" %} - -Sentry -^^^^^^ - -Sentry is an error logging aggregator service. You can sign up for a free account at http://getsentry.com or download and host it yourself. -The system is setup with reasonable defaults, including 404 logging and integration with the WSGI application. - -You must set the DSN url in production. - -{% endif %} - - -Deployment ----------- - -{% if cookiecutter.use_heroku == "y" %} - -Heroku -^^^^^^ - -.. image:: https://www.herokucdn.com/deploy/button.png - :target: https://heroku.com/deploy - -See detailed `cookiecutter-django Heroku documentation`_. - -.. _`cookiecutter-django Heroku documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-on-heroku.html - -{% endif %} - -{% if cookiecutter.use_docker == "y" %} - -Docker -^^^^^^ - -See detailed `cookiecutter-django Docker documentation`_. - -.. _`cookiecutter-django Docker documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html - -{% endif %} diff --git a/{{cookiecutter.project_slug}}/app.json b/{{cookiecutter.project_slug}}/app.json deleted file mode 100644 index 6e6585fd7f..0000000000 --- a/{{cookiecutter.project_slug}}/app.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "{{cookiecutter.project_slug}}", - "description": "{{cookiecutter.description}}", - "env": { - "BUILDPACK_URL": "https://github.com/heroku/heroku-buildpack-python", - "DJANGO_SETTINGS_MODULE": "config.settings.production", - "DJANGO_SECRET_KEY": { - "description": "A secret key for verifying the integrity of signed cookies.", - "generator": "secret" - }, - "DJANGO_ALLOWED_HOSTS": { - "description": "Comma-separated list of hosts", - "value": ".herokuapp.com" - }, - "DJANGO_ADMIN_URL": { - "description": "A secret URL for the Django admin", - "generator": "secret" - }, - "DJANGO_AWS_ACCESS_KEY_ID": "", - "DJANGO_AWS_SECRET_ACCESS_KEY": "", - "DJANGO_AWS_STORAGE_BUCKET_NAME": "", - "DJANGO_MAILGUN_SERVER_NAME": "", - {% if cookiecutter.use_newrelic == "y" -%} - "NEW_RELIC_LICENSE_KEY": "", - "NEW_RELIC_APP_NAME": "", - {%- endif %} - "DJANGO_MAILGUN_API_KEY": ""{% if cookiecutter.use_sentry == "y" -%}, - "DJANGO_SENTRY_DSN": ""{%- endif %} - }, - "scripts": { - "postdeploy": "python manage.py migrate" - }, - "addons": [ - "heroku-postgresql:hobby-dev", - "heroku-redis:hobby-dev", - "mailgun" - ] -} diff --git a/{{cookiecutter.project_slug}}/bin/post_compile b/{{cookiecutter.project_slug}}/bin/post_compile new file mode 100644 index 0000000000..a9c94b39ad --- /dev/null +++ b/{{cookiecutter.project_slug}}/bin/post_compile @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +compress_enabled() { +python << END +import sys + +from environ import Env + +env = Env(COMPRESS_ENABLED=(bool, True)) +if env('COMPRESS_ENABLED'): + sys.exit(0) +else: + sys.exit(1) + +END +} + +if compress_enabled +then + python manage.py compress +fi +python manage.py collectstatic --noinput diff --git a/{{cookiecutter.project_slug}}/compose/django/Dockerfile b/{{cookiecutter.project_slug}}/compose/django/Dockerfile deleted file mode 100644 index f2f662d5de..0000000000 --- a/{{cookiecutter.project_slug}}/compose/django/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -{% if cookiecutter.use_python2 == 'n' -%} -FROM python:3.5 -{% else %} -FROM python:2.7 -{%- endif %} -ENV PYTHONUNBUFFERED 1 - -# Requirements have to be pulled and installed here, otherwise caching won't work -COPY ./requirements /requirements - -RUN pip install -r /requirements/production.txt - -RUN groupadd -r django && useradd -r -g django django -COPY . /app -RUN chown -R django /app - -COPY ./compose/django/gunicorn.sh /gunicorn.sh -COPY ./compose/django/entrypoint.sh /entrypoint.sh -RUN sed -i 's/\r//' /entrypoint.sh -RUN sed -i 's/\r//' /gunicorn.sh -RUN chmod +x /entrypoint.sh && chown django /entrypoint.sh -RUN chmod +x /gunicorn.sh && chown django /gunicorn.sh - -WORKDIR /app - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/{{cookiecutter.project_slug}}/compose/django/Dockerfile-dev b/{{cookiecutter.project_slug}}/compose/django/Dockerfile-dev deleted file mode 100644 index c3f54a9e36..0000000000 --- a/{{cookiecutter.project_slug}}/compose/django/Dockerfile-dev +++ /dev/null @@ -1,18 +0,0 @@ -{% if cookiecutter.use_python2 == 'n' -%} -FROM python:3.5 -{% else %} -FROM python:2.7 -{%- endif %} -ENV PYTHONUNBUFFERED 1 - -# Requirements have to be pulled and installed here, otherwise caching won't work -COPY ./requirements /requirements -RUN pip install -r /requirements/local.txt - -COPY ./compose/django/entrypoint.sh /entrypoint.sh -RUN sed -i 's/\r//' /entrypoint.sh -RUN chmod +x /entrypoint.sh - -WORKDIR /app - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/{{cookiecutter.project_slug}}/compose/django/entrypoint.sh b/{{cookiecutter.project_slug}}/compose/django/entrypoint.sh deleted file mode 100644 index e9286baa52..0000000000 --- a/{{cookiecutter.project_slug}}/compose/django/entrypoint.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -set -e -# This entrypoint is used to play nicely with the current cookiecutter configuration. -# Since docker-compose relies heavily on environment variables itself for configuration, we'd have to define multiple -# environment variables just to support cookiecutter out of the box. That makes no sense, so this little entrypoint -# does all this for us. -export REDIS_URL=redis://redis:6379 - -# the official postgres image uses 'postgres' as default user if not set explictly. -if [ -z "$POSTGRES_USER" ]; then - export POSTGRES_USER=postgres -fi - -export DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres:5432/$POSTGRES_USER -{% if cookiecutter.use_celery == 'y' %} -export CELERY_BROKER_URL=$REDIS_URL/0 -{% endif %} -exec "$@" diff --git a/{{cookiecutter.project_slug}}/compose/django/gunicorn.sh b/{{cookiecutter.project_slug}}/compose/django/gunicorn.sh deleted file mode 100644 index 014f173e33..0000000000 --- a/{{cookiecutter.project_slug}}/compose/django/gunicorn.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -python /app/manage.py collectstatic --noinput -/usr/local/bin/gunicorn config.wsgi -w 4 -b 0.0.0.0:5000 --chdir=/app \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/compose/local/django/Dockerfile b/{{cookiecutter.project_slug}}/compose/local/django/Dockerfile new file mode 100644 index 0000000000..3ea6b2d4cc --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/local/django/Dockerfile @@ -0,0 +1,81 @@ +ARG PYTHON_VERSION=3.10-slim-bullseye + +# define an alias for the specfic python version used in this file. +FROM python:${PYTHON_VERSION} as python + +# Python build stage +FROM python as python-build-stage + +ARG BUILD_ENVIRONMENT=local + +# Install apt packages +RUN apt-get update && apt-get install --no-install-recommends -y \ + # dependencies for building Python packages + build-essential \ + # psycopg2 dependencies + libpq-dev + +# Requirements are installed here to ensure they will be cached. +COPY ./requirements . + +# Create Python Dependency and Sub-Dependency Wheels. +RUN pip wheel --wheel-dir /usr/src/app/wheels \ + -r ${BUILD_ENVIRONMENT}.txt + + +# Python 'run' stage +FROM python as python-run-stage + +ARG BUILD_ENVIRONMENT=local +ARG APP_HOME=/app + +ENV PYTHONUNBUFFERED 1 +ENV PYTHONDONTWRITEBYTECODE 1 +ENV BUILD_ENV ${BUILD_ENVIRONMENT} + +WORKDIR ${APP_HOME} + +# Install required system dependencies +RUN apt-get update && apt-get install --no-install-recommends -y \ + # psycopg2 dependencies + libpq-dev \ + # Translations dependencies + gettext \ + # cleaning up unused files + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* + +# All absolute dir copies ignore workdir instruction. All relative dir copies are wrt to the workdir instruction +# copy python dependency wheels from python-build-stage +COPY --from=python-build-stage /usr/src/app/wheels /wheels/ + +# use wheels to install python dependencies +RUN pip install --no-cache-dir --no-index --find-links=/wheels/ /wheels/* \ + && rm -rf /wheels/ + +COPY ./compose/production/django/entrypoint /entrypoint +RUN sed -i 's/\r$//g' /entrypoint +RUN chmod +x /entrypoint + +COPY ./compose/local/django/start /start +RUN sed -i 's/\r$//g' /start +RUN chmod +x /start + +{% if cookiecutter.use_celery == "y" %} +COPY ./compose/local/django/celery/worker/start /start-celeryworker +RUN sed -i 's/\r$//g' /start-celeryworker +RUN chmod +x /start-celeryworker + +COPY ./compose/local/django/celery/beat/start /start-celerybeat +RUN sed -i 's/\r$//g' /start-celerybeat +RUN chmod +x /start-celerybeat + +COPY ./compose/local/django/celery/flower/start /start-flower +RUN sed -i 's/\r$//g' /start-flower +RUN chmod +x /start-flower +{% endif %} + +# copy application code to WORKDIR +COPY . ${APP_HOME} + +ENTRYPOINT ["/entrypoint"] diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start new file mode 100644 index 0000000000..c04a7365e9 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start @@ -0,0 +1,8 @@ +#!/bin/bash + +set -o errexit +set -o nounset + + +rm -f './celerybeat.pid' +celery -A config.celery_app beat -l INFO diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start new file mode 100644 index 0000000000..bd3c9f2fd1 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start @@ -0,0 +1,11 @@ +#!/bin/bash + +set -o errexit +set -o nounset + + +celery \ + -A config.celery_app \ + -b "${CELERY_BROKER_URL}" \ + flower \ + --basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}" diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start new file mode 100644 index 0000000000..4ddcfa137f --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start @@ -0,0 +1,7 @@ +#!/bin/bash + +set -o errexit +set -o nounset + + +watchfiles celery.__main__.main --args '-A config.celery_app worker -l INFO' diff --git a/{{cookiecutter.project_slug}}/compose/local/django/start b/{{cookiecutter.project_slug}}/compose/local/django/start new file mode 100644 index 0000000000..3fe5473575 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/local/django/start @@ -0,0 +1,13 @@ +#!/bin/bash + +set -o errexit +set -o pipefail +set -o nounset + + +python manage.py migrate +{%- if cookiecutter.use_async == 'y' %} +uvicorn config.asgi:application --host 0.0.0.0 --reload --reload-include '*.html' +{%- else %} +python manage.py runserver_plus 0.0.0.0:8000 +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/compose/local/docs/Dockerfile b/{{cookiecutter.project_slug}}/compose/local/docs/Dockerfile new file mode 100644 index 0000000000..c45d18c959 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/local/docs/Dockerfile @@ -0,0 +1,64 @@ +ARG PYTHON_VERSION=3.10-slim-bullseye + +# define an alias for the specfic python version used in this file. +FROM python:${PYTHON_VERSION} as python + + +# Python build stage +FROM python as python-build-stage + +ENV PYTHONDONTWRITEBYTECODE 1 + +RUN apt-get update && apt-get install --no-install-recommends -y \ + # dependencies for building Python packages + build-essential \ + # psycopg2 dependencies + libpq-dev \ + # cleaning up unused files + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* + +# Requirements are installed here to ensure they will be cached. +COPY ./requirements /requirements + +# create python dependency wheels +RUN pip wheel --no-cache-dir --wheel-dir /usr/src/app/wheels \ + -r /requirements/local.txt -r /requirements/production.txt \ + && rm -rf /requirements + + +# Python 'run' stage +FROM python as python-run-stage + +ARG BUILD_ENVIRONMENT +ENV PYTHONUNBUFFERED 1 +ENV PYTHONDONTWRITEBYTECODE 1 + +RUN apt-get update && apt-get install --no-install-recommends -y \ + # To run the Makefile + make \ + # psycopg2 dependencies + libpq-dev \ + # Translations dependencies + gettext \ + # Uncomment below lines to enable Sphinx output to latex and pdf + # texlive-latex-recommended \ + # texlive-fonts-recommended \ + # texlive-latex-extra \ + # latexmk \ + # cleaning up unused files + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* + +# copy python dependency wheels from python-build-stage +COPY --from=python-build-stage /usr/src/app/wheels /wheels + +# use wheels to install python dependencies +RUN pip install --no-cache /wheels/* \ + && rm -rf /wheels + +COPY ./compose/local/docs/start /start-docs +RUN sed -i 's/\r$//g' /start-docs +RUN chmod +x /start-docs + +WORKDIR /docs diff --git a/{{cookiecutter.project_slug}}/compose/local/docs/start b/{{cookiecutter.project_slug}}/compose/local/docs/start new file mode 100644 index 0000000000..fd2e0de6a2 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/local/docs/start @@ -0,0 +1,7 @@ +#!/bin/bash + +set -o errexit +set -o pipefail +set -o nounset + +make livehtml diff --git a/{{cookiecutter.project_slug}}/compose/local/node/Dockerfile b/{{cookiecutter.project_slug}}/compose/local/node/Dockerfile new file mode 100644 index 0000000000..8062fa689f --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/local/node/Dockerfile @@ -0,0 +1,9 @@ +FROM node:16-bullseye-slim + +WORKDIR /app + +COPY ./package.json /app + +RUN npm install && npm cache clean --force + +ENV PATH ./node_modules/.bin/:$PATH diff --git a/{{cookiecutter.project_slug}}/compose/nginx/Dockerfile b/{{cookiecutter.project_slug}}/compose/nginx/Dockerfile deleted file mode 100644 index 196395763a..0000000000 --- a/{{cookiecutter.project_slug}}/compose/nginx/Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM nginx:latest -ADD nginx.conf /etc/nginx/nginx.conf \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/compose/nginx/nginx.conf b/{{cookiecutter.project_slug}}/compose/nginx/nginx.conf deleted file mode 100644 index 720b22e5b9..0000000000 --- a/{{cookiecutter.project_slug}}/compose/nginx/nginx.conf +++ /dev/null @@ -1,53 +0,0 @@ -user nginx; -worker_processes 1; - -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - - -events { - worker_connections 1024; -} - - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - #gzip on; - - upstream app { - server django:5000; - } - - server { - listen 80; - charset utf-8; - - - location / { - # checks for static file, if not found proxy to app - try_files $uri @proxy_to_app; - } - - location @proxy_to_app { - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $http_host; - proxy_redirect off; - - proxy_pass http://app; - } - - } -} \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/compose/postgres/Dockerfile b/{{cookiecutter.project_slug}}/compose/postgres/Dockerfile deleted file mode 100644 index 332723640a..0000000000 --- a/{{cookiecutter.project_slug}}/compose/postgres/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM postgres:9.5 - -# add backup scripts -ADD backup.sh /usr/local/bin/backup -ADD restore.sh /usr/local/bin/restore -ADD list-backups.sh /usr/local/bin/list-backups - -# make them executable -RUN chmod +x /usr/local/bin/restore -RUN chmod +x /usr/local/bin/list-backups -RUN chmod +x /usr/local/bin/backup diff --git a/{{cookiecutter.project_slug}}/compose/postgres/backup.sh b/{{cookiecutter.project_slug}}/compose/postgres/backup.sh deleted file mode 100644 index 97c95e1df4..0000000000 --- a/{{cookiecutter.project_slug}}/compose/postgres/backup.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# stop on errors -set -e - -# we might run into trouble when using the default `postgres` user, e.g. when dropping the postgres -# database in restore.sh. Check that something else is used here -if [ "$POSTGRES_USER" == "postgres" ] -then - echo "creating a backup as the postgres user is not supported, make sure to set the POSTGRES_USER environment variable" - exit 1 -fi - -# export the postgres password so that subsequent commands don't ask for it -export PGPASSWORD=$POSTGRES_PASSWORD - -echo "creating backup" -echo "---------------" - -FILENAME=backup_$(date +'%Y_%m_%dT%H_%M_%S').sql -pg_dump -h postgres -U $POSTGRES_USER >> /backups/$FILENAME - -echo "successfully created backup $FILENAME" diff --git a/{{cookiecutter.project_slug}}/compose/postgres/list-backups.sh b/{{cookiecutter.project_slug}}/compose/postgres/list-backups.sh deleted file mode 100644 index 75972b75b3..0000000000 --- a/{{cookiecutter.project_slug}}/compose/postgres/list-backups.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -echo "listing available backups" -echo "-------------------------" -ls /backups/ diff --git a/{{cookiecutter.project_slug}}/compose/postgres/restore.sh b/{{cookiecutter.project_slug}}/compose/postgres/restore.sh deleted file mode 100644 index 7500828032..0000000000 --- a/{{cookiecutter.project_slug}}/compose/postgres/restore.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash - -# stop on errors -set -e - -# we might run into trouble when using the default `postgres` user, e.g. when dropping the postgres -# database in restore.sh. Check that something else is used here -if [ "$POSTGRES_USER" == "postgres" ] -then - echo "restoring as the postgres user is not supported, make sure to set the POSTGRES_USER environment variable" - exit 1 -fi - -# export the postgres password so that subsequent commands don't ask for it -export PGPASSWORD=$POSTGRES_PASSWORD - -# check that we have an argument for a filename candidate -if [[ $# -eq 0 ]] ; then - echo 'usage:' - echo ' docker-compose run postgres restore ' - echo '' - echo 'to get a list of available backups, run:' - echo ' docker-compose run postgres list-backups' - exit 1 -fi - -# set the backupfile variable -BACKUPFILE=/backups/$1 - -# check that the file exists -if ! [ -f $BACKUPFILE ]; then - echo "backup file not found" - echo 'to get a list of available backups, run:' - echo ' docker-compose run postgres list-backups' - exit 1 -fi - -echo "beginning restore from $1" -echo "-------------------------" - -# delete the db -# deleting the db can fail. Spit out a comment if this happens but continue since the db -# is created in the next step -echo "deleting old database $POSTGRES_USER" -if dropdb -h postgres -U $POSTGRES_USER $POSTGRES_USER -then echo "deleted $POSTGRES_USER database" -else echo "database $POSTGRES_USER does not exist, continue" -fi - -# create a new database -echo "creating new database $POSTGRES_USER" -createdb -h postgres -U $POSTGRES_USER $POSTGRES_USER -O $POSTGRES_USER - -# restore the database -echo "restoring database $POSTGRES_USER" -psql -h postgres -U $POSTGRES_USER < $BACKUPFILE diff --git a/{{cookiecutter.project_slug}}/compose/production/aws/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/aws/Dockerfile new file mode 100644 index 0000000000..8282047b37 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/aws/Dockerfile @@ -0,0 +1,9 @@ +FROM garland/aws-cli-docker:1.15.47 + +COPY ./compose/production/aws/maintenance /usr/local/bin/maintenance +COPY ./compose/production/postgres/maintenance/_sourced /usr/local/bin/maintenance/_sourced + +RUN chmod +x /usr/local/bin/maintenance/* + +RUN mv /usr/local/bin/maintenance/* /usr/local/bin \ + && rmdir /usr/local/bin/maintenance diff --git a/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/download b/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/download new file mode 100644 index 0000000000..0c515935fc --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/download @@ -0,0 +1,23 @@ +#!/bin/sh + +### Download a file from your Amazon S3 bucket to the postgres /backups folder +### +### Usage: +### $ docker-compose -f production.yml run --rm awscli <1> + +set -o errexit +set -o pipefail +set -o nounset + +working_dir="$(dirname ${0})" +source "${working_dir}/_sourced/constants.sh" +source "${working_dir}/_sourced/messages.sh" + +export AWS_ACCESS_KEY_ID="${DJANGO_AWS_ACCESS_KEY_ID}" +export AWS_SECRET_ACCESS_KEY="${DJANGO_AWS_SECRET_ACCESS_KEY}" +export AWS_STORAGE_BUCKET_NAME="${DJANGO_AWS_STORAGE_BUCKET_NAME}" + + +aws s3 cp s3://${AWS_STORAGE_BUCKET_NAME}${BACKUP_DIR_PATH}/${1} ${BACKUP_DIR_PATH}/${1} + +message_success "Finished downloading ${1}." diff --git a/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/upload b/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/upload new file mode 100644 index 0000000000..9446b9304a --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/upload @@ -0,0 +1,29 @@ +#!/bin/sh + +### Upload the /backups folder to Amazon S3 +### +### Usage: +### $ docker-compose -f production.yml run --rm awscli upload + +set -o errexit +set -o pipefail +set -o nounset + +working_dir="$(dirname ${0})" +source "${working_dir}/_sourced/constants.sh" +source "${working_dir}/_sourced/messages.sh" + +export AWS_ACCESS_KEY_ID="${DJANGO_AWS_ACCESS_KEY_ID}" +export AWS_SECRET_ACCESS_KEY="${DJANGO_AWS_SECRET_ACCESS_KEY}" +export AWS_STORAGE_BUCKET_NAME="${DJANGO_AWS_STORAGE_BUCKET_NAME}" + + +message_info "Upload the backups directory to S3 bucket {$AWS_STORAGE_BUCKET_NAME}" + +aws s3 cp ${BACKUP_DIR_PATH} s3://${AWS_STORAGE_BUCKET_NAME}${BACKUP_DIR_PATH} --recursive + +message_info "Cleaning the directory ${BACKUP_DIR_PATH}" + +rm -rf ${BACKUP_DIR_PATH}/* + +message_success "Finished uploading and cleaning." diff --git a/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile new file mode 100644 index 0000000000..4652f0898b --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile @@ -0,0 +1,113 @@ +ARG PYTHON_VERSION=3.10-slim-bullseye + +{% if cookiecutter.frontend_pipeline == 'Gulp' -%} +FROM node:16-bullseye-slim as client-builder + +ARG APP_HOME=/app +WORKDIR ${APP_HOME} + +COPY ./package.json ${APP_HOME} +RUN npm install && npm cache clean --force +COPY . ${APP_HOME} +RUN npm run build + +{%- endif %} + +# define an alias for the specfic python version used in this file. +FROM python:${PYTHON_VERSION} as python + +# Python build stage +FROM python as python-build-stage + +ARG BUILD_ENVIRONMENT=production + +# Install apt packages +RUN apt-get update && apt-get install --no-install-recommends -y \ + # dependencies for building Python packages + build-essential \ + # psycopg2 dependencies + libpq-dev + +# Requirements are installed here to ensure they will be cached. +COPY ./requirements . + +# Create Python Dependency and Sub-Dependency Wheels. +RUN pip wheel --wheel-dir /usr/src/app/wheels \ + -r ${BUILD_ENVIRONMENT}.txt + + +# Python 'run' stage +FROM python as python-run-stage + +ARG BUILD_ENVIRONMENT=production +ARG APP_HOME=/app + +ENV PYTHONUNBUFFERED 1 +ENV PYTHONDONTWRITEBYTECODE 1 +ENV BUILD_ENV ${BUILD_ENVIRONMENT} + +WORKDIR ${APP_HOME} + +RUN addgroup --system django \ + && adduser --system --ingroup django django + + +# Install required system dependencies +RUN apt-get update && apt-get install --no-install-recommends -y \ + # psycopg2 dependencies + libpq-dev \ + # Translations dependencies + gettext \ + # cleaning up unused files + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* + +# All absolute dir copies ignore workdir instruction. All relative dir copies are wrt to the workdir instruction +# copy python dependency wheels from python-build-stage +COPY --from=python-build-stage /usr/src/app/wheels /wheels/ + +# use wheels to install python dependencies +RUN pip install --no-cache-dir --no-index --find-links=/wheels/ /wheels/* \ + && rm -rf /wheels/ + + +COPY --chown=django:django ./compose/production/django/entrypoint /entrypoint +RUN sed -i 's/\r$//g' /entrypoint +RUN chmod +x /entrypoint + + +COPY --chown=django:django ./compose/production/django/start /start +RUN sed -i 's/\r$//g' /start +RUN chmod +x /start + + +{%- if cookiecutter.use_celery == "y" %} +COPY --chown=django:django ./compose/production/django/celery/worker/start /start-celeryworker +RUN sed -i 's/\r$//g' /start-celeryworker +RUN chmod +x /start-celeryworker + + +COPY --chown=django:django ./compose/production/django/celery/beat/start /start-celerybeat +RUN sed -i 's/\r$//g' /start-celerybeat +RUN chmod +x /start-celerybeat + + +COPY ./compose/production/django/celery/flower/start /start-flower +RUN sed -i 's/\r$//g' /start-flower +RUN chmod +x /start-flower +{%- endif %} + + +# copy application code to WORKDIR +{%- if cookiecutter.frontend_pipeline == 'Gulp' %} +COPY --from=client-builder --chown=django:django ${APP_HOME} ${APP_HOME} +{% else %} +COPY --chown=django:django . ${APP_HOME} +{%- endif %} + +# make django owner of the WORKDIR directory as well. +RUN chown django:django ${APP_HOME} + +USER django + +ENTRYPOINT ["/entrypoint"] diff --git a/{{cookiecutter.project_slug}}/compose/production/django/celery/beat/start b/{{cookiecutter.project_slug}}/compose/production/django/celery/beat/start new file mode 100644 index 0000000000..42ddca9105 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/django/celery/beat/start @@ -0,0 +1,8 @@ +#!/bin/bash + +set -o errexit +set -o pipefail +set -o nounset + + +exec celery -A config.celery_app beat -l INFO diff --git a/{{cookiecutter.project_slug}}/compose/production/django/celery/flower/start b/{{cookiecutter.project_slug}}/compose/production/django/celery/flower/start new file mode 100644 index 0000000000..4180d6778d --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/django/celery/flower/start @@ -0,0 +1,11 @@ +#!/bin/bash + +set -o errexit +set -o nounset + + +exec celery \ + -A config.celery_app \ + -b "${CELERY_BROKER_URL}" \ + flower \ + --basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}" diff --git a/{{cookiecutter.project_slug}}/compose/production/django/celery/worker/start b/{{cookiecutter.project_slug}}/compose/production/django/celery/worker/start new file mode 100644 index 0000000000..af0c8f7b52 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/django/celery/worker/start @@ -0,0 +1,8 @@ +#!/bin/bash + +set -o errexit +set -o pipefail +set -o nounset + + +exec celery -A config.celery_app worker -l INFO diff --git a/{{cookiecutter.project_slug}}/compose/production/django/entrypoint b/{{cookiecutter.project_slug}}/compose/production/django/entrypoint new file mode 100644 index 0000000000..2fbcad9553 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/django/entrypoint @@ -0,0 +1,49 @@ +#!/bin/bash + +set -o errexit +set -o pipefail +set -o nounset + + +{% if cookiecutter.use_celery == 'y' %} +# N.B. If only .env files supported variable expansion... +export CELERY_BROKER_URL="${REDIS_URL}" +{% endif %} + +if [ -z "${POSTGRES_USER}" ]; then + base_postgres_image_default_user='postgres' + export POSTGRES_USER="${base_postgres_image_default_user}" +fi +export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" + +python << END +import sys +import time + +import psycopg2 + +suggest_unrecoverable_after = 30 +start = time.time() + +while True: + try: + psycopg2.connect( + dbname="${POSTGRES_DB}", + user="${POSTGRES_USER}", + password="${POSTGRES_PASSWORD}", + host="${POSTGRES_HOST}", + port="${POSTGRES_PORT}", + ) + break + except psycopg2.OperationalError as error: + sys.stderr.write("Waiting for PostgreSQL to become available...\n") + + if time.time() - start > suggest_unrecoverable_after: + sys.stderr.write(" This is taking longer than expected. The following exception may be indicative of an unrecoverable error: '{}'\n".format(error)) + + time.sleep(1) +END + +>&2 echo 'PostgreSQL is available' + +exec "$@" diff --git a/{{cookiecutter.project_slug}}/compose/production/django/start b/{{cookiecutter.project_slug}}/compose/production/django/start new file mode 100644 index 0000000000..2ba79501c9 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/django/start @@ -0,0 +1,34 @@ +#!/bin/bash + +set -o errexit +set -o pipefail +set -o nounset + + +python /app/manage.py collectstatic --noinput +{% if cookiecutter.use_whitenoise == 'y' and cookiecutter.frontend_pipeline == 'Django Compressor' %} +compress_enabled() { +python << END +import sys + +from environ import Env + +env = Env(COMPRESS_ENABLED=(bool, True)) +if env('COMPRESS_ENABLED'): + sys.exit(0) +else: + sys.exit(1) + +END +} + +if compress_enabled; then + # NOTE this command will fail if django-compressor is disabled + python /app/manage.py compress +fi +{%- endif %} +{%- if cookiecutter.use_async == 'y' %} +/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:5000 --chdir=/app -k uvicorn.workers.UvicornWorker +{%- else %} +/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/compose/production/postgres/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/postgres/Dockerfile new file mode 100644 index 0000000000..eca29bada1 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/postgres/Dockerfile @@ -0,0 +1,6 @@ +FROM postgres:{{ cookiecutter.postgresql_version }} + +COPY ./compose/production/postgres/maintenance /usr/local/bin/maintenance +RUN chmod +x /usr/local/bin/maintenance/* +RUN mv /usr/local/bin/maintenance/* /usr/local/bin \ + && rmdir /usr/local/bin/maintenance diff --git a/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/constants.sh b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/constants.sh new file mode 100644 index 0000000000..6ca4f0ca9c --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/constants.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + + +BACKUP_DIR_PATH='/backups' +BACKUP_FILE_PREFIX='backup' diff --git a/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/countdown.sh b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/countdown.sh new file mode 100644 index 0000000000..e6cbfb6ffc --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/countdown.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + + +countdown() { + declare desc="A simple countdown. Source: https://superuser.com/a/611582" + local seconds="${1}" + local d=$(($(date +%s) + "${seconds}")) + while [ "$d" -ge `date +%s` ]; do + echo -ne "$(date -u --date @$(($d - `date +%s`)) +%H:%M:%S)\r"; + sleep 0.1 + done +} diff --git a/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/messages.sh b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/messages.sh new file mode 100644 index 0000000000..f6be756e97 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/messages.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + + +message_newline() { + echo +} + +message_debug() +{ + echo -e "DEBUG: ${@}" +} + +message_welcome() +{ + echo -e "\e[1m${@}\e[0m" +} + +message_warning() +{ + echo -e "\e[33mWARNING\e[0m: ${@}" +} + +message_error() +{ + echo -e "\e[31mERROR\e[0m: ${@}" +} + +message_info() +{ + echo -e "\e[37mINFO\e[0m: ${@}" +} + +message_suggestion() +{ + echo -e "\e[33mSUGGESTION\e[0m: ${@}" +} + +message_success() +{ + echo -e "\e[32mSUCCESS\e[0m: ${@}" +} diff --git a/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/yes_no.sh b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/yes_no.sh new file mode 100644 index 0000000000..fd9cae1614 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/_sourced/yes_no.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + + +yes_no() { + declare desc="Prompt for confirmation. \$\"\{1\}\": confirmation message." + local arg1="${1}" + + local response= + read -r -p "${arg1} (y/[n])? " response + if [[ "${response}" =~ ^[Yy]$ ]] + then + exit 0 + else + exit 1 + fi +} diff --git a/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/backup b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/backup new file mode 100644 index 0000000000..ee0c9d63cd --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/backup @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + + +### Create a database backup. +### +### Usage: +### $ docker-compose -f .yml (exec |run --rm) postgres backup + + +set -o errexit +set -o pipefail +set -o nounset + + +working_dir="$(dirname ${0})" +source "${working_dir}/_sourced/constants.sh" +source "${working_dir}/_sourced/messages.sh" + + +message_welcome "Backing up the '${POSTGRES_DB}' database..." + + +if [[ "${POSTGRES_USER}" == "postgres" ]]; then + message_error "Backing up as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again." + exit 1 +fi + +export PGHOST="${POSTGRES_HOST}" +export PGPORT="${POSTGRES_PORT}" +export PGUSER="${POSTGRES_USER}" +export PGPASSWORD="${POSTGRES_PASSWORD}" +export PGDATABASE="${POSTGRES_DB}" + +backup_filename="${BACKUP_FILE_PREFIX}_$(date +'%Y_%m_%dT%H_%M_%S').sql.gz" +pg_dump | gzip > "${BACKUP_DIR_PATH}/${backup_filename}" + + +message_success "'${POSTGRES_DB}' database backup '${backup_filename}' has been created and placed in '${BACKUP_DIR_PATH}'." diff --git a/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/backups b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/backups new file mode 100644 index 0000000000..0484ccff50 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/backups @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + + +### View backups. +### +### Usage: +### $ docker-compose -f .yml (exec |run --rm) postgres backups + + +set -o errexit +set -o pipefail +set -o nounset + + +working_dir="$(dirname ${0})" +source "${working_dir}/_sourced/constants.sh" +source "${working_dir}/_sourced/messages.sh" + + +message_welcome "These are the backups you have got:" + +ls -lht "${BACKUP_DIR_PATH}" diff --git a/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/restore b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/restore new file mode 100644 index 0000000000..9661ca7f1a --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/postgres/maintenance/restore @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + + +### Restore database from a backup. +### +### Parameters: +### <1> filename of an existing backup. +### +### Usage: +### $ docker-compose -f .yml (exec |run --rm) postgres restore <1> + + +set -o errexit +set -o pipefail +set -o nounset + + +working_dir="$(dirname ${0})" +source "${working_dir}/_sourced/constants.sh" +source "${working_dir}/_sourced/messages.sh" + + +if [[ -z ${1+x} ]]; then + message_error "Backup filename is not specified yet it is a required parameter. Make sure you provide one and try again." + exit 1 +fi +backup_filename="${BACKUP_DIR_PATH}/${1}" +if [[ ! -f "${backup_filename}" ]]; then + message_error "No backup with the specified filename found. Check out the 'backups' maintenance script output to see if there is one and try again." + exit 1 +fi + +message_welcome "Restoring the '${POSTGRES_DB}' database from the '${backup_filename}' backup..." + +if [[ "${POSTGRES_USER}" == "postgres" ]]; then + message_error "Restoring as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again." + exit 1 +fi + +export PGHOST="${POSTGRES_HOST}" +export PGPORT="${POSTGRES_PORT}" +export PGUSER="${POSTGRES_USER}" +export PGPASSWORD="${POSTGRES_PASSWORD}" +export PGDATABASE="${POSTGRES_DB}" + +message_info "Dropping the database..." +dropdb "${PGDATABASE}" + +message_info "Creating a new database..." +createdb --owner="${POSTGRES_USER}" + +message_info "Applying the backup to the new database..." +gunzip -c "${backup_filename}" | psql "${POSTGRES_DB}" + +message_success "The '${POSTGRES_DB}' database has been restored from the '${backup_filename}' backup." diff --git a/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile new file mode 100644 index 0000000000..aa879052b7 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile @@ -0,0 +1,5 @@ +FROM traefik:v2.2.11 +RUN mkdir -p /etc/traefik/acme \ + && touch /etc/traefik/acme/acme.json \ + && chmod 600 /etc/traefik/acme/acme.json +COPY ./compose/production/traefik/traefik.yml /etc/traefik diff --git a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml new file mode 100644 index 0000000000..cc183cd6cf --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml @@ -0,0 +1,85 @@ +log: + level: INFO + +entryPoints: + web: + # http + address: ":80" + http: + # https://docs.traefik.io/routing/entrypoints/#entrypoint + redirections: + entryPoint: + to: web-secure + + web-secure: + # https + address: ":443" + {%- if cookiecutter.use_celery == 'y' %} + + flower: + address: ":5555" + {%- endif %} + +certificatesResolvers: + letsencrypt: + # https://docs.traefik.io/master/https/acme/#lets-encrypt + acme: + email: "{{ cookiecutter.email }}" + storage: /etc/traefik/acme/acme.json + # https://docs.traefik.io/master/https/acme/#httpchallenge + httpChallenge: + entryPoint: web + +http: + routers: + web-secure-router: + {%- if cookiecutter.domain_name.count('.') == 1 %} + rule: "Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)" + {%- else %} + rule: "Host(`{{ cookiecutter.domain_name }}`)" + {%- endif %} + entryPoints: + - web-secure + middlewares: + - csrf + service: django + tls: + # https://docs.traefik.io/master/routing/routers/#certresolver + certResolver: letsencrypt + {%- if cookiecutter.use_celery == 'y' %} + + flower-secure-router: + rule: "Host(`{{ cookiecutter.domain_name }}`)" + entryPoints: + - flower + service: flower + tls: + # https://docs.traefik.io/master/routing/routers/#certresolver + certResolver: letsencrypt + {%- endif %} + + middlewares: + csrf: + # https://docs.traefik.io/master/middlewares/headers/#hostsproxyheaders + # https://docs.djangoproject.com/en/dev/ref/csrf/#ajax + headers: + hostsProxyHeaders: ["X-CSRFToken"] + + services: + django: + loadBalancer: + servers: + - url: http://django:5000 + {%- if cookiecutter.use_celery == 'y' %} + + flower: + loadBalancer: + servers: + - url: http://flower:5555 + {%- endif %} + +providers: + # https://docs.traefik.io/master/providers/file/ + file: + filename: /etc/traefik/traefik.yml + watch: true diff --git a/{{cookiecutter.project_slug}}/config/__init__.py b/{{cookiecutter.project_slug}}/config/__init__.py index e69de29bb2..480655af17 100644 --- a/{{cookiecutter.project_slug}}/config/__init__.py +++ b/{{cookiecutter.project_slug}}/config/__init__.py @@ -0,0 +1,7 @@ +{% if cookiecutter.use_celery == 'y' -%} +# This will make sure the app is always imported when +# Django starts so that shared_task will use this app. +from .celery_app import app as celery_app + +__all__ = ("celery_app",) +{% endif -%} diff --git a/{{cookiecutter.project_slug}}/config/api_router.py b/{{cookiecutter.project_slug}}/config/api_router.py new file mode 100644 index 0000000000..743069b2c1 --- /dev/null +++ b/{{cookiecutter.project_slug}}/config/api_router.py @@ -0,0 +1,15 @@ +from django.conf import settings +from rest_framework.routers import DefaultRouter, SimpleRouter + +from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet + +if settings.DEBUG: + router = DefaultRouter() +else: + router = SimpleRouter() + +router.register("users", UserViewSet) + + +app_name = "api" +urlpatterns = router.urls diff --git a/{{cookiecutter.project_slug}}/config/asgi.py b/{{cookiecutter.project_slug}}/config/asgi.py new file mode 100644 index 0000000000..8c99bbf530 --- /dev/null +++ b/{{cookiecutter.project_slug}}/config/asgi.py @@ -0,0 +1,40 @@ +""" +ASGI config for {{ cookiecutter.project_name }} project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/dev/howto/deployment/asgi/ + +""" +import os +import sys +from pathlib import Path + +from django.core.asgi import get_asgi_application + +# This allows easy placement of apps within the interior +# {{ cookiecutter.project_slug }} directory. +ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent +sys.path.append(str(ROOT_DIR / "{{ cookiecutter.project_slug }}")) + +# If DJANGO_SETTINGS_MODULE is unset, default to the local settings +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + +# This application object is used by any ASGI server configured to use this file. +django_application = get_asgi_application() +# Apply ASGI middleware here. +# from helloworld.asgi import HelloWorldApplication +# application = HelloWorldApplication(application) + +# Import websocket application here, so apps from django_application are loaded first +from config.websocket import websocket_application # noqa isort:skip + + +async def application(scope, receive, send): + if scope["type"] == "http": + await django_application(scope, receive, send) + elif scope["type"] == "websocket": + await websocket_application(scope, receive, send) + else: + raise NotImplementedError(f"Unknown scope type {scope['type']}") diff --git a/{{cookiecutter.project_slug}}/config/celery_app.py b/{{cookiecutter.project_slug}}/config/celery_app.py new file mode 100644 index 0000000000..0728a649e0 --- /dev/null +++ b/{{cookiecutter.project_slug}}/config/celery_app.py @@ -0,0 +1,17 @@ +import os + +from celery import Celery + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + +app = Celery("{{cookiecutter.project_slug}}") + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +# - namespace='CELERY' means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object("django.conf:settings", namespace="CELERY") + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks() diff --git a/{{cookiecutter.project_slug}}/config/settings/__init__.py b/{{cookiecutter.project_slug}}/config/settings/__init__.py index 40a96afc6f..e69de29bb2 100644 --- a/{{cookiecutter.project_slug}}/config/settings/__init__.py +++ b/{{cookiecutter.project_slug}}/config/settings/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py new file mode 100644 index 0000000000..9cd6a081b5 --- /dev/null +++ b/{{cookiecutter.project_slug}}/config/settings/base.py @@ -0,0 +1,358 @@ +""" +Base settings to build other settings files upon. +""" +from pathlib import Path + +import environ + +ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent +# {{ cookiecutter.project_slug }}/ +APPS_DIR = ROOT_DIR / "{{ cookiecutter.project_slug }}" +env = environ.Env() + +READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False) +if READ_DOT_ENV_FILE: + # OS environment variables take precedence over variables from .env + env.read_env(str(ROOT_DIR / ".env")) + +# GENERAL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#debug +DEBUG = env.bool("DJANGO_DEBUG", False) +# Local time zone. Choices are +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# though not all of them may be available with every OS. +# In Windows, this must be set to your system time zone. +TIME_ZONE = "{{ cookiecutter.timezone }}" +# https://docs.djangoproject.com/en/dev/ref/settings/#language-code +LANGUAGE_CODE = "en-us" +# https://docs.djangoproject.com/en/dev/ref/settings/#site-id +SITE_ID = 1 +# https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n +USE_I18N = True +# https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n +USE_L10N = True +# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz +USE_TZ = True +# https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths +LOCALE_PATHS = [str(ROOT_DIR / "locale")] + +# DATABASES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#databases +{% if cookiecutter.use_docker == "y" -%} +DATABASES = {"default": env.db("DATABASE_URL")} +{%- else %} +DATABASES = { + "default": env.db( + "DATABASE_URL", + default="postgres://{% if cookiecutter.windows == 'y' %}localhost{% endif %}/{{cookiecutter.project_slug}}", + ), +} +{%- endif %} +DATABASES["default"]["ATOMIC_REQUESTS"] = True +# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-DEFAULT_AUTO_FIELD +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# URLS +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf +ROOT_URLCONF = "config.urls" +# https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application +WSGI_APPLICATION = "config.wsgi.application" + +# APPS +# ------------------------------------------------------------------------------ +DJANGO_APPS = [ + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.sites", + "django.contrib.messages", + "django.contrib.staticfiles", + # "django.contrib.humanize", # Handy template tags + "django.contrib.admin", + "django.forms", +] +THIRD_PARTY_APPS = [ + "crispy_forms", + "crispy_bootstrap5", + "allauth", + "allauth.account", + "allauth.socialaccount", +{%- if cookiecutter.use_celery == 'y' %} + "django_celery_beat", +{%- endif %} +{%- if cookiecutter.use_drf == "y" %} + "rest_framework", + "rest_framework.authtoken", + "corsheaders", + "drf_spectacular", +{%- endif %} +] + +LOCAL_APPS = [ + "{{ cookiecutter.project_slug }}.users", + # Your stuff: custom apps go here +] +# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps +INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS + +# MIGRATIONS +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#migration-modules +MIGRATION_MODULES = {"sites": "{{ cookiecutter.project_slug }}.contrib.sites.migrations"} + +# AUTHENTICATION +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends +AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", + "allauth.account.auth_backends.AuthenticationBackend", +] +# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model +AUTH_USER_MODEL = "users.User" +# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url +LOGIN_REDIRECT_URL = "users:redirect" +# https://docs.djangoproject.com/en/dev/ref/settings/#login-url +LOGIN_URL = "account_login" + +# PASSWORDS +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers +PASSWORD_HASHERS = [ + # https://docs.djangoproject.com/en/dev/topics/auth/passwords/#using-argon2-with-django + "django.contrib.auth.hashers.Argon2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", + "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", +] +# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" + }, + {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, +] + +# MIDDLEWARE +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#middleware +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", +{%- if cookiecutter.use_drf == 'y' %} + "corsheaders.middleware.CorsMiddleware", +{%- endif %} +{%- if cookiecutter.use_whitenoise == 'y' %} + "whitenoise.middleware.WhiteNoiseMiddleware", +{%- endif %} + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.common.BrokenLinkEmailsMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +# STATIC +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#static-root +STATIC_ROOT = str(ROOT_DIR / "staticfiles") +# https://docs.djangoproject.com/en/dev/ref/settings/#static-url +STATIC_URL = "/static/" +# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS +STATICFILES_DIRS = [str(APPS_DIR / "static")] +# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders +STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", +] + +# MEDIA +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#media-root +MEDIA_ROOT = str(APPS_DIR / "media") +# https://docs.djangoproject.com/en/dev/ref/settings/#media-url +MEDIA_URL = "/media/" + +# TEMPLATES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#templates +TEMPLATES = [ + { + # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND + "BACKEND": "django.template.backends.django.DjangoTemplates", + # https://docs.djangoproject.com/en/dev/ref/settings/#dirs + "DIRS": [str(APPS_DIR / "templates")], + # https://docs.djangoproject.com/en/dev/ref/settings/#app-dirs + "APP_DIRS": True, + "OPTIONS": { + # https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + "{{cookiecutter.project_slug}}.users.context_processors.allauth_settings", + ], + }, + } +] + +# https://docs.djangoproject.com/en/dev/ref/settings/#form-renderer +FORM_RENDERER = "django.forms.renderers.TemplatesSetting" + +# http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs +CRISPY_TEMPLATE_PACK = "bootstrap5" +CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" + +# FIXTURES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs +FIXTURE_DIRS = (str(APPS_DIR / "fixtures"),) + +# SECURITY +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly +SESSION_COOKIE_HTTPONLY = True +# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly +CSRF_COOKIE_HTTPONLY = True +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter +SECURE_BROWSER_XSS_FILTER = True +# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options +X_FRAME_OPTIONS = "DENY" + +# EMAIL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend +EMAIL_BACKEND = env( + "DJANGO_EMAIL_BACKEND", + default="django.core.mail.backends.smtp.EmailBackend", +) +# https://docs.djangoproject.com/en/dev/ref/settings/#email-timeout +EMAIL_TIMEOUT = 5 + +# ADMIN +# ------------------------------------------------------------------------------ +# Django Admin URL. +ADMIN_URL = "admin/" +# https://docs.djangoproject.com/en/dev/ref/settings/#admins +ADMINS = [("""{{cookiecutter.author_name}}""", "{{cookiecutter.email}}")] +# https://docs.djangoproject.com/en/dev/ref/settings/#managers +MANAGERS = ADMINS + +# LOGGING +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#logging +# See https://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s " + "%(process)d %(thread)d %(message)s" + } + }, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + } + }, + "root": {"level": "INFO", "handlers": ["console"]}, +} + +{% if cookiecutter.use_celery == 'y' -%} +# Celery +# ------------------------------------------------------------------------------ +if USE_TZ: + # https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-timezone + CELERY_TIMEZONE = TIME_ZONE +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-broker_url +CELERY_BROKER_URL = env("CELERY_BROKER_URL") +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-result_backend +CELERY_RESULT_BACKEND = CELERY_BROKER_URL +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#result-extended +CELERY_RESULT_EXTENDED = True +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-accept_content +CELERY_ACCEPT_CONTENT = ["json"] +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-task_serializer +CELERY_TASK_SERIALIZER = "json" +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#std:setting-result_serializer +CELERY_RESULT_SERIALIZER = "json" +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-time-limit +# TODO: set to whatever value is adequate in your circumstances +CELERY_TASK_TIME_LIMIT = 5 * 60 +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-soft-time-limit +# TODO: set to whatever value is adequate in your circumstances +CELERY_TASK_SOFT_TIME_LIMIT = 60 +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#beat-scheduler +CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" + +{%- endif %} +# django-allauth +# ------------------------------------------------------------------------------ +ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True) +# https://django-allauth.readthedocs.io/en/latest/configuration.html +ACCOUNT_AUTHENTICATION_METHOD = "username" +# https://django-allauth.readthedocs.io/en/latest/configuration.html +ACCOUNT_EMAIL_REQUIRED = True +# https://django-allauth.readthedocs.io/en/latest/configuration.html +ACCOUNT_EMAIL_VERIFICATION = "mandatory" +# https://django-allauth.readthedocs.io/en/latest/configuration.html +ACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.AccountAdapter" +# https://django-allauth.readthedocs.io/en/latest/forms.html +ACCOUNT_FORMS = {"signup": "{{cookiecutter.project_slug}}.users.forms.UserSignupForm"} +# https://django-allauth.readthedocs.io/en/latest/configuration.html +SOCIALACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter" +# https://django-allauth.readthedocs.io/en/latest/forms.html +SOCIALACCOUNT_FORMS = {"signup": "{{cookiecutter.project_slug}}.users.forms.UserSocialSignupForm"} +{% if cookiecutter.frontend_pipeline == 'Django Compressor' -%} +# django-compressor +# ------------------------------------------------------------------------------ +# https://django-compressor.readthedocs.io/en/latest/quickstart/#installation +INSTALLED_APPS += ["compressor"] +STATICFILES_FINDERS += ["compressor.finders.CompressorFinder"] +{%- endif %} +{% if cookiecutter.use_drf == "y" -%} +# django-rest-framework +# ------------------------------------------------------------------------------- +# django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/ +REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework.authentication.SessionAuthentication", + "rest_framework.authentication.TokenAuthentication", + ), + "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", +} + +# django-cors-headers - https://github.com/adamchainz/django-cors-headers#setup +CORS_URLS_REGEX = r"^/api/.*$" + +# By Default swagger ui is available only to admin user(s). You can change permission classes to change that +# See more configuration options at https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings +SPECTACULAR_SETTINGS = { + "TITLE": "{{ cookiecutter.project_name }} API", + "DESCRIPTION": "Documentation of API endpoints of {{ cookiecutter.project_name }}", + "VERSION": "1.0.0", + "SERVE_PERMISSIONS": ["rest_framework.permissions.IsAdminUser"], + "SERVERS": [ + {"url": "http://127.0.0.1:8000", "description": "Local Development server"}, + {"url": "https://{{ cookiecutter.domain_name }}", "description": "Production server"}, + ], +} +{%- endif %} +# Your stuff... +# ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/config/settings/common.py b/{{cookiecutter.project_slug}}/config/settings/common.py deleted file mode 100644 index 3668b4274e..0000000000 --- a/{{cookiecutter.project_slug}}/config/settings/common.py +++ /dev/null @@ -1,239 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Django settings for {{cookiecutter.project_name}} project. - -For more information on this file, see -https://docs.djangoproject.com/en/dev/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/dev/ref/settings/ -""" -from __future__ import absolute_import, unicode_literals - -import environ - -ROOT_DIR = environ.Path(__file__) - 3 # ({{ cookiecutter.project_slug }}/config/settings/common.py - 3 = {{ cookiecutter.project_slug }}/) -APPS_DIR = ROOT_DIR.path('{{ cookiecutter.project_slug }}') - -env = environ.Env() - -# APP CONFIGURATION -# ------------------------------------------------------------------------------ -DJANGO_APPS = ( - # Default Django apps: - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - # Useful template tags: - # 'django.contrib.humanize', - - # Admin - 'django.contrib.admin', -) -THIRD_PARTY_APPS = ( - 'crispy_forms', # Form layouts - 'allauth', # registration - 'allauth.account', # registration - 'allauth.socialaccount', # registration -) - -# Apps specific for this project go here. -LOCAL_APPS = ( - '{{ cookiecutter.project_slug }}.users', # custom users app - # Your stuff: custom apps go here -) - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps -INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS - -# MIDDLEWARE CONFIGURATION -# ------------------------------------------------------------------------------ -MIDDLEWARE_CLASSES = ( - # Make sure djangosecure.middleware.SecurityMiddleware is listed first - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) - -# MIGRATIONS CONFIGURATION -# ------------------------------------------------------------------------------ -MIGRATION_MODULES = { - 'sites': '{{ cookiecutter.project_slug }}.contrib.sites.migrations' -} - -# DEBUG -# ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug -DEBUG = env.bool('DJANGO_DEBUG', False) - -# FIXTURE CONFIGURATION -# ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS -FIXTURE_DIRS = ( - str(APPS_DIR.path('fixtures')), -) - -# EMAIL CONFIGURATION -# ------------------------------------------------------------------------------ -EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend') - -# MANAGER CONFIGURATION -# ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/dev/ref/settings/#admins -ADMINS = ( - ("""{{cookiecutter.author_name}}""", '{{cookiecutter.email}}'), -) - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#managers -MANAGERS = ADMINS - -# DATABASE CONFIGURATION -# ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases -DATABASES = { - # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ - 'default': env.db('DATABASE_URL', default='postgres://{% if cookiecutter.windows == 'y' %}localhost{% endif %}/{{cookiecutter.project_slug}}'), -} -DATABASES['default']['ATOMIC_REQUESTS'] = True - - -# GENERAL CONFIGURATION -# ------------------------------------------------------------------------------ -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# In a Windows environment this must be set to your system time zone. -TIME_ZONE = '{{ cookiecutter.timezone }}' - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code -LANGUAGE_CODE = 'en-us' - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id -SITE_ID = 1 - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n -USE_I18N = True - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n -USE_L10N = True - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz -USE_TZ = True - -# TEMPLATE CONFIGURATION -# ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/dev/ref/settings/#templates -TEMPLATES = [ - { - # See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs - 'DIRS': [ - str(APPS_DIR.path('templates')), - ], - 'OPTIONS': { - # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug - 'debug': DEBUG, - # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders - # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types - 'loaders': [ - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - ], - # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.template.context_processors.i18n', - 'django.template.context_processors.media', - 'django.template.context_processors.static', - 'django.template.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - # Your stuff: custom template context processors go here - ], - }, - }, -] - -# See: http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs -CRISPY_TEMPLATE_PACK = 'bootstrap3' - -# STATIC FILE CONFIGURATION -# ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root -STATIC_ROOT = str(ROOT_DIR('staticfiles')) - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url -STATIC_URL = '/static/' - -# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS -STATICFILES_DIRS = ( - str(APPS_DIR.path('static')), -) - -# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -) - -# MEDIA CONFIGURATION -# ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root -MEDIA_ROOT = str(APPS_DIR('media')) - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url -MEDIA_URL = '/media/' - -# URL Configuration -# ------------------------------------------------------------------------------ -ROOT_URLCONF = 'config.urls' - -# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application -WSGI_APPLICATION = 'config.wsgi.application' - -# AUTHENTICATION CONFIGURATION -# ------------------------------------------------------------------------------ -AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', - 'allauth.account.auth_backends.AuthenticationBackend', -) - -# Some really nice defaults -ACCOUNT_AUTHENTICATION_METHOD = 'username' -ACCOUNT_EMAIL_REQUIRED = True -ACCOUNT_EMAIL_VERIFICATION = 'mandatory' - -ACCOUNT_ALLOW_REGISTRATION = env.bool('DJANGO_ACCOUNT_ALLOW_REGISTRATION', True) -ACCOUNT_ADAPTER = '{{cookiecutter.project_slug}}.users.adapters.AccountAdapter' -SOCIALACCOUNT_ADAPTER = '{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter' - -# Custom user app defaults -# Select the correct user model -AUTH_USER_MODEL = 'users.User' -LOGIN_REDIRECT_URL = 'users:redirect' -LOGIN_URL = 'account_login' - -# SLUGLIFIER -AUTOSLUG_SLUGIFY_FUNCTION = 'slugify.slugify' -{% if cookiecutter.use_celery == 'y' %} -########## CELERY -INSTALLED_APPS += ('{{cookiecutter.project_slug}}.taskapp.celery.CeleryConfig',) -# if you are not using the django database broker (e.g. rabbitmq, redis, memcached), you can remove the next line. -INSTALLED_APPS += ('kombu.transport.django',) -BROKER_URL = env('CELERY_BROKER_URL', default='django://') -########## END CELERY -{% endif %} - -# Location of root django.contrib.admin URL, use {% raw %}{% url 'admin:index' %}{% endraw %} -ADMIN_URL = r'^admin/' - -# Your common stuff: Below this line define 3rd party library settings diff --git a/{{cookiecutter.project_slug}}/config/settings/local.py b/{{cookiecutter.project_slug}}/config/settings/local.py index b3227a2b6c..a5fe0f71c0 100644 --- a/{{cookiecutter.project_slug}}/config/settings/local.py +++ b/{{cookiecutter.project_slug}}/config/settings/local.py @@ -1,72 +1,99 @@ -# -*- coding: utf-8 -*- -""" -Local settings +from .base import * # noqa +from .base import env -- Run in Debug mode -- Use console backend for emails -- Add Django Debug Toolbar -- Add django-extensions as app -""" - -from .common import * # noqa - -# DEBUG +# GENERAL # ------------------------------------------------------------------------------ -DEBUG = env.bool('DJANGO_DEBUG', default=True) -TEMPLATES[0]['OPTIONS']['debug'] = DEBUG +# https://docs.djangoproject.com/en/dev/ref/settings/#debug +DEBUG = True +# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +SECRET_KEY = env( + "DJANGO_SECRET_KEY", + default="!!!SET DJANGO_SECRET_KEY!!!", +) +# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts +ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] -# SECRET CONFIGURATION +# CACHES # ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key -# Note: This key only used for development and testing. -SECRET_KEY = env('DJANGO_SECRET_KEY', default='CHANGEME!!!') +# https://docs.djangoproject.com/en/dev/ref/settings/#caches +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "", + } +} -# Mail settings +# EMAIL # ------------------------------------------------------------------------------ - +{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'y' -%} +# https://docs.djangoproject.com/en/dev/ref/settings/#email-host +EMAIL_HOST = env("EMAIL_HOST", default="mailhog") +# https://docs.djangoproject.com/en/dev/ref/settings/#email-port EMAIL_PORT = 1025 -{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'y' %} -EMAIL_HOST = env("EMAIL_HOST", default='mailhog') -{% else %} -EMAIL_HOST = 'localhost' -EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', - default='django.core.mail.backends.console.EmailBackend') -{% endif %} +{%- elif cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' -%} +# https://docs.djangoproject.com/en/dev/ref/settings/#email-host +EMAIL_HOST = "localhost" +# https://docs.djangoproject.com/en/dev/ref/settings/#email-port +EMAIL_PORT = 1025 +{%- else -%} +# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend +EMAIL_BACKEND = env( + "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend" +) +{%- endif %} -# CACHING +{%- if cookiecutter.use_whitenoise == 'y' %} + +# WhiteNoise # ------------------------------------------------------------------------------ -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': '' - } -} +# http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development +INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa F405 +{% endif %} # django-debug-toolbar # ------------------------------------------------------------------------------ -MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',) -INSTALLED_APPS += ('debug_toolbar', ) - -INTERNAL_IPS = ('127.0.0.1', '10.0.2.2',) - +# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites +INSTALLED_APPS += ["debug_toolbar"] # noqa F405 +# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware +MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa F405 +# https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config DEBUG_TOOLBAR_CONFIG = { - 'DISABLE_PANELS': [ - 'debug_toolbar.panels.redirects.RedirectsPanel', - ], - 'SHOW_TEMPLATE_CONTEXT': True, + "DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"], + "SHOW_TEMPLATE_CONTEXT": True, } +# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips +INTERNAL_IPS = ["127.0.0.1", "10.0.2.2"] +{% if cookiecutter.use_docker == 'y' -%} +if env("USE_DOCKER") == "yes": + import socket + + hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) + INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips] + {%- if cookiecutter.frontend_pipeline == 'Gulp' %} + try: + _, _, ips = socket.gethostbyname_ex("node") + INTERNAL_IPS.extend(ips) + except socket.gaierror: + # The node container isn't started (yet?) + pass + {%- endif %} +{%- endif %} # django-extensions # ------------------------------------------------------------------------------ -INSTALLED_APPS += ('django_extensions', ) +# https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration +INSTALLED_APPS += ["django_extensions"] # noqa F405 +{% if cookiecutter.use_celery == 'y' -%} -# TESTING +# Celery +# ------------------------------------------------------------------------------ +{% if cookiecutter.use_docker == 'n' -%} +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-always-eager +CELERY_TASK_ALWAYS_EAGER = True +{%- endif %} +# https://docs.celeryq.dev/en/stable/userguide/configuration.html#task-eager-propagates +CELERY_TASK_EAGER_PROPAGATES = True + +{%- endif %} +# Your stuff... # ------------------------------------------------------------------------------ -TEST_RUNNER = 'django.test.runner.DiscoverRunner' -{% if cookiecutter.use_celery == 'y' %} -########## CELERY -# In development, all tasks will be executed locally by blocking until the task returns -CELERY_ALWAYS_EAGER = True -########## END CELERY -{% endif %} -# Your local stuff: Below this line define 3rd party library settings diff --git a/{{cookiecutter.project_slug}}/config/settings/production.py b/{{cookiecutter.project_slug}}/config/settings/production.py index 80302bee44..8eed5a805c 100644 --- a/{{cookiecutter.project_slug}}/config/settings/production.py +++ b/{{cookiecutter.project_slug}}/config/settings/production.py @@ -1,310 +1,364 @@ -# -*- coding: utf-8 -*- -""" -Production Configurations - -- Use djangosecure -- Use Amazon's S3 for storing static files and uploaded media -- Use mailgun to send emails -- Use Redis on Heroku -{% if cookiecutter.use_sentry == 'y' %} -- Use sentry for error logging -{% endif %} -{% if cookiecutter.use_opbeat == 'y' %} -- Use opbeat for error reporting -{% endif %} -""" -from __future__ import absolute_import, unicode_literals - -from boto.s3.connection import OrdinaryCallingFormat -from django.utils import six -{% if cookiecutter.use_sentry == 'y' %} +{% if cookiecutter.use_sentry == 'y' -%} import logging -{% endif %} - -from .common import * # noqa -# SECRET CONFIGURATION -# ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key -# Raises ImproperlyConfigured exception if DJANGO_SECRET_KEY not in os.environ -SECRET_KEY = env('DJANGO_SECRET_KEY') +import sentry_sdk +{%- if cookiecutter.use_celery == 'y' %} +from sentry_sdk.integrations.celery import CeleryIntegration +{%- endif %} +from sentry_sdk.integrations.django import DjangoIntegration +from sentry_sdk.integrations.logging import LoggingIntegration +from sentry_sdk.integrations.redis import RedisIntegration -# This ensures that Django will be able to detect a secure connection -# properly on Heroku. -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +{% endif -%} +from .base import * # noqa +from .base import env -# django-secure +# GENERAL # ------------------------------------------------------------------------------ -INSTALLED_APPS += ('djangosecure', ) -{% if cookiecutter.use_sentry == 'y' -%} -# raven sentry client -# See https://docs.getsentry.com/hosted/clients/python/integrations/django/ -INSTALLED_APPS += ('raven.contrib.django.raven_compat', ) -{%- endif %} -SECURITY_MIDDLEWARE = ( - 'djangosecure.middleware.SecurityMiddleware', -) -{% if cookiecutter.use_whitenoise == 'y' -%} -# Use Whitenoise to serve static files -# See: https://whitenoise.readthedocs.io/ -WHITENOISE_MIDDLEWARE = ( - 'whitenoise.middleware.WhiteNoiseMiddleware', -) -MIDDLEWARE_CLASSES = WHITENOISE_MIDDLEWARE + MIDDLEWARE_CLASSES -{%- endif %} -{% if cookiecutter.use_sentry == 'y' -%} -RAVEN_MIDDLEWARE = ( - 'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware', -) -MIDDLEWARE_CLASSES = RAVEN_MIDDLEWARE + MIDDLEWARE_CLASSES -{%- endif %} +# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +SECRET_KEY = env("DJANGO_SECRET_KEY") +# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts +ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["{{ cookiecutter.domain_name }}"]) -# Make sure djangosecure.middleware.SecurityMiddleware is listed first -MIDDLEWARE_CLASSES = SECURITY_MIDDLEWARE + MIDDLEWARE_CLASSES +# DATABASES +# ------------------------------------------------------------------------------ +DATABASES["default"] = env.db("DATABASE_URL") # noqa F405 +DATABASES["default"]["ATOMIC_REQUESTS"] = True # noqa F405 +DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60) # noqa F405 -{% if cookiecutter.use_opbeat == 'y' -%} -# opbeat integration -# See https://opbeat.com/languages/django/ -INSTALLED_APPS += ('opbeat.contrib.django',) -OPBEAT = { - 'ORGANIZATION_ID': env('DJANGO_OPBEAT_ORGANIZATION_ID'), - 'APP_ID': env('DJANGO_OPBEAT_APP_ID'), - 'SECRET_TOKEN': env('DJANGO_OPBEAT_SECRET_TOKEN') +# CACHES +# ------------------------------------------------------------------------------ +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": env("REDIS_URL"), + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + # Mimicing memcache behavior. + # https://github.com/jazzband/django-redis#memcached-exceptions-behavior + "IGNORE_EXCEPTIONS": True, + }, + } } -MIDDLEWARE_CLASSES = ( - 'opbeat.contrib.django.middleware.OpbeatAPMMiddleware', -) + MIDDLEWARE_CLASSES -{%- endif %} -# set this to 60 seconds and then to 518400 when you can prove it works + +# SECURITY +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect +SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True) +# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure +SESSION_COOKIE_SECURE = True +# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure +CSRF_COOKIE_SECURE = True +# https://docs.djangoproject.com/en/dev/topics/security/#ssl-https +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds +# TODO: set this to 60 seconds first and then to 518400 once you prove the former works SECURE_HSTS_SECONDS = 60 +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool( - 'DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS', default=True) + "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True +) +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload +SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True) +# https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff SECURE_CONTENT_TYPE_NOSNIFF = env.bool( - 'DJANGO_SECURE_CONTENT_TYPE_NOSNIFF', default=True) -SECURE_BROWSER_XSS_FILTER = True -SESSION_COOKIE_SECURE = False -SESSION_COOKIE_HTTPONLY = True -SECURE_SSL_REDIRECT = env.bool('DJANGO_SECURE_SSL_REDIRECT', default=True) - -# SITE CONFIGURATION -# ------------------------------------------------------------------------------ -# Hosts/domain names that are valid for this site -# See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts -ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['{{cookiecutter.domain_name}}']) -# END SITE CONFIGURATION - -INSTALLED_APPS += ('gunicorn', ) - -# STORAGE CONFIGURATION -# ------------------------------------------------------------------------------ -# Uploaded Media Files -# ------------------------ -# See: http://django-storages.readthedocs.io/en/latest/index.html -INSTALLED_APPS += ( - 'storages', + "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True ) -AWS_ACCESS_KEY_ID = env('DJANGO_AWS_ACCESS_KEY_ID') -AWS_SECRET_ACCESS_KEY = env('DJANGO_AWS_SECRET_ACCESS_KEY') -AWS_STORAGE_BUCKET_NAME = env('DJANGO_AWS_STORAGE_BUCKET_NAME') -AWS_AUTO_CREATE_BUCKET = True +{% if cookiecutter.cloud_provider != 'None' -%} +# STORAGES +# ------------------------------------------------------------------------------ +# https://django-storages.readthedocs.io/en/latest/#installation +INSTALLED_APPS += ["storages"] # noqa F405 +{%- endif -%} +{% if cookiecutter.cloud_provider == 'AWS' %} +# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings +AWS_ACCESS_KEY_ID = env("DJANGO_AWS_ACCESS_KEY_ID") +# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings +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_QUERYSTRING_AUTH = False -AWS_S3_CALLING_FORMAT = OrdinaryCallingFormat() - -# AWS cache settings, don't change unless you know what you're doing: -AWS_EXPIRY = 60 * 60 * 24 * 7 - -# TODO See: https://github.com/jschneier/django-storages/issues/47 -# Revert the following and use str after the above-mentioned bug is fixed in -# either django-storage-redux or boto -AWS_HEADERS = { - 'Cache-Control': six.b('max-age=%d, s-maxage=%d, must-revalidate' % ( - AWS_EXPIRY, AWS_EXPIRY)) +# DO NOT change these unless you know what you're doing. +_AWS_EXPIRY = 60 * 60 * 24 * 7 +# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings +AWS_S3_OBJECT_PARAMETERS = { + "CacheControl": f"max-age={_AWS_EXPIRY}, s-maxage={_AWS_EXPIRY}, must-revalidate" } +# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings +AWS_S3_MAX_MEMORY_SIZE = env.int( + "DJANGO_AWS_S3_MAX_MEMORY_SIZE", + default=100_000_000, # 100MB +) +# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings +AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None) +# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#cloudfront +AWS_S3_CUSTOM_DOMAIN = env("DJANGO_AWS_S3_CUSTOM_DOMAIN", default=None) +aws_s3_domain = AWS_S3_CUSTOM_DOMAIN or f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com" +{% elif cookiecutter.cloud_provider == 'GCP' %} +GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME") +GS_DEFAULT_ACL = "publicRead" +{% endif -%} -# URL that handles the media served from MEDIA_ROOT, used for managing -# stored files. -{% if cookiecutter.use_whitenoise == 'y' -%} -MEDIA_URL = 'https://s3.amazonaws.com/%s/' % AWS_STORAGE_BUCKET_NAME -{% else %} -# See:http://stackoverflow.com/questions/10390244/ -from storages.backends.s3boto import S3BotoStorage -StaticRootS3BotoStorage = lambda: S3BotoStorage(location='static') -MediaRootS3BotoStorage = lambda: S3BotoStorage(location='media') -DEFAULT_FILE_STORAGE = 'config.settings.production.MediaRootS3BotoStorage' - -MEDIA_URL = 'https://s3.amazonaws.com/%s/media/' % AWS_STORAGE_BUCKET_NAME -{%- endif %} - -# Static Assets +{% if cookiecutter.cloud_provider != 'None' or cookiecutter.use_whitenoise == 'y' -%} +# STATIC # ------------------------ +{% endif -%} {% if cookiecutter.use_whitenoise == 'y' -%} -STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' -{% else %} -STATIC_URL = 'https://s3.amazonaws.com/%s/static/' % AWS_STORAGE_BUCKET_NAME -STATICFILES_STORAGE = 'config.settings.production.StaticRootS3BotoStorage' +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" +{% elif cookiecutter.cloud_provider == 'AWS' -%} +STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootS3Boto3Storage" +COLLECTFAST_STRATEGY = "collectfast.strategies.boto3.Boto3Strategy" +STATIC_URL = f"https://{aws_s3_domain}/static/" +{% elif cookiecutter.cloud_provider == 'GCP' -%} +STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootGoogleCloudStorage" +COLLECTFAST_STRATEGY = "collectfast.strategies.gcloud.GoogleCloudStrategy" +STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/" +{% endif -%} -# See: https://github.com/antonagestam/collectfast -# For Django 1.7+, 'collectfast' should come before -# 'django.contrib.staticfiles' -AWS_PRELOAD_METADATA = True -INSTALLED_APPS = ('collectfast', ) + INSTALLED_APPS +# MEDIA +# ------------------------------------------------------------------------------ +{%- if cookiecutter.cloud_provider == 'AWS' %} +DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootS3Boto3Storage" +MEDIA_URL = f"https://{aws_s3_domain}/media/" +{%- elif cookiecutter.cloud_provider == 'GCP' %} +DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootGoogleCloudStorage" +MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/" {%- endif %} # EMAIL # ------------------------------------------------------------------------------ -DEFAULT_FROM_EMAIL = env('DJANGO_DEFAULT_FROM_EMAIL', - default='{{cookiecutter.project_name}} ') -EMAIL_SUBJECT_PREFIX = env('DJANGO_EMAIL_SUBJECT_PREFIX', default='[{{cookiecutter.project_name}}] ') -SERVER_EMAIL = env('DJANGO_SERVER_EMAIL', default=DEFAULT_FROM_EMAIL) +# https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email +DEFAULT_FROM_EMAIL = env( + "DJANGO_DEFAULT_FROM_EMAIL", + default="{{cookiecutter.project_name}} ", +) +# https://docs.djangoproject.com/en/dev/ref/settings/#server-email +SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL) +# https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix +EMAIL_SUBJECT_PREFIX = env( + "DJANGO_EMAIL_SUBJECT_PREFIX", + default="[{{cookiecutter.project_name}}]", +) + +# ADMIN +# ------------------------------------------------------------------------------ +# Django Admin URL regex. +ADMIN_URL = env("DJANGO_ADMIN_URL") -# Anymail with Mailgun -INSTALLED_APPS += ("anymail", ) +# Anymail +# ------------------------------------------------------------------------------ +# https://anymail.readthedocs.io/en/stable/installation/#installing-anymail +INSTALLED_APPS += ["anymail"] # noqa F405 +# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend +# https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference +{%- if cookiecutter.mail_service == 'Mailgun' %} +# https://anymail.readthedocs.io/en/stable/esps/mailgun/ +EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend" +ANYMAIL = { + "MAILGUN_API_KEY": env("MAILGUN_API_KEY"), + "MAILGUN_SENDER_DOMAIN": env("MAILGUN_DOMAIN"), + "MAILGUN_API_URL": env("MAILGUN_API_URL", default="https://api.mailgun.net/v3"), +} +{%- elif cookiecutter.mail_service == 'Amazon SES' %} +# https://anymail.readthedocs.io/en/stable/esps/amazon_ses/ +EMAIL_BACKEND = "anymail.backends.amazon_ses.EmailBackend" +ANYMAIL = {} +{%- elif cookiecutter.mail_service == 'Mailjet' %} +# https://anymail.readthedocs.io/en/stable/esps/mailjet/ +EMAIL_BACKEND = "anymail.backends.mailjet.EmailBackend" ANYMAIL = { - "MAILGUN_API_KEY": env('DJANGO_MAILGUN_API_KEY'), + "MAILJET_API_KEY": env("MAILJET_API_KEY"), + "MAILJET_SECRET_KEY": env("MAILJET_SECRET_KEY"), } -EMAIL_BACKEND = "anymail.backends.mailgun.MailgunBackend" +{%- elif cookiecutter.mail_service == 'Mandrill' %} +# https://anymail.readthedocs.io/en/stable/esps/mandrill/ +EMAIL_BACKEND = "anymail.backends.mandrill.EmailBackend" +ANYMAIL = { + "MANDRILL_API_KEY": env("MANDRILL_API_KEY"), + "MANDRILL_API_URL": env( + "MANDRILL_API_URL", default="https://mandrillapp.com/api/1.0" + ), +} +{%- elif cookiecutter.mail_service == 'Postmark' %} +# https://anymail.readthedocs.io/en/stable/esps/postmark/ +EMAIL_BACKEND = "anymail.backends.postmark.EmailBackend" +ANYMAIL = { + "POSTMARK_SERVER_TOKEN": env("POSTMARK_SERVER_TOKEN"), + "POSTMARK_API_URL": env("POSTMARK_API_URL", default="https://api.postmarkapp.com/"), +} +{%- elif cookiecutter.mail_service == 'Sendgrid' %} +# https://anymail.readthedocs.io/en/stable/esps/sendgrid/ +EMAIL_BACKEND = "anymail.backends.sendgrid.EmailBackend" +ANYMAIL = { + "SENDGRID_API_KEY": env("SENDGRID_API_KEY"), + "SENDGRID_API_URL": env("SENDGRID_API_URL", default="https://api.sendgrid.com/v3/"), +} +{%- elif cookiecutter.mail_service == 'SendinBlue' %} +# https://anymail.readthedocs.io/en/stable/esps/sendinblue/ +EMAIL_BACKEND = "anymail.backends.sendinblue.EmailBackend" +ANYMAIL = { + "SENDINBLUE_API_KEY": env("SENDINBLUE_API_KEY"), + "SENDINBLUE_API_URL": env( + "SENDINBLUE_API_URL", default="https://api.sendinblue.com/v3/" + ), +} +{%- elif cookiecutter.mail_service == 'SparkPost' %} +# https://anymail.readthedocs.io/en/stable/esps/sparkpost/ +EMAIL_BACKEND = "anymail.backends.sparkpost.EmailBackend" +ANYMAIL = { + "SPARKPOST_API_KEY": env("SPARKPOST_API_KEY"), + "SPARKPOST_API_URL": env( + "SPARKPOST_API_URL", default="https://api.sparkpost.com/api/v1" + ), +} +{%- elif cookiecutter.mail_service == 'Other SMTP' %} +# https://anymail.readthedocs.io/en/stable/esps +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" +ANYMAIL = {} +{%- endif %} -{% if cookiecutter.use_newrelic == 'y'-%}# NEW RELIC +{% if cookiecutter.frontend_pipeline == 'Django Compressor' -%} +# django-compressor # ------------------------------------------------------------------------------ -NEW_RELIC_LICENSE_KEY = env('NEW_RELIC_LICENSE_KEY') -NEW_RELIC_APP_NAME = env('NEW_RELIC_APP_NAME') +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_ENABLED +COMPRESS_ENABLED = env.bool("COMPRESS_ENABLED", default=True) +{%- if cookiecutter.cloud_provider == 'None' %} +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE +COMPRESS_STORAGE = "compressor.storage.GzipCompressorFileStorage" +{%- elif cookiecutter.cloud_provider in ('AWS', 'GCP') and cookiecutter.use_whitenoise == 'n' %} +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE +COMPRESS_STORAGE = STATICFILES_STORAGE {%- endif %} -# TEMPLATE CONFIGURATION -# ------------------------------------------------------------------------------ -# See: -# https://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.loaders.cached.Loader -TEMPLATES[0]['OPTIONS']['loaders'] = [ - ('django.template.loaders.cached.Loader', [ - 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ]), -] - -# DATABASE CONFIGURATION +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_URL +COMPRESS_URL = STATIC_URL{% if cookiecutter.use_whitenoise == 'y' or cookiecutter.cloud_provider == 'None' %} # noqa F405{% endif %} +{%- if cookiecutter.use_whitenoise == 'y' %} +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_OFFLINE +COMPRESS_OFFLINE = True # Offline compression is required when using Whitenoise +{%- endif %} +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_FILTERS +COMPRESS_FILTERS = { + "css": [ + "compressor.filters.css_default.CssAbsoluteFilter", + "compressor.filters.cssmin.rCSSMinFilter", + ], + "js": ["compressor.filters.jsmin.JSMinFilter"], +} +{% endif %} +{%- if cookiecutter.use_whitenoise == 'n' -%} +# Collectfast # ------------------------------------------------------------------------------ -# Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ -DATABASES['default'] = env.db('DATABASE_URL') - -# CACHING +# https://github.com/antonagestam/collectfast#installation +INSTALLED_APPS = ["collectfast"] + INSTALLED_APPS # noqa F405 +{% endif %} +# LOGGING # ------------------------------------------------------------------------------ -# Heroku URL does not pass the DB number, so we parse it in -CACHES = { - 'default': { - 'BACKEND': 'django_redis.cache.RedisCache', - 'LOCATION': '{0}/{1}'.format(env('REDIS_URL', default='redis://127.0.0.1:6379'), 0), - 'OPTIONS': { - 'CLIENT_CLASS': 'django_redis.client.DefaultClient', - 'IGNORE_EXCEPTIONS': True, # mimics memcache behavior. - # http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior - } - } -} - -{% if cookiecutter.use_sentry == 'y' %} -# Sentry Configuration -SENTRY_DSN = env('DJANGO_SENTRY_DSN') -SENTRY_CLIENT = env('DJANGO_SENTRY_CLIENT', default='raven.contrib.django.raven_compat.DjangoClient') +# https://docs.djangoproject.com/en/dev/ref/settings/#logging +# See https://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +{% if cookiecutter.use_sentry == 'n' -%} +# A sample logging configuration. The only tangible logging +# performed by this configuration is to send an email to +# the site admins on every HTTP 500 error when DEBUG=False. LOGGING = { - 'version': 1, - 'disable_existing_loggers': True, - 'root': { - 'level': 'WARNING', - 'handlers': ['sentry'], - }, - 'formatters': { - 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s ' - '%(process)d %(thread)d %(message)s' - }, - }, - 'handlers': { - 'sentry': { - 'level': 'ERROR', - 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', - }, - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'verbose' + "version": 1, + "disable_existing_loggers": False, + "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s " + "%(process)d %(thread)d %(message)s" } }, - 'loggers': { - 'django.db.backends': { - 'level': 'ERROR', - 'handlers': ['console'], - 'propagate': False, + "handlers": { + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler", }, - 'raven': { - 'level': 'DEBUG', - 'handlers': ['console'], - 'propagate': False, + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", }, - 'sentry.errors': { - 'level': 'DEBUG', - 'handlers': ['console'], - 'propagate': False, + }, + "root": {"level": "INFO", "handlers": ["console"]}, + "loggers": { + "django.request": { + "handlers": ["mail_admins"], + "level": "ERROR", + "propagate": True, }, - 'django.security.DisallowedHost': { - 'level': 'ERROR', - 'handlers': ['console', 'sentry'], - 'propagate': False, + "django.security.DisallowedHost": { + "level": "ERROR", + "handlers": ["console", "mail_admins"], + "propagate": True, }, }, } -SENTRY_CELERY_LOGLEVEL = env.int('DJANGO_SENTRY_LOG_LEVEL', logging.INFO) -RAVEN_CONFIG = { - 'CELERY_LOGLEVEL': env.int('DJANGO_SENTRY_LOG_LEVEL', logging.INFO), - 'DSN': SENTRY_DSN -} -{% elif cookiecutter.use_sentry == 'n' %} -# LOGGING CONFIGURATION -# ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging -# A sample logging configuration. The only tangible logging -# performed by this configuration is to send an email to -# the site admins on every HTTP 500 error when DEBUG=False. -# See http://docs.djangoproject.com/en/dev/topics/logging for -# more details on how to customize your logging configuration. +{% else %} LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' + "version": 1, + "disable_existing_loggers": True, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s " + "%(process)d %(thread)d %(message)s" } }, - 'formatters': { - 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s ' - '%(process)d %(thread)d %(message)s' - }, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + } }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' + "root": {"level": "INFO", "handlers": ["console"]}, + "loggers": { + "django.db.backends": { + "level": "ERROR", + "handlers": ["console"], + "propagate": False, }, - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'verbose', + # Errors logged by the SDK itself + "sentry_sdk": {"level": "ERROR", "handlers": ["console"], "propagate": False}, + "django.security.DisallowedHost": { + "level": "ERROR", + "handlers": ["console"], + "propagate": False, }, }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True - }, - 'django.security.DisallowedHost': { - 'level': 'ERROR', - 'handlers': ['console', 'mail_admins'], - 'propagate': True - } - } } -{% endif %} -# Custom Admin URL, use {% raw %}{% url 'admin:index' %}{% endraw %} -ADMIN_URL = env('DJANGO_ADMIN_URL') -# Your production stuff: Below this line define 3rd party library settings +# Sentry +# ------------------------------------------------------------------------------ +SENTRY_DSN = env("SENTRY_DSN") +SENTRY_LOG_LEVEL = env.int("DJANGO_SENTRY_LOG_LEVEL", logging.INFO) + +sentry_logging = LoggingIntegration( + level=SENTRY_LOG_LEVEL, # Capture info and above as breadcrumbs + event_level=logging.ERROR, # Send errors as events +) + +{%- if cookiecutter.use_celery == 'y' %} +integrations = [ + sentry_logging, + DjangoIntegration(), + CeleryIntegration(), + RedisIntegration(), +] +{% else %} +integrations = [sentry_logging, DjangoIntegration(), RedisIntegration()] +{% endif -%} + +sentry_sdk.init( + dsn=SENTRY_DSN, + integrations=integrations, + environment=env("SENTRY_ENVIRONMENT", default="production"), + traces_sample_rate=env.float("SENTRY_TRACES_SAMPLE_RATE", default=0.0), +) +{% endif %} +# Your stuff... +# ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/config/settings/test.py b/{{cookiecutter.project_slug}}/config/settings/test.py new file mode 100644 index 0000000000..f103eb10b3 --- /dev/null +++ b/{{cookiecutter.project_slug}}/config/settings/test.py @@ -0,0 +1,33 @@ +""" +With these settings, tests run faster. +""" + +from .base import * # noqa +from .base import env + +# GENERAL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +SECRET_KEY = env( + "DJANGO_SECRET_KEY", + default="!!!SET DJANGO_SECRET_KEY!!!", +) +# https://docs.djangoproject.com/en/dev/ref/settings/#test-runner +TEST_RUNNER = "django.test.runner.DiscoverRunner" + +# PASSWORDS +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers +PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] + +# EMAIL +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend +EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" + +# DEBUGING FOR TEMPLATES +# ------------------------------------------------------------------------------ +TEMPLATES[0]["OPTIONS"]["debug"] = True # type: ignore # noqa F405 + +# Your stuff... +# ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/config/urls.py b/{{cookiecutter.project_slug}}/config/urls.py index f3a621127e..ab42cc1032 100644 --- a/{{cookiecutter.project_slug}}/config/urls.py +++ b/{{cookiecutter.project_slug}}/config/urls.py @@ -1,35 +1,72 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings -from django.conf.urls import include, url from django.conf.urls.static import static from django.contrib import admin -from django.views.generic import TemplateView +{%- if cookiecutter.use_async == 'y' %} +from django.contrib.staticfiles.urls import staticfiles_urlpatterns +{%- endif %} +from django.urls import include, path from django.views import defaults as default_views +from django.views.generic import TemplateView +{%- if cookiecutter.use_drf == 'y' %} +from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView +from rest_framework.authtoken.views import obtain_auth_token +{%- endif %} urlpatterns = [ - url(r'^$', TemplateView.as_view(template_name='pages/home.html'), name='home'), - url(r'^about/$', TemplateView.as_view(template_name='pages/about.html'), name='about'), - + path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), + path( + "about/", TemplateView.as_view(template_name="pages/about.html"), name="about" + ), # Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %} - url(settings.ADMIN_URL, include(admin.site.urls)), - + path(settings.ADMIN_URL, admin.site.urls), # User management - url(r'^users/', include('{{ cookiecutter.project_slug }}.users.urls', namespace='users')), - url(r'^accounts/', include('allauth.urls')), - + path("users/", include("{{ cookiecutter.project_slug }}.users.urls", namespace="users")), + path("accounts/", include("allauth.urls")), # Your stuff: custom urls includes go here - - ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +{%- if cookiecutter.use_async == 'y' %} +if settings.DEBUG: + # Static file serving when using Gunicorn + Uvicorn for local web socket development + urlpatterns += staticfiles_urlpatterns() +{%- endif %} +{% if cookiecutter.use_drf == 'y' %} +# API URLS +urlpatterns += [ + # API base url + path("api/", include("config.api_router")), + # DRF auth token + path("auth-token/", obtain_auth_token), + path("api/schema/", SpectacularAPIView.as_view(), name="api-schema"), + path( + "api/docs/", + SpectacularSwaggerView.as_view(url_name="api-schema"), + name="api-docs", + ), +] +{%- endif %} if settings.DEBUG: # This allows the error pages to be debugged during development, just visit # these url in browser to see how these error pages look like. urlpatterns += [ - url(r'^400/$', default_views.bad_request, kwargs={'exception': Exception('Bad Request!')}), - url(r'^403/$', default_views.permission_denied, kwargs={'exception': Exception('Permission Denied')}), - url(r'^404/$', default_views.page_not_found, kwargs={'exception': Exception('Page not Found')}), - url(r'^500/$', default_views.server_error), + path( + "400/", + default_views.bad_request, + kwargs={"exception": Exception("Bad Request!")}, + ), + path( + "403/", + default_views.permission_denied, + kwargs={"exception": Exception("Permission Denied")}, + ), + path( + "404/", + default_views.page_not_found, + kwargs={"exception": Exception("Page not Found")}, + ), + path("500/", default_views.server_error), ] + if "debug_toolbar" in settings.INSTALLED_APPS: + import debug_toolbar + + urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns diff --git a/{{cookiecutter.project_slug}}/config/websocket.py b/{{cookiecutter.project_slug}}/config/websocket.py new file mode 100644 index 0000000000..81adfbc664 --- /dev/null +++ b/{{cookiecutter.project_slug}}/config/websocket.py @@ -0,0 +1,13 @@ +async def websocket_application(scope, receive, send): + while True: + event = await receive() + + if event["type"] == "websocket.connect": + await send({"type": "websocket.accept"}) + + if event["type"] == "websocket.disconnect": + break + + if event["type"] == "websocket.receive": + if event["text"] == "ping": + await send({"type": "websocket.send", "text": "pong!"}) diff --git a/{{cookiecutter.project_slug}}/config/wsgi.py b/{{cookiecutter.project_slug}}/config/wsgi.py index 4790b1b501..a7de581cad 100644 --- a/{{cookiecutter.project_slug}}/config/wsgi.py +++ b/{{cookiecutter.project_slug}}/config/wsgi.py @@ -14,18 +14,15 @@ """ import os +import sys +from pathlib import Path -{% if cookiecutter.use_newrelic == 'y' -%} -if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production': - import newrelic.agent - newrelic.agent.initialize() -{%- endif %} from django.core.wsgi import get_wsgi_application -{% if cookiecutter.use_sentry == 'y' -%} -if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production': - from raven.contrib.django.raven_compat.middleware.wsgi import Sentry -{%- endif %} +# This allows easy placement of apps within the interior +# {{ cookiecutter.project_slug }} directory. +ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent +sys.path.append(str(ROOT_DIR / "{{ cookiecutter.project_slug }}")) # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks # if running multiple sites in the same mod_wsgi process. To fix this, use # mod_wsgi daemon mode with each site in its own daemon process, or use @@ -36,14 +33,6 @@ # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. application = get_wsgi_application() -{% if cookiecutter.use_sentry == 'y' -%} -if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production': - application = Sentry(application) -{%- endif %} -{% if cookiecutter.use_newrelic == 'y' -%} -if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production': - application = newrelic.agent.WSGIApplicationWrapper(application) -{%- endif %} # Apply WSGI middleware here. # from helloworld.wsgi import HelloWorldApplication # application = HelloWorldApplication(application) diff --git a/{{cookiecutter.project_slug}}/dev.yml b/{{cookiecutter.project_slug}}/dev.yml deleted file mode 100644 index 725ae50e37..0000000000 --- a/{{cookiecutter.project_slug}}/dev.yml +++ /dev/null @@ -1,55 +0,0 @@ -version: '2' - -volumes: - postgres_data_dev: {} - postgres_backup_dev: {} - -services: - postgres: - build: ./compose/postgres - volumes: - - postgres_data_dev:/var/lib/postgresql/data - - postgres_backup_dev:/backups - environment: - - POSTGRES_USER={{cookiecutter.project_slug}} - - django: - build: - context: . - dockerfile: ./compose/django/Dockerfile-dev - command: python /app/manage.py runserver_plus 0.0.0.0:8000 - depends_on: - - postgres - environment: - - POSTGRES_USER={{cookiecutter.project_slug}} - volumes: - - .:/app - ports: - - "8000:8000" - links: - - postgres -{% if cookiecutter.use_mailhog == 'y' %} - - mailhog -{% endif %} - -{% if cookiecutter.use_pycharm == 'y' %} - pycharm: - build: - context: . - dockerfile: ./compose/django/Dockerfile-dev - depends_on: - - postgres - environment: - - POSTGRES_USER={{cookiecutter.project_slug}} - volumes: - - .:/app - links: - - postgres -{% endif %} - -{% if cookiecutter.use_mailhog == 'y' %} - mailhog: - image: mailhog/mailhog - ports: - - "8025:8025" -{% endif %} diff --git a/{{cookiecutter.project_slug}}/docker-compose.yml b/{{cookiecutter.project_slug}}/docker-compose.yml deleted file mode 100644 index df7ddb49ce..0000000000 --- a/{{cookiecutter.project_slug}}/docker-compose.yml +++ /dev/null @@ -1,57 +0,0 @@ -version: '2' - -volumes: - postgres_data: {} - postgres_backup: {} - -services: - postgres: - build: ./compose/postgres - volumes: - - postgres_data:/var/lib/postgresql/data - - postgres_backup:/backups - env_file: .env - - django: - build: - context: . - dockerfile: ./compose/django/Dockerfile - user: django - depends_on: - - postgres - - redis - command: /gunicorn.sh - env_file: .env - - nginx: - build: ./compose/nginx - depends_on: - - django - ports: - - "0.0.0.0:80:80" - - redis: - image: redis:3.0 - {% if cookiecutter.use_celery == 'y' %} - celeryworker: - build: - context: . - dockerfile: ./compose/django/Dockerfile - user: django - env_file: .env - depends_on: - - postgres - - redis - command: celery -A {{cookiecutter.project_slug}}.taskapp worker -l INFO - - celerybeat: - build: - context: . - dockerfile: ./compose/django/Dockerfile - user: django - env_file: .env - depends_on: - - postgres - - redis - command: celery -A {{cookiecutter.project_slug}}.taskapp beat -l INFO - {% endif %} diff --git a/{{cookiecutter.project_slug}}/docs/Makefile b/{{cookiecutter.project_slug}}/docs/Makefile index a5d4ea96ed..cf080e4767 100644 --- a/{{cookiecutter.project_slug}}/docs/Makefile +++ b/{{cookiecutter.project_slug}}/docs/Makefile @@ -1,153 +1,36 @@ -# Makefile for Sphinx documentation +# Minimal makefile for Sphinx documentation # -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = ./_build +{%- if cookiecutter.use_docker == 'y' %} +APP = /app +{%- else %} +APP = ../{{cookiecutter.project_slug}} +{% endif %} + +.PHONY: help livehtml apidocs Makefile + +# Put it first so that "make" without argument is like "make help". help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/{{ cookiecutter.project_slug }}.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/{{ cookiecutter.project_slug }}.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/{{ cookiecutter.project_slug }}" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/{{ cookiecutter.project_slug }}" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -c . + +# Build, watch and serve docs with live reload +livehtml: + sphinx-autobuild -b html + {%- if cookiecutter.use_docker == 'y' %} --host 0.0.0.0 + {%- else %} --open-browser + {%- endif %} --port 9000 --watch $(APP) -c . $(SOURCEDIR) $(BUILDDIR)/html + +# Outputs rst files from django application code +apidocs: + sphinx-apidoc -o $(SOURCEDIR)/api $(APP) + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -c . diff --git a/{{cookiecutter.project_slug}}/docs/conf.py b/{{cookiecutter.project_slug}}/docs/conf.py index 78ca7ce5e1..c640e1c63d 100644 --- a/{{cookiecutter.project_slug}}/docs/conf.py +++ b/{{cookiecutter.project_slug}}/docs/conf.py @@ -1,247 +1,69 @@ -# -*- coding: utf-8 -*- +# Configuration file for the Sphinx documentation builder. # -# {{ cookiecutter.project_name }} documentation build configuration file, created by -# sphinx-quickstart. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html -from __future__ import unicode_literals - -import os -import sys +# -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] +import os +import sys +import django + +if os.getenv("READTHEDOCS", default=False) == "True": + sys.path.insert(0, os.path.abspath("..")) + os.environ["DJANGO_READ_DOT_ENV_FILE"] = "True" + os.environ["USE_DOCKER"] = "no" +else: +{%- if cookiecutter.use_docker == 'y' %} + sys.path.insert(0, os.path.abspath("/app")) +{%- else %} + sys.path.insert(0, os.path.abspath("..")) +{%- endif %} +os.environ["DATABASE_URL"] = "sqlite:///readthedocs.db" +{%- if cookiecutter.use_celery == 'y' %} +os.environ["CELERY_BROKER_URL"] = os.getenv("REDIS_URL", "redis://redis:6379") +{%- endif %} +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") +django.setup() + +# -- Project information ----------------------------------------------------- + +project = "{{ cookiecutter.project_name }}" +copyright = """{% now 'utc', '%Y' %}, {{ cookiecutter.author_name }}""" +author = "{{ cookiecutter.author_name }}" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", +] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = '{{ cookiecutter.project_name }}' -copyright = """{{ cookiecutter.year }}, {{ cookiecutter.author_name }}""" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.1' -# The full version, including alpha/beta/rc tags. -release = '0.1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' +# templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- +# -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None +# +html_theme = "alabaster" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = '{{ cookiecutter.project_slug }}doc' - - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # 'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', - '{{ cookiecutter.project_slug }}.tex', - '{{ cookiecutter.project_name }} Documentation', - """{{ cookiecutter.author_name }}""", 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', '{{ cookiecutter.project_slug }}', '{{ cookiecutter.project_name }} Documentation', - ["""{{ cookiecutter.author_name }}"""], 1) -] - -# If true, show URL addresses after external links. -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', '{{ cookiecutter.project_slug }}', '{{ cookiecutter.project_name }} Documentation', - """{{ cookiecutter.author_name }}""", '{{ cookiecutter.project_name }}', - """{{ cookiecutter.description }}""", 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' +# html_static_path = ["_static"] diff --git a/{{cookiecutter.project_slug}}/docs/deploy.rst b/{{cookiecutter.project_slug}}/docs/deploy.rst deleted file mode 100644 index 1e642c7988..0000000000 --- a/{{cookiecutter.project_slug}}/docs/deploy.rst +++ /dev/null @@ -1,4 +0,0 @@ -Deploy -======== - -This is where you describe how the project is deployed in production. diff --git a/{{cookiecutter.project_slug}}/docs/docker_ec2.rst b/{{cookiecutter.project_slug}}/docs/docker_ec2.rst deleted file mode 100644 index 444172c497..0000000000 --- a/{{cookiecutter.project_slug}}/docs/docker_ec2.rst +++ /dev/null @@ -1,186 +0,0 @@ -Developing with Docker -====================== - -You can develop your application in a `Docker`_ container for simpler deployment onto bare Linux machines later. This instruction assumes an `Amazon Web Services`_ EC2 instance, but it should work on any machine with Docker > 1.3 and `Docker compose`_ installed. - -.. _Docker: https://www.docker.com/ -.. _Amazon Web Services: http://aws.amazon.com/ -.. _Docker compose: https://docs.docker.com/compose/ - -Setting up -^^^^^^^^^^ - -Docker encourages running one container for each process. This might mean one container for your web server, one for Django application and a third for your database. Once you're happy composing containers in this way you can easily add more, such as a `Redis`_ cache. - -.. _Redis: http://redis.io/ - -The Docker compose tool (previously known as `fig`_) makes linking these containers easy. An example set up for your Cookiecutter Django project might look like this: - -.. _fig: http://www.fig.sh/ - -:: - - webapp/ # Your cookiecutter project would be in here - Dockerfile - ... - database/ - Dockerfile - ... - webserver/ - Dockerfile - ... - docker-compose.yml - -Each component of your application would get its own `Dockerfile`_. The rest of this example assumes you are using the `base postgres image`_ for your database. Your database settings in `config/common.py` might then look something like: - -.. _Dockerfile: https://docs.docker.com/reference/builder/ -.. _base postgres image: https://registry.hub.docker.com/_/postgres/ - -.. code-block:: python - - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'postgres', - 'USER': 'postgres', - 'HOST': 'database', - 'PORT': 5432, - } - } - -The `Docker compose documentation`_ explains in detail what you can accomplish in the `docker-compose.yml` file, but an example configuration might look like this: - -.. _Docker compose documentation: https://docs.docker.com/compose/#compose-documentation - -.. code-block:: yaml - - database: - build: database - webapp: - build: webapp: - command: /usr/bin/python3.4 manage.py runserver 0.0.0.0:8000 # dev setting - # command: gunicorn -b 0.0.0.0:8000 wsgi:application # production setting - volumes: - - webapp/your_project_name:/path/to/container/workdir/ - links: - - database - webserver: - build: webserver - ports: - - "80:80" - - "443:443" - links: - - webapp - -We'll ignore the webserver for now (you'll want to comment that part out while we do). A working Dockerfile to run your cookiecutter application might look like this: - -:: - - FROM ubuntu:14.04 - ENV REFRESHED_AT 2015-01-13 - - # update packages and prepare to build software - RUN ["apt-get", "update"] - RUN ["apt-get", "-y", "install", "build-essential", "vim", "git", "curl"] - RUN ["locale-gen", "en_GB.UTF-8"] - - # install latest python - RUN ["apt-get", "-y", "build-dep", "python3-dev", "python3-imaging"] - RUN ["apt-get", "-y", "install", "python3-dev", "python3-imaging", "python3-pip"] - - # prepare postgreSQL support - RUN ["apt-get", "-y", "build-dep", "python3-psycopg2"] - - # move into our working directory - # ADD must be after chown see http://stackoverflow.com/a/26145444/1281947 - RUN ["groupadd", "python"] - RUN ["useradd", "python", "-s", "/bin/bash", "-m", "-g", "python", "-G", "python"] - ENV HOME /home/python - WORKDIR /home/python - RUN ["chown", "-R", "python:python", "/home/python"] - ADD ./ /home/python - - # manage requirements - ENV REQUIREMENTS_REFRESHED_AT 2015-02-25 - RUN ["pip3", "install", "-r", "requirements.txt"] - - # uncomment the line below to use container as a non-root user - USER python:python - -Running `sudo docker-compose build` will follow the instructions in your `docker-compose.yml` file and build the database container, then your webapp, before mounting your cookiecutter project files as a volume in the webapp container and linking to the database. Our example yaml file runs in development mode but changing it to production mode is as simple as commenting out the line using `runserver` and uncommenting the line using `gunicorn`. - -Both are set to run on port `0.0.0.0:8000`, which is where the Docker daemon will discover it. You can now run `sudo docker-compose up` and browse to `localhost:8000` to see your application running. - -Deployment -^^^^^^^^^^ - -You'll need a webserver container for deployment. An example setup for `Nginx`_ might look like this: - -.. _Nginx: http://wiki.nginx.org/Main - -:: - - FROM ubuntu:14.04 - ENV REFRESHED_AT 2015-02-11 - - # get the nginx package and set it up - RUN ["apt-get", "update"] - RUN ["apt-get", "-y", "install", "nginx"] - - # forward request and error logs to docker log collector - RUN ln -sf /dev/stdout /var/log/nginx/access.log - RUN ln -sf /dev/stderr /var/log/nginx/error.log - VOLUME ["/var/cache/nginx"] - EXPOSE 80 443 - - # load nginx conf - ADD ./site.conf /etc/nginx/sites-available/your_cookiecutter_project - RUN ["ln", "-s", "/etc/nginx/sites-available/your_cookiecutter_project", "/etc/nginx/sites-enabled/your_cookiecutter_project"] - RUN ["rm", "-rf", "/etc/nginx/sites-available/default"] - - #start the server - CMD ["nginx", "-g", "daemon off;"] - -That Dockerfile assumes you have an Nginx conf file named `site.conf` in the same directory as the webserver Dockerfile. A very basic example, which forwards traffic onto the development server or gunicorn for processing, would look like this: - -:: - - # see http://serverfault.com/questions/577370/how-can-i-use-environment-variables-in-nginx-conf#comment730384_577370 - upstream localhost { - server webapp_1:8000; - } - server { - location / { - proxy_pass http://localhost; - } - } - -Running `sudo docker-compose build webserver` will build your server container. Running `sudo docker-compose up` will now expose your application directly on `localhost` (no need to specify the port number). - -Building and running your app on EC2 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -All you now need to do to run your app in production is: - -* Create an empty EC2 Linux instance (any Linux machine should do). - -* Install your preferred source control solution, Docker and Docker compose on the news instance. - -* Pull in your code from source control. The root directory should be the one with your `docker-compose.yml` file in it. - -* Run `sudo docker-compose build` and `sudo docker-compose up`. - -* Assign an `Elastic IP address`_ to your new machine. - -.. _Elastic IP address: https://aws.amazon.com/articles/1346 - -* Point your domain name to the elastic IP. - -**Be careful with Elastic IPs** because, on the AWS free tier, if you assign one and then stop the machine you will incur charges while the machine is down (presumably because you're preventing them allocating the IP to someone else). - -Security advisory -^^^^^^^^^^^^^^^^^ - -The setup described in this instruction will get you up-and-running but it hasn't been audited for security. If you are running your own setup like this it is always advisable to, at a minimum, examine your application with a tool like `OWASP ZAP`_ to see what security holes you might be leaving open. - -.. _OWASP ZAP: https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project diff --git a/{{cookiecutter.project_slug}}/docs/howto.rst b/{{cookiecutter.project_slug}}/docs/howto.rst new file mode 100644 index 0000000000..7f2d26a1e1 --- /dev/null +++ b/{{cookiecutter.project_slug}}/docs/howto.rst @@ -0,0 +1,45 @@ +How To - Project Documentation +====================================================================== + +Get Started +---------------------------------------------------------------------- + +Documentation can be written as rst files in `{{cookiecutter.project_slug}}/docs`. + +{% if cookiecutter.use_docker == 'n' %} +To build and serve docs, use the command:: + + make livehtml + +from inside the `{{cookiecutter.project_slug}}/docs` directory. +{% else %} +To build and serve docs, use the commands:: + + docker-compose -f local.yml up docs + +{% endif %} + +Changes to files in `docs/_source` will be picked up and reloaded automatically. + +`Sphinx `_ is the tool used to build documentation. + +Docstrings to Documentation +---------------------------------------------------------------------- + +The sphinx extension `apidoc `_ is used to automatically document code using signatures and docstrings. + +Numpy or Google style docstrings will be picked up from project files and available for documentation. See the `Napoleon `_ extension for details. + +For an in-use example, see the `page source <_sources/users.rst.txt>`_ for :ref:`users`. + +To compile all docstrings automatically into documentation source files, use the command: + :: + + make apidocs + +{% if cookiecutter.use_docker == 'y' %} +This can be done in the docker container: + :: + + docker run --rm docs make apidocs +{% endif -%} diff --git a/{{cookiecutter.project_slug}}/docs/index.rst b/{{cookiecutter.project_slug}}/docs/index.rst index 21ef98ee5b..cb4cbaeda8 100644 --- a/{{cookiecutter.project_slug}}/docs/index.rst +++ b/{{cookiecutter.project_slug}}/docs/index.rst @@ -4,17 +4,15 @@ contain the root `toctree` directive. Welcome to {{ cookiecutter.project_name }}'s documentation! -==================================================================== - -Contents: +====================================================================== .. toctree:: :maxdepth: 2 + :caption: Contents: - install - deploy - docker_ec2 - tests + howto{% if cookiecutter.use_pycharm == 'y' %} + pycharm/configuration{% endif %} + users diff --git a/{{cookiecutter.project_slug}}/docs/install.rst b/{{cookiecutter.project_slug}}/docs/install.rst deleted file mode 100644 index 1bc03335d4..0000000000 --- a/{{cookiecutter.project_slug}}/docs/install.rst +++ /dev/null @@ -1,4 +0,0 @@ -Install -========= - -This is where you write how to get a new laptop to run this project. diff --git a/{{cookiecutter.project_slug}}/docs/make.bat b/{{cookiecutter.project_slug}}/docs/make.bat index ec43148b38..6cd1129f03 100644 --- a/{{cookiecutter.project_slug}}/docs/make.bat +++ b/{{cookiecutter.project_slug}}/docs/make.bat @@ -1,190 +1,46 @@ @ECHO OFF +pushd %~dp0 + REM Command file for Sphinx documentation + if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build + set SPHINXBUILD=sphinx-build -c . ) +set SOURCEDIR=_source set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) +set APP=..\{{cookiecutter.project_slug}} if "%1" == "" goto help -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\{{ cookiecutter.project_slug }}.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\{{ cookiecutter.project_slug }}.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. echo. - echo.Build finished. - goto end + echo.Install sphinx-autobuild for live serving. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 ) -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) +%SPHINXBUILD% -b %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) +:livehtml +sphinx-autobuild -b html --open-browser -p 9000 --watch %APP% -c . %SOURCEDIR% %BUILDDIR%/html +GOTO :EOF -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) +:apidocs +sphinx-apidoc -o %SOURCEDIR%/api %APP% +GOTO :EOF -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) +:help +%SPHINXBUILD% -b help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end +popd diff --git a/{{cookiecutter.project_slug}}/docs/pycharm/configuration.rst b/{{cookiecutter.project_slug}}/docs/pycharm/configuration.rst index 005d96c8fd..d8e769167f 100644 --- a/{{cookiecutter.project_slug}}/docs/pycharm/configuration.rst +++ b/{{cookiecutter.project_slug}}/docs/pycharm/configuration.rst @@ -14,25 +14,17 @@ This repository comes with already prepared "Run/Debug Configurations" for docke .. image:: images/2.png -But as you can see, at the beggining there is something wrong with them. They have red X on django icon, and they cannot be used, withot configuring remote python interpteter. To do that, you have to go to *Settings > Build, Execution, Deployment* first. +But as you can see, at the beginning there is something wrong with them. They have red X on django icon, and they cannot be used, without configuring remote python interpreter. To do that, you have to go to *Settings > Build, Execution, Deployment* first. Next, you have to add new remote python interpreter, based on already tested deployment settings. Go to *Settings > Project > Project Interpreter*. Click on the cog icon, and click *Add Remote*. .. image:: images/3.png -Switch to *Docker Compose* and select `dev.yml` file from directory of your project, next set *Service name* to `django` +Switch to *Docker Compose* and select `local.yml` file from directory of your project, next set *Service name* to `django` .. image:: images/4.png -Because Pycharm restarts container every time you use Configuration Run, to not have server restarted during running tests, we defined second service in `dev.yml` file called pycharm. To use it, you have to add interpreter of second service as well. - -.. image:: images/5.png - -The final result should be: - -.. image:: images/6.png - Having that, click *OK*. Close *Settings* panel, and wait few seconds... .. image:: images/7.png @@ -44,12 +36,18 @@ After few seconds, all *Run/Debug Configurations* should be ready to use. **Things you can do with provided configuration**: * run and debug python code + .. image:: images/f1.png + * run and debug tests + .. image:: images/f2.png .. image:: images/f3.png -* run and debug migrations or different django managment commands + +* run and debug migrations or different django management commands + .. image:: images/f4.png + * and many others.. Known issues diff --git a/{{cookiecutter.project_slug}}/docs/pycharm/images/5.png b/{{cookiecutter.project_slug}}/docs/pycharm/images/5.png deleted file mode 100644 index 39fac9593e..0000000000 Binary files a/{{cookiecutter.project_slug}}/docs/pycharm/images/5.png and /dev/null differ diff --git a/{{cookiecutter.project_slug}}/docs/pycharm/images/6.png b/{{cookiecutter.project_slug}}/docs/pycharm/images/6.png deleted file mode 100644 index 0cd70d2495..0000000000 Binary files a/{{cookiecutter.project_slug}}/docs/pycharm/images/6.png and /dev/null differ diff --git a/{{cookiecutter.project_slug}}/docs/users.rst b/{{cookiecutter.project_slug}}/docs/users.rst new file mode 100644 index 0000000000..6cf6455622 --- /dev/null +++ b/{{cookiecutter.project_slug}}/docs/users.rst @@ -0,0 +1,15 @@ + .. _users: + +Users +====================================================================== + +Starting a new project, it’s highly recommended to set up a custom user model, +even if the default User model is sufficient for you. + +This model behaves identically to the default user model, +but you’ll be able to customize it in the future if the need arises. + +.. automodule:: {{cookiecutter.project_slug}}.users.models + :members: + :noindex: + diff --git a/{{cookiecutter.project_slug}}/env.example b/{{cookiecutter.project_slug}}/env.example deleted file mode 100644 index bbea38b22a..0000000000 --- a/{{cookiecutter.project_slug}}/env.example +++ /dev/null @@ -1,26 +0,0 @@ -POSTGRES_PASSWORD=mysecretpass -POSTGRES_USER=postgresuser - -DJANGO_ADMIN_URL= -DJANGO_SETTINGS_MODULE=config.settings.production -DJANGO_SECRET_KEY=CHANGEME!!! -DJANGO_ALLOWED_HOSTS=.{{ cookiecutter.domain_name }} -DJANGO_AWS_ACCESS_KEY_ID= -DJANGO_AWS_SECRET_ACCESS_KEY= -DJANGO_AWS_STORAGE_BUCKET_NAME= -DJANGO_MAILGUN_API_KEY= -DJANGO_SERVER_EMAIL= -DJANGO_SECURE_SSL_REDIRECT=False -DJANGO_ACCOUNT_ALLOW_REGISTRATION=True -{% if cookiecutter.use_sentry == 'y' -%} -DJANGO_SENTRY_DSN= -{% endif %} -{% if cookiecutter.use_newrelic == 'y' -%} -NEW_RELIC_LICENSE_KEY= -NEW_RELIC_APP_NAME={{cookiecutter.project_slug}} -{% endif %} -{% if cookiecutter.use_opbeat == 'y' -%} -DJANGO_OPBEAT_ORGANIZATION_ID -DJANGO_OPBEAT_APP_ID -DJANGO_OPBEAT_SECRET_TOKEN -{% endif %} diff --git a/{{cookiecutter.project_slug}}/gulpfile.js b/{{cookiecutter.project_slug}}/gulpfile.js new file mode 100644 index 0000000000..680e3672ee --- /dev/null +++ b/{{cookiecutter.project_slug}}/gulpfile.js @@ -0,0 +1,187 @@ +//////////////////////////////// +// Setup +//////////////////////////////// + +// Gulp and package +const { src, dest, parallel, series, watch } = require('gulp') +const pjson = require('./package.json') + +// Plugins +const autoprefixer = require('autoprefixer') +const browserSync = require('browser-sync').create() +const concat = require('gulp-concat') +const cssnano = require ('cssnano') +const imagemin = require('gulp-imagemin') +const pixrem = require('pixrem') +const plumber = require('gulp-plumber') +const postcss = require('gulp-postcss') +const reload = browserSync.reload +const rename = require('gulp-rename') +const sass = require('gulp-sass')(require('sass')) +const spawn = require('child_process').spawn +const uglify = require('gulp-uglify-es').default + +// Relative paths function +function pathsConfig(appName) { + this.app = `./${pjson.name}` + const vendorsRoot = 'node_modules' + + return { + bootstrapSass: `${vendorsRoot}/bootstrap/scss`, + vendorsJs: [ + `${vendorsRoot}/@popperjs/core/dist/umd/popper.js`, + `${vendorsRoot}/bootstrap/dist/js/bootstrap.js`, + ], + app: this.app, + templates: `${this.app}/templates`, + css: `${this.app}/static/css`, + sass: `${this.app}/static/sass`, + fonts: `${this.app}/static/fonts`, + images: `${this.app}/static/images`, + js: `${this.app}/static/js`, + } +} + +const paths = pathsConfig() + +//////////////////////////////// +// Tasks +//////////////////////////////// + +// Styles autoprefixing and minification +function styles() { + const processCss = [ + autoprefixer(), // adds vendor prefixes + pixrem(), // add fallbacks for rem units + ] + + const minifyCss = [ + cssnano({ preset: 'default' }) // minify result + ] + + return src(`${paths.sass}/project.scss`) + .pipe(sass({ + includePaths: [ + paths.bootstrapSass, + paths.sass + ] + }).on('error', sass.logError)) + .pipe(plumber()) // Checks for errors + .pipe(postcss(processCss)) + .pipe(dest(paths.css)) + .pipe(rename({ suffix: '.min' })) + .pipe(postcss(minifyCss)) // Minifies the result + .pipe(dest(paths.css)) +} + +// Javascript minification +function scripts() { + return src(`${paths.js}/project.js`) + .pipe(plumber()) // Checks for errors + .pipe(uglify()) // Minifies the js + .pipe(rename({ suffix: '.min' })) + .pipe(dest(paths.js)) +} + +// Vendor Javascript minification +function vendorScripts() { + return src(paths.vendorsJs) + .pipe(concat('vendors.js')) + .pipe(dest(paths.js)) + .pipe(plumber()) // Checks for errors + .pipe(uglify()) // Minifies the js + .pipe(rename({ suffix: '.min' })) + .pipe(dest(paths.js)) +} + +// Image compression +function imgCompression() { + return src(`${paths.images}/*`) + .pipe(imagemin()) // Compresses PNG, JPEG, GIF and SVG images + .pipe(dest(paths.images)) +} + +{%- if cookiecutter.use_async == 'y' -%} +// Run django server +function asyncRunServer() { + const cmd = spawn('gunicorn', [ + 'config.asgi', '-k', 'uvicorn.workers.UvicornWorker', '--reload' + ], {stdio: 'inherit'} + ) + cmd.on('close', function(code) { + console.log('gunicorn exited with code ' + code) + }) +} +{%- else %} +// Run django server +function runServer(cb) { + const cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'}) + cmd.on('close', function(code) { + console.log('runServer exited with code ' + code) + cb(code) + }) +} +{%- endif %} + +// Browser sync server for live reload +function initBrowserSync() { + browserSync.init( + [ + `${paths.css}/*.css`, + `${paths.js}/*.js`, + `${paths.templates}/*.html` + ], { + {%- if cookiecutter.use_docker == 'y' %} + // https://www.browsersync.io/docs/options/#option-open + // Disable as it doesn't work from inside a container + open: false, + {%- endif %} + // https://www.browsersync.io/docs/options/#option-proxy + proxy: { + {%- if cookiecutter.use_docker == 'n' %} + target: '127.0.0.1:8000', + {%- else %} + target: 'django:8000', + {%- endif %} + proxyReq: [ + function(proxyReq, req) { + // Assign proxy "host" header same as current request at Browsersync server + proxyReq.setHeader('Host', req.headers.host) + } + ] + } + } + ) +} + +// Watch +function watchPaths() { + watch(`${paths.sass}/*.scss`{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}, styles) + watch(`${paths.templates}/**/*.html`{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}).on("change", reload) + watch([`${paths.js}/*.js`, `!${paths.js}/*.min.js`]{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}, scripts).on("change", reload) +} + +// Generate all assets +const generateAssets = parallel( + styles, + scripts, + vendorScripts, + imgCompression +) + +// Set up dev environment +const dev = parallel( + {%- if cookiecutter.use_docker == 'n' %} + {%- if cookiecutter.use_async == 'y' %} + asyncRunServer, + {%- else %} + runServer, + {%- endif %} + {%- endif %} + initBrowserSync, + watchPaths +) + +exports.default = series(generateAssets, dev) +exports["generate-assets"] = generateAssets +exports["dev"] = dev diff --git a/{{cookiecutter.project_slug}}/local.yml b/{{cookiecutter.project_slug}}/local.yml new file mode 100644 index 0000000000..fb203acd66 --- /dev/null +++ b/{{cookiecutter.project_slug}}/local.yml @@ -0,0 +1,130 @@ +version: '3' + +volumes: + {{ cookiecutter.project_slug }}_local_postgres_data: {} + {{ cookiecutter.project_slug }}_local_postgres_data_backups: {} + +services: + django:{% if cookiecutter.use_celery == 'y' %} &django{% endif %} + build: + context: . + dockerfile: ./compose/local/django/Dockerfile + image: {{ cookiecutter.project_slug }}_local_django + container_name: {{ cookiecutter.project_slug }}_local_django + platform: linux/x86_64 + depends_on: + - postgres + {%- if cookiecutter.use_celery == 'y' %} + - redis + {%- endif %} + {%- if cookiecutter.use_mailhog == 'y' %} + - mailhog + {%- endif %} + volumes: + - .:/app:z + env_file: + - ./.envs/.local/.django + - ./.envs/.local/.postgres + ports: + - "8000:8000" + command: /start + + postgres: + build: + context: . + dockerfile: ./compose/production/postgres/Dockerfile + image: {{ cookiecutter.project_slug }}_production_postgres + container_name: {{ cookiecutter.project_slug }}_local_postgres + volumes: + - {{ cookiecutter.project_slug }}_local_postgres_data:/var/lib/postgresql/data:Z + - {{ cookiecutter.project_slug }}_local_postgres_data_backups:/backups:z + env_file: + - ./.envs/.local/.postgres + + docs: + image: {{ cookiecutter.project_slug }}_local_docs + container_name: {{ cookiecutter.project_slug }}_local_docs + platform: linux/x86_64 + build: + context: . + dockerfile: ./compose/local/docs/Dockerfile + env_file: + - ./.envs/.local/.django + volumes: + - ./docs:/docs:z + - ./config:/app/config:z + - ./{{ cookiecutter.project_slug }}:/app/{{ cookiecutter.project_slug }}:z + ports: + - "9000:9000" + command: /start-docs + {%- if cookiecutter.use_mailhog == 'y' %} + + mailhog: + image: mailhog/mailhog:v1.0.0 + container_name: {{ cookiecutter.project_slug }}_local_mailhog + ports: + - "8025:8025" + + {%- endif %} + {%- if cookiecutter.use_celery == 'y' %} + + redis: + image: redis:6 + container_name: {{ cookiecutter.project_slug }}_local_redis + + celeryworker: + <<: *django + image: {{ cookiecutter.project_slug }}_local_celeryworker + container_name: {{ cookiecutter.project_slug }}_local_celeryworker + depends_on: + - redis + - postgres + {%- if cookiecutter.use_mailhog == 'y' %} + - mailhog + {%- endif %} + ports: [] + command: /start-celeryworker + + celerybeat: + <<: *django + image: {{ cookiecutter.project_slug }}_local_celerybeat + container_name: {{ cookiecutter.project_slug }}_local_celerybeat + depends_on: + - redis + - postgres + {%- if cookiecutter.use_mailhog == 'y' %} + - mailhog + {%- endif %} + ports: [] + command: /start-celerybeat + + flower: + <<: *django + image: {{ cookiecutter.project_slug }}_local_flower + container_name: {{ cookiecutter.project_slug }}_local_flower + ports: + - "5555:5555" + command: /start-flower + + {%- endif %} + {%- if cookiecutter.frontend_pipeline == 'Gulp' %} + + node: + build: + context: . + dockerfile: ./compose/local/node/Dockerfile + image: {{ cookiecutter.project_slug }}_local_node + container_name: {{ cookiecutter.project_slug }}_local_node + depends_on: + - django + volumes: + - .:/app:z + # http://jdlm.info/articles/2016/03/06/lessons-building-node-app-docker.html + - /app/node_modules + command: npm run dev + ports: + - "3000:3000" + # Expose browsersync UI: https://www.browsersync.io/docs/options/#option-ui + - "3001:3001" + + {%- endif %} diff --git a/{{cookiecutter.project_slug}}/locale/README.rst b/{{cookiecutter.project_slug}}/locale/README.rst new file mode 100644 index 0000000000..c2f1dcd6f9 --- /dev/null +++ b/{{cookiecutter.project_slug}}/locale/README.rst @@ -0,0 +1,6 @@ +Translations +============ + +Translations will be placed in this folder when running:: + + python manage.py makemessages diff --git a/{{cookiecutter.project_slug}}/manage.py b/{{cookiecutter.project_slug}}/manage.py index b41522c7d6..c44cc826d6 100755 --- a/{{cookiecutter.project_slug}}/manage.py +++ b/{{cookiecutter.project_slug}}/manage.py @@ -1,10 +1,31 @@ #!/usr/bin/env python import os import sys +from pathlib import Path -if __name__ == '__main__': - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local') +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") - from django.core.management import execute_from_command_line + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django # noqa + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + + raise + + # This allows easy placement of apps within the interior + # {{ cookiecutter.project_slug }} directory. + current_path = Path(__file__).parent.resolve() + sys.path.append(str(current_path / "{{ cookiecutter.project_slug }}")) execute_from_command_line(sys.argv) diff --git a/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py new file mode 100644 index 0000000000..d702a5f67f --- /dev/null +++ b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py @@ -0,0 +1,67 @@ +import os +from collections.abc import Sequence +from pathlib import Path + +import pytest + +ROOT_DIR_PATH = Path(__file__).parent.resolve() +PRODUCTION_DOTENVS_DIR_PATH = ROOT_DIR_PATH / ".envs" / ".production" +PRODUCTION_DOTENV_FILE_PATHS = [ + PRODUCTION_DOTENVS_DIR_PATH / ".django", + PRODUCTION_DOTENVS_DIR_PATH / ".postgres", +] +DOTENV_FILE_PATH = ROOT_DIR_PATH / ".env" + + +def merge( + output_file_path: str, merged_file_paths: Sequence[str], append_linesep: bool = True +) -> None: + with open(output_file_path, "w") as output_file: + for merged_file_path in merged_file_paths: + with open(merged_file_path) as merged_file: + merged_file_content = merged_file.read() + output_file.write(merged_file_content) + if append_linesep: + output_file.write(os.linesep) + + +def main(): + merge(DOTENV_FILE_PATH, PRODUCTION_DOTENV_FILE_PATHS) + + +@pytest.mark.parametrize("merged_file_count", range(3)) +@pytest.mark.parametrize("append_linesep", [True, False]) +def test_merge(tmpdir_factory, merged_file_count: int, append_linesep: bool): + tmp_dir_path = Path(str(tmpdir_factory.getbasetemp())) + + output_file_path = tmp_dir_path / ".env" + + expected_output_file_content = "" + merged_file_paths = [] + for i in range(merged_file_count): + merged_file_ord = i + 1 + + merged_filename = f".service{merged_file_ord}" + merged_file_path = tmp_dir_path / merged_filename + + merged_file_content = merged_filename * merged_file_ord + + with open(merged_file_path, "w+") as file: + file.write(merged_file_content) + + expected_output_file_content += merged_file_content + if append_linesep: + expected_output_file_content += os.linesep + + merged_file_paths.append(merged_file_path) + + merge(output_file_path, merged_file_paths, append_linesep) + + with open(output_file_path) as output_file: + actual_output_file_content = output_file.read() + + assert actual_output_file_content == expected_output_file_content + + +if __name__ == "__main__": + main() diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/package.json index 47cf69441b..bff0a34af8 100644 --- a/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_slug}}/package.json @@ -3,19 +3,31 @@ "version": "{{ cookiecutter.version }}", "dependencies": {}, "devDependencies": { - "grunt": "~0.4.5", - "grunt-contrib-watch": "~0.6.1", - "grunt-bg-shell": "~2.3.1", - "connect-livereload": "~0.3.2", - "time-grunt": "~1.2.1", - "load-grunt-tasks": "~3.2.0", - "grunt-sass": "~1.0.0", - "grunt-postcss": "~0.5.5", - "cssnano": "~2.1.0", - "autoprefixer-core": "~5.2.1", - "pixrem": "~1.3.1" + "bootstrap": "^5.1.3", + "gulp-concat": "^2.6.1", + "@popperjs/core": "^2.10.2", + "autoprefixer": "^10.4.0", + "browser-sync": "^2.27.7", + "cssnano": "^5.0.11", + "gulp": "^4.0.2", + "gulp-imagemin": "^7.1.0", + "gulp-plumber": "^1.2.1", + "gulp-postcss": "^9.0.1", + "gulp-rename": "^2.0.0", + "gulp-sass": "^5.0.0", + "gulp-uglify-es": "^3.0.0", + "pixrem": "^5.0.0", + "postcss": "^8.3.11", + "sass": "^1.43.4" }, "engines": { - "node": ">=0.8.0" + "node": "16" + }, + "browserslist": [ + "last 2 versions" + ], + "scripts": { + "dev": "gulp", + "build": "gulp generate-assets" } } diff --git a/{{cookiecutter.project_slug}}/production.yml b/{{cookiecutter.project_slug}}/production.yml new file mode 100644 index 0000000000..4c1d64c20e --- /dev/null +++ b/{{cookiecutter.project_slug}}/production.yml @@ -0,0 +1,79 @@ +version: '3' + +volumes: + production_postgres_data: {} + production_postgres_data_backups: {} + production_traefik: {} + +services: + django:{% if cookiecutter.use_celery == 'y' %} &django{% endif %} + build: + context: . + dockerfile: ./compose/production/django/Dockerfile + image: {{ cookiecutter.project_slug }}_production_django + platform: linux/x86_64 + depends_on: + - postgres + - redis + env_file: + - ./.envs/.production/.django + - ./.envs/.production/.postgres + command: /start + + postgres: + build: + context: . + dockerfile: ./compose/production/postgres/Dockerfile + image: {{ cookiecutter.project_slug }}_production_postgres + volumes: + - production_postgres_data:/var/lib/postgresql/data:Z + - production_postgres_data_backups:/backups:z + env_file: + - ./.envs/.production/.postgres + + traefik: + build: + context: . + dockerfile: ./compose/production/traefik/Dockerfile + image: {{ cookiecutter.project_slug }}_production_traefik + depends_on: + - django + volumes: + - production_traefik:/etc/traefik/acme:z + ports: + - "0.0.0.0:80:80" + - "0.0.0.0:443:443" + {%- if cookiecutter.use_celery == 'y' %} + - "0.0.0.0:5555:5555" + {%- endif %} + + redis: + image: redis:6 + {%- if cookiecutter.use_celery == 'y' %} + + celeryworker: + <<: *django + image: {{ cookiecutter.project_slug }}_production_celeryworker + command: /start-celeryworker + + celerybeat: + <<: *django + image: {{ cookiecutter.project_slug }}_production_celerybeat + command: /start-celerybeat + + flower: + <<: *django + image: {{ cookiecutter.project_slug }}_production_flower + command: /start-flower + {%- endif %} + {%- if cookiecutter.cloud_provider == 'AWS' %} + + awscli: + build: + context: . + dockerfile: ./compose/production/aws/Dockerfile + env_file: + - ./.envs/.production/.django + volumes: + - production_postgres_data_backups:/backups:z + {%- endif %} diff --git a/{{cookiecutter.project_slug}}/pytest.ini b/{{cookiecutter.project_slug}}/pytest.ini index d19d28c542..969c7921ee 100644 --- a/{{cookiecutter.project_slug}}/pytest.ini +++ b/{{cookiecutter.project_slug}}/pytest.ini @@ -1,2 +1,6 @@ [pytest] -DJANGO_SETTINGS_MODULE=config.settings.local +addopts = --ds=config.settings.test --reuse-db +python_files = tests.py test_*.py +{%- if cookiecutter.frontend_pipeline == 'Gulp' %} +norecursedirs = node_modules +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/requirements.txt b/{{cookiecutter.project_slug}}/requirements.txt index d1197135ee..c1b500c2b4 100644 --- a/{{cookiecutter.project_slug}}/requirements.txt +++ b/{{cookiecutter.project_slug}}/requirements.txt @@ -1,3 +1,3 @@ -# This file is here because many Platforms as a Service look for -# requirements.txt in the root directory of a project. +# This file is expected by Heroku. + -r requirements/production.txt diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt index f5e35acd6f..af9c4b52e9 100644 --- a/{{cookiecutter.project_slug}}/requirements/base.txt +++ b/{{cookiecutter.project_slug}}/requirements/base.txt @@ -1,58 +1,48 @@ -{% if cookiecutter.use_python2 == 'n' -%} -# Wheel 0.25+ needed to install certain packages on CPython 3.5+ -# like Pillow and psycopg2 -# See http://bitly.com/wheel-building-fails-CPython-35 -# Verified bug on Python 3.5.1 -wheel==0.29.0 +pytz==2022.6 # https://github.com/stub42/pytz +python-slugify==6.1.2 # https://github.com/un33k/python-slugify +Pillow==9.3.0 # https://github.com/python-pillow/Pillow +{%- if cookiecutter.frontend_pipeline == 'Django Compressor' %} +{%- if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %} +rcssmin==1.1.0 --install-option="--without-c-extensions" # https://github.com/ndparker/rcssmin +{%- else %} +rcssmin==1.1.0 # https://github.com/ndparker/rcssmin {%- endif %} - -# Bleeding edge Django -django==1.9.6 - -# Configuration -django-environ==0.4.0 -django-secure==1.0.1 -{% if cookiecutter.use_whitenoise == 'y' -%} -whitenoise==3.0 {%- endif %} - - -# Forms -django-braces==1.8.1 -django-crispy-forms==1.6.0 -django-floppyforms==1.6.2 - -# Models -django-model-utils==2.5 - -# Images -Pillow==3.2.0 - -# For user registration, either via email or social -# Well-built with regular release cycles! -django-allauth==0.25.2 - -{% if cookiecutter.windows == 'y' -%} -# On Windows, you must download/install psycopg2 manually -# from http://www.lfd.uci.edu/~gohlke/pythonlibs/#psycopg -{% else %} -# Python-PostgreSQL Database Adapter -psycopg2==2.6.1 +argon2-cffi==21.3.0 # https://github.com/hynek/argon2_cffi +{%- if cookiecutter.use_whitenoise == 'y' %} +whitenoise==6.2.0 # https://github.com/evansd/whitenoise +{%- endif %} +redis==4.3.4 # https://github.com/redis/redis-py +{%- if cookiecutter.use_docker == "y" or cookiecutter.windows == "n" %} +hiredis==2.0.0 # https://github.com/redis/hiredis-py +{%- endif %} +{%- if cookiecutter.use_celery == "y" %} +celery==5.2.7 # pyup: < 6.0 # https://github.com/celery/celery +django-celery-beat==2.4.0 # https://github.com/celery/django-celery-beat +{%- if cookiecutter.use_docker == 'y' %} +flower==1.2.0 # https://github.com/mher/flower +{%- endif %} +{%- endif %} +{%- if cookiecutter.use_async == 'y' %} +uvicorn[standard]==0.19.0 # https://github.com/encode/uvicorn {%- endif %} -# Unicode slugification -unicode-slugify==0.1.3 -django-autoslug==1.9.3 - -# Time zones support -pytz==2016.4 - -# Redis support -django-redis==4.4.2 -redis>=2.10.0 - -{% if cookiecutter.use_celery == "y" %} -celery==3.1.23 -{% endif %} - -# Your custom requirements go here +# Django +# ------------------------------------------------------------------------------ +django==4.0.8 # pyup: < 4.1 # https://www.djangoproject.com/ +django-environ==0.9.0 # https://github.com/joke2k/django-environ +django-model-utils==4.2.0 # https://github.com/jazzband/django-model-utils +django-allauth==0.51.0 # https://github.com/pennersr/django-allauth +django-crispy-forms==1.14.0 # https://github.com/django-crispy-forms/django-crispy-forms +crispy-bootstrap5==0.7 # https://github.com/django-crispy-forms/crispy-bootstrap5 +{%- if cookiecutter.frontend_pipeline == 'Django Compressor' %} +django-compressor==4.1 # https://github.com/django-compressor/django-compressor +{%- endif %} +django-redis==5.2.0 # https://github.com/jazzband/django-redis +{%- if cookiecutter.use_drf == 'y' %} +# Django REST Framework +djangorestframework==3.14.0 # https://github.com/encode/django-rest-framework +django-cors-headers==3.13.0 # https://github.com/adamchainz/django-cors-headers +# DRF-spectacular for api documentation +drf-spectacular==0.24.2 # https://github.com/tfranzel/drf-spectacular +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index c88093fef4..130cb24cc9 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -1,16 +1,48 @@ -# Local development dependencies go here -r base.txt -coverage==4.0.3 -django_coverage_plugin==1.3 -Sphinx -django-extensions==1.6.7 -Werkzeug==0.11.9 -django-test-plus==1.0.12 -factory_boy==2.7.0 -django-debug-toolbar==1.4 -# improved REPL -ipdb==0.10.0 +Werkzeug[watchdog]==2.2.2 # https://github.com/pallets/werkzeug +ipdb==0.13.9 # https://github.com/gotcha/ipdb +{%- if cookiecutter.use_docker == 'y' %} +psycopg2==2.9.5 # https://github.com/psycopg/psycopg2 +{%- else %} +psycopg2-binary==2.9.5 # https://github.com/psycopg/psycopg2 +{%- endif %} +{%- if cookiecutter.use_async == 'y' or cookiecutter.use_celery == 'y' %} +watchfiles==0.18.1 # https://github.com/samuelcolvin/watchfiles +{%- endif %} -pytest-django==2.9.1 -pytest-sugar==0.7.1 +# Testing +# ------------------------------------------------------------------------------ +mypy==0.982 # https://github.com/python/mypy +django-stubs==1.12.0 # https://github.com/typeddjango/django-stubs +pytest==7.2.0 # https://github.com/pytest-dev/pytest +pytest-sugar==0.9.6 # https://github.com/Frozenball/pytest-sugar +{%- if cookiecutter.use_drf == "y" %} +djangorestframework-stubs==1.7.0 # https://github.com/typeddjango/djangorestframework-stubs +{%- endif %} + +# Documentation +# ------------------------------------------------------------------------------ +sphinx==5.3.0 # https://github.com/sphinx-doc/sphinx +sphinx-autobuild==2021.3.14 # https://github.com/GaretJax/sphinx-autobuild + +# Code quality +# ------------------------------------------------------------------------------ +flake8==5.0.4 # https://github.com/PyCQA/flake8 +flake8-isort==5.0.0 # https://github.com/gforcada/flake8-isort +coverage==6.5.0 # https://github.com/nedbat/coveragepy +black==22.10.0 # https://github.com/psf/black +pylint-django==2.5.3 # https://github.com/PyCQA/pylint-django +{%- if cookiecutter.use_celery == 'y' %} +pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery +{%- endif %} +pre-commit==2.20.0 # https://github.com/pre-commit/pre-commit + +# Django +# ------------------------------------------------------------------------------ +factory-boy==3.2.1 # https://github.com/FactoryBoy/factory_boy + +django-debug-toolbar==3.7.0 # https://github.com/jazzband/django-debug-toolbar +django-extensions==3.2.1 # https://github.com/django-extensions/django-extensions +django-coverage-plugin==2.0.4 # https://github.com/nedbat/django_coverage_plugin +pytest-django==4.5.2 # https://github.com/pytest-dev/pytest-django diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt index 5ac46e62dc..e2c2046d2b 100644 --- a/{{cookiecutter.project_slug}}/requirements/production.txt +++ b/{{cookiecutter.project_slug}}/requirements/production.txt @@ -1,45 +1,42 @@ -# Pro-tip: Try not to put anything here. Avoid dependencies in -# production that aren't in development. +# PRECAUTION: avoid production dependencies that aren't in development + -r base.txt -{% if cookiecutter.windows == 'y' -%} -# Python-PostgreSQL Database Adapter -# If using Win for dev, this assumes Unix in prod -# ------------------------------------------------ -psycopg2==2.6.1 +gunicorn==20.1.0 # https://github.com/benoitc/gunicorn +psycopg2==2.9.5 # https://github.com/psycopg/psycopg2 +{%- if cookiecutter.use_whitenoise == 'n' %} +Collectfast==2.2.0 # https://github.com/antonagestam/collectfast {%- endif %} - -# WSGI Handler -# ------------------------------------------------ -gevent==1.1.1 -gunicorn==19.5.0 - -# Static and Media Storage -# ------------------------------------------------ -boto==2.40.0 -django-storages-redux==1.3.2 -{% if cookiecutter.use_whitenoise != 'y' -%} -Collectfast==0.2.3 +{%- if cookiecutter.use_sentry == "y" %} +sentry-sdk==1.10.1 # https://github.com/getsentry/sentry-python {%- endif %} - -# Email backends for Mailgun, Postmark, SendGrid and more -# ------------------------------------------------------- -django-anymail==0.3.1 - -{% if cookiecutter.use_sentry == "y" -%} -# Raven is the Sentry client -# -------------------------- -raven +{%- if cookiecutter.use_docker == "n" and cookiecutter.windows == "y" %} +hiredis==2.0.0 # https://github.com/redis/hiredis-py {%- endif %} -{% if cookiecutter.use_newrelic == "y" -%} -# Newrelic agent for performance monitoring -# ----------------------------------------- -newrelic +# Django +# ------------------------------------------------------------------------------ +{%- if cookiecutter.cloud_provider == 'AWS' %} +django-storages[boto3]==1.13.1 # https://github.com/jschneier/django-storages +{%- elif cookiecutter.cloud_provider == 'GCP' %} +django-storages[google]==1.13.1 # https://github.com/jschneier/django-storages {%- endif %} - -{% if cookiecutter.use_opbeat == "y" -%} -# Opbeat agent for performance monitoring -# ----------------------------------------- -opbeat +{%- if cookiecutter.mail_service == 'Mailgun' %} +django-anymail[mailgun]==8.6 # https://github.com/anymail/django-anymail +{%- elif cookiecutter.mail_service == 'Amazon SES' %} +django-anymail[amazon_ses]==8.6 # https://github.com/anymail/django-anymail +{%- elif cookiecutter.mail_service == 'Mailjet' %} +django-anymail[mailjet]==8.6 # https://github.com/anymail/django-anymail +{%- elif cookiecutter.mail_service == 'Mandrill' %} +django-anymail[mandrill]==8.6 # https://github.com/anymail/django-anymail +{%- elif cookiecutter.mail_service == 'Postmark' %} +django-anymail[postmark]==8.6 # https://github.com/anymail/django-anymail +{%- elif cookiecutter.mail_service == 'Sendgrid' %} +django-anymail[sendgrid]==8.6 # https://github.com/anymail/django-anymail +{%- elif cookiecutter.mail_service == 'SendinBlue' %} +django-anymail[sendinblue]==8.6 # https://github.com/anymail/django-anymail +{%- elif cookiecutter.mail_service == 'SparkPost' %} +django-anymail[sparkpost]==8.6 # https://github.com/anymail/django-anymail +{%- elif cookiecutter.mail_service == 'Other SMTP' %} +django-anymail==8.6 # https://github.com/anymail/django-anymail {%- endif %} diff --git a/{{cookiecutter.project_slug}}/requirements/test.txt b/{{cookiecutter.project_slug}}/requirements/test.txt deleted file mode 100644 index 0451bc60f8..0000000000 --- a/{{cookiecutter.project_slug}}/requirements/test.txt +++ /dev/null @@ -1,18 +0,0 @@ -# Test dependencies go here. --r base.txt - -{% if cookiecutter.windows == 'y' -%} -# Python-PostgreSQL Database Adapter -# If using Win for dev, this assumes Unix in test/prod -psycopg2==2.6.1 -{%- endif %} - -coverage==4.0.3 -django_coverage_plugin==1.3 -flake8==2.5.4 -django-test-plus==1.0.12 -factory_boy==2.7.0 - -# pytest -pytest-django==2.9.1 -pytest-sugar==0.7.1 diff --git a/{{cookiecutter.project_slug}}/runtime.txt b/{{cookiecutter.project_slug}}/runtime.txt index 84c90354ca..69b0ccfc82 100644 --- a/{{cookiecutter.project_slug}}/runtime.txt +++ b/{{cookiecutter.project_slug}}/runtime.txt @@ -1,5 +1 @@ -{% if cookiecutter.use_python2 == 'n' -%} -python-3.5.1 -{% else %} -python-2.7.10 -{%- endif %} +python-3.10.8 diff --git a/{{cookiecutter.project_slug}}/setup.cfg b/{{cookiecutter.project_slug}}/setup.cfg index c18b80d95b..3bec1fbeea 100644 --- a/{{cookiecutter.project_slug}}/setup.cfg +++ b/{{cookiecutter.project_slug}}/setup.cfg @@ -1,7 +1,40 @@ [flake8] max-line-length = 120 -exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules +exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv -[pep8] +[pycodestyle] max-line-length = 120 -exclude=.tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules +exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv + +[isort] +line_length = 88 +known_first_party = {{cookiecutter.project_slug}},config +multi_line_output = 3 +default_section = THIRDPARTY +skip = venv/ +skip_glob = **/migrations/*.py +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true + +[mypy] +python_version = 3.10 +check_untyped_defs = True +ignore_missing_imports = True +warn_unused_ignores = True +warn_redundant_casts = True +warn_unused_configs = True +plugins = mypy_django_plugin.main{% if cookiecutter.use_drf == "y" %}, mypy_drf_plugin.main{% endif %} + +[mypy.plugins.django-stubs] +django_settings_module = config.settings.test + +[mypy-*.migrations.*] +# Django migrations should not produce any errors: +ignore_errors = True + +[coverage:run] +include = {{cookiecutter.project_slug}}/* +omit = *migrations*, *tests* +plugins = + django_coverage_plugin diff --git a/{{cookiecutter.project_slug}}/utility/install_os_dependencies.sh b/{{cookiecutter.project_slug}}/utility/install_os_dependencies.sh index cc1fb2f07a..ec9372fd8d 100755 --- a/{{cookiecutter.project_slug}}/utility/install_os_dependencies.sh +++ b/{{cookiecutter.project_slug}}/utility/install_os_dependencies.sh @@ -1,61 +1,72 @@ #!/bin/bash WORK_DIR="$(dirname "$0")" -OS_REQUIREMENTS_FILENAME="$WORK_DIR/requirements.apt" - -VER=$(lsb_release -sr) -if [ "$VER" == "16.04" ]; then - OS_REQUIREMENTS_FILENAME="requirements.apt.xenial" -else - OS_REQUIREMENTS_FILENAME="requirements.apt" +DISTRO_NAME=$(lsb_release -sc) +OS_REQUIREMENTS_FILENAME="requirements-$DISTRO_NAME.apt" + +cd $WORK_DIR + +# Check if a requirements file exist for the current distribution. +if [ ! -r "$OS_REQUIREMENTS_FILENAME" ]; then + cat <<-EOF >&2 + There is no requirements file for your distribution. + You can see one of the files listed below to help search the equivalent package in your system: + $(find ./ -name "requirements-*.apt" -printf " - %f\n") + EOF + exit 1; fi + # Handle call with wrong command function wrong_command() { - echo "${0##*/} - unknown command: '${1}'" - usage_message + echo "${0##*/} - unknown command: '${1}'" >&2 + usage_message } # Print help / script usage function usage_message() { - echo "usage: ./${0##*/} " - echo "available commands are:" - echo -e "\tlist\t\tPrint a list of all packages defined on ${OS_REQUIREMENTS_FILENAME} file" - echo -e "\thelp\t\tPrint this help" - echo -e "\n\tCommands that require superuser permission:" - echo -e "\tinstall\t\tInstall packages defined on ${OS_REQUIREMENTS_FILENAME} file. Note: This\n\t\t\t does not upgrade the packages already installed for new\n\t\t\t versions, even if new version is available in the repository." - echo -e "\tupgrade\t\tSame that install, but upgrate the already installed packages,\n\t\t\t if new version is available." - + cat <<-EOF + Usage: $WORK_DIR/${0##*/} + Available commands are: + list Print a list of all packages defined on ${OS_REQUIREMENTS_FILENAME} file + help Print this help + + Commands that require superuser permission: + install Install packages defined on ${OS_REQUIREMENTS_FILENAME} file. Note: This + does not upgrade the packages already installed for new versions, even if + new version is available in the repository. + upgrade Same that install, but upgrade the already installed packages, if new + version is available. + EOF } # Read the requirements.apt file, and remove comments and blank lines function list_packages(){ - grep -v "#" ${OS_REQUIREMENTS_FILENAME} | grep -v "^$"; + grep -v "#" "${OS_REQUIREMENTS_FILENAME}" | grep -v "^$"; } -function install() +function install_packages() { list_packages | xargs apt-get --no-upgrade install -y; } -function upgrade() +function upgrade_packages() { list_packages | xargs apt-get install -y; } - function install_or_upgrade() { P=${1} PARAN=${P:-"install"} if [[ $EUID -ne 0 ]]; then - echo -e "\nYou must run this with root privilege" 2>&1 - echo -e "Please do:\n" 2>&1 - echo "sudo ./${0##*/} $PARAN" 2>&1 - echo -e "\n" 2>&1 - + cat <<-EOF >&2 + You must run this script with root privilege + Please do: + sudo $WORK_DIR/${0##*/} $PARAN + EOF exit 1 else @@ -63,9 +74,9 @@ function install_or_upgrade() # Install the basic compilation dependencies and other required libraries of this project if [ "$PARAN" == "install" ]; then - install; + install_packages; else - upgrade; + upgrade_packages; fi # cleaning downloaded packages from apt-get cache @@ -73,16 +84,13 @@ function install_or_upgrade() exit 0 fi - - } - # Handle command argument case "$1" in install) install_or_upgrade;; upgrade) install_or_upgrade "upgrade";; list) list_packages;; - help) usage_message;; - *) wrong_command $1;; + help|"") usage_message;; + *) wrong_command "$1";; esac diff --git a/{{cookiecutter.project_slug}}/utility/install_python_dependencies.sh b/{{cookiecutter.project_slug}}/utility/install_python_dependencies.sh index d23fcf9e98..e09ebf6f85 100755 --- a/{{cookiecutter.project_slug}}/utility/install_python_dependencies.sh +++ b/{{cookiecutter.project_slug}}/utility/install_python_dependencies.sh @@ -6,11 +6,7 @@ PROJECT_DIR="$(dirname "$WORK_DIR")" pip --version >/dev/null 2>&1 || { echo >&2 -e "\npip is required but it's not installed." echo >&2 -e "You can install it by running the following command:\n" -{% if cookiecutter.use_python2 == 'n' -%} 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" -{% else %} - echo >&2 "wget https://bootstrap.pypa.io/get-pip.py --output-document=get-pip.py; chmod +x get-pip.py; sudo -H python2 get-pip.py" -{%- endif %} echo >&2 -e "\n" echo >&2 -e "\nFor more information, see pip documentation: https://pip.pypa.io/en/latest/" exit 1; @@ -19,11 +15,7 @@ pip --version >/dev/null 2>&1 || { virtualenv --version >/dev/null 2>&1 || { echo >&2 -e "\nvirtualenv is required but it's not installed." echo >&2 -e "You can install it by running the following command:\n" -{% if cookiecutter.use_python2 == 'n' -%} echo >&2 "sudo -H pip3 install virtualenv" -{% else %} - echo >&2 "sudo -H pip2 install virtualenv" -{%- endif %} echo >&2 -e "\n" echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/" exit 1; @@ -32,11 +24,7 @@ virtualenv --version >/dev/null 2>&1 || { if [ -z "$VIRTUAL_ENV" ]; then echo >&2 -e "\nYou need activate a virtualenv first" 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' -{% if cookiecutter.use_python2 == 'n' -%} echo >&2 -e "virtualenv venv --python=\`which python3\`" -{% else %} - echo >&2 -e "virtualenv venv --python=\`which python2\`" -{%- endif %} echo >&2 -e "\nTo leave/disable the currently active virtualenv, run the following command:\n" echo >&2 "deactivate" echo >&2 -e "\nTo activate the virtualenv again, run the following command:\n" @@ -45,11 +33,8 @@ if [ -z "$VIRTUAL_ENV" ]; then echo >&2 -e "\n" exit 1; else - pip install -r $PROJECT_DIR/requirements/local.txt - pip install -r $PROJECT_DIR/requirements/test.txt - {% if cookiecutter.use_heroku == "y" -%} + {%- if cookiecutter.use_heroku == "y" -%} pip install -r $PROJECT_DIR/requirements.txt {%- endif %} fi - diff --git a/{{cookiecutter.project_slug}}/utility/requirements-bionic.apt b/{{cookiecutter.project_slug}}/utility/requirements-bionic.apt new file mode 100644 index 0000000000..1ca82b2640 --- /dev/null +++ b/{{cookiecutter.project_slug}}/utility/requirements-bionic.apt @@ -0,0 +1,23 @@ +##basic build dependencies of various Django apps for Ubuntu Bionic 18.04 +#build-essential metapackage install: make, gcc, g++, +build-essential +#required to translate +gettext +python3-dev + +##shared dependencies of: +##Pillow, pylibmc +zlib1g-dev + +##Postgresql and psycopg2 dependencies +libpq-dev + +##Pillow dependencies +libtiff5-dev +libjpeg8-dev +libfreetype6-dev +liblcms2-dev +libwebp-dev + +##django-extensions +libgraphviz-dev diff --git a/{{cookiecutter.project_slug}}/utility/requirements-bullseye.apt b/{{cookiecutter.project_slug}}/utility/requirements-bullseye.apt new file mode 100644 index 0000000000..60f6028733 --- /dev/null +++ b/{{cookiecutter.project_slug}}/utility/requirements-bullseye.apt @@ -0,0 +1,23 @@ +##basic build dependencies of various Django apps for Debian Bullseye 11.x +#build-essential metapackage install: make, gcc, g++, +build-essential +#required to translate +gettext +python3-dev + +##shared dependencies of: +##Pillow, pylibmc +zlib1g-dev + +##Postgresql and psycopg2 dependencies +libpq-dev + +##Pillow dependencies +libtiff5-dev +libjpeg62-turbo-dev +libfreetype6-dev +liblcms2-dev +libwebp-dev + +##django-extensions +libgraphviz-dev diff --git a/{{cookiecutter.project_slug}}/utility/requirements-buster.apt b/{{cookiecutter.project_slug}}/utility/requirements-buster.apt new file mode 100644 index 0000000000..75957f40d4 --- /dev/null +++ b/{{cookiecutter.project_slug}}/utility/requirements-buster.apt @@ -0,0 +1,23 @@ +##basic build dependencies of various Django apps for Debian Jessie 10.x +#build-essential metapackage install: make, gcc, g++, +build-essential +#required to translate +gettext +python3-dev + +##shared dependencies of: +##Pillow, pylibmc +zlib1g-dev + +##Postgresql and psycopg2 dependencies +libpq-dev + +##Pillow dependencies +libtiff5-dev +libjpeg62-turbo-dev +libfreetype6-dev +liblcms2-dev +libwebp-dev + +##django-extensions +libgraphviz-dev diff --git a/{{cookiecutter.project_slug}}/utility/requirements-focal.apt b/{{cookiecutter.project_slug}}/utility/requirements-focal.apt new file mode 100644 index 0000000000..fe6f21e466 --- /dev/null +++ b/{{cookiecutter.project_slug}}/utility/requirements-focal.apt @@ -0,0 +1,23 @@ +##basic build dependencies of various Django apps for Ubuntu Focal 20.04 +#build-essential metapackage install: make, gcc, g++, +build-essential +#required to translate +gettext +python3-dev + +##shared dependencies of: +##Pillow, pylibmc +zlib1g-dev + +##Postgresql and psycopg2 dependencies +libpq-dev + +##Pillow dependencies +libtiff5-dev +libjpeg8-dev +libfreetype6-dev +liblcms2-dev +libwebp-dev + +##django-extensions +graphviz-dev diff --git a/{{cookiecutter.project_slug}}/utility/requirements-jammy.apt b/{{cookiecutter.project_slug}}/utility/requirements-jammy.apt new file mode 100644 index 0000000000..63d1587e68 --- /dev/null +++ b/{{cookiecutter.project_slug}}/utility/requirements-jammy.apt @@ -0,0 +1,23 @@ +##basic build dependencies of various Django apps for Ubuntu Jammy 22.04 +#build-essential metapackage install: make, gcc, g++, +build-essential +#required to translate +gettext +python3-dev + +##shared dependencies of: +##Pillow, pylibmc +zlib1g-dev + +##Postgresql and psycopg2 dependencies +libpq-dev + +##Pillow dependencies +libtiff5-dev +libjpeg8-dev +libfreetype6-dev +liblcms2-dev +libwebp-dev + +##django-extensions +graphviz-dev diff --git a/{{cookiecutter.project_slug}}/utility/requirements-jessie.apt b/{{cookiecutter.project_slug}}/utility/requirements-jessie.apt new file mode 100644 index 0000000000..5c49365ba9 --- /dev/null +++ b/{{cookiecutter.project_slug}}/utility/requirements-jessie.apt @@ -0,0 +1,23 @@ +##basic build dependencies of various Django apps for Debian Jessie 8.x +#build-essential metapackage install: make, gcc, g++, +build-essential +#required to translate +gettext +python3-dev + +##shared dependencies of: +##Pillow, pylibmc +zlib1g-dev + +##Postgresql and psycopg2 dependencies +libpq-dev + +##Pillow dependencies +libtiff5-dev +libjpeg62-turbo-dev +libfreetype6-dev +liblcms2-dev +libwebp-dev + +##django-extensions +graphviz-dev diff --git a/{{cookiecutter.project_slug}}/utility/requirements-stretch.apt b/{{cookiecutter.project_slug}}/utility/requirements-stretch.apt new file mode 100644 index 0000000000..a2b3a7e5e0 --- /dev/null +++ b/{{cookiecutter.project_slug}}/utility/requirements-stretch.apt @@ -0,0 +1,23 @@ +##basic build dependencies of various Django apps for Debian Jessie 9.x +#build-essential metapackage install: make, gcc, g++, +build-essential +#required to translate +gettext +python3-dev + +##shared dependencies of: +##Pillow, pylibmc +zlib1g-dev + +##Postgresql and psycopg2 dependencies +libpq-dev + +##Pillow dependencies +libtiff5-dev +libjpeg62-turbo-dev +libfreetype6-dev +liblcms2-dev +libwebp-dev + +##django-extensions +graphviz-dev diff --git a/{{cookiecutter.project_slug}}/utility/requirements.apt b/{{cookiecutter.project_slug}}/utility/requirements-trusty.apt similarity index 70% rename from {{cookiecutter.project_slug}}/utility/requirements.apt rename to {{cookiecutter.project_slug}}/utility/requirements-trusty.apt index 9a18d50d4e..455f1a8689 100644 --- a/{{cookiecutter.project_slug}}/utility/requirements.apt +++ b/{{cookiecutter.project_slug}}/utility/requirements-trusty.apt @@ -1,13 +1,9 @@ -##basic build dependencies of various Django apps for Ubuntu 14.04 +##basic build dependencies of various Django apps for Ubuntu Trusty 14.04 #build-essential metapackage install: make, gcc, g++, build-essential #required to translate gettext -{% if cookiecutter.use_python2 == 'n' -%} python3-dev -{% else %} -python-dev -{%- endif %} ##shared dependencies of: ##Pillow, pylibmc diff --git a/{{cookiecutter.project_slug}}/utility/requirements-xenial.apt b/{{cookiecutter.project_slug}}/utility/requirements-xenial.apt new file mode 100644 index 0000000000..ba84ef167d --- /dev/null +++ b/{{cookiecutter.project_slug}}/utility/requirements-xenial.apt @@ -0,0 +1,23 @@ +##basic build dependencies of various Django apps for Ubuntu Xenial 16.04 +#build-essential metapackage install: make, gcc, g++, +build-essential +#required to translate +gettext +python3-dev + +##shared dependencies of: +##Pillow, pylibmc +zlib1g-dev + +##Postgresql and psycopg2 dependencies +libpq-dev + +##Pillow dependencies +libtiff5-dev +libjpeg8-dev +libfreetype6-dev +liblcms2-dev +libwebp-dev + +##django-extensions +graphviz-dev diff --git a/{{cookiecutter.project_slug}}/utility/requirements.apt.xenial b/{{cookiecutter.project_slug}}/utility/requirements.apt.xenial deleted file mode 100644 index f29f6b400f..0000000000 --- a/{{cookiecutter.project_slug}}/utility/requirements.apt.xenial +++ /dev/null @@ -1,44 +0,0 @@ -##basic build dependencies of various Django apps for Ubuntu 14.04 -#build-essential metapackage install: make, gcc, g++, -build-essential -#required to translate -gettext -python-dev - -##shared dependencies of: -##Pillow, pylibmc -zlib1g-dev - -##Postgresql and psycopg2 dependencies -libpq-dev - -##Pillow dependencies -libtiff5-dev -libjpeg8-dev -libfreetype6-dev -liblcms2-dev -libwebp-dev - - -##django-extensions -graphviz-dev - -##hitch -python-setuptools -python3-dev -python-virtualenv -python-pip -firefox -automake -libtool -libreadline6 -libreadline6-dev -libreadline-dev -libsqlite3-dev -libxml2 -libxml2-dev -libssl-dev -libbz2-dev -wget -curl -llvm diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py index ebc6a5e12d..fb65327098 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py @@ -1,3 +1,5 @@ -# -*- coding: utf-8 -*- -__version__ = '{{ cookiecutter.version }}' -__version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')]) +__version__ = "{{ cookiecutter.version }}" +__version_info__ = tuple( + int(num) if num.isdigit() else num + for num in __version__.replace("-", ".", 1).split(".") +) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py new file mode 100644 index 0000000000..7095a4714b --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py @@ -0,0 +1,14 @@ +import pytest + +from {{ cookiecutter.project_slug }}.users.models import User +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(db) -> User: + return UserFactory() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/__init__.py index 776ac1718a..1c7ecc8941 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/__init__.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/__init__.py @@ -3,4 +3,3 @@ http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django """ -# -*- coding: utf-8 -*- diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/__init__.py index 776ac1718a..1c7ecc8941 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/__init__.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/__init__.py @@ -3,4 +3,3 @@ http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django """ -# -*- coding: utf-8 -*- diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py index 555d02c424..304cd6d7c2 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py @@ -1,31 +1,42 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations import django.contrib.sites.models +from django.contrib.sites.models import _simple_domain_name_validator +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Site', + name="Site", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('domain', models.CharField(verbose_name='domain name', max_length=100, validators=[django.contrib.sites.models._simple_domain_name_validator])), - ('name', models.CharField(verbose_name='display name', max_length=50)), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "domain", + models.CharField( + max_length=100, + verbose_name="domain name", + validators=[_simple_domain_name_validator], + ), + ), + ("name", models.CharField(max_length=50, verbose_name="display name")), ], options={ - 'verbose_name_plural': 'sites', - 'verbose_name': 'site', - 'db_table': 'django_site', - 'ordering': ('domain',), + "ordering": ("domain",), + "db_table": "django_site", + "verbose_name": "site", + "verbose_name_plural": "sites", }, - managers=[ - (b'objects', django.contrib.sites.models.SiteManager()), - ], - ), + bases=(models.Model,), + managers=[("objects", django.contrib.sites.models.SiteManager())], + ) ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py new file mode 100644 index 0000000000..2c8d6dac01 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py @@ -0,0 +1,20 @@ +import django.contrib.sites.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [("sites", "0001_initial")] + + operations = [ + migrations.AlterField( + model_name="site", + name="domain", + field=models.CharField( + max_length=100, + unique=True, + validators=[django.contrib.sites.models._simple_domain_name_validator], + verbose_name="domain name", + ), + ) + ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_set_site_domain_and_name.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_set_site_domain_and_name.py deleted file mode 100644 index e6292f0b09..0000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_set_site_domain_and_name.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -To understand why this file is here, please read: - -http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django -""" -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations - - -def update_site_forward(apps, schema_editor): - """Set site domain and name.""" - Site = apps.get_model('sites', 'Site') - Site.objects.update_or_create( - id=settings.SITE_ID, - defaults={ - 'domain': '{{cookiecutter.domain_name}}', - 'name': '{{cookiecutter.project_name}}' - } - ) - - -def update_site_backward(apps, schema_editor): - """Revert site domain and name to default.""" - Site = apps.get_model('sites', 'Site') - Site.objects.update_or_create( - id=settings.SITE_ID, - defaults={ - 'domain': 'example.com', - 'name': 'example.com' - } - ) - - -class Migration(migrations.Migration): - - dependencies = [ - ('sites', '0001_initial'), - ] - - operations = [ - migrations.RunPython(update_site_forward, update_site_backward), - ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py new file mode 100644 index 0000000000..080c734bbd --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py @@ -0,0 +1,63 @@ +""" +To understand why this file is here, please read: + +http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django +""" +from django.conf import settings +from django.db import migrations + + +def _update_or_create_site_with_sequence(site_model, connection, domain, name): + """Update or create the site with default ID and keep the DB sequence in sync.""" + site, created = site_model.objects.update_or_create( + id=settings.SITE_ID, + defaults={ + "domain": domain, + "name": name, + }, + ) + if created: + # We provided the ID explicitly when creating the Site entry, therefore the DB + # sequence to auto-generate them wasn't used and is now out of sync. If we + # don't do anything, we'll get a unique constraint violation the next time a + # site is created. + # To avoid this, we need to manually update DB sequence and make sure it's + # greater than the maximum value. + max_id = site_model.objects.order_by('-id').first().id + with connection.cursor() as cursor: + cursor.execute("SELECT last_value from django_site_id_seq") + (current_id,) = cursor.fetchone() + if current_id <= max_id: + cursor.execute( + "alter sequence django_site_id_seq restart with %s", + [max_id + 1], + ) + + +def update_site_forward(apps, schema_editor): + """Set site domain and name.""" + Site = apps.get_model("sites", "Site") + _update_or_create_site_with_sequence( + Site, + schema_editor.connection, + "{{cookiecutter.domain_name}}", + "{{cookiecutter.project_name}}", + ) + + +def update_site_backward(apps, schema_editor): + """Revert site domain and name to default.""" + Site = apps.get_model("sites", "Site") + _update_or_create_site_with_sequence( + Site, + schema_editor.connection, + "example.com", + "example.com", + ) + + +class Migration(migrations.Migration): + + dependencies = [("sites", "0002_alter_domain_unique")] + + operations = [migrations.RunPython(update_site_forward, update_site_backward)] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0004_alter_options_ordering_domain.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0004_alter_options_ordering_domain.py new file mode 100644 index 0000000000..f7118ca81a --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0004_alter_options_ordering_domain.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.7 on 2021-02-04 14:49 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("sites", "0003_set_site_domain_and_name"), + ] + + operations = [ + migrations.AlterModelOptions( + name="site", + options={ + "ordering": ["domain"], + "verbose_name": "site", + "verbose_name_plural": "sites", + }, + ), + ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/__init__.py index 776ac1718a..1c7ecc8941 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/__init__.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/__init__.py @@ -3,4 +3,3 @@ http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django """ -# -*- coding: utf-8 -*- diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/css/project.css b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/css/project.css index 5f23c427ab..f1d543daf1 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/css/project.css +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/css/project.css @@ -11,28 +11,3 @@ background-color: #f2dede; border-color: #eed3d7; } - -/* This is a fix for the bootstrap4 alpha release */ -@media (max-width: 47.9em) { - .navbar-nav .nav-item { - float: none; - width: 100%; - display: inline-block; - } - - .navbar-nav .nav-item + .nav-item { - margin-left: 0; - } - - .nav.navbar-nav.pull-xs-right { - float: none !important; - } -} - -/* Display django-debug-toolbar. - See https://github.com/django-debug-toolbar/django-debug-toolbar/issues/742 - and https://github.com/pydanny/cookiecutter-django/issues/317 -*/ -[hidden][style="display: block;"] { - display: block !important; -} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/images/favicon.ico b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/images/favicons/favicon.ico similarity index 100% rename from {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/images/favicon.ico rename to {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/images/favicons/favicon.ico diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/custom_bootstrap_vars.scss b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/custom_bootstrap_vars.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss index e737d593ff..370096bb39 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss @@ -1,5 +1,13 @@ +@import "custom_bootstrap_vars"; +@import "bootstrap"; + + // project specific CSS goes here +//////////////////////////////// + //Variables// +//////////////////////////////// + // Alert colors $white: #fff; @@ -9,6 +17,10 @@ $pink: #f2dede; $dark-pink: #eed3d7; $red: #b94a48; +//////////////////////////////// + //Alerts// +//////////////////////////////// + // bootstrap alert CSS, translated to the django-standard levels of // debug, info, success, warning, error @@ -23,29 +35,3 @@ $red: #b94a48; border-color: $dark-pink; color: $red; } - -// This is a fix for the bootstrap4 alpha release - -@media (max-width: 47.9em) { - .navbar-nav .nav-item { - display: inline-block; - float: none; - width: 100%; - } - - .navbar-nav .nav-item + .nav-item { - margin-left: 0; - } - - .nav.navbar-nav.pull-xs-right { - float: none !important; - } -} - -// Display django-debug-toolbar. -// See https://github.com/django-debug-toolbar/django-debug-toolbar/issues/742 -// and https://github.com/pydanny/cookiecutter-django/issues/317 - -[hidden][style="display: block;"] { - display: block !important; -} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py deleted file mode 100644 index 44c57f7c13..0000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py +++ /dev/null @@ -1,62 +0,0 @@ -{% if cookiecutter.use_celery == 'y' %} -from __future__ import absolute_import -import os -from celery import Celery -from django.apps import AppConfig -from django.conf import settings - - -if not settings.configured: - # set the default Django settings module for the 'celery' program. - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local') # pragma: no cover - - -app = Celery('{{cookiecutter.project_slug}}') - - -class CeleryConfig(AppConfig): - name = '{{cookiecutter.project_slug}}.taskapp' - verbose_name = 'Celery Config' - - 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') - app.autodiscover_tasks(lambda: settings.INSTALLED_APPS, force=True) - - {% if cookiecutter.use_sentry == 'y' -%} - if hasattr(settings, 'RAVEN_CONFIG'): - # Celery signal registration - from raven import Client as RavenClient - from raven.contrib.celery import register_signal as raven_register_signal - from raven.contrib.celery import register_logger_signal as raven_register_logger_signal - - raven_client = RavenClient(dsn=settings.RAVEN_CONFIG['DSN']) - raven_register_logger_signal(raven_client) - raven_register_signal(raven_client) - {%- endif %} - - {% if cookiecutter.use_opbeat == 'y' -%} - if hasattr(settings, 'OPBEAT'): - from opbeat.contrib.django.models import client as opbeat_client - from opbeat.contrib.django.models import logger as opbeat_logger - from opbeat.contrib.django.models import register_handlers as opbeat_register_handlers - from opbeat.contrib.celery import register_signal as opbeat_register_signal - - try: - opbeat_register_signal(opbeat_client) - except Exception as e: - opbeat_logger.exception('Failed installing celery hook: %s' % e) - - if 'opbeat.contrib.django' in settings.INSTALLED_APPS: - opbeat_register_handlers() - {%- endif %} - - -@app.task(bind=True) -def debug_task(self): - print('Request: {0!r}'.format(self.request)) # pragma: no cover -{% else %} -# Use this as a starting point for your project with celery. -# If you are not using celery, you can remove this app -{% endif -%} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403.html new file mode 100644 index 0000000000..4c4745f7d9 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403.html @@ -0,0 +1,10 @@ +{% raw %}{% extends "base.html" %} + +{% block title %}Forbidden (403){% endblock %} + +{% block content %} +

Forbidden (403)

+ +

{% if exception %}{{ exception }}{% else %}You're not allowed to access this page.{% endif %}

+{% endblock content %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html index 0d7a787f8e..d98241858a 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html @@ -1,9 +1,10 @@ {% raw %}{% extends "base.html" %} -{% block title %}Page Not found{% endblock %} +{% block title %}Page not found{% endblock %} {% block content %} -

Page Not found

+

Page not found

-

This is not the page you were looking for.

-{% endblock content %}{% endraw %} +

{% if exception %}{{ exception }}{% else %}This is not the page you were looking for.{% endif %}

+{% endblock content %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html index 122e0813e6..481bb2d0b9 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html @@ -9,5 +9,4 @@

Looks like something went wrong!

We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.

{% endblock content %} - -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html new file mode 100644 index 0000000000..ab910820ed --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html @@ -0,0 +1,12 @@ +{% raw %}{% extends "account/base.html" %} + +{% load i18n %} + +{% block head_title %}{% translate "Account Inactive" %}{% endblock %} + +{% block inner %} +

{% translate "Account Inactive" %}

+ +

{% translate "This account is inactive." %}

+{% endblock %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html index 6703bb8b0b..03c86724bb 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html @@ -1,3 +1,11 @@ {% raw %}{% extends "base.html" %} {% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %} -{% endraw %} \ No newline at end of file + +{% block content %} +
+
+ {% block inner %}{% endblock %} +
+
+{% endblock %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html index e757f21f35..1faa2b9fd5 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html @@ -1,80 +1,79 @@ -{% raw %}{% extends "account/base.html" %} +{% raw %} +{% extends "account/base.html" %} {% load i18n %} {% load crispy_forms_tags %} -{% block head_title %}{% trans "Account" %}{% endblock %} - -{% block content %} -
-
-
-

{% trans "E-mail Addresses" %}

- {% if user.emailaddress_set.all %} -

{% trans 'The following e-mail addresses are associated with your account:' %}

- - - - {% else %} -

{% trans 'Warning:'%} {% trans "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." %}

- - {% endif %} - - -

{% trans "Add E-mail Address" %}

- -
- {% csrf_token %} - {{ form|crispy }} - -
-
-
+{% block head_title %}{% translate "Account" %}{% endblock %} + +{% block inner %} +

{% translate "E-mail Addresses" %}

+ +{% if user.emailaddress_set.all %} +

{% translate 'The following e-mail addresses are associated with your account:' %}

+ + + +{% else %} +

{% 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." %}

+ +{% endif %} + + +

{% translate "Add E-mail Address" %}

+ +
+ {% csrf_token %} + {{ form|crispy }} + +
+ {% endblock %} -{% block extra_body %} +{% block inline_javascript %} +{{ block.super }} {% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html index d7886239a5..5e4924c839 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html @@ -3,35 +3,30 @@ {% load i18n %} {% load account %} -{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} +{% block head_title %}{% translate "Confirm E-mail Address" %}{% endblock %} -{% block content %} -
-
-
-

{% trans "Confirm E-mail Address" %}

+{% block inner %} +

{% translate "Confirm E-mail Address" %}

- {% if confirmation %} +{% if confirmation %} - {% user_display confirmation.email_address.user as user_display %} +{% user_display confirmation.email_address.user as user_display %} -

{% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

+

{% blocktranslate with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktranslate %}

-
- {% csrf_token %} - -
+
+{% csrf_token %} + +
- {% else %} +{% else %} - {% url 'account_email' as email_url %} +{% url 'account_email' as email_url %} -

{% blocktrans %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktrans %}

+

{% blocktranslate %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktranslate %}

+ +{% endif %} - {% endif %} -
-
-
{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirmed.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirmed.html deleted file mode 100644 index 97f2a82c08..0000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirmed.html +++ /dev/null @@ -1,22 +0,0 @@ -{% raw %}{% extends "account/base.html" %} - -{% load i18n %} -{% load account %} - -{% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} - - -{% block content %} -
-
-
-

{% trans "Confirm E-mail Address" %}

- - {% user_display email_address.user as user_display %} - -

{% blocktrans with email_address.email as email %}You have confirmed that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

-
-
-
-{% endblock %} -{% endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html index acbc50c216..25a292edad 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html @@ -1,48 +1,60 @@ {% raw %}{% extends "account/base.html" %} {% load i18n %} -{% load account %} -{% load socialaccount %} +{% load account socialaccount %} {% load crispy_forms_tags %} -{% block head_title %}{% trans "Sign In" %}{% endblock %} +{% block head_title %}{% translate "Sign In" %}{% endblock %} -{% block content %} -
-
-
-

{% trans "Sign In" %}

- {% get_providers as socialaccount_providers %} - {% if socialaccount_providers %} -

{% blocktrans with site.name as site_name %}Please sign in with one - of your existing third party accounts. Or, sign up - for a {{ site_name }} account and sign in below:{% endblocktrans %}

+{% block inner %} -
+

{% translate "Sign In" %}

-
    - {% include "socialaccount/snippets/provider_list.html" with process="login" %} -
+{% get_providers as socialaccount_providers %} - +{% if socialaccount_providers %} +

+ {% translate "Please sign in with one of your existing third party accounts:" %} + {% if ACCOUNT_ALLOW_REGISTRATION %} + {% blocktranslate trimmed %} + Or, sign up + for a {{ site_name }} account and sign in below: + {% endblocktranslate %} + {% endif %} +

-
+
- {% include "socialaccount/snippets/login_extra.html" %} +
    + {% include "socialaccount/snippets/provider_list.html" with process="login" %} +
- {% endif %} + - -
-
+ + {% include "socialaccount/snippets/login_extra.html" %} + +{% else %} + {% if ACCOUNT_ALLOW_REGISTRATION %} +

+ {% blocktranslate trimmed %} + If you have not created an account yet, then please + sign up first. + {% endblocktranslate %} +

+ {% endif %} +{% endif %} + + + {% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html index 039dc7be91..5edc60478e 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html @@ -2,27 +2,19 @@ {% load i18n %} -{% block head_title %}{% trans "Sign Out" %}{% endblock %} +{% block head_title %}{% translate "Sign Out" %}{% endblock %} -{% block content %} -
-
-
+{% block inner %} +

{% translate "Sign Out" %}

-

{% trans "Sign Out" %}

- -

{% trans 'Are you sure you want to sign out?' %}

- -
- {% csrf_token %} - {% if redirect_field_value %} - - {% endif %} - -
-
-
-
+

{% translate 'Are you sure you want to sign out?' %}

+
+ {% csrf_token %} + {% if redirect_field_value %} + + {% endif %} + +
{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html index 9855c0f5af..b8dd7ac535 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html @@ -2,21 +2,16 @@ {% load i18n %} {% load crispy_forms_tags %} -{% block head_title %}{% trans "Change Password" %}{% endblock %} -{% block content %} -
-
-
-

{% trans "Change Password" %}

+{% block head_title %}{% translate "Change Password" %}{% endblock %} -
- {% csrf_token %} - {{ form|crispy }} - -
-
-
-
+{% block inner %} +

{% translate "Change Password" %}

+ +
+ {% csrf_token %} + {{ form|crispy }} + +
{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html index 2e6c5a6fc8..f424b21111 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html @@ -4,36 +4,23 @@ {% load account %} {% load crispy_forms_tags %} -{% block head_title %}{% trans "Password Reset" %}{% endblock %} +{% block head_title %}{% translate "Password Reset" %}{% endblock %} -{% block content %} -
-
-
+{% block inner %} -

{% trans "Password Reset" %}

- {% if user.is_authenticated %} - {% include "account/snippets/already_logged_in.html" %} - {% endif %} +

{% translate "Password Reset" %}

+ {% if user.is_authenticated %} + {% include "account/snippets/already_logged_in.html" %} + {% endif %} -

{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

+

{% translate "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

-
- {% csrf_token %} - {{ form|crispy }} - -
+
+ {% csrf_token %} + {{ form|crispy }} + +
-

{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}

-
-
-
+

{% blocktranslate %}Please contact us if you have any trouble resetting your password.{% endblocktranslate %}

{% endblock %} - -{% block javascript %} - {{ block.super }} - -{% endblock javascript %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html index e954abc61d..76d07eb21c 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html @@ -3,21 +3,15 @@ {% load i18n %} {% load account %} -{% block head_title %}{% trans "Password Reset" %}{% endblock %} +{% block head_title %}{% translate "Password Reset" %}{% endblock %} -{% block content %} -
-
-
-

{% trans "Password Reset" %}

- - {% if user.is_authenticated %} - {% include "account/snippets/already_logged_in.html" %} - {% endif %} - -

{% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

-
-
-
+{% block inner %} +

{% translate "Password Reset" %}

+ + {% if user.is_authenticated %} + {% include "account/snippets/already_logged_in.html" %} + {% endif %} + +

{% blocktranslate %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktranslate %}

{% endblock %} -{% endraw %} \ No newline at end of file +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html index 8e1b39e26a..ce5d72a6d2 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html @@ -2,31 +2,24 @@ {% load i18n %} {% load crispy_forms_tags %} +{% block head_title %}{% translate "Change Password" %}{% endblock %} -{% block head_title %}{% trans "Change Password" %}{% endblock %} - -{% block content %} -
-
-
-

{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}

+{% block inner %} +

{% if token_fail %}{% translate "Bad Token" %}{% else %}{% translate "Change Password" %}{% endif %}

{% if token_fail %} {% url 'account_reset_password' as passwd_reset_url %} -

{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}

+

{% blocktranslate %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktranslate %}

{% else %} {% if form %} -
+ {% csrf_token %} {{ form|crispy }} - +
{% else %} -

{% trans 'Your password is now changed.' %}

+

{% translate 'Your password is now changed.' %}

{% endif %} {% endif %} -
-
-
{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html index 015028e4a3..34123fd535 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html @@ -1,16 +1,10 @@ {% raw %}{% extends "account/base.html" %} {% load i18n %} -{% block head_title %}{% trans "Change Password" %}{% endblock %} +{% block head_title %}{% translate "Change Password" %}{% endblock %} -{% block content %} -
-
-
-

{% trans "Change Password" %}

-

{% trans 'Your password is now changed.' %}

-
-
-
+{% block inner %} +

{% translate "Change Password" %}

+

{% translate 'Your password is now changed.' %}

{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html index 7e2967e377..812410fc0f 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html @@ -1,23 +1,17 @@ -{% raw %} -{% extends "account/base.html" %} +{% raw %}{% extends "account/base.html" %} -{% load i18n crispy_forms_tags %} +{% load i18n %} +{% load crispy_forms_tags %} -{% block head_title %}{% trans "Set Password" %}{% endblock %} +{% block head_title %}{% translate "Set Password" %}{% endblock %} -{% block content %} -
-
-
-

{% trans "Set Password" %}

+{% block inner %} +

{% translate "Set Password" %}

-
- {% csrf_token %} - {{ form|crispy }} - -
-
-
-
+
+ {% csrf_token %} + {{ form|crispy }} + +
{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html index 63e57a5fc7..8c1c11aca8 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html @@ -3,31 +3,21 @@ {% load i18n %} {% load crispy_forms_tags %} -{% block title %}{% trans "Signup" %}{% endblock title %} +{% block head_title %}{% translate "Signup" %}{% endblock %} -{% block content %} +{% block inner %} +

{% translate "Sign Up" %}

-
-
-
-

{% trans "Sign Up" %}

+

{% blocktranslate %}Already have an account? Then please sign in.{% endblocktranslate %}

-

{% blocktrans %}Already have an account? Then please sign in.{% endblocktrans %}

+ - -
-
-
- - -{% endblock content %} - - -{% endraw %} +{% endblock %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html index f5976dad5c..c2e64d14f4 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html @@ -2,17 +2,11 @@ {% load i18n %} -{% block head_title %}{% trans "Sign Up Closed" %}{% endblock %} +{% block head_title %}{% translate "Sign Up Closed" %}{% endblock %} -{% block content %} -
-
-
-

{% trans "Sign Up Closed" %}

+{% block inner %} +

{% translate "Sign Up Closed" %}

-

{% trans "We are sorry, but the sign up is currently closed." %}

-
-
-
+

{% translate "We are sorry, but the sign up is currently closed." %}

{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html index 0c082288cc..be8f1cef97 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html @@ -2,18 +2,12 @@ {% load i18n %} -{% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} +{% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %} -{% block content %} -
-
-
-

{% trans "Verify Your E-mail Address" %}

- -

{% blocktrans %}We have sent an e-mail to {{ email }} for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

-
-
-
+{% block inner %} +

{% translate "Verify Your E-mail Address" %}

+ +

{% 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 %}

{% endblock %} -{% endraw %} \ No newline at end of file +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html index 7d071c6a6f..2148a18042 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html @@ -2,27 +2,21 @@ {% load i18n %} -{% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} +{% block head_title %}{% translate "Verify Your E-mail Address" %}{% endblock %} -{% block content %} -
-
-
-

{% trans "Verify Your E-mail Address" %}

+{% block inner %} +

{% translate "Verify Your E-mail Address" %}

- {% url 'account_email' as email_url %} +{% url 'account_email' as email_url %} -

{% blocktrans %}This part of the site requires us to verify that - you are who you claim to be. For this purpose, we require that you - verify ownership of your e-mail address. {% endblocktrans %}

+

{% blocktranslate %}This part of the site requires us to verify that +you are who you claim to be. For this purpose, we require that you +verify ownership of your e-mail address. {% endblocktranslate %}

-

{% blocktrans %}We have sent an e-mail to you for - verification. Please click on the link inside this e-mail. Please - contact us if you do not receive it within a few minutes.{% endblocktrans %}

+

{% blocktranslate %}We have sent an e-mail to you for +verification. Please click on the link inside this e-mail. Please +contact us if you do not receive it within a few minutes.{% endblocktranslate %}

-

{% blocktrans %}Note: you can still change your e-mail address.{% endblocktrans %}

-
-
-
+

{% blocktranslate %}Note: you can still change your e-mail address.{% endblocktranslate %}

{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html index b54692228e..58aca7208d 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html @@ -1,83 +1,121 @@ -{% raw %}{% load staticfiles i18n %} - +{% raw %}{% load static i18n {% endraw %}{% if cookiecutter.frontend_pipeline == 'Django Compressor' %}compress{% endif %}{% raw %}%} +{% get_current_language as LANGUAGE_CODE %} + {% block title %}{% endraw %}{{ cookiecutter.project_name }}{% raw %}{% endblock title %} - - + + - - + {% block css %} - - - - - - + {%- endraw %} + {%- if cookiecutter.frontend_pipeline != 'Gulp' %} + {%- raw %} + + + {%- endraw %} + {%- endif %} + {%- raw %} + + + + {%- endraw %}{% if cookiecutter.frontend_pipeline == 'None' %}{% raw %} + {%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Django Compressor' %}{% raw %} + {% compress css %} + + {% endcompress %} + {%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %} + + {%- endraw %}{% endif %}{% raw %} {% endblock %} + + {# Placed at the top of the document so pages load faster with defer #} + {% block javascript %} + {%- endraw %}{% if cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %} + + + {%- endraw %}{% else %}{% raw %} + + + + {%- endraw %}{% endif %}{% raw %} - {% endraw %}{% if cookiecutter.use_angular == "y" %}{% raw %}{% block angular %} - - {% endblock %}{% endraw %}{% endif %}{% raw %} + + {%- endraw %}{% if cookiecutter.frontend_pipeline == 'None' %}{% raw %} + + {%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Django Compressor' %}{% raw %} + {% compress js %} + + {% endcompress %} + {%- endraw %}{% elif cookiecutter.frontend_pipeline == 'Gulp' %}{% raw %} + + {%- endraw %}{% endif %}{% raw %} + + {% endblock javascript %} -
- +
{% if messages %} - {% for message in messages %} -
{{ message }}
- {% endfor %} + {% for message in messages %} +
+ {{ message }} + +
+ {% endfor %} {% endif %} {% block content %} @@ -88,24 +126,15 @@ {% block modal %}{% endblock modal %} - - - {% block javascript %} - - - - - - - - - - - - - - {% endblock javascript %} + {% block inline_javascript %} + {% comment %} + Script tags with only code, no src (defer by default). To run + with a "defer" so that you run inline code: + + {% endcomment %} + {% endblock inline_javascript %} - {% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html index 94beff9c88..8968a3d4f9 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html @@ -1 +1 @@ -{% raw %}{% extends "base.html" %}{% endraw %} \ No newline at end of file +{% raw %}{% extends "base.html" %}{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html index 94beff9c88..8968a3d4f9 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html @@ -1 +1 @@ -{% raw %}{% extends "base.html" %}{% endraw %} \ No newline at end of file +{% raw %}{% extends "base.html" %}{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html index 5e186194a1..eed39ca3a2 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html @@ -20,9 +20,9 @@

{{ object.username }}

-
- My Info - E-Mail + @@ -30,7 +30,6 @@

{{ object.username }}

{% endif %} -
{% endblock content %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html index 5aeaafb50a..53e14a5308 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html @@ -4,14 +4,15 @@ {% block title %}{{ user.username }}{% endblock %} {% block content %} -

{{ user.username }}

-
- {% csrf_token %} - {{ form|crispy }} -
-
- -
-
-
-{% endblock %}{% endraw %} +

{{ user.username }}

+
+ {% csrf_token %} + {{ form|crispy }} +
+
+ +
+
+
+{% endblock %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_list.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_list.html deleted file mode 100644 index 8ea63bc435..0000000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_list.html +++ /dev/null @@ -1,22 +0,0 @@ -{% raw %}{% extends "base.html" %} -{% load static %}{% load i18n %} -{% block title %}Members{% endblock %} - -{% block content %} - -
- -

Users

- -
- {% for user in user_list %} - -

{{ user.username }}

-
- {% endfor %} - -
- -
- -{% endblock content %}{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/__init__.py index 40a96afc6f..e69de29bb2 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/__init__.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py index ce8a159d76..0d206fae4a 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py @@ -1,14 +1,16 @@ -# -*- coding: utf-8 -*- -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): - return getattr(settings, 'ACCOUNT_ALLOW_REGISTRATION', True) + 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): - return getattr(settings, 'ACCOUNT_ALLOW_REGISTRATION', True) + def is_open_for_signup(self, request: HttpRequest, sociallogin: Any): + return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py index bd437fe1e2..6675f483a6 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py @@ -1,38 +1,34 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -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 - - -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.' - }) +from django.contrib.auth import admin as auth_admin +from django.contrib.auth import get_user_model +from django.utils.translation import gettext_lazy as _ - class Meta(UserCreationForm.Meta): - model = User +from {{ cookiecutter.project_slug }}.users.forms import UserAdminChangeForm, UserAdminCreationForm - 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 UserAdmin(AuthUserAdmin): - form = MyUserChangeForm - add_form = MyUserCreationForm +class UserAdmin(auth_admin.UserAdmin): + + form = UserAdminChangeForm + add_form = UserAdminCreationForm + fieldsets = ( + (None, {"fields": ("username", "password")}), + (_("Personal info"), {"fields": ("name", "email")}), + ( + _("Permissions"), + { + "fields": ( + "is_active", + "is_staff", + "is_superuser", + "groups", + "user_permissions", + ), + }, + ), + (_("Important dates"), {"fields": ("last_login", "date_joined")}), + ) + list_display = ["username", "name", "is_superuser"] + search_fields = ["name"] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py new file mode 100644 index 0000000000..b5ccabba10 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py @@ -0,0 +1,14 @@ +from django.contrib.auth import get_user_model +from rest_framework import serializers + +User = get_user_model() + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ["username", "name", "url"] + + extra_kwargs = { + "url": {"view_name": "api:user-detail", "lookup_field": "username"} + } diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py new file mode 100644 index 0000000000..98bb04e7ba --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/views.py @@ -0,0 +1,25 @@ +from django.contrib.auth import get_user_model +from rest_framework import status +from rest_framework.decorators import action +from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin +from rest_framework.response import Response +from rest_framework.viewsets import GenericViewSet + +from .serializers import UserSerializer + +User = get_user_model() + + +class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet): + serializer_class = UserSerializer + queryset = User.objects.all() + lookup_field = "username" + + def get_queryset(self, *args, **kwargs): + assert isinstance(self.request.user.id, int) + return self.queryset.filter(id=self.request.user.id) + + @action(detail=False) + def me(self, request): + serializer = UserSerializer(request.user, context={"request": request}) + return Response(status=status.HTTP_200_OK, data=serializer.data) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py new file mode 100644 index 0000000000..2241e5eb58 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py @@ -0,0 +1,13 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class UsersConfig(AppConfig): + name = "{{ cookiecutter.project_slug }}.users" + verbose_name = _("Users") + + def ready(self): + try: + import {{ cookiecutter.project_slug }}.users.signals # noqa F401 + except ImportError: + pass diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/context_processors.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/context_processors.py new file mode 100644 index 0000000000..e2633aecde --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/context_processors.py @@ -0,0 +1,8 @@ +from django.conf import settings + + +def allauth_settings(request): + """Expose some settings from django-allauth in templates.""" + return { + "ACCOUNT_ALLOW_REGISTRATION": settings.ACCOUNT_ALLOW_REGISTRATION, + } diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py new file mode 100644 index 0000000000..6e1dd9d328 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py @@ -0,0 +1,42 @@ +from allauth.account.forms import SignupForm +from allauth.socialaccount.forms import SignupForm as SocialSignupForm +from django.contrib.auth import forms as admin_forms +from django.contrib.auth import get_user_model +from django.utils.translation import gettext_lazy as _ + +User = get_user_model() + + +class UserAdminChangeForm(admin_forms.UserChangeForm): + class Meta(admin_forms.UserChangeForm.Meta): + model = User + + +class UserAdminCreationForm(admin_forms.UserCreationForm): + """ + Form for User Creation in the Admin Area. + To change user signup, see UserSignupForm and UserSocialSignupForm. + """ + + class Meta(admin_forms.UserCreationForm.Meta): + model = User + + error_messages = { + "username": {"unique": _("This username has already been taken.")} + } + + +class UserSignupForm(SignupForm): + """ + Form that will be rendered on a user sign up section/screen. + Default fields will be added automatically. + Check UserSocialSignupForm for accounts created from social. + """ + + +class UserSocialSignupForm(SocialSignupForm): + """ + Renders the form when user has signed up using social accounts. + Default fields will be added automatically. + See UserSignupForm otherwise. + """ diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py index 8327d28900..acd18512fb 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py @@ -1,44 +1,124 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import django.utils.timezone import django.contrib.auth.models -import django.core.validators +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone class Migration(migrations.Migration): + initial = True + dependencies = [ - ('auth', '0006_require_contenttypes_0002'), + ("auth", "0012_alter_user_first_name_max_length"), ] operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)), - ('is_superuser', models.BooleanField(help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status', default=False)), - ('username', models.CharField(max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], verbose_name='username', error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True)), - ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)), - ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)), - ('email', models.EmailField(max_length=254, verbose_name='email address', blank=True)), - ('is_staff', models.BooleanField(help_text='Designates whether the user can log into this admin site.', verbose_name='staff status', default=False)), - ('is_active', models.BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active', default=True)), - ('date_joined', models.DateTimeField(verbose_name='date joined', default=django.utils.timezone.now)), - ('groups', models.ManyToManyField(related_name='user_set', blank=True, verbose_name='groups', to='auth.Group', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_query_name='user')), - ('user_permissions', models.ManyToManyField(related_name='user_set', blank=True, verbose_name='user permissions', to='auth.Permission', help_text='Specific permissions for this user.', related_query_name='user')), - ('name', models.CharField(max_length=255, verbose_name='Name of User', blank=True)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ( + "name", + models.CharField( + blank=True, max_length=255, verbose_name="Name of User" + ), + ), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.Group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.Permission", + verbose_name="user permissions", + ), + ), ], options={ - 'verbose_name': 'user', - 'abstract': False, - 'verbose_name_plural': 'users', + "verbose_name": "user", + "verbose_name_plural": "users", + "abstract": False, }, managers=[ - (b'objects', django.contrib.auth.models.UserManager()), + ("objects", django.contrib.auth.models.UserManager()), ], ), ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py index 0beebfb148..1f6f61bc06 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py @@ -1,22 +1,26 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import - from django.contrib.auth.models import AbstractUser -from django.core.urlresolvers import reverse -from django.db import models -from django.utils.encoding import python_2_unicode_compatible -from django.utils.translation import ugettext_lazy as _ +from django.db.models import CharField +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ -@python_2_unicode_compatible class User(AbstractUser): + """ + Default custom user model for {{cookiecutter.project_name}}. + If adding fields that need to be filled at user signup, + check forms.SignupForm and forms.SocialSignupForms accordingly. + """ - # 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 + #: First and last name do not cover name patterns around the globe + name = CharField(_("Name of User"), blank=True, max_length=255) + first_name = None # type: ignore + last_name = None # type: ignore def get_absolute_url(self): - return reverse('users:detail', kwargs={'username': self.username}) + """Get url for user's detail view. + + Returns: + str: URL for user detail. + + """ + return reverse("users:detail", kwargs={"username": self.username}) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tasks.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tasks.py new file mode 100644 index 0000000000..c99341c5ff --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tasks.py @@ -0,0 +1,11 @@ +from django.contrib.auth import get_user_model + +from config import celery_app + +User = get_user_model() + + +@celery_app.task() +def get_users_count(): + """A pointless Celery task to demonstrate usage.""" + return User.objects.count() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py index e2c967de77..e304762271 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py @@ -1,11 +1,33 @@ -import factory +from collections.abc import Sequence +from typing import Any +from django.contrib.auth import get_user_model +from factory import Faker, post_generation +from factory.django import DjangoModelFactory -class UserFactory(factory.django.DjangoModelFactory): - username = factory.Sequence(lambda n: 'user-{0}'.format(n)) - email = factory.Sequence(lambda n: 'user-{0}@example.com'.format(n)) - 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 = ( + extracted + if extracted + else Faker( + "password", + length=42, + special_chars=True, + digits=True, + upper_case=True, + lower_case=True, + ).evaluate(None, None, extra={"locale": None}) + ) + self.set_password(password) class Meta: - model = 'users.User' - django_get_or_create = ('username', ) + model = get_user_model() + django_get_or_create = ["username"] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py index 10b07b749d..a370784fcc 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py @@ -1,40 +1,37 @@ -from test_plus.test import TestCase - -from ..admin import MyUserCreationForm - - -class TestMyUserCreationForm(TestCase): - - def setUp(self): - self.user = self.make_user() - - def test_clean_username_success(self): - # Instantiate the form with a new username - form = MyUserCreationForm({ - 'username': 'alamode', - 'password1': '123456', - 'password2': '123456', - }) - # 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': '123456', - 'password2': '123456', - }) - # 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) +from django.urls import reverse + +from {{ cookiecutter.project_slug }}.users.models import User + + +class TestUserAdmin: + def test_changelist(self, admin_client): + url = reverse("admin:users_user_changelist") + response = admin_client.get(url) + assert response.status_code == 200 + + def test_search(self, admin_client): + url = reverse("admin:users_user_changelist") + response = admin_client.get(url, data={"q": "test"}) + assert response.status_code == 200 + + def test_add(self, admin_client): + url = reverse("admin:users_user_add") + response = admin_client.get(url) + assert response.status_code == 200 + + response = admin_client.post( + url, + data={ + "username": "test", + "password1": "My_R@ndom-P@ssw0rd", + "password2": "My_R@ndom-P@ssw0rd", + }, + ) + assert response.status_code == 302 + assert User.objects.filter(username="test").exists() + + def test_view_user(self, admin_client): + user = User.objects.get(username="admin") + url = reverse("admin:users_user_change", kwargs={"object_id": user.pk}) + response = admin_client.get(url) + assert response.status_code == 200 diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py new file mode 100644 index 0000000000..7d9c444d32 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_urls.py @@ -0,0 +1,21 @@ +from django.urls import resolve, reverse + +from {{ cookiecutter.project_slug }}.users.models import User + + +def test_user_detail(user: User): + assert ( + reverse("api:user-detail", kwargs={"username": user.username}) + == f"/api/users/{user.username}/" + ) + assert resolve(f"/api/users/{user.username}/").view_name == "api:user-detail" + + +def test_user_list(): + assert reverse("api:user-list") == "/api/users/" + assert resolve("/api/users/").view_name == "api:user-list" + + +def test_user_me(): + assert reverse("api:user-me") == "/api/users/me/" + assert resolve("/api/users/me/").view_name == "api:user-me" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py new file mode 100644 index 0000000000..4d163bf086 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py @@ -0,0 +1,30 @@ +from django.test import RequestFactory + +from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet +from {{ cookiecutter.project_slug }}.users.models import User + + +class TestUserViewSet: + def test_get_queryset(self, user: User, rf: RequestFactory): + view = UserViewSet() + request = rf.get("/fake-url/") + request.user = user + + view.request = request + + assert user in view.get_queryset() + + def test_me(self, user: User, rf: RequestFactory): + view = UserViewSet() + request = rf.get("/fake-url/") + request.user = user + + view.request = request + + response = view.me(request) + + assert response.data == { + "username": user.username, + "name": user.name, + "url": f"http://testserver/api/users/{user.username}/", + } diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py new file mode 100644 index 0000000000..261f88c8c6 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py @@ -0,0 +1,36 @@ +""" +Module for all Form Tests. +""" +from django.utils.translation import gettext_lazy as _ + +from {{ cookiecutter.project_slug }}.users.forms import UserAdminCreationForm +from {{ cookiecutter.project_slug }}.users.models import User + + +class TestUserAdminCreationForm: + """ + Test class for all tests related to the UserAdminCreationForm + """ + + def test_username_validation_error_msg(self, user: User): + """ + Tests UserAdminCreation Form's unique validator functions correctly by testing: + 1) A new user with an existing username cannot be added. + 2) Only 1 error is raised by the UserCreation Form + 3) The desired error message is raised + """ + + # The user already exists, + # hence cannot be created. + form = UserAdminCreationForm( + { + "username": user.username, + "password1": user.password, + "password2": user.password, + } + ) + + assert not form.is_valid() + assert len(form.errors) == 1 + assert "username" in form.errors + assert form.errors["username"][0] == _("This username has already been taken.") diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py index 894ed18325..b09bcdf0bf 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py @@ -1,19 +1,5 @@ -from test_plus.test import TestCase +from {{ cookiecutter.project_slug }}.users.models import User -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: User): + assert user.get_absolute_url() == f"/users/{user.username}/" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py new file mode 100644 index 0000000000..f97658b55c --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_swagger.py @@ -0,0 +1,21 @@ +import pytest +from django.urls import reverse + + +def test_swagger_accessible_by_admin(admin_client): + url = reverse("api-docs") + response = admin_client.get(url) + assert response.status_code == 200 + + +@pytest.mark.django_db +def test_swagger_ui_not_accessible_by_normal_user(client): + url = reverse("api-docs") + response = client.get(url) + assert response.status_code == 403 + + +def test_api_schema_generated_successfully(admin_client): + url = reverse("api-schema") + response = admin_client.get(url) + assert response.status_code == 200 diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py new file mode 100644 index 0000000000..41d5af292a --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py @@ -0,0 +1,16 @@ +import pytest +from celery.result import EagerResult + +from {{ cookiecutter.project_slug }}.users.tasks import get_users_count +from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory + +pytestmark = pytest.mark.django_db + + +def test_user_count(settings): + """A basic test to execute the get_users_count Celery task.""" + UserFactory.create_batch(3) + settings.CELERY_TASK_ALWAYS_EAGER = True + task_result = get_users_count.delay() + assert isinstance(task_result, EagerResult) + assert task_result.result == 3 diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py new file mode 100644 index 0000000000..7cd056db58 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py @@ -0,0 +1,21 @@ +from django.urls import resolve, reverse + +from {{ cookiecutter.project_slug }}.users.models import User + + +def test_detail(user: User): + assert ( + reverse("users:detail", kwargs={"username": user.username}) + == f"/users/{user.username}/" + ) + assert resolve(f"/users/{user.username}/").view_name == "users:detail" + + +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" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py index 23f30f0307..d968d7ec9d 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py @@ -1,64 +1,103 @@ +import pytest +from django.conf import settings +from django.contrib import messages +from django.contrib.auth.models import AnonymousUser +from django.contrib.messages.middleware import MessageMiddleware +from django.contrib.sessions.middleware import SessionMiddleware +from django.http import HttpRequest, HttpResponseRedirect from django.test import RequestFactory +from django.urls import reverse -from test_plus.test import TestCase - -from ..views import ( +from {{ cookiecutter.project_slug }}.users.forms import UserAdminChangeForm +from {{ cookiecutter.project_slug }}.users.models import User +from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory +from {{ cookiecutter.project_slug }}.users.views import ( UserRedirectView, - UserUpdateView + UserUpdateView, + user_detail_view, ) +pytestmark = pytest.mark.django_db + + +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 dummy_get_response(self, request: HttpRequest): + return None + + def test_get_success_url(self, user: User, rf: RequestFactory): + view = UserUpdateView() + request = rf.get("/fake-url/") + request.user = user + + view.request = request + + assert view.get_success_url() == f"/users/{user.username}/" + + def test_get_object(self, user: User, rf: RequestFactory): + view = UserUpdateView() + request = rf.get("/fake-url/") + request.user = user + + view.request = request + + assert view.get_object() == user -class BaseUserTestCase(TestCase): + def test_form_valid(self, user: User, rf: RequestFactory): + view = UserUpdateView() + request = rf.get("/fake-url/") - def setUp(self): - self.user = self.make_user() - self.factory = RequestFactory() + # Add the session/message middleware to the request + SessionMiddleware(self.dummy_get_response).process_request(request) + MessageMiddleware(self.dummy_get_response).process_request(request) + request.user = user + view.request = request + + # Initialize the form + form = UserAdminChangeForm() + form.cleaned_data = {} + form.instance = user + view.form_valid(form) + + messages_sent = [m.message for m in messages.get_messages(request)] + assert messages_sent == ["Information successfully updated"] -class TestUserRedirectView(BaseUserTestCase): - def test_get_redirect_url(self): - # Instantiate the view directly. Never do this outside a test! +class TestUserRedirectView: + def test_get_redirect_url(self, user: User, rf: RequestFactory): 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 + request = rf.get("/fake-url") + request.user = user + view.request = request - # Expect: '/users/testuser/', as that is the default username for - # self.make_user() - self.assertEqual( - view.get_redirect_url(), - '/users/testuser/' - ) - - -class TestUserUpdateView(BaseUserTestCase): - - 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_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/' - ) - - 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}/" + + +class TestUserDetailView: + def test_authenticated(self, user: User, rf: RequestFactory): + request = rf.get("/fake-url/") + request.user = UserFactory() + + response = user_detail_view(request, username=user.username) + + assert response.status_code == 200 + + def test_not_authenticated(self, user: User, rf: RequestFactory): + request = rf.get("/fake-url/") + request.user = AnonymousUser() + + response = user_detail_view(request, username=user.username) + login_url = reverse(settings.LOGIN_URL) + + assert isinstance(response, HttpResponseRedirect) + assert response.status_code == 302 + assert response.url == f"{login_url}?next=/fake-url/" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py index d0592ef97e..8c8c7e2ea2 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py @@ -1,36 +1,14 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals +from django.urls import path -from django.conf.urls import url - -from . import views +from {{ cookiecutter.project_slug }}.users.views import ( + user_detail_view, + user_redirect_view, + user_update_view, +) +app_name = "users" urlpatterns = [ - # URL pattern for the UserListView - url( - regex=r'^$', - view=views.UserListView.as_view(), - name='list' - ), - - # URL pattern for the UserRedirectView - url( - regex=r'^~redirect/$', - view=views.UserRedirectView.as_view(), - name='redirect' - ), - - # URL pattern for the UserDetailView - url( - regex=r'^(?P[\w.@+-]+)/$', - view=views.UserDetailView.as_view(), - name='detail' - ), - - # URL pattern for the UserUpdateView - url( - regex=r'^~update/$', - view=views.UserUpdateView.as_view(), - name='update' - ), + path("~redirect/", view=user_redirect_view, name="redirect"), + path("~update/", view=user_update_view, name="update"), + path("/", view=user_detail_view, name="detail"), ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py index 4cfe64a374..baa04a0d4e 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py @@ -1,48 +1,48 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - -from django.core.urlresolvers import reverse -from django.views.generic import DetailView, ListView, RedirectView, UpdateView - +from django.contrib.auth import get_user_model from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.messages.views import SuccessMessageMixin +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView, 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' + model = User + slug_field = "username" + slug_url_kwarg = "username" -class UserRedirectView(LoginRequiredMixin, RedirectView): - permanent = False - - def get_redirect_url(self): - return reverse('users:detail', - kwargs={'username': self.request.user.username}) +user_detail_view = UserDetailView.as_view() -class UserUpdateView(LoginRequiredMixin, UpdateView): - fields = ['name', ] +class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): - # we already imported User in the view code above, remember? model = User + fields = ["name"] + success_message = _("Information successfully updated") - # 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}) + assert ( + self.request.user.is_authenticated + ) # for mypy to know that the user is authenticated + return self.request.user.get_absolute_url() def get_object(self): - # Only get the User record for the user making the request - return User.objects.get(username=self.request.user.username) + return self.request.user -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_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}) + + +user_redirect_view = UserRedirectView.as_view() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py new file mode 100644 index 0000000000..b712d3239d --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/storages.py @@ -0,0 +1,25 @@ +{% if cookiecutter.cloud_provider == 'AWS' -%} +from storages.backends.s3boto3 import S3Boto3Storage + + +class StaticRootS3Boto3Storage(S3Boto3Storage): + location = "static" + default_acl = "public-read" + + +class MediaRootS3Boto3Storage(S3Boto3Storage): + location = "media" + file_overwrite = False +{%- elif cookiecutter.cloud_provider == 'GCP' -%} +from storages.backends.gcloud import GoogleCloudStorage + + +class StaticRootGoogleCloudStorage(GoogleCloudStorage): + location = "static" + default_acl = "publicRead" + + +class MediaRootGoogleCloudStorage(GoogleCloudStorage): + location = "media" + file_overwrite = False +{%- endif %}