Repository: cookiecutter/cookiecutter-django
Pull request: Fix docs service and add RTD support
<!-- Thank you for helping us out: your efforts mean a great deal to the project and the community as a whole! -->

Fixes #2733 
Closes #2878 
Closes #2674

Supersedes #2754

## Description

Fix docs service. Added `DATABASE_URL` environment variable inside `conf.py` to force `django.setup()` in both local and production environments to use a SQLite database. Also stays consistent with setting the other entrypoint env var, `CELERY_BROKER_URL`.

Checklist:

- [X] I've made sure that `tests/test_cookiecutter_generation.py` is updated accordingly (especially if adding or updating a template option)
- [X] I've updated the documentation or confirm that my change doesn't require any updates

## Rationale

Fixes doc service and adds doc option for production.

Problem statement:
RTD Support
## Description

ReadTheDocs support

## Rationale

Hosting an open-source website with documentation 

Also somewhat of a bug report because you need a .readthedocs.yml (I think) file in order to pip install the local.txt packages.

## Use case(s) / visualization(s)

Developers for an open-source (or closed-source) website can easily read up on how the website functions.

Hints from issue discussion:
More details on the `.readthedocs.yml` file:

https://docs.readthedocs.io/en/stable/config-file/v2.html

Agree that we need to at least configure the requirement file to use, and probably also the python version. Not sure if we can set environment variables to avoid similar issues as in #2674
~~Hm so long as .envs/.local environment variables are set, we can use the DJANGO_READ_DOT_ENV_FILE environment variable to force Django-environ to read the env files. I think we only need to specify the .django env file. Perhaps we should also leave a comment in the .django env file to specify any new env files in the `conf.py`.~~

~~Concern: in cookiecutter.json, we have this value "keep_local_envs_in_vcs": "y" which means if the user select "n", then the docs wouldn't work.~~

Edit: so the env var `DJANGO_READ_DOT_ENV_FILE` is technically defaulting to False, which means RTD will read the env files. It's just a matter of keeping the local envs in vcs.

Edit 2: seems like you can set env vars: https://docs.readthedocs.io/en/stable/guides/environment-variables.html
@browniebroke I'm currently testing the RTD support for a PR in a few. Primary concern is, in the conf.py file, I have:
```python
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["CELERY_BROKER_URL"] = "redis://redis:6379"
	os.environ["USE_DOCKER"] = "no"
	master_doc = "_source/index"
else:
	sys.path.insert(0, os.path.abspath("/app"))
os.environ.setdefault("DATABASE_URL", "")

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
django.setup()
```
The build is passing... but there's no documentation :P If you can spot a reason why, I'd love to hear it and try it out. The previous builds failed but adding the master_doc worked out. Unfortunately, that means none of the `.rst` files are actually documented........... (Note: I'll edit the os.environ to use setdefault instead; was just quickly coding)

In the meantime... I'm curious, for celery support, in the `settings/base.py` files, can we set the default to `REDIS_URL`?

Commit message: Move readthedocs config in the right folder

Patch to review:
diff --git a/docs/document.rst b/docs/document.rst
index 15a5396e19..a6e6b72f4e 100644
--- a/docs/document.rst
+++ b/docs/document.rst
@@ -15,15 +15,30 @@ If you set up your project to `develop locally with docker`_, run the following
 
 Navigate to port 7000 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/_sources/conf.py`` to avoid a dependency on PostgreSQL.
+
 Generate API documentation
 ----------------------------
 
 Edit the ``docs/_source`` files and project application docstrings to create your documentation.
 
-Sphinx can automatically include class and function signatures and docstrings in generated 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:7000/
 .. _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/{{cookiecutter.project_slug}}/.readthedocs.yml b/{{cookiecutter.project_slug}}/.readthedocs.yml
new file mode 100644
index 0000000000..3f316fdab2
--- /dev/null
+++ b/{{cookiecutter.project_slug}}/.readthedocs.yml
@@ -0,0 +1,9 @@
+version: 2
+
+sphinx:
+  configuration: docs/_source/conf.py
+
+python:
+  version: 3.8
+  install:
+    - requirements: requirements/local.txt
diff --git a/{{cookiecutter.project_slug}}/compose/local/docs/Dockerfile b/{{cookiecutter.project_slug}}/compose/local/docs/Dockerfile
index 7736777b30..315fdd405e 100644
--- a/{{cookiecutter.project_slug}}/compose/local/docs/Dockerfile
+++ b/{{cookiecutter.project_slug}}/compose/local/docs/Dockerfile
@@ -24,6 +24,8 @@ COPY ./requirements /requirements
 # All imports needed for autodoc.
 RUN pip install -r /requirements/local.txt -r /requirements/production.txt
 
-WORKDIR /docs
+COPY ./compose/local/docs/start /start-docs
+RUN sed -i 's/\r$//g' /start-docs
+RUN chmod +x /start-docs
 
-CMD make livehtml
+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}}/docs/conf.py b/{{cookiecutter.project_slug}}/docs/_source/conf.py
similarity index 78%
rename from {{cookiecutter.project_slug}}/docs/conf.py
rename to {{cookiecutter.project_slug}}/docs/_source/conf.py
index 691f351e51..3fc411aa62 100644
--- a/{{cookiecutter.project_slug}}/docs/conf.py
+++ b/{{cookiecutter.project_slug}}/docs/_source/conf.py
@@ -14,12 +14,20 @@
 import sys
 import django
 
-{% if cookiecutter.use_docker == 'y' %}
-sys.path.insert(0, os.path.abspath("/app"))
-os.environ.setdefault("DATABASE_URL", "")
-{% else %}
-sys.path.insert(0, os.path.abspath(".."))
+if os.getenv("READTHEDOCS", default=False) == "True":
+    sys.path.insert(0, os.path.abspath("../.."))
+    os.environ["DJANGO_READ_DOT_ENV_FILE"] = "True"
+    {%- if cookiecutter.use_celery == 'y' %}
+    os.environ["CELERY_BROKER_URL"] = os.getenv("REDIS_URL", "redis://redis:6379")
+    {%- endif %}
+    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"
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
 django.setup()
 
diff --git a/{{cookiecutter.project_slug}}/local.yml b/{{cookiecutter.project_slug}}/local.yml
index a6cbe54307..e285f34984 100644
--- a/{{cookiecutter.project_slug}}/local.yml
+++ b/{{cookiecutter.project_slug}}/local.yml
@@ -51,7 +51,8 @@ services:
       - ./{{ cookiecutter.project_slug }}:/app/{{ cookiecutter.project_slug }}:z
     ports:
       - "7000:7000"
-    
+    command: /start-docs
+
   {%- if cookiecutter.use_mailhog == 'y' %}
 
   mailhog:


Code review comments:
diff_hunk: @@ -14,12 +14,20 @@
 import sys
 import django
 
-{% if cookiecutter.use_docker == 'y' %}
-sys.path.insert(0, os.path.abspath("/app"))
-os.environ.setdefault("DATABASE_URL", "")
-{% else %}
-sys.path.insert(0, os.path.abspath(".."))
+if os.getenv("READTHEDOCS", default=False) == "True":
+    sys.path.insert(0, os.path.abspath("../.."))
+    os.environ["DJANGO_READ_DOT_ENV_FILE"] = "True"
+    {%- if cookiecutter.use_celery == 'y' %}
+    os.environ["CELERY_BROKER_URL"] = os.getenv("REDIS_URL", "redis://redis:6379")
path: {{cookiecutter.project_slug}}/docs/_source/conf.py
text: @user1:
This is also needed locally when Celery is selected

diff_hunk: @@ -14,12 +14,20 @@
 import sys
 import django
 
-{% if cookiecutter.use_docker == 'y' %}
-sys.path.insert(0, os.path.abspath("/app"))
-os.environ.setdefault("DATABASE_URL", "")
-{% else %}
-sys.path.insert(0, os.path.abspath(".."))
+if os.getenv("READTHEDOCS", default=False) == "True":
+    sys.path.insert(0, os.path.abspath("../.."))
+    os.environ["DJANGO_READ_DOT_ENV_FILE"] = "True"
+    {%- if cookiecutter.use_celery == 'y' %}
+    os.environ["CELERY_BROKER_URL"] = os.getenv("REDIS_URL", "redis://redis:6379")
+    {%- endif %}
+    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("../.."))
path: {{cookiecutter.project_slug}}/docs/_source/conf.py
text: @user1:
I did some tests and it works on RTD, but my earlier suggestion to move this file from `docs` to inside of `docs/_source` breals the local setup (at least when using Docker)...

I initially suggested this move to have `conf.py` next to the index file, the point was to fix the homepage in the right place for RTD. 

Since it breaks something else, we could do the other alternative, to move the source files one level up, inside `docs/`.

@author:
Ah sorry I didn't test this out locally. Completely my fault for not catching that.

Having everything inside the `/_source` made everything organizationally better; I'm just wondering if there's any other option... although, I think I'd have to agree that moving everything back to `docs/` may just be the only solution. 

@user1:
We do this in the template documentation and it's not too bad IMO. It's probably possible to make it work inside or `_source` but it feels easier being outside and I'm not sure I'm willing to invest more time in this at this point.