From acba7b111d3ce3a124fa5218fd7607ca8357cdde Mon Sep 17 00:00:00 2001 From: Rushil Umaretiya Date: Sat, 16 Apr 2022 21:04:25 -0400 Subject: [PATCH] feat(backend): added database models and API views --- server/Pipfile | 16 ++++ server/Pipfile.lock | 92 +++++++++++++++++++ server/api/__init__.py | 0 server/api/admin.py | 9 ++ server/api/apps.py | 6 ++ server/api/migrations/0001_initial.py | 67 ++++++++++++++ server/api/migrations/0002_consumer_wallet.py | 20 ++++ .../migrations/0003_alter_consumer_wallet.py | 19 ++++ server/api/migrations/__init__.py | 0 server/api/models.py | 50 ++++++++++ server/api/serializers.py | 62 +++++++++++++ server/api/tests.py | 3 + server/api/urls.py | 10 ++ server/api/views.py | 38 ++++++++ server/config/.env.sample | 2 + server/config/settings.py | 47 ++++++++-- server/config/urls.py | 3 +- 17 files changed, 434 insertions(+), 10 deletions(-) create mode 100644 server/Pipfile create mode 100644 server/Pipfile.lock create mode 100644 server/api/__init__.py create mode 100644 server/api/admin.py create mode 100644 server/api/apps.py create mode 100644 server/api/migrations/0001_initial.py create mode 100644 server/api/migrations/0002_consumer_wallet.py create mode 100644 server/api/migrations/0003_alter_consumer_wallet.py create mode 100644 server/api/migrations/__init__.py create mode 100644 server/api/models.py create mode 100644 server/api/serializers.py create mode 100644 server/api/tests.py create mode 100644 server/api/urls.py create mode 100644 server/api/views.py create mode 100644 server/config/.env.sample diff --git a/server/Pipfile b/server/Pipfile new file mode 100644 index 0000000..2f290e1 --- /dev/null +++ b/server/Pipfile @@ -0,0 +1,16 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +django = "*" +gunicorn = "*" +python-dotenv = "*" +djangorestframework = "*" +django-cors-headers = "*" + +[dev-packages] + +[requires] +python_version = "3.9" diff --git a/server/Pipfile.lock b/server/Pipfile.lock new file mode 100644 index 0000000..ff4f634 --- /dev/null +++ b/server/Pipfile.lock @@ -0,0 +1,92 @@ +{ + "_meta": { + "hash": { + "sha256": "6b09e5def16da359280b38cfa9690ef7fb07897b5befd3c0f5b16de35d664690" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "asgiref": { + "hashes": [ + "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0", + "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9" + ], + "markers": "python_version >= '3.7'", + "version": "==3.5.0" + }, + "django": { + "hashes": [ + "sha256:07c8638e7a7f548dc0acaaa7825d84b7bd42b10e8d22268b3d572946f1e9b687", + "sha256:4e8177858524417563cc0430f29ea249946d831eacb0068a1455686587df40b5" + ], + "index": "pypi", + "version": "==4.0.4" + }, + "django-cors-headers": { + "hashes": [ + "sha256:a22be2befd4069c4fc174f11cf067351df5c061a3a5f94a01650b4e928b0372b", + "sha256:eb98389bf7a2afc5d374806af4a9149697e3a6955b5a2dc2bf049f7d33647456" + ], + "index": "pypi", + "version": "==3.11.0" + }, + "djangorestframework": { + "hashes": [ + "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee", + "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa" + ], + "index": "pypi", + "version": "==3.13.1" + }, + "gunicorn": { + "hashes": [ + "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", + "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" + ], + "index": "pypi", + "version": "==20.1.0" + }, + "python-dotenv": { + "hashes": [ + "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f", + "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938" + ], + "index": "pypi", + "version": "==0.20.0" + }, + "pytz": { + "hashes": [ + "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", + "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" + ], + "version": "==2022.1" + }, + "sqlparse": { + "hashes": [ + "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae", + "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d" + ], + "markers": "python_version >= '3.5'", + "version": "==0.4.2" + }, + "tzdata": { + "hashes": [ + "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9", + "sha256:8b536a8ec63dc0751342b3984193a3118f8fca2afe25752bb9b7fffd398552d3" + ], + "markers": "sys_platform == 'win32'", + "version": "==2022.1" + } + }, + "develop": {} +} diff --git a/server/api/__init__.py b/server/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/api/admin.py b/server/api/admin.py new file mode 100644 index 0000000..11ac057 --- /dev/null +++ b/server/api/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from . import models + +# Register your models here. +admin.site.register(models.Consumer) +admin.site.register(models.Foundation) +admin.site.register(models.Wallet) +admin.site.register(models.FoundationOrder) \ No newline at end of file diff --git a/server/api/apps.py b/server/api/apps.py new file mode 100644 index 0000000..66656fd --- /dev/null +++ b/server/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'api' diff --git a/server/api/migrations/0001_initial.py b/server/api/migrations/0001_initial.py new file mode 100644 index 0000000..def98ba --- /dev/null +++ b/server/api/migrations/0001_initial.py @@ -0,0 +1,67 @@ +# Generated by Django 4.0.4 on 2022-04-16 20:35 + +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='Consumer', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('access_token', models.CharField(max_length=100)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'Consumers', + }, + ), + migrations.CreateModel( + name='Foundation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('description', models.TextField()), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'Foundations', + }, + ), + migrations.CreateModel( + name='Wallet', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('address', models.CharField(max_length=100)), + ], + options={ + 'verbose_name_plural': 'Wallets', + }, + ), + migrations.CreateModel( + name='FoundationOrder', + fields=[ + ('price', models.DecimalField(decimal_places=2, max_digits=9)), + ('uuid', models.UUIDField(primary_key=True, serialize=False, unique=True)), + ('consumer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='foundation_orders', to='api.consumer')), + ('foundation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='foundation_orders', to='api.foundation')), + ], + options={ + 'verbose_name_plural': 'Foundation Orders', + }, + ), + migrations.AddField( + model_name='foundation', + name='wallet', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.wallet'), + ), + ] diff --git a/server/api/migrations/0002_consumer_wallet.py b/server/api/migrations/0002_consumer_wallet.py new file mode 100644 index 0000000..cc6bf8a --- /dev/null +++ b/server/api/migrations/0002_consumer_wallet.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0.4 on 2022-04-16 20:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='consumer', + name='wallet', + field=models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='api.wallet'), + preserve_default=False, + ), + ] diff --git a/server/api/migrations/0003_alter_consumer_wallet.py b/server/api/migrations/0003_alter_consumer_wallet.py new file mode 100644 index 0000000..1c3064d --- /dev/null +++ b/server/api/migrations/0003_alter_consumer_wallet.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.4 on 2022-04-16 21:02 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0002_consumer_wallet'), + ] + + operations = [ + migrations.AlterField( + model_name='consumer', + name='wallet', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.wallet'), + ), + ] diff --git a/server/api/migrations/__init__.py b/server/api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/api/models.py b/server/api/models.py new file mode 100644 index 0000000..700b770 --- /dev/null +++ b/server/api/models.py @@ -0,0 +1,50 @@ +from django.contrib.auth.models import User + +from django.db import models + +# Create your models here. +class Wallet (models.Model): + address = models.CharField(max_length=100) + + def __str__(self): + return self.address[0:4] + # return f'{self.consumer.user.username}\'s wallet' + + class Meta: + verbose_name_plural = "Wallets" + +class Consumer (models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + wallet = models.OneToOneField(Wallet, null=True, blank=True, on_delete=models.CASCADE) + access_token = models.CharField(max_length=100) + + def __str__(self): + return f'{self.user.username}\'s profile' + + class Meta: + verbose_name_plural = "Consumers" + +class Foundation (models.Model): + wallet = models.OneToOneField(Wallet, on_delete=models.CASCADE) + user = models.OneToOneField(User, on_delete=models.CASCADE) + name = models.CharField(max_length=100) + description = models.TextField() + + def __str__(self): + return f'{self.user.username}\'s foundation' + + class Meta: + verbose_name_plural = "Foundations" + +class FoundationOrder (models.Model): + consumer = models.ForeignKey(Consumer, related_name='foundation_orders', on_delete=models.CASCADE) + foundation = models.ForeignKey(Foundation, related_name='foundation_orders', on_delete=models.CASCADE) + + price = models.DecimalField(max_digits=9, decimal_places=2) + uuid = models.UUIDField(primary_key=True, unique=True) + + class Meta: + verbose_name_plural = "Foundation Orders" + + def __str__(self): + return f'{self.consumer.user.username}\'s Order to {self.foundation.name} for {self.price}' \ No newline at end of file diff --git a/server/api/serializers.py b/server/api/serializers.py new file mode 100644 index 0000000..bfb063b --- /dev/null +++ b/server/api/serializers.py @@ -0,0 +1,62 @@ +from operator import mod +from django.contrib.auth.models import User +from rest_framework import serializers + +from . import models + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ('username', 'email', 'first_name', 'last_name') + +class ConsumerCreateSerializer(serializers.ModelSerializer): + password = serializers.CharField(write_only=True) + + def create(self, validated_data): + user = User.objects.create_user(**validated_data) + user.set_password(validated_data['password']) + user.save() + + consumer = models.Consumer.objects.create(user=user) + consumer.save() + + return user + + class Meta: + model = models.User + fields = ('username', 'password', 'email', 'first_name', 'last_name') + +class FoundationSerializer(serializers.ModelSerializer): + user = UserSerializer() + + class Meta: + model = models.Foundation + fields = ('user', 'name', 'description') + + +class ConsumerOrderSerializer(serializers.ModelSerializer): + foundation = FoundationSerializer() + + class Meta: + model = models.FoundationOrder + fields = ('price', 'uuid', 'foundation') + + +class ConsumerSerializer(serializers.ModelSerializer): + user = UserSerializer() + + class Meta: + model = models.Consumer + fields = ('user', 'access_token', 'foundation_orders') + +class FoundationOrderSerializer(serializers.ModelSerializer): + consumer = ConsumerSerializer() + + class Meta: + model = models.FoundationOrder + fields = ('price', 'uuid', 'consumer') + +class FoundationOrderCreateSerializer(serializers.ModelSerializer): + class Meta: + model = models.FoundationOrder + fields = ('price', 'uuid', 'foundation') diff --git a/server/api/tests.py b/server/api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/api/urls.py b/server/api/urls.py new file mode 100644 index 0000000..4e2a81e --- /dev/null +++ b/server/api/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from . import views + + +urlpatterns = [ + path('profile/create', views.ConsumerCreate.as_view()), + path('profile/', views.ConsumerDetail.as_view()), + path('foundation/', views.FoundationViewSet.as_view({'get': 'list', 'post': 'create'})), + path('foundation//', views.FoundationViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})), +] \ No newline at end of file diff --git a/server/api/views.py b/server/api/views.py new file mode 100644 index 0000000..83acf9a --- /dev/null +++ b/server/api/views.py @@ -0,0 +1,38 @@ +from django.shortcuts import render + +from .models import * +from .serializers import * + +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 + +# Create your views here. +class FoundationViewSet(ModelViewSet): + queryset = models.Foundation.objects.all() + serializer_class = FoundationSerializer + +class ConsumerOrderViewSet(ModelViewSet): + queryset = '' + serializerClass = ConsumerOrderSerializer + + def list(self, request, *args, **kwargs): + queryset = request.user.consumer.foundation_orders.all() + serializer = ConsumerOrderSerializer(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class ConsumerCreate(CreateAPIView): + model = User + permission_classes = [permissions.AllowAny] + serializer_class = ConsumerCreateSerializer + +class ConsumerDetail(APIView): + def get(self, request, format=None): + profile = request.user.consumer + serializer = ConsumerSerializer(profile) + return Response(serializer.data, status=status.HTTP_200_OK) + diff --git a/server/config/.env.sample b/server/config/.env.sample new file mode 100644 index 0000000..a2829a4 --- /dev/null +++ b/server/config/.env.sample @@ -0,0 +1,2 @@ +SECRET_KEY=notsosecret +DEBUG=True \ No newline at end of file diff --git a/server/config/settings.py b/server/config/settings.py index 1c25b53..97ccd5f 100644 --- a/server/config/settings.py +++ b/server/config/settings.py @@ -12,6 +12,10 @@ https://docs.djangoproject.com/en/4.0/ref/settings/ from pathlib import Path +import os +from dotenv import load_dotenv +load_dotenv() + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -20,26 +24,36 @@ BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-ioum!dn#uh5t3yn_15j)4qtjp6wyl24-xx*mv6d1kl&wk_-mx1' +SECRET_KEY = str(os.getenv('SECRET_KEY')) # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = str(os.getenv("DEBUG")).lower() == "true" -ALLOWED_HOSTS = [] +if DEBUG: + ALLOWED_HOSTS = ["*"] +else: + ALLOWED_HOSTS = ["api.roundedapp.tech"] # Application definition INSTALLED_APPS = [ + 'api', + 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + + 'corsheaders', + 'rest_framework' ] MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -68,17 +82,30 @@ TEMPLATES = [ ] WSGI_APPLICATION = 'config.wsgi.application' - +CORS_ORIGIN_ALLOW_ALL=True # Database # https://docs.djangoproject.com/en/4.0/ref/settings/#databases -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', +if DEBUG: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } } -} +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'rounded', + 'USER': 'rounded', + 'PASSWORD': 'rounded', + 'HOST': 'localhost', + 'PORT': '5432', + } + } + # Password validation @@ -116,6 +143,8 @@ USE_TZ = True # https://docs.djangoproject.com/en/4.0/howto/static-files/ STATIC_URL = 'static/' +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') + # Default primary key field type # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field diff --git a/server/config/urls.py b/server/config/urls.py index f997b89..f126660 100644 --- a/server/config/urls.py +++ b/server/config/urls.py @@ -14,8 +14,9 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import include, path urlpatterns = [ path('admin/', admin.site.urls), + path('api/', include('api.urls')), ]