Skip to content

Commit cfae5c2

Browse files
Clue88Christopher Liu
andauthored
Add user profile pictures (#140)
* Add profile pic to User model * Add profile pic upload endpoint * Add pillow * Add documentation about pipfile problems * Switch to aws s3 * Prevent uploading non-images * Add tests and move s3 to prod * Add more tests * Simplify image uploader code * Clarify image filepath generator * Add input checking * Add env variables to readme * More tests :/ * ¯\_(ツ)_/¯ * Specify http status code in tests * Add file size check Co-authored-by: Christopher Liu <christopherliu@sorcero.com>
1 parent afbdb82 commit cfae5c2

File tree

15 files changed

+506
-2
lines changed

15 files changed

+506
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ db.sqlite3
2525

2626
# Docker
2727
docker-compose.yml
28+
29+
# Uploaded media files
30+
backend/accounts/mediafiles

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ DATABASE_URL=mysql://USER:PASSWORD@HOST:PORT/NAME
1818
SECRET_KEY=secret
1919
DJANGO_SETTINGS_MODULE=Platform.settings.production
2020
SENTRY_URL=https://pub@sentry.example.com/product
21+
AWS_ACCESS_KEY_ID
22+
AWS_ACCESS_SECRET_ID
23+
AWS_STORAGE_BUCKET_NAME
2124
```
2225

2326
1. Run using docker: `docker run -d pennlabs/platform`

backend/Pipfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ django-email-tools = "*"
3636
twilio = "*"
3737
lxml = "*"
3838
requests = "*"
39+
pillow = "*"
40+
boto3 = "*"
41+
django-storages = "*"
3942

4043
[requires]
4144
python_version = "3"

backend/Pipfile.lock

Lines changed: 224 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/Platform/settings/base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"options.apps.OptionsConfig",
7474
"accounts.apps.AccountsConfig",
7575
"identity.apps.IdentityConfig",
76+
"storages",
7677
]
7778

7879

@@ -203,3 +204,7 @@
203204
"FROM_EMAIL": "Penn Labs <accounts@pennlabs.org>",
204205
"TEMPLATE_DIRECTORY": os.path.join(BASE_DIR, "Platform", "templates", "emails"),
205206
}
207+
208+
# Media Upload Settings
209+
MEDIA_ROOT = os.path.join(BASE_DIR, "accounts", "mediafiles")
210+
MEDIA_URL = "/media/"

backend/Platform/settings/production.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,10 @@
4343
EMAIL_USE_TLS = True
4444

4545
IS_DEV_LOGIN = os.environ.get("DEV_LOGIN", "False") in ["True", "TRUE", "true"]
46+
47+
# AWS S3
48+
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
49+
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
50+
AWS_ACCESS_SECRET_ID = os.getenv("AWS_SECRET_ACCESS_KEY")
51+
AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME")
52+
AWS_QUERYSTRING_AUTH = False

backend/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## Runbook
2+
This section is to collect thoughts/learnings from the codebase that have been hard-won, so we don't lose a record of it if and when the information proves useful again
3+
4+
### Dependency Installation on MacOS Ventura
5+
There's a couple of issues at play here. The first is that the current `Pipfile` and `Pipfile.lock` are out of sync, most significantly with regard to Django--the `Pipfile` would generate a lockfile with Django 4, however, we are using Django 3 in the current lockfile and our tests. For now, use the current lockfile and use `--keep-outdated` when adding a new package with `pipenv`.
6+
7+
However, there seems to be an issue with the way that lockfiles are interacting with MacOS Ventura. In that case, we have to ignore the lockfile entirely, using `pipenv install --dev --python 3.8 --skip-lock` to install dependenices. From what I can tell, this is the only way to actually install the dependencies without it failing, but it means that some tests might fail due to issues with Django 4.
8+
9+
The long term solution is to make sure that the `Pipfile` and lockfile are in sync, either by forcing Django 3 or by updating our code to support Django 4.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 4.1.3 on 2022-11-10 02:05
2+
3+
import accounts.models
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("accounts", "0003_auto_20210918_2041"),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name="user",
16+
name="profile_pic",
17+
field=models.ImageField(
18+
blank=True, null=True, upload_to=accounts.models.get_user_image_filepath
19+
),
20+
),
21+
]

backend/accounts/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import uuid
23

34
from django.contrib.auth import get_user_model
@@ -9,6 +10,15 @@
910
from phonenumber_field.modelfields import PhoneNumberField
1011

1112

13+
def get_user_image_filepath(instance, fname):
14+
"""
15+
Returns the provided User's profile picture image path. Maintains the
16+
file extension of the provided image file if it exists.
17+
"""
18+
suffix = "." + fname.rsplit(".", 1)[-1] if "." in fname else ""
19+
return os.path.join("images", f"{instance.username}{suffix}")
20+
21+
1222
class School(models.Model):
1323
"""
1424
Represents a school at the University of Pennsylvania.
@@ -53,6 +63,9 @@ class User(AbstractUser):
5363
pennid = models.IntegerField(primary_key=True)
5464
uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
5565
preferred_name = models.CharField(max_length=225, blank=True)
66+
profile_pic = models.ImageField(
67+
upload_to=get_user_image_filepath, blank=True, null=True
68+
)
5669

5770
VERIFICATION_EXPIRATION_MINUTES = 10
5871

backend/accounts/serializers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ class UserSerializer(serializers.ModelSerializer):
174174
student = StudentSerializer()
175175
emails = EmailSerializer(many=True)
176176
phone_numbers = PhoneNumberSerializer(many=True)
177+
profile_pic = serializers.ImageField(required=False, allow_empty_file=True)
177178

178179
class Meta:
179180
model = User
@@ -189,6 +190,7 @@ class Meta:
189190
"student",
190191
"phone_numbers",
191192
"emails",
193+
"profile_pic",
192194
)
193195

194196
read_only_fields = (

0 commit comments

Comments
 (0)