# django-workflows

Reusable Django service helpers for common account flows:

- Registration and verification-code email flow
- Forgot-password code flow
- Reset-code validation and lockout handling
- Mailgun email utility with recipient validation

## Installation

Install from PyPI:

```bash
pip install BmorricalDjangoWorkflows==1.2.3
```

In `requirements.txt`, pin it directly:

```txt
BmorricalDjangoWorkflows==1.2.3
```

Note: distribution name is `BmorricalDjangoWorkflows`, while imports remain under `django_workflows`.

## Host Project Requirements

This package expects the host Django project to provide:

- A Django user model available through `django.contrib.auth.get_user_model()`
- A user meta model referenced by `WORKFLOWS["USER_META_MODEL"]`
- A user manager `create_user(...)` method that accepts `username`, because this package explicitly sets `username=email`

The user meta model should include at least:

- `user` relation
- `verify_code` field
- `verify_time` field
- `attempts` field

Optional (used if present):

- `force_password_reset`

## Django Settings

Add a `WORKFLOWS` dict in your Django settings.

```python
WORKFLOWS = {
	# Required in most projects unless your model path matches the default.
	"USER_META_MODEL": "users.models_user_meta.UserMeta",

	# Optional settings with defaults shown.
	"COMPANY_NAME": "My Company",
	"ENABLE_MAILGUN": False,
	"RESET_CODE_TTL_MINUTES": 10,
	"MAX_VERIFY_ATTEMPTS": 3,
}
```

Notes:

- `USER_META_MODEL` must be a dotted import path such as `myapp.models.UserMeta`.
- If `ENABLE_MAILGUN` is true, configure the Mailgun environment variables used by the mail service.

## Mailgun Environment Variables

When mail sending is enabled, set:

- `MAILGUN_COMPANY_NAME`
- `MAILGUN_API_KEY`
- `MAILGUN_DOMAIN`
- `ENABLE_MAILGUN=true`

Optional:

- `MAILGUN_BCC_RECIPIENTS` as a comma-separated list

Example:

```bash
MAILGUN_BCC_RECIPIENTS="audit@example.com,ops@example.com"
```

If `MAILGUN_BCC_RECIPIENTS` is not set, no static BCC recipients are added.

## Example Usage

```python
from django_workflows.users.services.auth_flow import (
	register_user_and_send_verification_email,
	send_forgot_password_code,
	verify_reset_code,
	change_password,
)

result = register_user_and_send_verification_email(
	email="person@example.com",
	first_name="First",
	last_name="Last",
	password="example-password",
)

forgot = send_forgot_password_code("person@example.com")

verify = verify_reset_code(email="person@example.com", code="AB12CD")

changed = change_password(
	email="person@example.com",
	password="new-password",
	password_verify="new-password",
)
```

### Direct Mailgun Usage

Attachment tuple shape:

- `(filename, file_bytes_or_file_object, mimetype)`

Example attachment entry:

- `("report.pdf", pdf_bytes, "application/pdf")`

```python
from django_workflows.services.mailgun import send_mailgun_email

response = send_mailgun_email(
	to=["primary@example.com", "secondary@example.com"],
	subject="Welcome",
	html="<p>Thanks for joining.</p>",
	cc="manager@example.com",
	bcc=["audit@example.com"],
	attachments=[("report.pdf", pdf_bytes, "application/pdf")],
)

# Optional: explicit runtime override for enablement
send_mailgun_email(
	to="user@example.com",
	subject="Dry run",
	html="<p>This will not send.</p>",
	enabled=False,
)
```

## Local Development

Run tests:

```bash
make test
```

Run tests with coverage:

```bash
make test-coverage
```

CI runs tests on push and pull requests using [`.github/workflows/tests.yml`](.github/workflows/tests.yml).

## Troubleshooting

### ImproperlyConfigured for USER_META_MODEL

Error example:

```text
WORKFLOWS['USER_META_MODEL'] must be a dotted path like 'myapp.models.UserMeta'.
```

Fix:

- Set `WORKFLOWS["USER_META_MODEL"]` to a valid dotted import path.
- Verify the target model is importable by Django at runtime.

### UserMeta field errors

If you see attribute errors around verification state, confirm your user meta model provides:

- `verify_code`
- `verify_time`
- `attempts`

### Mailgun not sending

Check:

- `ENABLE_MAILGUN=true`
- `MAILGUN_API_KEY`
- `MAILGUN_DOMAIN`
- `MAILGUN_COMPANY_NAME`

### No static BCC recipients applied

This is expected unless you set `MAILGUN_BCC_RECIPIENTS`.

## Release

Create a release tag:

```bash
./release.sh 0.1.0
```

After release, update the package version in your consuming application's dependency files.

Tag pushes like `v1.2.3` trigger [`.github/workflows/publish.yml`](.github/workflows/publish.yml), which publishes to PyPI.

## Publishing To PyPI (Maintainer Steps)

1. Check whether the target package name is available.

```bash
curl -I https://pypi.org/pypi/BmorricalDjangoWorkflows/json
```

If this returns 404, the name is usually available.

2. Create a PyPI account: https://pypi.org/account/register/

3. Create a PyPI API token: PyPI Account -> Account settings -> API tokens.

4. Add repository secret in GitHub:

- Name: `PYPI_API_TOKEN`
- Value: your PyPI token (starts with `pypi-`)

5. Bump and tag a release:

```bash
./release.sh 1.2.3
```

6. Confirm the workflow run succeeded and the package appears on PyPI.

7. Update consuming applications:

```txt
BmorricalDjangoWorkflows==1.2.3
```

If upload fails because the name is already taken, change `name=` in [setup.py](setup.py), bump version, and release again.
