Skip to content

Commit

Permalink
Added support for AWS S3
Browse files Browse the repository at this point in the history
  • Loading branch information
TreyWW committed Feb 15, 2024
1 parent 71f21d5 commit bde1632
Show file tree
Hide file tree
Showing 10 changed files with 30,172 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 5.0.2 on 2024-02-14 19:26

from django.db import migrations, models

import settings.settings


class Migration(migrations.Migration):

dependencies = [
("backend", "0015_alter_notification_user_alter_team_name"),
]

operations = [
migrations.AlterField(
model_name="invoice",
name="logo",
field=models.ImageField(
blank=True,
null=True,
storage=settings.settings.CustomPrivateMediaStorage(),
upload_to="invoice_logos",
),
),
migrations.AlterField(
model_name="receipt",
name="image",
field=models.ImageField(
storage=settings.settings.CustomPrivateMediaStorage(),
upload_to="receipts",
),
),
migrations.AlterField(
model_name="usersettings",
name="profile_picture",
field=models.ImageField(
blank=True,
null=True,
storage=settings.settings.CustomPublicMediaStorage(),
upload_to="profile_pictures/",
),
),
]
7 changes: 4 additions & 3 deletions backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class UserSettings(models.Model):
choices=[(code, info["name"]) for code, info in CURRENCIES.items()],
)
profile_picture = models.ImageField(
upload_to="profile_pictures/", blank=True, null=True
upload_to="profile_pictures/", storage=settings.CustomPublicMediaStorage(), blank=True, null=True
)

@property
Expand Down Expand Up @@ -148,7 +148,7 @@ class Receipt(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
organization = models.ForeignKey(Team, on_delete=models.CASCADE, null=True)
name = models.CharField(max_length=100)
image = models.ImageField(upload_to="receipts")
image = models.ImageField(upload_to="receipts", storage=settings.CustomPrivateMediaStorage())
total_price = models.FloatField(null=True, blank=True)
date = models.DateField(null=True, blank=True)
date_uploaded = models.DateTimeField(auto_now_add=True)
Expand Down Expand Up @@ -254,7 +254,8 @@ class Invoice(models.Model):
reference = models.CharField(max_length=100, blank=True, null=True)
invoice_number = models.CharField(max_length=100, blank=True, null=True)
vat_number = models.CharField(max_length=100, blank=True, null=True)
logo = models.ImageField(upload_to="invoice_logos", blank=True, null=True)
logo = models.ImageField(upload_to="invoice_logos", storage=settings.CustomPrivateMediaStorage(), blank=True,
null=True)
notes = models.TextField(blank=True, null=True)

payment_status = models.CharField(
Expand Down
5 changes: 5 additions & 0 deletions backend/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,8 @@ def user_account_create_make_usersettings(sender, instance, created, **kwargs):

if not users_settings:
UserSettings.objects.create(user=instance)


@receiver(post_delete, sender=Receipt)
def delete_receipt_image_on_delete(sender, instance: Receipt, **kwargs):
instance.image.delete(False)
12 changes: 11 additions & 1 deletion docs/getting-setup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,14 @@ Welcome to MyFinances, this guide will help you get setup and running the app fo
- [Setup with PyCharm PRO](getting-setup/pycharm/fork)
- [Setup with anything else](getting-setup/other-environments/)

Then you can setup a local database. [View our guide](getting-setup/databases/)
### Setting up a database

Then you can setup a local database. [View our guide](getting-setup/databases/)

### Setting up AWS

We support many AWS Services, primarily S3 and Cloudfront

#### S3 for media

These are files that users upload. [View our Guide]()
3 changes: 3 additions & 0 deletions docs/getting-setup/_sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
- [Social Login Setup](getting-setup/social-logins?id=social-login)
* [Github](getting-setup/social-logins?id=set-up-social-login-with-github)
* [Google](getting-setup/social-logins?id=set-up-social-login-with-google)
- [Using AWS](getting-setup/aws)
* [S3 For Media storage (uploaded files from users)](getting-setup/aws?id=)
* [Github](getting-setup/aws?id=)
96 changes: 96 additions & 0 deletions docs/getting-setup/aws.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
## Using AWS with MyFinances

You can use a few of the AWS Services with our project. Some key use cases are [S3]() and [CloudFront CDN]()


> More information on how to set this up will come soon. This is just a prototype while we test our staging environment
### Setting up Static CDN with AWS

#### Variables

AWS_STATIC_ENABLED: (True/False) whether static files should be served through AWS
Alternative to AWS_STATIC_ENABLED is setting "STATIC_CDN_TYPE" equal to "AWS"

AWS_STATIC_LOCATION: The bucket path for static files. E.g. "bucket.com/`static`/main.js". So in this case `static` would be the
location.

AWS_STATIC_BUCKET_NAME: [REQUIRED] The bucket name for static files. E.g. "https://`myfinances`.s3.eu-west-2.amazonaws.com"
`myfinances`
would be the bucket name

AWS_STATIC_CUSTOM_DOMAIN: This would be the Cloudfront CDN url. For example `https://dxguxx2xxxx7x.cloudfront.net`. This can
also be an "Alternate domain name" that's set on Cloudfront.

AWS_STATIC_REGION_NAME: The region which your S3 bucket is hosted in. E.g. `eu-west-2` is london.

### Setting up Public Media with AWS

Public Media is media that users upload such as profile pictures

#### Variables

AWS_MEDIA_PUBLIC_ENABLED: (True/False) whether static files should be served through AWS

AWS_MEDIA_PUBLIC_LOCATION: The bucket path for public media files. E.g. "bucket.
com/`myfinances/media/public`/profile_pic.png".
So in this case `myfinances/media/public`would be the location.

AWS_MEDIA_PUBLIC_BUCKET_NAME: [REQUIRED] The bucket name for public media files. E.g. "https://`myfinances`
.s3.eu-west-2.amazonaws.com"
`myfinances` would be the bucket name

AWS_MEDIA_PUBLIC_FILE_OVERWRITE: (True/False) Whether files should be allowed to be overridden. It's recommended to have this
turned off.

AWS_MEDIA_PUBLIC_CUSTOM_DOMAIN: This would be the Cloudfront CDN url. For example `https://dxguxx2xxxx7x.cloudfront.net`. This can
also be an "Alternate domain name" that's set on Cloudfront.

AWS_MEDIA_PUBLIC_ACCESS_KEY_ID: An IAM user security access key ID
AWS_MEDIA_PUBLIC_ACCESS_KEY: An IAM user security access key secret

### Setting up Private Media with AWS

Private Media is media that users upload such as receipt images.

#### Variables

AWS_MEDIA_PRIVATE_ENABLED: (True/False) whether static files should be served through AWS

AWS_MEDIA_PRIVATE_LOCATION: The bucket path for private media files. E.g. "bucket.
com/`myfinances/media/private`/profile_pic.png".
So in this case `myfinances/media/private`would be the location.

AWS_MEDIA_PRIVATE_BUCKET_NAME: [REQUIRED] The bucket name for private media files. E.g. "https://`myfinances`
.s3.eu-west-2.amazonaws.com"
`myfinances` would be the bucket name

AWS_MEDIA_PRIVATE_FILE_OVERWRITE: (True/False) Whether files should be allowed to be overridden. It's recommended to have this
turned off.

AWS_MEDIA_PRIVATE_REGION_NAME: The region which your S3 bucket is hosted in. E.g. `eu-west-2` is london.

AWS_MEDIA_PRIVATE_CUSTOM_DOMAIN: This would be the Cloudfront CDN url. For example `https://dxguxx2xxxx7x.cloudfront.net`. This
can
also be an "Alternate domain name" that's set on Cloudfront.

AWS_MEDIA_PRIVATE_ACCESS_KEY_ID: An IAM user security access key ID
AWS_MEDIA_PRIVATE_ACCESS_KEY: An IAM user security access key secret

AWS_MEDIA_PRIVATE_CLOUDFRONT_PUBLIC_KEY_ID: A public key ID for cloudfront [view here](https://us-east-1.console.aws.amazon.
com/cloudfront/v4/home/publickey)
AWS_MEDIA_PRIVATE_CLOUDFRONT_PRIVATE_KEY: A BASE 64 ENCODED private key string that matches
AWS_MEDIA_PRIVATE_CLOUDFRONT_PUBLIC_KEY_ID. May
start with something like `-----BEGIN RSA PRIVATE KEY-----` or `-----BEGIN PRIVATE KEY-----`.

The easiest way to do this is to open a python terminal `$ py` and type:

```python
import base64

temp = base64.b64encode(b"""-----BEGIN RSA PRIVATE KEY-----KEY-----END RSA PRIVATE KEY-----""")
print(temp)
```

!> If you get an `Could not deserialize key data` ValueError, this is because the PRIVATE_KEY is formatted incorrectly. Python
needs the key WITH new lines! This is the most annoying bug to debug.
29,875 changes: 29,872 additions & 3 deletions frontend/static/js/bundle.js

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions settings/aws_s3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django.utils.deconstruct import deconstructible
from storages.backends.s3boto3 import S3Boto3Storage

from settings.settings import STATICFILES_LOCATION, AWS_CDN_S3_CUSTOM_DOMAIN, AWS_STORAGE_BUCKET_NAME


@deconstructible
class StaticStorage(S3Boto3Storage):
location = STATICFILES_LOCATION

def __init__(self, *args, **kwargs):
kwargs["custom_domain"] = AWS_CDN_S3_CUSTOM_DOMAIN

super(StaticStorage, self).__init__(*args, **kwargs)


@deconstructible
class MediaStorage(S3Boto3Storage):
def __init__(self, *args, **kwargs):
kwargs["bucket"] = AWS_STORAGE_BUCKET_NAME

super(MediaStorage, self).__init__(*args, **kwargs)
21 changes: 21 additions & 0 deletions settings/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import os

import environ

### NEEDS REFACTOR

env = environ.Env(DEBUG=(bool, False))
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
environ.Env.read_env(os.path.join(BASE_DIR, ".env"))
env = environ.Env()
environ.Env.read_env()


def get_var(key, default=None, required=False):
value = os.environ.get(key, default=default)

if required and not value:
raise ValueError(f"{key} is required")
if not default and not value: # So methods like .lower() don't error
value = ""
return value
Loading

0 comments on commit bde1632

Please sign in to comment.