diff --git a/backend/api/migrations/0001_initial.py b/backend/api/migrations/0001_initial.py new file mode 100644 index 0000000..634aca6 --- /dev/null +++ b/backend/api/migrations/0001_initial.py @@ -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', + }, + ), + ] diff --git a/backend/api/migrations/0002_auto_20210130_2301.py b/backend/api/migrations/0002_auto_20210130_2301.py new file mode 100644 index 0000000..a96f7a4 --- /dev/null +++ b/backend/api/migrations/0002_auto_20210130_2301.py @@ -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), + ), + ] diff --git a/backend/api/models.py b/backend/api/models.py index 49b02d5..d4c706f 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -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}' diff --git a/backend/api/serializers.py b/backend/api/serializers.py index 82197b8..a59e9e4 100644 --- a/backend/api/serializers.py +++ b/backend/api/serializers.py @@ -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') \ No newline at end of file + 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') diff --git a/backend/api/urls.py b/backend/api/urls.py index d9d8136..77177e7 100644 --- a/backend/api/urls.py +++ b/backend/api/urls.py @@ -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/', views.CharityViewSet.as_view({'get': 'retrieve'})), + path('charity', views.CharityViewSet.as_view({'get': 'list', 'post': 'create'})), + path('stock/', 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) ] diff --git a/backend/api/views.py b/backend/api/views.py index 91ea44a..35896bc 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -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 \ No newline at end of file diff --git a/backend/config/settings.py b/backend/config/settings.py index 1bd9a1b..bdfc8ec 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -153,3 +153,6 @@ USE_TZ = True # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/' + +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +MEDIA_URL = '/media/' \ No newline at end of file diff --git a/backend/config/urls.py b/backend/config/urls.py index e36fd86..672ec29 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -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) \ No newline at end of file