# Contributing

Thanks for your interest in the project! Here's some (hopefully) helpful tips on how to get started.

## Project layout

Your project directory should look something like this. Some entries have been removed for easier reading

```
.
└── tordonate
    ├── apps.py
    ├── asgi.py
    ├── civicrm
    │   ├── __init__.py
    │   ├── models.py
    │   └── repository.py
    ├── containers.py
    ├── forms.py
    ├── __init__.py
    ├── middleware.py
    ├── paypal
    │   ├── controller.py
    │   ├── __init__.py
    │   ├── urls.py
    │   └── views.py
    ├── settings.py
    ├── stripe
    │   ├── controller.py
    │   ├── forms.py
    │   ├── __init__.py
    │   ├── urls.py
    │   └── views.py
    ├── templates
    │   ├── cryptocurrency.html.jinja
    │   ├── donate_form.html.jinja
    │   ├── donate_thank_you.html.jinja
    │   └── layout.html
    ├── tests
    │   ├── __init__.py
    │   ├── test_crypto.py
    │   ├── test_donor_form.py
    │   └── test_stripe.py
    ├── urls.py
    ├── utils.py
    ├── views.py
    └── wsgi.py
```

This is a fairly standard Django layout, with a few interesting pieces here and there. Let's go over the important ones

- `tordonate/`: this is where the donate Django application lives. This is probably the only you'll need to edit files
  - `tordonate/civicrm/`: provides an interface for interacting with the CiviCRM REST API. Currently incomplete
  - `tordonate/containers.py`: dependency injection configuration. Provides an abstraction for injecting paypal/stripe/civicrm controllers to views and tests
  - `tordonate/forms.py`: form validation logic for the donation info form
  - `tordonate/middleware.py`: injects the civicrm controller into templates. You can ignore this, just know that it's there and that the civi controller can be used in templates
  - `tordonate/paypal/`: paypal views and business logic. Incomplete.
  - `tordonate/settings.py`: Django settings file. You can probably ignore this, but you'll need to edit it to add global configuration values.
  - `tordonate/stripe/`: stripe views and business logic. Incomplete.
  - `tordonate/templates/`: All the templates used by tordonate views. See below for more information.
  - `tordonate/tests/`: unit tests. every feature added should have a test written if possible. Good code coverage makes for better maintainability in the long run
  - `tordonate/urls.py`: this is how django maps URLs to view functions. If you add a new view, you need to give it a route here
  - `tordonate/utils.py`: utilities useful in multiple places across the project. There's some dragons here, particularly the "with sync" stuff (see below)
  - `tordonate/views.py`: main application views. If you want to add a new endpoint/page, you'll want to edit this file
  - `tordonate/asgi.py` and `tordonate/wsgi.py`: don't worry about these at all, you'll never need to edit them.

## With Sync

If you go looking in the `tordonate/utils.py` file, you may be confused by all the "with sync" functions and classes. These provide a very simple method of automatically generating safe synchronous wrappers for async methods. It's mainly used in `tordonate/civicrm/repository.py`. It uses some pretty complex python magic that can be really hard to understand. Fortunately, you don't need to understand it to use it, it's designed with ease-of-use in mind.

Here's a quote from the `WithSyncMeta` docstring:

> Add synchronous versions of `@with_sync` methods to a class.
>
> A method must be decorated with `with_async` in order for a sync method to be generated.
> Generated sync methods have the name `{async_method_name}_sync`. Example:
>
> ```
> class Foo(metaclass=WithSyncMeta):
>     @with_sync
>     async def bar(self):
>         await baz()
> ```
>
> Will be transformed into the equivalent of:
>
> ```
> class Foo:
>     async def bar(self):
>         await baz()
>
>     def bar_sync(self):
>         asgiref.sync.async_to_sync(baz)()
> ```

If you do decide to venture in, start by learning about [metaobject protocols][], and then read about [python's metaclasses][]. Don't worry if the concept doesn't make sense to you. It can be confusing, and difficult to wrap your head around.

[metaobject protocols]: <https://www.adamtornhill.com/reviews/amop.htm>
[python's metaclasses]: <https://docs.python.org/3/reference/datamodel.html#metaclasses>

## Testing, Linting, and Type Checking (oh my!)

To ensure that the code in this project works and can be easily maintained, our CI runs a series of checks any time an MR is opened, or code is pushed to the main branch of the repository.

CI can take a few minutes to run these checks, so you can run them locally for quicker feedback.

Tip: Run `poetry shell` before running these commands, or make sure to prefix all of them with `poetry run` (like `poetry run flake8`)

- unit tests: These run tests for the code to ensure that new contributions work the way they should and to make sure old code doesn't break.

```
coverage run --source='.' manage.py test && coverage report -m
```

- linting: Linting makes sure that the code follows a consistent, readable style

```
flake8
```

- type checking: We use static type checking in several places to catch as many errors as we can before running tests or deploying. Because mypy and django don't work very well together, we don't use mypy's `--strict` flag, and we disable type checking in a few places.

```
mypy -p tordonate
```
