Repository: cookiecutter/cookiecutter-django

Issue:
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.


Add entrypoint for docs to not error on build
[//]: # (Thank you for helping us out: your efforts mean great deal to the project and the community as a whole!)

[//]: # (Before you proceed:)

[//]: # (1. Make sure to add yourself to `CONTRIBUTORS.rst` through this PR provided you're contributing here for the first time)
[//]: # (2. Don't forget to update the `docs/` presuming others would benefit from a concise description of whatever that you're proposing)


## Description

[//]: # (What's it you're proposing?)

Docker building the docs error unless you add entry point to configure the database.

## Rationale

[//]: # (Why does the project need that?)

You see nothing unless you can build the docs... so you need to make sure Django is actually setup.


## Use case(s) / visualization(s)

[//]: # ("Better to see something once than to hear about it a thousand times.")

To see the docs.

~~Note: I wonder if we can do what we do for celery which is using anchors. The command: sphinx-autobuild -b html -H 0.0.0.0 -p 7000 --watch /app -c . ./_source ./_build/html~~ Still would need production.txt...

Fixes #2712 


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`?
Sure for consistency, it'd make sense. Considering the celery commands are also just one-liners, having the a start file would make more sense
Just tried it and I'm getting:

```
> docker-compose -f local.yml up docs
...
docs        | Waiting for PostgreSQL to become available...
docs        | Waiting for PostgreSQL to become available...
docs        | Waiting for PostgreSQL to become available...
docs        | Waiting for PostgreSQL to become available...
docs        | Waiting for PostgreSQL to become available...
docs        | Waiting for PostgreSQL to become available...
```

It works if I add this to `local.yml`:

```diff
  docs:
    image: my_awesome_project_local_docs
    container_name: docs
+   depends_on:
+     - postgres
    build:
      context: .
```

I think I need to get back to your original comment though, I don't understand why we need the DB to build the docs.
I believe in the docs, we're running `django.setup()` which means we're trying to connect to the database.

I'll take a look at why we need depends_on. Logically, it makes sense, but Postgres eventually comes up anyways right? That's kinda weird.
So we need `depends_on` to run the command to only start the docs container: `docker-compose -f local.yml up docs`. If you started your project before generating the docs, the DB might already be up, in which case no issue. 

If the `docs` container tries to connect to the DB, we need this `depends_on` to declare this dependency to docker compose. I think we can add it to fix this bug, and maybe see if we can cut this dependency separately.
Makes sense. Never thought about it like that. I've committed the necessary change.
I've just tried by generating a project on the master branch, and I'm able to get the docs working. There is no error preventing it from running, but there is this warning:

```
docs        | /usr/local/lib/python3.8/site-packages/environ/environ.py:437: UserWarning: Engine not recognized from url: {'NAME': '', 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', 'ENGINE': ''}
docs        |   warnings.warn("Engine not recognized from url: {0}".format(config))
```

Is that what you're trying to fix with this change?

To remove the dependency on the `postgres` container for the docs, maybe it would be simpler to set `DATABASE_URL` to a SQLite file for the `docs` container? I think we could do that by changing the `local.yml` as follows for instance:

```diff
  docs:
    image: my_awesome_project_local_docs
    container_name: docs
    build:
      context: .
      dockerfile: ./compose/local/docs/Dockerfile
    env_file:
      - ./.envs/.local/.django
    volumes:
      - ./docs:/docs
      - ./config:/app/config
      - ./my_awesome_project:/app/my_awesome_project
    ports:
      - "7000:7000"
+   environment:
+     DATABASE_URL: "sqlite:///database.db"
```

In which case we don't need to add the `entrypoint` or the postgres env file the `Dockerfile`.

Let me know if you were talking about another error, either I missed it or I'm able to reproduce.
Sure that'd work! The problem was with the DATABASE_URL and other variables set in the entrypoint plus Django was trying to connect to a database. Would people have to continuously add their environment variables to the docs service?

Either way works, but I feel like setting the entrypoint via the Dockerfile lets devs to forget about setting env vars from entrypoint in local.yml, especially for multiple contributors. I'm not... entirely sure what the best option is :P although setting the database to sqlite is clever and would work. Would it create a new file though...? Hopefully not since we're not creating tables in the docs start file.
> Would people have to continuously add their environment variables to the docs service?

That's a good point, not sure about that. My assumption was that the `entrypoint` is not expected to change much, but it depends what you'd do after generating the template. If we set good defaults, we shouldn't need to override a lot, but while writing this I noticed that we also have `CELERY_BROKER_URL` in there...

> Would it create a new file though

It probably would, but only inside the container, which would be destroyed when the command has finished. I don't know if that's the case with the snippet I suggested, but at least that's the idea.

The main potential problem I foresee with the current approach is that in some environments, it might not be easy/cheap to get hold of a database just to generate the docs, but it might be helpful to use the `Dockerfile`. With the use of `entrypoint`, one needs to set 4 environment variables, and is tied to PostgreSQL. 

On the other hand, it makes the `docs` container a more alike the `django` one.
I think your approach is more viable with the explicit setting of env vars in the compose file, especially when you say:

> entrypoint is not expected to change much

I think I've only edited entrypoint a couple of times for multi-db, so point taken. I agree that the dependency on other services would drag some projects. I'll test if SQLite is created and continues existing post `compose down` when I get the chance. In that case, I'll close this PR and start a new one with the new suggestions. (remove entrypoint; add env vars from entrypoint to docs service environment; change DATABASE_URL to a sqlite url; remove dependencies that docs service depends on).

Perhaps a couple of comments in the compose file or entrypoint file saying something like `Docs service requires the file's env vars to be in local.yml`? Or perhaps somewhere in the cookie cutter django docs, specify this necessity?

Resolving patch:
diff --git a/{{cookiecutter.project_slug}}/docs/conf.py b/{{cookiecutter.project_slug}}/docs/conf.py
--- a/{{cookiecutter.project_slug}}/docs/conf.py
+++ b/{{cookiecutter.project_slug}}/docs/conf.py
@@ -14,11 +14,19 @@
 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"
+    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()


Test patch: