This commit is contained in:
Joshua Hsueh 2021-01-30 23:52:19 -05:00
commit 56fb861943
10 changed files with 336 additions and 24 deletions

View File

@ -11,6 +11,11 @@ djangorestframework = "*"
python-dotenv = "*"
psycopg2 = "*"
robin-stocks = "*"
django-cors-headers = "*"
pillow = "*"
djangorestframework-jwt = "*"
gunicorn = "*"
whitenoise = "*"
[requires]
python_version = "3.8"

79
backend/Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "512cec129202578daefb819a6bf109831ef31164fad71ade999b9699e8c59402"
"sha256": "cd366320b75b1430efc97f93ca86418b4431990547ddea8eeb3a7f7f35343bc6"
},
"pipfile-spec": 6,
"requires": {
@ -47,6 +47,14 @@
"index": "pypi",
"version": "==3.1.5"
},
"django-cors-headers": {
"hashes": [
"sha256:1ac2b1213de75a251e2ba04448da15f99bcfcbe164288ae6b5ff929dc49b372f",
"sha256:96069c4aaacace786a34ee7894ff680780ec2644e4268b31181044410fecd12e"
],
"index": "pypi",
"version": "==3.7.0"
},
"djangorestframework": {
"hashes": [
"sha256:0209bafcb7b5010fdfec784034f059d512256424de2a0f084cb82b096d6dd6a7",
@ -55,6 +63,22 @@
"index": "pypi",
"version": "==3.12.2"
},
"djangorestframework-jwt": {
"hashes": [
"sha256:5efe33032f3a4518a300dc51a51c92145ad95fb6f4b272e5aa24701db67936a7",
"sha256:ab15dfbbe535eede8e2e53adaf52ef0cf018ee27dbfad10cbc4cbec2ab63d38c"
],
"index": "pypi",
"version": "==1.11.0"
},
"gunicorn": {
"hashes": [
"sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626",
"sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"
],
"index": "pypi",
"version": "==20.0.4"
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
@ -63,6 +87,44 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
},
"pillow": {
"hashes": [
"sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6",
"sha256:1d208e670abfeb41b6143537a681299ef86e92d2a3dac299d3cd6830d5c7bded",
"sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865",
"sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174",
"sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032",
"sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a",
"sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e",
"sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378",
"sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17",
"sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c",
"sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913",
"sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7",
"sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0",
"sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820",
"sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba",
"sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2",
"sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b",
"sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9",
"sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234",
"sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d",
"sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5",
"sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206",
"sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9",
"sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8",
"sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59",
"sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d",
"sha256:cf6e33d92b1526190a1de904df21663c46a456758c0424e4f947ae9aa6088bf7",
"sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a",
"sha256:d673c4990acd016229a5c1c4ee8a9e6d8f481b27ade5fc3d95938697fa443ce0",
"sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b",
"sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d",
"sha256:f50e7a98b0453f39000619d845be8b06e611e56ee6e8186f7f60c3b1e2f0feae"
],
"index": "pypi",
"version": "==8.1.0"
},
"psycopg2": {
"hashes": [
"sha256:00195b5f6832dbf2876b8bf77f12bdce648224c89c880719c745b90515233301",
@ -84,6 +146,13 @@
"index": "pypi",
"version": "==2.8.6"
},
"pyjwt": {
"hashes": [
"sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e",
"sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"
],
"version": "==1.7.1"
},
"pyotp": {
"hashes": [
"sha256:2a54d393aff3a244b566d78d597c9cb42e91b3b12f3169cec89d9dfff1c9c5bc",
@ -137,6 +206,14 @@
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.3"
},
"whitenoise": {
"hashes": [
"sha256:05ce0be39ad85740a78750c86a93485c40f08ad8c62a6006de0233765996e5c7",
"sha256:05d00198c777028d72d8b0bbd234db605ef6d60e9410125124002518a48e515d"
],
"index": "pypi",
"version": "==5.2.0"
}
},
"develop": {}

View File

@ -0,0 +1,59 @@
# Generated by Django 3.1.5 on 2021-01-30 20:48
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Charity',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ein', models.CharField(blank=True, max_length=50, null=True)),
('name', models.CharField(blank=True, max_length=50, null=True)),
('sub_name', models.CharField(blank=True, max_length=50, null=True)),
('city', models.CharField(blank=True, max_length=20, null=True)),
('state', models.CharField(blank=True, max_length=2, null=True)),
],
options={
'verbose_name_plural': 'Charities',
},
),
migrations.CreateModel(
name='Profile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nickname', models.CharField(blank=True, max_length=20, null=True)),
('profile_pic', models.ImageField(default='default.jpg', upload_to='profile_pics')),
('percentage', models.DecimalField(decimal_places=2, max_digits=3)),
('charity', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='api.charity')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name_plural': 'Profiles',
},
),
migrations.CreateModel(
name='Stock',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ticker', models.CharField(blank=True, max_length=5, null=True)),
('buy_price', models.DecimalField(decimal_places=2, max_digits=9)),
('quantity', models.DecimalField(decimal_places=2, max_digits=9)),
('uuid', models.UUIDField(editable=False, unique=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stock', to='api.profile')),
],
options={
'verbose_name_plural': 'Stocks',
},
),
]

View File

@ -0,0 +1,43 @@
# Generated by Django 3.1.5 on 2021-01-30 23:01
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('api', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='charity',
name='id',
),
migrations.RemoveField(
model_name='stock',
name='id',
),
migrations.AlterField(
model_name='charity',
name='ein',
field=models.CharField(max_length=50, primary_key=True, serialize=False),
preserve_default=False,
),
migrations.AlterField(
model_name='profile',
name='percentage',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=3, null=True),
),
migrations.AlterField(
model_name='stock',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stocks', to='api.profile'),
),
migrations.AlterField(
model_name='stock',
name='uuid',
field=models.UUIDField(primary_key=True, serialize=False, unique=True),
),
]

View File

@ -1,13 +1,29 @@
from django.contrib.auth.models import User
from django.db import models
from PIL import Image
# Create your models here.
class Charity (models.Model):
ein = models.CharField(primary_key=True, max_length=50)
name = models.CharField(max_length=50, blank=True, null=True)
sub_name = models.CharField(max_length=50, blank=True, null=True)
city = models.CharField(max_length=20, blank=True, null=True)
state = models.CharField(max_length=2, blank=True, null=True)
class Meta:
verbose_name_plural = "Charities"
def __str__(self):
return self.name
class Profile (models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
charity = models.OneToOneField(Charity, blank=True, null=True, on_delete=models.DO_NOTHING)
nickname = models.CharField(max_length=20, blank=True, null=True)
profile_pic = models.ImageField(default='default.jpg', upload_to='profile_pics')
charity = models.OneToOneField(Charity, blank=True, null=True)
percentage = models.DecimalField(max_digits=3, decimal_places=2)
percentage = models.DecimalField(max_digits=3, decimal_places=2, blank=True, null=True)
def __str__(self):
return f'{self.user.username}\'s profile'
@ -22,22 +38,18 @@ class Profile (models.Model):
img.thumbnail(size)
img.save(self.profile_pic.path)
class Charity (models.Model):
ein = models.CharField(max_length=50, blank=True, null=True)
name = models.CharField(max_length=50, blank=True, null=True)
sub_name = models.CharField(max_length=50, blank=True, null=True)
city = models.CharField(max_length=20, blank=True, null=True)
state = models.CharField(max_length=2, blank=True, null=True)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Profiles"
class Stock (models.Model):
user = models.ForeignKey(Profile, related_name='stock', on_delete=models.CASCADE)
user = models.ForeignKey(Profile, related_name='stocks', on_delete=models.CASCADE)
ticker = models.CharField(max_length=5, blank=True, null=True)
buy_price = models.DecimalField(max_digits=9, decimal_places=2)
quantity = models.DecimalField(max_digits=9, decimal_places=2)
uuid = models.UUIDField(editable=False, unique=True)
uuid = models.UUIDField(primary_key=True, unique=True)
class Meta:
verbose_name_plural = "Stocks"
def __str__(self):
return f'{self.user.user.username}\'s Stock: {self.ticker} @ {self.buy_price}'

View File

@ -1,7 +1,56 @@
from rest_framework import serializers
from django.contrib.auth.models import User
from . import models
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name')
fields = ('username', 'email', 'first_name', 'last_name')
class UserCreateSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
print("I WAS CALLED!")
user = User.objects.create(
username = validated_data['username'],
email = validated_data['email'],
first_name = validated_data['first_name'],
last_name = validated_data['last_name']
)
user.set_password(validated_data['password'])
user.save()
profile = models.Profile.objects.create(user=user)
profile.save()
return user
class Meta:
model = User
fields = ('username', 'password', 'email', 'first_name', 'last_name')
class StockSerializer(serializers.ModelSerializer):
class Meta:
model = models.Stock
fields = ('ticker', 'buy_price', 'quantity', 'uuid')
class StockCreateSerializer(serializers.ModelSerializer):
class Meta:
model = models.Stock
fields = ('user', 'ticker', 'buy_price', 'quantity', 'uuid')
class CharitySerializer(serializers.ModelSerializer):
class Meta:
model = models.Charity
fields = ('ein', 'name', 'sub_name', 'city', 'state')
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
charity = CharitySerializer()
stocks = StockSerializer(many=True)
class Meta:
model = models.Profile
fields = ('user', 'charity', 'nickname', 'profile_pic', 'percentage', 'stocks')

View File

@ -1,8 +1,16 @@
from rest_framework_jwt.views import obtain_jwt_token
from django.urls import path
from . import views
from django.urls import path, include
from rest_framework.routers import DefaultRouter
urlpatterns = [
path('token/', obtain_jwt_token)
path('charity/<int:pk>', views.CharityViewSet.as_view({'get': 'retrieve'})),
path('charity', views.CharityViewSet.as_view({'get': 'list', 'post': 'create'})),
path('stock/<uuid:pk>', views.StockViewSet.as_view({'get': 'retrieve'})),
path('stock', views.StockViewSet.as_view({'get': 'list', 'post': 'create'})),
path('profile/create', views.UserProfileCreate.as_view()),
path('profile', views.UserProfileDetail.as_view()),
path('token', obtain_jwt_token)
]

View File

@ -1,3 +1,51 @@
from django.shortcuts import render
from django.shortcuts import render, get_object_or_404
from django.http import QueryDict
# Create your views here.
from rest_framework import status, permissions
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from rest_framework.generics import CreateAPIView
from rest_framework.response import Response
from .models import *
from .serializers import *
class CharityViewSet(ModelViewSet):
queryset = models.Charity.objects.all()
serializer_class = CharitySerializer
class StockViewSet(ModelViewSet):
queryset = ''
serializer_class = StockSerializer
def list(self, request, *args, **kwargs):
queryset = request.user.profile.stocks.all()
serializer = StockSerializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def retrieve(self, request, pk=None, *args, **kwargs):
queryset = request.user.profile.stocks.filter(uuid=pk)
if queryset.count() != 1:
return Response({"message": "Stock not found."}, status=status.HTTP_404_NOT_FOUND)
serializer = StockSerializer(queryset.first())
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request, *args, **kwargs):
data = QueryDict.copy(request.data)
data.update({'user': request.user})
serializer = StockCreateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
class UserProfileDetail(APIView):
def get(self, request, format=None):
profile = request.user.profile
serializer = ProfileSerializer(profile)
return Response(serializer.data, status=status.HTTP_200_OK)
class UserProfileCreate(CreateAPIView):
model = User
permission_classes = [permissions.AllowAny]
serializer_class = UserCreateSerializer

View File

@ -27,12 +27,12 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.getenv("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv("DEBUG")
DEBUG = os.getenv("DEBUG") == 'True'
if DEBUG:
ALLOWED_HOSTS = ["*"]
else:
ALLOWED_HOSTS = ["reinvest.online"]
ALLOWED_HOSTS = ["api.reinvest.space"]
# Application definition
@ -54,6 +54,7 @@ MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@ -62,6 +63,8 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
@ -152,4 +155,7 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

View File

@ -13,10 +13,15 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path
from django.urls import path, include
urlpatterns = [
path('', include('api.urls'))
path('api/', include('api.urls')),
path('admin/', admin.site.urls),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)